Маршрутизация
Исходный кодТребование: предполагается, что вы ознакомились с вводными руководствами и запустили приложение Phoenix и убедились, что оно работает.
Требование: предполагается, что вы ознакомились с руководством по жизненному циклу запроса.
Маршрутизаторы — главные узлы приложений Phoenix. Они сопоставляют HTTP-запросы с действиями контроллеров, связывают обработчики каналов реального времени и определяют последовательности преобразований конвейера, применяемые к заданным группам маршрутов.
Файл маршрутизатора, который генерирует Phoenix, lib/hello_web/router.ex, будет выглядеть примерно так:
defmodule HelloWeb.Router do
use HelloWeb, :router
pipeline :browser do
plug :accepts, ["html"]
plug :fetch_session
plug :fetch_live_flash
plug :put_root_layout, html: {HelloWeb.Layouts, :root}
plug :protect_from_forgery
plug :put_secure_browser_headers
end
pipeline :api do
plug :accepts, ["json"]
end
scope "/", HelloWeb do
pipe_through :browser
get "/", PageController, :home
end
# Other scopes may use custom stacks.
# scope "/api", HelloWeb do
# pipe_through :api
# end
# ...
end
Названия модулей маршрутизатора и контроллера будут начинаться с имени, заданного вами для приложения, за которым следует Web.
Первая строка этого модуля, use HelloWeb, :router, просто делает функции маршрутизатора Phoenix доступными в нашем конкретном маршрутизаторе.
Областям видимости посвящён отдельный раздел этого руководства, поэтому здесь мы не будем подробно разбирать блок scope "/", HelloWeb do. Строка pipe_through :browser будет подробно рассмотрена в разделе «Конвейеры». Пока достаточно знать, что конвейеры позволяют применять набор подключаемых модулей к различным группам маршрутов.
Однако внутри блока области видимости находится наш первый настоящий маршрут:
get "/", PageController, :home
get — это макрос Phoenix, соответствующий HTTP-методу GET. Для других HTTP-методов существуют похожие макросы, в том числе POST, PUT, PATCH, DELETE, OPTIONS, CONNECT, TRACE и HEAD.
Зачем нужны макросы?
Phoenix старается свести к минимуму использование макросов. Однако вы могли заметить, что Phoenix.Router активно использует макросы. Почему?
Для определения маршрутов мы используем get, post, put и delete. Макросы нужны нам по двум причинам:
Они определяют механизм маршрутизации, используемый при каждом запросе для выбора контроллера, которому будет передан запрос. Благодаря макросам Phoenix компилирует все маршруты в огромный оператор case с правилами сопоставления с образцом, который сильно оптимизирован виртуальной машиной Erlang.
Для каждого определённого вами маршрута мы также задаём метаданные, необходимые для реализации
Phoenix.VerifiedRoutes. Как мы скоро узнаем, проверенные маршруты позволяют ссылаться на любой маршрут как на обычную строку, при этом компилятор проверяет её корректность. Благодаря этому значительно сложнее отправить в продакшен неработающие ссылки, формы, письма и прочее.
Иными словами, маршрутизатор использует макросы для создания более быстрых и надёжных приложений. Также помните, что макросы Elixir работают только во время компиляции, что обеспечивает стабильность после компиляции кода. Как мы узнаем далее, Phoenix также предоставляет возможность изучать все определённые маршруты с помощью mix phx.routes.
Просмотр маршрутов
Phoenix предоставляет удобный инструмент для изучения маршрутов приложения: mix phx.routes.
Посмотрим, как он работает. Перейдите в корневой каталог только что созданного приложения Phoenix и выполните mix phx.routes. Вы увидите примерно такой список всех имеющихся на данный момент маршрутов:
$ mix phx.routes GET / HelloWeb.PageController :home ...
Маршрут выше показывает, что любой HTTP-запрос GET к корню приложения будет обработан действием home контроллера HelloWeb.PageController.
Ресурсы
Помимо макросов для HTTP-методов, таких как get, post и put, маршрутизатор поддерживает и другие макросы. Самый важный из них — resources. Добавим ресурс в файл lib/hello_web/router.ex следующим образом:
scope "/", HelloWeb do pipe_through :browser get "/", PageController, :home resources "/users", UserController ... end
Пока неважно, что у нас на самом деле нет HelloWeb.UserController.
Ещё раз выполните mix phx.routes в корневом каталоге проекта. Вы увидите примерно следующее:
... GET /users HelloWeb.UserController :index GET /users/:id/edit HelloWeb.UserController :edit GET /users/new HelloWeb.UserController :new GET /users/:id HelloWeb.UserController :show POST /users HelloWeb.UserController :create PATCH /users/:id HelloWeb.UserController :update PUT /users/:id HelloWeb.UserController :update DELETE /users/:id HelloWeb.UserController :delete ...
Это стандартный набор HTTP-методов, путей и действий контроллера. Некоторое время такие маршруты называли RESTful-маршрутами, но сегодня большинство считает это название неверным. Рассмотрим их по отдельности.
- Запрос GET к
/usersвызовет действиеindex, чтобы отобразить всех пользователей. - Запрос GET к
/users/:id/editвызовет действиеeditс идентификатором, чтобы получить отдельного пользователя из хранилища данных и представить информацию в форме для редактирования. - Запрос GET к
/users/newвызовет действиеnew, чтобы отобразить форму создания нового пользователя. - Запрос GET к
/users/:idвызовет действиеshowс идентификатором, чтобы отобразить отдельного пользователя с этим идентификатором. - Запрос POST к
/usersвызовет действиеcreate, чтобы сохранить нового пользователя в хранилище данных. - Запрос PATCH к
/users/:idвызовет действиеupdateс идентификатором, чтобы сохранить обновлённые данные пользователя в хранилище. - Запрос PUT к
/users/:idтакже вызовет действиеupdateс идентификатором, чтобы сохранить обновлённые данные пользователя в хранилище. - Запрос DELETE к
/users/:idвызовет действиеdeleteс идентификатором, чтобы удалить пользователя из хранилища данных.
Если нам нужны не все эти маршруты, мы можем выбрать необходимые, используя параметры :only и :except для фильтрации отдельных действий.
Допустим, у нас есть ресурс публикаций, доступный только для чтения. Его можно определить так:
resources "/posts", PostController, only: [:index, :show]
Команда mix phx.routes покажет, что теперь определены только маршруты для действий index и show.
GET /posts HelloWeb.PostController :index GET /posts/:id HelloWeb.PostController :show
Аналогично, если у нас есть ресурс комментариев и мы не хотим предоставлять маршрут для их удаления, можно определить маршрут следующим образом.
resources "/comments", CommentController, except: [:delete]
Теперь команда mix phx.routes покажет, что у нас есть все маршруты, кроме запроса DELETE для действия удаления.
GET /comments HelloWeb.CommentController :index GET /comments/:id/edit HelloWeb.CommentController :edit GET /comments/new HelloWeb.CommentController :new GET /comments/:id HelloWeb.CommentController :show POST /comments HelloWeb.CommentController :create PATCH /comments/:id HelloWeb.CommentController :update PUT /comments/:id HelloWeb.CommentController :update
В макросе Phoenix.Router.resources/4 описаны дополнительные параметры настройки маршрутов ресурсов.
Проверенные маршруты
Phoenix включает модуль Phoenix.VerifiedRoutes, который проверяет соответствие путей маршрутизатора его маршрутам во время компиляции с помощью сигила ~p. Например, вы можете задавать пути в контроллерах, тестах и шаблонах, а компилятор проверит, что они соответствуют маршрутам, определённым в маршрутизаторе.
Посмотрим, как это работает. Выполните iex -S mix в корневом каталоге проекта. Мы определим временный пример модуля, который строит несколько путей маршрутов ~p.
iex> defmodule RouteExample do
...> use HelloWeb, :verified_routes
...>
...> def example do
...> ~p"/comments"
...> ~p"/unknown/123"
...> end
...> end
warning: no route path for HelloWeb.Router matches "/unknown/123"
iex:5: RouteExample.example/0
{:module, RouteExample, ...}
iex>
Обратите внимание: при первом вызове существующего маршрута ~p"/comments" предупреждения нет, а неправильный путь маршрута ~p"/unknown/123" вызывает предупреждение компилятора, как и должен. Это важно, поскольку позволяет записывать в приложении пути, которые в противном случае были бы жёстко заданы, а компилятор сообщит нам, если мы укажем неправильный маршрут или изменим структуру маршрутизации.
Проекты Phoenix изначально настроены так, чтобы проверенные маршруты можно было использовать на всех уровнях веб-приложения, в том числе в тестах. Например, в шаблонах можно создавать ссылки ~p:
<a href={~p"/"}>Welcome Page!</a>
<a href={~p"/comments"}>View Comments</a>
А в контроллере — выполнять перенаправление:
redirect(conn, to: ~p"/comments/#{comment}")
Использование ~p для путей маршрутов гарантирует, что пути и URL-адреса приложения будут актуальны и соответствовать определениям маршрутизатора. Компилятор обнаружит ошибки и сообщит нам, если мы изменим маршруты, на которые есть ссылки в других частях приложения.
Подробнее о проверенных маршрутах
Как быть с путями, содержащими строки запроса? Можно добавлять параметры строки запроса непосредственно, в виде ключевого списка или карты значений. Например:
~p"/users/17?admin=true&active=false"
"/users/17?admin=true&active=false"
~p"/users/17?#{[admin: true]}"
"/users/17?admin=true"
~p"/users/17?#{%{admin: true}}"
"/users/17?admin=true"
Что делать, если вместо пути нужен полный URL? Просто оберните путь вызовом Phoenix.VerifiedRoutes.url/1. Эта функция импортирована везде, где доступен ~p:
url(~p"/users") "http://localhost:4000/users"
Вызовы url получают из параметров конфигурации, заданных для каждой среды, сведения о хосте, порте, порте прокси и SSL, необходимые для построения полного URL. Подробнее о конфигурации мы поговорим в отдельном руководстве. А пока можете посмотреть файл config/dev.exs в своём проекте, чтобы увидеть эти значения.
Вложенные ресурсы
В маршрутизаторе Phoenix можно также вкладывать ресурсы друг в друга. Допустим, у нас есть ресурс posts, связанный отношением «многие к одному» с users. Иными словами, пользователь может создавать множество публикаций, а отдельная публикация принадлежит только одному пользователю. Это можно представить, добавив в lib/hello_web/router.ex вложенный маршрут, как показано ниже:
resources "/users", UserController do resources "/posts", PostController end
Теперь, если выполнить mix phx.routes, помимо маршрутов, которые мы рассмотрели выше для users, мы получим следующий набор маршрутов:
... GET /users/:user_id/posts HelloWeb.PostController :index GET /users/:user_id/posts/:id/edit HelloWeb.PostController :edit GET /users/:user_id/posts/new HelloWeb.PostController :new GET /users/:user_id/posts/:id HelloWeb.PostController :show POST /users/:user_id/posts HelloWeb.PostController :create PATCH /users/:user_id/posts/:id HelloWeb.PostController :update PUT /users/:user_id/posts/:id HelloWeb.PostController :update DELETE /users/:user_id/posts/:id HelloWeb.PostController :delete ...
Как видно, каждый из этих маршрутов ограничивает публикации идентификатором пользователя. Для первого маршрута будет вызвано действие index контроллера PostController, которому будет передан user_id. Это означает, что будут отображены только публикации данного пользователя. Такое же ограничение действует для всех этих маршрутов.
При построении путей для вложенных маршрутов нужно подставлять идентификаторы в соответствующие места определения маршрута. Для следующего маршрута show значение 42 — это user_id, а 17 — это post_id.
user_id = 42
post_id = 17
~p"/users/#{user_id}/posts/#{post_id}"
"/users/42/posts/17"
Проверенные маршруты также поддерживают протокол Phoenix.Param, но пока нам не нужно разбираться в протоколах Elixir. Достаточно знать, что когда мы начнём строить приложение с помощью структур, таких как %User{} и %Post{}, мы сможем напрямую подставлять эти структуры данных в пути ~p, а Phoenix извлечёт нужные поля для маршрута.
~p"/users/#{user}/posts/#{post}"
"/users/42/posts/17"
Обратите внимание, что нам не пришлось подставлять user.id или post.id. Это особенно удобно, если позже мы решим сделать URL-адреса более понятными и начать использовать вместо них слаги. Нам не придётся менять ни один из наших ~p!
Маршруты в областях видимости
Области видимости позволяют группировать маршруты с общим префиксом пути и набором подключаемых модулей. Это может понадобиться для административных функций, API и особенно для версионируемых API. Допустим, пользователи сайта могут оставлять отзывы, которые сначала должен одобрить администратор. Семантика этих ресурсов сильно различается, и у них могут быть разные контроллеры. Области видимости позволяют разделить такие маршруты.
Пути пользовательских отзывов будут выглядеть как обычный ресурс.
/reviews /reviews/1234 /reviews/1234/edit ...
Пути отзывов для администрирования можно снабдить префиксом /admin.
/admin/reviews /admin/reviews/1234 /admin/reviews/1234/edit ...
Для этого можно определить маршрут в области видимости, задав параметр пути /admin, как показано ниже. Эту область можно вложить в другую, но вместо этого мы зададим её отдельно в корне, добавив в lib/hello_web/router.ex следующее:
scope "/admin", HelloWeb.Admin do pipe_through :browser resources "/reviews", ReviewController end
Мы определяем новую область видимости, в которой ко всем маршрутам добавляется префикс /admin, а все контроллеры находятся в пространстве имён HelloWeb.Admin.
Если снова выполнить mix phx.routes, то в дополнение к предыдущему набору маршрутов мы получим следующее:
... GET /admin/reviews HelloWeb.Admin.ReviewController :index GET /admin/reviews/:id/edit HelloWeb.Admin.ReviewController :edit GET /admin/reviews/new HelloWeb.Admin.ReviewController :new GET /admin/reviews/:id HelloWeb.Admin.ReviewController :show POST /admin/reviews HelloWeb.Admin.ReviewController :create PATCH /admin/reviews/:id HelloWeb.Admin.ReviewController :update PUT /admin/reviews/:id HelloWeb.Admin.ReviewController :update DELETE /admin/reviews/:id HelloWeb.Admin.ReviewController :delete ...
Выглядит хорошо, но здесь есть проблема. Мы хотели иметь как пользовательские маршруты отзывов /reviews, так и маршруты администратора /admin/reviews. Если теперь добавить пользовательские отзывы в маршрутизатор в корневой области видимости, как показано ниже:
scope "/", HelloWeb do pipe_through :browser ... resources "/reviews", ReviewController end scope "/admin", HelloWeb.Admin do pipe_through :browser resources "/reviews", ReviewController end
и выполнить mix phx.routes, мы получим вывод для каждого маршрута в области видимости:
... GET /reviews HelloWeb.ReviewController :index GET /reviews/:id/edit HelloWeb.ReviewController :edit GET /reviews/new HelloWeb.ReviewController :new GET /reviews/:id HelloWeb.ReviewController :show POST /reviews HelloWeb.ReviewController :create PATCH /reviews/:id HelloWeb.ReviewController :update PUT /reviews/:id HelloWeb.ReviewController :update DELETE /reviews/:id HelloWeb.ReviewController :delete ... GET /admin/reviews HelloWeb.Admin.ReviewController :index GET /admin/reviews/:id/edit HelloWeb.Admin.ReviewController :edit GET /admin/reviews/new HelloWeb.Admin.ReviewController :new GET /admin/reviews/:id HelloWeb.Admin.ReviewController :show POST /admin/reviews HelloWeb.Admin.ReviewController :create PATCH /admin/reviews/:id HelloWeb.Admin.ReviewController :update PUT /admin/reviews/:id HelloWeb.Admin.ReviewController :update DELETE /admin/reviews/:id HelloWeb.Admin.ReviewController :delete
Что, если у нас есть несколько ресурсов, которыми управляют администраторы? Их можно поместить в одну и ту же область видимости:
scope "/admin", HelloWeb.Admin do pipe_through :browser resources "/images", ImageController resources "/reviews", ReviewController resources "/users", UserController end
Вот что показывает команда mix phx.routes:
... GET /admin/images HelloWeb.Admin.ImageController :index GET /admin/images/:id/edit HelloWeb.Admin.ImageController :edit GET /admin/images/new HelloWeb.Admin.ImageController :new GET /admin/images/:id HelloWeb.Admin.ImageController :show POST /admin/images HelloWeb.Admin.ImageController :create PATCH /admin/images/:id HelloWeb.Admin.ImageController :update PUT /admin/images/:id HelloWeb.Admin.ImageController :update DELETE /admin/images/:id HelloWeb.Admin.ImageController :delete GET /admin/reviews HelloWeb.Admin.ReviewController :index GET /admin/reviews/:id/edit HelloWeb.Admin.ReviewController :edit GET /admin/reviews/new HelloWeb.Admin.ReviewController :new GET /admin/reviews/:id HelloWeb.Admin.ReviewController :show POST /admin/reviews HelloWeb.Admin.ReviewController :create PATCH /admin/reviews/:id HelloWeb.Admin.ReviewController :update PUT /admin/reviews/:id HelloWeb.Admin.ReviewController :update DELETE /admin/reviews/:id HelloWeb.Admin.ReviewController :delete GET /admin/users HelloWeb.Admin.UserController :index GET /admin/users/:id/edit HelloWeb.Admin.UserController :edit GET /admin/users/new HelloWeb.Admin.UserController :new GET /admin/users/:id HelloWeb.Admin.UserController :show POST /admin/users HelloWeb.Admin.UserController :create PATCH /admin/users/:id HelloWeb.Admin.UserController :update PUT /admin/users/:id HelloWeb.Admin.UserController :update DELETE /admin/users/:id HelloWeb.Admin.UserController :delete
Отлично, именно то, что нам нужно. Обратите внимание, что каждый маршрут и контроллер имеют правильное пространство имён.
Области видимости можно вкладывать на любом уровне, но делать это следует осторожно: вложенность иногда усложняет код и снижает его ясность. Тем не менее, допустим, у нас есть версионируемый API с ресурсами изображений, отзывов и пользователей. Тогда технически маршруты для версионируемого API можно настроить так:
scope "/api", HelloWeb.Api do
pipe_through :api
scope "/v1", V1 do
resources "/images", ImageController
resources "/reviews", ReviewController
resources "/users", UserController
end
end
Выполните mix phx.routes, чтобы посмотреть, как будут выглядеть эти определения.
Интересно, что можно использовать несколько областей видимости с одинаковым путём, если не допускать дублирования маршрутов. Следующий маршрутизатор вполне корректен: в нём определены две области видимости с одинаковым путём:
defmodule HelloWeb.Router do
use Phoenix.Router
...
scope "/", HelloWeb do
pipe_through :browser
resources "/users", UserController
end
scope "/", AnotherAppWeb do
pipe_through :browser
resources "/posts", PostController
end
...
end
Если же дублировать маршрут — то есть определить два маршрута с одинаковым путём, — появится знакомое предупреждение:
warning: this clause cannot match because a previous clause at line 16 always matches
Конвейеры
Мы уже прошли большой путь в этом руководстве, так и не поговорив об одной из первых строк, которые увидели в маршрутизаторе: pipe_through :browser. Пора это исправить.
Конвейеры — это последовательности подключаемых модулей, которые можно привязать к определённым областям видимости. Если вы не знакомы с подключаемыми модулями, у нас есть подробное руководство по ним.
Маршруты определяются внутри областей видимости, а области видимости могут проходить через несколько конвейеров. Когда маршрут совпадает с запросом, Phoenix вызывает все подключаемые модули во всех конвейерах, связанных с этим маршрутом. Например, запрос к / пройдёт через конвейер :browser, вызвав все его подключаемые модули.
По умолчанию Phoenix определяет два конвейера: :browser и :api. Их можно использовать для решения ряда типичных задач. Мы можем настроить их или создать новые конвейеры в соответствии с нашими потребностями.
Конвейеры :browser и :api
Как следует из названий, конвейер :browser предназначен для маршрутов, обрабатывающих запросы браузера, а конвейер :api — для маршрутов, возвращающих данные API.
Конвейер :browser включает шесть подключаемых модулей. plug :accepts, ["html"] задаёт допустимый формат запроса или форматы. :fetch_session, как нетрудно догадаться, получает данные сеанса и делает их доступными в соединении. :fetch_live_flash получает сообщения flash из LiveView и объединяет их с сообщениями flash контроллера. Затем подключаемый модуль :put_root_layout сохраняет корневой макет для последующего отображения. Далее :protect_from_forgery и :put_secure_browser_headers защищают отправку форм от подделки межсайтовых запросов.
Сейчас в конвейере :api определён только plug :accepts, ["json"].
Маршрутизатор запускает конвейер для маршрута, определённого внутри области видимости. У маршрутов вне областей видимости нет конвейеров. Хотя использование вложенных областей видимости не рекомендуется (см. приведённый выше пример версионируемого API), при вызове pipe_through во вложенной области маршрутизатор запустит все pipe_through родительских областей, а затем вложенной области.
Всё это может звучать запутанно. Давайте рассмотрим несколько примеров, чтобы разобраться.
Ещё раз взглянем на маршрутизатор только что созданного приложения Phoenix: на этот раз раскомментируем область видимости /api и добавим маршрут.
defmodule HelloWeb.Router do
use HelloWeb, :router
pipeline :browser do
plug :accepts, ["html"]
plug :fetch_session
plug :fetch_live_flash
plug :put_root_layout, html: {HelloWeb.Layouts, :root}
plug :protect_from_forgery
plug :put_secure_browser_headers
end
pipeline :api do
plug :accepts, ["json"]
end
scope "/", HelloWeb do
pipe_through :browser
get "/", PageController, :home
end
# Other scopes may use custom stacks.
scope "/api", HelloWeb do
pipe_through :api
resources "/reviews", ReviewController
end
# ...
end
Когда сервер принимает запрос, сначала он всегда проходит через подключаемые модули конечной точки, после чего маршрутизатор пытается сопоставить путь и HTTP-метод.
Предположим, запрос соответствует первому маршруту: GET-запросу к /. Сначала маршрутизатор пропустит этот запрос через конвейер :browser, который получит данные сеанса и сообщения flash, а также выполнит защиту от подделки запросов, а затем передаст запрос действию home контроллера PageController.
Напротив, предположим, что запрос соответствует одному из маршрутов, определённых макросом resources/2. В этом случае маршрутизатор пропустит его через конвейер :api, который сейчас выполняет только согласование содержимого, а затем передаст запрос нужному действию контроллера HelloWeb.ReviewController.
Если ни один маршрут не соответствует запросу, конвейер не запускается и возникает ошибка 404.
Создание новых конвейеров
Phoenix позволяет создавать собственные конвейеры в любом месте маршрутизатора. Для этого нужно вызвать макрос pipeline/2, передав ему два аргумента: атом с именем нового конвейера и блок со всеми нужными подключаемыми модулями.
defmodule HelloWeb.Router do
use HelloWeb, :router
pipeline :browser do
plug :accepts, ["html"]
plug :fetch_session
plug :fetch_live_flash
plug :put_root_layout, html: {HelloWeb.Layouts, :root}
plug :protect_from_forgery
plug :put_secure_browser_headers
end
pipeline :auth do
plug HelloWeb.Authentication
end
scope "/reviews", HelloWeb do
pipe_through [:browser, :auth]
resources "/", ReviewController
end
end
В примере выше предполагается, что существует подключаемый модуль HelloWeb.Authentication, выполняющий аутентификацию, и теперь он входит в конвейер :auth.
Обратите внимание, что сами конвейеры тоже являются подключаемыми модулями, поэтому один конвейер можно подключить к другому. Например, можно переписать показанный выше конвейер auth так, чтобы он автоматически запускал browser, упростив вызов последующего конвейера:
pipeline :auth do
plug :browser
plug :ensure_authenticated_user
plug :ensure_user_owns_review
end
scope "/reviews", HelloWeb do
pipe_through :auth
resources "/", ReviewController
end
Как организовать маршруты?
В Phoenix мы обычно определяем несколько конвейеров, каждый из которых предоставляет определённую функциональность. Например, конвейеры :browser и :api предназначены для определённых клиентов: браузеров и HTTP-клиентов соответственно.
Возможно, ещё важнее то, что часто определяют конвейеры для аутентификации и авторизации. Например, один конвейер может требовать, чтобы все пользователи прошли аутентификацию. Другой может ограничивать доступ к определённым маршрутам только администраторами.
Определив конвейеры, вы повторно используете их в нужных областях видимости и группируете маршруты в соответствии с этими конвейерами. Вернёмся к примеру с отзывами. Допустим, читать отзыв может любой пользователь, но создавать отзывы могут только прошедшие аутентификацию. Маршруты могут выглядеть так:
pipeline :browser do ... end pipeline :auth do plug HelloWeb.Authentication end scope "/" do pipe_through [:browser] get "/reviews", PostController, :index get "/reviews/:id", PostController, :show end scope "/" do pipe_through [:browser, :auth] get "/reviews/new", PostController, :new post "/reviews", PostController, :create end
Обратите внимание, что в примере выше маршруты распределены по разным областям видимости. Поначалу такое разделение может сбивать с толку, но у него есть большое преимущество: можно легко просмотреть маршруты и определить, какие из них требуют аутентификации, а какие — нет. Это помогает проводить аудит и проверять, что маршруты находятся в подходящих областях видимости.
Можно создавать любое количество областей видимости. Поскольку конвейеры можно повторно использовать в разных областях, они помогают инкапсулировать общую функциональность, которую можно компоновать по мере необходимости для каждой области.
Перенаправление
Макрос Phoenix.Router.forward/4 позволяет передавать все запросы, начинающиеся с определённого пути, определённому подключаемому модулю. Допустим, за запуск фоновых задач отвечает отдельная часть нашей системы (это может быть даже отдельное приложение или библиотека), у которой может быть собственный веб-интерфейс для проверки состояния задач. Перенаправить запросы в этот интерфейс администратора можно так:
defmodule HelloWeb.Router do
use HelloWeb, :router
...
scope "/", HelloWeb do
...
end
forward "/jobs", BackgroundJob.Plug
end
Это означает, что все маршруты, начинающиеся с /jobs, будут передаваться модулю BackgroundJob.Plug. Внутри подключаемого модуля можно сопоставлять подмаршруты, например /pending и /active, которые показывают состояние определённых задач.
Макрос forward/4 можно сочетать с конвейерами. Если для доступа к странице задач требуется аутентификация пользователя и наличие у него прав администратора, в маршрутизаторе можно использовать следующий код.
defmodule HelloWeb.Router do
use HelloWeb, :router
...
scope "/" do
pipe_through [:authenticate_user, :ensure_admin]
forward "/jobs", BackgroundJob.Plug
end
end
Это означает, что подключаемые модули конвейеров authenticate_user и ensure_admin будут вызваны до BackgroundJob.Plug, что позволит им отправить подходящий ответ и при необходимости остановить обработку запроса.
BackgroundJob.Plug можно реализовать как любой «подключаемый модуль-модуль», рассмотренный в руководстве по Plug. Однако перенаправлять запросы на другую конечную точку Phoenix не рекомендуется. Подключаемые модули приложения и конечной точки, на которую выполнено перенаправление, будут вызваны дважды, что может привести к ошибкам.
Итоги
Маршрутизация — большая тема, и мы рассмотрели здесь немало материала. Вот основные выводы из этого руководства:
- Маршруты, начинающиеся с имени HTTP-метода, раскрываются в одну ветвь функции сопоставления.
- Маршруты, объявленные с помощью
resources, раскрываются в 8 ветвей функции сопоставления. - Число ветвей функции сопоставления для ресурсов можно ограничить с помощью параметров
only:илиexcept:. - Любые из этих маршрутов можно вкладывать друг в друга.
- Любые из этих маршрутов можно поместить в область видимости с заданным путём.
- Используйте проверенные маршруты с
~pдля проверки маршрутов во время компиляции.
© 2014 Chris McCord
Licensed under the MIT License.
https://phoenix.hexdocs.pm/routing.html