跳过正文

reqwest

··4902 字
Rust Rust-Crate
目录
rust crate - 这篇文章属于一个选集。
§ 11: 本文

reqwest 是在 hyper 基础上实现的高层 HTTP client 库,支持异步和同步接口。

request::get() 发送一个 GET 请求,每次请求都创建临时的 Client,效率不高。

// 一次性异步 GET 请求
pub async fn get<T: IntoUrl>(url: T) -> Result<Response>

let body = reqwest::get("https://www.rust-lang.org")
    .await?
    .text() // Response 的 text() 方法
    .await?;
println!("body = {body:?}");

建议:创建可复用的 Client 来发送请求,它内部维护 keepalive 连接池。

// 可以复用的 client
let client = reqwest::Client::new();

// Post text body
let res = client.post("http://httpbin.org/post")
    .body("the exact body that is sent")
    .send()
    .await?;

// Post 表单,可以是 tuple array,或 HashMap 或实现 Serialize 的自定义类型。
let params = [("foo", "bar"), ("baz", "quux")];
let res = client.post("http://httpbin.org/post")
    .form(&params)
    .send()
    .await?;

// Post JSON body, 任何可以被 serialized 为 JSON 的类型值均可。
let mut map = HashMap::new();
map.insert("lang", "rust");
map.insert("body", "json");
let res = client.post("http://httpbin.org/post")
    .json(&map)
    .send()
    .await?;

reqwest 默认支持重定向(最多 10 hops),可以通过 ClientBuilder 的 redirect::Policy 来控制。

let custom = redirect::Policy::custom(|attempt| {
    if attempt.previous().len() > 5 {
        attempt.error("too many redirects")
    } else if attempt.url().host_str() == Some("example.domain") {
        // prevent redirects to 'example.domain'
        attempt.stop()
    } else {
        attempt.follow()
    }
});

let client = reqwest::Client::builder()
    .redirect(custom)
    .build()?;

reqwest 默认支持 HTTP/HTTPS 代理,支持代理环境变量,可以通过 ClientBuilder 的 Proxy 来控制。

  • 如果使用的是 SOCKS5 代理,还需要 手动开启 socks feature 才能支持。

reqwest 默认支持 TLS,可以添加自定义的 Server/Client 证书。

  • 默认开启 default-tls feature,当前对应的是 native-tls;
  • 支持的 TLS backend:
    • native-tls:
      • SChannel on Windows (via the schannel crate)
      • Secure Transport on macOS (via the security-framework crate)
      • OpenSSL (via the openssl crate) on all other platforms.
    • rustls-tls:使用 rustls create;
// reqwest::Certificate 代表自定义 Server CA 证书,后续可以使用 ClientBuilder::add_root_certificate() 添加。
let mut buf = Vec::new();
File::open("my_cert.der")?
    .read_to_end(&mut buf)?;
let cert = reqwest::Certificate::from_der(&buf)?;

let mut buf = Vec::new();
File::open("my_cert.pem")?
    .read_to_end(&mut buf)?;
let cert = reqwest::Certificate::from_pem(&buf)?;

let mut buf = Vec::new();
File::open("ca-bundle.crt")?
    .read_to_end(&mut buf)?;
let certs = reqwest::Certificate::from_pem_bundle(&buf)?;

// reqwest::Identify 代表 client 证书
let mut buf = Vec::new();
File::open("my-ident.pfx")?
    .read_to_end(&mut buf)?;
let pkcs12 = reqwest::Identity::from_pkcs12_der(&buf, "my-privkey-password")?;

// Parses a chain of PEM encoded X509 certificates, with the leaf certificate first. key is a PEM
// encoded PKCS #8 formatted private key for the leaf certificate.
let cert = fs::read("client.pem")?;
let key = fs::read("key.pem")?;
let pkcs8 = reqwest::Identity::from_pkcs8_pem(&cert, &key)?;

// Parses PEM encoded private key and certificate.
let mut buf = Vec::new();
File::open("my-ident.pem")?
    .read_to_end(&mut buf)?;
let id = reqwest::Identity::from_pem(&buf)?;

reqwest 默认不启用 cookie store,但是提供了简易的实现类型 Jar:

use reqwest::{cookie::Jar, Url};

let cookie = "foo=bar; Domain=yolo.local";
let url = "https://yolo.local".parse::<Url>().unwrap();

let jar = Jar::default();
jar.add_cookie_str(cookie, &url);

// and now add to a `ClientBuilder`?

1 Url
#

Url 用于在构建 Reqeust 时使用。

  • reqwest 没有使用 http crate 中的 Uri module,而是自定义实现。
pub struct Url { /* private fields */ }

impl Url

// 解析 URL 字符串(自动 URL 编码)
pub fn parse(input: &str) -> Result<Url, ParseError>
    let url = Url::parse("https://example.net")?;

// 解析 URL 字符串+表单参数(自动 URL 编码)
pub fn parse_with_params<I, K, V>( input: &str, iter: I ) -> Result<Url, ParseError>
    where I: IntoIterator, <I as IntoIterator>::Item: Borrow<(K, V)>, K: AsRef<str>, V: AsRef<str>,

    let url = Url::parse_with_params("https://example.net?dont=clobberme", &[("lang", "rust"), ("browser", "servo")])?;
    assert_eq!("https://example.net/?dont=clobberme&lang=rust&browser=servo", url.as_str());

// 添加一级 URL Path
pub fn join(&self, input: &str) -> Result<Url, ParseError>
    let base = Url::parse("https://example.net/a/b.html")?;
    let url = base.join("c.png")?;
    assert_eq!(url.as_str(), "https://example.net/a/c.png");  // Not /a/b.html/c.png
    let base = Url::parse("https://example.net/a/b/")?;
    let url = base.join("c.png")?;
    assert_eq!(url.as_str(), "https://example.net/a/b/c.png");

pub fn make_relative(&self, url: &Url) -> Option<String>
    let base = Url::parse("https://example.net/a/b/")?;
    let url = Url::parse("https://example.net/a/d/c.png")?;
    let relative = base.make_relative(&url);
    assert_eq!(relative.as_ref().map(|s| s.as_str()), Some("../d/c.png"));

pub fn options<'a>() -> ParseOptions<'a>
pub fn as_str(&self) -> &str
pub fn into_string(self) -> String

pub fn origin(&self) -> Origin

pub fn scheme(&self) -> &str
    let url = Url::parse("file:///tmp/foo")?;
    assert_eq!(url.scheme(), "file");

pub fn is_special(&self) -> bool
pub fn has_authority(&self) -> bool
    let url = Url::parse("ftp://[email protected]")?;
    assert!(url.has_authority());

pub fn authority(&self) -> &str
    let url = Url::parse("file:///tmp/foo")?;
    assert_eq!(url.authority(), "");
    let url = Url::parse("https://user:[email protected]/tmp/foo")?;
    assert_eq!(url.authority(), "user:[email protected]");

pub fn cannot_be_a_base(&self) -> bool
pub fn username(&self) -> &str
    let url = Url::parse("ftp://[email protected]")?;
    assert_eq!(url.username(), "rms");

pub fn password(&self) -> Option<&str>
    let url = Url::parse("ftp://:[email protected]")?;
    assert_eq!(url.password(), Some("secret123"));

pub fn has_host(&self) -> bool
pub fn host_str(&self) -> Option<&str>
pub fn host(&self) -> Option<Host<&str>>
pub fn domain(&self) -> Option<&str>
pub fn port(&self) -> Option<u16>
pub fn port_or_known_default(&self) -> Option<u16>

pub fn socket_addrs( &self, default_port_number: impl Fn() -> Option<u16> ) -> Result<Vec<SocketAddr>, Error>

pub fn path(&self) -> &str
    let url = Url::parse("https://example.com/countries/việt nam")?;
    assert_eq!(url.path(), "/countries/vi%E1%BB%87t%20nam");

pub fn path_segments(&self) -> Option<Split<'_, char>>

//  返回编码后的查询字符串值
pub fn query(&self) -> Option<&str>
    fn run() -> Result<(), ParseError> {
    let url = Url::parse("https://example.com/products?page=2")?;
    let query = url.query();
    assert_eq!(query, Some("page=2"));
    let url = Url::parse("https://example.com/?country=español")?;
    let query = url.query();
    assert_eq!(query, Some("country=espa%C3%B1ol"));

pub fn query_pairs(&self) -> Parse<'_>
pub fn fragment(&self) -> Option<&str>
    let url = Url::parse("https://example.com/data.csv#row=4")?;
    assert_eq!(url.fragment(), Some("row=4"));

pub fn set_fragment(&mut self, fragment: Option<&str>)
pub fn set_query(&mut self, query: Option<&str>)
    let mut url = Url::parse("https://example.com/products")?;
    assert_eq!(url.as_str(), "https://example.com/products");
    url.set_query(Some("page=2"));
    assert_eq!(url.as_str(), "https://example.com/products?page=2");
    assert_eq!(url.query(), Some("page=2"));

pub fn query_pairs_mut(&mut self) -> Serializer<'_, UrlQuery<'_>>
pub fn set_path(&mut self, path: &str)
    let mut url = Url::parse("https://example.com/api")?;
    url.set_path("data/report.csv");
    assert_eq!(url.as_str(), "https://example.com/data/report.csv");
    assert_eq!(url.path(), "/data/report.csv");

pub fn path_segments_mut(&mut self) -> Result<PathSegmentsMut<'_>, ()>
pub fn set_port(&mut self, port: Option<u16>) -> Result<(), ()>
pub fn set_host(&mut self, host: Option<&str>) -> Result<(), ParseError>
pub fn set_ip_host(&mut self, address: IpAddr) -> Result<(), ()>
pub fn set_password(&mut self, password: Option<&str>) -> Result<(), ()>
pub fn set_username(&mut self, username: &str) -> Result<(), ()>
pub fn set_scheme(&mut self, scheme: &str) -> Result<(), ()>
pub fn from_file_path<P>(path: P) -> Result<Url, ()> where P: AsRef<Path>,
pub fn from_directory_path<P>(path: P) -> Result<Url, ()> where P: AsRef<Path>,
pub fn to_file_path(&self) -> Result<PathBuf, ()>

Url::parse()/parse_with_params() 均会对 URL 和 params 字符串进行 URL 编码:

#[tokio::main]
async fn main()  -> Result<(), Box<dyn std::error::Error>>{
    use reqwest::Url;
    let url = Url::parse_with_params(
        "https://example.net?abc=def %中&dont=clobberme#% 中",
        &[("lang", "rust"), ("&browser", "servo se#"), ("%asdfa asdf", "a中文c")],
    )?;
    println!("url: {}", url);
    Ok(())
}

// zj@a:~/.emacs.d/rust-playground/at-2024-06-05-202535$ cargo run
//    Compiling foo v0.1.0 (/Users/alizj/.emacs.d/rust-playground/at-2024-06-05-202535)
//     Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.28s
//      Running `target/debug/foo`
// url: https://example.net/?abc=def%20%%E4%B8%AD&dont=clobberme&lang=rust&%26browser=servo+se%23&%25asdfa+asdf=a%E4%B8%AD%E6%96%87c#%%20%E4%B8%AD

2 Request/RequestBuilder
#

axum 自定义了 Request/Response 类型,且实现了到 http crate 的 Request 和 Response 的 TryFrom 转换。。

axum 也自定义了 Body 类型,且实现了 http::Body trait。

Request 是可以被 Client::execute() 发送的请求:

pub struct Request { /* private fields */ }

// 创建 Request
pub fn new(method: Method, url: Url) -> Self

pub fn method(&self) -> &Method
pub fn method_mut(&mut self) -> &mut Method

pub fn url(&self) -> &Url
pub fn url_mut(&mut self) -> &mut Url

pub fn headers(&self) -> &HeaderMap
pub fn headers_mut(&mut self) -> &mut HeaderMap

// 返回的是 reqwest::Body struct 类型
pub fn body(&self) -> Option<&Body>
pub fn body_mut(&mut self) -> &mut Option<Body>

pub fn timeout(&self) -> Option<&Duration>
pub fn timeout_mut(&mut self) -> &mut Option<Duration>

pub fn version(&self) -> Version
pub fn version_mut(&mut self) -> &mut Version

pub fn try_clone(&self) -> Option<Request>

// HttpRequest 是 http::Request 类型,Body 是 reqwest::Body 类型。
// 提供了 reqwest::Request 和 http::Request 类型相互转换的 TryFrom trait。
impl<T> TryFrom<HttpRequest<T>> for Request where T: Into<Body>
impl TryFrom<Request> for HttpRequest<Body>

RequestBuilder 用来构造 Request:

  • build() 返回构造的 Request;
  • send() 构造并发送 异步 Request ,返回 Response。需要事先调用 from_parts() 来设置 Client。
pub struct RequestBuilder { /* private fields */ }

// RequestBuilder 实现的方法
pub fn from_parts(client: Client, request: Request) -> RequestBuilder

// 为请求添加一个 header
pub fn header<K, V>(self, key: K, value: V) -> RequestBuilder
where
    HeaderName: TryFrom<K>,
    <HeaderName as TryFrom<K>>::Error: Into<Error>,
    HeaderValue: TryFrom<V>,
    <HeaderValue as TryFrom<V>>::Error: Into<Error>

// 将传入的 headers merge 到 Request 中
pub fn headers(self, headers: HeaderMap) -> RequestBuilder

// 设置认证
pub fn basic_auth<U, P>( self, username: U, password: Option<P> ) -> RequestBuilder where U: Display, P: Display
pub fn bearer_auth<T>(self, token: T) -> RequestBuilder where T: Display

// 设置请求 body
pub fn body<T: Into<Body>>(self, body: T) -> RequestBuilder

// 设置 Request timeout,从开始建立连接到响应 Body 结束。
// 只影响这一次请求,重载 ClientBuilder::timeout() 中定义的超时。
pub fn timeout(self, timeout: Duration) -> RequestBuilder

// 设置 HTTP 版本
pub fn version(self, version: Version) -> RequestBuilder

// 设置 URL query string,添加指定的 query 内容。
// 例如:.query(&[("foo", "a"), ("foo", "b")]) gives "foo=a&foo=b".
pub fn query<T: Serialize + ?Sized>(self, query: &T) -> RequestBuilder

// 设置 Body 为传入的 from 表单编码的结果,Content-Type 为 application/x-www-form-urlencoded
// 自动对 form 数据进行 URL 编码。
pub fn form<T: Serialize + ?Sized>(self, form: &T) -> RequestBuilder

// 设置 Body 为 multipart/form-data body,可以包含表单数据和文件数据
pub fn multipart(self, multipart: Form) -> RequestBuilder

//  设置 Body 为 json 数据。
pub fn json<T: Serialize + ?Sized>(self, json: &T) -> RequestBuilder

pub fn fetch_mode_no_cors(self) -> RequestBuilder

// 构造 Request
pub fn build(self) -> Result<Request>
pub fn build_split(self) -> (Client, Result<Request>)

// 构造并异步发送 Request,返回 Response
pub fn send(self) -> impl Future<Output = Result<Response, Error>>

pub fn try_clone(&self) -> Option<RequestBuilder>

Request 的 form() 和 multipart() 方法的区别:

  1. form() 方法:传入一个 Serialize 对象,对其各字段进行 URL 编码,设置 Content-Type: application/x-www-form-urlencoded
  2. multipart() 方法:传入一个 multipart::Form 对象,设置 Content-Type: multipart/form-data ;

一般使用 Client 提供的方法来创建自包含 Client 和 Method 的 RequestBuilder,如 reqwest::Client::new().get(url) :

Client 是使用连接池的 HTTP Clint,可以使用 new() 方法创建缺省配置的 Client,也可以使用 ClientBuilder 来自定义 Client。

3 multipart Form/Part
#

使用 multipart/form 需要开启 multipart feature。

multipart/form-data MIME 类型用于提交包含文件和二进制数据的表单,适用于文件上传等场景。结构如下:

  1. 边界 :开始部分 --boundary
  2. 头部 :包含内容描述 Header,如 Content-DispositionContent-Type
  3. 空行 :用于分隔头部和内容
  4. 内容 :表单字段或文件数据
  5. 边界 :结束部分 --boundary-- (⚠️:以 – 结尾表示结束边界)
POST /upload HTTP/1.1
Host: example.com
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW

------WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="field1"

value1
------WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="field2"

value2
------WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="file"; filename="example.txt"
Content-Type: text/plain

This is the content of the file.
------WebKitFormBoundary7MA4YWxkTrZu0gW--

Form 是一个异步的 multipart/form-data 请求,支持同时上传表单数据和文件数据:

impl Form

pub fn new() -> Form

// 返回该 From 使用的 boundary 字符串
pub fn boundary(&self) -> &str

// 添加表单数据
pub fn text<T, U>(self, name: T, value: U) -> Form where T: Into<Cow<'static, str>>, U: Into<Cow<'static, str>>

// 添加文件数据,part 参数为 multipart::Part 对象,包含 bytes 和 filename
pub fn part<T>(self, name: T, part: Part) -> Form where T: Into<Cow<'static, str>>

pub fn percent_encode_path_segment(self) -> Form
pub fn percent_encode_attr_chars(self) -> Form
pub fn percent_encode_noop(self) -> Form

reqwest::multipart::Part 是 multipart 的一个 part 部分,可以同时包含表单和文件数据:

  • text() 表单数据;
  • bytes() 任意二进制数据;
    • mime_str() 设置本 part 的 MIME 类型;
    • file_name() 设置本 part 的 filename;
    • headers() 设置本 part 的自定义 header;
impl Part

// 文本数据
pub fn text<T>(value: T) -> Part where T: Into<Cow<'static, str>>
// 二进制数据
pub fn bytes<T>(value: T) -> Part where T: Into<Cow<'static, [u8]>>
pub fn stream<T: Into<Body>>(value: T) -> Part
pub fn stream_with_length<T: Into<Body>>(value: T, length: u64) -> Part
// MIME 类型
pub fn mime_str(self, mime: &str) -> Result<Part>
// 文件名
pub fn file_name<T>(self, filename: T) -> Part where T: Into<Cow<'static, str>>
// http header
pub fn headers(self, headers: HeaderMap) -> Part

示例:使用 multipart/form-data 上传表单和文件:

  • 先创建一个 multipart::Form 对象,.text() 设置表单,.part() 设置文件;
  • 调用 request::multipart() 方法,传入上一步创建的 multipart form 对象;
use reqwest::multipart;
use reqwest::Client;
use std::fs;
use tokio;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    // 创建 HTTP 客户端
    let client = Client::new();

    // 读取文件内容
    let file_content = fs::read("path/to/your/file")?;

    // 创建多部分表单
    let form = multipart::Form::new()
        .text("field1", "value1")
        .text("field2", "value2")
        .part("file", multipart::Part::bytes(file_content).file_name("your_file.txt"));

    // 发送 POST 请求
    let response = client
        .post("http://example.com/upload")
        .multipart(form)
        .send()
        .await?;

    // 检查响应状态
    if response.status().is_success() {
        println!("Upload successful!");
    } else {
        println!("Upload failed: {:?}", response.status());
    }

    Ok(())
}

4 Body
#

两类 Body:

  1. Body trait:在 http_body crate 中定义,在 hyper/axum/reqwest 中得到复用;
  2. Body struct:在 reqwest::Body 中定义, 实现了 http_body::Body trait ;

reqwest::Body 没有构建方法,但是实现了 From<T> trait,可以从其它类型转换而来:

pub struct Body { /* private fields */ }

impl Body
pub fn as_bytes(&self) -> Option<&[u8]>
pub fn wrap_stream<S>(stream: S) -> Body
where
    S: TryStream + Send + Sync + 'static,
    S::Error: Into<Box<dyn Error + Send + Sync>>,
    Bytes: From<S::Ok>
// 实现 http_body::Body trait,返回的 Data 类型是 bytes::Bytes,可以当作 &[u8] 来使用。
impl Body for Body
    type Data = Bytes
    type Error = Error

// 创建 Body
impl From<&'static [u8]> for Body
impl From<&'static str> for Body
impl From<Bytes> for Body
impl From<File> for Body
impl From<Response> for Body
impl From<String> for Body
impl From<Vec<u8>> for Body

Body 的使用场景:

  1. reqwest::Request::body() : 获取 Body;
  2. reqwest::RequestBuilder::body(): 设置 Body;

5 Client/ClientBuilder
#

Client 对象用于构造 RequestBuilder 和发送 Request,它内部使用连接池和 Arc,是线程安全的:

impl Client
// 创建一个缺省配置的 Client
pub fn new() -> Client

// 创建一个 RequestBuilder
// 通用 request builder
pub fn request<U: IntoUrl>(&self, method: Method, url: U) -> RequestBuilder

// 后调调用 RequestBuilder 的 send() 方法来发送请求
pub fn get<U: IntoUrl>(&self, url: U) -> RequestBuilder
pub fn post<U: IntoUrl>(&self, url: U) -> RequestBuilder
pub fn put<U: IntoUrl>(&self, url: U) -> RequestBuilder
pub fn patch<U: IntoUrl>(&self, url: U) -> RequestBuilder
pub fn delete<U: IntoUrl>(&self, url: U) -> RequestBuilder
pub fn head<U: IntoUrl>(&self, url: U) -> RequestBuilder

// 异步执行请求(返回一个 Future)
pub fn execute(&self, request: Request) -> impl Future<Output = Result<Response, Error>>

// 示例
// 创建缺省配置的、可复用的 Client(没有使用 ClientBuilder)
let client = reqwest::Client::new();
let resp = client.delete("http://httpbin.org/delete") // 创建 RequestBuilder
    .basic_auth("admin", Some("good password")) // 设置 RequestBuilder
    .send() // 发送请求获得响应
    .await?;

// 发送 mulitipart 表单和文件数据
let form = reqwest::multipart::Form::new().text("key3", "value3").text("key4", "value4");
let response = client
    .post("your url") // 返回 RequestBuilder
    .multipart(form) // 设置 RequestBuilder
    .send()
    .await?;

// 发送 form 表单数据
let mut params = HashMap::new();
params.insert("lang", "rust# 中"); // 会进行 URL 编码
let client = reqwest::Client::new();
let req = client
    .post("http://httpbin.org")
    .form(&params).build().unwrap();
let bytes =  req.body().unwrap().as_bytes().unwrap();
println!("req body: {:?}", String::from_utf8_lossy(bytes));
// req body: "lang=rust%23+%E4%B8%AD"

// 发送 JSON 数据
let mut map = HashMap::new();
map.insert("lang", "rust# %#"); // 不会进行 URL 编码
map.insert("body", "json");
let client = reqwest::Client::new();
let res = client
    .post("http://httpbin.org/post")
    .json(&map)
    .build()
    .unwrap();
let bytes = res.body().unwrap().as_bytes().unwrap();
println!("req json: {:?}", String::from_utf8_lossy(bytes));
// req json: "{\"lang\":\"rust# %#\",\"body\":\"json\"}"

ClientBuilder 用于自定义 Client 配置:

  • HeaderMap 等 header 相关类型实际是 http crate 的 header module 提供的。
impl ClientBuilder
pub fn new() -> ClientBuilder
pub fn build(self) -> Result<Client> // 返回一个 Client

pub fn user_agent<V>(self, value: V) -> ClientBuilder where V: TryInto<HeaderValue>, V::Error: Into<Error>
// 为每个 reqeust 设置的缺省 header
pub fn default_headers(self, headers: HeaderMap) -> ClientBuilder

// 缺省情况下,不开启 cookie sotre,需要调用以下两个方法之一来开启
// 1. 开启并使用缺省的 cookie store
pub fn cookie_store(self, enable: bool) -> ClientBuilder
// 2. 开启并使用指定的 cookiestore
pub fn cookie_provider<C: CookieStore + 'static>(self, cookie_store: Arc<C>) -> ClientBuilder

pub fn gzip(self, enable: bool) -> ClientBuilder
pub fn brotli(self, enable: bool) -> ClientBuilder
pub fn zstd(self, enable: bool) -> ClientBuilder
pub fn deflate(self, enable: bool) -> ClientBuilder
pub fn no_gzip(self) -> ClientBuilder
pub fn no_brotli(self) -> ClientBuilder
pub fn no_zstd(self) -> ClientBuilder
pub fn no_deflate(self) -> ClientBuilder

pub fn redirect(self, policy: Policy) -> ClientBuilder
// 缺省为 true
pub fn referer(self, enable: bool) -> ClientBuilder
// 缺省从环境变量中获取 HTTP、HTTPS 代理,socks5 代理需要开启 socks feature 的情况下才支持。
pub fn proxy(self, proxy: Proxy) -> ClientBuilder
pub fn no_proxy(self) -> ClientBuilder

// 从建立连接到读取 body 结束的超时时间(默认不超时)
pub fn timeout(self, timeout: Duration) -> ClientBuilder
// 每次 read 超时时间,read 成功后重置(默认不超时)
pub fn read_timeout(self, timeout: Duration) -> ClientBuilder
// 建立连接超时时间(默认不超时)
pub fn connect_timeout(self, timeout: Duration) -> ClientBuilder
// 启用后,发送 TRACE 级别的 log
pub fn connection_verbose(self, verbose: bool) -> ClientBuilder

// 连接池空闲时间(默认 90s)
pub fn pool_idle_timeout<D>(self, val: D) -> ClientBuilder where D: Into<Option<Duration>>
// 默认无限制
pub fn pool_max_idle_per_host(self, max: usize) -> ClientBuilder

// Send headers as title case instead of lowercase.
pub fn http1_title_case_headers(self) -> ClientBuilder
pub fn http1_allow_obsolete_multiline_headers_in_responses(self, value: bool ) -> ClientBuilder
pub fn http1_ignore_invalid_headers_in_responses( self, value: bool) -> ClientBuilder
pub fn http1_allow_spaces_after_header_name_in_responses( self, value: bool ) -> ClientBuilder
pub fn http1_only(self) -> ClientBuilder
pub fn http09_responses(self) -> ClientBuilder

pub fn http2_prior_knowledge(self) -> ClientBuilder
pub fn http2_initial_stream_window_size( self, sz: impl Into<Option<u32>> ) -> ClientBuilder
pub fn http2_initial_connection_window_size( self, sz: impl Into<Option<u32>> ) -> ClientBuilder
pub fn http2_adaptive_window(self, enabled: bool) -> ClientBuilder
pub fn http2_max_frame_size(self, sz: impl Into<Option<u32>>) -> ClientBuilder
pub fn http2_keep_alive_interval( self, interval: impl Into<Option<Duration>>) -> ClientBuilder
pub fn http2_keep_alive_timeout(self, timeout: Duration) -> ClientBuilder
pub fn http2_keep_alive_while_idle(self, enabled: bool) -> ClientBuilder

// 缺省为 true
pub fn tcp_nodelay(self, enabled: bool) -> ClientBuilder

pub fn local_address<T>(self, addr: T) -> ClientBuilder where T: Into<Option<IpAddr>>
pub fn interface(self, interface: &str) -> ClientBuilder
pub fn tcp_keepalive<D>(self, val: D) -> ClientBuilder where D: Into<Option<Duration>>

// 添加自定义 root 证书
pub fn add_root_certificate(self, cert: Certificate) -> ClientBuilder

// 缺省为 true,使用系统 root 证书,设置为 false 时只使用添加的证书
pub fn tls_built_in_root_certs( self, tls_built_in_root_certs: bool ) -> ClientBuilder
pub fn tls_built_in_webpki_certs(self, enabled: bool) -> ClientBuilder
pub fn tls_built_in_native_certs(self, enabled: bool) -> ClientBuilder

// 指定 client 证书
pub fn identity(self, identity: Identity) -> ClientBuilder

// 接受无效的证书
pub fn danger_accept_invalid_hostnames( self, accept_invalid_hostname: bool ) -> ClientBuilder
pub fn danger_accept_invalid_certs( self, accept_invalid_certs: bool) -> ClientBuilder

// 默认为 true
pub fn tls_sni(self, tls_sni: bool) -> ClientBuilder

pub fn min_tls_version(self, version: Version) -> ClientBuilder
pub fn max_tls_version(self, version: Version) -> ClientBuilder

// tls backend 选择
pub fn use_native_tls(self) -> ClientBuilder
pub fn use_rustls_tls(self) -> ClientBuilder

pub fn use_preconfigured_tls(self, tls: impl Any) -> ClientBuilder
// Add TLS information as TlsInfo extension to responses
pub fn tls_info(self, tls_info: bool) -> ClientBuilder

// 默认为 false
pub fn https_only(self, enabled: bool) -> ClientBuilder

// Enables the hickory-dns async resolver instead of a default threadpool using getaddrinfo.
// If the hickory-dns feature is turned on, the default option is enabled.
pub fn hickory_dns(self, enable: bool) -> ClientBuilder
pub fn no_trust_dns(self) -> ClientBuilder
pub fn no_hickory_dns(self) -> ClientBuilder

// 将 domain 的地址重写为指定值
pub fn resolve(self, domain: &str, addr: SocketAddr) -> ClientBuilder

// 将 domain 的域名解析结果重写为指定 slice
pub fn resolve_to_addrs( self, domain: &str, addrs: &[SocketAddr]) -> ClientBuilder

// 使用指定的 DNS Resolve 实现
pub fn dns_resolver<R: Resolve + 'static>( self, resolver: Arc<R>) -> ClientBuilder

示例:

// Name your user agent after your app?
static APP_USER_AGENT: &str = concat!(
    env!("CARGO_PKG_NAME"),
    "/",
    env!("CARGO_PKG_VERSION"),
);

let client = reqwest::Client::builder()
    .user_agent(APP_USER_AGENT)
    .build()?;
let res = client
    .get("https://www.rust-lang.org") // 创建一个 RequestBuilder
    .send() // 异步发送 Request
    .await?;

use reqwest::header;
let mut headers = header::HeaderMap::new();
headers.insert("X-MY-HEADER", header::HeaderValue::from_static("value"));

// Consider marking security-sensitive headers with `set_sensitive`.
let mut auth_value = header::HeaderValue::from_static("secret");
auth_value.set_sensitive(true);
headers.insert(header::AUTHORIZATION, auth_value);

// get a client builder
let client = reqwest::Client::builder()
    .default_headers(headers)
    .build()?;
let res = client.get("https://www.rust-lang.org").send().await?;

6 Response
#

reqwest 自定义了 Response 类型。

pub struct Response { /* private fields */ }

impl Response
pub fn status(&self) -> StatusCode
pub fn version(&self) -> Version
pub fn headers(&self) -> &HeaderMap
pub fn headers_mut(&mut self) -> &mut HeaderMap
pub fn content_length(&self) -> Option<u64>
pub fn cookies<'a>(&'a self) -> impl Iterator<Item = Cookie<'a>> + 'a
pub fn url(&self) -> &Url
pub fn remote_addr(&self) -> Option<SocketAddr>

pub fn extensions(&self) -> &Extensions
pub fn extensions_mut(&mut self) -> &mut Extensions

// 返回 utf-8 编码的 body 文本
pub async fn text(self) -> Result<String>
pub async fn text_with_charset(self, default_encoding: &str) -> Result<String>
    let content = reqwest::get("http://httpbin.org/range/26").await?.text().await?;
    println!("text: {content:?}");

pub async fn json<T: DeserializeOwned>(self) -> Result<T>
    #[derive(Deserialize)]
    struct Ip { origin: String, }
    let ip = reqwest::get("http://httpbin.org/ip") .await? .json::<Ip>() .await?;
    println!("ip: {}", ip.origin);

pub async fn bytes(self) -> Result<Bytes> // bytes::Bytes 类型
    let bytes = reqwest::get("http://httpbin.org/ip") .await? .bytes() .await?;
    println!("bytes: {bytes:?}");

pub async fn chunk(&mut self) -> Result<Option<Bytes>> // 流式响应
    let mut res = reqwest::get("https://hyper.rs").await?;
    while let Some(chunk) = res.chunk().await? {
        println!("Chunk: {chunk:?}");
}

pub fn bytes_stream(self) -> impl Stream<Item = Result<Bytes>>
    use futures_util::StreamExt;
    let mut stream = reqwest::get("http://httpbin.org/ip") .await? .bytes_stream();
    while let Some(item) = stream.next().await {
        println!("Chunk: {:?}", item?);
    }

pub fn error_for_status(self) -> Result<Self>
pub fn error_for_status_ref(&self) -> Result<&Self>

pub async fn upgrade(self) -> Result<Upgraded>

7 Error
#

Struct reqwest::Error 表示在发送 Reqeust 时遇到的错误:

pub struct Error { /* private fields */ }

// reqwest::RequestBuilder 的 send() 返回 Error
pub fn send(self) -> impl Future<Output = Result<Response, Error>>

// reqwest::Client 的 execute() 返回 Error
pub fn execute(
    &self,
    request: Request,
) -> impl Future<Output = Result<Response, Error>>

request::Error 类型的方法:

pub fn url(&self) -> Option<&Url>
pub fn url_mut(&mut self) -> Option<&mut Url>

// Error 信息默认打印请求的完整 URL,可能包含敏感信息,可以调用 without_url() 来清理不显示。
pub fn with_url(self, url: Url) -> Self
pub fn without_url(self) -> Self

// 产生该 Error 相关的各种原因
pub fn is_builder(&self) -> bool
pub fn is_redirect(&self) -> bool
pub fn is_status(&self) -> bool
pub fn is_timeout(&self) -> bool
pub fn is_request(&self) -> bool
pub fn is_connect(&self) -> bool
pub fn is_body(&self) -> bool
pub fn is_decode(&self) -> bool

// Returns the status code, if the error was generated from a response.
pub fn status(&self) -> Option<StatusCode>

示例:

// displays last stop of a redirect loop
let response = reqwest::get("http://site.with.redirect.loop").await;
if let Err(e) = response {
    if e.is_redirect() {
        if let Some(final_stop) = e.url() {
            println!("redirect loop at {final_stop}");
        }
    }
}

use http::StatusCode;
assert_eq!(StatusCode::from_u16(200).unwrap(), StatusCode::OK);
assert_eq!(StatusCode::NOT_FOUND.as_u16(), 404);
assert!(StatusCode::OK.is_success());
rust crate - 这篇文章属于一个选集。
§ 11: 本文

相关文章

axum
··19783 字
Rust Rust-Crate
axum 是基于 hyper 实现的高性能异步 HTTP 1/2 Server 库。
http/http_body crate
··5410 字
Rust Rust-Crate
http/http_body crate 是公共的 http 和 body 定义,在 tokio 系列的 HTTP 库,如 hyper/axum/reqwest 中得到广泛应用。
clap
··6447 字
Rust Rust-Crate
clap 用于快速构建命令行程序,提供命令&参数定义、解析等功能。
config
··2084 字
Rust Rust-Crate
config 提供从文件或环境变量解析配置参数的功能。