1. Hello, Rocket
首先在 Cargo.toml 中添加依赖:
[dependencies]
rocket = "0.5.0-rc.1"
#[macro_use] extern crate rocket;
use rocket::{Build, Rocket};
#[get("/")]
fn index() -> &'static str {
"Hello, Rocket!"
}
#[launch]
fn rocket() -> Rocket<Build> {
rocket::build().mount("/", routes![index])
}
2. 异步路由
路由处理器可以是 async 函数。
#[macro_use] extern crate rocket;
use rocket::{Build, Rocket};
use rocket::tokio::time::{sleep, Duration};
#[get("/delay")]
async fn delay() -> &'static str {
sleep(Duration::from_secs(1)).await;
"Hello, Rocket!"
}
#[launch]
fn rocket() -> Rocket<Build> {
rocket::build().mount("/", routes![delay])
}
注:mount 中指定的路径是基础路径,路由处理器对应的具体路径是“基础路径/ 路由处理器上指定的路径”。
3. 动态路径
通过 <变量名> 来指定。
#[macro_use] extern crate rocket;
use rocket::{Build, Rocket};
#[get("/hello/<name>")]
fn hello(name: &str) -> String {
format!("Hello, {}!", name)
}
#[launch]
fn rocket() -> Rocket<Build> {
rocket::build().mount("/", routes![hello])
}
4. 查询参数
#[macro_use] extern crate rocket;
use rocket::{Build, Rocket};
#[get("/pictures?<id>&<width>&<height>")]
fn get_picture(id: &str, width: Option<usize>, height: Option<usize>) -> String {
format!("id={}, width={}, height={}",
id,
match width {
Some(v) => v,
_ => 0,
},
match height {
Some(v) => v,
_ => 0,
},
)
}
#[launch]
fn rocket() -> Rocket<Build> {
rocket::build().mount("/", routes![get_picture])
}
请求:
http://127.0.0.1:8000/pictures?id=1&width=30
响应:
id=1, width=30, height=0
5. 处理 JSON
修改/添加依赖:
rocket = {version = "0.5.0-rc.1", features = ["json"]}
serde = { version = "1.0", features = ["derive"] }
#[macro_use] extern crate rocket;
use rocket::{Build, Rocket};
use rocket::serde::{Serialize, Deserialize, json::Json};
#[derive(Serialize)]
#[derive(Deserialize)]
struct User {
id: u32,
name: String,
}
#[post("/new", data="<user>")]
fn new_user(user: Json<User>) -> Json<User> {
Json(User {
id: user.id,
name: String::from(&user.name),
})
}
#[launch]
fn rocket() -> Rocket<Build> {
rocket::build().mount("/user", routes![new_user])
}
6. 处理表单
#[macro_use] extern crate rocket;
use rocket::{Build, Rocket};
use rocket::form::Form;
#[derive(FromForm)]
struct User {
id: u32,
name: String,
}
#[post("/new", data="<user>")]
fn new_user(user: Form<User>) -> String {
format!("id = {}, name = {}", user.id, user.name)
}
#[launch]
fn rocket() -> Rocket<Build> {
rocket::build().mount("/user", routes![new_user])
}
7. 上传文件
Rocket 会先将文件保存至临时目录(可配置)。
#[macro_use] extern crate rocket;
use rocket::{Build, Rocket};
use rocket::fs::TempFile;
use rocket::form::Form;
#[derive(FromForm)]
struct Upload<'f> {
file: TempFile<'f>
}
#[post("/upload", data="<form>")]
async fn upload(mut form: Form<Upload<'_>>) {
let path = r"E:\tmp\tmp.txt";
println!("saved to: {:?}", form.file.path());
form.file.copy_to(path).await.unwrap();
}
#[launch]
fn rocket() -> Rocket<Build> {
rocket::build().mount("/", routes![upload])
}
8. 下载文件
#[macro_use] extern crate rocket;
use rocket::{Build, Rocket};
use rocket::tokio::fs::File;
use rocket::http::ContentType;
use rocket::response::content;
#[get("/download")]
async fn download() -> content::Custom<File> {
let path = r"E:\tmp\tmp.txt";
content::Custom(ContentType::Binary, File::open(path).await.unwrap())
}
#[launch]
fn rocket() -> Rocket<Build> {
rocket::build().mount("/", routes![download])
}
或者使用:
#[get("/download")]
async fn download() -> File {
let path = r"E:\tmp\tmp.zip";
File::open(path).await.unwrap()
}
9. 中间件
#[macro_use] extern crate rocket;
use rocket::{Build, Rocket};
use rocket::{Request, Data, Response};
use rocket::fairing::{Fairing, Info, Kind};
use rocket::http::{Method, ContentType, Status};
use std::io::Cursor;
use std::sync::atomic::{AtomicUsize, Ordering};
struct Counter {
get: AtomicUsize,
post: AtomicUsize,
}
impl Counter {
fn new() -> Self {
Counter {
get: AtomicUsize::new(0),
post: AtomicUsize::new(0),
}
}
}
#[rocket::async_trait]
impl Fairing for Counter {
fn info(&self) -> Info {
Info {
name: "GET/POST Counter",
kind: Kind::Request | Kind::Response
}
}
async fn on_request(&self, request: &mut Request<'_>, _: &mut Data<'_>) {
match request.method() {
Method::Get => self.get.fetch_add(1, Ordering::Relaxed),
Method::Post => self.post.fetch_add(1, Ordering::Relaxed),
_ => return
};
}
async fn on_response<'r>(&self, request: &'r Request<'_>, response: &mut Response<'r>) {
if response.status() != Status::NotFound {
return
}
if request.method() == Method::Get && request.uri().path() == "/counts" {
let get_count = self.get.load(Ordering::Relaxed);
let post_count = self.post.load(Ordering::Relaxed);
let body = format!("Get: {}\nPost: {}", get_count, post_count);
response.set_status(Status::Ok);
response.set_header(ContentType::Plain);
response.set_sized_body(body.len(), Cursor::new(body));
}
}
}
#[get("/")]
fn index() -> &'static str {
"Hello, Rocket!"
}
#[launch]
fn rocket() -> Rocket<Build> {
rocket::build()
.attach(Counter::new())
.mount("/", routes![index])
}
on_request :处理请求前执行;on_response :发回响应前执行。
10. 请求守卫
在调用请求处理器之前执行,可以用来验证请求的合法性。
#[macro_use] extern crate rocket;
use rocket::{Build, Rocket};
use rocket::request::{FromRequest, Outcome};
use rocket::Request;
use rocket::http::Status;
struct User {
id: u32,
}
struct Admin {
id: u32,
}
#[rocket::async_trait]
impl<'r> FromRequest<'r> for User {
type Error = ();
async fn from_request(request: &'r Request<'_>) -> Outcome<Self, ()> {
match request.headers().get_one("role") {
Some("admin") | Some("user") => Outcome::Success(User{id: 1}),
_ => Outcome::Failure((Status::Forbidden, ())),
}
}
}
#[rocket::async_trait]
impl<'r> FromRequest<'r> for Admin {
type Error = ();
async fn from_request(request: &'r Request<'_>) -> Outcome<Admin, ()> {
match request.headers().get_one("role") {
Some("admin") => Outcome::Success(Admin {id: 0}),
Some(_) => Outcome::Forward(()),
_ => Outcome::Failure((Status::Forbidden, ())),
}
}
}
#[get("/dashboard")]
fn admin_dashboard(admin: Admin) -> String {
format!("Admin: {}", admin.id)
}
#[get("/dashboard", rank=2)]
fn user_dashboard(user: User) -> String {
format!("User: {}", user.id)
}
#[launch]
fn rocket() -> Rocket<Build> {
rocket::build()
.mount("/", routes![admin_dashboard, user_dashboard])
}
-
请求守卫的返回值:
Success(S) :验证成功,S 对应的值会成为请求处理器相应的参数。Failure(Status, E) :验证失败,直接返回响应。Forward :转发给下一个匹配的请求处理器,如,以 User 身份请求 /dashboard 时,此处先尝试 admin_dashboard (转发),然后尝试 user_dashboard 。 -
可以有多个请求守卫(遵从短路逻辑),如,此处 a, b, c 均是请求守卫(没有出现在 get 属性中)。 #[get("/<param>")]
fn index(param: isize, a: A, b: B, c: C) { }
11. 自定义配置
创建 Rocket.toml 文件:(Rocket 会在当前目录中查找此文件,找不到的话就到父目录中继续找,不断往上搜索)
[default]
address = "127.0.0.1"
port = 8000
workers = 16
keep_alive = 5
ident = "Rocket"
log_level = "normal"
temp_dir = "/tmp"
cli_colors = true
secret_key = "hPRYyVRiMyxpw5sBB1XeCMN1kFsDCqKvBi2QJxBVHQk="
[default.limits]
forms = "64 kB"
json = "1 MiB"
msgpack = "2 MiB"
"file/jpg" = "5 MiB"
[default.tls]
certs = "path/to/cert-chain.pem"
key = "path/to/key.pem"
[default.shutdown]
ctrlc = true
signals = ["term", "hup"]
grace = 5
mercy = 5
[release]
port = 9999
secret_key = "hPRYyVRiMyxpw5sBB1XeCMN1kFsDCqKvBi2QJxBVHQk="
每个参数的具体含义见 https://api.rocket.rs/v0.5-rc/rocket/struct.Config.html。
注:如果使用 TLS,则需要修改依赖
[dependencies]
rocket = { version = "0.5.0-rc.1", features = ["tls"] }
|