Closed didip closed 3 years ago
Hey everyone, do we have some progress on this? I see the same error after delayed calls e.g:
import time
from httpx import Client
client = Client()
client.get("/")
time.sleep(300)
client.get("/")
.....
h11._util.RemoteProtocolError: can't handle event type ConnectionClosed when role=SERVER and state=SEND_RESPONSE
Does anyone know some workarounds for this? I'm just wondering why I can't see such error in requests
module ..
Hey, will changes be introduced in version above 0.7.8
, right?
@vyahello Yes, this fix along with some others will be released soon, probably as 0.7.9. :) If you need them now you can install from master.
thanks!
Hi guys.
I am getting a similar exception running my FastAPI app like
gunicorn -k uvicorn.workers.UvicornH11Worker
Traceback (most recent call last):
File "/usr/local/lib/python3.7/asyncio/events.py", line 88, in _run
self._context.run(self._callback, *self._args)
File "/usr/local/lib/python3.7/site-packages/uvicorn/protocols/http/h11_impl.py", line 338, in timeout_keep_alive_handler
self.conn.send(event)
File "/usr/local/lib/python3.7/site-packages/h11/_connection.py", line 464, in send
data_list = self.send_with_data_passthrough(event)
File "/usr/local/lib/python3.7/site-packages/h11/_connection.py", line 490, in send_with_data_passthrough
self._process_event(self.our_role, event)
File "/usr/local/lib/python3.7/site-packages/h11/_connection.py", line 238, in _process_event
self._cstate.process_event(role, type(event), server_switch_event)
File "/usr/local/lib/python3.7/site-packages/h11/_state.py", line 238, in process_event
self._fire_event_triggered_transitions(role, event_type)
File "/usr/local/lib/python3.7/site-packages/h11/_state.py", line 253, in _fire_event_triggered_transitions
.format(event_type.__name__, role, self.states[role]))
h11._util.LocalProtocolError: can't handle event type ConnectionClosed when role=SERVER and state=SEND_BODY
Traceback (most recent call last):
File "/usr/local/lib/python3.7/site-packages/uvicorn/protocols/http/h11_impl.py", line 383, in run_asgi
result = await app(self.scope, self.receive, self.send)
File "/usr/local/lib/python3.7/site-packages/uvicorn/middleware/proxy_headers.py", line 45, in __call__
return await self.app(scope, receive, send)
File "/usr/local/lib/python3.7/site-packages/fastapi/applications.py", line 139, in __call__
await super().__call__(scope, receive, send)
File "/usr/local/lib/python3.7/site-packages/starlette/applications.py", line 134, in __call__
await self.error_middleware(scope, receive, send)
File "/usr/local/lib/python3.7/site-packages/starlette/middleware/errors.py", line 178, in __call__
raise exc from None
File "/usr/local/lib/python3.7/site-packages/starlette/middleware/errors.py", line 156, in __call__
await self.app(scope, receive, _send)
File "/usr/local/lib/python3.7/site-packages/starlette/middleware/base.py", line 26, in __call__
await response(scope, receive, send)
File "/usr/local/lib/python3.7/site-packages/starlette/responses.py", line 200, in __call__
await send({"type": "http.response.body", "body": b"", "more_body": False})
File "/usr/local/lib/python3.7/site-packages/starlette/middleware/errors.py", line 153, in _send
await send(message)
File "/usr/local/lib/python3.7/site-packages/uvicorn/protocols/http/h11_impl.py", line 477, in send
output = self.conn.send(event)
File "/usr/local/lib/python3.7/site-packages/h11/_connection.py", line 464, in send
data_list = self.send_with_data_passthrough(event)
File "/usr/local/lib/python3.7/site-packages/h11/_connection.py", line 480, in send_with_data_passthrough
"Can't send data when our state is ERROR")
h11._util.LocalProtocolError: Can't send data when our state is ERROR
python 3.7
fastapi==0.42.0
gunicorn==19.9.0
h11==0.8.1
uvicorn==0.10.0
I get it intermittently in a somewhat low but significant rate on my API. Like 8 errors in a 1000 requests sample.
Do you think is somehow related?
Do you think is somehow related?
Possibly the same situation, but that error is coming from Uvicorn’s usage of h11, not HTTPX’s.
Possibly the same situation, but that error is coming from Uvicorn’s usage of h11, not HTTPX’s.
Edit: So should I report it on uvicorn's or h11's repo?
Hey, still getting this exception:
httpcore._exceptions.ProtocolError: can't handle event type ConnectionClosed when role=SERVER and state=SEND_RESPONSE
raise to_exc(exc) from None
File "/usr/local/lib/python3.8/site-packages/httpcore/_exceptions.py", line 12, in map_exceptions
self.gen.throw(type, value, traceback)
File "/usr/local/lib/python3.8/contextlib.py", line 131, in __exit__
event = self.h11_state.next_event()
File "/usr/local/lib/python3.8/site-packages/httpcore/_async/http11.py", line 142, in _receive_event
event = await self._receive_event(timeout)
File "/usr/local/lib/python3.8/site-packages/httpcore/_async/http11.py", line 115, in _receive_response
) = await self._receive_response(timeout)
File "/usr/local/lib/python3.8/site-packages/httpcore/_async/http11.py", line 62, in request
return await self.connection.request(method, url, headers, stream, timeout)
File "/usr/local/lib/python3.8/site-packages/httpcore/_async/connection.py", line 78, in request
response = await connection.request(
File "/usr/local/lib/python3.8/site-packages/httpcore/_async/connection_pool.py", line 152, in request
) = await transport.request(
File "/usr/local/lib/python3.8/site-packages/httpx/_client.py", line 1264, in send_single_request
response = await self.send_single_request(request, timeout)
File "/usr/local/lib/python3.8/site-packages/httpx/_client.py", line 1232, in send_handling_auth
response = await self.send_handling_auth(
File "/usr/local/lib/python3.8/site-packages/httpx/_client.py", line 1195, in send_handling_redirects
response = await self.send_handling_redirects(
File "/usr/local/lib/python3.8/site-packages/httpx/_client.py", line 1168, in send
response = await self.send(
File "/usr/local/lib/python3.8/site-packages/httpx/_client.py", line 1147, in request
return await self.request(
File "/usr/local/lib/python3.8/site-packages/httpx/_client.py", line 1305, in get
return await client.get(url, timeout=timeout)
File …
Just found out that it's uvicorn
causing this exception, probably some bug. Switching service requested via httpx
to hypercorn
solved the issue for me.
@uriva - Can you provide more information on how we can reproduce that error?
I don't have a reproduction yet. One idea is that this happens when spending too much time doing sync work, while processing an async post request. Does that make any sense?
My case is:
uvicorn
async with ServiceBClient() as client: ...
, ServiceBClient
inherits httpx.AsyncClient
)ProtocolError
is raised in service A with very high probabilityIf I change 1. to: FastAPI service A is running under uvicorn
and FastAPI service B under hypercorn
-- my problem is solved
One of the traces to clarify a bit.
│Jul 21 07:21:44 ⋮ ⋮: service_a ERROR: Exception in ASGI application │
│Jul 21 07:21:44 ⋮ ⋮: service_a Traceback (most recent call last): │
│Jul 21 07:21:44 ⋮ ⋮: service_a File ".../lib/python3.8/site-packages/uvicorn/protocols/http/httptools_impl.py", line 386, in run_asgi │
│Jul 21 07:21:44 ⋮ ⋮: service_a result = await app(self.scope, self.receive, self.send) │
│Jul 21 07:21:44 ⋮ ⋮: service_a File ".../lib/python3.8/site-packages/uvicorn/middleware/proxy_headers.py", line 45, in __call__ │
│Jul 21 07:21:44 ⋮ ⋮: service_a return await self.app(scope, receive, send) │
│Jul 21 07:21:44 ⋮ ⋮: service_a File ".../lib/python3.8/site-packages/fastapi/applications.py", line 181, in __call__ │
│Jul 21 07:21:44 ⋮ ⋮: service_a await super().__call__(scope, receive, send) │
│Jul 21 07:21:44 ⋮ ⋮: service_a File ".../lib/python3.8/site-packages/starlette/applications.py", line 102, in __call__ │
│Jul 21 07:21:44 ⋮ ⋮: service_a await self.middleware_stack(scope, receive, send) │
│Jul 21 07:21:44 ⋮ ⋮: service_a File ".../lib/python3.8/site-packages/starlette/middleware/errors.py", line 181, in __call__ │
│Jul 21 07:21:44 ⋮ ⋮: service_a raise exc from None │
│Jul 21 07:21:44 ⋮ ⋮: service_a File ".../lib/python3.8/site-packages/starlette/middleware/errors.py", line 159, in __call__ │
│Jul 21 07:21:44 ⋮ ⋮: service_a await self.app(scope, receive, _send) │
│Jul 21 07:21:44 ⋮ ⋮: service_a File ".../lib/python3.8/site-packages/sentry_sdk/integrations/asgi.py", line 106, in _run_asgi3 │
│Jul 21 07:21:44 ⋮ ⋮: service_a return await self._run_app(scope, lambda: self.app(scope, receive, send)) │
│Jul 21 07:21:44 ⋮ ⋮: service_a File ".../lib/python3.8/site-packages/sentry_sdk/integrations/asgi.py", line 143, in _run_app │
│Jul 21 07:21:44 ⋮ ⋮: service_a raise exc from None │
│Jul 21 07:21:44 ⋮ ⋮: service_a File ".../lib/python3.8/site-packages/sentry_sdk/integrations/asgi.py", line 140, in _run_app │
│Jul 21 07:21:44 ⋮ ⋮: service_a return await callback() │
│Jul 21 07:21:44 ⋮ ⋮: service_a File ".../lib/python3.8/site-packages/starlette/middleware/base.py", line 25, in __call__ │
│Jul 21 07:21:44 ⋮ ⋮: service_a response = await self.dispatch_func(request, self.call_next) │
│Jul 21 07:21:44 ⋮ ⋮: service_a File "./service_a/lib/middleware/filter_session_cookie.py", line 32, in web_front_auth_cookie │
│Jul 21 07:21:44 ⋮ ⋮: service_a response: Response = await call_next(request) │
│Jul 21 07:21:44 ⋮ ⋮: service_a File ".../lib/python3.8/site-packages/starlette/middleware/base.py", line 45, in call_next │
│Jul 21 07:21:44 ⋮ ⋮: service_a task.result() │
│Jul 21 07:21:44 ⋮ ⋮: service_a File ".../lib/python3.8/site-packages/starlette/middleware/base.py", line 38, in coro │
│Jul 21 07:21:44 ⋮ ⋮: service_a await self.app(scope, receive, send) │
│Jul 21 07:21:44 ⋮ ⋮: service_a File ".../lib/python3.8/site-packages/starlette/middleware/base.py", line 25, in __call__ │
│Jul 21 07:21:44 ⋮ ⋮: service_a response = await self.dispatch_func(request, self.call_next) │
│Jul 21 07:21:44 ⋮ ⋮: service_a File "./lib/middleware/correlation_id.py", line 22, in add_request_id │
│Jul 21 07:21:44 ⋮ ⋮: service_a return await call_next(request) │
│Jul 21 07:21:44 ⋮ ⋮: service_a File ".../lib/python3.8/site-packages/starlette/middleware/base.py", line 45, in call_next │
│Jul 21 07:21:44 ⋮ ⋮: service_a task.result() │
│Jul 21 07:21:44 ⋮ ⋮: service_a File ".../lib/python3.8/site-packages/starlette/middleware/base.py", line 38, in coro │
│Jul 21 07:21:44 ⋮ ⋮: service_a await self.app(scope, receive, send) │
│Jul 21 07:21:44 ⋮ ⋮: service_a File ".../lib/python3.8/site-packages/starlette/middleware/base.py", line 25, in __call__ │
│Jul 21 07:21:44 ⋮ ⋮: service_a response = await self.dispatch_func(request, self.call_next) │
│Jul 21 07:21:44 ⋮ ⋮: service_a File ".../lib/python3.8/site-packages/starlette_context/middleware.py", line 47, in dispatch │
│Jul 21 07:21:44 ⋮ ⋮: service_a response = await call_next(request) │
│Jul 21 07:21:44 ⋮ ⋮: service_a File ".../lib/python3.8/site-packages/starlette/middleware/base.py", line 45, in call_next │
│Jul 21 07:21:44 ⋮ ⋮: service_a task.result() │
│Jul 21 07:21:44 ⋮ ⋮: service_a File ".../lib/python3.8/site-packages/starlette/middleware/base.py", line 38, in coro │
│Jul 21 07:21:44 ⋮ ⋮: service_a await self.app(scope, receive, send) │
│Jul 21 07:21:44 ⋮ ⋮: service_a File ".../lib/python3.8/site-packages/starlette/exceptions.py", line 82, in __call__ │
│Jul 21 07:21:44 ⋮ ⋮: service_a raise exc from None │
│Jul 21 07:21:44 ⋮ ⋮: service_a File ".../lib/python3.8/site-packages/starlette/exceptions.py", line 71, in __call__ │
│Jul 21 07:21:44 ⋮ ⋮: service_a await self.app(scope, receive, sender) │
│Jul 21 07:21:44 ⋮ ⋮: service_a File ".../lib/python3.8/site-packages/starlette/routing.py", line 550, in __call__ │
│Jul 21 07:21:44 ⋮ ⋮: service_a await route.handle(scope, receive, send) │
│Jul 21 07:21:44 ⋮ ⋮: service_a File ".../lib/python3.8/site-packages/starlette/routing.py", line 227, in handle │
│Jul 21 07:21:44 ⋮ ⋮: service_a await self.app(scope, receive, send) │
│Jul 21 07:21:44 ⋮ ⋮: service_a File ".../lib/python3.8/site-packages/starlette/routing.py", line 41, in app │
│Jul 21 07:21:44 ⋮ ⋮: service_a response = await func(request) │
│Jul 21 07:21:44 ⋮ ⋮: service_a File ".../lib/python3.8/site-packages/fastapi/routing.py", line 196, in app │
│Jul 21 07:21:44 ⋮ ⋮: service_a raw_response = await run_endpoint_function( │
│Jul 21 07:21:44 ⋮ ⋮: service_a File ".../lib/python3.8/site-packages/fastapi/routing.py", line 147, in run_endpoint_function │
│Jul 21 07:21:44 ⋮ ⋮: service_a return await dependant.call(**values) │
│Jul 21 07:21:44 ⋮ ⋮: service_a File "./service_a/routers/auth/oauth.py", line 235, in apple_callback │
│Jul 21 07:21:44 ⋮ ⋮: service_a return await callback(request, OAuthProviders.apple, state, code, error, │
│Jul 21 07:21:44 ⋮ ⋮: service_a File "./service_a/routers/auth/oauth.py", line 151, in callback │
│Jul 21 07:21:44 ⋮ ⋮: service_a user = await client.oauth_login_by_uid( │
│Jul 21 07:21:44 ⋮ ⋮: service_a File "./service_a/lib/clients/user.py", line 170, in oauth_login_by_uid │
│Jul 21 07:21:44 ⋮ ⋮: service_a resp: 'httpx.Response' = await self.put( │
│Jul 21 07:21:44 ⋮ ⋮: service_a File ".../lib/python3.8/site-packages/httpx/_client.py", line 1402, in put │
│Jul 21 07:21:44 ⋮ ⋮: service_a return await self.request( │
│Jul 21 07:21:44 ⋮ ⋮: service_a File ".../lib/python3.8/site-packages/httpx/_client.py", line 1147, in request │
│Jul 21 07:21:44 ⋮ ⋮: service_a response = await self.send( │
│Jul 21 07:21:44 ⋮ ⋮: service_a File ".../lib/python3.8/site-packages/httpx/_client.py", line 1168, in send │
│Jul 21 07:21:44 ⋮ ⋮: service_a response = await self.send_handling_redirects( │
│Jul 21 07:21:44 ⋮ ⋮: service_a File ".../lib/python3.8/site-packages/httpx/_client.py", line 1195, in send_handling_redirects │
│Jul 21 07:21:44 ⋮ ⋮: service_a response = await self.send_handling_auth( │
│Jul 21 07:21:44 ⋮ ⋮: service_a File ".../lib/python3.8/site-packages/httpx/_client.py", line 1232, in send_handling_auth │
│Jul 21 07:21:44 ⋮ ⋮: service_a response = await self.send_single_request(request, timeout) ├
│Jul 21 07:21:44 ⋮ ⋮: service_a File ".../lib/python3.8/site-packages/httpx/_client.py", line 1264, in send_single_request │
│Jul 21 07:21:44 ⋮ ⋮: service_a ) = await transport.request( ├
│Jul 21 07:21:44 ⋮ ⋮: service_a File ".../lib/python3.8/site-packages/httpcore/_async/connection_pool.py", line 152, in request │
│Jul 21 07:21:44 ⋮ ⋮: service_a response = await connection.request( │
│Jul 21 07:21:44 ⋮ ⋮: service_a File ".../lib/python3.8/site-packages/httpcore/_async/connection.py", line 78, in request │
│Jul 21 07:21:44 ⋮ ⋮: service_a return await self.connection.request(method, url, headers, stream, timeout) │
│Jul 21 07:21:44 ⋮ ⋮: service_a File ".../lib/python3.8/site-packages/httpcore/_async/http11.py", line 62, in request │
│Jul 21 07:21:44 ⋮ ⋮: service_a ) = await self._receive_response(timeout) │
│Jul 21 07:21:44 ⋮ ⋮: service_a File ".../lib/python3.8/site-packages/httpcore/_async/http11.py", line 115, in _receive_response │
│Jul 21 07:21:44 ⋮ ⋮: service_a event = await self._receive_event(timeout) │
│Jul 21 07:21:44 ⋮ ⋮: service_a File ".../lib/python3.8/site-packages/httpcore/_async/http11.py", line 142, in _receive_event │
│Jul 21 07:21:44 ⋮ ⋮: service_a event = self.h11_state.next_event() │
│Jul 21 07:21:44 ⋮ ⋮: service_a File "/data/marty-pyenv/services/versions/3.8.1/lib/python3.8/contextlib.py", line 131, in __exit__ │
│Jul 21 07:21:44 ⋮ ⋮: service_a self.gen.throw(type, value, traceback) │
│Jul 21 07:21:44 ⋮ ⋮: service_a File ".../lib/python3.8/site-packages/httpcore/_exceptions.py", line 12, in map_exceptions │
│Jul 21 07:21:44 ⋮ ⋮: service_a raise to_exc(exc) from None │
│Jul 21 07:21:44 ⋮ ⋮: service_a httpcore._exceptions.ProtocolError: can't handle event type ConnectionClosed when role=SERVER and state=SEND_RESPONSE
sometimes the top of stack differs a little bit:
│Jul 21 15:37:27 ⋮ ⋮: service_a event = await self._receive_event(timeout) │
│Jul 21 15:37:27 ⋮ ⋮: service_a File ".../lib/python3.8/site-packages/httpcore/_async/http11.py", line 145, in _receive_e │
│Jul 21 15:37:27 ⋮ ⋮: service_a data = await self.socket.read(self.READ_NUM_BYTES, timeout) │
│Jul 21 15:37:27 ⋮ ⋮: service_a File ".../lib/python3.8/site-packages/httpcore/_backends/asyncio.py", line 134, in read │
│Jul 21 15:37:27 ⋮ ⋮: service_a return await asyncio.wait_for( │
│Jul 21 15:37:27 ⋮ ⋮: service_a File "/data/marty-pyenv/services/versions/3.8.1/lib/python3.8/contextlib.py", line 131, in __exit__ │
│Jul 21 15:37:27 ⋮ ⋮: service_a self.gen.throw(type, value, traceback) │
│Jul 21 15:37:27 ⋮ ⋮: service_a File ".../lib/python3.8/site-packages/httpcore/_exceptions.py", line 12, in map_exception │
│Jul 21 15:37:27 ⋮ ⋮: service_a raise to_exc(exc) from None │
│Jul 21 15:37:27 ⋮ ⋮: service_a httpcore._exceptions.ReadTimeout ```
@tomchristie should we reopen to make it easier for others to find this issue?
At this point I'd be speculating this is a bug/edge case in Uvicorn, rather than HTTPX behaving badly. Uvicorn might be doing something that causes it to close the connection too early while it is sending data to the client.
What would really help now I think is a detailed and complete reproduction setup with complete code listings, eg based on the description above. If we can strip FastAPI out of the equation (eg use a raw ASGI app), then even better.
We can reopen to mark this as ongoing discussion but once again it's not clear to me yet whether this qualifies as an HTTPX bug.
I can definitely tell one thing: a testcase with 2 simple services running locally doesn't reproduce this case. There is something else that don't get right now: maybe a network delay, a proxy to the outer world (yes, there is one)
Oh! Forgot to mention: today I made tests with --timeout-keep-alive 30
.
Well... there is a difference:
ReadTimeout
, never ProtocolError
Hope this helps...
@dmig Could you share the full code you used to try and replicate locally?
Sure!
service_a.py
import logging
import httpx
from fastapi import FastAPI, Response
app = FastAPI()
logging.getLogger().__name__ = __name__
@app.get('/', response_class=Response, status_code=204)
async def main():
async with httpx.AsyncClient(trust_env=False, base_url='http://localhost:8001', timeout=10) as client:
logging.info('sending request to app2/ep1')
resp = await client.get('/ep1')
logging.info('ep1 resp: %d %s', resp.status_code, resp.reason_phrase)
logging.info('sending request to app2/ep2')
resp = await client.get('/ep2')
logging.info('ep2 resp: %d %s', resp.status_code, resp.reason_phrase)
service_b.py
import logging
import httpx
from fastapi import FastAPI, Response
app = FastAPI(default_response_class=Response)
logging.getLogger().__name__ = __name__
@app.get('/ep1', status_code=204)
async def ep1():
logging.info('sending request to httpbin')
async with httpx.AsyncClient(trust_env=False, timeout=10) as client:
resp = await client.get('https://httpbin.org/delay/6')
logging.info('resp: %d %s', resp.status_code, resp.reason_phrase)
@app.get('/ep2', status_code=204)
async def ep2():
logging.info('sending request to httpbin')
async with httpx.AsyncClient(timeout=10) as client:
resp = await client.get('https://httpbin.org/delay/6')
logging.info('resp: %d %s', resp.status_code, resp.reason_phrase)
Basically this is the same code as one that causes ProtocolError
/ReadTimeout
, but it works as it should.
Should we move the discussion to uvicorn
issues?
One more thing that might give a clue: test servers where the error is reproducible are running ancient ubuntu 14.04 with 3.13.0-170-generic kernel
My exception didn't happen within a uvicorn context.
I think this will have been resolved by encode/httpcore#112 - once that's released I think you'll find that you're getting a much more clear ReadError
raised here, and see that the connection has been closed.
Might also be that asyncio
isn't doing a good job of detecting when the connection has been closed by the remote end.
I'm currently able to reproduce a similar (maybe same) problem locally on 0.13.3, it's always ReadTimeout
when trying to send second request data:
... skip ...
INFO: uvicorn.access: 127.0.0.1:53976 - "POST /ep1/ HTTP/1.1" 201 Created
DEBUG [2020-07-30 17:23:57] httpx._client - HTTP Request: POST http://127.0.0.1:9093/ep1/ "HTTP/1.1 201 Created"
DEBUG: httpx._client: HTTP Request: POST http://127.0.0.1:9093/ep1/ "HTTP/1.1 201 Created"
TRACE [2020-07-30 17:23:57] httpcore._async.http11 - receive_event=Data(<840 bytes>)
TRACE: httpcore._async.http11: receive_event=Data(<840 bytes>)
TRACE [2020-07-30 17:23:57] httpcore._async.http11 - receive_event=EndOfMessage(headers=[])
TRACE: httpcore._async.http11: receive_event=EndOfMessage(headers=[])
TRACE [2020-07-30 17:23:57] httpcore._async.http11 - response_closed our_state=DONE their_state=DONE
TRACE: httpcore._async.http11: response_closed our_state=DONE their_state=DONE
TRACE [2020-07-30 17:23:57] httpcore._async.connection_pool - get_connection_from_pool=(b'http', b'127.0.0.1', 9093)
TRACE: httpcore._async.connection_pool: get_connection_from_pool=(b'http', b'127.0.0.1', 9093)
TRACE [2020-07-30 17:23:57] httpcore._async.connection_pool - reuse connection=<httpcore._async.connection.AsyncHTTPConnection object at 0x7f5e85807730>
TRACE: httpcore._async.connection_pool: reuse connection=<httpcore._async.connection.AsyncHTTPConnection object at 0x7f5e85807730>
TRACE [2020-07-30 17:23:57] httpcore._async.connection - connection.request method=b'PATCH' url=(b'http', b'127.0.0.1', 9093, b'/ep2/') headers=[(b'host', b'127.0.0.1:9093'), (b'user-agent', b'python-httpx/0.13.3'), (b'accept', b'*/*'), (b'accept-encoding', b'gzip, deflate'), (b'connection', b'keep-alive'), (b'x-correlation-id', b'e5065f60-1c2e-4820-9e58-268ac492b8aa'), (b'content-length', b'127'), (b'content-type', b'application/json')]
TRACE: httpcore._async.connection: connection.request method=b'PATCH' url=(b'http', b'127.0.0.1', 9093, b'/ep2/') headers=[(b'host', b'127.0.0.1:9093'), (b'user-agent', b'python-httpx/0.13.3'), (b'accept', b'*/*'), (b'accept-encoding', b'gzip, deflate'), (b'connection', b'keep-alive'), (b'x-correlation-id', b'e5065f60-1c2e-4820-9e58-268ac492b8aa'), (b'content-length', b'127'), (b'content-type', b'application/json')]
TRACE [2020-07-30 17:23:57] httpcore._async.http11 - send_request method=b'PATCH' url=(b'http', b'127.0.0.1', 9093, b'/ep2/') headers=[(b'host', b'127.0.0.1:9093'), (b'user-agent', b'python-httpx/0.13.3'), (b'accept', b'*/*'), (b'accept-encoding', b'gzip, deflate'), (b'connection', b'keep-alive'), (b'x-correlation-id', b'e5065f60-1c2e-4820-9e58-268ac492b8aa'), (b'content-length', b'127'), (b'content-type', b'application/json')]
TRACE: httpcore._async.http11: send_request method=b'PATCH' url=(b'http', b'127.0.0.1', 9093, b'/ep2/') headers=[(b'host', b'127.0.0.1:9093'), (b'user-agent', b'python-httpx/0.13.3'), (b'accept', b'*/*'), (b'accept-encoding', b'gzip, deflate'), (b'connection', b'keep-alive'), (b'x-correlation-id', b'e5065f60-1c2e-4820-9e58-268ac492b8aa'), (b'content-length', b'127'), (b'content-type', b'application/json')]
TRACE [2020-07-30 17:23:57] httpcore._async.http11 - send_data=Data(<127 bytes>)
TRACE: httpcore._async.http11: send_data=Data(<127 bytes>)
TRACE [2020-07-30 17:24:02] httpcore._async.connection_pool - remove from pool connection=<httpcore._async.connection.AsyncHTTPConnection object at 0x7f5e85807730>
TRACE: httpcore._async.connection_pool: remove from pool connection=<httpcore._async.connection.AsyncHTTPConnection object at 0x7f5e85807730>
INFO: uvicorn.access: 127.0.0.1:33056 - "POST /api/v1/endpoint/ HTTP/1.1" 500 Internal Server Error
ERROR: uvicorn.error: Exception in ASGI application
... skip loooong stack trace ...
File "./service_a/lib/mylib.py", line 239, in register
await client.call_ep2(**params)
File "./service_a/lib/clients/service_b.py", line 44, in call_ep2
resp: 'httpx.Response' = await self.patch('/ep2/', json=kwargs)
File ".../lib/python3.8/site-packages/httpx/_client.py", line 1430, in patch
return await self.request(
File ".../lib/python3.8/site-packages/httpx/_client.py", line 1147, in request
response = await self.send(
File ".../lib/python3.8/site-packages/httpx/_client.py", line 1168, in send
response = await self.send_handling_redirects(
File ".../lib/python3.8/site-packages/httpx/_client.py", line 1195, in send_handling_redirects
response = await self.send_handling_auth(
File ".../lib/python3.8/site-packages/httpx/_client.py", line 1232, in send_handling_auth
response = await self.send_single_request(request, timeout)
File ".../lib/python3.8/site-packages/httpx/_client.py", line 1264, in send_single_request
) = await transport.request(
File ".../lib/python3.8/site-packages/httpcore/_async/connection_pool.py", line 152, in request
response = await connection.request(
File ".../lib/python3.8/site-packages/httpcore/_async/connection.py", line 78, in request
return await self.connection.request(method, url, headers, stream, timeout)
File ".../lib/python3.8/site-packages/httpcore/_async/http11.py", line 62, in request
) = await self._receive_response(timeout)
File ".../lib/python3.8/site-packages/httpcore/_async/http11.py", line 115, in _receive_response
event = await self._receive_event(timeout)
File ".../lib/python3.8/site-packages/httpcore/_async/http11.py", line 145, in _receive_event
data = await self.socket.read(self.READ_NUM_BYTES, timeout)
File ".../lib/python3.8/site-packages/httpcore/_backends/asyncio.py", line 134, in read
return await asyncio.wait_for(
File "/usr/lib/python3.8/contextlib.py", line 131, in __exit__
self.gen.throw(type, value, traceback)
File ".../lib/python3.8/site-packages/httpcore/_exceptions.py", line 12, in map_exceptions
raise to_exc(exc) from None
httpcore._exceptions.ReadTimeout
Hope it helps.
Tomorrow I'll try to debug this.
The best thing to do next would be to find out if it replicates with the sync Client
, or with the AsyncClient
while using trio
.
Also if there's any way we can replicate this locally that'd be amazing, eg does it replicate consistently against some particular external url? Does it replicate without proxies or only with them?
It does reproduce consistently, all communication is between 2 local services. No proxies, no SSL. But I still don't have enough ifno to create a small separate testcase: it is easily reproducible with one endpoint, but never with another...
100% reproducible with sync Client
:
... skip ...
INFO: uvicorn.access: 127.0.0.1:56226 - "POST /ep1/ HTTP/1.1" 201 Created
DEBUG [2020-07-31 10:10:31] httpx._client - HTTP Request: POST http://127.0.0.1:9093/ep1/ "HTTP/1.1 201 Created"
DEBUG: httpx._client: HTTP Request: POST http://127.0.0.1:9093/ep1/ "HTTP/1.1 201 Created"
TRACE [2020-07-31 10:10:31] httpcore._sync.http11 - receive_event=Data(<840 bytes>)
TRACE: httpcore._sync.http11: receive_event=Data(<840 bytes>)
TRACE [2020-07-31 10:10:31] httpcore._sync.http11 - receive_event=EndOfMessage(headers=[])
TRACE: httpcore._sync.http11: receive_event=EndOfMessage(headers=[])
TRACE [2020-07-31 10:10:31] httpcore._sync.http11 - response_closed our_state=DONE their_state=DONE
TRACE: httpcore._sync.http11: response_closed our_state=DONE their_state=DONE
TRACE [2020-07-31 10:10:31] httpcore._sync.connection_pool - get_connection_from_pool=(b'http', b'127.0.0.1', 9093)
TRACE: httpcore._sync.connection_pool: get_connection_from_pool=(b'http', b'127.0.0.1', 9093)
TRACE [2020-07-31 10:10:31] httpcore._sync.connection_pool - reuse connection=<httpcore._sync.connection.SyncHTTPConnection object at 0x7fde344640d0>
TRACE: httpcore._sync.connection_pool: reuse connection=<httpcore._sync.connection.SyncHTTPConnection object at 0x7fde344640d0>
TRACE [2020-07-31 10:10:31] httpcore._sync.connection - connection.request method=b'PATCH' url=(b'http', b'127.0.0.1', 9093, b'/ep2/') headers=[(b'host', b'127.0.0.1:9093'), (b'user-agent', b'python-httpx/0.13.3'), (b'accept', b'*/*'), (b'accept-encoding', b'gzip, deflate'), (b'connection', b'keep-alive'), (b'content-length', b'127'), (b'content-type', b'application/json')]
TRACE: httpcore._sync.connection: connection.request method=b'PATCH' url=(b'http', b'127.0.0.1', 9093, b'/ep2/') headers=[(b'host', b'127.0.0.1:9093'), (b'user-agent', b'python-httpx/0.13.3'), (b'accept', b'*/*'), (b'accept-encoding', b'gzip, deflate'), (b'connection', b'keep-alive'), (b'content-length', b'127'), (b'content-type', b'application/json')]
TRACE [2020-07-31 10:10:31] httpcore._sync.http11 - send_request method=b'PATCH' url=(b'http', b'127.0.0.1', 9093, b'/ep2/') headers=[(b'host', b'127.0.0.1:9093'), (b'user-agent', b'python-httpx/0.13.3'), (b'accept', b'*/*'), (b'accept-encoding', b'gzip, deflate'), (b'connection', b'keep-alive'), (b'content-length', b'127'), (b'content-type', b'application/json')]
TRACE: httpcore._sync.http11: send_request method=b'PATCH' url=(b'http', b'127.0.0.1', 9093, b'/ep2/') headers=[(b'host', b'127.0.0.1:9093'), (b'user-agent', b'python-httpx/0.13.3'), (b'accept', b'*/*'), (b'accept-encoding', b'gzip, deflate'), (b'connection', b'keep-alive'), (b'content-length', b'127'), (b'content-type', b'application/json')]
TRACE [2020-07-31 10:10:31] httpcore._sync.http11 - send_data=Data(<127 bytes>)
TRACE: httpcore._sync.http11: send_data=Data(<127 bytes>)
TRACE [2020-07-31 10:10:36] httpcore._sync.connection_pool - remove from pool connection=<httpcore._sync.connection.SyncHTTPConnection object at 0x7fde344640d0>
TRACE: httpcore._sync.connection_pool: remove from pool connection=<httpcore._sync.connection.SyncHTTPConnection object at 0x7fde344640d0>
INFO: uvicorn.access: 127.0.0.1:35306 - "POST /api/v1/endpoint/ HTTP/1.1" 500 Internal Server Error
ERROR: uvicorn.error: Exception in ASGI application
Traceback (most recent call last):
... skip ...
File "./service_a/lib/mylib.py", line 249, in register
resp = client.patch('/ep2/',
File ".../lib/python3.8/site-packages/httpx/_client.py", line 880, in patch
return self.request(
File ".../lib/python3.8/site-packages/httpx/_client.py", line 600, in request
return self.send(
File ".../lib/python3.8/site-packages/httpx/_client.py", line 620, in send
response = self.send_handling_redirects(
File ".../lib/python3.8/site-packages/httpx/_client.py", line 647, in send_handling_redirects
response = self.send_handling_auth(
File ".../lib/python3.8/site-packages/httpx/_client.py", line 684, in send_handling_auth
response = self.send_single_request(request, timeout)
File ".../lib/python3.8/site-packages/httpx/_client.py", line 714, in send_single_request
) = transport.request(
File ".../lib/python3.8/site-packages/httpcore/_sync/connection_pool.py", line 152, in request
response = connection.request(
File ".../lib/python3.8/site-packages/httpcore/_sync/connection.py", line 78, in request
return self.connection.request(method, url, headers, stream, timeout)
File ".../lib/python3.8/site-packages/httpcore/_sync/http11.py", line 62, in request
) = self._receive_response(timeout)
File ".../lib/python3.8/site-packages/httpcore/_sync/http11.py", line 115, in _receive_response
event = self._receive_event(timeout)
File ".../lib/python3.8/site-packages/httpcore/_sync/http11.py", line 145, in _receive_event
data = self.socket.read(self.READ_NUM_BYTES, timeout)
File ".../lib/python3.8/site-packages/httpcore/_backends/sync.py", line 62, in read
return self.sock.recv(n)
File "/usr/lib/python3.8/contextlib.py", line 131, in __exit__
self.gen.throw(type, value, traceback)
File ".../lib/python3.8/site-packages/httpcore/_exceptions.py", line 12, in map_exceptions
raise to_exc(exc) from None
httpcore._exceptions.ReadTimeout: timed out
sync code is:
with httpx.Client(base_url='http://127.0.0.1:9093') as client:
resp: httpx.Response = client.post(
'/ep1/',
data=orjson.dumps(somedata),
headers={'Content-Type': 'application/json', 'X-Application': app_id}
)
forward_response_error(resp)
resp_data = orjson.loads(resp.content)
resp = client.patch('/ep2/', json=someotherdata)
forward_response_error(resp)
async:
async with httpx.AsyncClient(base_url='http://127.0.0.1:9093') as client:
resp: httpx.Response = await client.post(
'/ep1/',
data=orjson.dumps(somedata),
headers={'Content-Type': 'application/json', 'X-Application': app_id}
)
forward_response_error(resp)
resp_data = orjson.loads(resp.content)
resp = await client.patch('/ep2/', json=someotherdata)
forward_response_error(resp)
reconnect (one more async with httpx.AsyncClient(base_url='http://127.0.0.1:9093') as client:
) before the second request solves the problem
@dmig-alarstudios Thanks for the sync code snippet. However the Client()
is missing a timeout=10
, so assuming you're still using the service that calls the /delay/6
endpoint on HTTPBin, the ReadTimeout
is to be expected.
I tried to reproduce this myself based on https://github.com/encode/httpx/issues/96#issuecomment-662290816, and wasn't able to.
@dmig-alarstudios Can you try the code below and let me know if it works fine on your side?
Web app that proxies requests to HTTPBin:
# app.py
import httpx
from starlette.applications import Starlette
from starlette.routing import Route
from starlette.responses import PlainTextResponse
async def ep1(request):
async with httpx.AsyncClient(timeout=10) as client:
print("ep1 request")
await client.get("https://httpbin.org/delay/6")
return PlainTextResponse("OK")
async def ep2(request):
async with httpx.AsyncClient(timeout=10) as client:
print("ep2 request")
await client.get("https://httpbin.org/delay/6")
return PlainTextResponse("OK")
app = Starlette(routes=[Route("/ep1", ep1), Route("/ep2", ep2)])
Client script (I assume this doesn't need to be wrapped in a web app?):
# client.py
import httpx
async def main():
async with httpx.AsyncClient(timeout=10) as client:
print("ep1")
await client.get("http://localhost:8000/ep1")
print("ep2")
await client.get("http://localhost:8000/ep2")
if __name__ == "__main__":
import asyncio
import trio
asyncio.run(main())
trio.run(main)
Start the web app:
HTTPX_LOG_LEVEL=trace uvicorn app:app
Run the client script:
HTTPX_LOG_LEVEL=trace python client.py
I tried to reproduce this myself based on #96 (comment), and wasn't able to.
I've said that it should reproduce the problem but it doesn't.
However the Client() is missing a
timeout=10
...
Timeout doesn't matter. If I set it to 10 -- I'll just have to wait 10 seconds before the ReadTimeout
-- tried that already.
Have a close look at trace, especially at timings: it tries to send data for the second request but fails.
Okay well, can you then share the full, minimal reproduction code for your example in https://github.com/encode/httpx/issues/96#issuecomment-667002995, since that seems to reproduce the issue correctly?
What we really need here is a full, minimal, dependency-free code listing like I did in https://github.com/encode/httpx/issues/96#issuecomment-667019998. This would allow anyone to reproduce the issue at least somewhat consistently. Then we can go and debug what's wrong. :confused:
Okay well, can you then share the full, minimal reproduction code for your example in #96 (comment), since that seems to reproduce the issue correctly?
Still working on that... :(
There is definitely something related to the first service_b response: maybe something remains unread?
Also helpful, what behaviour does requests
have here?
And to be 100% clear - does this only replicate when sent via a proxy?
@tomchristie my last case -- no proxies, no SSL. I'll make a test using requests.
And are you testing against an external URL, or a service that's running locally?
No: https://github.com/encode/httpx/issues/96#issuecomment-666976890 2 services locally, interacting with each other.
And yes, it's 100% reproducible with requests
. Definitely not an httpx
issue but uvicorn
.
DEBUG:urllib3.connectionpool:Starting new HTTP connection (1): 127.0.0.1:9093
send: b'POST /ep1/ HTTP/1.1\r\nHost: 127.0.0.1:9093\r\nUser-Agent: python-requests/2.24.0\r\nAccept-Encoding: gzip, deflate\r\nAccept: */*\r\nConnection: keep-alive\r\nContent-Type: application/json\r\nContent-Length: 214\r\n\r\n'
send: b'some data'
reply: 'HTTP/1.1 201 Created\r\n'
header: date: Fri, 31 Jul 2020 13:53:22 GMT
header: server: uvicorn
header: content-length: 835
header: content-type: application/json
header: x-correlation-id: 23856694-cc1e-46a0-a9dc-b735eaa1ac9c
DEBUG:urllib3.connectionpool:http://127.0.0.1:9093 "POST /ep1/ HTTP/1.1" 201 835
send: b'PATCH /ep2/ HTTP/1.1\r\nHost: 127.0.0.1:9093\r\nUser-Agent: python-requests/2.24.0\r\nAccept-Encoding: gzip, deflate\r\nAccept: */*\r\nConnection: keep-alive\r\nContent-Length: 122\r\nContent-Type: application/json\r\n\r\n'
send: b'some other data'
reply: ''
Traceback (most recent call last):
File ".../lib/python3.8/site-packages/urllib3/connectionpool.py", line 670, in urlopen
httplib_response = self._make_request(
File ".../lib/python3.8/site-packages/urllib3/connectionpool.py", line 426, in _make_request
six.raise_from(e, None)
File "<string>", line 3, in raise_from
File ".../lib/python3.8/site-packages/urllib3/connectionpool.py", line 421, in _make_request
httplib_response = conn.getresponse()
File "/usr/lib/python3.8/http/client.py", line 1332, in getresponse
response.begin()
File "/usr/lib/python3.8/http/client.py", line 303, in begin
version, status, reason = self._read_status()
File "/usr/lib/python3.8/http/client.py", line 272, in _read_status
raise RemoteDisconnected("Remote end closed connection without"
http.client.RemoteDisconnected: Remote end closed connection without response
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File ".../lib/python3.8/site-packages/requests/adapters.py", line 439, in send
resp = conn.urlopen(
File ".../lib/python3.8/site-packages/urllib3/connectionpool.py", line 726, in urlopen
retries = retries.increment(
File ".../lib/python3.8/site-packages/urllib3/util/retry.py", line 403, in increment
raise six.reraise(type(error), error, _stacktrace)
File ".../lib/python3.8/site-packages/urllib3/packages/six.py", line 734, in reraise
raise value.with_traceback(tb)
File ".../lib/python3.8/site-packages/urllib3/connectionpool.py", line 670, in urlopen
httplib_response = self._make_request(
File ".../lib/python3.8/site-packages/urllib3/connectionpool.py", line 426, in _make_request
six.raise_from(e, None)
File "<string>", line 3, in raise_from
File ".../lib/python3.8/site-packages/urllib3/connectionpool.py", line 421, in _make_request
httplib_response = conn.getresponse()
File "/usr/lib/python3.8/http/client.py", line 1332, in getresponse
response.begin()
File "/usr/lib/python3.8/http/client.py", line 303, in begin
version, status, reason = self._read_status()
File "/usr/lib/python3.8/http/client.py", line 272, in _read_status
raise RemoteDisconnected("Remote end closed connection without"
urllib3.exceptions.ProtocolError: ('Connection aborted.', RemoteDisconnected('Remote end closed connection without response'))
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "test-with-requests.py", line 39, in <module>
resp = client.patch(base_url + '/ep2/',
File ".../lib/python3.8/site-packages/requests/sessions.py", line 602, in patch
return self.request('PATCH', url, data=data, **kwargs)
File ".../lib/python3.8/site-packages/requests/sessions.py", line 530, in request
resp = self.send(prep, **send_kwargs)
File ".../lib/python3.8/site-packages/requests/sessions.py", line 643, in send
r = adapter.send(request, **kwargs)
File ".../lib/python3.8/site-packages/requests/adapters.py", line 498, in send
raise ConnectionError(err, request=request)
requests.exceptions.ConnectionError: ('Connection aborted.', RemoteDisconnected('Remote end closed connection without response'))
requests
raises exactly same RemoteDisconnect
exception. it's strange, that httpx
changed behavior.
Well that narrows it down to either:
You could narrow that down for yourself by running the same application with hypercorn and/or daphne and seeing what behaviour it exhibits then.
In any case, no, not an httpx issue, but we could have some clearer messaging in our exceptions here.
There ought to be an improvement with the upcoming 0.14, since we properly identify closed connections vs raising a protocol error from h11
if a socket read operation returns b""
I'm pretty sure, that's a uvicorn issue. Because running my service under hypercorn solves this problem:
DEBUG:urllib3.connectionpool:Starting new HTTP connection (1): 127.0.0.1:9093
send: b'POST /ep1/ HTTP/1.1\r\nHost: 127.0.0.1:9093\r\nUser-Agent: python-requests/2.24.0\r\nAccept-Encoding: gzip, deflate\r\nAccept: */*\r\nConnection: keep-alive\r\nContent-Type: application/json\r\nContent-Length: 214\r\n\r\n'
send: b'some data'
reply: 'HTTP/1.1 201 \r\n'
header: content-length: 835
header: content-type: application/json
header: x-correlation-id: d4ade3c6-e50f-4a6b-9e25-13a1d609fab5
header: date: Fri, 31 Jul 2020 14:11:57 GMT
header: server: hypercorn-h11
DEBUG:urllib3.connectionpool:http://127.0.0.1:9093 "POST /ep1/ HTTP/1.1" 201 835
send: b'PATCH /ep2/ HTTP/1.1\r\nHost: 127.0.0.1:9093\r\nUser-Agent: python-requests/2.24.0\r\nAccept-Encoding: gzip, deflate\r\nAccept: */*\r\nConnection: keep-alive\r\nContent-Length: 122\r\nContent-Type: application/json\r\n\r\n'
send: b'some other data'
reply: 'HTTP/1.1 204 \r\n'
header: x-correlation-id: 9f653cf6-7cc8-4df1-903c-0fbe0d5f2693
header: date: Fri, 31 Jul 2020 14:11:57 GMT
header: server: hypercorn-h11
DEBUG:urllib3.connectionpool:http://127.0.0.1:9093 "PATCH /ep2/ HTTP/1.1" 204 0
Something we might want to do... if we get a ReadTimeout, check .is_connection_dropped
and include that information in the exception text.
@tomchristie since I have an environment with 100% reproducible issue, can you point me where to look at, to debug the issue in uvicorn
?
Start by checking it run against hypercorn. If it works on hypercorn but not uvicorn, then file an issue on the uvicorn repo.
Ok, nailed it: https://github.com/encode/starlette/issues/919
Is this issue resolved from a client perspective? We seem to be getting a similar error with a rest client. I don't have a backtrace yet though not a good way to reproduce.
Not sure if this is "won't fix" now, but I'm getting the same error message which seems to stem from event = self.h11_state.next_event()
and leads to httpx.RemoteProtocolError: can't handle event type ConnectionClosed when role=SERVER and state=SEND_RESPONSE
as documented above.
Not using uvicorn, traceback below
```
Traceback (most recent call last):
File "/home/louis/miniconda3/lib/python3.8/site-packages/httpx/_exceptions.py", line 326, in map_exceptions
yield
File "/home/louis/miniconda3/lib/python3.8/site-packages/httpx/_client.py", line 1492, in _send_single_request
(status_code, headers, stream, ext) = await transport.arequest(
File "/home/louis/miniconda3/lib/python3.8/site-packages/httpx/_transports/default.py", line 169, in arequest
return await self._pool.arequest(
File "/home/louis/miniconda3/lib/python3.8/site-packages/httpcore/_async/connection_pool.py", line 218, in arequest
response = await connection.arequest(
File "/home/louis/miniconda3/lib/python3.8/site-packages/httpcore/_async/connection.py", line 106, in arequest
return await self.connection.arequest(method, url, headers, stream, ext)
File "/home/louis/miniconda3/lib/python3.8/site-packages/httpcore/_async/http11.py", line 72, in arequest
) = await self._receive_response(timeout)
File "/home/louis/miniconda3/lib/python3.8/site-packages/httpcore/_async/http11.py", line 133, in _receive_response
event = await self._receive_event(timeout)
File "/home/louis/miniconda3/lib/python3.8/site-packages/httpcore/_async/http11.py", line 169, in _receive_event
event = self.h11_state.next_event()
File "/home/louis/miniconda3/lib/python3.8/contextlib.py", line 131, in __exit__
self.gen.throw(type, value, traceback)
File "/home/louis/miniconda3/lib/python3.8/site-packages/httpcore/_exceptions.py", line 12, in map_exceptions
raise to_exc(exc) from None
httpcore.RemoteProtocolError: can't handle event type ConnectionClosed when role=SERVER and state=SEND_RESPONSE
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
File "
A bit of a 'Heisenbug', happens less than 10% of the time when requesting (many) JSON files for episode metadata from the BBC's radio schedules. Using my library beeb
I get it from beeb.nav.sched.ProgrammeCatalogue("r4", n_days=30)
and can greatly increase its frequency by switching my httpx.AsyncClient
to use http2=True
.
Not disastrous, I will probably just catch and retry, but thought I should pitch in with my experience :smiley:
@lmmx look at https://github.com/encode/httpx/issues/96#issuecomment-672884672 -- there is a workaround
I intermittently got this error when load testing uvicorn endpoint.
This error comes from a proxy endpoint where I am also using
encode/http3
to perform HTTP client calls.