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

e22m4u 359a40ee75 docs: updates README.md 3 weeks ago
.husky ec156b6f74 chore: adds CommonJS support 1 year ago
dist 49a592a060 feat: adds the meta option to RequestContext 3 weeks ago
examples 4aa9d4caa5 fix: pre-handler hooks invocation and request body encoding 2 months ago
src 49a592a060 feat: adds the meta option to RequestContext 3 weeks ago
.c8rc 9c123b7342 chore: initial commit 1 year ago
.commitlintrc 9c123b7342 chore: initial commit 1 year ago
.editorconfig 9c123b7342 chore: initial commit 1 year ago
.gitignore 9c123b7342 chore: initial commit 1 year ago
.mocharc.cjs 9c123b7342 chore: initial commit 1 year ago
.prettierrc 9c123b7342 chore: initial commit 1 year ago
LICENSE b706848c29 chore: updates license 4 months ago
README.md 359a40ee75 docs: updates README.md 3 weeks ago
build-cjs.js 84a81875ee chore: updates esbuild config 1 year ago
eslint.config.js 0e24aa91d8 chore: updates dependencies 2 months ago
package.json 3f507b8eed chore: bumps version to 0.3.5 4 weeks ago
tsconfig.json 9c123b7342 chore: initial commit 1 year ago

README.md

@e22m4u/js-trie-router

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

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

Содержание

Установка

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

npm install @e22m4u/js-trie-router

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

ESM

import {TrieRouter} from '@e22m4u/js-trie-router';

CommonJS

const {TrieRouter} = require('@e22m4u/js-trie-router');

Обзор

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

import http from 'http';
import {TrieRouter} from '@e22m4u/js-trie-router';

const server = new http.Server(); // создание экземпляра HTTP сервера
const router = new TrieRouter();  // создание экземпляра роутера

router.defineRoute({
  method: 'GET',                  // метод запроса "GET", "POST" и т.д.
  path: '/',                      // шаблон пути, пример "/user/:id"
  handler(ctx) {                  // обработчик маршрута
    return 'Hello world!';
  },
});

server.on('request', router.requestListener); // подключение роутера
server.listen(3000, 'localhost');             // прослушивание запросов

// Open in browser http://localhost:3000

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

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

  • container: ServiceContainer экземпляр сервис-контейнера
  • req: IncomingMessage нативный поток входящего запроса
  • res: ServerResponse нативный поток ответа сервера
  • params: ParsedParams объект ключ-значение с параметрами пути
  • query: ParsedQuery объект ключ-значение с параметрами строки запроса
  • headers: ParsedHeaders объект ключ-значение с заголовками запроса
  • cookies: ParsedCookies объект ключ-значение разобранного заголовка Cookie
  • method: string метод запроса в верхнем регистре, например GET, POST и т.д.
  • path: string путь включающий строку запроса, например /myPath?foo=bar
  • pathname: string путь запроса, например /myPath
  • body: unknown тело запроса
  • meta: object мета-данные из определения маршрута

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

router.defineRoute({
  method: 'GET',
  path: '/users/:id',
  meta: {prop: 'value'},
  handler(ctx) {
    // GET /users/10?include=city
    // Cookie: foo=bar; baz=qux;
    console.log(ctx.req);      // IncomingMessage
    console.log(ctx.res);      // ServerResponse
    console.log(ctx.params);   // {id: 10}
    console.log(ctx.query);    // {include: 'city'}
    console.log(ctx.headers);  // {cookie: 'foo=bar; baz=qux;'}
    console.log(ctx.cookies);  // {foo: 'bar', baz: 'qux'}
    console.log(ctx.method);   // "GET"
    console.log(ctx.path);     // "/users/10?include=city"
    console.log(ctx.pathname); // "/users/10"
    console.log(ctx.meta);     // {prop: 'value'}
    // ...
  },
});

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

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

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

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

router.defineRoute({     // регистрация маршрута
  // ...
  handler(ctx) {         // обработчик входящего запроса
    return {foo: 'bar'}; // ответ будет представлен в виде JSON
  },
});

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

router.defineRoute({
  // ...
  handler(ctx) {
    ctx.res.statusCode = 404;
    ctx.res.setHeader('content-type', 'text/plain; charset=utf-8');
    ctx.res.end('404 Not Found', 'utf-8');
  },
});

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

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

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

preHandler

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

router.defineRoute({ // регистрация маршрута
  // ...
  preHandler(ctx) {
    // перед обработчиком маршрута
    console.log(`Incoming request ${ctx.method} ${ctx.path}`);
    // > incoming request GET /myPath
  },
  handler(ctx) {
    return 'Hello world!';
  },
});

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

router.defineRoute({ // регистрация маршрута
  // ...
  preHandler(ctx) {
    // возвращение ответа сервера
    return 'Are you authorized?';
  },
  handler(ctx) {
    // данный обработчик не будет вызван, так как
    // хук "preHandler" уже отправил ответ
  },
});

postHandler

Возвращаемое значение обработчика маршрута передается вторым аргументом хука postHandler. По аналогии с preHandler, если возвращаемое значение отличается от undefined и null, то такое значение будет использовано в качестве ответа сервера. Это может быть полезно для модификации возвращаемого ответа.

router.defineRoute({
  // ...
  handler(ctx) {
    return 'Hello world!';
  },
  postHandler(ctx, data) {
    // после обработчика маршрута
    return data.toUpperCase(); // HELLO WORLD!
  },
});

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

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

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

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

router.addPreHandler((ctx) => {
  // перед обработчиком маршрута
});
router.addPostHandler((ctx, data) => {
  // после обработчика маршрута
});

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

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

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

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

import http from 'http';
import {TrieRouter} from '@e22m4u/js-trie-router';

const server = new http.Server();
const router = new TrieRouter();

// глобальный pre-handler хук, который срабатывает
// перед основным обработчиком каждого маршрута
router.addPreHandler((ctx) => {
  // доступ к мета-данным текущего маршрута
  console.log(ctx.meta); // {foo: 'bar'}
});

router.defineRoute({
  method: 'GET',
  path: '/',
  meta: {foo: 'bar'}, // мета-данные
  handler(ctx) {
    return 'Hello World!';
  },
});

server.on('request', router.requestListener);
server.listen(3000, 'localhost');

Отладка

Установка переменной DEBUG включает вывод логов.

DEBUG=jsTrieRouter* npm run test

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

npm run test

Лицензия

MIT