Пользовательские страницы ошибок
Исходный кодВ новых проектах Phoenix есть два представления ошибок с именами ErrorHTML и ErrorJSON, которые находятся в lib/hello_web/controllers/. Эти представления предназначены для обработки ошибок каждого формата в общем виде из одного централизованного места.
Представления ошибок
В новых приложениях представления ErrorHTML и ErrorJSON выглядят так:
defmodule HelloWeb.ErrorHTML do
use HelloWeb, :html
# If you want to customize your error pages,
# uncomment the embed_templates/1 call below
# and add pages to the error directory:
#
# * lib/<%= @lib_web_name %>/controllers/error_html/404.html.heex
# * lib/<%= @lib_web_name %>/controllers/error_html/500.html.heex
#
# embed_templates "error_html/*"
# The default is to render a plain text page based on
# the template name. For example, "404.html" becomes
# "Not Found".
def render(template, _assigns) do
Phoenix.Controller.status_message_from_template(template)
end
end
defmodule HelloWeb.ErrorJSON do
# If you want to customize a particular status code,
# you may add your own clauses, such as:
#
# def render("500.json", _assigns) do
# %{errors: %{detail: "Internal Server Error"}}
# end
# By default, Phoenix returns the status message from
# the template name. For example, "404.json" becomes
# "Not Found".
def render(template, _assigns) do
%{errors: %{detail: Phoenix.Controller.status_message_from_template(template)}}
end
end
Прежде чем перейти к подробностям, посмотрим, как сообщение 404 Not Found выглядит в браузере. В среде разработки Phoenix по умолчанию отлаживает ошибки и показывает очень информативную страницу отладки. Однако здесь мы хотим увидеть страницу, которую приложение отобразило бы в рабочей среде. Для этого нужно установить debug_errors: false в config/dev.exs.
import Config config :hello, HelloWeb.Endpoint, ..., debug_errors: false, code_reloader: true, ...
После изменения файла конфигурации нужно перезапустить сервер, чтобы изменения вступили в силу. После перезапуска сервера откроем http://localhost:4000/such/a/wrong/path в запущенном локальном приложении и посмотрим, что получится.
Ну, это не очень впечатляет. Мы видим обычную строку "Not Found" без разметки и оформления.
Первый вопрос: откуда берётся эта строка ошибки? Ответ находится прямо в ErrorHTML.
def render(template, _assigns) do Phoenix.Controller.status_message_from_template(template) end
Отлично, у нас есть функция render/2, которая принимает шаблон и карту assigns, которую мы игнорируем. Когда вы вызываете render(conn, :some_template) из контроллера, Phoenix сначала ищет функцию some_template/1 в модуле представления. Если такой функции нет, он вызывает render/2, передавая шаблон и имя формата, например "some_template.html".
Иными словами, чтобы создать пользовательские страницы ошибок, достаточно определить подходящую ветвь функции render/2 в HelloWeb.ErrorHTML.
def render("404.html", _assigns) do
"Page Not Found"
end
Но можно сделать ещё лучше.
Phoenix генерирует для нас ErrorHTML, но не создаёт каталог lib/hello_web/controllers/error_html. Давайте создадим его. В новом каталоге добавим шаблон с именем 404.html.heex и разметку — сочетание макета приложения и нового <div> с сообщением для пользователя.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1"/>
<title>Welcome to Phoenix!</title>
<link rel="stylesheet" href="/assets/css/app.css"/>
<script defer type="text/javascript" src="/assets/js/app.js"></script>
</head>
<body>
<header>
<section class="container">
<nav>
<ul>
<li><a href="https://phoenix.hexdocs.pm/overview.html">Get Started</a></li>
</ul>
</nav>
<a href="https://phoenixframework.org/" class="phx-logo">
<img src="/images/logo.svg" alt="Phoenix Framework Logo"/>
</a>
</section>
</header>
<main class="container">
<section class="phx-hero">
<p>Sorry, the page you are looking for does not exist.</p>
</section>
</main>
</body>
</html>
После создания файла шаблона не забудьте удалить соответствующую ветвь render/2 для этого шаблона, иначе функция переопределит шаблон. Сделаем это для ветви 404.html, которую мы ранее добавили в lib/hello_web/controllers/error_html.ex. Также нужно указать Phoenix, что шаблоны необходимо встроить в модуль:
+ embed_templates "error_html/*"
- def render("404.html", _assigns) do
- "Page Not Found"
- end
Теперь, если вернуться на http://localhost:4000/such/a/wrong/path, мы увидим гораздо более приятную страницу ошибки. Стоит отметить, что мы не отображали шаблон 404.html.heex через макет приложения, хотя хотим, чтобы наша страница ошибки выглядела так же, как и остальные страницы сайта. Это нужно, чтобы избежать циклических ошибок. Например, что произойдёт, если приложение завершилось с ошибкой из-за ошибки в макете? Повторная попытка отобразить макет приведёт к новой ошибке. Поэтому в идеале нужно свести к минимуму зависимости и логику в шаблонах ошибок, используя только самое необходимое.
Пользовательские исключения
В Elixir есть макрос defexception/1 для определения пользовательских исключений. Исключения представлены структурами, а структуры нужно определять внутри модулей.
Чтобы создать пользовательское исключение, нужно определить новый модуль. По соглашению, в его названии используется слово "Error". Внутри этого модуля нужно определить новое исключение с помощью defexception/1; файл lib/hello_web.ex кажется подходящим местом для него.
defmodule HelloWeb.SomethingNotFoundError do defexception [:message] end
Новое исключение можно вызвать так:
raise HelloWeb.SomethingNotFoundError, "oops"
По умолчанию Plug и Phoenix считают все исключения ошибками 500. Однако в Plug есть протокол Plug.Exception, с помощью которого можно настроить статус и добавить действия, которые структуры исключений могут возвращать на странице отладки ошибок.
Если для ошибки HelloWeb.SomethingNotFoundError нужно указать статус 404, можно определить реализацию протокола Plug.Exception в lib/hello_web.ex следующим образом:
defimpl Plug.Exception, for: HelloWeb.SomethingNotFoundError do def status(_exception), do: 404 def actions(_exception), do: [] end
Также можно определить поле plug_status непосредственно в структуре исключения:
defmodule HelloWeb.SomethingNotFoundError do defexception [:message, plug_status: 404] end
Однако ручная реализация протокола Plug.Exception может быть удобна в некоторых случаях, например при создании ошибок с предлагаемыми действиями.
Ошибки с предлагаемыми действиями
Действия исключений — это функции, которые можно вызвать со страницы ошибки. По сути, это список карт, задающих label и handler для выполнения. Например, Phoenix отобразит ошибку, если у вас есть ожидающие миграции, и добавит на страницу ошибки кнопку для их выполнения.
Если debug_errors равно true, они отображаются на странице ошибки в виде набора кнопок и имеют следующий формат:
[
%{
label: String.t(),
handler: {module(), function :: atom(), args :: []}
}
]
Чтобы вернуть действия для HelloWeb.SomethingNotFoundError, можно реализовать Plug.Exception следующим образом:
defimpl Plug.Exception, for: HelloWeb.SomethingNotFoundError do
def status(_exception), do: 404
def actions(_exception) do
[
%{
label: "Run seeds",
handler: {Code, :eval_file, ["priv/repo/seeds.exs"]}
}
]
end
end
© 2014 Chris McCord
Licensed under the MIT License.
https://phoenix.hexdocs.pm/custom_error_pages.html