When a request is made over HTTP/1.1, the chrome impersonate target still
emits a priority request header (e.g. priority: u=0, i). Real Chrome
only sends the priority header on HTTP/2 and HTTP/3 — it's the RFC 9218
Extensible Prioritization signal, which has no HTTP/1.1 form. So on any
HTTP/1.1 connection the impersonation is slightly less faithful than the
real browser: it carries an extra header Chrome would never send on that
connection.
This matters for sites whose server only negotiates HTTP/1.1 (so every
request is 1.1): the spurious priority header is a small but real
fingerprint divergence from genuine Chrome traffic, which is the exact
thing impersonate is meant to avoid. I verified against captured real
Chrome 149 HTTP/1.1 traffic — no priority header is present on any
request; curl_cffi adds one.
Environment
- curl_cffi: 0.15.0
- Python: 3.13 (Windows 11) — but it's protocol-dependent, not
OS-dependent
- impersonate target: chrome (resolves to chrome146)
Reproduction
import threading
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
from curl_cffi import requests
from curl_cffi.const import CurlHttpVersion
class H(BaseHTTPRequestHandler):
def do_GET(self):
hdrs = {k.lower(): v for k, v in self.headers.items()}
print("HTTP version:", self.request_version)
print("priority:", hdrs.get("priority"))
self.send_response(204); self.end_headers()
def log_message(self, *a): pass
srv = ThreadingHTTPServer(("127.0.0.1", 0), H)
port = srv.server_address[1]
threading.Thread(target=srv.serve_forever, daemon=True).start()
with requests.Session() as s:
s.get(f"http://127.0.0.1:{port}/", impersonate="chrome",
http_version=CurlHttpVersion.V1_1)
srv.shutdown()
Actual
HTTP version: HTTP/1.1
priority: u=0, i
Expected
No priority header on an HTTP/1.1 request — matching real Chrome, which
only emits priority on HTTP/2/HTTP/3.
Notes / suggested fix
- The default header set appears to be applied independently of the
negotiated protocol. priority (and arguably anything else that is
h2/h3-specific) should be omitted when the connection is HTTP/1.1, i.e.
the injected header list should be protocol-aware.
- This likely lives in the upstream curl-impersonate header definitions
rather than the Python layer, but filing here as the entry point.
- It only surfaces when the connection is actually HTTP/1.1 (server
offering only 1.1, or http_version pinned). On the default HTTP/2 path
priority is correct, so most users won't hit it.
- Current workaround for anyone who needs HTTP/1.1 fidelity: drop the
header explicitly by passing headers={"priority": None} (curl_cffi's
"disable this header" sentinel).
When a request is made over HTTP/1.1, the chrome impersonate target still
emits a priority request header (e.g. priority: u=0, i). Real Chrome
only sends the priority header on HTTP/2 and HTTP/3 — it's the RFC 9218
Extensible Prioritization signal, which has no HTTP/1.1 form. So on any
HTTP/1.1 connection the impersonation is slightly less faithful than the
real browser: it carries an extra header Chrome would never send on that
connection.
This matters for sites whose server only negotiates HTTP/1.1 (so every
request is 1.1): the spurious priority header is a small but real
fingerprint divergence from genuine Chrome traffic, which is the exact
thing impersonate is meant to avoid. I verified against captured real
Chrome 149 HTTP/1.1 traffic — no priority header is present on any
request; curl_cffi adds one.
Environment
OS-dependent
Reproduction
import threading
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
from curl_cffi import requests
from curl_cffi.const import CurlHttpVersion
class H(BaseHTTPRequestHandler):
def do_GET(self):
hdrs = {k.lower(): v for k, v in self.headers.items()}
print("HTTP version:", self.request_version)
print("priority:", hdrs.get("priority"))
self.send_response(204); self.end_headers()
def log_message(self, *a): pass
srv = ThreadingHTTPServer(("127.0.0.1", 0), H)
port = srv.server_address[1]
threading.Thread(target=srv.serve_forever, daemon=True).start()
with requests.Session() as s:
s.get(f"http://127.0.0.1:{port}/", impersonate="chrome",
http_version=CurlHttpVersion.V1_1)
srv.shutdown()
Actual
HTTP version: HTTP/1.1
priority: u=0, i
Expected
No priority header on an HTTP/1.1 request — matching real Chrome, which
only emits priority on HTTP/2/HTTP/3.
Notes / suggested fix
negotiated protocol. priority (and arguably anything else that is
h2/h3-specific) should be omitted when the connection is HTTP/1.1, i.e.
the injected header list should be protocol-aware.
rather than the Python layer, but filing here as the entry point.
offering only 1.1, or http_version pinned). On the default HTTP/2 path
priority is correct, so most users won't hit it.
header explicitly by passing headers={"priority": None} (curl_cffi's
"disable this header" sentinel).