Repository navigation
Network and TLS
English · 中文
In-page fetch and XHR, external scripts and navigations go out through Necko — Firefox's network stack with HTTP/1.1, HTTP/2 and HTTP/3 — and TLS is done by NSS, the same library real Firefox uses. Nothing is emulated. Each Context has its own proxy, TLS profile, cookie jar and resource directory.
| Call | What happens on the network |
|---|---|
load_page(url, html) |
You supply the HTML; url becomes the document address, so relative URLs resolve against it. Classic scripts with src are read from the resource directory or the network. |
navigate(url) |
A browser-initiated top-level navigation, fetched over Necko with no Referer, Sec-Fetch-Site: none and Sec-Fetch-User: ?1. Call advance() to let it fetch and commit. |
fetch / XHR in the page |
Start them with eval(), then drive the event loop with advance(). |
with Context(proxy="http://127.0.0.1:7890") as ctx:
ctx.navigate("https://example.com/")- The proxy belongs to one context; it does not alter the host environment or other contexts.
-
Noneor""means a direct connection. - The
Contextdocstring describesproxyas an explicit HTTP(S) proxy URL; the README FAQ also lists SOCKS5. - If a context hangs, check that the proxy is reachable.
The shipped examples choose a proxy for you unless you pass --direct: --proxy, then the HTTPS_PROXY / https_proxy / HTTP_PROXY / http_proxy environment variables, then http://127.0.0.1:7890 if something listens there, otherwise a direct connection.
The TLS ClientHello — cipher suites, extensions, ALPN, key shares and their order — is produced by the real NSS library. The Necko transport can switch between Firefox 100 – 159 profiles; each reproduces that version's ClientHello, so the JA3 / JA4 fingerprint on the wire changes with it. That is useful for rotating browser identities or testing how a site reacts to different Firefox releases.
with Context(proxy="http://127.0.0.1:7890", timeout=30, tls="120") as context:
context.load_page("https://example.com/", "")
context.eval("globalThis.r = null; fetch('/api').then(x => x.json()).then(v => r = v)")
context.advance(25) # drive the event loop until it settles
print(context.eval("r"))- Pick the release per context with
tls=:"100"–"159"(also120,"120.0"or"firefox-120"). The default is 159; withtls=NoneanNECKO_FIREFOXenvironment variable is used if set.context.tlsshows the resolved version. - On Windows this sets the TLS ClientHello, the HTTP/2 settings and the HTTP/3 priorities of that context's Necko transport.
- It changes the network fingerprint only. Set a matching user agent with
configure_fingerprintwhen the page should agree — see Fingerprint and Environment. The Akamai example's--tlsoption sets both.
The profile table the Windows wheel ships (_tls_profiles.py, as described in docs/tls-profile.md) is organised as seven clusters rather than sixty unrelated hellos:
| Versions | What changes from the previous cluster |
|---|---|
| 100–118 | 17 cipher values, 6 groups, one extra key share, no GREASE ECH, no certificate compression, no SCT |
| 119–131 | GREASE ECH turns on |
| 132–134 | A hybrid group is first, two extra key shares, certificate compression turns on |
| 135–150 | SCT turns on |
| 151–153 | The cipher list loses one value (16 left) |
| 154–155 | The cipher list loses one more value (15 left) |
| 156–159 | Two finite-field groups drop out (5 groups left) |
On the macOS source build, direct HTTPS follows tls= through an arm64 libnecko_core.dylib when that library reports NSS 3.131; a proxy, a missing library or a different NSS build stays on the donor xpcshell. Pages were checked for 100 and 159, and eight cluster-boundary versions as library requests. Details and the remaining gaps: docs/macos.md, docs/tls-profile.md, docs/tls-profile-report.zh-CN.md, docs/closed-route.zh-CN.md.
The resource directory lets you serve responses yourself, up to replaying a whole captured flow with no network:
context.put_resource("https://smoke.test/app.js",
"window.loaded = document.title + ':' + location.host;")
context.load_page("https://smoke.test/",
"<!doctype html><title>smoke</title><script src='/app.js'></script>")
print(context.eval("window.loaded")) # smoke:smoke.test-
put_resource(url, body, status=200, headers=None)stores astr(text) orbytesbody under the keyurl, atomically and without any network request.headersare ordered name/value pairs. The runtime validates status, fields and storage limits. -
get_resource(url)returns an independent snapshot orNone;resource_status()returns the resource count and charged bytes;clear_resources()empties the directory. - The directory belongs to one context; iframes use their creator's directory.
- The resource-network owner record (docs/owners/RESOURCE_NETWORK.md) records the contract: a
putto an existing key replaces it after full validation; agettries the exact URL first, then the URL without its query; limits are 16 KiB per key, 10,000 resources, 8 MiB per body, 256 header fields / 64 KiB per header list and 64 MiB charged in total; a failedputkeeps the previous value.
net_log() returns the page's requests in order. Each entry has id, method, url, requestBody, status and matched (whether the resource directory matched it); a request that carried cookies also lists cookieNames — names only, never values. The DataDome example reads its interstitial POST body from requestBody.
- The context's cookie jar is shared by
document.cookie,CookieStore, HTTP requests and frames. Necko storesSet-Cookiefrom in-page responses and sends the cookies on later in-page requests; the DataDome example relies on this to fetch the homepage with its new cookie. -
export_cookies()snapshots every live cookie, HttpOnly included, with domain/path, flags,sameSite, expiry (Unix seconds;Nonefor session cookies), creation time and order. -
cookie_header(url, *, source_url=None, navigation=False, method="GET")gives you the Cookie header the runtime would select, for a request you make with another HTTP client. Recompute it for every redirect target.Set-Cookieheaders in that external response are not stored in the context; handle them yourself.
-
Originand the bodyContent-Typeon fetch / XHR /sendBeacon, andSec-Fetch-Siteacross redirects (0.2.0). - Navigation and resource timing report the DNS, connect and TLS phases measured by Necko; a reused connection reads
fetchStart, as in Firefox (0.2.1). - IP literals are resolved without a lookup, and names are cached (
network.dnsCacheExpiration60 s, 1600 entries) (0.2.1).
ruyiHttp — a Requests-style Python HTTP library built on Firefox's native networking components.
ruyiJS · GitHub · PyPI · LinuxDO · License: AGPL-3.0-only or commercial