Skip to content

v2.0.0

Latest

Choose a tag to compare

@deedy5 deedy5 released this 26 Aug 15:21
· 1 commit to main since this release

What's Changed

  • feat!: v2.0.0 — native primp crate (workspace restructure) by @deedy5 in #160

Summary

Major release v2.0.0maindev367 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.400.23.42, hashbrown 0.160.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.12.0.0
  • primp-python 1.3.12.0.0
  • MSRV 1.89, panic = "abort" / lto = "fat" / codegen-units = 1 unchanged

Python

  • pyo3 / pyo3-async-runtimes 0.280.29
  • html2text 0.160.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_sizeGOAWAY 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_initPyRuntimeError
  • 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 (RangeGuardidentity), 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"

Full Changelog: v1.3.1...v2.0.0