You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Currently httpx is squarely focused on HTTP's traditional request / response paradigm, and there are well-established packages for WebSocket support such as websockets. In an HTTP/1.1-only world, this split of responsabilities makes perfect sense as HTTP requests / WebSockets work independently.
However, with HTTP/2 already widely deployed and HTTP/3 standardisation well under way I'm not sure the model holds up:
implementations such as websockets are usually tied to HTTP/1.1 only, whereas httpx has support for HTTP/2 (and hopefully soon HTTP/3)
we are missing out on the opportunity to multiplex "regular" HTTP request with WebSocket connections
Using the sans-IO wsproto combined with httpx's connection management we could provide WebSocket support spanning HTTP/1.1, HTTP/2 and HTTP/3. What are your thoughts on this?
One caveat: providing WebSocket support would only make sense using the AsyncClient interface.
So my assumption here would be "no", but that it's possible we'll want to expose just enough API in order to allow a websocket implementation to use httpx for handshaking the connection, and sharing the connection pool.
So, to the extent that we might allow it to be possible, I'd expect that to be in the form of a third party package, that has httpx as a dependency.
I'm not sure exactly what API that'd imply that we'd need to support, but it's feasible that we might end up wanting to expose some low-level connection API specifically for supporting this kind of use case. The most sensible tack onto thrashing out what we'd need there would be a proof-of-concept implementation that'd help us concretely indentify how much API we'd need to make that possible.
Does all that make sense, or am I off on the wrong track here?
Interesting points, yeah. I guess I'd be open to reassessing this as we go.
We'd need to identify what the API would look like, both at the top user-facing level, and at whatever new lower-level cutoffs we'd need to be introducing.
I'm in favor of adding websockets definitely, would be a good feature to work towards in a v2? Still so much to do to get a v1 released.
I'll see if I can find time to put together a minimal PR supporting HTTP/1.1 and HTTP/2 to scope out what this entails. Obviously if there are more pressing needs for v1 there is zero pressure to merge it.
@aaugustin just pinging you so you're in the loop : this is not a hostile move against websockets, and your insights would be most welcome
I'm currently working on refactoring websockets to provide a sans-I/O layer. If it's ready within a reasonable timeframe, perhaps you could use it. It will handle correctly a few things that wsproto doesn't, if the comments I'm seeing in wsproto's source code are correct.
One of my issues with sans-I/O is the prohibition async / await, making it impossible to provide a high-level API. For this reason, I'm not sure that sans-I/O as currently specified is the best approach. This makes me hesitate on a bunch of API design questions...
Building an API that httpx will consume (either strict sans-I/O or something else) would be a great step forwards. Perhaps we can cooperate on this?
Also I would really like websockets to work over HTTP/2 and HTTP/3.
I'm quite happy with my implementation of a subset of HTTP/1.1 — I'm not kidding, even though it's home-grown, I honestly think it's all right. It may be possible to achieve HTTP/2 in the same way but it will be partial e.g. it will be impossible to multiplex websockets and HTTP on the same connection. Given what I've seen of aioquic, I don't think it's reasonable to do HTTP/3 that way.
So I'm interested in working with third-party packages that handle HTTP/2 and HTTP/3 to figure out what that entails.
Reacted by Kim Christie, Jeremy Lainé, Kyle Bebak, VeNoMouS, databasedav, Daniel Enrique and sha2fiddy
Best wishes to the author, this lib is a great help for me.
PS: aiohttp is good enough, except some frequently changing api protocol (like variable names), which raised a backward incompatibility error by a new version.
@aaugustin That's awesome news! :) Thanks for updating this thread. It'd be interesting to see how an upgrade from h11 into the websockets state-machine might look. Something to definitely write up a POC for that little exchange.
Since we're talking about httpx, I'm focusing on the client side here, but the same logic would apply on the server side.
In addition to the regular APIs that receive and send bytes, websockets should provide an API to receive and send already parsed HTTP headers. This API will support bolting websockets on top of any HTTP/1.1 implementation.
fromwebsocketsimportClientConnectionws=ClientConnection()
request=ws.build_handshake_request()
# here request is (path, headers) tuplebytes_to_send=serialize(request) # <-- you can do this with any library you want
...
response=parse(bytes_received) # <-- you can do this with any library you want# here response is a (status_code, reason_phrase, headers) tuplews.receive_handshake_response()
The latter happens under the hood anyway so it's clearly doable.
It's "just" a matter of naming things :-) — which I care deeply about.
WebSocket over HTTP/2 requires a different handshake, so websockets will need another APIs to support it. I have no idea about WebSocket over HTTP/3.
WebSockets over HTTP/3 haven't been officially specified, but it is extremely likely it will work like for HTTP/2 (RFC 8441), namely using a :protocol pseudo-header. This is what I have assumed for the aioquic demo client + server, and I believe @pgjones did the same for hypercorn.
I'd like us to treat this as out-of-scope at this point in time.
Yes I think we'll want to provide enough API to make this do-able at some point in the not too distant future, but being able to do that while still aiming for a sqaured away API stable 1.0 release isn't something we're able to do just yet.
FWIW websockets provides a Sans-I/O layer too. It used to be more feature-complete; wsproto caught up in the recent years; I don't know how the two libraries compare these days.
Based on the docs, I believe websockets' handling of failure scenarios is more robust i.e. it tells you when things have gone wrong and you should just close the TCP connection (even if you're the client).
One thing that websockets is missing — and that I had in mind when I wrote the Sans-I/O layer — is the ability to handshake over HTTP/2. I believe you have that :-) I'd be interested in figuring out the graft.
Neato. Step zero would be to ping up a little example similar to the wsproto based one above, to demo using the websockets Sans-IO API together with httpx for the networking. We could figure out where we wanted to go from there.
One thing that websockets is missing — and that I had in mind when I wrote the Sans-I/O layer — is the ability to handshake over HTTP/2.
I'd suppose the benefits of using httpx here are that it would mean you'd be able to support asyncio, trio, and threaded versions, add that you'd be able to lean on all the same auth, SSL config, network timeout behaviours etc. that httpx provides. No doubt you've got websockets already super well covered, but it'd be neat to have a unified http and websockets client.
We could feasibly add websockets-over-http/2 as well, yes, although it'd need a bit more work, see above.
The main pain point for me was to actually implement an ASGITransport able to handle WebSockets using the network_stream approach. Other than that, the API in itself was quite straightforward to bootstrap.
It's still very experimental, but it's a start 😊
Reacted by David Brochart, Ilya Siamionau and T-256
A pretty big difference with the example with wsproto above — I'm actually executing the opening handshake logic, meaning that extensions, subprotocols, etc. are negotiated. In practice, this means that compression works.
Also, lots of small things could be cleaner: for example, httpx insists on having a http:// url while websockets wants a ws:// url.
importhttpx# ClientConnection is renamed to ClientProtocol in websockets 11.0.# Sorry, I grew unhappy with my inital attempt at naming things!# I'm using the future-proof name here.fromwebsockets.clientimportClientConnectionasClientProtocolfromwebsockets.connectionimportStatefromwebsockets.datastructuresimportHeadersfromwebsockets.framesimportOpcodefromwebsockets.http11importResponsefromwebsockets.uriimportparse_uridefconnect_to_websocket(url):
# Force the connection state to OPEN instead of CONNNECTING because# we're handling the opening handshake outside of websockets.protocol=ClientProtocol(
parse_uri(url.replace("http://", "ws://")),
state=State.OPEN,
)
# Start WebSocket opening handshake.request=protocol.connect()
withhttpx.stream("GET", url, headers=request.headers) asresponse:
# Get the raw network stream.network_steam=response.extensions["network_stream"]
# Convert httpx response to websockets response.response=Response(
response.status_code,
response.reason_phrase,
Headers(response.headers),
)
# Complete WebSocket opening handshake.protocol.process_response(response)
# Write a WebSocket text frame to the stream.protocol.send_text("hello, world!".encode())
foroutgoing_datainprotocol.data_to_send():
network_steam.write(outgoing_data)
# Wait for a response.incoming_data=network_steam.read(max_bytes=4096)
protocol.receive_data(incoming_data)
forframeinprotocol.events_received():
ifframe.opcodeisOpcode.TEXT:
print("Got data:", frame.data.decode())
# Write a WebSocket close to the stream.protocol.send_close()
foroutgoing_datainprotocol.data_to_send():
network_steam.write(outgoing_data)
if__name__=="__main__":
connect_to_websocket("http://127.0.0.1:8765/")
Re. asyncio / trio / threads, actually websockets isn't all that well covered:
asyncio is the original implementation, predating the Sans-I/O layer (actually predating the concept of Sans-I/O in the Python community), robust but considered legacy;
threads will be the first built-in implementation on top of the Sans-I/O layer, ETA this year;
We do already handle the correct default port mapping for ws and wss, and httpx will send ws URLs to the transport layer, so it's just the one fix pointed to above that's needed.
@aaugustin - Thanks for your example above! I've reworked that to provide a basic API example...
That's looking pretty neat. It occurs to me that a neater API would be to make the client instance optional, so that you've got the basic case covered...
Is there a plan to get this built into AsyncClient?
I'm struggling a bit to write end-to-end tests where I want to listen to websockets in the background while i post data to fastapi.
I will try to work with your examples, but this really is a lot of code that would be nice to have as a method on AsyncClient like Starlette has on it's TestClient
They're such important use cases that I'd probably suggest something like prominent "Websockets" and "Server Sent Events" headings above the existing "Plugins"?
Reacted by François Voron, MichadeGroot, elonzh and T-256
I needed a behavior where I could connect to the websocket and listen to messages from the server indefinitely, until I needed to disconnect, while making sure that every time a message came the program would perform the action I wanted. In this implementation, it didn't work so the recv method would save first message and wouldn't listen to anything after that, but would endlessly return the message. Chat-GPT made some changes to the method and now it works the way I need it to. If you put it in a loop, it will work as long as you need. I'm sharing, maybe it will be useful for someone:
async def recv(self):
while True:
incoming_data = await self._network_stream.read(max_bytes=4096)
self._protocol.receive_data(incoming_data)
self._events = self._protocol.events_received()
for event in self._events:
if event.opcode is Opcode.TEXT:
return event.data.decode()
elif event.opcode is Opcode.CLOSE:
raise ConnectionClosed()
async def hello(url):
async with connect(url) as websocket:
while True:
# Send a message to the server
await websocket.send("Hello world")
# Receive a message from the server
message = await websocket.recv()
# Print the message
print(message)
@ll2pakll Thanks for the prompt. I can see the error there now, although the Chat-CPT version isn't quite right either. (Might block reading from the network while there's pending events that could be returned.)
Activity
lovelydinosaur commentedon Sep 2, 2019
So my assumption here would be "no", but that it's possible we'll want to expose just enough API in order to allow a websocket implementation to use httpx for handshaking the connection, and sharing the connection pool.
So, to the extent that we might allow it to be possible, I'd expect that to be in the form of a third party package, that has
httpxas a dependency.I'm not sure exactly what API that'd imply that we'd need to support, but it's feasible that we might end up wanting to expose some low-level connection API specifically for supporting this kind of use case. The most sensible tack onto thrashing out what we'd need there would be a proof-of-concept implementation that'd help us concretely indentify how much API we'd need to make that possible.
Does all that make sense, or am I off on the wrong track here?
jlaine commentedon Sep 2, 2019
What you are saying does make sense, I haven't quite made up my own mind on this.
Pros of a third-party package handling the websockets:
Cons:
lovelydinosaur commentedon Sep 2, 2019
Interesting points, yeah. I guess I'd be open to reassessing this as we go.
We'd need to identify what the API would look like, both at the top user-facing level, and at whatever new lower-level cutoffs we'd need to be introducing.
sethmlarson commentedon Sep 2, 2019
I'm in favor of adding websockets definitely, would be a good feature to work towards in a v2? Still so much to do to get a v1 released.
Thanks for bringing this up @jlaine! I've not had to deal with websockets so I didn't even think they'd be at home in a library like HTTPX. :)
lovelydinosaur commentedon Sep 2, 2019
Probably not a bad call, yeah.
jlaine commentedon Sep 2, 2019
I'll see if I can find time to put together a minimal PR supporting HTTP/1.1 and HTTP/2 to scope out what this entails. Obviously if there are more pressing needs for v1 there is zero pressure to merge it.
@aaugustin just pinging you so you're in the loop : this is not a hostile move against
websockets, and your insights would be most welcomeaaugustin commentedon Sep 2, 2019
I'm currently working on refactoring websockets to provide a sans-I/O layer. If it's ready within a reasonable timeframe, perhaps you could use it. It will handle correctly a few things that wsproto doesn't, if the comments I'm seeing in wsproto's source code are correct.
One of my issues with sans-I/O is the prohibition async / await, making it impossible to provide a high-level API. For this reason, I'm not sure that sans-I/O as currently specified is the best approach. This makes me hesitate on a bunch of API design questions...
Building an API that httpx will consume (either strict sans-I/O or something else) would be a great step forwards. Perhaps we can cooperate on this?
Also I would really like
websocketsto work over HTTP/2 and HTTP/3.I'm quite happy with my implementation of a subset of HTTP/1.1 — I'm not kidding, even though it's home-grown, I honestly think it's all right. It may be possible to achieve HTTP/2 in the same way but it will be partial e.g. it will be impossible to multiplex websockets and HTTP on the same connection. Given what I've seen of aioquic, I don't think it's reasonable to do HTTP/3 that way.
So I'm interested in working with third-party packages that handle HTTP/2 and HTTP/3 to figure out what that entails.
ClericPy commentedon Oct 1, 2019
As for me, httpx is not only the next generation HTTP client for requests but also aiohttp.
So expecting a new choice to take the place of aiohttp's websocket. https://github.com/ftobia/aiohttp-websockets-example/blob/master/client.py
Best wishes to the author, this lib is a great help for me.
PS: aiohttp is good enough, except some frequently changing api protocol (like variable names), which raised a backward incompatibility error by a new version.
aaugustin commentedon Oct 6, 2019
FYI I started implementing a Sans-I/O layer for websockets: python-websockets/websockets#466
It may not be obvious from the first commits because I have to untangle a lot of stuff from asyncio before I can get anything done :-)
sethmlarson commentedon Oct 6, 2019
@aaugustin That's awesome news! :) Thanks for updating this thread. It'd be interesting to see how an upgrade from
h11into thewebsocketsstate-machine might look. Something to definitely write up a POC for that little exchange.(Also, congrats on the Tidelift sponsorship!)
aaugustin commentedon Oct 7, 2019
So, here's how I think the bridge could work.
Since we're talking about httpx, I'm focusing on the client side here, but the same logic would apply on the server side.
In addition to the regular APIs that receive and send bytes, websockets should provide an API to receive and send already parsed HTTP headers. This API will support bolting websockets on top of any HTTP/1.1 implementation.
In addition to:
I need to provide something like:
The latter happens under the hood anyway so it's clearly doable.
It's "just" a matter of naming things :-) — which I care deeply about.
WebSocket over HTTP/2 requires a different handshake, so websockets will need another APIs to support it. I have no idea about WebSocket over HTTP/3.
jlaine commentedon Oct 17, 2019
WebSockets over HTTP/3 haven't been officially specified, but it is extremely likely it will work like for HTTP/2 (RFC 8441), namely using a
:protocolpseudo-header. This is what I have assumed for theaioquicdemo client + server, and I believe @pgjones did the same forhypercorn.lovelydinosaur commentedon Dec 5, 2019
I'd like us to treat this as out-of-scope at this point in time.
Yes I think we'll want to provide enough API to make this do-able at some point in the not too distant future, but being able to do that while still aiming for a sqaured away API stable 1.0 release isn't something we're able to do just yet.
29 remaining items
aaugustin commentedon Nov 18, 2022
FWIW websockets provides a Sans-I/O layer too. It used to be more feature-complete; wsproto caught up in the recent years; I don't know how the two libraries compare these days.
Based on the docs, I believe websockets' handling of failure scenarios is more robust i.e. it tells you when things have gone wrong and you should just close the TCP connection (even if you're the client).
aaugustin commentedon Nov 18, 2022
One thing that websockets is missing — and that I had in mind when I wrote the Sans-I/O layer — is the ability to handshake over HTTP/2. I believe you have that :-) I'd be interested in figuring out the graft.
lovelydinosaur commentedon Nov 18, 2022
Neato. Step zero would be to ping up a little example similar to the
wsprotobased one above, to demo using thewebsocketsSans-IO API together withhttpxfor the networking. We could figure out where we wanted to go from there.I'd suppose the benefits of using
httpxhere are that it would mean you'd be able to supportasyncio,trio, and threaded versions, add that you'd be able to lean on all the same auth, SSL config, network timeout behaviours etc. thathttpxprovides. No doubt you've gotwebsocketsalready super well covered, but it'd be neat to have a unified http and websockets client.We could feasibly add websockets-over-http/2 as well, yes, although it'd need a bit more work, see above.
frankie567 commentedon Nov 22, 2022
I started to work on this, following what you recommended, @tomchristie: https://github.com/frankie567/httpx-ws
The main pain point for me was to actually implement an
ASGITransportable to handle WebSockets using thenetwork_streamapproach. Other than that, the API in itself was quite straightforward to bootstrap.It's still very experimental, but it's a start 😊
aaugustin commentedon Nov 22, 2022
Here's one way to do it with websockets.
A pretty big difference with the example with wsproto above — I'm actually executing the opening handshake logic, meaning that extensions, subprotocols, etc. are negotiated. In practice, this means that compression works.
Also, lots of small things could be cleaner: for example, httpx insists on having a
http://url while websockets wants aws://url.aaugustin commentedon Nov 22, 2022
Re. asyncio / trio / threads, actually websockets isn't all that well covered:
lovelydinosaur commentedon Nov 23, 2022
Would someone like to raise an issue so we can resolve that?
There's a very minor change needed in
httpcore.We do already handle the correct default port mapping for ws and wss, and
httpxwill sendwsURLs to the transport layer, so it's just the one fix pointed to above that's needed.@aaugustin - Thanks for your example above! I've reworked that to provide a basic API example...
You can then use that with
asyncio...Or with
trio...To implement the equivalent threaded version, drop the
async/awaitcalls everywhere, and usecontextlib.contextmanagerandhttpx.Client.@frankie567
That's looking pretty neat. It occurs to me that a neater API would be to make the
clientinstance optional, so that you've got the basic case covered...and if you want shared connection pooling, then...
wholmen commentedon Nov 30, 2022
Is there a plan to get this built into AsyncClient?
I'm struggling a bit to write end-to-end tests where I want to listen to websockets in the background while i post data to fastapi.
I will try to work with your examples, but this really is a lot of code that would be nice to have as a method on AsyncClient like Starlette has on it's TestClient
frankie567 commentedon Nov 30, 2022
@wholmen You can try the library I've just created: https://github.com/frankie567/httpx-ws
In particular, here is how you could set up a test client to test a FastAPI app: https://frankie567.github.io/httpx-ws/usage/asgi/
lovelydinosaur commentedon Nov 30, 2022
So I suppose we should add this and
https://github.com/florimondmanca/httpx-sseto the https://www.python-httpx.org/third_party_packages/ docs?They're such important use cases that I'd probably suggest something like prominent "Websockets" and "Server Sent Events" headings above the existing "Plugins"?
ll2pakll commentedon Dec 29, 2022
I needed a behavior where I could connect to the websocket and listen to messages from the server indefinitely, until I needed to disconnect, while making sure that every time a message came the program would perform the action I wanted. In this implementation, it didn't work so the
recvmethod would save first message and wouldn't listen to anything after that, but would endlessly return the message. Chat-GPT made some changes to the method and now it works the way I need it to. If you put it in a loop, it will work as long as you need. I'm sharing, maybe it will be useful for someone:lovelydinosaur commentedon Dec 29, 2022
@ll2pakll Thanks for the prompt. I can see the error there now, although the Chat-CPT version isn't quite right either. (Might block reading from the network while there's pending events that could be returned.)
Here's an updated
ws_protobased implementation that'll work with eitherasyncioortrio... https://gist.github.com/tomchristie/3293d5b118b5646ce79cc074976744b0leafayon commentedon Feb 3, 2024
Not websocket support so ?
dimaqq commentedon Dec 4, 2025
Given that
httpx-wsgot mentioned, I think that this issue can be closed.