Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

uvicorn depends on the legacy server implementation #975

Open
euri10 opened this issue May 28, 2021 · 10 comments
Open

uvicorn depends on the legacy server implementation #975

euri10 opened this issue May 28, 2021 · 10 comments

Comments

@euri10
Copy link

euri10 commented May 28, 2021

greetings and thanks for the great library !

I'd have a question for you on what you think would be the best way to proceed for us in uvicorn : we recently unpinned websockets from websockets==8.* to websockets>=8.* which went flawlessly in the CI.

However we have a pending PR that aims at being stricter (basically transform warnings into failures) and in that context we'd have to do

- websockets.handshake.check_request(headers)
+ websockets.legacy.handshake.check_request(headers)

So if I understand correctly the 9.0 changelog wording, in particular:

Despite the name

this fix is correct in the sense it is still supported and will be until v10 happens, it this the right way to understand it ? In which case we should probably do websockets>=8.*, <10 ?

Second question, I'm unsure about the following:

The framing, handshake, headers, http, and uri modules in the websockets package are deprecated. These modules provided low-level APIs for reuse by other WebSocket implementations, but that never happened. Keeping these APIs public makes it more difficult to improve websockets for no actual benefit.

so the deprecation warning we have on websockets.handshake.check_request(headers) is very clear when reading the above statement, this said what would be an "equivalent" way to proceed on 9.* without relying on those "not public" APIs ?

merci !

@aaugustin
Copy link
Member

I have to give you more context :-)


The backwards compatibility policy says that I intend to maintain backwards compatibility for 5 years after deprecating an API. No worries — we have plenty of time to figure this out! I have kept backwards compatibility shims for longer than 5 years when I suspected that others depended on them.

The fix works and will continue working for several years, certainly until websockets 10, most likely in the following major versions. (SemVer forces me to bump the major version for changes that might break a few users even if they're fine for most... so the major version number climbs quite fast...)

Technically the legacy module is a private API but I know that it will be imported directly so I'll be careful if I ever decide to remove it. I don't have specific plans at this time. Keeping it "foverever" is an option if there are good reasons to do so.


So, here's how we end up there.

When I started websockets in 2013, I designed it in a modular way so that others could reuse bits and documented public APIs. Quickly I realized this aspect of the design didn't work because several projects ended up resorting to hacks for integrating websockets :-/

When Sans-I/O came up (in 2017 I think?) I thought that providing a Sans-I/O layer (similar to wsproto) would be a much better abstraction. So I set out to refactor websockets into a Sans-I/O layer and integration layers.

The Sans-I/O layer is there: https://github.com/aaugustin/websockets/blob/2990cf761177073e6d741e1ef81dc1d6d3c5dba8/src/websockets/server.py#L51
Until I finish at least one integration layer, it could still change, so I didn't document or advertise it yet.

The most advanced integration layer is #885 — not done yet.

During this refactoring, I deprecated all low-level public APIs from the original, failed design. The driver was "let's make it private; no one uses it anyway AFAIK; then I get more freedom to refactor".

I didn't expect anyone to be using this low level APIs. Now I realize uvicorn does. Obviously I'm not going to strand you :-) At least I need to fix the changelog; "that never happened" is wrong.

I released 9.0 half-way through the refactoring effort (i.e. with the current implementation moved to legacy and the future implementation not done yet) because I wanted to release other bugfixes and improvements. This explains the less-than-ideal state of the project for you. (It's OK for typical users; you aren't a typical user.)


Looking forwards, there are two solutions:

  1. I keep the legacy.handshake module until further notice. The interesting part is negotiating extensions; it's a lot more code that one would expect; so I would advise against copy-pasting it in uvicorn.
  2. Once the Sans-I/O layer is ready, you switch to using it. I believe this is hard to assess until it's done.

If you tell me exactly what you use websockets for, perhaps we can find the best way together?

The whole point of this huge refactoring is to make websockets easier to integrate for you. I'd love to know your thoughts!

@euri10
Copy link
Author

euri10 commented May 28, 2021

I have to give you more context :-)

that was indeed very detailed.

At least I need to fix the changelog; "that never happened" is wrong.

nah as you said it's probably not the typical use, I can bear with it of course !!

Looking forwards, there are two solutions:

1 is perfectly fine I think for the time being, assessing 2 in due course will be interesting

If you tell me exactly what you use websockets for, perhaps we can find the best way together?

I don't know how intimate you are with the asgi spec so the following may sound ovious, but uvicorn is an asgi webserver (it implements that specification, you can think of it as an async version of the wsgi spec), and for the ws part of that spec we use your library by sublassing WebSocketServerProtocol
I think the original author @tomchristie could definitly be a more interesting person than me to talk about the design though !

@aaugustin
Copy link
Member

Yes I'm familiar with ASGI. (I'm a Django committer; in 2013, I investigated what it would take to make Django async and failed; then Andrew picked up this flag and eventually came up with Django Channels and ASGI.)

I talked with Tom a few years back. I just reviewed how uvicorn integrates websockets, and indeed, there's quite a bit of surgery involved to integrate it. It works but it depends rather heavily on websockets internals.

Likely you could build something cleaner on top of the Sans-I/O layer. I'll keep this issue open and let you know when it's ready.

@aaugustin aaugustin changed the title Handshake deprecation warning advice uvicorn depends on the legacy server implementation May 28, 2021
@aaugustin
Copy link
Member

Sanic has the same issue: sanic-org/sanic#2154

However they're actively moving to the Sans-I/O implementation which will remove the dependency on the handshake module.

@aaugustin
Copy link
Member

Hello!

The Sans-I/O layer I was discussing earlier is now a public API in websockets 10.0.

Here's how to integrate it:

Here's Sanic adopting it:

@aaugustin
Copy link
Member

In case this helps, #1332 integrate the Sans-I/O layer in an asyncio implementation.

@Kludex
Copy link

Kludex commented Aug 8, 2024

@aaugustin I just don't know the right thing to do here.

In Uvicorn, we are using the WebSockets legacy, but it's working just fine - and I've fixed a lot of issues in the Uvicorn implementation over the last two years.

We have support for 2 websocket implementations. Uvicorn uses websockets legacy if websockets is installed, and uses the wsproto one if the latter is installed.

What do you recommend us to do? Create a new implementation with the WebSockets Sans-IO, and deprecate the old one? Are you going to not accept changes on the legacy implementation if we find issues?

@aaugustin
Copy link
Member

Create a new implementation with the WebSockets Sans-IO, and deprecate the old one?

Ideally, yes. This should result in a cleaner, more reliable integration, also more similar to the wsproto integration.

By "more reliable", I mean "less vulnerable to issues like #1396 (comment)".

Are you going to not accept changes on the legacy implementation if we find issues?

It's so stable at this point that I'm not really worried about bugs — and I'm still going to fix them for several years.

It's just annoying to keep it compatible with new Python versions, mypy updates, etc. This implementation was born in the very early days of asyncio, when it was still called tulip, and there are home-grown bits (like my own version of unittest.IsolatedAsyncioTestCase, etc.)

I'm less motivated to keep it alive now that the new asyncio implementation — with a better design, more reliable tests, etc. — has landed in the main branch.


If we're pragmatic, options are:

  1. an uvicorn contributor is motivated to reimplement the websockets integration in uvicorn
  2. I am motivated to do it
  3. nothing changes; I have to keep maintaining the legacy implementation to avoid stranding you on a version that doesn't get security updates

If 1. doesn't happen and 3. becomes too painful, then it will have to mean 2.

@Kludex
Copy link

Kludex commented Aug 8, 2024

There's already one implementation in a close PR, I can't reopen, and ask your review. If that's okay.

But the thing is... We'll have 3 implementations of websockets, and if we are doing things by the book, the legacy will be the default when you have websockets installed, and optionally allow the new implementation to be selected. What I worry is that no one will select the new one...

I'll reopen the old PR soon - when I get to the computer.

@aaugustin
Copy link
Member

IMO you should remove the old websockets implementation (using private APIs) relatively quickly and keep only the new implementation (using the Sans-I/O layer & public API) relatively quickly.

Rationale:

  • I'm highly confident that websockets' Sans-I/O layer is robust.
  • Based on how you've had to do the current integration, I'm quite confident that a rewrite on top of the Sans-I/O layer will be at least as good.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants