Rust Rocket 路由、挂载、Launching启动
生命周期
Rocket的主要任务是监听传入的网络请求,将请求分派给应用程序代码,并向客户端返回响应。我们把这个从请求到响应的过程称为 “生命周期”。我们把生命周期总结为以下的步骤序列:
路由
Rocket将传入的HTTP请求解析为本地结构,你的代码间接地对其进行操作。Rocket通过与你的应用程序中声明的路由属性相匹配来决定调用哪个请求处理器。
验证
Rocket根据匹配的路由中存在的类型和防护措施来验证传入的请求。如果验证失败,Rocket将请求转发到下一个匹配的路由或调用一个错误处理程序。
处理
与路由相关的请求处理程序被调用,其参数经过验证。这是一个应用程序的主要业务逻辑。处理过程通过返回一个响应来完成。
响应
返回的Response被处理。Rocket生成适当的HTTP响应并将其发送给客户端。这就完成了整个生命周期。Rocket继续监听请求,为每个传入的请求重新启动生命周期。
Routing路由
Rocket应用以路由和处理程序为中心。路由是一个组合:
一组参数,用于匹配一个传入的请求。
一个处理程序,用于处理请求并返回一个响应。
处理程序是一个简单的函数,它接受任意数量的参数并返回任意类型。
匹配的参数包括静态路径、动态路径、路径段、表单、查询字符串、请求格式指定器和正文数据。Rocket使用属性,它看起来像其他语言中的函数装饰器,使声明路由变得简单。路由的声明是通过注释一个函数,即处理程序,以及要匹配的参数集。一个完整的路由声明看起来像这样:
#[get(“/world”)] // <- route attribute
fn world() -> &’static str { // <- request handler
“hello, world!”
}
这宣布了世界路由将与传入的GET请求的静态路径"/world "相匹配。我们可以使用#[get],而不是#[post]或#[put]来处理其他HTTP方法,或者使用#[catch]来处理自定义错误页面。此外,在构建更有趣的应用程序时,其他路由参数可能是必要的。在本章之后的Requests一章,有关于路由和错误处理的进一步细节。 Mounting挂载安装 在Rocket可以向一个路由调度请求之前,路由需要被安装: 通过routes! 列出的路线:这里是routes![world],有多条路线:routes![a, b, c]。 这将通过build函数创建一个新的Rocket实例,并将world路由挂载到/hello基本路径上,使Rocket知道这个路由。对/hello/world的GET请求将被引导到world函数。 挂载方法,就像Rocket上的所有其他构建方法一样,可以被连锁任何次数,并且路由可以被挂载点重复使用:rocket::build() .mount("/hello", routes![world]) .mount("/hi", routes![world]);通过将world挂载到/hello和/hi,对"/hello/world “和”/hi/world "的请求将被引导到world函数。
Launching启动
Rocket在启动后开始为请求提供服务,它启动了一个多线程的异步服务器,并在请求到达时将其分配给匹配的路由。有两种机制可以启动Rocket。第一种也是最好的方法是通过#[launch]路由属性,它可以生成一个主函数,设置一个异步运行时并启动服务器。通过#[launch],我们完整的Hello, world!应用程序看起来像:
第一种:
#[macro_use] extern crate rocket; #[get("/world")] fn world() -> &'static str { "Hello, world!" } #[launch] fn rocket() -> _ { rocket::build().mount("/hello", routes![world]) }运行后输出:
> cargo run Configured for debug. >> address: 127.0.0.1 >> port: 8000 >> workers: [..] >> keep-alive: 5s >> limits: [..] >> tls: disabled >> temp dir: /tmp >> log level: normal >> cli colors: true Routes: >> (world) GET /hello/world Rocket has launched from http://127.0.0.1:8000#[launch]能够自动判断输出类型
与Rocket的#[launch]属性特别的是,当返回类型被设置为_时,用#[launch]装饰的函数的返回类型会被自动推断出来。如果你愿意,你也可以明确地将返回类型设置为Rocket。更多例子在这https://github.com/SergioBenitez/Rocket/tree/v0.5-rc/examples/
第二种:
使用#[rocket::main]
#[rocket::main] async fn main() -> Result<(), rocket::Error> { let _rocket = rocket::build() .mount("/hello", routes![world]) .launch() .await?; Ok(()) }#[rocket::main]在需要一个由launch()返回的Future的句柄,或者需要检查launch()的返回值时,是非常有用的。例如,错误处理的例子中就检查了返回值。
Futures and Async
Rocket使用Rust Futures来实现并发性。使用Futures和async/await的异步编程允许路由处理程序执行等待量大的I/O,如文件系统和网络访问,同时仍然允许其他请求取得进展。一般来说,你应该倾向于在Rocket应用程序中使用异步的库,而不是同步。
async出现在Rocket的几个地方:
1.路由和错误捕捉器可以是async fns。在一个async fn中,你可以从Rocket或其他库中.await Futures。 2.Rocket的一些特性,如FromData和FromRequest,有一些方法可以返回Futures。 3.Data和DataStream,传入的请求数据,以及Response和Body,传出的响应数据,都是基于tokio::io::AsyncRead而不是std::io::Read。你可以在crates.io上找到带有async标签的async-ready库。
Rocket v0.5使用tokio运行时。如果你使用#[launch]或#[rocket::main],运行时就会为你启动,但你仍然可以通过不使用这两个属性在自定义的运行时上启动()一个Rocket实例。
Async Routes异步路由
Rocket使async/await更容易使用在路由上
use rocket::tokio::time::{sleep, Duration}; #[get("/delay/<seconds>")] async fn delay(seconds: u64) -> String { sleep(Duration::from_secs(seconds)).await; format!("Waited for {} seconds", seconds) }首先,注意到路由函数是一个async fn。这使得在处理程序中可以使用await。 sleep是一个异步函数,所以我们必须等待它。
Multitasking多任务处理
Rust的Futures是一种合作多任务的形式。一般来说,Futures和async fn应该只对操作进行.await,而不应该阻塞。一些常见的阻塞例子包括锁定非同步的互斥,加入线程,或使用非同步库函数(包括std中的函数)来执行I/O。
如果一个Future或async fn阻塞了线程,就会出现资源使用效率低下、停顿,有时甚至是死锁。
有时对于一个库或操作来说,没有好的异步选择。如果有必要,你可以用tokio::task::spwn_blocking将同步操作转换为异步操作。
use std::io; use rocket::tokio::task::spawn_blocking; #[get("/blocking_task")] async fn blocking_task() -> io::Result<Vec<u8>> { // In a real app, use rocket::fs::NamedFile or tokio::fs::File. let vec = spawn_blocking(|| std::fs::read("data.txt")).await .map_err(|e| io::Error::new(io::ErrorKind::Interrupted, e))??; Ok(vec) }Requests
请求守卫
在 Rocket 中,请求守卫是一种机制,用于在请求处理函数执行之前对请求进行拦截和处理。请求守卫通常用于验证和授权,以确保请求符合安全和业务规则。如果请求守卫失败,则会返回一个错误响应,而不是执行请求处理函数。Rocket 的请求守卫可以是全局的(应用程序范围内的所有路由都将受到影响),也可以是特定路由的(仅特定路由将受到影响)。请求守卫可以使用 Rust 的 trait 来定义和实现。它们可以是同步或异步的,并可以在请求级别或会话级别上工作。
Rocket 支持多种类型的请求守卫,例如:
Guards:验证请求是否符合安全或业务规则,如果验证失败则返回错误响应。 Catchers:捕获未处理的异常,并返回一个适当的错误响应。 Fairings:在请求处理函数执行之前或之后运行,用于进行一些与请求相关的操作,例如记录、修改响应头、启用跨域资源共享等。可以使用 #[guard]、#[catch]、#[fairing] 属性来定义请求守卫。例如:
rustCopy code#[get("/hello/<name>")] #[guard(MyGuard)] fn hello(name: &str) -> String { format!("Hello, {}!", name) } #[catch(404)] fn not_found(req: &Request) -> String { format!("Sorry, {} does not exist.", req.uri()) } #[derive(Debug)] struct MyGuard; impl<'a, 'r> rocket::request::FromRequest<'a, 'r> for MyGuard { type Error = (); fn from_request(request: &'a rocket::Request<'r>) -> rocket::request::Outcome<Self, Self::Error> { if request.headers().contains("Authorization") { rocket::Outcome::Success(MyGuard) } else { rocket::Outcome::Forward(()) } } }在上面的示例中,MyGuard 是一个请求守卫,它将验证请求头是否包含“Authorization”字段。如果验证通过,
它将返回一个 rocket::Outcome::Success 对象,否则将返回一个 rocket::Outcome::Forward 对象。请求处理函数 hello 使用了 #[guard(MyGuard)] 属性来指定 MyGuard 作为守卫,而 not_found 使用了 #[catch(404)] 属性来指定 404 错误时的处理函数。
除了Fairing以外,Rocket框架还提供了以下可实现的trait:
FromForm:用于从表单数据解析到Rust结构体。 FromParam:用于从URL参数中解析到Rust数据类型。 Responder:用于将Rust类型转换为HTTP响应。 Serialize:用于将Rust结构体/枚举/元组转换为JSON或其他格式的序列化输出。 Template:用于将Rust结构体/枚举/元组渲染为HTML或其他模板。 Stream:用于生成HTTP响应的数据流。 WebSocket:用于创建WebSocket连接的trait。 Responder<'r, 'o>:用于将Rust类型转换为HTTP响应,并可指定响应体的生命周期。 FromData:用于从请求的正文中解析到Rust结构体/枚举/元组。其中,FromForm、FromParam、Serialize和Template等都是用于处理请求的输入数据,而Responder、Stream、WebSocket和Responder<'r, 'o>等则是用于构造响应的输出数据。FromData是用于解析请求体的任意数据的通用trait。
路由属性和函数签名一起指定了一个请求必须是真实的,以使路由的处理程序被调用。你已经看到了一个实际的例子:
#[get("/world")] fn handler() { /* .. */ }这个路由表明,它只匹配对/world路由的GET请求。Rocket在处理程序被调用之前确保了这一点。当然,你可以做的比指定请求的方法和路径多得多。在其他方面,你可以要求Rocket自动进行验证:
一个动态路径段的类型。
几个动态路径段的类型。
传入的主体数据的类型。
查询字符串、表单和表单值的类型。
一个请求的预期传入或传出格式。
任何任意的、用户定义的安全或验证策略。路由属性和函数签名协同工作来描述这些验证。Rocket的代码生成负责实际验证这些属性。本节描述了如何要求Rocket对所有这些属性进行验证,以及更多。
Methods
一个路由属性可以是get,put,post,delete,head,patch,options每个属性都对应于要匹配的HTTP方法
#[post("/")]HEAD Requests
当存在一个GET路由时,Rocket会自动处理HEAD请求,否则会匹配。它通过从响应中剥离正文(如果有的话)来做到这一点。你也可以通过声明一个路由来专门处理HEAD请求;Rocket不会干涉你的应用程序明确处理的HEAD请求。Reinterpreting
因为网络浏览器只支持以GET或POST请求的方式提交HTML表单,Rocket在某些条件下会重新解释请求方法。如果一个POST请求包含一个Content-Type: application/x-www-form-urlencoded的主体,并且表单的第一个字段的名称是_method,并且其值是一个有效的HTTP方法名称(如 “PUT”),那么该字段的值将被用作传入请求的方法。这使得Rocket应用程序可以提交非POST表单。todo例子利用这个功能从一个web表单提交PUT和DELETE请求。Dynamic Paths
你可以通过在路由的路径中的变量名周围使用角括号来声明路径段为动态#[get("/hello/<name>")] fn hello(name: &str) -> String { format!("Hello, {}!", name) }如果我们把路径挂在根部(.mount(“/”, routes![hello])),那么任何对有两个非空段的路径的请求,其中第一个段是hello,将被派发到hello路由。例如,如果我们要访问/hello/John,应用程序将响应Hello, John!。
任何数量的动态路径段都是允许的。一个路径段可以是任何类型,包括你自己的,只要该类型实现了FromParam的特性。我们称这些类型为参数保护器。Rocket为许多标准库中的类型,以及一些特殊的Rocket类型实现了FromParam。对于所提供的实现的完整列表,请参阅FromParam API文档。这里有一个更完整的路线来说明不同的用法:
#[get("/hello/<name>/<age>/<cool>")] fn hello(name: &str, age: u8, cool: bool) -> String { if cool { format!("You're a cool {} year old, {}!", age, name) } else { format!("{}, we need to talk about your coolness.", name) } }Multiple Segments多段式
你也可以通过在路由路径中使用<param…>来匹配多个段。这些参数的类型,被称为片段护卫,必须实现FromSegments。段落守护必须是路径的最后一个组成部分:在段落守护之后的任何文本将导致编译时错误。use std::path::PathBuf; #[get("/page/<path..>")] fn get_page(path: PathBuf) { /* ... */ }在/page/之后的路径将在path参数中提供,对于简单的/page、/page/、/page/等路径,path参数可能为空。PathBuf的FromSegments实现确保路径不能导致路径遍历攻击。有了这个,一个安全可靠的静态文件服务器只需要4行就可以实现了
use std::path::{Path, PathBuf}; use rocket::fs::NamedFile; #[get("/<file..>")] async fn files(file: PathBuf) -> Option<NamedFile> { NamedFile::open(Path::new("static/").join(file)).await.ok() }Ignored Segments
一个路由的一个组件可以通过使用<>完全忽略,多个组件可以通过使用<…>忽略。换句话说,通配符名称_是一个动态参数名称,可以忽略该动态参数。一个被忽略的参数不能出现在函数参数列表中。声明为<>的段可以匹配单个段中的任何内容,而声明为<…>的段可以无条件地匹配任意数量的段。例如,下面的foo_bar路由匹配任何以/foo/开始、以/bar结束的三段式URI的GET请求。下面的everything路由匹配每个GET请求。
#[get("/foo/<_>/bar")] fn foo_bar() -> &'static str { "Foo _____ bar!" } #[get("/<_..>")] fn everything() -> &'static str { "Hey, you're here." }Forwarding
请求转发可以有多个方法,用于处理同一个请求不同的参数类型。rank:用于表示转发处理的优先级,默认-12到-1
#[get("/user/<id>")] fn user(id: usize) { /* ... */ } #[get("/user/<id>", rank = 2)] fn user_int(id: isize) { /* ... */ } #[get("/user/<id>", rank = 3)] fn user_str(id: &str) { /* ... */ } #[launch] fn rocket() -> _ { rocket::build().mount("/", routes![user, user_int, user_str]) }Default Ranking
如果没有明确指定等级,Rocket会分配一个默认等级。在路径和查询中,默认等级优先于静态网段:路由的路径和查询越是静态,其优先级就越高。路径和查询有三种 colors:
static,意味着所有组件都是静态的
partial,意味着至少有一个组件是动态的
wild,意思是所有组件都是动态的路径类型 查询类型 默认优先级
静态 静态 -12
静态 部分动态 -11
静态 全部动态 -10
静态 无 -9
部分动态 静态 -8
部分动态 部分动态 -7
部分动态 全部动态 -6
部分动态 无 -5
全部动态 静态 -4
全部动态 部分动态 -3
全部动态 全部动态 -2
全部动态 无 -1
例子:#[get("/foo/<_>/bar")] fn foo_bar() { } #[get("/<_..>")] fn everything() { }上面的是部分动态,下面的是全部动态