10 releases

0.2.4 Dec 26, 2023
0.2.3 May 23, 2023
0.2.2 Dec 23, 2022
0.2.1 Nov 1, 2022
0.1.2 Jun 9, 2022

#96 in Authentication

Download history 1/week @ 2023-12-11 3/week @ 2023-12-18 40/week @ 2023-12-25 5/week @ 2024-01-22 1/week @ 2024-01-29 190/week @ 2024-02-26 17/week @ 2024-03-04 28/week @ 2024-03-11 17/week @ 2024-03-18

252 downloads per month
Used in 5 crates (2 directly)

MIT license

1MB
21K SLoC

Qiniu-Http-Client

qiniu-http-client docs.rs Run Test Cases GitHub release MIT licensed

概览

基于 qiniu-http 提供具有重试功能的 HTTP 客户端 (同时提供阻塞客户端和异步客户端,异步客户端则需要启用 async 功能), 通过对七牛特有的状态码和响应头进行合理的处理和重试,竭力保证七牛 API 可以调用成功。

该接口库可以通过启用不同的功能来选择不同的客户端实现, 例如可以通过启用 ureq 功能导入 qiniu-ureq 库作为 HTTP 客户端, 通过启用 reqwest 功能导入 qiniu-reqwest 库作为 HTTP 客户端, 通过启用 isahc 功能导入 qiniu-isahc 库作为 HTTP 客户端, 您也可以显式传入任何基于 qiniu-http 接口的 HTTP 客户端实现来提供给 qiniu-http-client 使用。

qiniu-http-client 提供的功能主要分为两个大类:区域相关和 HTTP 客户端逻辑相关。

区域相关

qiniu-http-client 表示一个服务器地址有两种方式, 一种是 IP 地址加端口号,用 IpAddrWithPort 表示(与 Rust 自带的 std::net::SocketAddr 不同的是,端口号是可选参数), 另一种是域名加端口号,用 DomainWithPort 表示。这两个类型都可以用 Endpoint 这个枚举类来统一表示。

对七牛来说,大部分服务都可以有多个域名或是服务器 IP 地址,因此用 Endpoints 来表示一个服务的多个域名和服务器 IP 地址。

无论是七牛的公有云还是私有云,一个区域包含多个服务,用 Region 来表示一个区域,用于存储该区域的 ID 和所有服务的 Endpoints。 区域并不总是需要用户静态配置,可以通过实现 RegionsProvider 接口来定义区域获取的不同方式, StaticRegionsProvider 实现了最基础的静态配置区域; AllRegionsProvider 实现了查询七牛所有区域的功能,支持内存缓存和文件系统缓存; BucketRegionsQueryer 可以用来查询七牛某个存储空间的所在区域和相关的多活区域,支持内存缓存和文件系统缓存;

在调用一个七牛服务的 API 时,需要传入所有服务器可用的域名或服务器 IP 地址以便于客户端重试,如果只能静态配置将十分麻烦, 因此我们也提供 EndpointsProvider 供用户实现不同的 Endpoints 获取方式, RegionsProviderEndpoints 可以从任意一个 RegionsProvider 实现中获取 EndpointsBucketDomainsQueryer 可以用来查询七牛某个存储空间绑定的域名,支持内存缓存和文件系统缓存。

HTTP 客户端逻辑相关

qiniu_http::HttpCaller

qiniu_http::HttpCaller 提供 HTTP 请求接口(同时提供阻塞接口和异步接口,异步接口则需要启用 async 功能)。 qiniu_ureq::Client 提供基于 ureq 库的 HTTP 客户端(需要启用 ureq 功能),特点是代码精简,依赖简单,但不支持异步接口; qiniu_reqwest::SyncClientqiniu_reqwest::AsyncClient 提供基于 reqwest 库的 HTTP 客户端 (需要启用 reqwest 功能,如果需要用到异步接口还需要额外启用 async 功能), 特点是支持阻塞接口和异步接口,但两个客户端不能混用, 即 qiniu_reqwest::SyncClient 只能用于发送阻塞请求,而 qiniu_reqwest::AsyncClient 只能用来发送异步请求, 且由于 reqwest 库自身基于异步接口实现,因此即使不启用 async 功能,也会用线程启动 tokio 异步环境驱动 HTTP 请求发送; qiniu_isahc::Client 提供基于 isahc 库的 HTTP 客户端 (需要启用 reqwest 功能,如果需要用到异步接口还需要额外启用 async 功能), 特点是功能全面,且同时支持阻塞接口和异步接口,但依赖原生的 libcurl 库, 且由于 isahc 库自身基于异步接口实现,因此即使不启用 async 功能,也会用线程启动 tokio 异步环境驱动 HTTP 请求发送; 可以通过配置 HttpClientBuilder::http_caller 来指定使用哪个客户端。如果不指定,默认通过当前启用的功能来判定。

Resolver

Resolver 提供域名解析的接口(同时提供阻塞接口和异步接口,异步接口则需要启用 async 功能),可以将一个域名解析为 IP 地址列表。 qiniu-http-client 提供多种域名解析的实现, SimpleResolver 提供最简单的,基于 libc 库的域名解析实现; CAresResolver (需要启用 c_ares 功能)提供基于 c-ares 库的域名解析实现; TrustDnsResolver (需要同时启用 trust_dns 功能和 async 功能)提供基于 trust-dns 库的域名解析实现; TimeoutResolver 为任意一个 Resolver 实现提供超时功能,不过该实现不是很高效,如果 Resolver 实现本身就带有超时功能,还是尽量使用自带的超时功能更好; ShuffledResolver 可以将任意一个 Resolver 实现提供打乱解析结果的功能,便于在客户端层面实现简单的服务器的负载均衡功能; ChainedResolver 提供对多个 Resolver 的链式调用,可以依次尝试不同的 Resolver 实现直到获得一个有效的解析结果为止; CachedResolver 提供对 Resolver 的缓存功能,支持内存缓存和文件系统缓存; 可以通过配置 HttpClientBuilder::resolver 来指定使用哪个解析器,如果不指定,默认通过当前启用的功能来判定,并使用 CachedResolverShuffledResolver 对其进行包装。

Chooser

Chooser 提供 IP 地址选择的功能,以及提供反馈接口以修正自身选择逻辑的功能(同时提供阻塞接口和异步接口,异步接口则需要启用 async 功能)。 qiniu-http-client 提供多种选择器的实现, DirectChooser 提供最直接的选择器,即不做任何筛选,直接将所有传入的 IP 地址返回; IpChooser 提供包含 IP 地址黑名单的选择器,即反馈 API 调用失败,则将所有相关 IP 地址冻结一段时间,在这段时间内,这些 IP 地址将会被过滤,不会被选择到; SubnetChooser 提供包含子网黑名单的选择器,即反馈 API 调用失败,则将所有相关 IP 地址所在的子网冻结一段时间,在这段时间内,任何与这些 IP 地址处于同一子网内的所有 IP 地址都将会被过滤,不会被选择到; ShuffledChooser 可以将任意一个 Chooser 实例提供打乱选择结果的功能,便于在客户端层面实现简单的服务器的负载均衡功能; NeverEmptyHandedChooser 确保 Chooser 实例不会因为所有可选择的 IP 地址都被屏蔽而导致 HTTP 客户端直接返回错误,在内置的 Chooser 没有返回结果时,将会随机返回一定比例的 IP 地址供 HTTP 客户端做一轮尝试。 可以通过配置 HttpClientBuilder::chooser 来指定使用哪个选择器,如果不指定,默认使用 SubnetChooser,并使用 ShuffledChooserNeverEmptyHandedChooser 对其进行包装。

RequestRetrier

RequestRetrier 根据 HTTP 客户端返回的错误,决定是否重试请求,重试决定由 RetryDecision 定义。 qiniu-http-client 提供多种重试器的实现, NeverRetrier 总是返回不重试的决定; ErrorRetrier 致力于通过七牛 API 返回的状态码作出正确的重试决定; LimitedRetrier 为一个 RequestRetrier 实例增加重试次数上限,即重试次数到达上限时,无论错误是什么,都切换服务器地址或不再予以重试。 可以通过配置 HttpClientBuilder::request_retrier 来指定使用哪个重试器,如果不指定,默认使用 ErrorRetrier,并使用 LimitedRetrier 对其进行包装。

Backoff

Backoff 根据 HTTP 客户端返回的错误和 RequestRetrier 返回的重试决定,决定退避时长。 qiniu-http-client 提供多种退避器的实现, FixedBackoff 总是返回固定的退避时长; ExponentialBackoff 根据重试次数返回指数级增长的退避时长; LimitedBackoff 为一个 Backoff 实例增加上限和下限,即如果基础的 Backoff 实例返回的退避时长超过限制,则返回极限值; RandomizedBackoff 为一个 Backoff 实例返回的退避时长增加随机范围,即返回的退避时长随机化。 可以通过配置 HttpClientBuilder::backoff 来指定使用哪个退避器,如果不指定,默认使用 ExponentialBackoff,并使用 LimitedBackoffRandomizedBackoff 对其进行包装。

安装

不启用异步接口,推荐使用 ureq

[dependencies]
qiniu-http-client = { version = "0.2.1", features = ["ureq"] }

启用 Isahc 异步接口

[dependencies]
qiniu-http-client = { version = "0.2.1", features = ["async", "isahc"] }

启用 Reqwest 异步接口

[dependencies]
qiniu-http-client = { version = "0.2.1", features = ["async", "reqwest"] }

其他功能

c_ares

启用 c-ares 库作为 DNS 解析器

trust_dns

启用 trust-dns 库作为 DNS 解析器

dns-over-https

启用 trust-dns 库作为 DNS 解析器,并使用 DOH 协议

dns-over-tls

启用 trust-dns 库作为 DNS 解析器,并使用 DOT 协议

代码示例

阻塞代码示例

私有云获取当前账户的 Buckets 列表

use qiniu_credential::Credential;
use qiniu_http_client::{Authorization, HttpClient, Region, RegionsProviderEndpoints, ServiceName};

let region = Region::builder("z0")
    .add_uc_preferred_endpoint("uc-qos.pocdemo.qiniu.io".parse()?)
    .build();
let credential = Credential::new("abcdefghklmnopq", "1234567890");
let bucket_names: Vec<String> = HttpClient::default()
    .get(&[ServiceName::Uc], RegionsProviderEndpoints::new(region))
    .use_https(false)
    .authorization(Authorization::v2(credential))
    .accept_json()
    .path("/buckets")
    .call()
    .parse_json()
    .into_body();

公有云获取对象信息

use qiniu_credential::Credential;
use qiniu_http_client::{Authorization, BucketRegionsQueryer, HttpClient, RegionsProviderEndpoints, ServiceName};
use serde_json::Value;

let credential = Credential::new("abcdefghklmnopq", "1234567890");
let value: Value = HttpClient::default()
    .get(
        &[ServiceName::Rs],
        RegionsProviderEndpoints::new(
            BucketRegionsQueryer::new().query(credential.access_key().to_owned(), "test-bucket"),
        ),
    )
    .path("/stat/dGVzdC1idWNrZXQ6dGVzdC1rZXk=")
    .authorization(Authorization::v2(credential))
    .accept_json()
    .call()
    .parse_json()
    .into_body();

公有云私有空间下载文件(存储空间必须绑定至少一个域名)

use qiniu_credential::Credential;
use qiniu_http_client::{Authorization, BucketDomainsQueryer, HttpClient};

let credential = Credential::new("abcdefghklmnopq", "1234567890");
let response = HttpClient::default()
    .get(
        &[],
        BucketDomainsQueryer::new().query(credential.to_owned(), "test-bucket"),
    )
    .path("/test-key")
    .use_https(false)
    .authorization(Authorization::download(credential))
    .call();

异步代码示例

私有云获取当前账户的 Buckets 列表

use qiniu_credential::Credential;
use qiniu_http_client::{Authorization, HttpClient, Region, RegionsProviderEndpoints, ServiceName};

let region = Region::builder("z0")
    .add_uc_preferred_endpoint("uc-qos.pocdemo.qiniu.io".parse()?)
    .build();
let credential = Credential::new("abcdefghklmnopq", "1234567890");
let bucket_names: Vec<String> = HttpClient::default()
    .async_get(&[ServiceName::Uc], RegionsProviderEndpoints::new(region))
    .use_https(false)
    .authorization(Authorization::v2(credential))
    .accept_json()
    .path("/buckets")
    .call()
    .await?
    .parse_json()
    .await?
    .into_body();

公有云获取对象信息

use qiniu_credential::Credential;
use qiniu_http_client::{Authorization, BucketRegionsQueryer, HttpClient, RegionsProviderEndpoints, ServiceName};
use serde_json::Value;

let credential = Credential::new("abcdefghklmnopq", "1234567890");
let value: Value = HttpClient::default()
    .async_get(
        &[ServiceName::Rs],
        RegionsProviderEndpoints::new(
            BucketRegionsQueryer::new().query(credential.access_key().to_owned(), "test-bucket"),
        ),
    )
    .path("/stat/dGVzdC1idWNrZXQ6dGVzdC1rZXk=")
    .authorization(Authorization::v2(credential))
    .accept_json()
    .call()
    .await?
    .parse_json()
    .await?
    .into_body();

公有云私有空间下载文件(存储空间必须绑定至少一个域名)

use qiniu_credential::Credential;
use qiniu_http_client::{Authorization, BucketDomainsQueryer, HttpClient};

let credential = Credential::new("abcdefghklmnopq", "1234567890");
let response = HttpClient::default()
    .async_get(
        &[],
        BucketDomainsQueryer::new().query(credential.to_owned(), "test-bucket"),
    )
    .path("/test-key")
    .use_https(false)
    .authorization(Authorization::download(credential))
    .call()
    .await?;

最低支持的 Rust 版本(MSRV)

1.70.0

联系我们

  • 如果需要帮助,请提交工单(在portal右侧点击咨询和建议提交工单,或者直接向 support@qiniu.com 发送邮件)
  • 如果有什么问题,可以到问答社区提问,问答社区
  • 更详细的文档,见官方文档站
  • 如果发现了bug, 欢迎提交 Issue
  • 如果有功能需求,欢迎提交 Issue
  • 如果要提交代码,欢迎提交 Pull Request
  • 欢迎关注我们的微信 微博,及时获取动态信息。

代码许可

This project is licensed under the MIT license.

Dependencies

~11–35MB
~593K SLoC