Fastify: декораторы и жизненный цикл
Плагины из урока 02 дали нам дерево областей. Теперь два вопроса: как
расширять request и reply своими полями (декораторы) и где именно в
обработке запроса можно вклиниться (хуки жизненного цикла). Вместе это
инструмент, которым в Fastify пишут всю сквозную логику: аутентификацию,
логирование, контекст запроса. И всё это уважает инкапсуляцию из урока 02:
хук виден только своей области и потомкам.
1. Декораторы: расширяем инстанс, запрос и ответ
Декоратор это способ добавить поле или метод к одному из трёх объектов
Fastify: инстансу приложения, объекту request или объекту reply.
// на инстанс: общий сервис или конфиг, живёт всё время работы
app.decorate('db', databaseClient);
// на каждый запрос: поле, которое заполняется по ходу обработки
app.decorateRequest('user', null);
// на каждый ответ: хелпер ответа
app.decorateReply('ok', function (payload: unknown) {
return this.send({ ok: true, data: payload });
});
decorate(name, value)кладёт значение на инстанс. Этоsharedсостояние: один объект на всё приложение (или область). Так регистрируют сервисы, мы делали это сdbчерезfpв уроке 02.decorateRequest(name, value)добавляет поле каждомуrequest.decorateReply(name, value)добавляет метод или поле каждомуreply.
1.1 Ловушка: объекты в decorateRequest
Главная тонкость декораторов запроса. Если передать в decorateRequest
объект, он станет общим для всех запросов: Fastify положит одну и ту же
ссылку каждому request, и запросы начнут затирать данные друг друга.
// ОПАСНО: один объект session на все запросы сразу
app.decorateRequest('session', { userId: null });
// ПРАВИЛЬНО: примитив-плейсхолдер, наполняем в хуке
app.decorateRequest('user', null);
Для примитивов (null, число, строка) проблемы нет: их Fastify копирует.
Для объектов нужно либо ставить null и наполнять поле в хуке (см. § 3),
либо использовать фабрику через геттер:
// каждый запрос получит свой свежий объект
app.decorateRequest('session', {
getter() {
return { userId: null };
},
});
Почему так, понятно из урока 06-node про модель памяти: объект это ссылка, и положить одну ссылку всем, значит дать им общий мутабельный стейт.
2. Полный жизненный цикл запроса
Каждый запрос в Fastify проходит фиксированную последовательность стадий, и в большинство из них можно вклиниться хуком. Вот конвейер по порядку:
onRequest запрос принят, тела ещё нет
-> preParsing до парсинга тела (сырой поток)
-> preValidation тело распарсено, до проверки схемой
-> preHandler тело провалидировано (тут обычно auth)
-> handler основная логика роута
-> preSerialization payload до сериализации
-> onSend сериализованный ответ, последний шанс
-> onResponse ответ отправлен (только логи и метрики)
Плюс две ветки в стороне: onError (если где-то брошено исключение) и
onTimeout (если запрос превысил лимит). И отдельный класс,
application-хуки: onReady (приложение готово), onClose (останов),
onRoute (зарегистрирован роут), onRegister (зарегистрирован плагин).
Прокрути запрос по конвейеру: выбери сценарий и жми «шаг». Посмотри, как
успешный путь проходит все стадии, как preHandler с отказом
короткозамыкает на onSend мимо handler, и как брошенная ошибка уводит в
ветку onError.
- onRequest
- preParsing
- preValidation
- preHandler
- handler
- preSerialization
- onSend
- onResponse
- onError (ветка ошибки)
Запрос ещё не вошёл в конвейер.
2.1 Где что делают
Стадии не взаимозаменяемы, у каждой своя роль:
onRequest: самое раннее. Тела ещё нет, поэтому тут дёшево отказать (ранний rate-limit, проверка IP), поставитьreqId, начать таймер.preHandler: тело уже провалидировано. Это каноническое место аутентификации: достать пользователя из токена и положить вrequest.user, при нехватке прав отклонить.preSerialization: payload ещё объект. Удобно обернуть ответ в общий конверт{ data, meta }на уровне области.onSend: тело уже строка или буфер. Последний шанс поменять заголовки или подменить тело.onResponse: ответ уже у клиента, менять нечего. Только метрики и лог длительности.
2.2 Прерывание и порядок
Хук может прервать обработку, отправив ответ через reply.send (или
бросив ошибку). Тогда оставшиеся хуки до handler и сам handler
пропускаются, как в сценарии «preHandler отклоняет» на виджете.
app.addHook('preHandler', async (request, reply) => {
if (!request.headers.authorization) {
reply.code(401).send({ error: 'unauthorized' });
// handler не вызовется: ответ уже отправлен
}
});
В async-хуке для прерывания достаточно return reply после send или
просто throw. Несколько хуков одной стадии выполняются в порядке
регистрации.
дальше после входа
Урок целиком открыт всем, кто вошёл. Войди по своему email, чтобы читать дальше.
Вход пока по приглашению. Если хочешь учиться, напиши Vassiliy.