Зарегистрируйтесь, чтобы продолжить обучение

Маршрутизация JS: HTTP Server

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

https://ru.hexlet.io/code_reviews/4172
https://ru.hexlet.io/courses/programming-basics
https://ru.hexlet.io/account/profile/edit

Каждый адрес из примера выше представляет собой конкретный маршрут (роут). Причём их можно разделить по типу: статические и динамические.

Статические маршруты

Характеризуются тем, что адрес совпадает с самим маршрутом. Например, account/profile/edit. Несмотря на то, что адрес один, у разных пользователей он будет отображать разные данные, зависящие от того, кто сейчас авторизован.

Динамические маршруты

А что, если у нас есть адреса, которые обозначают одно и тоже, но содержат параметр. Типичный пример /users/5. Без особого труда можно понять, что по этой ссылке мы получим информацию о пользователе с номером 5. Но тогда возникает вопрос: если у нас в базе тысячи пользователей, нам придётся определять тысячи маршрутов?

К счастью, нет, здесь нам на помощь приходят регулярные выражения. Создаётся один маршрут, который выглядит примерно так: ^/users/(\w+). А дальше нужно просто сопоставить это регулярное выражение со строкой запроса. Другими словами, мы определили один единственный маршрут, который покрывает подобные ссылки:

/users/4
/users/1234
/users/3
/users/robocop

После этого момента становится понятно, что процесс роутинга – это чуть сложнее, чем просто большой if/switch. В будущем мы начнём работать с фреймворками, в которых роутинг является одной из основных подсистем. Это справедливо для всех web-фреймворков на всех языках.

Как правило, фреймворки предоставляют более высокоуровневый способ работы с роутами. То есть, вы пишете не сырые регулярные выражения, а строки с плейсхолдерами: /users/:id. Эти строки внутри заменяются на регулярные выражения и сопоставляются с запрашиваемыми адресами. Плейсхолдеры, в подавляющем большинстве фреймворков, заменяются на группу \w+. Эта группа не включает в себя / и требует обязательного наличия хотя бы одного символа. Это значит, что следующие маршруты не подходят под маршрут /users/:id:

/users
/users/4/photos
/users/my/d

В нашем сервере мы реализовали следующий способ работы роутера. Отдельно описывается объект, содержащий правила роутинга (как ключи) и обработчики маршрутов (как значения):

const router = {
  'GET': {
    '/': (req, res, matches) => {
      /* ... */
    },

    '/search.json': (req, res, matches) => {
      /* ... */
    },
  },
}

Обратите внимание на важную деталь. Метод http тоже является частью роутинга. Для GET- и POST-запросов на /users будет использоваться два роута. Это связано с большим количеством причин, одной из которых является семантика http. А вот параметры запроса (например, /users?name=Tirion) не являются частью процесса маршрутизации (при таком роутинге, как выше). Они уже используются внутри обработчиков на ваше усмотрение.

Также имеет значение порядок, в котором заданы маршруты. Поэтому первыми должны идти статические и наиболее конкретизированные маршруты, а в конце более общие. Это правило справедливо в том случае, если есть пересечения.

Ещё остаётся работа с ошибками, такими как "страница не найдена". Здесь уже возможны варианты. Некоторые фреймворки обрабатывают эту ситуацию отдельно, проверяя, что ни один маршрут не совпал с запрошенным адресом, и выполняя специальный обработчик для этой ситуации. В других достаточно определить в самом конце маршрут *, в который попадет всё, что не попало в другие места. И из обработчика этого роута можно делать всё, что нужно для правильного отображения ошибки.

Пример простого способа обрабатывать маршруты:

export default http.createServer((request, response) => {
  // извлекаем из адреса часть без query params
  const url = new URL(request.url, `http://${request.headers.host}`);
  const { pathname } = url;

  const routes = router[request.method];
  // Обходим маршруты с помощью find,
  // чтобы остановиться после того, как маршрут найден
  const result = Object.keys(routes).find((str) => {
    const regexp = new RegExp(`^${str}$`);
    // Проверяем совпадение с маршрутом (записанным в виде регулярного выражения)
    const matches = pathname.match(regexp);

    // Маршрут не найден, двигаемся дальше
    if (!matches) {
      return false;
    }

    // Выполнение обработчика
    routes[str](request, response, matches);
    return true;
  });

  // Особая обработка ситуации когда не было найдено соответствующего маршрута
  if (!result) {
    response.writeHead(404);
    response.end();
  }
});

Соглашения

В web-разработке существует понятие CRUD (в русскоязычной среде говорят "круд"), которое расшифровывается как CREATE, READ, UPDATE, DELETE. И самое простое и базовое, что делают разработчики — это круды. Например, любой административный интерфейс (админка сайта) — это большой набор разных крудов для всевозможных сущностей: круд постов в блог, круд товаров и так далее.

Существуют соглашения о том, как грамотно делать круды с точки зрения маршрутизации. Они включают в себя особенности семантики http.

GET /users # список
POST /users # создание

GET /users/10 # просмотр
PATCH /users/10 # обновление
DELETE /users/10 # удаление

GET /users/10/photos # список фотографий

Аватары экспертов Хекслета

Остались вопросы? Задайте их в разделе «Обсуждение»

Вам ответят команда поддержки Хекслета или другие студенты

Открыть доступ

Курсы программирования для новичков и опытных разработчиков. Начните обучение бесплатно

  • 130 курсов, 2000+ часов теории
  • 1000 практических заданий в браузере
  • 360 000 студентов
Отправляя форму, вы принимаете «Соглашение об обработке персональных данных» и условия «Оферты», а также соглашаетесь с «Условиями использования»

Наши выпускники работают в компаниях:

Логотип компании Альфа Банк
Логотип компании Aviasales
Логотип компании Yandex
Логотип компании Tinkoff

Используйте Хекслет по-максимуму!

  • Задавайте вопросы по уроку
  • Проверяйте знания в квизах
  • Проходите практику прямо в браузере
  • Отслеживайте свой прогресс

Зарегистрируйтесь или войдите в свой аккаунт

Отправляя форму, вы принимаете «Соглашение об обработке персональных данных» и условия «Оферты», а также соглашаетесь с «Условиями использования»