应用:简单的HTTP Server
来用async
/await
创建一个回显服务器吧!
首先,使用rustup update stable
来确保你在使用Rust 1.39或者更新的版本。完成后,再输入cargo new async-await-echo
来创建一个新项目,打开async-await-echo
的文件夹。
在Cargo.toml
中添加依赖:
[dependencies]
# Hyper is an asynchronous HTTP library. We'll use it to power our HTTP
# server and to make HTTP requests.
hyper = "0.13"
# To setup some sort of runtime needed by Hyper, we will use the Tokio runtime.
tokio = { version = "0.2", features = ["full"] }
# (only for testing)
anyhow = "1.0.31"
reqwest = { version = "0.10.4", features = ["blocking"] }
解决了依赖问题后,就写点代码吧。先添加必要的import:
use {
hyper::{
// Following functions are used by Hyper to handle a `Request`
// and returning a `Response` in an asynchronous manner by using a Future
service::{make_service_fn, service_fn},
// Miscellaneous types from Hyper for working with HTTP.
Body,
Client,
Request,
Response,
Server,
Uri,
},
std::net::SocketAddr,
};
之后,再加入用于处理请求的模板:
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 type which implements the `MakeService` trait.
// `make_service_fn` converts a closure into a type which
// implements the `MakeService` trait. That closure must return a
// type that implements the `Service` trait, and `service_fn`
// converts a request-response function into a type that implements
// the `Service` trait.
.serve(make_service_fn(|_| async {
Ok::<_, hyper::Error>(service_fn(serve_req))
}));
// 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.await {
eprintln!("server error: {}", e);
}
}
#[tokio::main]
async 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 using `await`;
run_server(addr).await;
}
现在cargo run
的话,终端里应该会显示 "Listening on http://127.0.0.1:3000" 的信息。若在浏览器中打开,就能看到其显示 "hello, world!"。恭喜!你刚刚写出了你的第一个异步的Rust webserver。
看一看请求的内容,就会发现它包含了请求的URI、HTTP版本号、请求头和其他的元数据一类的信息。可以这样直接输出请求的URI:
println!("Got request at {:?}", _req.uri());
可能你已经注意到了,在处理请求时我们并没有以异步的方式进行——我们只是立即进行响应,因此没能充分使用async fn
的便利。比起仅仅返回一个静态的消息,来试试用Hyper的HTTP client来代理用户请求吧。
首先要解析请求的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).await?;
// Return the result of the request directly to the user
println!("request finished-- returning response");
Ok(res)
Client::get
会返回一个hyper::client::ResponseFuture
,其实现为Future<Output = Result<Response<Body>>>
(在future 0.1阶段为Future<Item = Response<Body>, Error = Error>
)。当对该future使用.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请求。