actix-web 中的 URL 调度(2) 中介绍了资源模式语法、作用域路由、匹配信息,以及路径信息提取部分,在 actix-web 中的 URL 调度(3) 中,我们将继续介绍 actix-web 中生成资源 URL、外部资源、路径规范化,以及重定向到附加斜杠的路由、使用作用域前缀组合应用、自定义路由卫语句、修改卫语句的值,以及更改默认的 Not Found 响应等剩余部分。
生成资源 URL
使用 HttpRequest.url_for() 方法,生成基于资源模式的 URL。例如,如果您配置了一个名称为“foo”,且模式为“{a}/{b}/{c}”的资源,则可以执行以下操作:
use actix_web::{get, guard, http::header, HttpRequest, HttpResponse, Result};#[get("/test/")]async fn index(req: HttpRequest) -> Result { let url = req.url_for("foo", &["1", "2", "3"])?; // Ok(HttpResponse::Found() .header(header::LOCATION, url.as_str()) .finish())}#[actix_web::main]async fn main() -> std::io::Result { use actix_web::{web, App, HttpServer}; HttpServer::new(|| { App::new() .service( web::resource("/test/{a}/{b}/{c}") .name("foo") // .guard(guard::Get()) .to(|| HttpResponse::Ok()), ) .service(index) }) .bind("127.0.0.1:8080")? .run() .await}
这会返回类似 http://example.com/test/1/2/3 的字符串(协议和主机名仅为示例)。url_for() 返回结构体 Url 对象,你可以对 url 进行修改(添加查询参数、锚点等)。只有已命名资源调用可以 url_for() 方法,否则返回错误。
外部资源
有效的资源 URL,可以注册为外部资源。外部资源仅用于生成 URL,在请求时,从不考虑进行匹配。
use actix_web::{get, App, HttpRequest, HttpServer, Responder};#[get("/")]async fn index(req: HttpRequest) -> impl Responder { let url = req.url_for("youtube", &["oHg5SJYRHA0"]).unwrap(); assert_eq!(url.as_str(), "https://youtube.com/watch/oHg5SJYRHA0"); url.into_string()}#[actix_web::main]async fn main() -> std::io::Result { HttpServer::new(|| { App::new() .service(index) .external_resource("youtube", "https://youtube.com/watch/{video_id}") }) .bind("127.0.0.1:8080")? .run() .await}
路径规范化,以及重定向到附加斜杠的路由
路径规范化意味着:
对路径附加尾部斜杠;
规范路径中的斜杠,用一个斜杠替换连续的多个斜杠。
路径规范化处理程序一旦找到正确解析的路径,就会立刻返回。如果启用了所有规范化条件,则其顺序为:1)合并,2)合并和追加,以及 3)追加。如果路径至少在其中一个条件下解析,它将重定向到新路径。
use actix_web::{middleware, HttpResponse};async fn index() -> HttpResponse { HttpResponse::Ok().body("Hello")}#[actix_web::main]async fn main() -> std::io::Result { use actix_web::{web, App, HttpServer}; HttpServer::new(|| { App::new() .wrap(middleware::NormalizePath::default()) .route("/resource/", web::to(index)) }) .bind("127.0.0.1:8080")? .run() .await}
示例中,//resource/// 将会被重定向为 /resource/。
上述示例中,为所有方法都注册了路径规范化处理程序,但你不应依赖于这种机制去重定向 POST 请求。附加斜杠的 Not Found 路径,其重定向会丢失原始请求中的所有 POST 数据,将 POST 请求转换为 GET 请求。
可以仅对 GET 请求注册路径规范化处理程序:
use actix_web::{get, http::Method, middleware, web, App, HttpServer};#[actix_web::main]async fn main() -> std::io::Result { HttpServer::new(|| { App::new() .wrap(middleware::NormalizePath::default()) .service(index) .default_service(web::route().method(Method::GET)) }) .bind("127.0.0.1:8080")? .run() .await}
使用作用域前缀组合应用
web::scope() 方法允许设置特定的应用程序作用域。此作用域表示一个资源前缀,该前缀将预置到由资源配置添加的所有资源模式中。这可以用来帮助装载一组路由到新的 URL 路径,而与其包含的可调用 URL 路径不同,同时仍保持相同的资源名称。
例如:
use actix_web::{get, http::Method, middleware, web, App, HttpServer};#[actix_web::main]async fn main() -> std::io::Result { HttpServer::new(|| { App::new() .wrap(middleware::NormalizePath::default()) .service(index) .default_service(web::route().method(Method::GET)) }) .bind("127.0.0.1:8080")? .run() .await}
在上面的示例中,show_users 路由将具有有效路由模式 /users/show,而不是 /show,因为应用程序作用域将预先添加到路由模式中。只有当 URL 路径匹配 /users/show,并且使用路由名称 show_users 调用 HttpRequest.url_for() 函数时,它将生成具有相同路径的 URL。
自定义路由卫语句
可以将卫语句视作为一个简单的函数,它接受请求 对象引用,并返回 true 或 false。从形式上讲,卫语句是实现 Guard trait 的任何对象。actix 提供了几个断言,详细了解请可以查看 API 文档的函数章节。
下面示例是一个简单的卫语句,用于检查请求是否包含特定的消息头 :
use actix_web::{dev::RequestHead, guard::Guard, http, HttpResponse};struct ContentTypeHeader;impl Guard for ContentTypeHeader { fn check(&self, req: &RequestHead) -> bool { req.headers().contains_key(http::header::CONTENT_TYPE) }}#[actix_web::main]async fn main() -> std::io::Result { use actix_web::{web, App, HttpServer}; HttpServer::new(|| { App::new().route( "/", web::route() .guard(ContentTypeHeader) .to(|| HttpResponse::Ok()), ) }) .bind("127.0.0.1:8080")? .run() .await}
上述示例中,只有当请求包含 CONTENT-TYPE 消息头时,才会调用index handler。
卫语句不能访问或修改请求对象,但是可以在请求扩展中存储额外的信息。
修改卫语句的值
通过将断言值包裹在 Not 断言中,可以反转任何断言值的含义。例如,如果要为除 GET 之外的所有方法返回 METHOD NOT ALLOWED 响应:
use actix_web::{guard, web, App, HttpResponse, HttpServer};#[actix_web::main]async fn main() -> std::io::Result { HttpServer::new(|| { App::new().route( "/", web::route() .guard(guard::Not(guard::Get())) .to(|| HttpResponse::MethodNotAllowed()), ) }) .bind("127.0.0.1:8080")? .run() .await}
如果要匹配所提供卫语句列表中的任意一个,可以使用 Any 卫语句。即:
#![allow(unused)]fn main() {guard::Any(guard::Get()).or(guard::Post())}
如果要匹配所提供的卫语句列表中的全部项,可以使用 All 卫语句。即:
#![allow(unused)]fn main() {guard::All(guard::Get()).and(guard::Header("content-type", "plain/text"))}
更改默认的 Not Found 响应
如果在路由表中不能发现路径模式,或资源找不到可匹配的路由,则会使用默认资源。默认的响应是 NOT FOUND,我们可以使用 App::default_service() 方法重写 NOT FOUND 响应。此方法通过 App::service() 方法接受配置函数,与普通资源配置方法相同。
#[actix_web::main]async fn main() -> std::io::Result { HttpServer::new(|| { App::new() .service(web::resource("/").route(web::get().to(index))) .default_service( web::route() .guard(guard::Not(guard::Get())) .to(|| HttpResponse::MethodNotAllowed()), ) }) .bind("127.0.0.1:8080")? .run() .await}
因微信公众号阅读体验和篇幅限制,内容可能有所删减。访问芽之家书籍资料,或者 actix-web 开发指南站点 https://actix-web.budshome.com,可以进行更详细的了解,以及获取更多技术资料。
请点击阅读原文进行更详细的学习。