Express — популярный фреймворк Node.js, который значительно упрощает разработку и поддержку приложений. Express позволяет быстро создавать обработчики маршрутов на основе HTTP-методов и URL-адресов. Чтобы понять, как это работает, заглянем в “закулисье” Express-маршрутизации.
Пример с “Hello World”
Для начала рассмотрим пример простейшего приложения на основе Express.
const express = require('express')
const app = express()
app.get('/', (req, res) => {
res.send('Hello World!')
})
app.listen(3000, () => {
console.log('Example app listening on port 3000')
})
Приведенный код создаст новый HTTP-сервер, прослушивающий запросы на порту 3000. Когда сервер получит GET-запрос на URL “/”, он вернет ответ “Hello World!”.
Приложение Express
После вызова express()
мы вычисляем функцию createApplication()
, создающую новый объект app
. Посмотрим, как она работает.
Во 2-й строке app
определяется как анонимная функция, которая обертывает handle(req, res, next)
. Эта функция будет вызываться при каждом запросе, который получит сервер.
В 7-й строке объект app
объединяется с Express-методами, такими как use()
, listen()
, get()
и т.д.
Важным свойством объекта app
является свойство Router
, которое отвечает за то, какие методы будут вызываться, когда сервер получит новый запрос.
Прослушивание запросов
Если погрузиться глубже, то увидим, что Express, как правило, использует http-модуль Node.js.
При вызове app.listen()
создается новый http-сервер, аргумент this
— это функция, которая будет вызываться при каждом новом http-запросе. Это та же анонимная функция, что была создана в createApplication()
. Она просто обертывает метод app.handle()
.
Как работает Router
Объект Router имеет стек “слоев”, каждый из которых снабжен методом handle
и свойством path
.
Вот эти слои.
- Middleware (промежуточная обработка) — функция, имеющая сигнатуру
function(req, res, next)
и обладающая логикой, способной изменятьreq
иres
и в конечном итоге вызыватьnext()
— для вызова следующего слоя — или использоватьsend()
для возврата ответа. При передаче массива функций каждая функция будет отдельным слоем. - Route — HTTP-метод (GET, POST и т.д.) и функция с той же сигнатурой, что и функция промежуточной обработки. Функция будет срабатывать только для запроса с тем же HTTP-методом. Также можно определить
route
, который будет обрабатывать все HTTP-методы с помощью.all()
. В случае передачи массива функций,route
сохраняет заданные функции в виде стека и каждый вызовnext()
вызывает следующую функцию. - Router — мы можем создать новый объект
router
(который также будет содержать собственный стек слоев).Router
можно рассматривать как “мини-приложение”, которое в конечном итоге может вызывать функции промежуточной обработки и функцииroute
или даже другой объектrouter
.
Подбор релевантного пути
Как Express определяет, какой слой является релевантным для конкретного запроса?
Express использует библиотеку под названием path-to-regexp для сопоставления маршрутов запроса с определенным путем. Путь может быть строкой, строковым шаблоном и регулярным выражением.
Вот несколько примеров сопоставления путей.
- Путь, который будет сопоставлять запрос с
/index.html
.
app.get('/index.html', (req, res) => {
res.send('index');
});
2. Путь, который будет сопоставлять запросы с /abc
и /ac
.
app.get('/ab?c', (req, res) => {
res.send('ab?c');
});
3. Путь, который будет сопоставлять запросы с abc
, abbbc
и так далее.
app.get('/ab+c', (req, res) => {
res.send('ab+c');
});
4. Путь, который будет сопоставлять запросы с axxxb
, `aRANDOMb
и так далее.
app.get('/a*b', (req, res) => {
res.send('a*b');
});
Более подробную информацию и примеры можно найти в документации path-to-regexp.
Обработка запроса
После ознакомления с базовой структурой маршрутизации Express, разберемся с тем, что происходит, когда сервер получает запрос.
Как уже говорилось, при создании нового приложения в Express и вызове метода app.listen()
создается http-сервер, который будет вызывать app.handle()
для каждого нового http-запроса.
- При получении нового запроса вызывается метод
app.handle()
. - Метод
app.handle()
выполняет методhandle
корневого объектаrouter
. - Метод
handle
будет перебирать слои объектаrouter
, пока не найдет подходящий слой с помощьюpath
, и вызывает методhandle
этого слоя. Если слой является объектомrouter
, функцияhandle
выполнит цикл по нему. - Если будет вызван обратный вызов
next()
, то будет произведен поиск следующего подходящего слоя и его обработка. Процесс остановится, когда клиенту будет отправлен ответ.
Вот пример маршрутизации, объекта router
и функции промежуточной обработки (middleware):
Заключение
Это был краткий обзор маршрутизации Express, показывающий, как фреймворк понимает, какая функция должна обрабатывать тот или иной запрос. Для более глубокого погружения в тему стоит ознакомиться с документацией по Express и детально изучить исходный код.
Читайте также:
- Как организовать Express-контроллеры для крупных баз кода
- 6 проверенных методов повышения безопасности Node.js
- Руководство по созданию настольного приложения в Electron
Читайте нас в Telegram, VK и Дзен
Перевод статьи Shay Pepper: How Express Routing Works