actix-web 中的 URL 调度(1) 中介绍了资源配置、路由配置,以及路由匹配,在 actix-web 中的 URL 调度(2) 中,我们将继续介绍 actix-web 中资源模式语法、作用域路由、匹配信息,以及路径信息提取部分。
资源模式语法
在模式参数匹配中,actix 使用的模式匹配语法简单明确。
在路由配置中,使用的模式可以以斜杠字符 / 开头。如果模式不是以斜杠字符 / 开头,匹配时则会在其前面加上一个隐式斜杠。例如,以下模式是等效的:
{foo}/bar/baz
以及:
/{foo}/bar/baz
可变部分(替换标记)以 {id} 的形式指定,这意味着——下一个斜杠字符 / 之前,接受任意字符,并将其用作 HttpRequest.match_info() 对象的名称。
模式中的替换标记,匹配正则表达式 [^{}/]+。
匹配信息(match_info)是 Params 对象,表示以路由模式为依据,从 URL 中提取的动态部分。匹配信息(match_info)也可以作为请求的匹配信息,如 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 字段。
如果两种文本路径都要匹配,可以使用两个替换标记:
foo/{name}.{ext}
文本路径 /foo/biz.html 将匹配上面的路由模式,匹配结果为 Params{’name’: ‘biz’, ‘ext’: ‘html’}。这样写是因为在替换标记 {name} 和 {ext} 之间,存在一个文本部分 .(点号)。
替换标记可以可选地指定一个正则表达式,该表达式将用于决定路径段是否应与替换标记匹配。要指定替换标记仅匹配正则表达式定义的特定字符集,必须对替换标记语法做一些形式上的扩展。在大括号 {} 中,替换标记名称后,必须跟随冒号 :,然后是正则表达式。与替换标记 1+ 关联的默认正则表达式,可匹配一个或多个非斜杠字符。例如,底层的替换标记 {foo} 可以更详细地写为 {foo:1+}。你可以将此更改为具体的正则表达式,以匹配具体的字符序列。比如更改为 {foo:\d+},将仅匹配数字。
路径段必须至少包含一个字符,才能匹配路径的替换标记。例如,对于 URL 路径 /abc/:
/abc/{foo} 不会匹配; /{foo}/ 可以匹配。
注意:在匹配模式前,将对 URL 路径去除引号,并解码为有效的 unicode 字符串;且代表路径段的匹配值,也将是去除引号的 URL。
例如,对于如下模式:
foo/{bar}
在匹配如下 URL 时:
http://example.com/foo/La%20Pe%C3%B1a
匹配字典如下所示(URL 解码后的值):
Params{'bar': 'La Pe\xf1a'}
路径段中的文本字符串代表路径的解码值,以提供给 actix。你不会希望在模式中使用 URL 编码值。例如,不是这样的 URL 编码值:
/Foo%20Bar/{baz}
你会希望使用这样的值:
/Foo Bar/{baz}
但这样做有可能得到“尾部匹配(tail match)”,为此,必须使用自定义正则表达式。
foo/{bar}/{tail:.*}
上述模式可匹配如下 URL,并生成如下匹配信息:
foo/1/2/ -> Params{'bar':'1', 'tail': '2/'}foo/abc/def/a/b/c -> Params{'bar':u'abc', 'tail': 'def/a/b/c'}
作用域路由
作用域可以帮助你组织路由,以共享共用的根路径。作用域可以嵌套。
比如,你希望组织一组路径,用于查看 “Users” 端资源。这些路径可能包括:
/users
/users/show
/users/show/{id}
这些路径的作用域布局如下所示:
#[get("/show")]async fn show_users() -> HttpResponse { HttpResponse::Ok().body("Show users")}#[get("/show/{id}")]async fn user_detail(path: web::Pathu32,)>) HttpResponse::Ok().body(format!("User detail: {}", path.into_inner().0))}#[actix_web::main]async fn main() -> std::io::Result { HttpServer::new(|| { App::new().service( web::scope("/users") .service(show_users) .service(user_detail), ) }) .bind("127.0.0.1:8080")? .run() .await}
作用域 路径可以包含可变路径段,与非作用域路径用法一致。
你可以使用 HttpRequest::match_info() 方法获取可变路径段,Path 提取器也可以提取作用域层级的变量段。
匹配信息
所有代表路径段的匹配值,都可以使用 HttpRequest::match_info 方法获得。Path::get() 方法可用于检索特定值。
use actix_web::{get, App, HttpRequest, HttpServer, Result};#[get("/a/{v1}/{v2}/")]async fn index(req: HttpRequest) -> Result<String> { let v1: u8 = req.match_info().get("v1").unwrap().parse().unwrap(); let v2: u8 = req.match_info().query("v2").parse().unwrap(); let (v3, v4): (u8, u8) = req.match_info().load().unwrap(); Ok(format!("Values {} {} {} {}", v1, v2, v3, v4))}#[actix_web::main]async fn main() -> std::io::Result { HttpServer::new(|| App::new().service(index)) .bind("127.0.0.1:8080")? .run() .await}
示例中的路径 ‘/a/1/2/’,其中的 v1 和 v2 两个值将被解析为 “1” 和 “2”。
可以由路径尾部的参数创建 PathBuf,PathBuf 返回值经百分比解码(URL 解码)。如果分段是 ..,则跳过前一个分段(如果存在)。
出于安全目的,如果分段满足以下任一条件,则返回一个 Err,表示该条件已满足:
解码段的开头为(任一):.(不包括 ..),*
解码段的结尾为(任一)::,>,<
解码段包含(任一):/
Windows 环境,解码段包含(任一):\
百分比编码(URL 编码)导致的无效 UTF8。
use actix_web::{get, App, HttpRequest, HttpServer, Result};use std::path::PathBuf;#[get("/a/{tail:.*}")]async fn index(req: HttpRequest) -> Result { let path: PathBuf = req.match_info().query("tail").parse().unwrap(); Ok(format!("Path {:?}", path))}#[actix_web::main]async fn main() -> std::io::Result { HttpServer::new(|| App::new().service(index)) .bind("127.0.0.1:8080")? .run() .await}
路径信息提取
actix 提供类型安全的路径信息提取的功能。使用 Path 结构体提取路径信息后,目标类型可以定义为几种不同的形式。最简单的方式是使用元组(tuple)类型,元组中的每个元素必须对应于路径模式中的一个元素。也就是说,你可以将路径模式 /{id}/{username}/ 与类型 Path 成功匹配,但是与类型 Path 的匹配就会失败。
use actix_web::{get, web, App, HttpServer, Result};#[get("/{username}/{id}/index.html")] // async fn index(info: web::PathString, u32)>) -> Result< let info = info.into_inner(); Ok(format!("Welcome {}! id: {}", info.0, info.1))}#[actix_web::main]async fn main() -> std::io::Result { HttpServer::new(|| App::new().service(index)) .bind("127.0.0.1:8080")? .run() .await}
也可以将路径模式信息提取到结构体中。下述示例中,结构体必须反序列化,实现 serde crate 的 Deserialize trait。
use actix_web::{get, web, App, HttpServer, Result};use serde::Deserialize;#[derive(Deserialize)]struct Info { username: String,}// extract path info using serde#[get("/{username}/index.html")] // async fn index(info: web::Path) -> Result { Ok(format!("Welcome {}!", info.username))}#[actix_web::main]async fn main() -> std::io::Result { HttpServer::new(|| App::new().service(index)) .bind("127.0.0.1:8080")? .run() .await}
Query 结构体为请求查询参数提供了类似的功能。
未完待续,……
因微信公众号阅读体验和篇幅限制,内容可能有所删减。访问芽之家书籍资料,或者 actix-web 开发指南站点 https://actix-web.budshome.com,可以进行更详细的了解,以及获取更多技术资料。
请点击阅读原文进行更详细的学习。