Spec-Zone.ru › Phoenix

Жизненный цикл запроса

Исходный код

Требование: предполагается, что вы ознакомились с вводными руководствами и запустили приложение Phoenix, следуя руководству по запуску.

Цель этого руководства — рассказать о жизненном цикле запроса в Phoenix. Мы будем разбираться на практике: добавим в проект Phoenix две новые страницы и по ходу дела обсудим, как взаимодействуют составляющие приложения.

Давайте перейдём к созданию первой новой страницы Phoenix!

Добавление новой страницы

Когда браузер обращается к адресу http://localhost:4000/, он отправляет HTTP-запрос службе, запущенной по этому адресу, — в данном случае нашему приложению Phoenix. HTTP-запрос состоит из метода и пути. Например, следующие запросы браузера соответствуют таким значениям:

Адрес в адресной строке браузера Метод Путь
http://localhost:4000/ GET /
http://localhost:4000/hello GET /hello
http://localhost:4000/hello/world GET /hello/world

Существуют и другие HTTP-методы. Например, при отправке формы обычно используется метод POST.

Веб-приложения обычно обрабатывают запросы, сопоставляя каждую пару «метод/путь» с определённой частью приложения. В Phoenix это сопоставление выполняет маршрутизатор. Например, мы можем сопоставить путь "/articles" с частью приложения, которая показывает все статьи. Поэтому первая задача при добавлении новой страницы — добавить новый маршрут.

Новый маршрут

Маршрутизатор сопоставляет уникальные пары HTTP-метод/путь с парами контроллер/действие, которые будут их обрабатывать. Контроллеры в Phoenix — это просто модули Elixir. Действия — функции, определённые в этих контроллерах.

В новых приложениях Phoenix создаёт для нас файл маршрутизатора по пути lib/hello_web/router.ex. В этом разделе мы будем работать именно с ним.

Маршрут для страницы «Добро пожаловать в Phoenix!» из предыдущего руководства по запуску выглядит так:

get "/", PageController, :home

Разберём, что означает этот маршрут. При переходе по адресу http://localhost:4000/ отправляется HTTP-запрос GET к корневому пути. Все подобные запросы будут обрабатываться функцией home/2 модуля HelloWeb.PageController, определённого в lib/hello_web/controllers/page_controller.ex.

Страница, которую мы собираемся создать, будет выводить «Привет, мир от Phoenix!», когда мы перейдём в браузере по адресу http://localhost:4000/hello.

Сначала нужно добавить маршрут для новой страницы. Откроем 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

Пока оставим конвейеры и использование scope без внимания и сосредоточимся на добавлении маршрута. Мы обсудим их в руководстве по маршрутизации.

Добавим в маршрутизатор новый маршрут, который сопоставляет запрос GET для /hello с действием index ещё не созданного HelloWeb.HelloController внутри блока scope "/" do маршрутизатора:

scope "/", HelloWeb do
  pipe_through :browser

  get "/", PageController, :home
  get "/hello", HelloController, :index
end

Новый контроллер

Контроллеры — это модули Elixir, а действия — определённые в них функции Elixir. Действия собирают данные и выполняют задачи, необходимые для отображения страницы. В нашем маршруте указано, что нам нужен модуль HelloWeb.HelloController с функцией index/2.

Чтобы действие index заработало, создадим новый файл lib/hello_web/controllers/hello_controller.ex со следующим содержимым:

defmodule HelloWeb.HelloController do
  use HelloWeb, :controller

  def index(conn, _params) do
    render(conn, :index)
  end
end

Обсуждение use HelloWeb, :controller оставим для руководства по контроллерам. Пока сосредоточимся на действии index.

Все действия контроллера принимают два аргумента. Первый — conn, структура, содержащая множество данных о запросе. Второй — params, то есть параметры запроса. Здесь мы не используем params, поэтому добавляем перед ним _, чтобы избежать предупреждений компилятора.

Основная часть этого действия — render(conn, :index). Эта инструкция сообщает Phoenix, что нужно отобразить шаблон index. Модули, отвечающие за отображение, называются представлениями. По умолчанию представления Phoenix называются по имени контроллера (HelloController) и формата (в данном случае HTML), поэтому Phoenix ожидает, что существует модуль HelloWeb.HelloHTML, в котором определена функция index/1.

Новое представление

Представления Phoenix отвечают за презентационный слой. Например, ожидается, что результат отображения index будет полноценной HTML-страницей. Чтобы упростить себе задачу, мы часто используем шаблоны для создания таких HTML-страниц.

Создадим новое представление. Создайте lib/hello_web/controllers/hello_html.ex со следующим содержимым:

defmodule HelloWeb.HelloHTML do
  use HelloWeb, :html
end

Шаблоны для этого представления можно определить как функции в модуле или в отдельных файлах.

Начнём с определения функции:

defmodule HelloWeb.HelloHTML do
  use HelloWeb, :html

  def index(assigns) do
    ~H"""
    Hello!
    """
  end
end

Мы определили функцию, принимающую assigns в качестве аргументов, и использовали сигил ~H, чтобы задать содержимое для отображения. Внутри сигила ~H мы использовали язык шаблонов HEEx, название которого расшифровывается как «HTML+EEx». EEx — это библиотека для встраивания Elixir, поставляемая в составе самого Elixir. «HTML+EEx» — расширение EEx от Phoenix, учитывающее HTML и поддерживающее проверку HTML, компоненты и автоматическое экранирование значений. Последнее защищает от уязвимостей безопасности, таких как межсайтовый скриптинг, без дополнительных усилий с вашей стороны. Функцию, которая принимает assigns и возвращает шаблоны, мы называем функциональным компонентом.

Файл шаблона работает таким же образом. Попробуем создать шаблон в отдельном файле. Сначала удалим определённую выше функцию def index(assigns) и заменим её объявлением embed_templates:

defmodule HelloWeb.HelloHTML do
  use HelloWeb, :html

  embed_templates "hello_html/*"
end

Здесь мы указываем, что хотим встроить все шаблоны .heex из соседнего каталога hello_html в наш модуль в виде функциональных компонентов.

Теперь нужно добавить файлы в каталог lib/hello_web/controllers/hello_html. Файл шаблона имеет следующую структуру: NAME.FORMAT.TEMPLATING_LANGUAGE. В нашем случае создадим файл index.html.heex по пути lib/hello_web/controllers/hello_html/index.html.heex:

<section>
  <h2>Hello World, from Phoenix!</h2>
</section>

Phoenix обнаружит файл шаблона и скомпилирует его в функцию index(assigns), как и в предыдущем примере. Между этими двумя подходами нет различий во время выполнения или производительности.

Обратите внимание, что имя контроллера (HelloController) и представления (HelloHTML), а также соответствующие файлы hello_controller.ex и hello_html.ex следуют одному соглашению об именовании и связаны друг с другом. Каталог можно назвать как угодно, если обновить соответствующую настройку embed_templates, но лучше придерживаться соглашений.

lib/hello_web
├── controllers
│   ├── hello_controller.ex
│   ├── hello_html.ex
│   ├── hello_html
|       ├── index.html.heex   (NEW FILE!)

Теперь, когда у нас есть маршрут, контроллер, представление и шаблон, можно перейти в браузере по адресу http://localhost:4000/hello и увидеть приветствие от Phoenix!

Phoenix Greets Us

Если вы остановили сервер, перезапустить его можно с помощью задачи mix phx.server. Если сервер всё ещё работает, изменения должны примениться автоматически: Phoenix поддерживает горячую перезагрузку кода!

Макеты

Хотя файл index.html.heex состоит всего из одного тега section, мы получаем полноценный HTML-документ. На самом деле наш шаблон index отображается внутри отдельного макета: lib/hello_web/components/layouts/root.html.heex, содержащего базовый HTML-каркас страницы. Если открыть этот файл, внизу вы увидите строку примерно такого вида:

{@inner_content}

Эта строка вставляет наш шаблон в макет до отправки HTML в браузер. Корневой макет, как следует из названия, — это простой макет, содержащий в основном тег <head> и структуру, общую для всех страниц. Более функциональные элементы, такие как боковая панель, меню и прочее, относятся к макету приложения; мы рассмотрим их ниже.

От конечной точки до представлений

Создав первую страницу, мы уже начинаем понимать, как устроен жизненный цикл запроса. Теперь рассмотрим его целиком.

Все HTTP-запросы начинаются с конечной точки нашего приложения. Это модуль HelloWeb.Endpoint в файле lib/hello_web/endpoint.ex. Открыв файл конечной точки, вы увидите, что, как и маршрутизатор, она содержит множество вызовов plug. Plug — это библиотека и спецификация для объединения веб-приложений. Она играет важную роль в обработке запросов Phoenix; подробно мы обсудим её в следующем руководстве по Plug.

Пока достаточно знать, что каждый plug отвечает за отдельный этап обработки запроса. В файле конечной точки вы найдёте примерно такую основу:

defmodule HelloWeb.Endpoint do
  use Phoenix.Endpoint, otp_app: :hello

  plug Plug.Static, ...
  plug Plug.RequestId
  plug Plug.Telemetry, ...
  plug Plug.Parsers, ...
  plug Plug.MethodOverride
  plug Plug.Head
  plug Plug.Session, ...
  plug HelloWeb.Router
end

У каждого из этих plug есть своя задача, с которой мы познакомимся позже. Последний plug — это как раз модуль HelloWeb.Router. Благодаря ему конечная точка передаёт всю дальнейшую обработку запросов маршрутизатору. Как мы уже знаем, основная задача маршрутизатора — сопоставлять пары «метод/путь» с контроллерами. Затем контроллер указывает представлению отобразить шаблон.

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

  • конечная точка (Phoenix.Endpoint) — содержит общую начальную цепочку, через которую проходят все запросы. Если нужно выполнить какое-либо действие для каждого запроса, его следует добавить в конечную точку.

  • маршрутизатор (Phoenix.Router) — отвечает за перенаправление пар «метод/путь» к контроллерам. Маршрутизатор также позволяет ограничивать область действия функциональности. Например, для доступа к некоторым страницам приложения может потребоваться аутентификация пользователя, а для других — нет.

  • контроллер (Phoenix.Controller) — получает информацию о запросе, взаимодействует с бизнес-доменом приложения и подготавливает данные для презентационного слоя.

  • представление — обрабатывает структурированные данные от контроллера и преобразует их в формат для показа пользователям. Представления часто называют по формату содержимого, которое они отображают.

Кратко повторим, как взаимодействуют последние три компонента, добавив ещё одну страницу. На этот раз мы воспользуемся дополнительными возможностями, например компонентами макета и интерполяцией содержимого.

Ещё одна новая страница

Добавим приложению немного сложности. Мы создадим новую страницу, которая будет распознавать часть URL, называть её «посланником» и передавать через контроллер в шаблон, чтобы наш посланник мог поздороваться.

Как и в прошлый раз, сначала создадим новый маршрут.

Ещё один новый маршрут

В этом упражнении мы повторно используем HelloController, созданный на предыдущем шаге, и добавим новое действие show. Добавим строку сразу после последнего маршрута:

scope "/", HelloWeb do
  pipe_through :browser

  get "/", PageController, :home
  get "/hello", HelloController, :index
  get "/hello/:messenger", HelloController, :show
end

Обратите внимание на синтаксис :messenger в пути. Phoenix возьмёт значение, указанное в этой позиции URL, и преобразует его в параметр. Например, если перейти в браузере по адресу http://localhost:4000/hello/Frank, значением "messenger" будет "Frank".

Ещё одно новое действие

Запросы к нашему новому маршруту будут обрабатываться действием show контроллера HelloWeb.HelloController. Контроллер уже находится в lib/hello_web/controllers/hello_controller.ex, поэтому нам нужно лишь отредактировать его и добавить действие show. На этот раз нужно извлечь посланника из параметров, чтобы передать его в шаблон. Для этого добавим в контроллер функцию show:

def show(conn, %{"messenger" => messenger}) do
  render(conn, :show, messenger: messenger)
end

В теле действия show мы также передаём функции render третий аргумент — пару ключ-значение, где :messenger — ключ, а переменная messenger передаётся в качестве значения.

Если в теле действия нужен доступ ко всей карте параметров, связанной с переменной params, помимо связанной переменной messenger, можно определить show/2 следующим образом:

def show(conn, %{"messenger" => messenger} = params) do
  ...
end

Важно помнить, что ключи карты params всегда будут строками, а знак равенства означает не присваивание, а утверждение о сопоставлении с образцом.

Ещё один новый шаблон

Для завершения этой головоломки нам понадобится новый шаблон. Поскольку он предназначен для действия show контроллера HelloController, он будет находиться в каталоге lib/hello_web/controllers/hello_html и называться show.html.heex. Он будет удивительно похож на наш шаблон index.html.heex, но в нём нужно будет вывести имя посланника. Запишем новый шаблон, а затем разберём, что он делает:

<Layouts.app flash={@flash}>
  <section>
    <h2>Hello World, from {@messenger}!</h2>
  </section>
</Layouts.app>

Если перейти в браузере по адресу http://localhost:4000/hello/Frank, вы увидите страницу примерно такого вида:

Frank Greets Us from Phoenix

Разберём шаблон по частям. У него расширение .heex, которое означает HTML + Embedded Elixir. В шаблоне выше мы используем три возможности HEEx:

  • Назначенные значения (assigns), например @messenger и @flash — значения, которые контроллер передаёт представлению, вместе называются «назначенными значениями». Мы могли бы получить доступ к значению посланника через assigns.messenger и assigns.flash, но Phoenix предлагает гораздо более лаконичный синтаксис @ для использования в шаблонах.

  • Интерполяция содержимого, например {@messenger} — любой код Elixir между {...} будет выполнен, а полученное значение заменит тег в HTML-выводе.

  • Теги функциональных компонентов, например <Layouts.app> — шаблоны называются функциональными компонентами, потому что их можно объединять! В этом случае есть функция HelloWeb.Layouts.app, которая определяет макет приложения. Мы вызываем её, передавая собственное содержимое.

Также обратите внимание, как сочетаются эти три возможности: мы передаём назначенное значение @flash как значение Elixir компоненту <Layouts.app>. Как мы узнаем позже, flash-сообщения используются для отображения пользователю временных уведомлений, например сообщений об успехе или ошибке. Подробнее о них мы узнаем из руководства по компонентам и шаблонам HEEx.

Готово! Можете немного поэкспериментировать. Всё, что вы укажете после /hello/, появится на странице как имя вашего посланника.

← Предыдущая страница Структура каталогов
Следующая страница → Plug

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

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

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

Spec-Zone.ru

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