前言
学学用Rocket框架搞一搞web开发。
Optional和Result
先补一下之前漏掉的重要内容,Rust的错误处理。
主动报错panic
相当于PHP下面的exit函数,调用之后会打印错误信息,然后退出程序:
1 2 3 4 5 6 7 8 9 10
| fn main() { for n in 1..=100 { match n { 1 => println!("One!!!0x{:x}", n), 2 | 4 | 6 | 8 => println!("Two!!!0x{:x}", n), 20..=30 => println!("Three!!!0x{:x}", n), _ => panic!("No!"), } } }
|
结果:
1 2 3 4 5
| PS D:\Code\Rust\study> .\index.exe One!!!0x1 Two!!!0x2 thread 'main' panicked at .\index.rs:9:18: No!
|
空值检查Optional
大部分语言以空值为对象调用函数都会发生一个空指针错误,在Java语言下一般会事先通过判断值是否为null的方式(如!=或者Optional)来规避这种错误,而在Rust语言下,这种情况可以用Option加上match匹配的方式来解决:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| fn get_data(i: u8) -> Option<String>{ if i < 3 { Some(i.to_string()) }else { None } }
fn check(data: Option<String>) { match data { Some(inner) => println!("Number: {}.", inner), None => println!("None."), } }
fn main() { for n in 1..=3 { let res: Option<String> = get_data(n); check(res); } }
|
Option里面只有两种类型,代表有数值的Some和代表空值的None。
错误检查Result
除了空指针问题,代码运行时还经常会遇到错误,比如字符串转换为数字的格式错误,此时就可以用Result:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| use std::num::ParseIntError;
fn get_number(s: &str) -> Result<i32, ParseIntError> { let n = match s.parse::<i32>() { Ok(n) => n, Err(e) => return Err(e), }; Ok(n + 1) }
fn print(res: Result<i32, ParseIntError>) { match res { Ok(n) => println!("n is {}", n), Err(e) => println!("Error: {}", e), } }
fn main() { print(get_number("01234")); print(get_number("no")); }
|
Result里面同样也只有两种类型,代表执行正常的Ok和代表执行异常的Err,如果执行异常,可以在match中使用return语句提前跳出函数返回结果。
?
也是提前返回的功能,遇到问题时会返回一个Err:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| use std::num::ParseIntError;
fn get_number(s1: &str, s2: &str) -> Result<i32, ParseIntError> { let n1 = s1.parse::<i32>()?; let n2 = s2.parse::<i32>()?; Ok(n1 + n2) }
fn print(res: Result<i32, ParseIntError>) { match res { Ok(n) => println!("n is {}", n), Err(e) => println!("Error: {}", e), } }
fn main() { print(get_number("01234", "43210")); print(get_number("no", "1")); }
|
环境搭建
开始学习Rocker Web开发,首先要用包管理工具cargo创建一个新的项目:
1
| cargo new rocket-web --bin
|
注意项目名不要与rocket一致,然后安装rocket依赖:
Hello World
直接复制黏贴到main.rs里面测试一下:
1 2 3 4 5 6 7 8 9 10 11
| #[macro_use] extern crate rocket;
#[get("/")] fn index() -> &'static str { "Hello, world!" }
#[launch] fn rocket() -> _ { rocket::build().mount("/", routes![index]) }
|
然后编译:
第一次编译要把依赖都编译了,还挺久的,然后运行:
1
| .\target\debug\rocket-web.exe
|
或者用cargo run命令也可以,Hello World运行成功,还有一些日志记录:
1 2 3 4 5 6 7 8
| GET / text/html: >> Matched: (index) GET / >> Outcome: Success(200 OK) >> Response succeeded. GET /favicon.ico image/avif: >> No matching routes for GET /favicon.ico image/avif. >> No 404 catcher registered. Using Rocket default. >> Response succeeded.
|
JSON
先在项目里引入一下序列化、反序列化和JSON:
1
| use rocket::serde::{Deserialize, Serialize, json::Json};
|
但是出现问题:
1
| unresolved import `rocket::serde::json`
|
serde是Rust序列化、反序列化框架,根据rocket的官方文档的描述,想要使用rocket的serde框架,需要修改配置文件Cargo.toml,将dependencies下面的内容改为:
1 2 3
| rocket = { version = "0.5.1", features = ["json"] } serde = { version = "1.0", features = ["derive"] } serde_json = "1.0"
|
然后再次build就没问题了,根据官方文档,features为条件编译,可选择项目需要的依赖。
解决了依赖引入问题,然后定义一个结构体和一个接收JSON格式数据的路由:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| #[macro_use] extern crate rocket; use rocket::serde::{Deserialize, Serialize, json::Json};
#[derive(Debug, Deserialize, Serialize)] #[serde(crate = "rocket::serde")] struct User<'a> { uid: u8, username: &'a str, password: &'a str, login_status: bool }
#[get("/")] fn index() -> &'static str { "Hello, world!" }
#[post("/login", data = "<user>")] fn login(user: Json<User>) -> Json<User> { println!("Received user: {:?}", user); Json(User { uid: user.uid, username: user.username, password: user.password, login_status: user.login_status, }) }
#[launch] fn rocket() -> _ { rocket::build().mount("/", routes![index]) .mount("/", routes![login]) }
|
接受一个JSON数据,然后原样返回。
Cookies
有登录自然就要有保存登陆状态的功能,在Rocket框架中可以靠加密cookie的方式来实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55
| #[macro_use] extern crate rocket; use rocket::serde::{Deserialize, Serialize, json::Json}; use rocket::http::CookieJar; use serde_json;
#[derive(Debug, Deserialize, Serialize)] #[serde(crate = "rocket::serde")] struct User<'a> { uid: u8, username: &'a str, password: &'a str, status: bool }
#[get("/")] fn index(cookies: &CookieJar<>) -> Json<bool> { let login_status: bool; login_status = match cookies.get_private("user") { Some(cookie) => match serde_json::from_str::<User>(cookie.value()) { Ok(user) => user.username == "admin", Err(_) => false }, None => false }; Json(login_status) }
#[post("/login", data = "<input_user>")] fn login<'a>(cookies: &CookieJar<'a>, input_user: Json<User<'a>>) -> Json<User<'a>> { println!("Received user: {:?}", input_user); let user = User { uid: input_user.uid, username: input_user.username, password: input_user.password, status: true, }; match serde_json::to_string(&user) { Ok(json_str) => cookies.add_private(("user", json_str)), Err(e) => println!("Error: {}", e), } Json(user) }
#[get("/logout")] fn logout<'a>(cookies: &CookieJar<'_>) -> Json<bool> { cookies.remove_private("user"); Json(true) }
#[launch] fn rocket() -> _ { rocket::build().mount("/", routes![index]) .mount("/", routes![login]) .mount("/", routes![logout]) }
|
文件上传
他的FileName总是不带后缀名,麻烦:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| #[post("/upload", data = "<upload_file>")] async fn upload<'a>(cookies: &CookieJar<'a>, upload_file: Form<Upload<'a>>) -> Status { if !check_login(cookies) { return Status::Unauthorized } let upload_struct: Upload<'a>= upload_file.into_inner(); let mut temp_file: TempFile = upload_struct.file; let saved_name: &str; match temp_file.raw_name() { Some(file_name) => saved_name = file_name.dangerous_unsafe_unsanitized_raw().as_str(), None => return Status::InternalServerError }; match temp_file.persist_to(Path::new("C:/test").join(saved_name)).await { Ok(_) => println!("Upload Successfully"), Err(e) => println!("Error: {}", e), }; println!("Title: {}", upload_struct.category); Status::Accepted }
|
文件下载
由于需要输出文件,所以返回类型不太一样:
1 2 3 4 5 6 7 8
| #[post("/download", data = "<download>")] async fn download<'a>(cookies: &CookieJar<'a>, download: Json<Download<'a>>) -> Result<NamedFile, Status> { if !check_login(cookies) { return Err(Status::Unauthorized) } println!("Received download request: {:?}", download); NamedFile::open(Path::new("C:/test").join(download.category).join(download.file_name)).await.map_err(|_| Status::NotFound) }
|
官方文档-错误管理
官方文档-cargo
Rust 学习之 Some、Option、Result
Rust Rocket简单入门
Rocket JSON
官方文档-features