案例:HTTP 伺服器

我們用 async/.await 打造一個 echo 伺服器吧!

首先,執行 rustup update nightly 讓你去的最新最棒的 Rust,我們正使用最前沿的功能,確保最新版本非常重要。當你完成上述指令,執行 cargo +nightly new async-await-echo 來建立新專案,並開啟 async-await-echo 資料夾。

讓我們在 Cargo.toml 中加上一些相依函式庫:

[dependencies] # The latest version of the "futures" library, which has lots of utilities # for writing async code. Enable the "compat" feature to include the # functions for using futures 0.3 and async/await with the Hyper library, # which use futures 0.1. futures-preview = { version = "=0.3.0-alpha.17", features = ["compat"] } # Hyper is an asynchronous HTTP library. We'll use it to power our HTTP # server and to make HTTP requests. hyper = "0.12.9"

現在已經取得相依函式庫,可以開始寫程式了。我們有些 import 需要新增:

use { hyper::{ // Miscellaneous types from Hyper for working with HTTP. Body, Client, Request, Response, Server, Uri, // This function turns a closure which returns a future into an // implementation of the the Hyper `Service` trait, which is an // asynchronous function from a generic `Request` to a `Response`. service::service_fn, // A function which runs a future to completion using the Hyper runtime. rt::run, }, futures::{ // Extension trait for futures 0.1 futures, adding the `.compat()` method // which allows us to use `.await` on 0.1 futures. compat::Future01CompatExt, // Extension traits providing additional methods on futures. // `FutureExt` adds methods that work for all futures, whereas // `TryFutureExt` adds methods to futures that return `Result` types. future::{FutureExt, TryFutureExt}, }, std::net::SocketAddr, };

當這些 imports 加入後,將這些鍋碗瓢盆放在一起,就能開始接收請求:

async fn serve_req(_req: Request<Body>) -> Result<Response<Body>, hyper::Error> { // Always return successfully with a response containing a body with // a friendly greeting ;) Ok(Response::new(Body::from("hello, world!"))) } async fn run_server(addr: SocketAddr) { println!("Listening on http://{}", addr); // Create a server bound on the provided address let serve_future = Server::bind(&addr) // Serve requests using our `async serve_req` function. // `serve` takes a closure which returns a type implementing the // `Service` trait. `service_fn` returns a value implementing the // `Service` trait, and accepts a closure which goes from request // to a future of the response. To use our `serve_req` function with // Hyper, we have to box it and put it in a compatability // wrapper to go from a futures 0.3 future (the kind returned by // `async fn`) to a futures 0.1 future (the kind used by Hyper). .serve(|| service_fn(|req| serve_req(req).boxed().compat())); // Wait for the server to complete serving or exit with an error. // If an error occurred, print it to stderr. if let Err(e) = serve_future.compat().await { eprintln!("server error: {}", e); } } fn main() { // Set the address to run our socket on. let addr = SocketAddr::from(([127, 0, 0, 1], 3000)); // Call our `run_server` function, which returns a future. // As with every `async fn`, for `run_server` to do anything, // the returned future needs to be run. Additionally, // we need to convert the returned future from a futures 0.3 future into a // futures 0.1 future. let futures_03_future = run_server(addr); let futures_01_future = futures_03_future.unit_error().boxed().compat(); // Finally, we can run the future to completion using the `run` function // provided by Hyper. run(futures_01_future); }

如果現在跑 cargo run,應該會看到「Listening on http://127.0.0.1:3000」輸出在終端機上。若在你的瀏覽器開啟這個鏈結,會看到「hello, world」出現在你的瀏覽器。恭喜!你剛寫下第一個 Rust 非同步網頁伺服器。

你也可以檢視這個請求本身,會包含譬如請求的 URI、HTTP 版本、headers、以及其他詮釋資料。舉例來說,你可以打印出請求的 URI:

println!("Got request at {:?}", req.uri());

你可能注意到我們尚未做任何非同步的事情來處理這個請求,我們就只是即刻回應罷了,所以我們並無善用 async fn 給予我們的彈性。與其只返回一個靜態訊息,讓我們使用 Hyper 的 HTTP 客戶端來代理使用者的請求到其他網站。

我們從解析想要請求的 URL 開始:

let url_str = "http://www.rust-lang.org/en-US/"; let url = url_str.parse::<Uri>().expect("failed to parse URL");

接下來我們建立一個新的 hyper::Client 並用之生成一個 GET 請求,這個請求會回傳一個回應給使用者。

let res = Client::new().get(url).compat().await; // Return the result of the request directly to the user println!("request finished-- returning response"); res

Client::get 返回一個 hyper::client::FutureResponse,這個 future 實作了 Future<Output = Result<Response, Error>>(或 futures 0.1 的 Future<Item = Response, Error = Error>)。當我們 .await 這個 future,將會發送一個 HTTP 請求,當前的任務會暫時停止(suspend),而這個任務會進入佇列中,在收到回應後繼續執行。

現在,執行 cargo run 並在瀏覽器中打開 http://127.0.0.1:3000/foo,會看到 Rust 首頁,以及以下的終端機輸出:

Listening on http://127.0.0.1:3000 Got request at /foo making request to http://www.rust-lang.org/en-US/ request finished-- returning response

恭喜呀!你剛剛代理了一個 HTTP 請求。