前言

学学用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)
}

官方文档-错误管理

官方文档-cargo

Rust 学习之 Some、Option、Result

Rust Rocket简单入门

Rocket JSON

官方文档-features


Web Rust Rocket

本博客所有文章除特别声明外,均采用 CC BY-SA 3.0协议 。转载请注明出处!

复习vue3.0+ElementUI
Rust后端开发入门