Hyper 是一个用 Rust 编写的高性能 HTTP 库,专注于提供底层的 HTTP 实现,而非上层的 Web 框架。它以其速度、安全性和异步支持而闻名,是 Rust 异步生态系统中构建 HTTP 服务的核心组件之一。无论是开发高性能的 HTTP 服务器、客户端,还是作为更高级 Web 框架(如 Warp、Axum)的底层驱动,Hyper 都扮演着至关重要的角色。它的设计哲学是提供一个灵活、可组合且符合 Rust 所有权和类型系统原则的 HTTP 接口。

核心思想:

  • 高性能与异步:基于 Rust 的异步运行时 (如 Tokio) 实现非阻塞 I/O,提供极高的吞吐量和低延迟。
  • 安全与零成本抽象:利用 Rust 的内存安全特性,避免常见的并发问题,并尽量减少运行时开销。
  • 低层级 HTTP 抽象:提供 HTTP/1.1 和 HTTP/2 协议的核心实现,不涉及路由、模板等 Web 框架功能。
  • 可组合性:通过 Service trait 和 Future 实现高度模块化和可扩展的设计,方便与现有异步生态系统集成。

一、为什么选择 Hyper?

在 Rust 的异步网络编程领域,Hyper 几乎是构建 HTTP 通信的首选库。其优势主要体现在以下几个方面:

  1. 性能卓越:得益于 Rust 的底层控制能力和零成本抽象,以及 Tokio 异步运行时的高效调度,Hyper 能够处理大量并发连接,在性能上与 C++ 或 Go 编写的同类应用相媲美甚至超越。
  2. 内存安全:Rust 的核心优势在于其内存安全保障,Hyper 自然继承了这一点。它防止了数据竞争、空指针解引用等常见内存错误,为构建可靠的网络服务提供了坚实基础。
  3. 异步非阻塞:Hyper 完全基于 Rust 的 async/await 语法和 Future trait 构建,实现了非阻塞 I/O。这意味着单个线程可以高效地处理多个网络连接,极大地提高了资源利用率。
  4. 协议支持:原生支持 HTTP/1.1 和 HTTP/2 协议,并为 HTTP/2 提供了丰富的特性,包括多路复用、服务器推送等。
  5. 模块化与灵活性:Hyper 专注于 HTTP 协议本身,不强制引入额外的Web框架组件。这种模块化设计使得它非常适合作为基础设施层,与其他库和服务无缝集成。许多流行的 Rust Web 框架,如 Axum, Warp, Tide 等,都在底层使用了 Hyper。

二、核心概念

要理解 Hyper,需要先掌握 Rust 异步编程中的一些关键概念。

2.1 Async/Await 与 Futures

Rust 通过 async/await 语法糖来编写异步代码,其底层核心是 Future trait。

  • Future trait:表示一个可能尚未完成的异步计算。当 poll 方法被多次调用并最终返回 Poll::Ready(T) 时,表示计算完成并返回了结果 T
  • async fn:一个异步函数,它声明返回一个实现了 Future trait 的类型。
  • await 关键字:用于暂停当前 async 函数的执行,直到一个 Future 完成。在等待期间,当前线程可以执行其他任务。

Hyper 的服务器和客户端操作都返回或接受 Future,这使得所有网络 I/O 操作都是非阻塞的。

2.2 Tokio 运行时

Tokio 是 Rust 最流行的异步运行时,它提供了一个事件循环和任务调度器,用于执行 Future。Hyper 依赖 Tokio 来管理网络连接、定时器和任务调度。当你使用 Hyper 时,通常需要将你的 main 函数标记为 #[tokio::main],或者在一个 Tokio 运行时中显式地 Spawm Hyper 的任务。

2.3 Service Trait

hyper::service::Service 是 Hyper 的核心抽象,它定义了如何处理 HTTP 请求。它是一个通用的 trait,可以被任何类型实现,以处理异步请求并返回异步响应。

1
2
3
4
5
6
7
8
9
10
11
12
pub trait Service<Request> {
// 异步任务在等待期间可以存储的状态。
// 如果 Service 能够立即处理请求,则返回Ok(Self::Response)。
// 否则返回 Err(Self::Error)。
type Response;
type Error;
type Future: Future<Output = Result<Self::Response, Self::Error>>;

fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>>;

fn call(&mut self, req: Request) -> Self::Future;
}
  • Request: 服务接受的请求类型 (例如 hyper::Request<hyper::body::Incoming>)。
  • Response: 服务返回的响应类型 (例如 hyper::Response<hyper::body::Bytes>)。
  • Error: 服务可能返回的错误类型。
  • Future: call 方法返回的异步操作,它最终会产生 Result<Response, Error>
  • poll_ready: 检查服务是否准备好接受新请求。这对于限制并发请求或处理资源限制非常有用。
  • call: 实际处理请求的异步方法,返回一个 Future

在 Hyper 服务器中,你需要提供一个实现了 Service trait 的类型,或者使用 hyper::service::service_fn 辅助函数将一个 async fn 转换为简单的 Service

三、Hyper 作为 HTTP 服务器

构建一个 Hyper 服务器意味着你需要监听特定的网络地址,并为每个传入的 HTTP 请求提供一个响应。

3.1 服务器架构

一个典型的 Hyper 服务器流程如下:

  1. 绑定地址:服务器绑定到一个 IP 地址和端口上。
  2. 接受连接:监听传入的 TCP 连接。
  3. 处理请求:对于每个连接,读取传入的 HTTP 请求,并通过 Service trait 来处理这些请求。
  4. 发送响应:将 Service 返回的响应写回客户端。

3.2 简单 HTTP 服务器示例

下面是一个使用 Hyper 和 Tokio 构建的简单 HTTP 服务器,它对所有请求都返回 “Hello, world!”。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
use hyper::server::conn::http1;
use hyper::service::service_fn;
use hyper::{body::Incoming as HttpIncomingBody, Request, Response, StatusCode};
use hyper::body::Bytes;
use hyper_util::rt::TokioIo;
use tokio::net::TcpListener;
use std::convert::Infallible;

// 异步函数,用于处理每个请求
// 它接收一个 hyper::Request 并返回一个 hyper::Response 的 Future
async fn hello_world(
_req: Request<HttpIncomingBody>,
) -> Result<Response<Bytes>, Infallible> {
Ok(Response::new("Hello, world!".into()))
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
let addr = "127.0.0.1:3000".parse()?; // 监听本地地址 127.0.0.1:3000

// 创建一个 TCP 监听器
let listener = TcpListener::bind(addr).await?;
println!("Listening on http://{}", addr);

// 循环接受新连接
loop {
let (stream, _) = listener.accept().await?;

// `TokioIo` 将 Tokio 的 `TcpStream` 适配为 Hyper 需要的 `AsyncRead + AsyncWrite` trait
let io = TokioIo::new(stream);

// 使用 `tokio::task::spawn` 来为每个连接在一个新的异步任务中处理
// `service_fn` 创建了一个 Service 实现,将请求传递给 `hello_world` 函数
tokio::task::spawn(async move {
if let Err(err) = http1::Builder::new()
.serve_connection(io, service_fn(hello_world))
.await
{
eprintln!("Error serving connection: {:?}", err);
}
});
}
}

代码解释

  1. hello_world 函数:这是一个 async 函数,它接收一个 Request 对象并返回一个 Response 对象。这里为了简单起见,所有请求都返回相同的 “Hello, world!” 字符串。Infallible 错误类型表示此服务永远不会失败。
  2. main 函数:
    • #[tokio::main]:宏将 main 函数转换为一个异步入口点,并初始化 Tokio 运行时。
    • TcpListener::bind(addr).await?:在指定地址上创建一个 TCP 监听器。
    • listener.accept().await?:在 loop 中等待并接受新的 TCP 连接。
    • TokioIo::new(stream):将标准的 tokio::net::TcpStream 封装为 hyper_util::rt::TokioIo,使其满足 Hyper 对异步 I/O 的要求。
    • http1::Builder::new().serve_connection(...):这是 Hyper 处理 HTTP/1.1 连接的核心方法。它接收一个 I/O 流和一个 Service 实现。
    • service_fn(hello_world):这是一个便利函数,它将一个满足特定签名的 async fn 转换为一个实现了 Service trait 的类型。
    • tokio::task::spawn(async move { ... }):为每个新连接创建一个独立的异步任务。这样可以并发处理多个连接,一个连接的等待不会阻塞其他连接。

四、Hyper 作为 HTTP 客户端

Hyper 客户端允许你发起 HTTP 请求到远程服务器。

4.1 客户端架构

Hyper 客户端的工作流程如下:

  1. 创建客户端:实例化一个 hyper::Client 对象。
  2. 构建请求:使用 hyper::Request 构建 HTTP 请求(包括方法、URI、头部和请求体)。
  3. 发送请求:使用客户端发送请求,这会返回一个 Future
  4. 处理响应Future 完成后,获取 hyper::Response 对象,并读取响应状态、头部和响应体。

4.2 简单 HTTP 客户端示例

下面是一个使用 Hyper 和 Tokio 构建的简单 HTTP 客户端,它向 http://httpbin.org/ip 发送一个 GET 请求,并打印响应体。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
use hyper_util::client::legacy::{Client, connect::HttpConnector};
use hyper::Uri;
use hyper_util::rt::TokioIo;
use tokio::io::{stdout, AsyncWriteExt as _};

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
// 1. 创建一个 Hyper 客户端
// HttpConnector 是 Hyper 提供的默认用于 HTTP 连接的连接器
let client = Client::builder(TokioIo::new(HttpConnector::new()))
.build();

// 2. 解析目标 URI
let url = "http://httpbin.org/ip".parse::<Uri>()?;

// 3. 发送 GET 请求
// client.get 接收一个 Uri,并返回一个 Future,该 Future 在请求完成后产生 Result<Response>
let mut resp = client.get(url).await?;

println!("Response: {}", resp.status());

// 4. 读取响应体
let body_bytes = hyper::body::to_bytes(resp.body_mut()).await?;

// 5. 打印响应体
stdout().write_all(&body_bytes).await?;
stdout().write_all(b"\n").await?;

Ok(())
}

代码解释

  1. Client::builder().build():创建一个 Hyper 客户端实例。hyper::Client 是一个异步客户端,用于发送 HTTP 请求。新版本的 Hyper (1.0+) 需要显式构建一个 Service 作为连接器,这里使用了 hyper_util 库中的 TokioIo (for tokio::net::TcpStream) 配合 HttpConnector 来实现。
  2. url.parse::<Uri>()?:将字符串形式的 URL 解析为 hyper::Uri 类型。
  3. client.get(url).await?:发送一个 HTTP GET 请求,并等待服务器的响应。这个调用返回一个 hyper::Response 对象。
  4. hyper::body::to_bytes(resp.body_mut()).await?:从响应中提取响应体。Hyper 的响应体是流式的 (hyper::body::Incoming),通常需要异步读取其内容到 Bytes 或其他数据结构中。
  5. stdout().write_all(...):将读取到的响应体打印到标准输出。

五、进阶主题概览

Hyper 提供了许多高级特性和抽象,以支持更复杂的用例。

5.1 请求与响应体 (Body)

在 Hyper 中,请求体和响应体都是通过 hyper::body::Incominghyper::body::Bytes 等类型来处理的。

  • Incoming: 用于接收器(服务器的请求体,客户端的响应体),是一个异步流,可以从中逐步读取数据。
  • Bytes: 一个高效的字节缓冲区,常用于构建发送端的请求体或响应体,或者将 Incoming 的所有数据汇聚到内存中。

5.2 错误处理

Hyper 中的错误通常通过 Result 类型来传播。对于复杂的服务器,通常会将多种错误类型装箱为 Box<dyn std::error::Error + Send + Sync>,以简化函数签名。

5.3 HTTP/2 支持

Hyper 原生支持 HTTP/2 协议,这对于需要高性能、低延迟且支持服务器推送和多路复用的应用(如 gRPC)至关重要。使用 hyper::server::conn::http2hyper_util::client::legacy::Client 配合 h2::client::Connection 可以实现 HTTP/2 通信。

5.4 中间件 (Middleware)

虽然 Hyper 本身不提供 Web 框架级别的中间件系统,但通过实现或组合 Service trait,可以构建自定义的中间件层,例如日志记录、认证、限流等。许多基于 Hyper 的 Web 框架(如 Axum)都提供了强大的中间件抽象。

六、总结

Hyper 是 Rust 异步网络编程生态系统中不可或缺的一部分。它提供了构建高性能、安全和可扩展 HTTP 服务器和客户端所需的底层工具。通过深入理解其核心概念,特别是 async/await、Tokio 运行时和 Service trait,开发者可以充分利用 Rust 的强大功能来创建高效的网络应用程序。无论是作为独立服务还是其他 Web 框架的基础,Hyper 都展现了 Rust 在系统级网络编程中的巨大潜力。