【Rust每周一库】hyper – 底层http库
- 2020 年 2 月 12 日
- 筆記
现在说到写应用,网络框架肯定是必不可少的。今天就给大家简单介绍一下hyper。hyper是一个偏底层的http库,支持HTTP/1和HTTP/2,支持异步Rust,并且同时提供了服务端和客户端的API支持。很多同学可能觉得既然hyper是个偏底层的框架,那是不是就不需要去了解了呢?首先很多上层的框架,比如rocket、iron和reqwest底层都是基于hyper的。(关于Rust中各种网络开发框架,这里有个很全面的综述和比较。)所以如果在使用这些框架的时候遇到了一些问题,对hyper的了解肯定是有一定的帮助的。再者学习Rust的我们都是奔着成为大佬的路线去的,很难说不会有直接操作偏底层框架的需求。
Hello World
我们首先来实现一个简单的服务器端和客户端,支持最简单的GET操作。
服务器端
首先是依赖,除了hyper本身之外,我们还需要tokio的runtime去执行async函数
[dependencies] hyper = "0.13" tokio = { version = "0.2", features = ["full"] }
然后就是main.rs
use std::{convert::Infallible, net::SocketAddr}; use hyper::{Body, Request, Response, Server}; use hyper::service::{make_service_fn, service_fn}; // 返回200 async fn handle(_: Request<Body>) -> Result<Response<Body>, Infallible> { Ok(Response::new("Hello, World!n".into())) } #[tokio::main] async fn main() { let addr = SocketAddr::from(([127, 0, 0, 1], 3000)); // 从handle创建一个服务 let make_svc = make_service_fn(|_conn| async { Ok::<_, Infallible>(service_fn(handle)) }); let server = Server::bind(&addr).serve(make_svc); // 运行server if let Err(e) = server.await { eprintln!("server error: {}", e); } }
客户端
依赖同服务器端
use hyper::Client; use hyper::body::HttpBody as _; use tokio::io::{stdout, AsyncWriteExt as _}; #[tokio::main] async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> { // 构建一个client,调用GET let client = Client::new(); let uri = "http://127.0.0.1:3000".parse()?; let mut resp = client.get(uri).await?; println!("Response: {}", resp.status()); // 将response(是个stream)输出到stdout while let Some(chunk) = resp.body_mut().data().await { stdout().write_all(&chunk?).await?; } Ok(()) }
先启动服务端,然后启动客户端,就可以看到服务端成功相应客户端的GET请求啦~
Response: 200 OK Hello, World!
更真实的例子
下面我们通过实现一个echo服务主要看一下服务器端如何进行路由,以及如何支持POST请求
服务器端
依赖
[dependencies] hyper = "0.13" tokio = { version = "0.2", features = ["full"] } futures-util = { version = "0.3", default-features = false }
代码
use futures_util::TryStreamExt; use hyper::service::{make_service_fn, service_fn}; use hyper::{Body, Method, Request, Response, Server, StatusCode}; async fn echo(req: Request<Body>) -> Result<Response<Body>, hyper::Error> { let mut response = Response::new(Body::empty()); // 通过req.method()和req.uri().path()来识别方法和请求路径 match (req.method(), req.uri().path()) { (&Method::GET, "/") => { *response.body_mut() = Body::from("Try POSTing data to /echo"); }, (&Method::POST, "/echo") => { // 将POST的内容保持不变返回 *response.body_mut() = req.into_body(); }, (&Method::POST, "/echo/uppercase") => { // 把请求stream中的字母都变成大写,并返回 let mapping = req .into_body() .map_ok(|chunk| { chunk.iter() .map(|byte| byte.to_ascii_uppercase()) .collect::<Vec<u8>>() }); // 把stream变成body *response.body_mut() = Body::wrap_stream(mapping); }, (&Method::POST, "/echo/reverse") => { // 这里需要完整的body,所以需要等待全部的stream并把它们变为bytes let full_body = hyper::body::to_bytes(req.into_body()).await?; // 把body逆向 let reversed = full_body.iter() .rev() .cloned() .collect::<Vec<u8>>(); *response.body_mut() = reversed.into(); }, _ => { *response.status_mut() = StatusCode::NOT_FOUND; }, }; Ok(response) } #[tokio::main] async fn main() { let addr = ([127, 0, 0, 1], 3000).into(); let make_svc = make_service_fn(|_conn| async { Ok::<_, hyper::Error>(service_fn(echo)) }); let server = Server::bind(&addr).serve(make_svc); if let Err(e) = server.await { eprintln!("server error: {}", e); } }
客户端
依赖和之前客户端一样。我们这里的代码以向/echo/reverse提交内容为echo的POST请求为例:
use hyper::Client; use hyper::{Body, Method, Request}; #[tokio::main] async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> { let req = Request::builder() .method(Method::POST) .uri("http://127.0.0.1:3000/echo/reverse") .body(Body::from("echo"))?; let client = Client::new(); let resp = client.request(req).await?; println!("Response: {}", resp.status()); println!("{:?}", hyper::body::to_bytes(resp.into_body()).await.unwrap()); Ok(()) }
依次启动服务端和客户端,就可以看到服务端响应了客户端的POST请求啦~
Response: 200 OK b"ohce"
好了,对hyper的介绍就到这里了。接下来就靠大家自己去深似海的网络编程世界中去摸索啦~