horaa-tls Documentation
A high-performance Python HTTP client built to completely eliminate OpenSSL TLS fingerprint leakage. It executes in-process on an isolated Go networking core, providing exact JA3/JA4 browser emulation, 0-RTT HTTP/3 QUIC protocol racing, and Zero-GIL execution.
Standard Python HTTP libraries (requests, httpx) fail modern anti-bot systems (Cloudflare, Akamai, Datadome) at the TLS handshake layer because Python's OpenSSL lacks browser GREASE extensions and uses fixed cipher orders. horaa-tls replaces the entire network stack with authentic browser Client Hellos.
from horaa_tls import Session, ClientProfile # Exact Chrome 133 TLS ClientHello (BoringSSL stack) with Session(profile=ClientProfile.CHROME_133) as session: response = session.get("https://tls.peet.ws/api/all") data = response.json() print("Status:", response.status_code) print("JA3 Hash:", data["tls"]["ja3_hash"]) print("JA4 Hash:", data["tls"]["ja4"])
Installation
horaa-tls is published to the official Python Package Index (PyPI) and includes compiled C-shared binaries for 64-bit systems.
# Install or upgrade via pip pip install --upgrade horaa-tls # Or install using uv (Astral) uv add horaa-tls
Supported Platforms
| Platform | Architecture | Driver Binary | Compatibility |
|---|---|---|---|
| Windows | AMD64 (x86_64) | tls-client-windows-64.dll |
Python 3.10+ |
| Linux | x86_64 / arm64 | tls-client-linux-amd64.so |
Python 3.10+ |
| macOS | Apple Silicon & Intel | tls-client-darwin-arm64.dylib |
Python 3.10+ |
30-Second Quickstart
Use horaa-tls just like requests, but with authentic browser fingerprints.
from horaa_tls import Session, ClientProfile # 1. Choose your browser target profile with Session(profile=ClientProfile.CHROME_133) as session: # 2. Make standard GET with query parameters res = session.get("https://httpbin.org/get", params={"page": 1}) print("Status:", res.status_code) print("URL:", res.url) # 3. Make POST with JSON post_res = session.post( "https://httpbin.org/post", json={"user": "alice", "action": "login"}, timeout=15 ) print("Post Echo:", post_res.json()["json"])
Ctypes Native Bridge & Zero-GIL
In standard Python, socket I/O and OpenSSL handshakes execute under Python's Global Interpreter Lock (GIL). horaa-tls eliminates this bottleneck:
- Memory-Isolated C Bridge: Python transfers pointers directly to compiled Go machine code (
backend.ctypes_go). - Zero GIL Locking: During socket connection, TLS handshakes, ALPN negotiation, and compression, Python's GIL is completely released. Go handles the connection in its own lightweight goroutines.
- Automatic Memory Cleanup: Responses returned from Go are decoded into Python objects, and the underlying C-allocated memory is freed immediately.
Client Profiles
The ClientProfile enum defines pre-tested browser configurations that match genuine browser cryptographic stacks:
| Profile Enum | String Value | Browser Engine |
|---|---|---|
ClientProfile.CHROME_133 |
"chrome_133" |
Modern Chromium with Post-Quantum Kyber & HTTP/3 |
ClientProfile.CHROME_120 |
"chrome_120" |
Stable Chromium baseline |
ClientProfile.CHROME_110 |
"chrome_110" |
Legacy Chromium 110 |
ClientProfile.CHROME_103 |
"chrome_103" |
Legacy Chromium 103 |
ClientProfile.FIREFOX_133 |
"firefox_133" |
Current Firefox Gecko TLS stack |
ClientProfile.FIREFOX_123 |
"firefox_123" |
Stable Firefox baseline |
ClientProfile.FIREFOX_117 |
"firefox_117" |
Extended legacy Firefox |
ClientProfile.SAFARI_16_0 |
"safari_16_0" |
macOS Safari WebKit stack |
ClientProfile.SAFARI_IOS_17_0 |
"safari_ios_17_0" |
Mobile iOS 17 Safari WebKit |
ClientProfile.OPERA_90 |
"opera_90" |
Opera Desktop emulation |
HTTP/3 QUIC Protocol Racing
Real web browsers do not send simple sequential TCP requests. They initiate protocol racing, attempting UDP QUIC (HTTP/3) concurrently with TCP TLS 1.3 to achieve 0-RTT connects.
from horaa_tls import Session, ClientProfile, Protocol with Session( profile=ClientProfile.CHROME_133, with_protocol_racing=True ) as session: res = session.get("https://cloudflare.com") print("Negotiated Protocol:", res.protocol) # Outputs Protocol.HTTP_3
Standard HTTP/HTTPS proxies only support TCP. If you run with_protocol_racing=True through an HTTP proxy, UDP packets would bypass the proxy and leak your real machine IP! horaa-tls strictly requires SOCKS5 proxies when protocol racing is active, raising BackendError if an HTTP proxy is configured.
Interactive Handshake & Fingerprint Tester
Select different profiles below to see how horaa-tls shapes the low-level TLS Client Hello and JA3/JA4 digests:
Proxies & Pool Rotation
horaa-tls provides native proxy pool management built directly into the Session constructor:
from horaa_tls import Session, ClientProfile # 'failover' rotates proxy if connection errors occur # 'request' rotates proxy on every HTTP call session = Session( profile=ClientProfile.CHROME_133, proxies=[ "socks5://user:pass@proxy1:1080", "socks5://user:pass@proxy2:1080" ], proxy_mode="failover" ) res = session.get("https://api.ipify.org?format=json") print("IP:", res.json()["ip"])
Concurrency Best Practices
A Session is not thread-safe because cookies and proxy-rotator state are mutated per request. To achieve maximum multi-threaded performance, allocate one Session per thread.
from concurrent.futures import ThreadPoolExecutor from horaa_tls import Session, ClientProfile def worker(urls): # Create an isolated Session per worker thread with Session(profile=ClientProfile.CHROME_133) as session: for url in urls: res = session.get(url) print(f"[{res.status_code}] {res.url}") urls = [f"https://httpbin.org/get?page={i}" for i in range(50)] chunks = [urls[i:i + 5] for i in range(0, len(urls), 5)] with ThreadPoolExecutor(max_workers=10) as executor: executor.map(worker, chunks)
Resiliency Middlewares
horaa-tls automatically registers RetryMiddleware, ProxyRotatorMiddleware, and RedirectMiddleware on every session.
from horaa_tls import Session from horaa_tls.middleware import RetryMiddleware session = Session() # Customize retry parameters custom_retry = RetryMiddleware( max_retries=5, backoff_factor=1.0, retry_on_status=(429, 500, 502, 503, 504), jitter=True ) session.middleware_pipeline.add(custom_retry)
Writing Custom Middlewares
Subclass BaseMiddleware to inspect or mutate payloads before requests go to the Go engine, or inspect responses returning from it:
from horaa_tls import Session, ClientProfile from horaa_tls.middleware.base import BaseMiddleware class AuthHeaderMiddleware(BaseMiddleware): def __init__(self, token: str): self.token = token def before_request(self, session, payload: dict) -> None: headers = payload.setdefault("headers", {}) headers["Authorization"] = f"Bearer {self.token}" session = Session(profile=ClientProfile.CHROME_133) session.middleware_pipeline.add(AuthHeaderMiddleware("sk_test_12345")) res = session.get("https://httpbin.org/headers") print(res.json()["headers"])
API Reference: horaa_tls.Session
The primary connection and request manager in horaa-tls.
Constructor
Session( profile: str | ClientProfile = ClientProfile.CHROME_120, proxy: str | None = None, proxies: list[str] | None = None, proxy_mode: str = "failover", header_order: list[str] | None = None, pseudo_header_order: list[str] | None = None, insecure_skip_verify: bool = False, random_tls_extension_order: bool = False, use_mitm_when_active: bool = False, force_http1: bool = False, with_protocol_racing: bool = False, disable_http3: bool = False, cookies: dict[str, str] | None = None, timeout_seconds: float = 30, )
Request Methods
session.get(url, **kwargs) -> Response: Sends an HTTP GET.session.post(url, data=None, json=None, **kwargs) -> Response: Sends an HTTP POST.session.put(url, **kwargs) -> Response: Sends an HTTP PUT.session.delete(url, **kwargs) -> Response: Sends an HTTP DELETE.session.patch(url, **kwargs) -> Response: Sends an HTTP PATCH.session.head(url, **kwargs) -> Response: Sends an HTTP HEAD.session.options(url, **kwargs) -> Response: Sends an HTTP OPTIONS.await session.request_async(method, url, **kwargs) -> Response: Asynchronous coroutine.session.close() -> None: Closes session and frees Go connection resources.
API Reference: horaa_tls.Response
The response object returned from all session request invocations:
| Attribute / Method | Type | Description |
|---|---|---|
response.status_code |
int |
HTTP response status integer (e.g. 200, 404). |
response.protocol |
Protocol |
Negotiated protocol enum: Protocol.HTTP_3, Protocol.HTTP_2, or Protocol.HTTP_1_1. |
response.text |
str |
Decoded unicode response body string. |
response.content |
bytes |
Raw binary payload bytes. |
response.json() |
dict | list |
Parses response body into native Python primitives. |
response.headers |
CaseInsensitiveDict |
Case-insensitive dictionary of response headers. |
response.cookies |
RequestsCookieJar |
Cookies set by the target server. |
response.elapsed |
timedelta |
Total round-trip time for the request. |
Exception Hierarchy
All exceptions inherit from horaa_tls.exceptions.HoraaTLSError:
HoraaTLSError ├── BackendError │ └── BackendMissingError ├── TimeoutError ├── ProxyError ├── RedirectLoopError └── TLSFingerprintError