Skip to content

Commit d553162

Browse files
vdusekclaude
andauthored
fix: Prevent _prepare_request_call from mutating caller's headers dict (#746)
## Summary `_prepare_request_call` in `HttpClientBase` was mutating the caller's `headers` dict in place — the `if not headers: headers = {}` guard only allocated a new dict when the caller passed `None`/empty, so a non-empty caller-owned dict was mutated directly. Users of the documented custom `HttpClient` / `HttpClientAsync` extension points that reuse a shared headers dict across `call()` invocations would see stale `Content-Type` / `Content-Encoding` headers leak into subsequent requests (e.g. a bodyless `GET` carrying `Content-Encoding: gzip` from a prior `POST`). Fix: copy up-front with `headers = dict(headers) if headers else {}`. Added a regression test covering the aliased-dict case. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 6c3b84d commit d553162

2 files changed

Lines changed: 19 additions & 2 deletions

File tree

src/apify_client/_http_clients/_base.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -204,8 +204,7 @@ def _prepare_request_call(
204204
if json is not None and data is not None:
205205
raise ValueError('Cannot pass both "json" and "data" parameters at the same time!')
206206

207-
if not headers:
208-
headers = {}
207+
headers = dict(headers) if headers else {}
209208

210209
# Dump JSON data to string so it can be gzipped.
211210
if json is not None:

tests/unit/test_http_clients.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -380,6 +380,24 @@ def test_prepare_request_call_with_params() -> None:
380380
assert params == {'limit': 10, 'flag': 'true'}
381381

382382

383+
def test_prepare_request_call_does_not_mutate_caller_headers() -> None:
384+
"""Test _prepare_request_call does not mutate the caller's headers dict.
385+
386+
A caller that reuses a shared headers dict across calls must not see stale
387+
`Content-Type`/`Content-Encoding` headers leak in from a prior JSON/body call.
388+
"""
389+
client = _ConcreteHttpClient()
390+
391+
caller_headers = {'x-trace-id': 'abc-123'}
392+
original = dict(caller_headers)
393+
394+
client._prepare_request_call(headers=caller_headers, json={'x': 1})
395+
assert caller_headers == original
396+
397+
client._prepare_request_call(headers=caller_headers, data='payload')
398+
assert caller_headers == original
399+
400+
383401
def test_build_url_with_params_none() -> None:
384402
"""Test _build_url_with_params with None params."""
385403
client = _ConcreteHttpClient()

0 commit comments

Comments
 (0)