前言
学学用Rocket框架搞一搞web开发。
Optional和Result
先补一下之前漏掉的重要内容,Rust的错误处理。
主动报错panic
相当于PHP下面的exit函数,调用之后会打印错误信息,然后退出程序:
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!"),
}
}
}
结果:
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匹配的方式来解决:
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:
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:
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创建一个新的项目:
cargo new rocket-web --bin
注意项目名不要与rocket一致,然后安装rocket依赖:
cargo add rocket
Hello World
直接复制黏贴到main.rs里面测试一下:
#[macro_use] extern crate rocket;
#[get("/")]
fn index() -> &'static str {
"Hello, world!"
}
#[launch]
fn rocket() -> _ {
rocket::build().mount("/", routes![index])
}
然后编译:
cargo build
第一次编译要把依赖都编译了,还挺久的,然后运行:
.\target\debug\rocket-web.exe
或者用cargo run命令也可以,Hello World运行成功,还有一些日志记录:
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:
use rocket::serde::{Deserialize, Serialize, json::Json};
但是出现问题:
unresolved import `rocket::serde::json`
serde是Rust序列化、反序列化框架,根据rocket的官方文档的描述,想要使用rocket的serde框架,需要修改配置文件Cargo.toml,将dependencies下面的内容改为:
rocket = { version = "0.5.1", features = ["json"] }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
然后再次build就没问题了,根据官方文档,features为条件编译,可选择项目需要的依赖。
解决了依赖引入问题,然后定义一个结构体和一个接收JSON格式数据的路由:
#[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的方式来实现:
#[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总是不带后缀名,麻烦:
#[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
}
文件下载
由于需要输出文件,所以返回类型不太一样:
#[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)
}