1.5. 应用:简单HTTP服务器
应用:简单HTTP服务器
让我们使用async/ .await
构建一个echo服务器!
首先,请rustup update 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"
现在我们已经完成依赖项了,让我们开始编写一些代码。我们先添加一些导入:
use {
hyper::{
// Hyper中的其他类型,用于构建HTTP
Body, Client, Request, Response, Server, Uri,
// 该函数将闭包转换为实现了Hyper的Service trait的future
// 它是从通用Request到Response的异步函数。
service::service_fn,
// 使用Hyper运行时可以运行future到完成的函数。
rt::run,
},
futures::{
// futures 0.1版本的一个扩展trait,添加了'.compat()'方法
// 允许我们在0.1版本的futures中使用'.await'语法
compat::Future01CompatExt,
// 扩展trait在futures上提供了额外的方法在
// `FutureExt` 添加了适用于所有futures的方法,
// `TryFutureExt` 给futures添加了可以放回‘Result’类型的方法
future::{FutureExt, TryFutureExt},
},
std::net::SocketAddr,
};
一旦导入完成,我们就可以开始整理样板,以便我们提供请求服务:
async fn serve_req(_req: Request<Body>) -> Result<Response<Body>, hyper::Error> {
// 通常成功地返回一个包含友好问候的body的响应(response)
Ok(Response::new(Body::from("hello, world!")))
}
async fn run_server(addr: SocketAddr) {
println!("Listening on http://{}", addr);
// 创建绑定到提供的地址的服务器
let serve_future = Server::bind(&addr)
// 服务器请求使用 `async serve_req` 这个函数.
// `serve` 方法采取闭包操作只要求返回的值实现‘Service’这个trait,
// `service_fn` 这个函数正好返回一个实现'Service' 这个trait的值
// 该方法也是采用闭包操作,返回的是一个响应future的请求
// 为了使用Hyper的serve_req这个函数,我们必须用box把它包裹起来
// 并且用compat方法让他获得0.1futures的兼容性(Hyper还是0.1的futures,所以显得麻烦)
.serve(|| service_fn(|req|
serve_req(req).boxed().compat()
));
// 等待服务完成服务或者因为某个错误而退出
// 如果一个错误出现,输出它到stderr
if let Err(e) = serve_future.compat().await {
eprintln!("server error: {}", e);
}
}
fn main() {
// 设置地址以运行我们的套接字
let addr = SocketAddr::from(([127, 0, 0, 1], 3000));
// 调用 run_server 函数, 将返回一个future
// 与每一个`async fn`一样, 对于`run_server`做任何事情,
// 返回的future需要运行
// 额外地,我们需要将返回的future从futures0.3转换成0.1版本的future
let futures_03_future = run_server(addr);
let futures_01_future =
futures_03_future.unit_error().boxed().compat();
// 最后我们用Hyper提供的run方法运行我们的future直到完成
run(futures_01_future);
}
如果您现在cargo run
,您应该在终端上看到"Listening on http://127.0.0.1:3000"
消息。如果您在所选择的浏览器中打开该URL,你应该看到”hello, world!” 出现在浏览器中。恭喜!您刚刚在Rust中编写了第一个异步Web服务器。
您还可以检查请求本身,其中包含请求URI,HTTP版本,标头和其他元数据等信息。例如,我们可以打印出请求的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:
,他实现了 :FutureResponse
Future<Output = Result<Response, Error>>
(或Future<Item = Response, Error = Error>
在futures 0.1)。当我们.await
这个future
时,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请求。