fix: seam mutable cookie api, byte-safe noble bodies, drop header baking, coerce noble session timeout (v0.1.4)
TLSSession.set_cookie/get_cookies/clear_cookies crashed with AttributeError on both backends (they reached into self.session.cookie_jar, which curl_cffi and noble_tls sessions don't have); both backends now route the mutable cookie api through their own requests-style session.cookies store. Noble.raw_request now requests is_byte_response=True and decodes the resulting base64 data-URI body, since noble_tls's default text response silently corrupts any binary payload (image/zip/pdf) via lossy UTF-8 decoding on the Go side. CurlCffi/Noble.create_session no longer bake session-default headers into the underlying client; baking caused clear_headers()/get_headers() to lie about what's actually still on the wire (a credential-leak divergence from the aiohttp base, which never bakes). Headers flow through aioweb's per-request merge only, matching the base's documented contract. Noble.create_session now applies the same max(1, ceil()) timeout coercion raw_request already had (extracted into a shared _noble_timeout_seconds helper) — without it, a sub-second/float session-default timeout made every request fail Go-side JSON unmarshal. README corrected: dropped the 'every aioweb feature behaves identically' overclaim re: cookies, documented the binary-body handling and the no-header-baking rationale, bumped install pins to v0.1.4. Signed-off-by: disqualifier <dev@disqualifier.me>
This commit is contained in:
@@ -22,17 +22,17 @@ you want; importing the package never fails because an extra is missing.
|
||||
`requirements.txt` (pick the extra you need):
|
||||
|
||||
```
|
||||
aioweb_tls[curl] @ git+ssh://git@git.rethinkstudios.io/rethink-public/aioweb_tls.git@v0.1.3
|
||||
aioweb_tls[noble] @ git+ssh://git@git.rethinkstudios.io/rethink-public/aioweb_tls.git@v0.1.3
|
||||
aioweb_tls[all] @ git+ssh://git@git.rethinkstudios.io/rethink-public/aioweb_tls.git@v0.1.3
|
||||
aioweb_tls[curl] @ git+ssh://git@git.rethinkstudios.io/rethink-public/aioweb_tls.git@v0.1.4
|
||||
aioweb_tls[noble] @ git+ssh://git@git.rethinkstudios.io/rethink-public/aioweb_tls.git@v0.1.4
|
||||
aioweb_tls[all] @ git+ssh://git@git.rethinkstudios.io/rethink-public/aioweb_tls.git@v0.1.4
|
||||
```
|
||||
|
||||
Direct:
|
||||
|
||||
```bash
|
||||
pip install "aioweb_tls[curl] @ git+ssh://git@git.rethinkstudios.io/rethink-public/aioweb_tls.git@v0.1.3"
|
||||
pip install "aioweb_tls[noble] @ git+ssh://git@git.rethinkstudios.io/rethink-public/aioweb_tls.git@v0.1.3"
|
||||
pip install "aioweb_tls[all] @ git+ssh://git@git.rethinkstudios.io/rethink-public/aioweb_tls.git@v0.1.3"
|
||||
pip install "aioweb_tls[curl] @ git+ssh://git@git.rethinkstudios.io/rethink-public/aioweb_tls.git@v0.1.4"
|
||||
pip install "aioweb_tls[noble] @ git+ssh://git@git.rethinkstudios.io/rethink-public/aioweb_tls.git@v0.1.4"
|
||||
pip install "aioweb_tls[all] @ git+ssh://git@git.rethinkstudios.io/rethink-public/aioweb_tls.git@v0.1.4"
|
||||
```
|
||||
|
||||
- `[curl]` → curl_cffi backend · `[noble]` → noble_tls backend · `[all]` → both.
|
||||
@@ -44,7 +44,7 @@ pip install "aioweb_tls[all] @ git+ssh://git@git.rethinkstudios.io/rethink-publi
|
||||
Constructing a backend whose client isn't installed raises that `RuntimeError` at
|
||||
construction, never at import.
|
||||
|
||||
Drop the `@v0.1.3` suffix from the line above to install the latest unpinned.
|
||||
Drop the `@v0.1.4` suffix from the line above to install the latest unpinned.
|
||||
|
||||
## curl_cffi backend
|
||||
|
||||
@@ -80,6 +80,11 @@ async with TLSSession(backend=Noble(client="chrome_133")) as s:
|
||||
- noble_tls downloads a Go shared library on first use. `await s.setup()` fetches it
|
||||
once at startup; if you skip it, the first request fetches it lazily. The fetch is
|
||||
guarded by a lock, so even concurrent first requests download it exactly once.
|
||||
- Binary bodies (images, zips, PDFs, protobuf) round-trip as true bytes: noble_tls
|
||||
returns response bodies as a UTF-8 JSON string by default, which mangles non-UTF-8
|
||||
bytes (`U+FFFD` replacement, wrong length) even on a 200 response — the Noble
|
||||
backend always requests `is_byte_response=True` and decodes the resulting
|
||||
base64 data-URI back into raw bytes, so `resp.content` is never lossy.
|
||||
|
||||
## Writing your own backend (the `TLSBackend` protocol)
|
||||
|
||||
@@ -99,6 +104,9 @@ for the authoritative contract):
|
||||
| `raw_request` | **required** | `async (session, method, url, **kwargs) -> aioweb.Response` | send one request; adapt the client's response into an `aioweb.Response` |
|
||||
| `is_closed` | **required** | `(session) -> bool` | whether the session is closed |
|
||||
| `cookies_for_url` | optional | `(session, url) -> dict` | cookies for `preview()`; defaults to `{}` |
|
||||
| `set_cookie` | optional | `(session, name, value, domain=None, path="/") -> None` | backs `TLSSession.set_cookie()`; raises `NotImplementedError` if absent |
|
||||
| `get_cookies` | optional | `(session) -> dict` | backs `TLSSession.get_cookies()`; raises `NotImplementedError` if absent |
|
||||
| `clear_cookies` | optional | `(session) -> None` | backs `TLSSession.clear_cookies()`; raises `NotImplementedError` if absent |
|
||||
| `setup` | optional | `async () -> None` | one-time prep (e.g. fetch a native lib); idempotent |
|
||||
| `close` | optional | `async (session) -> None` | close the session; defaults to `await session.close()` |
|
||||
|
||||
@@ -156,8 +164,9 @@ async with TLSSession(backend=GoTLSBackend("http://localhost:8080")) as s:
|
||||
## Inherited features work unchanged
|
||||
|
||||
aioweb's overwrite/domain/ephemeral/proxy/retry/preview logic operates on plain dicts
|
||||
and never touches the HTTP backend — only the seams do. Every aioweb feature behaves
|
||||
identically on any backend:
|
||||
and never touches the HTTP backend — only the seams do. Header overwrites, domain
|
||||
rewriting, ephemeral headers, proxies, retries, and previews behave identically on
|
||||
any backend:
|
||||
|
||||
```python
|
||||
async with TLSSession(backend=CurlCffi(impersonate="chrome")) as s:
|
||||
@@ -167,6 +176,19 @@ async with TLSSession(backend=CurlCffi(impersonate="chrome")) as s:
|
||||
print(s.preview("GET", "https://internal.local/x").as_curl()) # reflects all of the above
|
||||
```
|
||||
|
||||
Session-default headers are never baked into the underlying client (neither
|
||||
`CurlCffi` nor `Noble` passes `headers=` to their client's constructor) — they flow
|
||||
through aioweb's own per-request `_default_headers` merge instead. That keeps
|
||||
`update_headers()` / `clear_headers()` accurate for both backends: what
|
||||
`get_headers()` and `preview()` report is what actually goes out on the wire, with
|
||||
no stale, already-baked value resurfacing after a clear.
|
||||
|
||||
The mutable cookie API — `set_cookie()` / `get_cookies()` / `clear_cookies()` — is
|
||||
also backend-aware: `CurlCffi` and `Noble` each route it through their own client's
|
||||
cookie store (both expose a `requests`-style `session.cookies` with `set()` /
|
||||
`items()` / `clear()`), so these calls work the same way they do on the base
|
||||
`aioweb.ExtendedSession`, not just `_cookies_for_url()` (used by `preview()`).
|
||||
|
||||
## Honesty note
|
||||
|
||||
TLS fingerprinting changes one layer — the TLS/HTTP fingerprint. It does **not** by
|
||||
|
||||
Reference in New Issue
Block a user