rex-tls
rex-tls is a Python HTTP client backed by a native Rust TLS and HTTP core. It
provides a requests-style API with transport profiles for Android Chrome and
OkHttp, persistent sessions, cookies, proxies, streaming I/O, asynchronous
requests, and bounded connection pools.
Installation
python -m pip install rex-tls
Python 3.9 or newer is required. Supported platforms are:
- Windows x86-64
- Linux x86-64 with manylinux 2.28 or newer
Transport profiles
| Profile | HTTP/1.1 | HTTP/2 | HTTP/3 |
|---|---|---|---|
chrome_android_149 |
Yes | Yes | Yes |
chrome_android_150 |
Yes | Yes | Yes |
okhttp_4.12 |
Yes | Yes | No |
okhttp_5.4 |
Yes | Yes | No |
chrome_android and chrome_android_latest are aliases for
chrome_android_150. okhttp and okhttp_latest are aliases for okhttp_5.4.
Use the versioned names when reproducibility matters.
Quick start
import rex_tls
response = rex_tls.get(
"https://example.com/",
profile="chrome_android_150",
params={"page": 1},
timeout=20,
)
response.raise_for_status()
print(response.status_code)
print(response.http_version)
print(response.text)
JSON and form requests use familiar keyword arguments:
response = rex_tls.post(
"https://api.example.com/items",
profile="okhttp_5.4",
json={"name": "example", "enabled": True},
)
response = rex_tls.post(
"https://api.example.com/form",
profile="okhttp_4.12",
data={"name": "example"},
)
Persistent sessions
A Session keeps cookies, connections, and TLS session state across requests.
from rex_tls import Session
with Session(
profile="chrome_android_150",
timeout=20,
) as session:
session.headers.update({"accept": "application/json"})
session.cookies.set("locale", "en-US")
first = session.get("https://example.com/api/profile")
second = session.get("https://example.com/api/settings")
print(second.connection_reused)
print(session.cookies.get_dict())
Per-request cookies are supported and do not modify the session jar:
response = session.get(
"https://example.com/api/items",
params={"page": 2},
cookies={"request-only": "value"},
headers={"accept": "application/json"},
)
Cookies
Session.cookies provides the commonly used requests-style CookieJar methods:
session.cookies.update({"theme": "dark"})
session.cookies.set(
"api-token",
"value",
domain="api.example.com",
path="/v1",
secure=True,
)
print(session.cookies.get("theme"))
print(session.cookies.get_dict())
print(session.cookies.items())
session.cookies.clear(domain="api.example.com", path="/v1", name="api-token")
Supported helpers include set(), set_cookie(), get(), get_dict(),
update(), clear(), keys(), values(), items(), list_domains(), and
list_paths(). Response cookies are validated against domain, path, Secure,
expiry, IP-address, and public-suffix rules before they enter the jar.
Proxies
Use proxy= as a single proxy for all supported routes:
proxy = "http://username:password@proxy.example:8080"
with Session("okhttp_5.4", proxy=proxy) as session:
response = session.get("https://example.com/")
Or use a requests-style mapping:
proxies = {
"http": "http://proxy.example:8080",
"https": "http://proxy.example:8080",
"all": "http://fallback.example:8080",
"no_proxy": ".internal.example,localhost,127.0.0.1",
}
with Session("chrome_android_149", proxies=proxies) as session:
response = session.get("https://example.com/")
Session.proxies is mutable. A per-request proxies= mapping overrides the
session mapping. Set trust_env=True to read HTTP_PROXY, HTTPS_PROXY,
ALL_PROXY, and NO_PROXY.
Proxy URLs must use http://. HTTPS destinations use HTTP CONNECT. Basic proxy
credentials are supported; percent-encode reserved characters in usernames and
passwords. HTTPS proxies, SOCKS, PAC, NTLM/Digest, and MASQUE are not supported.
HTTP version selection
The negotiated protocol is available as response.http_version.
Require an actual HTTP/2 connection with http2=True:
with Session("chrome_android_150", http2=True) as session:
response = session.get("https://example.com/")
assert response.http_version == "HTTP/2"
http2=True preserves the profile ALPN list and rejects the response if the
server does not negotiate HTTP/2. It supports HTTPS only and is mutually
exclusive with HTTP/3 modes.
Chrome profiles support the following http3 values:
| Value | Behavior |
|---|---|
off |
Disable HTTP/3. This is the default. |
auto |
Enable the Chrome HTTP/3 path with HTTP/2 or HTTP/1.1 fallback. |
only |
Require HTTP/3 and fail if it cannot be used. |
with Session("chrome_android_150", http3="auto") as session:
response = session.get("https://example.com/")
with Session("chrome_android_149", http3="only") as session:
response = session.get("https://example.com/")
assert response.http_version == "HTTP/3"
HTTP/3 requires HTTPS and a versioned Chrome profile. OkHttp profiles do not
enable HTTP/3. Conventional HTTP proxies cannot carry the QUIC path, so
http3="only" is rejected when such a proxy is selected.
Request headers
Headers may be supplied as a mapping or as ordered (name, value) pairs. The
native core applies the selected profile's protocol-specific ordering and casing
rules before transmission.
If both session and request headers are empty, the profile's default request headers are used. A non-empty caller header set is sent without adding unrelated profile defaults. Protocol-required fields and fields derived from cookies or the request body may still be generated.
HTTP/1.1 preserves the profile's observable field-name casing. HTTP/2 and HTTP/3
field names are lowercase as required by those protocols. Hop-by-hop HTTP/1.1
fields such as Connection are rejected for HTTP/2 and HTTP/3 instead of being
silently removed.
Streaming downloads
Use a context manager so the connection is released when the body reaches EOF or the response is closed:
from rex_tls import Session
with Session("chrome_android_150") as session:
with session.get("https://example.com/large.bin", stream=True) as response:
response.raise_for_status()
with open("large.bin", "wb") as output:
for chunk in response.iter_content(64 * 1024):
output.write(chunk)
iter_lines(), raw.read(), and raw.readinto() are also available. Set
decode_content=False to receive the compressed response body without automatic
content decoding.
Streaming uploads and multipart files
File objects and byte iterables are uploaded incrementally:
with Session("okhttp_5.4") as session:
with open("large.bin", "rb") as source:
response = session.post(
"https://example.com/upload",
content=source,
)
with open("image.png", "rb") as source:
response = session.post(
"https://example.com/form",
data={"title": "example"},
files={"file": ("image.png", source, "image/png")},
)
Seekable upload sources can be replayed across 307/308 redirects. A one-shot
source raises UnrewindableBodyError if a redirect requires replay.
Async API
AsyncSession provides an asyncio interface without blocking the event loop:
import asyncio
from rex_tls import AsyncSession
async def main() -> None:
async with AsyncSession(
"okhttp_5.4",
max_concurrency=8,
http2=True,
) as session:
urls = [f"https://example.com/items/{item}" for item in range(10)]
responses = await asyncio.gather(*(session.get(url) for url in urls))
print([response.status_code for response in responses])
asyncio.run(main())
Async streaming uses aiter_content() and aiter_lines():
from rex_tls import AsyncSession
async def stream_events(session: AsyncSession) -> None:
response = await session.get("https://example.com/events", stream=True)
async with response:
async for line in response.aiter_lines():
print(line)
Bounded session pools
SessionPool and AsyncSessionPool create multiple independent native sessions
with a fixed concurrency limit.
from concurrent.futures import ThreadPoolExecutor
from rex_tls import SessionPool
urls = [f"https://example.com/items/{item}" for item in range(20)]
with SessionPool(
"okhttp_4.12",
max_connections=8,
session_mode="shared",
) as pool:
pool.headers["accept"] = "application/json"
with ThreadPoolExecutor(max_workers=16) as executor:
responses = list(executor.map(pool.get, urls))
Pool session modes:
shared: members share headers, proxy configuration, and one thread-safe CookieJar. Connections remain independent.isolated: each member owns independent headers, proxy configuration, cookies, connections, and TLS state.
Lease a specific member when multiple requests must keep the same isolated state:
from rex_tls import SessionPool
with SessionPool(
"okhttp_5.4",
max_connections=4,
session_mode="isolated",
) as pool:
with pool.acquire(timeout=1) as account:
account.headers["authorization"] = "Bearer example-token"
account.cookies.set("account", "one")
account.get("https://example.com/step-1")
account.get("https://example.com/step-2")
Use pool_timeout= to limit how long a request waits for a pool member. The
ordinary timeout= argument continues to control the network request.
Response API
Common response attributes and methods include:
status_code,reason,url, andhttp_versionheaders, includingget_all()and the orderedrawviewcontent,text, andjson()okandraise_for_status()historyelapsed_secondslocal_addressandremote_addressconnection_reusedandtls_session_reusediter_content(),iter_lines(),close(), andaclose()
Requests compatibility
The API directly supports common requests-style arguments and attributes:
- HTTP method helpers and
Session params,headers,data,json,files, andcookiestimeout,verify,proxy, andproxiesSession.headers,Session.proxies,Session.cookies, andtrust_envstream=True,iter_content(),iter_lines(), and raw reads- per-request
allow_redirects .content,.text,.json(), and.raise_for_status()
It is not a drop-in replacement for every requests extension point. auth,
hooks, adapters, mount, and custom RequestsCookieJar policy objects are not
implemented.
TLS verification
Certificate verification is enabled by default and uses the installed certifi
CA bundle.
# Default CA bundle
Session("chrome_android_150", verify=True)
# Custom CA file
Session("chrome_android_150", verify="/path/to/private-ca.pem")
# Disable verification explicitly
Session("chrome_android_150", verify=False)
Disabling verification removes server identity protection and should only be used in controlled test environments.
Error handling
import rex_tls
try:
response = rex_tls.get(
"https://example.com/",
profile="chrome_android_150",
timeout=10,
)
response.raise_for_status()
except rex_tls.HTTPError as exc:
print(f"HTTP error: {exc}")
except rex_tls.RequestError as exc:
print(f"Request failed: {exc}")
except rex_tls.MobileTLSError as exc:
print(f"Native client error: {exc}")
except (TypeError, ValueError) as exc:
print(f"Invalid configuration: {exc}")
Additional exceptions include CookieConflictError, ContentDecodingError,
InvalidHeader, InvalidURL, SessionClosedError, StreamClosedError,
StreamConsumedError, and UnrewindableBodyError.
Runtime information
import rex_tls
print(rex_tls.__version__)
print(rex_tls.profiles())
print(dict(rex_tls.native_versions()))
print(rex_tls.profile_info("chrome_android_150"))
License
MIT
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distributions
Built Distributions
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file rex_tls-2.2.1-cp39-abi3-win_amd64.whl.
File metadata
- Download URL: rex_tls-2.2.1-cp39-abi3-win_amd64.whl
- Upload date:
- Size: 2.7 MB
- Tags: CPython 3.9+, Windows x86-64
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/7.0.0 CPython/3.14.0
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
5a0c7f068cd1341de7235115273d22a0808647145a5738747a6edb22480587f3
|
|
| MD5 |
6e130c00252eea71865cdcba42e81b59
|
|
| BLAKE2b-256 |
4cde9341ebb9068b5a708a93c44fb010f8d0b10c6f53edf56d001b91e4007f18
|
File details
Details for the file rex_tls-2.2.1-cp39-abi3-manylinux_2_28_x86_64.whl.
File metadata
- Download URL: rex_tls-2.2.1-cp39-abi3-manylinux_2_28_x86_64.whl
- Upload date:
- Size: 2.6 MB
- Tags: CPython 3.9+, manylinux: glibc 2.28+ x86-64
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/7.0.0 CPython/3.14.0
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
b5fc011ba968a4f17283a18d6fa46f4d760b14cb97dc03af5c07189f9cab389f
|
|
| MD5 |
3b5ee1746a3f337b8b6fcf5140308277
|
|
| BLAKE2b-256 |
dbc724d138643140584f1709ac55bf564a475c23dcd4ec00bcdc982aea3a9ad9
|