-
-
Notifications
You must be signed in to change notification settings - Fork 754
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
Send code 1012 on shutdown for websockets #1816
Conversation
@@ -90,7 +90,6 @@ def __init__( | |||
self.connect_sent = False | |||
self.lost_connection_before_handshake = False | |||
self.accepted_subprotocol: Optional[Subprotocol] = None | |||
self.transfer_data_task: asyncio.Task = None # type: ignore[assignment] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is necessary to call fail_connection()
, because internally there's a check like if hasattr(self, "transfer_data_task")
, which should succeed.
4aeb55c
to
eb21ef0
Compare
eb21ef0
to
ed7d669
Compare
if self.conn.connection is None: | ||
output = self.conn.send(wsproto.events.RejectConnection(status_code=500)) | ||
else: | ||
msg = h11.Response( | ||
status_code=500, headers=headers, reason="Internal Server Error" | ||
output = self.conn.send( | ||
wsproto.events.RejectConnection( | ||
status_code=500, headers=headers, has_body=True | ||
) | ||
output = self.conn.send(msg) | ||
msg = h11.Data(data=b"Internal Server Error") | ||
output += self.conn.send(msg) | ||
msg = h11.EndOfMessage() | ||
output += self.conn.send(msg) | ||
) | ||
output += self.conn.send( | ||
wsproto.events.RejectData(data=b"Internal Server Error") | ||
) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Don't be afraid here. I'll explain what's happening.
- We were sending
RejectConnection(status_code=500)
without a body, but onwebsockets
implementation we were sending the "Internal Server Error" body on the analogous behavior. TheRejectData
matches the behavior. - We are removing the conditional because it's never reached, and the reason for it is that we only call
send_500_response
is we didn't complete the handshake - which makes a lot of sense to remove it.
Hope it's clear.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Am I missing EndOfMessage
? I need to check this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, I'm not missing it. wsproto
internally adds the h11.EndOfMessage
. 🙏
if self.handshake_completed_event.is_set(): | ||
self.fail_connection(1012) | ||
else: | ||
self.send_500_response() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is the change that motivated this PR.
We were only sending 1006 to the client, even when the handshake was not completed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why self.fail_connection(1012)
instead of just calling self.close(1012)
?
I've been look at websockets
server.close() and procotol.close() to see why uvicorn isn't calling `close(), but haven't gotten terribly far yet.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Because close()
is async.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ahh, missed the lack of async here.
Looking deeper at the difference, it seems like the primary difference is fail_connection
will proactively cancel the data transfer task but close
will simply send the close frame and then wait.
Is there any consequence to that distinction here? My very, very basic testing seems to suggest with uvicorn tasks are given a chance to shutdown cleanly, but I'm still testing.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, this code still waits for it to finish. 👍
assert response is not None | ||
assert response.status_code == 500, response.text | ||
assert response.text == "Internal Server Error" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We are now testing what the client receives, instead of only the ASGI application.
task.cancel() | ||
assert response is not None | ||
assert response.status_code == 500, response.text | ||
assert response.text == "Internal Server Error" | ||
assert disconnect_message == {"type": "websocket.disconnect", "code": 1006} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm... On a second thought... This doesn't make sense, does it? Why are we even sending a websocket.disconnect
when the handshake was not even completed? 🤔
I think this was on purpose because then the application could receive a websocket
event, but thinking about it again, does it make sense?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I need to check the issues/PRs about this decision. It shouldn't be a blocker for this PR tho.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
According to django/asgiref#364 it's fine.
Can confirm that
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So PRs like this do highlight to me how awkward it is to try to follow the flow when using protocols rather than streams.
Anyways.
I agree. 🤷 |
Changes