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

MIT/Apache

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::Requesthttp::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。默认选项。一个最小的 tokio rt-core,它在单个线程中执行调用。除非当前线程在调用 .block() 时阻塞,否则它什么都不做。
  • TokioShared。通过使用 Handle 来获取共享运行时。此运行时不能使用 .block() 扩展特质,因为这需要直接连接到 tokio Runtime
  • TokioOwned。使用一个预配置的 tokio Runtime,并将其“交给” 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_encodecontent_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_sourcecharset_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