awesome-everything EN
↑ Обратно к восхождению

Архитектура бэкенда

Маршрутизация и middleware: что выполняется и в каком порядке

Суть Маршрутизация связывает запрос с одним обработчиком; цепочка middleware решает, что выполняется вокруг него. Порядок — не косметика: он определяет, реально ли защищают auth, лимиты тела и сжатие.
Высота — путь к senior
НольJuniorMiddleSenior
Ты на middle-высоте — в небе
◷ 13 min

Security-ревью находит, что один админский эндпоинт принимает неаутентифицированные запросы. Auth-middleware существует, хорошо протестирован и применён — но зарегистрирован после этого маршрута. Обработчик сработал первым. Никто не написал баг; кто-то написал строки в неверном порядке.

Маршрутизация: сопоставление пути с обработчиком

Маршрутизация превращает POST /users/42/orders в один обработчик плюс извлечённые параметры (userId=42). Способ сопоставления определяет её стоимость:

  • Линейный перебор — пробовать каждый маршрут по порядку. Просто, но O(n) по числу маршрутов; нормально для десятков.
  • Radix-trie (httprouter, Echo, find-my-way у Fastify) — префиксное дерево по сегментам пути. O(длины пути), независимо от числа маршрутов, поэтому 10 и 10 000 маршрутов сопоставляются за одно время.

На практике маршрутизация — это микросекунды в любом случае; она почти не доминирует в задержке. Настоящие баги маршрутизации — это корректность: пересекающиеся шаблоны (/users/:id и /users/me), несовпадение по завершающему слешу и путаница методов (GET-маршрут перекрывает POST на том же пути).

Middleware: цепочка вокруг обработчика

Middleware — функции, которые выполняются до и/или после обработчика, каждая может прочитать запрос, изменить его, прервать или передать управление дальше. Они обслуживают сквозные задачи (cross-cutting concerns) — то, что нужно каждому эндпоинту, но что ни один не должен реализовывать заново: аутентификация, логирование, парсинг тела, rate limiting, сжатие, обработка ошибок.

Модель — луковица. Запрос идёт внутрь через каждый слой к обработчику, затем ответ идёт наружу через те же слои в обратном порядке:

запрос  →  logging → auth → bodyParser → handler
ответ   ← logging ← auth ← bodyParser ← handler

Слой, вызывающий next(), передаёт управление внутрь. Слой, который отвечает без вызова next(), прерывает цепочку (short-circuit) — обработчик не выполняется. Именно так rate limiter возвращает 429, а auth-слой возвращает 401 до запуска бизнес-логики.

Порядок — это граница безопасности

Поскольку каждый слой может прервать цепочку, порядок регистрации решает, что реально защищает обработчик:

Неверный порядокПоследствие
Маршрут зарегистрирован до auth-middlewareОбработчик выполняется без аутентификации
Парсер тела до лимита размера телаСервер буферизует загрузку 2 ГБ до отказа
Сжатие до authСжатые ошибки утекают; CPU тратится на запросы, которые получат 401
Обработчик ошибок зарегистрирован первымОн ничего не ловит — ошибки проходят мимо него
Rate limiter после дорогой работыВы делаете работу, а потом отказываетесь её принять

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

Куда реально уходит время

Middleware — ещё и место, где копится невидимая задержка. Каждый слой выполняется на каждом запросе. Парсер тела, разбирающий 1 МБ JSON, слой логирования с синхронным DNS-запросом, auth-слой, дёргающий удалённый сервис токенов без кеша — всё это добавляет миллисекунды к каждому запросу, а не только к медленным. Профилировать медленный эндпоинт — значит замерять цепочку, а не только обработчик.

Викторина

Админский эндпоинт доступен без аутентификации, хотя auth-middleware реализован и протестирован. Наиболее вероятная причина?

Викторина

Зачем ставить лимит размера запроса ПЕРЕД парсером тела в цепочке middleware?

Расставь шаги по порядку

Расставьте здравую цепочку middleware от самого внешнего (первый) к обработчику:

  1. 1 Обработчик ошибок охватывает всё (ловит throw ниже по цепочке)
  2. 2 Логирование / трейсинг (присвоить request ID)
  3. 3 Rate limiting (прервать 429 до выполнения работы)
  4. 4 Аутентификация (прервать 401 до обработчика)
  5. 5 Лимит размера тела, затем парсинг тела
  6. 6 Обработчик (бизнес-логика)
Вспомните перед уходом
  1. 01
    Чем radix-trie роутер отличается от линейного перебора и доминирует ли маршрутизация в задержке?
  2. 02
    Объясните луковичную модель middleware и что значит «прерывание цепочки».
  3. 03
    Почему порядок middleware — граница безопасности и какое правило порядка?
Итог

С разобранным запросом сервер выбирает, какой код выполнить. Маршрутизация связывает метод и путь ровно с одним обработчиком; radix-trie держит время сопоставления независимым от числа маршрутов, но кусаются баги корректности — пересечения и путаница методов. Вокруг обработчика — цепочка middleware, луковица сквозных слоёв (auth, логирование, парсинг тела, rate limiting, сжатие, обработка ошибок), где любой слой может прервать запрос. Эта возможность делает порядок регистрации границей безопасности и производительности: защитные дешёвые слои первыми, дорогие опциональные — последними, обработчик ошибок — самым внешним. Middleware — ещё и место, где тихо копится задержка на запрос. Следующая остановка — центр луковицы: сам обработчик и превращение его результата в ответ.

Связанные уроки
встречается в185
Продолжить восхождение ↑Обработчик и ответ: от бизнес-логики до байтов на проводе
хоткеи развернуть
поиск
K
пред. пьеса
k
след. пьеса
j
тиры
t
это меню
?
sources3
expand
  1. 01
  2. 02
  3. 03

Trademarks belong to their respective owners. Editorial reference only.