Хуки onRequest и preHandler

Хук onRequest выполняется самым первым в жизненном цикле обработки HTTP-запроса. Его основная задача — перехватить запрос до того, как он попадёт в маршрутизацию, обработчики или систему валидации.

В этот момент доступны только базовые данные запроса: заголовки, метод, URL и низкоуровневые параметры соединения. Это делает onRequest идеальным местом для операций, которые должны происходить максимально рано.

Основные особенности

  • выполняется до маршрутизации
  • не имеет доступа к результату обработки (его ещё не существует)
  • может завершить запрос досрочно
  • поддерживает асинхронные операции
  • часто используется для глобальных проверок

Сигнатура

onRequest(request, reply)

Где:

  • request — объект входящего запроса
  • reply — объект управления ответом

Типовые сценарии использования

1. Логирование входящих запросов

app.onRequest((request, reply) => {
  console.log(`[${request.method}] ${request.url}`);
});

На этом уровне фиксируются все обращения к серверу, включая потенциально невалидные.

2. Предварительная фильтрация IP

const blockedIPs = ['192.168.0.10'];

app.onRequest((request, reply) => {
  const ip = request.ip;

  if (blockedIPs.includes(ip)) {
    reply.code(403).send({ error: 'Forbidden' });
  }
});

Важно: на этом этапе можно полностью остановить обработку запроса.

3. Установка глобальных контекстных данных

app.onRequest((request, reply) => {
  request.startTime = Date.now();
});

Такие данные часто используются позже для метрик и профилирования.

Особенности работы

Хук onRequest не должен содержать тяжёлую бизнес-логику. Его задача — подготовка и первичная проверка запроса. Любые сложные вычисления или обращения к БД здесь ухудшают общую производительность системы.


preHandler

Хук preHandler выполняется после маршрутизации запроса, но до вызова основного обработчика (handler). На этом этапе уже известно, какой endpoint будет вызван, и доступны параметры маршрута, query и body (если они были распарсены).

Основные особенности

  • выполняется после выбора маршрута
  • имеет доступ к параметрам запроса
  • может изменять request перед handler’ом
  • поддерживает асинхронные операции
  • часто используется для авторизации и валидации

Сигнатура

preHandler(request, reply)

Основные сценарии использования

1. Проверка авторизации

app.preHandler((request, reply) => {
  const token = request.headers.authorization;

  if (!token) {
    return reply.code(401).send({ error: 'Unauthorized' });
  }

  request.user = decodeToken(token);
});

Здесь выполняется ключевая проверка доступа к ресурсу.


2. Валидация бизнес-правил

app.preHandler(async (request, reply) => {
  const { id } = request.params;

  const exists = await db.users.exists(id);

  if (!exists) {
    return reply.code(404).send({ error: 'User not found' });
  }
});

В отличие от схемной валидации, здесь можно проверять данные через внешние системы.


3. Модификация запроса перед обработчиком

app.preHandler((request) => {
  request.meta = {
    locale: request.headers['accept-language'] || 'en'
  };
});

Это позволяет централизованно добавлять данные, которые будут использоваться в handler.


Разница между onRequest и preHandler

Порядок выполнения

  1. onRequest
  2. маршрутизация
  3. preHandler
  4. handler

Ключевые отличия

onRequest

  • работает с «сырым» запросом
  • не знает маршрут
  • используется для глобальной логики
  • идеален для логирования и блокировок

preHandler

  • работает уже с выбранным маршрутом
  • имеет доступ к параметрам
  • используется для бизнес-логики
  • подходит для авторизации и проверки данных

Влияние на поток обработки

Хуки формируют цепочку, в которой каждый следующий этап зависит от предыдущего. Ошибка или завершение ответа в любом из них останавливает дальнейшее выполнение.

Request → onRequest → Router → preHandler → Handler → Response

Если в onRequest возвращён ответ, preHandler и handler не выполняются.


Асинхронное поведение

Оба хука поддерживают async функции, что позволяет выполнять операции с сетью или базами данных.

app.preHandler(async (request, reply) => {
  const session = await redis.get(request.headers.token);

  if (!session) {
    reply.code(401).send();
  }
});

Важно учитывать, что задержки в этих хуках напрямую увеличивают latency запроса.


Обработка ошибок

Ошибка, выброшенная в onRequest, блокирует дальнейшую обработку запроса сразу.

app.onRequest(() => {
  throw new Error('Critical failure');
});

Аналогично, ошибка в preHandler предотвращает вызов handler.


Рекомендации по архитектуре использования

onRequest

  • логирование
  • rate limiting
  • ранний отказ (IP, заголовки)
  • установка метаданных

preHandler

  • аутентификация
  • авторизация
  • проверка существования ресурсов
  • обогащение запроса данными

Частые ошибки при использовании

Перегрузка onRequest

Размещение сложной логики в onRequest приводит к ухудшению производительности всей системы, так как он выполняется для каждого запроса вне зависимости от маршрута.

Дублирование логики

Если проверка авторизации дублируется в onRequest и preHandler, это создаёт избыточность и усложняет сопровождение кода.

Блокирующие операции

Синхронные тяжёлые операции в хуках увеличивают время ответа и могут блокировать event loop.


Контекст запроса

И onRequest, и preHandler часто используются для обогащения объекта request, чтобы передавать данные дальше по цепочке:

app.onRequest((request) => {
  request.traceId = generateTraceId();
});

app.preHandler((request) => {
  request.userContext = getUserContext(request);
});

Так формируется единый контекст запроса, который затем используется в handler и логировании.


Поведение при завершении ответа

Если в любом из хуков вызывается:

reply.send()

или устанавливается код ответа, последующие этапы обработки не выполняются. Это позволяет реализовывать быстрые выходы без загрузки маршрутов и бизнес-логики.


Композиция хуков

Хуки могут быть объявлены глобально или на уровне конкретного маршрута:

app.get('/profile', {
  preHandler: (request, reply) => {
    if (!request.user) {
      reply.code(401).send();
    }
  }
}, handler);

Такой подход позволяет строить модульную систему обработки запросов с переиспользуемыми слоями логики.