leyline-http 0.1.5

HTTP client that impersonates Chrome, Firefox, and Safari on the wire: TLS (JA3/JA4), HTTP/2, and HTTP/3 fingerprints
Documentation

leyline-http

Leyline is an async Rust HTTP client that impersonates browsers on the wire. The TLS ClientHello, HTTP/2 SETTINGS and priorities, HTTP/3 transport parameters, and request header order all come from captures of shipped browsers, so a server that reads JA3, JA4, or HTTP/2 fingerprints sees Chrome, Firefox, or Safari, not a Rust library. It does for Rust what curl-impersonate does for curl.

use leyline::{Browser, Session};

#[tokio::main]
async fn main() -> leyline::Result<()> {
    let session = Session::builder()
        .browser(Browser::Chrome154)
        .audit(true)
        .build()?;

    let resp = session.get("https://example.com/").await?;
    if let Some(audit) = resp.audit() {
        // t13d1517h2_8daaf6152771_cb7bf5808d99, the JA4 of Chrome 154
        println!("{}", audit.ja4);
    }
    Ok(())
}

For the profiles captured from shipped browsers, the live test suite checks that tls.peet.ws sees the same JA4 and HTTP/2 fingerprint from Leyline as from the browser capture.

Plain client

Session::new() is a plain session:

use std::time::Duration;

use leyline::{RetryPolicy, Session};

# async fn run() -> leyline::Result<()> {
let page = leyline::get("https://example.com/").await?.text().await?;

let api = Session::builder()
    .base_url("https://api.example/v1/")
    .bearer_auth("my-token")
    .user_agent("my-tool/1.0")
    .timeout(Duration::from_secs(30))
    .retry(RetryPolicy::transient())
    .build()?;
let status = api.get("status").error_for_status().await?;
# drop((page, status));
# Ok(())
# }

No browser headers. A 4xx or 5xx is not an error unless you call error_for_status(). See API clients.

What it matches

A user agent is one header. A fingerprinting server also reads the ClientHello, the HTTP/2 frames, and the header order. Leyline matches every layer a capture shows:

Layer What Leyline sends
TLS Cipher suites, extensions and their order, GREASE, key shares, and ALPN, from BoringSSL at the revision Chrome 155 ships.
HTTP/2 SETTINGS, window update, pseudo-header order, and stream priority.
HTTP/3 QUIC ClientHello, transport parameters, connection ID lengths, HTTP/3 SETTINGS, and the size of the first datagram.
Headers The browser's header set and order for each kind of request: navigation, form, script, image, XHR, frame, and reload.
Fetch metadata sec-fetch-site, Origin, and Referer follow the Fetch rules across redirect chains.
Cookies SameSite follows each browser: Chrome judges the final target, Firefox the whole redirect chain.

Of the 31 bundled profiles, 25 are captures of the shipped browser. Each profile names the build it came from in captured_against, and the profile reference lists the source of every one.

Profiles

Browser Versions Notes
Chrome 145 to 155 Android is captured for Chrome 145 only.
Brave 146, 154, 155
Firefox 148 to 158 Firefox 155 to 157 offer QUIC v2.
Safari 18, 26, 27 macOS.
Safari on iOS 17, 18, 27
OkHttp 4.12 Android.
CFNetwork iOS 18, iOS 27, macOS 26 Native app traffic, not Safari.

Edge and Opera are brand overlays on the Chrome profiles for desktop platforms. The Opera overlay covers Chrome 145 to 153. To send a profile that is not bundled, load it at runtime with SessionBuilder::profile.

Install

Add Leyline and Tokio to Cargo.toml:

[dependencies]
leyline-http = "0.1"
tokio = { version = "1", features = ["macros", "rt-multi-thread"] }

The package is leyline-http. The library is leyline.

Requirements

  • Rust 1.96 or later, and Tokio.
  • One of these targets: aarch64-apple-darwin, x86_64-unknown-linux-gnu, aarch64-unknown-linux-gnu, x86_64-unknown-linux-musl, aarch64-unknown-linux-musl, or x86_64-pc-windows-msvc. Intel macOS is not supported.
  • A C and C++ compiler and git, to build BoringSSL from source: Xcode Command Line Tools on macOS, GCC or Clang on Linux, the MSVC build tools on Windows. On musl, a musl C and C++ toolchain. No CMake, NASM, Go, or libclang is needed.

The first build compiles BoringSSL, so it takes longer than a pure Rust dependency. Later builds reuse it.

Choose a browser

Session::browser(Browser::default()) sends the newest bundled Chrome with a Windows identity. A patch release can move that default to a newer capture. To keep one fingerprint, pin the browser and the platform:

use leyline::{Browser, Platform, ProtocolPolicy, Session};

# fn main() -> leyline::Result<()> {
let session = Session::builder()
    .browser(Browser::Chrome150)
    .platform(Platform::MacOS)
    .protocol(ProtocolPolicy::Race)
    .build()?;
# drop(session);
# Ok(())
# }

Reuse one session to share connections and cookies.

Also included

  • Crawling: HostLimits, ProxyPool, and Response::block().
  • Accounts: Tab for pages and forms, Device to save an account to a file.
  • Atomic downloads and Link pagination.
  • Proxies: HTTP and HTTPS, and SOCKS5 with the socks feature. The pool keeps connections per proxy, and HTTP/3 runs through SOCKS5 UDP ASSOCIATE.
  • Cookies: a jar with RFC 6265bis limits and Secure rules that you can save and restore with serde.
  • WebSocket, streaming bodies, multipart forms, and gzip, deflate, Brotli, and zstd decoding.
  • TLS trust: custom roots, certificate pins, client certificates, and a TLS version floor through TlsTrustConfig.
  • Typed errors: Error::category() names the failure, and is_retryable() says whether to try again. Retries are off until you set a RetryPolicy.
  • Debug output masks passwords, tokens, cookies, and query values.
  • The bundled BoringSSL prefixes its symbols with LEYLINE_, so it links beside OpenSSL and Cloudflare's boring.

Limits

  • A profile covers the TLS, HTTP/2, HTTP/3, and header properties its capture shows. It does not reproduce every byte a browser sends, and it runs no JavaScript.
  • Firefox on Android sends the desktop Firefox ClientHello.
  • The kernel picks the TCP option order and window scale. Leyline sets TTL, TCP_NODELAY, and, where the OS allows, MSS, don't-fragment, and a window clamp.
  • HTTP/3 sends no 0-RTT data, and it can't run through an HTTP or HTTPS proxy. Safari on iOS 17, OkHttp, and the CFNetwork profiles have no HTTP/3 data.
  • Response::audit() reports what the configured profile sends. It is not a packet capture.
  • Detection by a remote site is not a security defect. See SECURITY.md.

Documentation

Page Contents
Quick start From an empty project to a parsed response.
User guide Sessions, requests, streaming, proxies, cookies, crawling, accounts, HTTP/3, and TLS trust.
API reference Every public item, and each task mapped to its one call.
Profile reference Bundled profiles and their capture status.
Benchmarks Paired runs against wreq, reqwest, and tls-client, losses included.
Changelog Release notes and the version policy.

Security

To report a vulnerability, follow SECURITY.md.

Contributing

CONTRIBUTING.md describes the build and the checks.

License

Leyline is licensed under the MIT license. It depends on forks of BoringSSL (MIT, Apache-2.0, and BSD-3-Clause) and quiche (BSD-2-Clause), published as separate crates. NOTICE lists the forks and their licenses.

Copyright 2026 Manaforge Technologies, LLC.