В этом уроке мы научимся использовать мидлвары. Мидлвара — это функция, которая выполняется перед обработкой запроса сервером. Она может выполнять различные задачи, такие как проверка прав доступа, логирование или преобразование данных. В этом уроке мы рассмотрим, как создавать и использовать мидлвары в Fastify для улучшения производительности и безопасности вашего приложения.
Оборачивание
Прежде чем перейти к изучению непосредственно мидлвар, разберем их принцип действия на примере обычных функций.
Представим, что у нас есть функция, которая прибавляет 5 к аргументу:
const plusFive = x => x + 5
Наша задача расширить функционал этой функции, при этом не меняя саму функцию. Например, мы хотим прибавлять 5 к удвоенному значению аргумента. Чтобы решить эту задачу, мы можем написать новую функцию, которая будет внутри себя использовать уже существующую функцию:
const doublePlusFive = x => plusFive(x * 2)
Точно так же мы можем расширить и вторую функцию:
const nextFunc = x => doublePlusFive(x - 10) - 3
nextFunc(20) // ((((20 - 10) * 2) + 5) - 3) = 22
В общем случае расширение функции выглядит так:
const nextF = (/* args */) => {
// preprocessing
const result = prevF(/* updatedArgs */)
// afterprocessing
return /* newResult */
}
Чтобы расширить поведение функции, нужно написать новую функцию, в которой используется первоначальная. Единственное условие, которое нужно соблюсти, это совпадение интерфейса этих функций: количество, тип аргументов и возвращаемого результата должны совпадать. В таком случае код, использующий вашу обёрнутую функцию, даже не сможет догадаться о том, что она обёрнута, ну а главное, что его не нужно переписывать, ведь интерфейс функции не поменялся, хотя и появилось новое поведение. Такой способ так же называют декорированием, и в справочниках по шаблонам проектирования описывают как "паттерн декоратор".
По похожей идее работают мидлвары. Они расширяют функционал обработчиков запросов, оборачивая их в подобные функции.
Чтобы мидлвары заработали, к приложению нужно подключить плагин @fastify/middie
или @fastify/express
с помощью метода register()
.
Установка плагина:
npm i @fastify/middie
Пример кода с подключением:
import fastify from 'fastify'
import middie from '@fastify/middie'
import morgan from 'morgan'
const app = fastify()
const port = 3000
await app.register(middie)
app.use((req, res) => {
res.end('Hello from middleware!')
})
app.listen({ port }, () => {
console.log(`Example app listening on port ${port}`)
})
В примере выше подключена мидлвара, которая возвращает текст 'Hello from middleware!'. Далее мы разберем более подробно как это работает.
Middleware
Плагин предоставляет механизм, который расширяет наше приложение функциями, называемыми middleware
. Каждый раз, когда мы используем use
, очередная middleware
добавляется в общую очередь. В конечном счёте получается объект, наполненный мидлварами. Каждый запрос, отправляемый на обработку в приложение, проходит через цепочку этих middleware
пока не наткнётся на терминальную мидлвару.
В свою очередь каждая мидлвара принимает на вход три параметра: request
, response
и next
. Она может поменять их и в конце должна вызвать next
для передачи управления следующей по списку мидлваре. В этом и заключается вся мощь микрофреймворков. Удачный дизайн позволяет легко разбивать систему на модули-мидлвары и расширять за счёт мидлвар, которые, в большом количестве, пишут сторонние разработчики.
app.use((req, res, next) => {
res.setHeader('key', 'value')
next()
})
В примере выше мидлвара добавляет заголовок в каждый запрос.
Mount middleware
Самое интересное в мидлварах, что обработчики конкретных маршрутов — это тоже, всего-навсего, мидлвары. Их особенностью является привязка к конкретному маршруту, в отличие от мидлвар, которые выполняются для всех запросов.
app.use('/foo', (req, res, next) => {
// req.url starts with "/foo"
next()
})
app.use('/bar', (req, res, next) => {
// req.url starts with "/bar"
next()
})
Такие мидлвары позволяют реализовывать базовый роутинг без привязки к конкретному глаголу http и без поддержки динамических маршрутов.
Terminate
Но далеко не всегда мы хотим двигаться вглубь. Более того, в какой-то момент одна из мидлвар должна взять обработку на себя.
app.use((req, res) => {
res.end('Hello from first!')
})
app.use((req, res) => {
res.send('Hello from second!')
})
У такого поведения, когда есть цепочка функций и любая из них в процессе обработки может принять решение остановки цепочки и возврата ответа, есть имя. Такие цепочки называют chain responsibility
, и это тоже паттерн.
Библиотеки
Многие библиотеки, добавляющие функционал в приложение, работают с помощью мидлвар. Разберем пример на библиотеке morgan. Это библиотека для логирования запросов. Перед ее использованием установим пакет:
npm i morgan
Пример кода:
import fastify from 'fastify'
import middie from '@fastify/middie'
import morgan from 'morgan'
const app = fastify()
const port = 3000
const logger = morgan('combined')
await app.register(middie)
app.use(logger)
app.listen({ port }, () => {
console.log(`Example app listening on port ${port}`)
})
Выше в примере мы создали функцию логирования с помощью функции morgan()
и затем передали его в качестве мидлвары. Теперь на каждый запрос будет появляться подробный лог в приложении. Например:
Example app listening on port 3000
127.0.0.1 - - [18/Apr/2024:08:15:59 +0000] "GET / HTTP/1.1" 404 72 "-" "Mozilla/5.0 (X11; Linux x86_64; rv:124.0) Gecko/20100101 Firefox/124.0"
Для полного доступа к курсу нужен базовый план
Базовый план откроет полный доступ ко всем курсам, упражнениям и урокам Хекслета, проектам и пожизненный доступ к теории пройденных уроков. Подписку можно отменить в любой момент.