#http-request #send-request #request #client-send #type #client

http-typed

支持自定义请求和响应类型的HTTP客户端

6个版本 (3个重大更改)

0.4.0 2023年8月28日
0.3.3 2023年8月25日
0.2.0 2023年8月18日
0.1.0 2023年7月31日

#234 in HTTP客户端

Download history 29/week @ 2024-07-06 131/week @ 2024-07-27

每月160次下载

MIT/Apache

32KB
246

HTTP Typed

支持自定义请求和响应类型的HTTP客户端。将任何类型传递给 send 函数或方法,它将返回您期望的响应类型的结果。send 处理请求序列化、HTTP消息和响应反序列化。

为了使这个crate保持简单,它面向一种特定但非常常见的模式。如果您的用例符合以下条件,这个crate将适用于您

  1. 请求-响应通信
  2. 异步Rust函数
  3. 通过HTTP进行通信(底层使用reqwest)
  4. HTTP体被序列化为JSON
  5. 200范围之外的状态码被视为错误
  6. 请求和响应类型必须使用serde进行序列化和反序列化
  7. 路径和HTTP方法可以从用于请求的具体Rust类型中确定

使用方法

典型

要使用此库,您的请求和响应类型必须分别实现serde::Serialize和serde::Deserialize。

要充分利用所有库功能,您可以为您请求的每个类型实现Request,创建一个Client实例,然后您可以直接调用 Client::send 发送请求。

let client = Client::new("http://example.com");
let response = client.send(MyRequest::new()).await?;

基本

如果您不想实现Request或创建Client,使用此库的最手动和基本的方式是使用 send_custom

let my_response: MyResponse = send_custom(
    "http://example.com/path/to/my/request/",
    HttpMethod::Get,
    MyRequest::new()
)
.await?;

客户端

send_custom (和 send) 函数的一个缺点是它会为每个请求实例化一个客户端,这是昂贵的。为了提高性能,您可以使用 Client::send_custom (和 Client::send) 方法来重用现有客户端。

let client = Client::default();
let my_response: MyResponse = client.send_custom(
    "http://example.com/path/to/my/request/",
    HttpMethod::Get,
    MyRequest::new()
)
.await?;

请求

您可能也喜欢不指定每次发送请求时的请求元数据,因为这些内容可能对这种类型的每个请求都是相同的。通过实现Request trait在类型系统中描述请求元数据。

pub trait Request {
    type Response;
    fn method(&self) -> HttpMethod;
    fn path(&self) -> String;
}

这增加了设计灵活性并减少了样板代码。下面关于API客户端设计的部分提供了解释。

如果您不控制带有请求和响应结构的crate,您可以使用newtype模式或可重用的泛型包装结构体为它们实现任何特质。

实现此特质后,您可以使用send函数和方法,这需要包含基本URL,而不是完整的URL。所有其他有关如何发送请求和响应的信息都由输入类型隐含。这仍然会在每个请求上创建一个客户端,所以如果您正在发送多个请求,性能可能不是最佳的。

let my_response = send("http://example.com", MyRequest::new()).await?;
// The type of my_response is determined by the trait's associated type.
// It does not need to be inferrable from the calling context.
return my_response.some_field

如果您想发送多个请求,或者您不想在调用send时包含基本URL,请实例化一个Client。

let client = Client::new("http://example.com");
let my_response = client.send(MyRequest::new()).await?;

请求组

您还可以定义请求组。这定义了一个客户端类型,明确指出它可以处理哪些请求。如果您尝试用错误的客户端发送请求,代码将无法编译。

request_group!(MyApi { MyRequest1, MyRequest2 });
let my_client = Client::<MyApi>::new("http://example.com");
let my_response1 = my_client.send(MyRequest1::new()).await?; // works
let other_response = my_client.send(OtherRequest::new()).await?; // does not compile

send_to

如果您想限制请求组,但仍然想在每次调用send时包含URL,MyClient有一个send_to方法,可以与默认客户端一起使用,在调用点指定URL。

let my_client = Client::<MyApi>::default();
let my_response2 = my_client.send_to("http://example.com", MyRequest2::new()).await?; // works
let other_response = my_client.send_to("http://example.com", OtherRequest::new()).await?; // does not compile

send_to方法还可以用于在base_url之后和在请求路径之前插入一个字符串。

let my_client = Client::new("http://example.com");
let my_response = my_client.send_to("/api/v2", MyRequest::new()).await?;

Cargo功能

通常,默认功能应该没问题

http-typed = "0.3"

默认功能包括完整的客户端实现,并依赖于系统tls库。

所有功能

  • default = ["client", "native-tls"]
  • client:包括上面描述的客户端实现,并依赖于reqwest。
  • native-tls:依赖于动态链接的系统tls库。
  • rustls-tls:将所有tls依赖项与webpki静态链接,不需要系统中的tls。

没有系统tls?使用rustls

要静态链接tls依赖项,请使用此方法

http-typed = { version = "0.3", default-features = false, features = ["client", "rustls-tls"] }

没有Client

如果您想排除Client实现及其对reqwest和tls库的所有依赖项,请使用此方法

http-typed = { version = "0.3", default-features = false }

这允许您作为服务器开发人员排除服务器中不需要的依赖项。例如,您可能有一个包含所有请求和响应结构的API crate,您既在服务器中导入它,又使其对客户端可用。您可以在API库中为客户端设置功能门

# api library's Cargo.toml

[features]
default = ["client"]
client = ["http-typed/client"]

...然后在服务器的Cargo.toml中禁用它。如下所示

# server binary's Cargo.toml

[dependencies]
my-api-client = { path = "../client", default-features = false }

可以使用类似的模式为客户端提供在native-tls和rustls-tls之间的选择。

API客户端设计

通常,您可能会实现一个自定义客户端结构来连接到API,包括为每个请求实现一个自定义方法。在这个过程中,您已迫使API的所有依赖者在这两种选项之间做出选择

  1. 使用已经实现的特定自定义客户端结构,接受其中可能存在的问题。
  2. 从头开始实现一个自定义客户端,重新编写和维护每个请求的所有细节,包括使用什么HTTP方法,使用什么路径,如何序列化/反序列化消息等。

相反,您可以通过特质的定义来描述元数据,从而获得最大的灵活性,而不必将依赖者锁定在客户端实现中或需要实现任何自定义客户端结构。API的依赖者现在有了更好的选择

  1. 使用http-typed提供的客户端结构,接受其中可能存在的问题。这对您来说更容易支持,因为您不需要担心发送请求的一般实现细节。您只需导出或别名为Client结构。
  2. 实现一个自定义客户端,它可以通用地处理实现Request类型的类型,通过使用它们的方法返回的数据。这对依赖者来说更容易,因为他们不需要编写任何特定于请求的代码。Request特质暴露了这些信息,而没有将它们锁定在客户端实现中。只需要一个单一泛型请求处理器就足够了。

其他用例

如果您的用例不满足介绍中描述的2-7条件之一,您会发现我的其他crate很有用,它分别对每个这些条件进行了泛化,允许它们中的任何一个以最小的样板代码进行个别定制。这目前是一个正在进行中的项目,但几乎完成了。这个crate和那个crate将具有源代码兼容性,这意味着其他crate可以用作这个crate的即插即用替换,无需更改任何代码,只是提供了更多的定制选项。

依赖

~0.8–12MB
~152K SLoC