Applied: Simple HTTP Server

让我们用async/.await建立一个回声服务器!

开始之前,运行rustup update stable,以确保你有 stable Rust 1.39 或更新的版本。一旦完成,就cargo 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 = { version = "0.3", 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"
                        

既然我们已经摆脱了依赖关系,让我们开始编写一些代码。我们有一些 imports 要添加:


                        #![allow(unused_variables)]
                        fn main() {
                        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 中编写了,第一个异步 web 服务器。

您还可以检查 request(请求) 本身,它包含诸如,request 的 URI、HTTP 版本、header 和其他元数据等信息。例如,我们可以打印出请求的 URI,如下所示:


                        #![allow(unused_variables)]
                        fn main() {
                        println!("Got request at {:?}", req.uri());
                        }
                        

您可能已经注意到,在处理请求时,我们还没有做任何异步操作,我们只是立即响应,所以我们没有利用上async fn给我们的灵活性。与其只返回静态消息,不如尝试使用 Hyper 的 HTTP 客户端,将用户的请求代理到另一个网站。

我们首先解析出要请求的 URL:


                        #![allow(unused_variables)]
                        fn main() {
                        let url_str = "http://www.rust-lang.org/en-US/";
                        let url = url_str.parse::<Uri>().expect("failed to parse URL");
                        }
                        

然后,我们可以新建一个新的hyper::Client,并使用它,制造一个GET请求,将响应返回给用户:


                        #![allow(unused_variables)]
                        fn main() {
                        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<Output = Result<Response, Error>>(或Future<Item = Response, Error = Error>在 futures 0.1 版)。当我们.await以后,会发送一个 HTTP 请求,挂起当前任务,并在响应可用时,任务会排队等待继续。

现在,如果你现在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 请求。