查看源代码 Phoenix.Router (Phoenix v1.7.14)
定义 Phoenix 路由器。
路由器提供了一组宏,用于生成将请求分发到特定控制器和操作的路由。这些宏以 HTTP 动词命名。例如
defmodule MyAppWeb.Router do
use Phoenix.Router
get "/pages/:page", PageController, :show
end
上面的 get/3
宏接收对 /pages/hello
的请求,并将它分发到 PageController
的 show
操作,并将 %{"page" => "hello"}
放入 params
中。
Phoenix 的路由器非常高效,因为它依靠 Elixir 模式匹配来匹配路由和服务请求。
路由
get/3
、post/3
、put/3
以及其他以 HTTP 动词命名的宏用于创建路由。
路由
get "/pages", PageController, :index
匹配对 /pages
的 GET
请求,并将它分发到 PageController
中的 index
操作。
get "/pages/:page", PageController, :show
匹配 /pages/hello
,并将它分发到 show
操作,并将 %{"page" => "hello"}
放入 params
中。
defmodule PageController do
def show(conn, params) do
# %{"page" => "hello"} == params
end
end
可以匹配部分和多个片段。例如
get "/api/v:version/pages/:id", PageController, :show
匹配 /api/v1/pages/2
,并将 %{"version" => "1", "id" => "2"}
放入 params
中。只有片段的尾部可以被捕获。
路由从上到下匹配。这里的第二个路由
get "/pages/:page", PageController, :show
get "/pages/hello", PageController, :hello
永远不会匹配 /pages/hello
,因为 /pages/:page
首先匹配它。
路由可以使用类似通配符的模式来匹配尾随片段。
get "/pages/*page", PageController, :show
匹配 /pages/hello/world
,并将通配符片段放入 params["page"]
中。
GET /pages/hello/world
%{"page" => ["hello", "world"]} = params
通配符不能有前缀或后缀,但可以与变量混合使用
get "/pages/he:page/*rest", PageController, :show
匹配
GET /pages/hello
%{"page" => "llo", "rest" => []} = params
GET /pages/hey/there/world
%{"page" => "y", "rest" => ["there" "world"]} = params
为什么使用宏?
Phoenix 尽力减少宏的使用。然而,您可能已经注意到,
Phoenix.Router
严重依赖于宏。为什么呢?我们使用
get
、post
、put
和delete
来定义您的路由。我们使用宏有两种目的
它们定义了路由引擎,该引擎用于每个请求,以选择将请求分发到哪个控制器。由于使用了宏,Phoenix 将所有路由编译到一个带有模式匹配规则的单个 case 语句中,该语句由 Erlang VM 进行了高度优化
对于您定义的每个路由,我们还定义元数据以实现
Phoenix.VerifiedRoutes
。正如我们很快将要了解的,已验证的路由允许我们将任何路由引用为一个普通字符串,不同的是它经过编译器的验证,确保它是有效的(这使得将损坏的链接、表单、邮件等发送到生产环境变得更加困难)换句话说,路由器依赖于宏来构建更快、更安全的应用程序。还要记住,Elixir 中的宏只是编译时宏,这在代码编译后提供了极大的稳定性。Phoenix 还通过
mix phx.routes
为所有定义的路由提供自省。
生成路由
有关在应用程序中生成路由的信息,请参阅 Phoenix.VerifiedRoutes
文档,了解基于 ~p
的路由生成,这是使用编译时验证生成路由路径和 URL 的首选方法。
Phoenix 还支持生成函数帮助程序,这是 Phoenix v1.6 及更早版本中的默认机制。我们将在下面进行探讨。
帮助程序(已弃用)
默认情况下,Phoenix 会在您的路由器中生成一个名为 Helpers
的模块,该模块包含命名帮助程序,帮助开发人员生成和保持其路由的最新状态。可以通过将 helpers: false
传递给 use Phoenix.Router
来禁用帮助程序。
帮助程序会根据控制器名称自动生成。例如,路由
get "/pages/:page", PageController, :show
将生成以下命名帮助程序
MyAppWeb.Router.Helpers.page_path(conn_or_endpoint, :show, "hello")
"/pages/hello"
MyAppWeb.Router.Helpers.page_path(conn_or_endpoint, :show, "hello", some: "query")
"/pages/hello?some=query"
MyAppWeb.Router.Helpers.page_url(conn_or_endpoint, :show, "hello")
"http://example.com/pages/hello"
MyAppWeb.Router.Helpers.page_url(conn_or_endpoint, :show, "hello", some: "query")
"http://example.com/pages/hello?some=query"
如果路由包含类似通配符的模式,则必须将这些模式的参数作为列表提供
MyAppWeb.Router.Helpers.page_path(conn_or_endpoint, :show, ["hello", "world"])
"/pages/hello/world"
在命名 URL 帮助程序中生成的 URL 基于 :url
、:http
和 :https
的配置。但是,如果您需要手动控制 URL 生成,URL 帮助程序也允许您传入一个 URI
结构体
uri = %URI{scheme: "https", host: "other.example.com"}
MyAppWeb.Router.Helpers.page_url(uri, :show, "hello")
"https://other.example.com/pages/hello"
可以使用 :as
选项自定义命名帮助程序。给定路由
get "/pages/:page", PageController, :show, as: :special_page
命名帮助程序将是
MyAppWeb.Router.Helpers.special_page_path(conn, :show, "hello")
"/pages/hello"
作用域和资源
在 Phoenix 应用程序中,将所有路由命名空间在应用程序作用域下是很常见的
scope "/", MyAppWeb do
get "/pages/:id", PageController, :show
end
上面的路由将分发到 MyAppWeb.PageController
。这种语法不仅对开发人员来说很方便,因为我们不必在所有路由上重复 MyAppWeb.
前缀,而且它还允许 Phoenix 减少对 Elixir 编译器的压力。如果我们改为编写
get "/pages/:id", MyAppWeb.PageController, :show
Elixir 编译器将推断路由器直接依赖于 MyAppWeb.PageController
,但事实并非如此。通过使用作用域,Phoenix 可以正确地提示 Elixir 编译器控制器不是路由器的实际依赖项。这将提供更快的编译时间。
作用域允许我们在任何路径上,甚至在帮助程序名称上进行作用域
scope "/v1", MyAppWeb, host: "api." do
get "/pages/:id", PageController, :show
end
例如,上面的路由将在路径 "/api/v1/pages/1"
上匹配,命名路由将是 api_v1_page_path
,这与提供给 scope/2
选项的值相符。
与所有路径一样,您可以定义动态片段,这些片段将在控制器中作为参数应用
scope "/api/:version", MyAppWeb do
get "/pages/:id", PageController, :show
end
例如,上面的路由将在路径 "/api/v1/pages/1"
上匹配,并且在控制器中,params
参数将包含一个键为 :version
、值为 "v1"
的映射。
Phoenix 还提供了一个 resources/4
宏,允许开发人员为给定资源生成“RESTful”路由
defmodule MyAppWeb.Router do
use Phoenix.Router
resources "/pages", PageController, only: [:show]
resources "/users", UserController, except: [:delete]
end
最后,Phoenix 附带了一个 mix phx.routes
任务,它可以很好地格式化给定路由器中的所有路由。我们可以使用它来验证上面路由器中包含的所有路由
$ mix phx.routes
page_path GET /pages/:id PageController.show/2
user_path GET /users UserController.index/2
user_path GET /users/:id/edit UserController.edit/2
user_path GET /users/new UserController.new/2
user_path GET /users/:id UserController.show/2
user_path POST /users UserController.create/2
user_path PATCH /users/:id UserController.update/2
PUT /users/:id UserController.update/2
还可以将路由器作为参数显式传递给任务
$ mix phx.routes MyAppWeb.Router
有关更多信息,请查看 scope/2
和 resources/4
。
管道和插头
当请求到达 Phoenix 路由器时,它会通过管道进行一系列转换,直到请求被分发到所需的路由。
此类转换是通过插头定义的,如 Plug 规范中所定义的那样。一旦管道被定义,它就可以通过每个作用域进行管道化。
例如
defmodule MyAppWeb.Router do
use Phoenix.Router
pipeline :browser do
plug :fetch_session
plug :accepts, ["html"]
end
scope "/" do
pipe_through :browser
# browser related routes and resources
end
end
Phoenix.Router
从 Plug.Conn
和 Phoenix.Controller
中导入函数,以帮助定义插头。在上面的示例中,fetch_session/2
来自 Plug.Conn
,而 accepts/2
来自 Phoenix.Controller
。
请注意,路由器管道只在找到路由后才会调用。如果找不到匹配项,则不会调用任何插头。
如何组织我的路由?
在 Phoenix 中,我们倾向于定义几个管道,这些管道提供特定的功能。例如,上面的 pipeline :browser
包含适用于所有旨在通过浏览器访问的路由的插头。类似地,如果您还提供 :api
请求,您将拥有一个单独的 :api
管道来验证特定于您的端点的信息。
也许更重要的是,定义特定于身份验证和授权的管道也很常见。例如,您可能有一个管道要求所有用户都经过身份验证。另一个管道可能强制只有管理员用户才能访问某些路由。由于路由从上到下匹配,因此建议将经过身份验证/授权的路由放置在限制较少的路由之前,以确保它们首先匹配。
一旦您的管道被定义,您就在所需的作用域中重复使用这些管道,将您的路由分组到它们的管道周围。例如,假设您正在构建一个博客。任何人都可以阅读帖子,但只有经过身份验证的用户才能创建帖子。您的路由可能如下所示
pipeline :browser do
plug :fetch_session
plug :accepts, ["html"]
end
pipeline :auth do
plug :ensure_authenticated
end
scope "/" do
pipe_through [:browser, :auth]
get "/posts/new", PostController, :new
post "/posts", PostController, :create
end
scope "/" do
pipe_through [:browser]
get "/posts", PostController, :index
get "/posts/:id", PostController, :show
end
请注意上面的路由如何分布在不同的作用域中。虽然这种分离一开始可能会令人困惑,但它有一个很大的好处:非常容易检查您的路由,并查看例如哪些路由需要身份验证,哪些路由不需要。这有助于审核并确保您的路由具有适当的作用域。
您可以创建任意数量的作用域。由于管道可以在作用域之间重复使用,因此它们有助于封装公共功能,并且您可以在定义的每个作用域上按需组合它们。
摘要
函数
生成一个路由,以处理对给定路径的连接请求。
生成一个路由,以处理对给定路径的删除请求。
将给定路径上的请求转发到插头。
生成一个路由,以处理对给定路径的 get 请求。
生成一个路由,以处理对给定路径的 head 请求。
根据任意 HTTP 方法生成路由匹配。
生成一个路由来处理对给定路径的 options 请求。
生成一个路由来处理对给定路径的 patch 请求。
定义一个连接将要经过的插件(和管道)列表。
定义一个插件管道。
在管道内定义一个插件。
生成一个路由来处理对给定路径的 post 请求。
生成一个路由来处理对给定路径的 put 请求。
为资源定义“RESTful”路由。
从给定的路由器返回所有路由信息。
定义一个范围,其中路由可以嵌套。
定义一个具有给定路径的范围。
定义一个具有给定路径和别名的范围。
生成一个路由来处理对给定路径的 trace 请求。
反射
返回请求的编译时路由信息和运行时路径参数。
该 path
可以是字符串,也可以是 path_info
段。
返回一个包含以下键的元数据映射
:log
- 配置的日志级别。例如:debug
:path_params
- 运行时路径参数的映射:pipe_through
- 路由范围的管道列表,例如[:browser]
:plug
- 要将路由分派到的插件,例如AppWeb.PostController
:plug_opts
- 调用插件时要传递的选项,例如::index
:route
- 字符串路由模式,例如"/posts/:id"
示例
iex> Phoenix.Router.route_info(AppWeb.Router, "GET", "/posts/123", "myhost")
%{
log: :debug,
path_params: %{"id" => "123"},
pipe_through: [:browser],
plug: AppWeb.PostController,
plug_opts: :show,
route: "/posts/:id",
}
iex> Phoenix.Router.route_info(MyRouter, "GET", "/not-exists", "myhost")
:error
返回带有当前作用域的别名前缀的完整别名。
对于将相同的简写别名处理应用于除路由定义中第二个参数以外的其他值很有用。
示例
scope "/", MyPrefix do
get "/", ProxyPlug, controller: scoped_alias(__MODULE__, MyController)
end
返回带有当前作用域的路径前缀的完整路径。
函数
生成一个路由,以处理对给定路径的连接请求。
connect("/events/:id", EventController, :action)
参见 match/5
获取选项。
生成一个路由,以处理对给定路径的删除请求。
delete("/events/:id", EventController, :action)
参见 match/5
获取选项。
将给定路径上的请求转发到插头。
所有与转发前缀匹配的路径都将发送到转发的插件。这对于在应用程序之间共享路由器,甚至将大型路由器拆分为更小的路由器很有用。路由器管道将在转发连接之前调用。
但是,我们不建议转发到另一个端点。原因是您的应用程序和转发端点定义的插件将被调用两次,这可能会导致错误。
示例
scope "/", MyApp do
pipe_through [:browser, :admin]
forward "/admin", SomeLib.AdminDashboard
forward "/api", ApiRouter
end
生成一个路由,以处理对给定路径的 get 请求。
get("/events/:id", EventController, :action)
参见 match/5
获取选项。
生成一个路由,以处理对给定路径的 head 请求。
head("/events/:id", EventController, :action)
参见 match/5
获取选项。
根据任意 HTTP 方法生成路由匹配。
对于定义不在内置宏中的路由很有用。
通配符动词 :*
也可以用于匹配所有 HTTP 方法。
选项
:as
- 配置命名助手。如果为nil
,则不生成助手。在专门使用已验证路由时无效:alias
- 配置是否应将作用域别名应用于路由。默认为 true,如果为 false,则禁用作用域。:log
- 用于记录路由分派的级别,可以设置为 false。默认为:debug
。路由分派包含有关如何处理路由的信息(调用哪个控制器操作、哪些参数可用以及使用了哪些管道),并且与插件级别的日志记录分开。要更改插件日志级别,请参见 https://hexdocs.erlang.ac.cn/phoenix/Phoenix.Logger.html#module-dynamic-log-level。:private
- 当路由匹配时要合并到连接中的私有数据映射:assigns
- 当路由匹配时要合并到连接中的数据映射:metadata
- 电信事件使用的元数据映射,以及由route_info/4
返回:warn_on_verify
- 用于在Phoenix.VerifiedRoutes
中,是否匹配此路由触发未匹配路由警告的布尔值。它对于忽略在验证路由时与之匹配的否则为通配符的路由定义很有用。默认为false
。
示例
match(:move, "/events/:id", EventController, :move)
match(:*, "/any", SomeController, :any)
生成一个路由来处理对给定路径的 options 请求。
options("/events/:id", EventController, :action)
参见 match/5
获取选项。
生成一个路由来处理对给定路径的 patch 请求。
patch("/events/:id", EventController, :action)
参见 match/5
获取选项。
定义一个连接将要经过的插件(和管道)列表。
插件使用任何导入的 2 元函数的原子名称来指定,该函数接受一个 %Plug.Conn{}
和选项并返回一个 %Plug.Conn{}
;例如,:require_authenticated_user
。
管道在路由器中定义;有关更多信息,请参见 pipeline/2
。
pipe_through [:my_imported_function, :my_pipeline]
定义一个插件管道。
管道在路由器根目录中定义,并且可以从任何作用域使用。
示例
pipeline :api do
plug :token_authentication
plug :dispatch
end
然后,作用域可以使用此管道作为
scope "/" do
pipe_through :api
end
每次调用 pipe_through/1
时,都会将新的管道附加到之前给出的管道。
在管道内定义一个插件。
有关更多信息,请参见 pipeline/2
。
生成一个路由来处理对给定路径的 post 请求。
post("/events/:id", EventController, :action)
参见 match/5
获取选项。
生成一个路由来处理对给定路径的 put 请求。
put("/events/:id", EventController, :action)
参见 match/5
获取选项。
参见 resources/4
。
参见 resources/4
。
为资源定义“RESTful”路由。
给定的定义
resources "/users", UserController
将包括到以下操作的路由
GET /users
=>:index
GET /users/new
=>:new
POST /users
=>:create
GET /users/:id
=>:show
GET /users/:id/edit
=>:edit
PATCH /users/:id
=>:update
PUT /users/:id
=>:update
DELETE /users/:id
=>:delete
选项
此宏接受一组选项
:only
- 要为其生成路由的操作列表,例如:[:show, :edit]
:except
- 要排除生成路由的操作列表,例如:[:delete]
:param
- 此资源的参数名称,默认为"id"
:name
- 此资源的前缀。这用于命名助手,并作为嵌套资源中参数的前缀。默认值会自动从控制器名称派生,即UserController
将具有名称"user"
:as
- 配置命名助手。如果为nil
,则不生成助手。在专门使用已验证路由时无效:singleton
- 为单例资源定义路由,这些资源由客户端在不引用 ID 的情况下查找。阅读以下内容以了解更多信息
单例资源
当资源需要在不引用 ID 的情况下查找时,因为它在给定上下文中只包含一个条目,则可以使用 :singleton
选项来生成一组特定于此类单个资源的路由
GET /user
=>:show
GET /user/new
=>:new
POST /user
=>:create
GET /user/edit
=>:edit
PATCH /user
=>:update
PUT /user
=>:update
DELETE /user
=>:delete
使用示例
resources "/account", AccountController, only: [:show], singleton: true
嵌套资源
此宏还支持传递嵌套的路由定义块。这有助于将子资源嵌套在其父级中以生成嵌套路由。
给定的定义
resources "/users", UserController do
resources "/posts", PostController
end
将包括以下路由
user_post_path GET /users/:user_id/posts PostController :index
user_post_path GET /users/:user_id/posts/:id/edit PostController :edit
user_post_path GET /users/:user_id/posts/new PostController :new
user_post_path GET /users/:user_id/posts/:id PostController :show
user_post_path POST /users/:user_id/posts PostController :create
user_post_path PATCH /users/:user_id/posts/:id PostController :update
PUT /users/:user_id/posts/:id PostController :update
user_post_path DELETE /users/:user_id/posts/:id PostController :delete
从给定的路由器返回所有路由信息。
定义一个范围,其中路由可以嵌套。
示例
scope path: "/api/v1", alias: API.V1 do
get "/pages/:id", PageController, :show
end
上面生成的路由将匹配路径 "/api/v1/pages/:id"
,并将分派到 API.V1.PageController
中的 :show
操作。还会生成一个名为 api_v1_page_path
的命名助手。
选项
支持的选项是
:path
- 包含路径范围的字符串。:as
- 包含命名助手范围的字符串或原子。设置为 false 时,它会重置嵌套的助手范围。当仅使用经过验证的路由时,它没有效果。:alias
- 包含控制器范围的别名(原子)。设置为 false 时,它会重置所有嵌套的别名。:host
- 包含主机范围或前缀主机范围的字符串或字符串列表,例如"foo.bar.com"
,"foo."
:private
- 当路由匹配时要合并到连接中的私有数据映射:assigns
- 当路由匹配时要合并到连接中的数据映射:log
- 用于记录路由分派的级别,可以设置为 false。默认为:debug
。路由分派包含有关如何处理路由的信息(调用哪个控制器操作、哪些参数可用以及使用了哪些管道),并且与插件级别的日志记录分开。要更改插件日志级别,请参见 https://hexdocs.erlang.ac.cn/phoenix/Phoenix.Logger.html#module-dynamic-log-level。
定义一个具有给定路径的范围。
此函数是以下内容的快捷方式
scope path: path do
...
end
示例
scope "/v1", host: "api." do
get "/pages/:id", PageController, :show
end
定义一个具有给定路径和别名的范围。
此函数是以下内容的快捷方式
scope path: path, alias: alias do
...
end
示例
scope "/v1", API.V1, host: "api." do
get "/pages/:id", PageController, :show
end
生成一个路由来处理对给定路径的 trace 请求。
trace("/events/:id", EventController, :action)
参见 match/5
获取选项。