rquest 5.1.0

A blazing-fast Rust HTTP Client with TLS fingerprint
Documentation
#![cfg(not(target_arch = "wasm32"))]
mod support;

use std::time::Duration;

use http::header::{AUTHORIZATION, CACHE_CONTROL, REFERER};
use http_body_util::BodyExt;
use rquest::{CertStore, EmulationProvider, TlsConfig, TlsInfo};
use support::server;

#[tokio::test]
async fn update_headers() {
    let _ = env_logger::try_init();
    let server = server::http(move |req| async move {
        assert_eq!(
            req.headers().get(http::header::ACCEPT).unwrap(),
            "application/json"
        );
        http::Response::default()
    });

    let client = rquest::Client::new();

    client
        .update()
        .headers(|headers| {
            headers.insert(
                http::header::ACCEPT,
                http::HeaderValue::from_static("application/json"),
            );
        })
        .apply()
        .unwrap();

    let url = format!("http://{}", server.addr());
    let resp = client.get(url).send().await.unwrap();
    assert!(resp.status().is_success());
    assert!(client.headers().contains_key(http::header::ACCEPT));

    let client2 = client.clone();
    tokio::spawn(async move {
        client2
            .update()
            .headers(|headers| {
                headers.insert(
                    http::header::ACCEPT_ENCODING,
                    http::HeaderValue::from_static("gzip"),
                );
            })
            .apply()
            .unwrap();
    })
    .await
    .unwrap();

    let server = server::http(move |req| async move {
        assert_eq!(
            req.headers().get(http::header::ACCEPT_ENCODING).unwrap(),
            "gzip"
        );
        http::Response::default()
    });

    let url = format!("http://{}", server.addr());
    let resp = client.get(url).send().await.unwrap();
    assert!(resp.status().is_success());
    assert!(client.headers().contains_key(http::header::ACCEPT_ENCODING));
}

#[tokio::test]
async fn test_headers_order_with_client_update() {
    use http::{HeaderName, HeaderValue};
    use rquest::Client;
    use rquest::header::{ACCEPT, CONTENT_TYPE, USER_AGENT};

    let server = server::http(move |req| async move {
        assert_eq!(req.method(), "POST");

        let expected_headers = vec![
            ("user-agent", "my-test-client"),
            ("accept", "*/*"),
            ("cookie", "cookie1=cookie1"),
            ("cookie", "cookie2=cookie2"),
            ("content-type", "application/json"),
            ("authorization", "Bearer test-token"),
            ("referer", "https://example.com"),
            ("cache-control", "no-cache"),
        ];

        for (i, (expected_key, expected_value)) in expected_headers.iter().enumerate() {
            let (key, value) = req.headers().iter().nth(i).unwrap();
            assert_eq!(key.as_str(), *expected_key);
            assert_eq!(value.as_bytes(), expected_value.as_bytes());
        }

        let full: Vec<u8> = req
            .into_body()
            .collect()
            .await
            .expect("must succeed")
            .to_bytes()
            .to_vec();

        assert_eq!(full, br#"{"message":"hello"}"#);

        http::Response::default()
    });

    let url = format!("http://{}/test", server.addr());

    let client = Client::builder().no_proxy().build().unwrap();

    client
        .update()
        .headers(|headers| {
            headers.clear();
            headers.insert(ACCEPT, HeaderValue::from_static("*/*"));
            headers.insert(CONTENT_TYPE, HeaderValue::from_static("application/json"));
            headers.insert(USER_AGENT, HeaderValue::from_static("my-test-client"));
            headers.insert(AUTHORIZATION, HeaderValue::from_static("Bearer test-token"));
            headers.insert(REFERER, HeaderValue::from_static("https://example.com"));
            headers.insert(CACHE_CONTROL, HeaderValue::from_static("no-cache"));
            headers.append("cookie", HeaderValue::from_static("cookie1=cookie1"));
            headers.append("cookie", HeaderValue::from_static("cookie2=cookie2"));
        })
        .headers_order(vec![
            HeaderName::from_static("user-agent"),
            HeaderName::from_static("accept"),
            HeaderName::from_static("cookie"),
            HeaderName::from_static("content-type"),
            HeaderName::from_static("authorization"),
            HeaderName::from_static("referer"),
            HeaderName::from_static("cache-control"),
        ])
        .apply()
        .unwrap();

    let res = client
        .post(&url)
        .body(r#"{"message":"hello"}"#)
        .send()
        .await
        .unwrap();

    assert_eq!(res.status(), rquest::StatusCode::OK);
}

#[tokio::test]
async fn update_emulation() {
    let _ = env_logger::try_init();
    let server = server::http(move |req| async move {
        assert_eq!(
            req.headers().get(http::header::ACCEPT).unwrap(),
            "application/json"
        );
        http::Response::default()
    });

    let client = rquest::Client::new();

    client
        .update()
        .emulation(
            EmulationProvider::builder()
                .tls_config(TlsConfig::default())
                .default_headers({
                    let mut headers = http::HeaderMap::new();
                    headers.insert(
                        http::header::ACCEPT,
                        http::HeaderValue::from_static("application/json"),
                    );
                    headers
                })
                .build(),
        )
        .apply()
        .unwrap();

    let url = format!("http://{}", server.addr());
    let resp = client.get(url).send().await.unwrap();
    assert!(resp.status().is_success());
    assert!(client.headers().contains_key(http::header::ACCEPT));

    let client2 = client.clone();
    tokio::spawn(async move {
        client2
            .update()
            .emulation(
                EmulationProvider::builder()
                    .tls_config(TlsConfig::default())
                    .default_headers({
                        let mut headers = http::HeaderMap::new();
                        headers.insert(
                            http::header::ACCEPT_ENCODING,
                            http::HeaderValue::from_static("gzip"),
                        );
                        headers
                    })
                    .build(),
            )
            .apply()
            .unwrap();
    })
    .await
    .unwrap();

    let server = server::http(move |req| async move {
        assert_eq!(
            req.headers().get(http::header::ACCEPT_ENCODING).unwrap(),
            "gzip"
        );
        http::Response::default()
    });

    let url = format!("http://{}", server.addr());
    let resp = client.get(url).send().await.unwrap();
    assert!(resp.status().is_success());
    assert!(client.headers().contains_key(http::header::ACCEPT_ENCODING));
}

#[tokio::test]
async fn updatea_cloned() {
    let _ = env_logger::try_init();
    let server = server::http(move |req| async move {
        assert_eq!(
            req.headers().get(http::header::ACCEPT).unwrap(),
            "application/json"
        );
        http::Response::default()
    });

    let client = rquest::Client::new();

    client
        .update()
        .emulation(
            EmulationProvider::builder()
                .tls_config(TlsConfig::default())
                .default_headers({
                    let mut headers = http::HeaderMap::new();
                    headers.insert(
                        http::header::ACCEPT,
                        http::HeaderValue::from_static("application/json"),
                    );
                    headers
                })
                .build(),
        )
        .apply()
        .unwrap();

    let url = format!("http://{}", server.addr());
    let resp = client.get(url).send().await.unwrap();
    assert!(resp.status().is_success());
    assert!(client.headers().contains_key(http::header::ACCEPT));

    let client2 = client.cloned();
    client2
        .update()
        .emulation(
            EmulationProvider::builder()
                .tls_config(TlsConfig::default())
                .default_headers({
                    let mut headers = http::HeaderMap::new();
                    headers.insert(
                        http::header::ACCEPT_ENCODING,
                        http::HeaderValue::from_static("gzip"),
                    );
                    headers
                })
                .build(),
        )
        .apply()
        .unwrap();

    let server = server::http(move |req| async move {
        assert_ne!(
            req.headers().get(http::header::ACCEPT_ENCODING),
            Some(&http::HeaderValue::from_static("gzip"))
        );
        http::Response::default()
    });

    let url = format!("http://{}", server.addr());
    let resp = client.get(url).send().await.unwrap();
    assert!(resp.status().is_success());
    assert!(!client.headers().contains_key(http::header::ACCEPT_ENCODING));
}

#[tokio::test]
async fn update_ssl_verify() {
    let client = rquest::Client::builder()
        .connect_timeout(Duration::from_secs(360))
        .cert_verification(false)
        .no_proxy()
        .build()
        .unwrap();

    let res = client.get("https://self-signed.badssl.com/").send().await;
    assert!(res.is_ok());

    client
        .update()
        .emulation(EmulationProvider::default())
        .apply()
        .unwrap();

    let res = client.get("https://self-signed.badssl.com/").send().await;
    assert!(res.is_ok());
}

#[tokio::test]
async fn update_ssl_certs_verify_stroe() {
    let client = rquest::Client::builder()
        .connect_timeout(Duration::from_secs(360))
        .cert_verification(false)
        .tls_info(true)
        .build()
        .unwrap();

    let resp = client
        .get("https://self-signed.badssl.com/")
        .send()
        .await
        .unwrap();

    let peer_cert_der = resp
        .extensions()
        .get::<TlsInfo>()
        .and_then(|info| info.peer_certificate())
        .unwrap();

    let store = CertStore::builder()
        .add_der_cert(peer_cert_der)
        .build()
        .unwrap();

    let client = rquest::Client::builder()
        .cert_store(store)
        .connect_timeout(Duration::from_secs(360))
        .no_proxy()
        .build()
        .unwrap();

    let res = client.get("https://self-signed.badssl.com/").send().await;
    assert!(res.is_ok());

    client
        .update()
        .emulation(EmulationProvider::default())
        .apply()
        .unwrap();

    let res = client.get("https://self-signed.badssl.com/").send().await;
    assert!(res.is_ok());
}