uvicorn icon indicating copy to clipboard operation
uvicorn copied to clipboard

Switch WebSockets legacy implementation to Sans-I/O

Open Kludex opened this issue 1 year ago • 14 comments

Given https://github.com/aaugustin/websockets/issues/975 and https://github.com/aaugustin/websockets/issues/1310#issuecomment-1479780072 we should try to follow @aaugustin's recommendation.

We can discuss if we should deprecate the current WebSocketProtocol, or if we should replace it, and that's it.

[!IMPORTANT]

  • We're using Polar.sh so you can upvote and help fund this issue.
  • We receive the funding once the issue is completed & confirmed by you.
  • Thank you in advance for helping prioritize & fund our backlog.
Fund with Polar

Kludex avatar Mar 22 '23 15:03 Kludex

The documentation for this is here: https://websockets.readthedocs.io/en/stable/howto/sansio.html

I don't remember how uvicorn integrates websockets so I'm not sure how much work it would take to upgrade to the Sans-I/O implementation. It would probably result in a more straightforward and testable design, though.

As a reference, here's how websockets would use its own Sans-I/O layer: https://github.com/aaugustin/websockets/pull/1245

You might find it helpful to look at Sanic's integration too: https://github.com/sanic-org/sanic/pull/2158

I'm happy to answer questions and assist with reviews.

aaugustin avatar Mar 22 '23 19:03 aaugustin

Is it possible to send data as soon as I have available on the server with send_response?

I want to send the headers, and then the body, and then more body, and maybe more body after...

Kludex avatar Mar 23 '23 12:03 Kludex

That can't be a WebSocket handshake response; it's unsupported in websockets.

You aren't going to involve websockets anymore after sending such a response. You can ignore websockets and write directly to the network connection.

If it makes the implementation cleaner or easier, you could call websockets's send_response method with only the headers; then you would write the body as you generate it, without involving websockets.

aaugustin avatar Mar 24 '23 12:03 aaugustin

If it makes the implementation cleaner or easier, you could call websockets's send_response method with only the headers; then you would write the body as you generate it, without involving websockets.

So I reject with websockets, and then write the body? Just to confirm.

Kludex avatar Mar 24 '23 13:03 Kludex

Well, reject isn't going to be entirely helpful, because it wants to set Content-Length:

https://github.com/aaugustin/websockets/blob/ba1ed7a65cc876ff4e0fcd4dd4711402836475e2/src/websockets/server.py#L505

If you want to rely as much as possible on public API to avoid breaking websockets' invariants, here's my recommendation:

  1. Call reject() with a dummy body. It will return a Response object that doesn't look exactly the way you want, since you didn't have the right body yet.
  2. Here you have two options: a. alter response returned by reject e.g. del response.headers["Content-Length"] b. ignore it and build a new response = Response(status.value, status.phrase, headers, body) from scratch
  3. send_response(response)
  4. ... = data_to_send() --> send that to the network
  5. send the rest of the body to the network

If you find that annoying, you can just send what you want to the network and ignore exceptions thrown by websockets.

aaugustin avatar Mar 24 '23 16:03 aaugustin

I don't have bandwidth to do this. Whoever wants to do it, feel free to. I can review, give feedback, and also guide you on how to do it.

Kludex avatar Apr 15 '23 14:04 Kludex

@Kludex Happy to take this one!

parthvnp avatar May 07 '23 05:05 parthvnp

@Kludex Happy to take this one!

Go ahead. 🙏

You can use https://github.com/encode/uvicorn/pull/1911 if it helps. 😁

Kludex avatar May 07 '23 06:05 Kludex

Hi @Kludex Is this task still open. If it is , I tried implementing it and have opened a PR for it. Could you please review it. https://github.com/encode/uvicorn/pull/2060

gourav-kandoria avatar Jul 27 '23 12:07 gourav-kandoria

To implement SansIO shouldn't we use Server SansIO, but as the websocket is pinned in the requirements file the module is not available.

Are there any breaking changes with using a newer websocket release?

valentin994 avatar Jul 28 '23 13:07 valentin994

See #1927. It looks like an issue in the current implementation. If you're replacing it with a new implementation, you can safely bump the dependency.

aaugustin avatar Jul 29 '23 07:07 aaugustin

@aaugustin I implemented it on version 10.4. (https://github.com/encode/uvicorn/pull/2060) where ServerConnection was providing sans-I/O. Changing the version to 11.0 would require to change it to ServerProtocol. and I guess, the apis of both the classes is same. or should some changes also be required there.

gourav-kandoria avatar Jul 29 '23 07:07 gourav-kandoria

It's just a renaming.

There's another minor change:

Sans-I/O protocol constructors now use keyword-only arguments.

but it doesn't affect you because you're already using keyword arguments in your PR.

aaugustin avatar Jul 29 '23 17:07 aaugustin

@Kludex As websockets version was changed to 11.0.3 on master. So changed the pr accordingly

gourav-kandoria avatar Aug 01 '23 16:08 gourav-kandoria