概要
Rocket 提供最基础的架构来构建rust服务端应用:剩下的取决于你自己。简而言之,Rocket提供路由,请求前置处理和相应后置处理。至于,请求前置如何处理,相应后置如何处理,请求如何处理,都是你的业务代码决定的。
生命周期
Rocket的主要任务时监听新来的web请求,分发这些请求到业务代码,并且返回响应到客户端。我们把从请求到响应的过程叫做生命周期。我们把生命周期总结为一下几个步骤:
- 路由
Rocket 会把新来的HTTP请求解析为你的代码可以操作的rust结构。并且通过匹配你程序中声明的路由属性,来检测,应该调用那个一个处理器。 - 验证
Rocket在匹配了路由以后就会对请求的数据做类型及有效性的验证。如果验证失败,则Rocket会把请求转到下一个匹配的路由,或者直接调用失败处理器。 - 处理器
请求的数据被验证成功之后,会作为参数来调用绑定在路由上的处理器。作为应用的主要逻辑,在调用结束后,会返回一个响应。 - 响应
返回的响应也是被处理过的。Rocket 会生成合适的HTTP响应并且发送到客户端。这样一个生命周期就结束了。Rocket继续监听新的请求。并为每个请求创建一个生命周期。
这个章节剩余的部分会详述路由部分和Rocket分发请求到请求处理器所需要的组件。之后的章节会详述请求、响应部分和Rocket的其它组件。
路由
Rocket应用都是以路由和处理器为中心。路由由下列组成:
- 一组参数来匹配新来的请求。
- 一个处理请求和返回响应的处理器。
处理器是一个简单的函数,接受任意个数的参数并且返回任意类型的结果。
用来匹配的参数包括静态路径、动态路径、路径参数、表单、查询参数、特定的请求格式和 请求体数据。Rocket 利用属性(类似其它语言的装饰器一样),使得路由声明变得加单。路由声明就是给处理器方法添加注解并且有一组参数用来匹配。一个完整的路由声明是像这样的:
#[get("/world")] // <- route attribute
fn world() -> &'static str { // <- request handler
"Hello, world!"
}
这个声明了world
路由用来匹配静态路径"/world"
的GET
请求。"world"
路由比较简单,不过当构建复杂的应用个的时候,额外的路由参数是必须的。请求 一节里面讲解了构建路由的所有情况。
挂载
在Rocket 能够分发请求到一个路由之前,路由需要先完成挂载。 挂载路由,类似给路由一个命名空间。用 Rocket实例的mount方法来挂载一个路由。Rocket实例通常使用rocket::ignite()静态方法来创建。
mount
方法需要:
- 一个包含一系列路由的命名空间的路径。
- 一组对应路由的处理器!和生成Rocket应用代码的宏。
例如,挂载之前声明的world
路由,我们可以这样写:
rocket::ignite().mount("/hello", routes![world]);
这块代码通过 ignite 函数创建了一个新的Rocket实例,并且将 world
路由挂载到了“/hello” 路径下面。
其结果就是,GET请求 “/hello/world”路径就会访问world函数。
命名空间
当一个路由是在root之外的其它模块里声明的,你会在挂载的时候会得到一个异常:
mod other {
#[get("/world")]
pub fn world() -> &'static str {
"Hello, world!"
}
}
use other::world;
fn main() {
// error[E0425]: cannot find value `static_rocket_route_info_for_world` in this scope
rocket::ignite().mount("/hello", routes![world]);
}
这个错误出现是因为 宏 routes! 在生成Rocket代码的时候隐式地将 route的名称 转换为了当前解构里的名称。解决方法是在写路由名称的时候加上模块的名字:
rocket::ignite().mount("/hello", routes![other::world]);
运行
现在Rocket已经有了路由,你可以用launch方法来启动Rocket接受请求。launch用来方法启动服务等待请求。当请求到达时,Rocket 会找到匹配的路由,并将请求分发到该路由的处理器。
通常情况下我们在main
方法里调用launch
方法。现在我们已经完成了Hello, world!
程序,看起来像这样:
#![feature(plugin)]
#![plugin(rocket_codegen)]
extern crate rocket;
#[get("/world")]
fn world() -> &'static str {
"Hello, world!"
}
fn main() {
rocket::ignite().mount("/hello", routes![world]).launch();
}
注意到我们添加了#![feature(plugin)]
和 #![plugin(rocket_codegen)]
这两行,是告诉Rust我们使用了Rocket的代码生成插件。同样我们将通过extern crate rocket
将 rocket
crate(箱)引入了我们的命名空间。最后,我们在main
函数里调用了launch
方法。
运行这个程序,控制台会显示内容:
🔧 Configured for development.
=> address: localhost
=> port: 8000
=> log: normal
=> workers: [logical cores * 2]
=> secret key: generated
=> limits: forms = 32KiB
=> tls: disabled
🛰 Mounting '/hello':
=> GET /hello/world
🚀 Rocket has launched from http://localhost:8000
我们访问 localhost:8000/hello/world
,就会看到Hello, world
, 正好是我们预期的。
在GitHub上有这个例子一个完整版的crate(箱),只要cargo run
就能运行。 你可以在 GitHub examples directory 中找到更多的例子,涵盖了所有Rocket的特性。