14个版本 (7个重大更新)
0.8.0 | 2021年3月14日 |
---|---|
0.7.1 | 2020年11月14日 |
0.7.0 | 2020年9月13日 |
0.3.0 | 2020年6月12日 |
在 HTTP客户端 中排名 258
330KB
6K SLoC
hreq
hreq 是一个以用户为中心的异步HTTP客户端和服务器。
初期阶段
这个库需要实际道路测试。欢迎提交错误报告和PR!
原则
本库的原则是
- 基于http crate构建的用户优先API。
- 异步(或通过最小运行时进行阻塞)。
- 纯Rust。
use hreq::prelude::*;
fn main() -> Result<(), hreq::Error> {
// Use plain http API request builder with
// trait extensions for extra convenience
// in handling query parameters and other
// request configurations.
let response = Request::builder()
.uri("https://myapi.acme.com/ingest")
.query("api_key", "secret")
.call().block()?;
// More convenience on the http response.
// Like shortcuts to read or parse
// response headers.
let x_req_id =
response.header_as::<usize>("x-req-id")
.unwrap();
// A Body type with easy ways to
// get the content.
let mut body = response.into_body();
let contents = body.read_to_string().block()?;
assert_eq!(contents, "Hello world!");
Ok(())
}
用户优先
用户优先 意味着在舒适性与性能、或舒适性与正确性之间需要做出权衡时,额外的权重将倾向于舒适性。hreq不会试图在同时赢得任何性能或基准竞赛,因为它不应该特别慢或浪费系统资源。
http crate
许多Rust HTTP客户端/服务器使用某种变体的http crate。它通常被复制到本地源树中,并从那里进行扩展。
当编写同时使用Web服务器和客户端crate的服务时,通常会得到类似但并非完全相同的类型,如http::Request
和http::Response
。
hreq仅使用扩展特性。它重新导出http crate,但不复制或修改它。因此,它严格遵循http crate设定的API定义,并避免了具有相同名称的多个类型所引起的混淆。
异步和阻塞
Rust的异步故事很棒,但并非所有情况都需要异步。hreq通过默认具有一个非常最小的tokio运行时(rt-core
)并结合一个放置在我们期望在异步情况下进行.await
的位置的.block()
调用,来“伪造”一个阻塞库。
所有使用 .block()
的示例都可以使用 .await
预计 hreq 通常在异步上下文中使用,然而 rustdoc 不允许我们以这种方式记录代码。在文档中的每个 .block()
,你都可以将其替换为 .await
use hreq::prelude::*;
let res = Request::get("https://httpbin.org/get")
.call().block(); // this can be .await in async
为什么?
hreq 完全是异步的,并最终依赖于 TcpStream
的异步变体来工作。因为 TCP 套接字是与异步事件循环紧密耦合的东西之一,所以 TcpStream
需要由运行时(tokio)提供
有关于 rust 提供一个简单单线程执行器作为 std 库一部分的讨论。这仅解决了问题的一半,因为 TcpStream
与运行时耦合在一起。
异步运行时
异步运行时是“可插拔”的,并且有多种不同的风味。
TokioSingle
。默认选项。一个最小的 tokiort-core
,它在单个线程中执行调用。除非当前线程在调用.block()
时阻塞,否则它什么都不做。TokioShared
。通过使用Handle
来获取共享运行时。此运行时不能使用.block()
扩展特质,因为这需要直接连接到 tokioRuntime
TokioOwned
。使用一个预配置的 tokioRuntime
,并将其“交给” hreq。
如何配置这些选项在 AsyncRuntime
中有解释。
仅限 Tokio
这个项目最初的目标是具有运行时无关性,特别是要支持异步-std(和/或 smol),然而在实践中,由于维护工作太多,这不是一个可行的方案。Rust 很可能最终会提供一个可插拔的运行时机制,在这种情况下,这个库将再次尝试具有无关性。
代理、重定向和重试
hreq 中的所有调用都通过一个 Agent
进行。代理提供三个主要功能
- 重试
- 连接池
- Cookie 处理
然而,使用 hreq 的最简单方法是为每个调用创建一个新的代理,这意味着连接池和 Cookie 处理只发生在一个有限的程度(当跟随重定向时)。
use hreq::prelude::*;
let res1 = Request::get("https://httpbin.org/get")
.call().block(); // creates a new agent
// this call doesn't reuse any cookies or connections.
let res2 = Request::get("https://httpbin.org/get")
.call().block(); // creates another new agent
要使用多个调用之间的连接池和 Cookie,我们需要创建一个代理。
use hreq::prelude::*;
use hreq::Agent;
let mut agent = Agent::new();
let req1 = Request::get("https://httpbin.org/get")
.with_body(()).unwrap();
let res1 = agent.send(req1).block();
let req2 = Request::get("https://httpbin.org/get")
.with_body(()).unwrap();
// this call (tries to) reuse the connection in
// req1 since we are using the same agent.
let res2 = agent.send(req2).block();
重试
互联网是一个危险的地方,HTTP 请求总是失败。hreq 试图有所帮助,并默认内置了重试功能。然而,它只会在适当的时候重试。
- 默认的重试次数为 5 次,回退时间为 125、250、500、1000 毫秒。
- 仅适用于幂等方法:GET、HEAD、OPTIONS、TRACE、PUT 和 DELETE。
- 仅当遇到的错误是可重试的,例如 BrokenPipe、ConnectionAborted、ConnectionReset、Interrupted 时。
要禁用重试,必须使用已配置的代理。
use hreq::prelude::*;
use hreq::Agent;
let mut agent = Agent::new();
agent.retries(0); // disable all retries
let req = Request::get("https://httpbin.org/get")
.with_body(()).unwrap();
let res = agent.send(req).block();
重定向
默认情况下,hreq 会跟随最多 5 个重定向。可以通过使用与重试相同的显式代理来关闭重定向。
压缩
hreq 支持请求和响应的内容压缩。该功能通过接收或设置 content-encoding
标头为 gzip
来启用。目前 hreq 只支持 gzip
。
带有 gzip 体部的示例请求
use hreq::prelude::*;
let res = Request::post("https://my-special-server/content")
.header("content-encoding", "gzip") // enables gzip compression
.send("request that is compressed".to_string()).block();
自动压缩和解压缩可以被关闭,请参阅 content_encode
和 content_decode
。
字符集
与体部压缩类似,hreq 提供了一种自动编码和解码请求/响应体中文本的方式。Rust 使用 utf-8 对 String
进行编码,并假定文本体应以 utf-8 编码。使用 content-type
,我们可以更改 hreq 处理请求和响应的方式。
发送 iso-8859-1 编码体部的示例。
use hreq::prelude::*;
// This is a &str in rust default utf-8
let content = "Und in die Bäumen hängen Löwen und Bären";
let req = Request::post("https://my-euro-server/")
// This header converts the body to iso8859-1
.header("content-type", "text/plain; charset=iso8859-1")
.send(content).block();
接收其他字符集的体部对用户来说大多是透明的。如果响应中存在 content-type
标头,它将解码体为 utf-8。
只有具有 text/*
MIME 类型的内容类型将被解码。
字符集编码不需要仅与 utf-8 一起工作。它可以在适当的编码之间进行转码。请参阅 charset_encode_source
和 charset_decode_target
。
体部大小
根据如何将体部提供给请求,hreq 可能能够或无法知道总体部大小。例如,当体部作为字符串提供时,hreq 将设置 content-size
标头,而当体部是 Reader
时,hreq 将不知道内容大小,但用户可以将其设置。
如果 HTTP1.1 中不知道内容大小,hreq 将被迫使用 transfer-encoding: chunked
。对于 HTTP2,这个问题永远不会出现。
JSON
默认情况下,hreq 使用 serde 包来发送和接收 JSON 编码的体部。由于 serde 在 Rust 中非常普遍,因此默认启用此功能。
use hreq::Body;
use serde_derive::Serialize;
#[derive(Serialize)]
struct MyJsonThing {
name: String,
age: u8,
}
let json = MyJsonThing {
name: "Karl Kajal".to_string(),
age: 32,
};
let body = Body::from_json(&json);
服务器
hreq 从客户端开始,但现在也获得了简单的服务器机制。它可以路由请求、使用中间件、处理状态并服务 TLS。
有关详细信息,请参阅 server 模块文档
。
use hreq::prelude::*;
async fn start_server() {
let mut server = Server::new();
server.at("/hello/:name").get(hello_there);
let (shut, addr) = server.listen(0).await.expect("Failed to listen");
println!("Listening to: {}", addr);
shut.shutdown().await;
}
async fn hello_there(req: http::Request<Body>) -> String {
let name = req.path_param("name").unwrap();
format!("Hello there {}!\n", name)
}
功能
- 异步或阻塞
- 纯 Rust
- HTTP/2 和 HTTP/1.1
- TLS (https)
- 整个请求和读取响应的超时
- 默认情况下为单线程
- 作为
http
包的扩展构建。 - 在请求构建器中操纵查询参数
- 许多创建请求体的方式
- 跟随重定向
- 在连接问题上进行重试
- HTTP/1.1 转发编码块
- Gzip 编码/解码
- 字符集编码/解码
- 连接池
- JSON 序列化/反序列化
- Cookie
许可证:MIT/Apache-2.0
依赖关系
~12–29MB
~488K SLoC