路由分发
路由分发提供了一种简单的方式,使用模式匹配将 URL 映射到请求处理程序。如果其中一个模式与请求关联的 URL 匹配,则调用该的处理程序。
请求处理程序是一个函数,它可以接收 0 个或多个可以从请求中提取信息的参数(即 impl FromRequest),并返回一个可以转换为 HttpResponse 的值(即 impl Responder)。更多的信息可以参考 handler 章节。
资源配置
资源配置是向应用中添加新资源的行为。资源都有一个名称,作为生成 URL 时的标识符。资源的名称也允许开发者向已存在的资源添加路由。资源还会有一个 pattern,用于匹配 URL 的 PATH 部分 (在端口号之后的部分, 例如 URL http://localhost:8080/foo/bar?q=value 的 /foo/ba 部分)。不会匹配到 QUERY 部分(? 后面的部分,例如 http://localhost:8080/foo/bar?q=value 中的 q=value 部分).
App::route() 方法提供了注册路由的简单方式。该方法会向应用路由表中添加一个路由。该方法接收一个 path patter,HTTP 请求方法 和一个处理函数作为参数。route()
方法可以对多个路径调用多次,这样同一个资源路径下可以注册多个路由。
虽然 App::route() 提供了注册路由的简单方式,但要访问完整的资源配置,必须使用另一种方法。App::service() 方法将单个 资源 添加到应用路由表中。该方法接受 path pattern、guards 以及一个或多个路由。
如果访问的资源不包含任何路由或没有任何匹配的路由,则会返回 NOT FOUND HTTP 响应。
路由配置
资源包含一组路由。每个路由有包含一组守卫(guards
)和一个处理程序。可以使用 Resource::route()
方法创建新路由,该方法返回对新 路由 实例的引用。默认情况下,路由不包含任何 guards,因此可以匹配所有请求,而默认处理程序是 HttpNotFound
。
应用会根据资源注册和路由注册时定义的条件来路由传入的请求。资源会按照通过 Resource::route()
注册的路由顺序匹配其包含的所有路由。
一个路由可以包含任意数量的 守卫,但仅可以包含一个处理程序。
在这个例子中,如果 GET 请求的头部 Content-Type
的值为 text/plain,并且 URL 路径为 /path
,则返回 HttpResponse::Ok()
。
如果一个资源没有匹配到任何路由,则会响应 NOT FOUND。
ResourceHandler::route() 返回一个 Route 对象。路由可以使用类似构建器的模式进行配置。配置方法如下:
- Route::guard() 注册一个导航守卫。每一个路由都可以注册任意数量的守卫。
- Route::method() 注册一个请求方法。每个路由都可以注册任意数量的请求方法。
- Route::to() 注册一个异步的处理函数。只能注册一个处理函数,通常最后一个注册的处理函数会生效。
路由匹配
路由匹配的主要目的是将请求的 path
与 URL 路径模式进行匹配(或不匹配)。path
表示请求的 URL 的路径部分。
actix-web 实现这一功能的方法非常简单。每当一个请求到达,对于应用中存在的每个资源配置,actix 都会根据声明的默认对请求的路径进行检查。这种检查按照通过 App::service()
方法注册的路由的顺序进行。如果找不到资源,则使用 默认资源 作为匹配的资源。
声明路由配置时,可以包含路由守卫参数。与路由配置关联的所有路由守卫都必须为给定请求返回 true
,才能在检查期间使用该路由配置。如果在路由配置的路由守卫中的任一个守卫在检查期间返回 false
,则跳过该路由,并且路由匹配将继续通过路由模式的有序集合进行。
如果有路由匹配到,则匹配过程会停止,并调用与路由绑定的处理程序。如果所有的路由都检查了,仍然没有路由匹配到,则返回 NOT FOUND 响应。
资源 pattern 语法
actix 的模式匹配语法很简单。
路由配置中使用的模式可以以 /
开头。如果不以 /
开头,则在匹配时会自动添加 /
。例如,以下模式是等价的:
{foo}/bar/baz
和:
/{foo}/bar/baz
可变部分(替换标记)使用 {identifier} 的形式指定,意思是接收下一个 /
之前的任何字符,并将其用作 HttpRequest.match_info()
对象中的名称。
模式中的替换标记相与正则表达式 [^{}/]+
相匹配。
match_info
是 Params
的对象,代表根据路由模式从 URL 中提取的动态部分。它可以通过 request.match_info
使用。例如,下面的模式定义了一个文字段(foo)和两个替换标记(baz 和 bar):
foo/{baz}/{bar}
上面的模式将匹配这些 URL,并生成以下匹配信息:
foo/1/2 -> Params {'baz': '1', 'bar': '2'}
foo/abc/def -> Params {'baz': 'abc', 'bar': 'def'}
但它不匹配以下模式:
foo/1/2/ -> No match (trailing slash)
bar/abc/def -> First segment literal mismatch
对替换标记进行匹配时,最多只能匹配到模式中第一个非字母/数字字符。举例来说,如果使用以下路由模式:
foo/{name}.html
路径 /foo/biz.html
将与上面的路由模式匹配,匹配结果将是 Params {'name': 'biz'}
。但是路径 /foo/biz
将不会匹配,因为这个路径的末尾不包含 .html
,而是只包含 biz
。
为了捕获到这两个部分,可以使用两个替换标记:
foo/{name}.{ext}
路径 /foo/biz.html
将会与上面的模式匹配,匹配结果是 Params {'name': 'biz', 'ext': 'html'}
。这是因为在两个替换标记 {name}
和 {ext}
之间有一个字面量部分 .
(句点)。
替换标记部分也可以指定一个正则表达式,用于决定路径段是否与标记匹配。要指定替换标记只匹配正则表达式定义的特定字符集,必须使用扩展的替换标记语法。在大括号内,替换标记名称后必须跟一个冒号,然后直接跟正则表达式。默认情况下,普通替换比较所对应的正则表达式为 [^/]+
。例如,替换标记 {foo}
可以拼写为 {foo:[^/]+}
,你可以将其改为任意正则表达式,以匹配任意字符序列,例如,"{foo:\d+}`"只匹配数字。
路径段中至少包含一个字符,才能匹配到替换标记。例如,对于 URL /abc/
:
/abc/{foo}
不会匹配。/{foo}/
正确匹配。
提示:在匹配模式之前,路径将取消 URL 引号并解码为有效的 unicode 字符串,代表匹配路径段的值也将取消 URL 引号。
举例来说,下面的模式:
foo/{bar}
当匹配下面的 URL 时:
http://example.com/foo/La%20Pe%C3%B1a
匹配后的参数字典如下所示(值为解码后的):
Params {'bar': 'La Pe\xf1a'}
提供给 actix 的路径段中的字面量应该是解码后的值。不要在模式中使用 URL 编码的值。例如,不要使用这样的模式:
/Foo%20Bar/{baz}
你应该像下面这样指定:
/Foo Bar/{baz}
为了获得“尾部匹配”,必须使用自定义的正则表达式。
foo/{bar}/{tail:.*}
上面的模式将匹配这些 URL,并生成以下匹配信息:
foo/1/2/ -> Params {'bar': '1', 'tail': '2/'}
foo/abc/def/a/b/c -> Params {'bar': 'abc', 'tail': 'def/a/b/c'}
路由作用域
作用域可以帮助你组织共享公共跟路径的路由。也可以在作用域中嵌套作用域。
假设要组织查询用户信息的路径,可能包括:
- /users
- /users/show
- /users/show/{id}
这些路径的作用域布局如下:
一个 作用域 路径也可以包含可变的路径段,这与非作用域路径一致。
你可以从 HttpRequest::match_info()
中获取可变路径段的匹配值。Path
提取器 也可以提取作用域级别的可变路径段。
匹配信息
匹配到的所有可变标记的值都可以通过 HttpRequest::match_info()
获取。特定值可通过 Path::get()
获取。
在这个例子中,对于路径 /a/1/2/
,v1 和 v2 的值将被解析为 "1" 和 "2"。
路径信息提取器
Actix 提供了类型安全的路径信息提取功能。使用 Path 提取信息时,目标类型可以定义为几种不同的形式。最简单的方式是使用元组。元组中的每个元素都必须与路径模式中的一个元素相对应。例如,你可以将路径模式 "/{id}/{username}/" 与 Path<(u32, String)> 类型匹配,但不能与 "Path<(String, String, String)>
类型相匹配。
也可以将路径模式信息提取到一个结构体中,这个结构体必须实现了 serde 中的 Deserialize
特质。
Query 提供了类似的功能,但是它用于提取请求的查询参数。
生成资源 URL
使用 HttpRequest.url_for() 方法会基于资源模式生成 URL。例如,如果你配置了一个名称为 "foo" 和模式为 "{a}/{b}/{c}" 的资源,你可以这样做:
这将返回类似 http://example.com/test/1/2/
这样的字符串(在当前协议和主机名是 http://example.com 的情况下)。url_for
方法返回 Url 对象,所以你可以修改该 url(添加查询参数,锚点等)。url_for()
只能为 命名 资源调用,否会会返回错误。
外部资源
有效的 URL 资源可以注册为外部资源。它们只用于生成 URL,在请求时不会考虑匹配。
路径规范化和重定向到附加 /
的路由
规范化意味着:
- 在路径尾部追加
/
。 - 讲多个
/
合并为一个/
。
一旦找到正确解析的路径,就会立即返回处理程序。如果启用全部规范化条件,将会按照如下顺序匹配路由:1)合并,2)合并和追加,3)追加,如果按照这个顺序满足其中一个条件,就会重定向到新路径。
在本例中,//resource//
将重定向到/resource/
。
在本例中,为所有路由都注册了路径规范化处理程序,但不应该依赖这个机制来重定向 POST 请求。如果追加 /
后仍然 Not Found,会将 POST 请求转换为 GET 请求,从而丢失原始请求中的所有POST数据。
可以只为 GET 请求注册规范化路径:
使用一个应用前缀来组合应用
web::scope()
方法允许设置特定的应用程序作用域。该作用域代表一个资源前缀,将被添加到所有资源模式中。这有助于将一组路由挂载到与可调用资源作者意图不同的位置,同时仍保持相同的资源名称。
例如:
在上看的例子中,show_users 路由的有效路由模式是 /users/show,而不是 /show,因为作用域将被添加到模式之前。这样,只有当 URL 路径为 /users/show 时,路由才会匹配,而当使用路由名称 show_users 调用 HttpRequest.url_for()
函数时,它将生成一个具有相同路径的 URL。
自定义路由守卫
你可以把路由守卫看成一个普通的函数,这个函数接收 request 对象引用作为参数,并返回 true 或 false。从形式上看,路由守卫是实现了 Guard
特质的任何对象。Actix 提供了几个守卫,你可以在 API 文档的 functions section 中查看。
下面是一个简单的守卫示例,它检查请求是否包含特定的 请求头:
在这个例子中,只有当请求头包含 CONTENT-TYPE 时,才会调用 index 处理程序。
守卫不能访问或修改请求对象,但是可以在 request extensions 中存储额外的信息。
改变守卫的含义
你可以使用 Not
守卫来包装任何其他守卫,以反转守卫的含义。例如,如果你想为除 GET 之外的所有请求方法返回 "METHOD NOT ALLOWED" 响应:
Any
守卫接收一个守卫列表,如其中任意一个守卫匹配,则匹配。例如:
guard::Any(guard::Get()).or(guard::Post())
All
守卫接收一个守卫列表,所有的守卫都匹配时才会皮诶,例如:
guard::All(guard::Get()).and(guard::Header("content-type", "plain/text"))
改变默认的 Not Found 响应
如果在路由表或资源表中不能匹配到路由,则会使用默认的响应。现在应用中默认响应是 NOT FOUND。也可以使用 App::default_service()
来覆盖默认的 NOT FOUND 响应。该方法接收一个 配置函数,这与使用 App::service()
方法配置资源一样。