Spec-Zone.ru › Phoenix

Маршрутизация

Исходный код

Требование: предполагается, что вы ознакомились с вводными руководствами и запустили приложение 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 для проверки маршрутов во время компиляции.
← Предыдущая страница Plug
Следующая страница → Контроллеры

Пакет Hex Предпросмотр Hex (текущий файл) Скачать версию ePub

Создано с помощью ExDoc (v0.40.3) для языка программирования Elixir

© 2014 Chris McCord
Licensed under the MIT License.
https://phoenix.hexdocs.pm/routing.html

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API