DesseraLab

Technology exploration and development

0%

Elixir Phoenix 笔记 - 路由

的确,Elixir 是一个好语言,它的函数式不如 Haskell 那么激烈,也不像 Rust 一类通用语言那样内敛。Elixir 的函数式是折中的,既要实用、又要优雅,同时,借由 Erlang VM 虚拟机的支持,它能够高性能地运行动态类型的代码。

Phoenix 是基于 Elixir 的 Web 框架,是我近期的学习重心。这个框架的学习资料很多,但中文资料较少且比较陈旧,我写这个系列文章的目的是记录我的学习内容。

路由流程

当我们访问 Phoenix 监听的端口,请求会经过 Router 传递给 Controller

让我们查看以下 Router 的定义(我的工程名为preflight,故模块名中带有Preflight):

该文件位于lib/${your_project_name}_web/router.ex

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
defmodule PreflightWeb.Router do
use PreflightWeb, :router

pipeline :browser do
plug :accepts, ["html"]
plug :fetch_session
plug :fetch_live_flash
plug :put_root_layout, html: {PreflightWeb.Layouts, :root}
plug :protect_from_forgery
plug :put_secure_browser_headers
end

pipeline :api do
plug :accepts, ["json"]
end

scope "/", PreflightWeb do
pipe_through :browser

get "/", PageController, :home
end

# Other scopes may use custom stacks.
# scope "/api", PreflightWeb do
# pipe_through :api
# end

# Enable LiveDashboard and Swoosh mailbox preview in development
if Application.compile_env(:preflight, :dev_routes) do
import Phoenix.LiveDashboard.Router

scope "/dev" do
pipe_through :browser

live_dashboard "/dashboard", metrics: PreflightWeb.Telemetry
forward "/mailbox", Plug.Swoosh.MailboxPreview
end
end
end

忽略大部分内容(因为现在的我看不懂),单单看这个模块的几个部分:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
defmodule PreflightWeb.Router do

# ...

# 域声明
scope "/", PreflightWeb do
pipe_through :browser

# 处理根目录的 get 请求
get "/", PageController, :home
end

# ...

end

真正定义了路由操作的是get "/", PageController, :home,它将访问根目录的GET请求交予PageContorller.home处理,我们来看一下该函数的定义:

该模块位于lib/${your_project_name}_web/controllers/page_controller.ex

1
2
3
4
5
6
7
defmodule PreflightWeb.PageController do
use PreflightWeb, :controller

def home(conn, _params) do
render(conn, :home, layout: false)
end
end

这个函数调用了render函数渲染页面。

等等,问题来了,页面呢?让我们重新审视以下这个函数,谁最像是那个页面?没错,一定是那个:home

这个原子变量指向PageHTML中声明的模板:

该模块位于lib/${your_project_name}_web/controllers/page_html.ex

1
2
3
4
5
6
7
8
9
10
defmodule PreflightWeb.PageHTML do
@moduledoc """
This module contains pages rendered by PageController.

See the `page_html` directory for all templates available.
"""
use PreflightWeb, :html

embed_templates "page_html/*"
end

该模块将page_html下的所有heex模板导入到当前目录,我们能够在该目录下发现home.html.heex,这就是我们真正的渲染对象。

路由宏

上文的get是 Phoenix 提供的宏,类似的,还有postputpatchdeleteoptionsconnecttracehead等。

我们可以通过mix phx.routes检查当前所有的路由:

1
2
3
4
5
6
7
8
9
10
11
mix phs.routes
GET / PreflightWeb.PageController :home
GET /dev/dashboard/css-:md5 Phoenix.LiveDashboard.Assets :css
GET /dev/dashboard/js-:md5 Phoenix.LiveDashboard.Assets :js
GET /dev/dashboard Phoenix.LiveDashboard.PageLive :home
GET /dev/dashboard/:page Phoenix.LiveDashboard.PageLive :page
GET /dev/dashboard/:node/:page Phoenix.LiveDashboard.PageLive :page
* /dev/mailbox Plug.Swoosh.MailboxPreview []
WS /live/websocket Phoenix.LiveView.Socket
GET /live/longpoll Phoenix.LiveView.Socket
POST /live/longpoll Phoenix.LiveView.Socket

除了默认的/路径外,还有一系列 Phoenix 预定义的路径。

resources

resources 是特殊的宏,目的是为了快速生成针对资源的增删改查:

1
2
3
4
5
6
7
8
9
10
defmodule PreflightWeb.Router do
# ...
scope "/", PreflightWeb do
pipe_through :browser
# ...

resources "/users", UserController
end
# ...
end

将生成以下路由:

1
2
3
4
5
6
7
8
GET     /users           PreflightWeb.UserController :index
GET /users/:id/edit PreflightWeb.UserController :edit
GET /users/new PreflightWeb.UserController :new
GET /users/:id PreflightWeb.UserController :show
POST /users PreflightWeb.UserController :create
PATCH /users/:id PreflightWeb.UserController :update
PUT /users/:id PreflightWeb.UserController :update
DELETE /users/:id PreflightWeb.UserController :delete

我们可以为该宏传入第三个配置参数来移除个别路由:

1
2
3
4
# 保留
resources "/posts", PostController, only: [:index, :show]
# 过滤
resources "/comments", CommentController, except: [:delete]

resources可以嵌套,将生成嵌套路由:

1
2
3
resources "/users", UserController do
resources "/posts", PostController
end

将生成以下结构:

1
2
3
4
5
6
7
8
GET     /users/:user_id/posts           PreflightWeb.PostController :index
GET /users/:user_id/posts/:id/edit PreflightWeb.PostController :edit
GET /users/:user_id/posts/new PreflightWeb.PostController :new
GET /users/:user_id/posts/:id PreflightWeb.PostController :show
POST /users/:user_id/posts PreflightWeb.PostController :create
PATCH /users/:user_id/posts/:id PreflightWeb.PostController :update
PUT /users/:user_id/posts/:id PreflightWeb.PostController :update
DELETE /users/:user_id/posts/:id PreflightWeb.PostController :delete

scope

scope可以声明 API 域:

1
2
3
4
scope "/admin, PreflightWeb do
pipe_through :browser
# ...
end

以上调用将该域内定义的请求路由至PreflightWeb对应的Controller中。