Skip to content

# feat!: v2.0.0 — native primp crate (workspace restructure) - #160

Merged
deedy5 merged 28 commits into
mainfrom
dev
Aug 26, 2026
Merged

deedy5 merged 28 commits into
mainfrom
dev

Conversation

@deedy5

@deedy5 deedy5 commented Aug 26, 2026

Copy link
Copy Markdown
Owner

Summary

Major release v2.0.0 — main → dev — 367 files +32k / -73k.

Former wrapper around forked reqwest / hyper / hyper-util / hyper-rustls / tokio-rustls replaced by a standalone crates/primp built directly on hyper, hyper-util, h2 and rustls. The Python extension is rebound to the new native core.


Breaking Changes / Migration

Workspace

  • Removed crates: primp-reqwest, primp-hyper, primp-hyper-util, primp-hyper-rustls, primp-tokio-rustls
  • crates/primp is now the product; Cargo.toml workspace.members trimmed to primp, primp-rustls/rustls, primp-h2, primp-python
  • [profile.bench] added (lto = false, codegen-units = 256)

Dependencies

  • primp/Cargo.toml no longer re-exports reqwest/*; now declares hyper@1.8, hyper-util@0.1, tower@0.5 + tower-http@0.7 (follow-redirect, decompression-*), http-body / http-body-util, tokio, foldhash@0.2, rustls@0.23 (impersonate, aws-lc-rs, ml-dsa), hickory-resolver@0.26 optional, quinn / h3 optional
  • primp-rustls 0.23.40 → 0.23.42, hashbrown 0.16 → 0.17
  • primp-h2 tracks upstream 0.4.17 / 0.4.18

Features

  • default = [cookies, gzip, brotli, zstd, deflate, query, form, json, multipart, stream, system-proxy, charset]
  • Removed impersonate / default-tls / http2 / rustls-no-provider (always on via rustls / h2)
  • Fine-grained charset / hickory-dns / http3 remain

Versions

  • primp 1.3.1 → 2.0.0
  • primp-python 1.3.1 → 2.0.0
  • MSRV 1.89, panic = "abort" / lto = "fat" / codegen-units = 1 unchanged

Python

  • pyo3 / pyo3-async-runtimes 0.28 → 0.29
  • html2text 0.16 → 0.17
  • Added jiter@0.16, log; crate-type = cdylib doc disabled

Features

Core

  • Native src/{lib, config, error, connect, tls, tls_bridge, impersonation} + async_impl/{client, body, request, response, multipart, negotiate, range_guard, h1_client, h2_client, h3_client} + dns/{cache, lru, hosts, resolve, plain, doh, dot, gai, hickory} + imp/{chrome, edge, opera, firefox, safari} + retry / redirect / proxy / cookie

Pool / ALPN

  • H1 keep-alive: busy / idle_since, PlaceholderGuard, origin-form rewrite, lazy evict_stale (90s / 256)
  • H2 / H3: AtomicU64 last_used, active_streams refcount, owner-only abort + detach_driver_until_idle, watch sentinel dedup, busy-gate, single conn per key, REFUSED retry ≤32, backpressure 1 KiB gate
  • Other: proxy / redirect / TLS bridge (bounded 128 drain, close_notify), DNS LRU 30s/5s + DynResolver::http_resolve + ChainedResolver

Impersonation

  • Browser profiles chrome_144-152, edge_144-151, opera_126-135, firefox_140,146-151, safari_18.5/26/26.3/26.4 + random
  • TLS / JA4 / Akamai / header-order capture-verified
  • emulator_extension_order per-version (GREASE 0x6a6a / 0x0a0a, ALPS 0x44cd, compress_certificate brotli-vs-zlib, ML-DSA 150+ / 134+)
  • SelectedUnoffered cipher fallback

DNS / Proxy / Cookie

  • Global OnceLock HostsMap (Box<str> + Arc<[IpAddr]>), non-blocking threaded load (3.6s → 0.2s)
  • Custom-port / empty-set / DNSTimeout capping; SOCKS primp-owned (bracket-strip, ATYP=4, 255 B caps, 8 KiB CONNECT, with_timeout)
  • First-match Proxy-Authorization per-hop re-attach
  • PSL (psl@2.1) rejection, one-shot cookie merge, referer=False honored

Fixes / Performance

  • Pool: Drop no-abort while active_streams > 0 → fixes broken pipe under 100 concurrent temp clients; off-runtime GC mem::forget leak, primp_body_error_to_pyerr is_timeout before is_decode (pool_concurrent_drop)
  • Hosts: compaction + threaded load; foldhash::fast::RandomState for H1/H2/H3/DNS HashMap + h2 IndexMap (~2×: 17ms vs 107ms hash, 615ms → 278ms pool 256-entry)
  • Proxy: socks5-https via h2 + timeout / tls-buffer fixes; referer / proxy attrs + cipher fallback; StatusError.status_code/url
  • Forks: sync to upstream rustls@0.23.42 / h2@0.4.17, header-abuse (>4× max_header_list_size → GOAWAY ENHANCE_YOUR_CALM vs 431+RST) + flow-control hardening; clippy clean, manylinux_2_28 for ppc64le (GCC 7.5), Windows TimeoutError for 127.0.0.1:1

Python

  • Rebound primp-python to native primp@2.0 (Client / AsyncClient / RequestBuilder / Response)
  • Real coroutines via future_into_coroutine (14 sites, create_task-checked), ACTIVE_TASKS + SHUTTING_DOWN + call_soon_threadsafe interpreter-exit safety, py.detach + block_on, get_runtime PyOnceLock::get_or_try_init → PyRuntimeError
  • aread cache _drained, exhausted Arc<AtomicBool> iterators (TOCTOU stream_exhausted → quiet EOF)
  • chunk_size / timeout validation, text_markdown (html2text), DNSTimeoutError PyOnceLock

Tests / Docs

  • New suites: h1_fallback, h2_pool_saturation, range_accept_encoding (RangeGuard → identity), cookie_psl, proxy_review, redirect, retry (tower×pool 3×33=99), tls_close_notify, pool_concurrent_drop, socks_ipv6, window_increment, test_pool_concurrent_drop.py
  • Python: test_client / request_params / streaming / exceptions
  • primp.pyi / examples updated; README primp = "2"

deedy5 added 28 commits June 27, 2026 01:54
- client_builder.rs: always set builder.referer() so referer=False disables Referer on redirects
- lib.rs/async/client.rs: remove #[pyo3(get,set)] on proxy field so custom get_proxy/set_proxy updates shared ProxyMatcher
- hs.rs: fallback for SelectedUnofferedCipherSuite when server selects advertised-but-unsupported legacy CBC/RSA suites, preserving ja4/ja4_ro/akamai
… tls buffer

h2 connector allows non-http proxy schemes, total timeout surfaces
as socks connect error, tls bridge drains plaintext buffer.
Ensure Store is empty after clear_recv_buffer test so the
unstable Drop assertion does not panic.
StatusError now has status_code/url properties (alias for args)
so docs example e.status_code works; stub updated. Other
issues #151, #154, #155, #156 already fixed or not reproducible.
Parse /etc/hosts once via OnceLock<Arc<HostsMap>> with
with_capacity and Box<str>+Arc<[IpAddr]> shared across clients.
Disables hickory's per-client Hosts::from_system which rehashes
6→13→27→400 MB on 500k  entries. Hosts now always on.
Pool::Drop previously aborted the driver even when active_streams>0,

causing broken pipe DecodeError with many concurrent temp clients.

Gate on active_streams==0; if >0 detach via detach_driver_until_idle

until idle, or leak handle+Arc when no runtime (Python GC off-runtime)

to avoid spawn panic. Map body errors via primp_body_error_to_pyerr

checking is_timeout() before is_decode() with full source chain,

so mid-body timeout becomes TimeoutError not DecodeError.

Add pool_concurrent_drop tests (100 concurrent temp clients)
Replace std SipHash13 with foldhash::fast::RandomState via
foldhash::HashMap/HashMapExt for H1/H2/H3 pools, DNS LRU/cache/hosts
and client dns_overrides, and IndexMap<_,_,RandomState> for h2 store.
2× faster hash_one (17ms vs 107ms) and ~2.2× faster pool/LRU ops
(256-entry H1/H2, 1024-entry DNS), IndexMap ~2×.

Benchmark: size 256 std 615ms -> foldhash 278ms, LRU 4.77ms -> 3.52ms,
client_creation unchanged (noise).
Parsing /etc/hosts with 470k entries blocks the
current_thread runtime for 3.6s (1.2s counting + 1.9s inserts),
exceeding the 200ms hanging-DNS deadline. hanging_dns_with_short_connect_timeout and explicit_dns_timeout then surface as
connect timeout instead of DNS. Load hosts in a detached thread
via HOSTS_INIT_TRIGGER and serve the first lookup from inner
resolver; also estimate HashMap capacity from file size to avoid
the counting pass (1.6s vs 3.6s).

Fixes hanging_dns_with_short_connect_timeout_is_dns_error and
explicit_dns_timeout_is_dns_error (were 3.4s, now 0.2s/0.1s).
aws-lc-sys 0.44.0 bcm.c fails on manylinux2014 (GCC 4.8.5) for
powerpc64le-unknown-linux-gnu (exit 1, no diagnostic). manylinux
2_28 provides GCC 7.5 and succeeds for that arch. Keep other
linux targets on auto, only ppc64le on 2_28.
…irects leak checks

On Windows 127.0.0.1:1 with timeout=2 raises TimeoutError (operation timed
out) instead of ConnectError (connection refused on Linux). Accept both
to keep leak regression tests green on Windows.
@deedy5
deedy5 merged commit 1d746a2 into main Aug 26, 2026
38 checks passed
@deedy5
deedy5 deleted the dev branch August 26, 2026 15:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant