簡介

Rocket 是一個基於 Rust 編寫的上層網絡框架 , 是目前 rust 主流的網絡框架之一,有 8.8k 的 star。而它的 http 部分就是基於之前提到的 hyper。按官方說法,具有如下三個特點:1 安全無誤、開發體驗好 2 自動解決請求處理的類型問題,且無需全局狀態 3 各種可插拔的可選組件。那讓我們來一起看一看吧~

準備工作

需要在 Cargo.toml 中加入依賴

    [dependencies]  
    rocket = "0.4.2"  

然後需要注意,Rocket 需要使用 nigthly 編譯。也可以使用以下指令在當前目錄中默認使用 nightly

    rustup override set nightly  

Hello World

首先我們來寫一個最簡單的服務器,向請求返回 hello world

    // Rocket 用到的 rust  nightly 的特性  
    #![feature(proc_macro_hygiene, decl_macro)]  

    use rocket::{get, routes, Rocket};  

    // get 函數 hello  
    #[get("/")]  
    fn hello() -> &'static str {  
        "Hello, world!"  
    }  

    fn rocket() -> Rocket {  
        //  hello 函數掛載到 /  
        rocket::ignite().mount("/", routes![hello])  
    }  

    fn main() {  
        rocket().launch();  
    }  

那大家可能會好奇,爲什麼 hello 返回的是一個字符串,Rocket 就能把它作爲 response 返回呢? 這是因爲 Rocket 中返回類型需要實現 Responder Trait。而一些標準庫中的類型已經有了實現,比如 String 的實現如下

    impl Responder<'static> for String {  
        fn respond_to(self,_: &Request;) -> Result'static>, Status> {  
            Response::build()  
                .header(ContentType::Plain)  
                .sized_body(Cursor::new(self))  
                .ok()  
        }  
    }  

因此我們可以直接返回這些常用類型,而 Rocket 就能夠自動幫我們把他們轉化爲 response。 我們也可以類似的定義自己的類型去實現 Responder。

動態分發

如果需要路由中有動態部分,可以使用 <>。比如我們現在升級下我們的 hello 服務器,使得路徑中可以有一個動態的名字變量 name

    #[get("/hello/")]  
    fn hello(name: String) -> String {  
        format!("Hello, {}!", name)  
    }  

也支持路由後的 query 參數,按照如下格式

    #[get("/hello?wave&")]  

測試

Rocket 本身提供了本地的客戶端,可以方便對服務器進行測試。比如之前我們寫過 hello 服務器升級版,就可以很容易的進行測試

    #[cfg(test)]  
    mod test {  
        use super::rocket;  
        use rocket::local::Client;  
        use rocket::http::Status;  

        #[test]  
        fn hello() {  
            let client = Client::new(rocket()).expect("valid rocket instance");  
            let mut response = client.get("/hello/john").dispatch();  
            assert_eq!(response.status(), Status::Ok);  
            assert_eq!(response.body_string(), Some("Hello, john!".into()));  
        }  
    }  

中間件

Rocket 中相當於中間件的,有 Request Guard 和 Fairing。前者可以用來處理權限管理等邏輯,而後者主要用來加入全局的 Hook。 先來一個 Guard 的例子,這裏是 Rocket 內置的 Cookie。完整的代碼點這裏

    // 這個例子也可以看到 Rocket 對錶格的友好支持  
    #[derive(FromForm)]  
    struct Message {  
        message: String,  
    }  

    // 表格中的信息被加入 Cookie  
    #[post("/submit", data = "")]  
    fn submit(mut cookies: Cookies, message: Form) -> Redirect {  
        cookies.add(Cookie::new("message", message.into_inner().message));  
        Redirect::to("/")  
    }  

    // 讀取 Cookie 的內容  
    #[get("/")]  
    fn index(cookies: Cookies) -> Template {  
        let cookie = cookies.get("message");  
        let mut context = HashMap::new();  
        if let Some(ref cookie) = cookie {  
            context.insert("message", cookie.value());  
        }  

        Template::render("index", &context;)  
    }  

注意,Request Guard 的一般形式是

    #[get("/")]  
    fn index(param: isize, a: A, b: B, c: C) -> ... { ... }  

其中 a,b,c 這些不在參數列表的就是 Request Guard 了,需要實現 FromRequest Trait。

而下面的例子則是一個 Fairing,用來給 GET 和 POST 請求加上一個計數器(Fairing 一共可以有 on_attach, on_launch, on_request 和 on_response 這四個 Hook,on_attach 是在被 attach 到 Rocket 實例的時候,on_launch 是 Rocket 實例啓動的時候,後面兩個就是字面意思啦)

    #[derive(Default)]  
    struct Counter {  
        get: AtomicUsize,  
        post: AtomicUsize,  
    }  

    impl Fairing for Counter {  
        fn info(&self) -> Info {  
            Info {  
                name: "GET/POST Counter",  
                kind: Kind::Request | Kind::Response  
            }  
        }  

        fn on_request(&self, request: &mut Request,_: &Data;) {  
            if request.method() == Method::Get {  
                self.get.fetch_add(1, Ordering::Relaxed);  
            } else if request.method() == Method::Post {  
                self.post.fetch_add(1, Ordering::Relaxed);  
            }  
        }  

        fn on_response(&self, request: &Request;, response: &mut Response) {  
            // Don't change a successful user's response, ever.  
            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(Cursor::new(body));  
            }  
        }  
    }  

配置文件

一般會在運行的根目錄下放置 Rocket.toml,配置 Rocket 在 development,staging 和 production 環境中的參數,比如服務器地址端口,請求限制,worker 線程數量等。下面是一個示例:

    [development]  
    address = "localhost"  
    port = 8000  
    limits = { forms = 32768 }  

    [production]  
    address = "0.0.0.0"  
    port = 8000  
    workers = [number of cpus * 2]  
    keep_alive = 5  
    log = "critical"  
    secret_key = [randomly generated at launch]  
    limits = { forms = 32768 }  

小結

其實 Rocket 還有很多可以講的內容。限於篇幅就講到這裏啦。大家有興趣的可以自己去閱讀官方的例子和教程,並在實踐中學習吧~

來源鏈接:mp.weixin.qq.com