js-trie-router

0

Описание

HTTP маршрутизатор для Node.js на основе префиксного дерева

Языки

  • JavaScript100%
README.md

@e22m4u/js-trie-router

npm version license

HTTP маршрутизатор для Node.js на основе префиксного дерева (trie).

  • Поддержка path-to-regexp синтаксиса.
  • Автоматический парсинг JSON-тела запроса.
  • Парсинг строки запроса и заголовка
    Cookie
    .
  • Поддержка
    preHandler
    и
    postHandler
    хуков.
  • Позволяет использовать асинхронные обработчики.
  • Поддержка ветвления маршрутов с общим префиксом.

Содержание

Установка

Требуется Node.js 16 и выше.

Модуль поддерживает ESM и CommonJS стандарты.

ESM

CommonJS

Расширения

Расширение функционала выполняется с помощью NPM модулей.

модульописание
@e22m4u/js-trie-router-corsМодуль поддержки CORS (кросс-доменные запросы)
@e22m4u/js-trie-router-openapiМодуль для создания OpenAPI документа

Использование

Базовый пример создания экземпляра маршутизатора, объявления маршрута и передачи слушателя запросов HTTP серверу.

i. Для указания метода запроса рекомендуется использовать константу

HttpMethod
, чтобы избежать опечаток.

Параметры маршрутизатора

Конструктор класса

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

При работе с глобальным сервис-контейнером, объект с параметрами необходимо передавать вторым аргументом, как это показано ниже.

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

Первый параметр обработчика маршрута принимает экземпляр класса

RequestContext
с набором свойств, содержащих разобранные данные входящего запроса.

  • params: ParsedParams
    объект ключ-значение с параметрами пути;
  • query: ParsedQuery
    объект ключ-значение с параметрами строки запроса;
  • headers: ParsedHeaders
    объект ключ-значение с заголовками запроса;
  • cookies: ParsedCookies
    объект ключ-значение разобранного заголовка
    Cookie
    ;
  • method: HttpMethod
    метод запроса в верхнем регистре, например
    GET
    ,
    POST
    и т.д.;
  • path: string
    путь включающий строку запроса, например
    /myPath?foo=bar
    ;
  • pathname: string
    путь запроса, например
    /myPath
    ;
  • body: unknown
    тело запроса;

Дополнительные свойства:

  • container: ServiceContainer
    экземпляр сервис-контейнера;
  • request: IncomingMessage
    нативный поток входящего запроса;
  • response: ServerResponse
    нативный поток ответа сервера;
  • route: Route
    экземпляр текущего маршрута;
  • meta: object
    геттер для доступа к метаданным маршрута (
    route.meta
    );
  • state: object
    объект для обмена данными между хуками и обработчиком;

Пример доступа к контексту из обработчика маршрута.

Отправка ответа

Возвращаемое значение обработчика маршрута используется в качестве ответа сервера. Тип значения влияет на представление возвращаемых данных. Например, если результатом будет являться тип

object
, то такое значение автоматически сериализуется в JSON.

valuecontent-type
string
text/plain
number
application/json
boolean
application/json
object
application/json
Buffer
application/octet-stream
Stream
application/octet-stream

Пример возвращаемого значения обработчиком маршрута.

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

ctx
содержит нативный экземпляр класса
ServerResponse
модуля
http
, который может быть использован для ручного управления ответом.

Парсинг тела запроса

Для разбора тела входящего запроса отслеживается заголовок

Content-Type
, определяющий формат передаваемых данных. По умолчанию маршрутизатор включает парсеры для следующих форматов:

  • application/json
    разбирается как JSON;
  • text/plain
    преобразуется в строку;

Если входящий запрос содержит данные, но для его формата не найден подходящий парсер, маршрутизатор прервет обработку запроса и вернет ошибку 415 Unsupported Media Type.

Чтобы избежать появления ошибки для форматов, которые предполагается обрабатывать особым способом, предусмотрен параметр маршрутизатора

ignoredMediaTypes
для игнорирования указанных медиа-типов. Параметр позволяет пропустить встроенный парсинг, как это сделано в примере ниже.

Регистрация пользовательского парсера

Для расширения поддерживаемых форматов предусмотрена возможность регистрации пользовательской функции парсинга для определенного медиа-типа. Управление такими функциями выполняется сервисом

RequestBodyParser
, который доступен через глобальный контейнер маршрутизатора.

Регистрируемая функция принимает извлеченные данные в виде строки и возвращает преобразованный результат. Итоговое значение впоследствии будет доступно в контексте обработки запроса.

Жизненный цикл

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

На этапе запуска приложения

  1. Глобальные хуки
    onDefineRoute
    .
    - Вызываются при регистрации маршрута.

При получении входящего HTTP-запроса

  1. Глобальные хуки

    .
    - Вызываются до поиска маршрута и разбора входящих данных.

  2. Поиск маршрута.
    - Если маршрут не найден, отправляется ответ

    404
    , а дальнейшая обработка прерывается.

  3. Создание

    RequestContext
    и парсинг.
    - Создается экземпляр контекста, разбирается строка запроса, заголовки и извлекается тело запроса.

  4. Глобальные хуки

    .
    - Вызываются последовательно. Если хук возвращает значение или отправляет ответ, цикл прерывается.

  5. Хуки маршрута

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

  6. Основной обработчик (функция

    handler
    ).
    - Обработчик запроса вызывается для формирования ответа сервера.

  7. Хуки маршрута

    .
    - Получают результат обработчика и могут трансформировать его перед отправкой.

  8. Глобальные хуки

    .
    - Завершают цепочку трансформации ответа.

  9. Отправка ответа.
    - Маршрутизатор сериализует итоговые данные, устанавливает заголовки и отправляет клиенту.

Процесс обработки запроса обернут в глобальный

try/catch
блок. Любая ошибка, выброшенная на любом этапе (в хуках, при разборе тела или в самом обработчике), будет перехвачена и передана в
RouterErrorSender
для формирования ответа с ошибкой.

Хуки маршрута

Определение маршрута методом

defineRoute
позволяет задать хуки для отслеживания и перехвата входящего запроса и ответа конкретного маршрута.

  • preHandler
    выполняется перед вызовом обработчика;
  • postHandler
    выполняется после вызова обработчика;

preHandler (для маршрута)

Перед вызовом обработчика маршрута может потребоваться выполнение таких операции как авторизация и проверка параметров запроса. Для этого можно использовать хук

preHandler
.

Если хук

preHandler
возвращает значение отличное от
undefined
, то такое значение будет использовано в качестве ответа сервера, а вызов следующих хуков и основного обработчика маршрута будет прерван.

Допускается определение множества хуков

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

Кроме возвращаемого значения, маршрутизатор отслеживает состояние отправки ответа через экземпляр

ServerResponse
. Если сервер уже отправил ответ, то вызов следующих хуков и основного обработчика прерывается.

postHandler (для маршрута)

Данный хук выполняется после вызова основного обработчика маршрута (или после

preHandler
, если тот завершил запрос досрочно). Его главной задачей является перехват и трансформация данных перед отправкой клиенту. Хук принимает контекст запроса первым аргументом, а вторым данные для отправки.

В отличие от

preHandler
, хуки
postHandler
работают по принципу конвейера. Значение, возвращаемое хуком (если оно отлично от
undefined
), автоматически заменяет собой текущие данные. Обновленный результат передается следующему зарегистрированному хуку.

Единственным условием для досрочного прерывания вызова

postHandler
хуков является принудительная отправка HTTP-ответа внутри самого хука с использованием нативного объекта
ctx.response
. В таком случае выполнение оставшихся хуков прерывается.

Глобальные хуки

Экземпляр маршрутизатора

TrieRouter
позволяет задавать глобальные хуки, которые выполняются на различных этапах жизненного цикла.

  • onDefineRoute
    выполняется перед регистрацией маршрута;
  • onRequest
    выполняется при получении входящего HTTP-запроса;
  • preHandler
    выполняется перед вызовом обработчика каждого маршрута;
  • postHandler
    выполняется после вызова обработчика каждого маршрута;

Добавить глобальные хуки можно методом маршрутизатора

addHook
.

onDefineRoute

Перед регистрацией каждого маршрута выполняются хуки

onDefineRoute
. Данный хук может быть только синхронным. В первый аргумент вызова передается копия определения маршрута, а во второй экземпляр сервис-контейнера.

Возвращаемым значением данного хука может быть модифицированное определение маршрута, либо

undefined
. Чтобы изменения параметров маршрута были учтены маршрутизатором, требуется передать новое определение в качестве результата.

onRequest

Глобальный хук

onRequest
выполняется самым первым при получении входящего запроса. В этот момент маршрутизатор еще не начал поиск подходящего маршрута, не разобрал тело запроса и не создавал
RequestContext
(контекст запроса).

Хук принимает три аргумента:

  • request: IncomingMessage
    нативный экземпляр запроса;
  • response: ServerResponse
    нативный экземпляр ответа;
  • container: ServiceContainer
    сервис-контейнер приложения;

Это идеальное место для установки общих CORS-заголовков, раннего логирования или блокировки нежелательных запросов (например, по IP).

Если хук отправляет ответ клиенту (например, вызывает

res.end()
) или явно возвращает логическое значение
true
, маршрутизатор немедленно прерывает обработку запроса. Поиск маршрута, чтение тела и вызов остальных хуков выполнены не будут.

Если хук

onRequest
возвращает значение, оно обязано быть логическим типом или
undefined
. Также допускается
Promise
, разрешающийся этими значениями. Попытка вернуть строку или объект приведет к выбросу ошибки.

preHandler (глобальный)

Глобальный хук

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

Если глобальный хук

preHandler
возвращает значение отличное от
undefined
, то такое значение будет использовано как ответ сервера. При этом, вызов следующих хуков и основного обработчика маршрута будет прерван.

Кроме возвращаемого значения, маршрутизатор отслеживает состояние отправки ответа через экземпляр

ServerResponse
. Если сервер уже отправил ответ, то вызов следующих хуков и основного обработчика маршрута будет прерван.

postHandler (глобальный)

Глобальный хук

postHandler
работает по такому же принципу, как и одноименный хук на уровне маршрута, но применяется абсолютно ко всем обработанным запросам. Хук принимает контекст запроса первым аргументом, а вторым данные для отправки.

Глобальные хуки

postHandler
позволяют применять трансформацию ко всем ответам маршрутизатора. Это может быть использовано для приведения ответов к единому формату, когда результат работы любого маршрута автоматически оборачивается в стандартизированную структуру с добавлением метаинформации.

Если внутри хука выполнена отправка HTTP-ответа через методы нативного объекта

ctx.response
(например, для перенаправления), то выполнение цепочки хуков немедленно прерывается, и все последующие хуки игнорируются.

Метаданные маршрута

Иногда требуется связать с маршрутом дополнительные, статические данные, которые могут быть использованы хуками для расширения функционала. Например, это могут быть схемы для валидации данных, правила доступа или настройки кэширования. Для этой цели определение маршрута поддерживает необязательное свойство

meta
.

Маршрутизатор передает в контекст запроса найденный маршрут. Контекст, в свою очередь, предоставляет доступ к мета-данным этого маршрута через свойство

meta
, откуда их могут прочитать обработчики или хуки.

Состояние запроса

Объект

ctx.state
инициализируется как пустой объект
{}
для каждого нового запроса. Он предназначен для передачи динамических данных (например, профиля пользователя после авторизации) из
preHandler
хуков в основной обработчик маршрута или
postHandler
хуки.

Ветвление маршрутов

Механизм ветвления позволяет группировать маршруты по общему префиксу пути. Созданная ветка предоставляет методы для объявления маршрутов и создания вложенных веток. Параметры ветки объединяются с параметрами родителя. Пути объединяются, массивы хуков объединяются, а объект метаданных подвергается глубокому слиянию.

Ветки позволяют задавать общие хуки и метаданные для группы маршрутов. Это удобно для реализации проверок авторизации или логирования в рамках определенного раздела приложения.

Допускается создание вложенных веток любой глубины.

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

Маршрутизатор автоматически перехватывает любые ошибки, выброшенные из хуков или обработчика маршрута. По умолчанию, любая ошибка приводит к ответу сервера со статусом 500 Internal Server Error.

Для более гибкого управления HTTP-статусами рекомендуется использовать библиотеку http-errors. Стандартный обработчик ошибок спроектирован для работы с данной библиотекой и автоматически извлекает

statusCode
,
message
и другие свойства ошибки.

Структура ответа будет зависеть от данных, содержащихся в объекте ошибки. Например, первая ошибка из примера выше

HttpErrors.NotFound
приведет к ответу со статусом
404
и телом, содержащим указанное сообщение.

Если объект ошибки содержит дополнительные поля (например,

details
или
code
), они автоматически включаются в ответ. Что позволяет передавать клиенту более детальную информацию.

Перехват и логирование ошибок

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

Если требуется реализовать собственное логирование, то можно переопределить встроенный сервис

RouterErrorSender
. Для этого потребуется унаследовать класс данного сервиса и подменить стандартную реализацию в контейнере маршрутизатора.

При необходимости можно переопределить метод

send404(request, response)
, чтобы отслеживать запросы к несуществующим маршрутам или изменять формат ответа.

Отладка

Установка переменной

DEBUG
включает вывод логов.

Тестирование

Лицензия

MIT