mimic::http_session::HttpSession sends plain HTTP requests that look like
the Chrome a browser page came from. A browser spends a second or more on a page;
a request spends milliseconds. Once a page has earned its cookies (a login, a
passed check), an HttpSession carries them on: the measured TLS ClientHello
and HTTP/2 start, the header names, order and spelling of that Chrome on that
system, and the user agent, client hints and Accept-Language of the page or
identity. A browser or system that was not measured is refused, not
approximated.
Making one#
HttpSession::from_page(&page, egress, options)(async) copies the cookies of the page’s browser profile into the jar, readsnavigator.userAgentandnavigator.languagesfrom the page, and follows the route its traffic takes: the proxy of its browser context.egressis the exit the page leaves through;Nonetakes the context’s own proxy or goes direct. A browser-wide proxy, such as a MITM proxy the browser was launched with, is not visible to a page, so say so withegress.HttpSession::from_identity(&identity, egress, options)builds one from aStealthIdentity: its fingerprint becomes the user agent, hints and languages, and its cookies fill the jar.
Both refuse, before any request, what would contradict the measured handshake:
- A Chrome of another major version than the measured one:
HttpError::VersionMismatch { browser, measured }. For a page, both the user agent andBrowser.getVersionare checked. - A browser that is not Chrome (Edge, Opera, Firefox and the like), a Chrome
whose user agent says
HeadlessChrome, or a system that was not measured:HttpError::Impersonate(..). A headless Chrome that was given no fingerprint announces itself, so give it one or run it headed. - An identity of another egress than the one passed, or a page whose context
proxy is another than
egress:HttpError::Identity(..).
Requests#
session.get(url) sends a GET. session.request(method, url) returns an
HttpRequest; nothing is sent until .send().await, and a mistake in a setter
is reported there.
GETandHEADcarry the headers of a navigation (RequestShape::Document), every other method those of a script’s request (RequestShape::Fetch);.shape(..)chooses..header,.body,.json,.formand.timeoutcomplete the request.Methodiswreq::Method, re-exported.- The profile owns
User-Agent,Sec-CH-UA*,Accept-Language,Accept-Encoding,Cookieandpriority, and the connection ownsHost,Content-Lengthand the like. Setting one isHttpError::ReservedHeader, not a silent overwrite. Any other header replaces the profile’s value in place or is added.CONNECT,TRACEandTRACKare refused, and so is a URL with credentials in it or a scheme other than http and https. - A status other than 2xx is a reply, not an error.
HttpReplyhasstatus(),headers()(ReplyHeaders),bytes(),text(),json::<T>(),url()after redirects (andredacted_url()),redirects(),protocol(),elapsed()anddetect().
What the session does#
- Cookies. The jar is the RFC 6265
CookieJar. The session writes theCookieheader and readsSet-Cookieitself; wreq’s own store is off. A cookie for a public suffix, for another host, or against theSecure,__Host-andSameSiterules is refused as a browser refuses it.sync_cookies_back(&page)writes only what this session changed into the browser and returns how many cookies it wrote or removed; if the browser refuses, the changes are kept for the next attempt. - Redirects are followed by hand, up to 10 by default: the cookies of each
hop are stored and sent per URL,
303becomes aGET, and the headers you set are dropped when a hop leaves the origin (AcceptandContent-Typestay). A redirect is followed where it points, a private address included, so a session that fetches URLs for someone else must filter them before it asks.
Limits are HttpOptions, with defaults that are safe on the open web:
Default: "10 s"
Default: "64 MiB"
Also event_sink, trust(CertStore) for a private root (certificate
verification is never turned off), resolve(host, addr), bypass(host),
installed_chrome_major(major) and clock(..). HttpOptions::validate
refuses a value out of range before a client exists.
-
Judging replies.
reply.detect()runs the challenge detection.session.assess(&reply, &Attempt, &Rules, &identity)adds what theMitigationPolicyand theSessionPolicymake of it, as aVerdict { detection, action, transition };session.fetch(url, &attempt, &rules, &identity)sends aGETand judges it in one call (Fetched { reply, verdict });session.preflight(url, &identity, state, &rules)asks, before a request, whether the identity’s clearance is about to end so that the browser should take the URL first. They are the pieces of your own loop;StealthSessionis that loop. -
Identity.
export_identity(&base)puts the session’s cookies back into aStealthIdentityand refreshes its clearances.egress()is the exit without credentials,cookie_count()andprofile()read the state. Cloning gives another handle to the same session. -
Secrets. Cookies, credentials and proxy passwords never reach
Debug, logs, events or errors: URLs are redacted, a failure of the HTTP client is reduced to a word, and header values are not quoted.
What to know#
The first request to an origin is written for HTTP/2. The priority header
is a Chrome HTTP/2 header. The session cannot know the protocol before it
connects, so when an origin turns out to speak HTTP/1.1 only, that first
request carries an HTTP/2 priority header. The session then remembers the
origin and writes the HTTP/1.1 headers from the second request on. The tests
assert the second half (the second request has no priority header,
the_protocol_an_origin_settled_on_is_remembered in
tests/impersonate_http/basics.rs); the first request’s header is what the
module documentation states, and no test asserts it.
It does not run JavaScript, solve a challenge, open a WebSocket, or pass canvas, WebGL or behavioural checks, and it does not make automation permitted by a site’s terms. A matching handshake is one signal among many.
What was run against a real Chrome. tests/impersonate_http_chrome.rs
(#[ignore]d, headless Chrome 155 on macOS, against a loopback TLS site)
shows that the real navigator.userAgent, navigator.languages,
Browser.getVersion and Network.getAllCookies are what from_page reads,
that a host-only cookie set by a response reaches Chrome as host-only, that a
removal reaches it too, and that a page announcing itself as headless is
refused. The rest of HttpSession is tested against loopback sites
(tests/impersonate_http.rs) and a scripted browser, with no Chrome.