|
I'm building an async client based on I thought a way around this might be using event hooks, and at first they seemed to work well. Though in at least one high volume test, I ran into a bunch of 401s. The docs aren't clear exactly when the request hook is processed, so I'm not sure what to make of it. Are request hooks a good way to handle this? Any other thoughts appreciated! |
Replies: 3 comments
|
A request hook is the right public extension point for signing, but it runs a little earlier than “socket write.” HTTPX invokes it immediately before A practical pattern is: async def sign(request: httpx.Request) -> None:
# Recompute both values from the final request bytes on every attempt.
request.headers["X-Timestamp"] = current_timestamp()
request.headers["Authorization"] = make_signature(request)
timeout = httpx.Timeout(30.0, pool=5.0)
limits = httpx.Limits(max_connections=50)
client = httpx.AsyncClient(
timeout=timeout,
limits=limits,
event_hooks={"request": [sign]},
)The short pool timeout is important: it bounds how stale a hook-generated signature can become while queued. On Allow margin for connection/TLS setup and request-body writes too. If the protocol truly requires signing only after a connection has been acquired, HTTPX currently has no public event hook at that exact point; that would require a lower-level transport/httpcore integration. For a five-minute validity window, bounded pool waiting plus application backpressure should normally avoid that complexity. |
|
A request hook runs too early to guarantee a fresh signing timestamp at high concurrency. In both the synchronous and asynchronous client paths, HTTPX invokes the request hooks in A custom The robust options are generally to:
Be careful with automatic retries: retrying the same already-signed |
|
@ryux1 Thank you for the very thorough answer. Much appreciated! |
A request hook runs too early to guarantee a fresh signing timestamp at high concurrency.
In both the synchronous and asynchronous client paths, HTTPX invokes the request hooks in
_send_handling_redirects()and only afterwards calls_send_single_request()._send_single_request()then enters the transport, and the default transport/httpcore layer is where connection-pool acquisition happens. A request can therefore sit waiting for an available pooled connection after your hook has signed it. The hook also runs once per redirect request, but that does not move it past pool acquisition.A custom
Authflow has the same fundamental timing issue: it prepares/yields the request before the transp…