wreq

repository·main·Indexed 21 days ago

https://github.com/0x676e67/wreq

An ergonomic, privacy-aware Rust HTTP client and hard fork of reqwest designed for high-fidelity protocol matching. wreq specializes in emulating browser fingerprints (JA3, JA4, Akamai) and preserving HTTP/1 header case sensitivity to bypass WAFs and bot protection. It provides fine-grained control over TLS and HTTP/2 extensions, and integrates with wreq-util for over 100 browser device emulation profiles.

Tokens
19.8K
Snippets
68
Records
87
Agent score
75%

What's inside wreq

  1. How wreq handles HTTP/1 and HTTP/2 signatures

    main

    Unlike many Rust HTTP clients that rely on the http library (which may lowercase headers and trigger WAF rejections), wreq provides:

    • HTTP/1 Case Sensitivity: Full support for preserving header case to avoid rejection by Web Application Firewalls (WAFs).
    • HTTP/2 and TLS Parity: Instead of using simple fingerprint strings, wreq provides fine-grained control over TLS and HTTP/2 extensions and settings. This allows for precise emulation of browser fingerprints like JA3, JA4, and Akamai.
    • Device Emulation: Uses wreq-util to maintain 100+ browser device emulation profiles, ensuring that underlying protocol behaviors match specific browser models.
  2. Install wreq and wreq-util

    main

    To use wreq with the Tokio runtime and device emulation capabilities, add the following to your Cargo.toml. Note that wreq and wreq-util versions may vary; the example uses release candidates.

    [dependencies]
    tokio = { version = "1", features = ["full"] }
    wreq = "6.0.0-rc"
    wreq-util = "3.0.0-rc"
  3. Build wreq with BoringSSL dependencies

    main

    To avoid symbol conflicts between openssl-sys and boringssl (which can cause link failures), especially on Linux and Android, you should enable the prefix-symbols feature.

    Before building, ensure you have installed the BoringSSL build dependencies:

    sudo apt-get install build-essential cmake perl pkg-config libclang-dev musl-tools git -y

    Then build the project:

    cargo build --release
    sudo apt-get install build-essential cmake perl pkg-config libclang-dev musl-tools git -y
    cargo build --release
  4. What is OrigHeaderMap and when to use it

    main

    OrigHeaderMap is a specialized collection designed to preserve the original casing and insertion order of HTTP headers. While standard HTTP headers are case-insensitive, OrigHeaderMap tracks the exact spelling (e.g., X-Test vs x-test) and the order in which they were added.

    Use OrigHeaderMap when you need to:

    • Reproduce HTTP/1.x messages exactly as received.
    • Act as a proxy where header order or specific casing matters.
    • Debug or log exact header spellings.
    • Maintain specific header ordering during serialization.
    let mut headers = OrigHeaderMap::new();
    headers.insert("X-Test");
    headers.insert("x-test2");
    // Order and casing are preserved.
    for (name, orig) in headers.iter() {
        println!("Name: {:?}, Original: {:?}", name, orig.as_ref());
    }
  5. Use the Jar for stateful cookie management

    main

    The Jar struct is a thread-safe, in-memory cookie store used to manage stateful cookies across HTTP requests. It implements the CookieStore trait, allowing it to be integrated into a wreq::Client. It handles RFC 6265 compliant storage, including domain matching, path matching, expiration, and security attributes (like Secure).

    Key behaviors:

    • Domain Matching: Supports both host-only cookies and domain-scoped cookies.
    • Path Matching: Matches cookies based on the request URI path.
    • Security: Prevents insecure origins from setting or overlaying Secure cookies.
    • Expiration: Automatically filters out expired cookies during retrieval.
    use wreq::cookie::Jar;
    let jar = Jar::default();
  6. Understand the WebSocket graceful closing protocol

    main

    To perform a graceful close instead of an 'unclean' close (which happens automatically if you simply drop the WebSocket object), follow the protocol:

    1. Peer A sends a Close frame (optionally with a CloseFrame containing a CloseCode and reason).
    2. Peer B responds with a Close frame.
    3. Peer A processes any remaining messages sent by Peer B.
    4. Both peers close the connection.

    Note: After sending a Close frame, you can still read messages, but any attempt to send a new message will result in an error. wreq automatically handles responding to received Close frames.

  7. Access redirect history

    main

    When a request follows a chain of redirects, the history of those redirects is stored in the response extensions. You can retrieve this history by accessing the History object attached to the response. The History object can be iterated over to view HistoryEntry items, which contain the status code, URI, previous URI, and headers for each step in the chain.

    // Assuming 'resp' is the response from a request that followed redirects
    if let Some(history) = resp.extensions().get::<wreq::redirect::History>() {
        for entry in history.iter() {
            println!("Status: {}, URI: {}", entry.status, entry.uri);
        }
    }
  8. Cookie matching and scoping rules in Jar

    main

    The Jar enforces strict scoping for cookies based on domain and host identity:

    • Subdomain Isolation: Cookies explicitly scoped to a subdomain (e.g., Domain=api.example.com) will not leak to the parent domain (example.com) or sibling domains (other.example.com).
    • IPv4 Addressing: The jar does not perform suffix matching on IPv4 addresses. A cookie set on 192.168.0.1 will not match a request to a different IP even if the domain attribute is partially matched.
    • IPv6 Identity: The jar correctly identifies equivalent IPv6 addresses (e.g., [2001:db8::1] and [2001:0db8:0:0:0:0:0:1]) to ensure cookies are sent to the correct host.
  9. Cookie expiration and Max-Age behavior in Jar

    main

    The Jar manages cookie lifecycles using Max-Age and Expires attributes.

    • Max-Age Priority: If both Max-Age and Expires are present, Max-Age takes precedence and overrides Expires regardless of their order in the cookie string.
    • Positive Max-Age Required: Cookies with a Max-Age of 0 or a negative value are treated as expired and are not stored/retrieved. A valid Max-Age must be a positive integer.
    • Last Valid Max-Age: If multiple Max-Age attributes are provided, the last valid (positive) one is used.
    • Malformed Attributes: If Max-Age is malformed (e.g., Max-Age=invalid), the jar ignores that specific attribute. If the cookie has no valid expiration mechanism left, it is treated as a session cookie (no max_age or expires set).
  10. Manage original header casing with HeaderCaseName

    main

    HeaderCaseName is a type that stores both the normalized HeaderName and the original casing received in an HTTP message. It implements AsRef<[u8]>, allowing you to access the raw bytes of the original header name.

    It can be created from several types via the IntoHeaderCaseName trait:

    • &'static str
    • String
    • Bytes
    • HeaderName
    • &HeaderName
    • HeaderCaseName
    • &HeaderCaseName
  11. How Request and RequestBuilder work together

    main

    The Request type represents a fully constructed HTTP request that can be executed by a Client. The RequestBuilder is the intermediate state used to assemble a Request.

    • RequestBuilder::build(): Consumes the builder and returns a Result<Request>. Use this if you want to inspect or modify the Request object before sending it.
    • RequestBuilder::build_split(): Consumes the builder and returns a tuple (Client, Result<Request>), allowing you to keep the client for future use.
    • RequestBuilder::send(): The most common way to execute; it consumes the builder and returns a future that resolves to a Result<Response>.
    • Request::try_clone(): Attempts to create a copy of a Request. This returns None if the request body is a stream (as streams cannot be cloned easily).
    // Using build() to inspect the request
    let request = client.get(uri).build().expect("failed to build");
    println!("Method: {:?}", request.method());
    
    // Using send() to execute immediately
    let response = client.get(uri).send().await?;
  12. Configure per-request options via RequestConfig

    main

    You can override or specify connection-level settings for a specific request by attaching a RequestConfig<RequestOptions> to the request's extensions.

    Supported options in RequestOptions include:

    • proxy
    • version
    • tls_options
    • http1_options
    • http2_options
    • socket_bind_options
    • group

    When the HttpClient processes the request, it extracts these options and applies them to the underlying protocol builders (H1 or H2) or the connection descriptor.