HTTP Method

Rocket.rs 支援了大部分的 HTTP Request Method 如:getputpost等:

#[get("/")]
#[put("/")]
#[post("/")]
#[delete("/")]
#[head("/")]
#[patch("/")]
#[options("/")]

最基礎的動態路由

動態路由以<param>為基礎,直接以下方的程式碼來舉例:

#[get("/users/<id>")]
fn get_user(id: usize) -> String {
    return format!("Hello user{}", id);
}

fn main() {
    rocket::ignite().mount("/", routes![get_user]).launch();
}

這樣當我們訪問/users/1時就會得到Hello user1的回覆內容。

Forwarding 路由

當一個請求進入之後,Rocket.rs 會開始匹配路由,當發現找到第一個路由不符合規則,則會將請求轉發到下一個路由中,以下面的程式碼來舉例:

#[get("/users/<id>")]
fn get_user_u8(id: u8) -> String {
     return format!("Hello user{}, message from get_user_u8", id);
}

#[get("/users/<id>", rank = 2)]
fn get_user_u16(id: u16) -> String {
     return format!("Hello user{}, message from get_user_u16", id);
}

fn main() {
    rocket::ignite().mount("/", routes![
        get_user_u8, 
        get_user_u16
    ]).launch();
}

這樣當我們訪問/users/1時就會得到Hello user1, message from get_user_u8, 而當我們訪問/users/257時就會得到Hello user257, message from get_user_u16這是因為257這個值並沒有辦法轉成型態u8, 因此就會將請求 forward 到get_user_u16()中。

對於這種路由,我們通常需要定義一個 fallback 用的路由,用於接受那些沒有被前面所有的路由所匹配的請求:

#[get("/users/<id>")]
fn get_user_u8(id: u8) -> String {
    return format!("Hello user{}, message from get_user_u8", id);
}

#[get("/users/<id>", rank = 2)]
fn get_user_u16(id: u16) -> String {
    return format!("Hello user{}, message from get_user_u16", id);
}

// 收納不匹配所有路由的請求
#[get("/users/<id>", rank = 3)]
fn get_user_string(id: String) -> String {
    return format!("Hello user{}, message from get_user_string", id);
}

fn main() {
    rocket::ignite().mount("/", routes![
        get_user_u8, 
        get_user_u16,
        get_user_string
    ]).launch();
}

另外,注意到每一個路由定義都有rank=n的標注,這個 rank 代表的就是路由的優先級別,愈小的 rank 就會愈優先匹配。

預設 ranking

請求ranking範例
純靜態路由-6/hello?world=ture
靜態路由 + 動態 Query String-5/hello?<world>
靜態路由-4/hello
動態路由 + 靜態 Query String-3/<hello>?world=true
動態路由 + 動態 Query String-2/<hello>?<world>
純動態路由-1/<hello>

Query String

必要 Query String

Query String 也是透過<param>的方式來撰寫:

#[get("/users?<id>")]
fn get_user(id: usize) -> String {
    return format!("Hello, user{}!", id)
}

fn main() {
    rocket::ignite().mount("/", routes![get_user]).launch();
}

這樣當我們訪問/users?id=1時就會收到Hello, user1!

可選 Query String

在某些狀況下,比如說商品列表,搜尋的 Query String 通常為可選, 換句話說就是當沒有search時則顯示全部,有的話則顯示符合搜尋條件的結果,我們可以寫這樣:

#[get("/products?<search>")]
fn get_products(search: Option<String>) -> String {
    return match search {
        None => format!("No search query string"),
        Some(search) => format!("You search project: {}", search)
    }
}

fn main() {
    rocket::ignite().mount("/", routes![get_products]).launch();
}

將參數的型態定義為Option<string>就可以將search這個 Query String 的參數定義為可選, 上面的程式碼執行後,訪問/products則會返回No search query string, 而訪問/products?search=123則會得到You search project: 123

將 Query String 包裝成 struct

我們也可以將 Query String 包成 struct 的型態方便處理,在這裡就直接拿官方文件的東西來舉例:

use rocket::request::Form;

#[derive(FromForm)]
struct User {
    name: String,
    account: usize,
}

#[get("/item?<id>&<user..>")]
fn item(id: usize, user: Form<User>) -> String {
    return format!("Id: {}, name: {}, account: {}", id, user.name, user.account);
}

fn main() {
    rocket::ignite().mount("/", routes![get_products, item]).launch();
}

這樣當我們訪問/item?id=1&name=floatflower&account=123時,就可以得到Id: 1, name: floatflower, account: 123

Forwarding

當然在這裡使用場景中,forwarding 依然適用,我們把上面的程式碼改寫一下變成這樣:

use rocket::request::Form;

#[derive(FromForm)]
struct User {
    name: String,
    account: usize,
}

// 增加 User1 的 struct
#[derive(FromForm)]
struct User1 {
    name: String,
    account: String,
}

#[get("/item?<id>&<user..>")]
fn item(id: usize, user: Form<User>) -> String {
    return format!("Id: {}, name: {}, account: {}, item", id, user.name, user.account);
}

// 當 account 的值不能與 usize 匹配時,就會 forward 到這個路由。
#[get("/item?<id>&<user..>", rank = 2)]
fn item1(id: usize, user: Form<User1>) -> String {
    return format!("Id: {}, name: {}, account: {}, item1", id, user.name, user.account);
}

fn main() {
    rocket::ignite().mount("/", routes![get_products, item, item1]).launch();
}

到這裡就是動態路由的匹配筆記,最後要注意的是,在路由衝突的時候,需要定義 ranking, 在對應相同的路由時,通常一個 ranking 愈小的,包含的 URL 的集合會愈小,我們透過改寫上方的範例來舉例:

#[get("/users/<id>", rank = 2)]
fn get_user_u8(id: u8) -> String {
    return format!("Hello user{}, message from get_user_u8", id);
}

#[get("/users/<id>", rank = 1)]
fn get_user_u16(id: u16) -> String {
    return format!("Hello user{}, message from get_user_u16", id);
}

#[get("/users/<id>", rank = 3)]
fn get_user_string(id: String) -> String {
    return format!("Hello user{}, message from get_user_string", id);
}

fn main() {
    rocket::ignite().mount("/", routes![
        get_user_u8,
        get_user_u16,
        get_user_string
    ]).launch();
}

如果今天是將u16的匹配放在u8,那麼因為u16已經包含所有u8的值了, 因此當get_user_u16()不匹配時,get_user_u8()也必然不匹配,這樣也就沒有後者存在的必要了, 因此務必注意 ranking 所包含的 URL 集合, 假設今天訪問的路由是/users/65537那麼必然會跳過get_user_u16()但是也必然會跳過get_user_u8(), 這也就是為什麼上面的 Default Ranking 是這樣排列的了。