檢視原始碼 自訂錯誤頁面
新的 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 找不到
訊息是什麼樣子。Phoenix 預設會在開發環境中偵錯錯誤,並向我們顯示資訊豐富的偵錯頁面。然而,我們希望看到應用程式在正式環境中會提供什麼樣的頁面。為此,我們需要在 config/dev.exs
中設定 debug_errors: false
。
import Config
config :hello, HelloWeb.Endpoint,
http: [port: 4000],
debug_errors: false,
code_reloader: true,
. . .
在修改設定檔之後,我們需要重新啟動伺服器才能讓變更生效。重新啟動伺服器後,讓我們開啟一個正在執行的本機應用程式 https://127.0.0.1:4000/such/a/wrong/path,看看會出現什麼結果。
嗯,這沒什麼新鮮的。我們只會看到一個顯示在裸字串中的「找不到」,而沒有任何標記或樣式。
第一個問題是,此錯誤字串從何而來?答案就在 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"
。
換句話說,如果要提供自訂錯誤頁面,我們只要在 HelloWeb.ErrorHTML
中定義適當的 render/2
函式子句。
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/app.css"/>
<script defer type="text/javascript" src="/assets/app.js"></script>
</head>
<body>
<header>
<section class="container">
<nav>
<ul>
<li><a href="https://hexdocs.dev.org.tw/phoenix/overview.html">Get Started</a></li>
</ul>
</nav>
<a href="https://phoenix.dev.org.tw/" 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
條款,否則函數將覆寫範本。讓我們對先前在 lib/hello_web/controllers/error_html.ex
中引用的 404.html 條款這麼做。我們也需要告訴 Phoenix 將範本嵌入至模組
+ embed_templates "error_html/*"
- def render("404.html", _assigns) do
- "Page Not Found"
- end
現在,當我們回到 https://127.0.0.1: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 狀態,我們可以使用 lib/hello_web.ex
中的 Plug.Exception
協定定義一個實現,如下所示
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
協定在某些情況下會很方便,例如提供可行的錯誤。
可行的錯誤
例外處理動作是在錯誤頁面觸發的函數,它們基本上是定義 標籤
和處理常式
的清單,以便執行。例如,如果您有待處理的遷移,則 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