Router
определяет, какие промежуточные обработчики (middleware) и Handler
будут обрабатывать HTTP-запрос. Это базовая и ключевая функциональность в Salvo.
Внутри Router
фактически состоит из набора фильтров (Filter). Когда поступает запрос, роутер последовательно проверяет соответствие себя и своих дочерних элементов запросу в порядке их добавления. Если соответствие найдено, последовательно выполняются все middleware во всей цепочке роутера и его дочерних элементов. Если в процессе обработки статус Response
устанавливается как ошибка (4XX, 5XX) или перенаправление (3XX), последующие middleware и Handler
пропускаются. Также можно вручную вызвать ctrl.skip_rest()
, чтобы пропустить оставшиеся middleware и Handler
.
В процессе сопоставления существует информация о пути URL, которую можно рассматривать как объект, который должен быть полностью "потреблен" фильтрами в процессе сопоставления. Если все фильтры в роутере успешно сопоставлены и информация о пути URL полностью "потреблена", считается, что сопоставление прошло успешно.
Например:
Фактически эквивалентно:
Если обратиться к GET /articles/
, сопоставление будет успешным и выполнится list_articles
. Но если обратиться к GET /articles/123
, сопоставление не удастся и вернется ошибка 404, потому что Router::with_path("articles")
"потребил" только /articles
из информации о пути URL, оставив /123
необработанным, поэтому сопоставление считается неудачным. Чтобы сделать сопоставление успешным, роутер можно изменить так:
Здесь {**}
сопоставится с любым оставшимся путем, поэтому он сможет обработать GET /articles/123
и выполнить list_articles
.
Мы можем определять роуты в плоском стиле:
Также можно определять роуты в виде дерева, что является рекомендуемым способом:
Такой стиль определения делает структуру роутера более четкой и простой для сложных проектов.
Многие методы в Router
возвращают Self
, что позволяет писать код в цепочке. Иногда вам нужно решить, как маршрутизировать запросы, исходя из определенных условий. Система роутинга предоставляет функцию then
, которую легко использовать:
Этот пример показывает, что только когда сервер находится в режиме admin_mode
, будут добавлены роуты для создания, редактирования и удаления статей.
В приведенном выше коде {id}
определяет параметр. Мы можем получить его значение через экземпляр Request
:
{id}
сопоставляется с сегментом пути. Обычно id
статьи - это просто число, поэтому мы можем использовать регулярное выражение, чтобы ограничить правило сопоставления для id
, например r"{id|\d+}"
.
Для числовых типов есть более простой способ - использовать <id:num>
:
{id:num}
- сопоставляется с любым количеством цифр;{id:num[10]}
- сопоставляется ровно с 10 цифрами;{id:num(..10)}
- сопоставляется с 1 до 9 цифрами;{id:num(3..10)}
- сопоставляется с 3 до 9 цифрами;{id:num(..=10)}
- сопоставляется с 1 до 10 цифрами;{id:num(3..=10)}
- сопоставляется с 3 до 10 цифрами;{id:num(10..)}
- сопоставляется с 10 и более цифрами.Также можно использовать {**}
, {*+}
или {*?}
для сопоставления с оставшимися сегментами пути. Для лучшей читаемости кода можно добавить подходящее имя, чтобы сделать путь более понятным, например: {**file_path}
.
{**}
: означает, что сопоставленная часть может быть пустой строкой. Например, путь /files/{**rest_path}
сопоставится с /files
, /files/abc.txt
, /files/dir/abc.txt
;{*+}
: означает, что сопоставленная часть должна существовать и не может быть пустой строкой. Например, путь /files/{*+rest_path}
не сопоставится с /files
, но сопоставится с /files/abc.txt
, /files/dir/abc.txt
;{*?}
: означает, что сопоставленная часть может быть пустой строкой, но может содержать только один сегмент пути. Например, путь /files/{*?rest_path}
не сопоставится с /files/dir/abc.txt
, но сопоставится с /files
, /files/abc.txt
.Можно комбинировать несколько выражений для сопоставления с одним сегментом пути, например /articles/article_{id:num}/
, /images/{name}.{ext}
.
Middleware можно добавить с помощью функции hoop
в роутере:
В этом примере корневой роутер использует check_authed
для проверки, вошел ли текущий пользователь в систему. Все дочерние роутеры будут подвержены влиянию этого middleware.
Если пользователи просто просматривают информацию о писателях и их статьи, мы можем разрешить им делать это без входа в систему. Мы можем определить роуты следующим образом:
Хотя два роутера имеют одинаковое определение пути path("articles")
, они все равно могут быть добавлены к одному родительскому роутеру.
Router
внутренне использует фильтры для определения соответствия роута. Фильтры поддерживают базовые логические операции or
и and
. Роутер может содержать несколько фильтров, и когда все фильтры успешно сопоставлены, роутер считается соответствующим.
Структура пути веб-сайта имеет древовидную форму, но эта структура не обязательно совпадает с древовидной структурой организации роутеров. Один путь на сайте может соответствовать нескольким узлам роутера. Например, некоторый контент по пути articles/
требует входа в систему, а другой - нет. Мы можем организовать пути, требующие входа, в роутер с middleware проверки аутентификации, а пути, не требующие входа, - в другой роутер без такой проверки:
Роутер использует фильтры для фильтрации запросов и отправки их соответствующим middleware и Handler
.
path
и method
- два наиболее часто используемых фильтра. path
используется для сопоставления с информацией о пути; method
используется для сопоставления с методом запроса, например: GET, POST, PATCH и т.д.
Мы можем использовать and
, or
для соединения фильтров роутера:
Фильтры на основе пути запроса используются наиболее часто. В фильтрах пути можно определить параметры, например:
В Handler
можно получить параметры через функцию get_param
объекта Request
:
Фильтруют запросы на основе HTTP-метода, например:
Здесь get
, patch
, delete
- это фильтры методов. Фактически это эквивалентно:
Для часто встречающихся выражений сопоставления мы можем зарегистрировать короткое имя с помощью PathFilter::register_wisp_regex
или PathFilter::register_wisp_builder
. Например, формат GUID часто встречается в путях, и обычно его нужно сопоставлять так:
Каждый раз писать такое сложное регулярное выражение легко ошибиться, и код становится менее читаемым. Вместо этого можно сделать так:
Достаточно зарегистрировать один раз, и в дальнейшем можно использовать простую запись {id:guid}
для сопоставления с GUID, упрощая написание кода.
Основные отличия фреймворков с роутингом (таких как Salvo) от традиционных MVC или фреймворков с контроллерами:
Гибкость: Роутинг позволяет более гибко определять процесс обработки запросов, точно контролируя логику обработки каждого пути. Например, в Salvo вы можете напрямую определить функцию обработки для конкретного пути:
В то время как в подходе с контроллерами обычно нужно сначала определить класс контроллера, а затем методы в нем для обработки разных запросов:
Интеграция middleware: Фреймворки с роутингом обычно предоставляют более простые способы интеграции middleware, которые можно применять к конкретным роутам. В Salvo middleware можно точно применять к определенным роутам:
Организация кода: Подход с роутингом склоняется к организации кода по функциональности или конечным точкам API, а не по слоям модели-представления-контроллера. Роутинг поощряет организацию кода по функциональности конечных точек API:
Легковесность: Обычно подход с роутингом более легковесный, уменьшая количество обязательных концепций и ограничений фреймворка. Вы можете включать только нужные компоненты, не следуя строгой структуре фреймворка.
Роутинг делает разработку API более интуитивной, особенно подходя для современных микросервисов и RESTful API. В таких фреймворках,