forked from sanic-org/sanic
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* First attempt at new Websockets implementation based on websockets >= 9.0, with sans-i/o features. Requires more work. * Update sanic/websocket.py Co-authored-by: Adam Hopkins <[email protected]> * Update sanic/websocket.py Co-authored-by: Adam Hopkins <[email protected]> * Update sanic/websocket.py Co-authored-by: Adam Hopkins <[email protected]> * wip, update websockets code to new Sans/IO API * Refactored new websockets impl into own modules Incorporated other suggestions made by team * Another round of work on the new websockets impl * Added websocket_timeout support (matching previous/legacy support) * Lots more comments * Incorporated suggested changes from previous round of review * Changed RuntimeError usage to ServerError * Changed SanicException usage to ServerError * Removed some redundant asserts * Change remaining asserts to ServerErrors * Fixed some timeout handling issues * Fixed websocket.close() handling, and made it more robust * Made auto_close task smarter and more error-resilient * Made fail_connection routine smarter and more error-resilient * Further new websockets impl fixes * Update compatibility with Websockets v10 * Track server connection state in a more precise way * Try to handle the shutdown process more gracefully * Add a new end_connection() helper, to use as an alterative to close() or fail_connection() * Kill the auto-close task and keepalive-timeout task when sanic is shutdown * Deprecate WEBSOCKET_READ_LIMIT and WEBSOCKET_WRITE_LIMIT configs, they are not used in this implementation. * Change a warning message to debug level Remove default values for deprecated websocket parameters * Fix flake8 errors * Fix a couple of missed failing tests * remove websocket bench from examples * Integrate suggestions from code reviews Use Optional[T] instead of union[T,None] Fix mypy type logic errors change "is not None" to truthy checks where appropriate change "is None" to falsy checks were appropriate Add more debug logging when debug mode is on Change to using sanic.logger for debug logging rather than error_logger. * Fix long line lengths of debug messages Add some new debug messages when websocket IO is paused and unpaused for flow control Fix websocket example to use app.static() * remove unused import in websocket example app * re-run isort after Flake8 fixes Co-authored-by: Adam Hopkins <[email protected]> Co-authored-by: Adam Hopkins <[email protected]>
- Loading branch information
1 parent
4e746cb
commit 982d8c2
Showing
20 changed files
with
1,376 additions
and
269 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,165 @@ | ||
from typing import TYPE_CHECKING, Optional, Sequence | ||
|
||
from websockets.connection import CLOSED, CLOSING, OPEN | ||
from websockets.server import ServerConnection | ||
|
||
from sanic.exceptions import ServerError | ||
from sanic.log import error_logger | ||
from sanic.server import HttpProtocol | ||
|
||
from ..websockets.impl import WebsocketImplProtocol | ||
|
||
|
||
if TYPE_CHECKING: | ||
from websockets import http11 | ||
|
||
|
||
class WebSocketProtocol(HttpProtocol): | ||
|
||
websocket: Optional[WebsocketImplProtocol] | ||
websocket_timeout: float | ||
websocket_max_size = Optional[int] | ||
websocket_ping_interval = Optional[float] | ||
websocket_ping_timeout = Optional[float] | ||
|
||
def __init__( | ||
self, | ||
*args, | ||
websocket_timeout: float = 10.0, | ||
websocket_max_size: Optional[int] = None, | ||
websocket_max_queue: Optional[int] = None, # max_queue is deprecated | ||
websocket_read_limit: Optional[int] = None, # read_limit is deprecated | ||
websocket_write_limit: Optional[int] = None, # write_limit deprecated | ||
websocket_ping_interval: Optional[float] = 20.0, | ||
websocket_ping_timeout: Optional[float] = 20.0, | ||
**kwargs, | ||
): | ||
super().__init__(*args, **kwargs) | ||
self.websocket = None | ||
self.websocket_timeout = websocket_timeout | ||
self.websocket_max_size = websocket_max_size | ||
if websocket_max_queue is not None and websocket_max_queue > 0: | ||
# TODO: Reminder remove this warning in v22.3 | ||
error_logger.warning( | ||
DeprecationWarning( | ||
"Websocket no longer uses queueing, so websocket_max_queue" | ||
" is no longer required." | ||
) | ||
) | ||
if websocket_read_limit is not None and websocket_read_limit > 0: | ||
# TODO: Reminder remove this warning in v22.3 | ||
error_logger.warning( | ||
DeprecationWarning( | ||
"Websocket no longer uses read buffers, so " | ||
"websocket_read_limit is not required." | ||
) | ||
) | ||
if websocket_write_limit is not None and websocket_write_limit > 0: | ||
# TODO: Reminder remove this warning in v22.3 | ||
error_logger.warning( | ||
DeprecationWarning( | ||
"Websocket no longer uses write buffers, so " | ||
"websocket_write_limit is not required." | ||
) | ||
) | ||
self.websocket_ping_interval = websocket_ping_interval | ||
self.websocket_ping_timeout = websocket_ping_timeout | ||
|
||
def connection_lost(self, exc): | ||
if self.websocket is not None: | ||
self.websocket.connection_lost(exc) | ||
super().connection_lost(exc) | ||
|
||
def data_received(self, data): | ||
if self.websocket is not None: | ||
self.websocket.data_received(data) | ||
else: | ||
# Pass it to HttpProtocol handler first | ||
# That will (hopefully) upgrade it to a websocket. | ||
super().data_received(data) | ||
|
||
def eof_received(self) -> Optional[bool]: | ||
if self.websocket is not None: | ||
return self.websocket.eof_received() | ||
else: | ||
return False | ||
|
||
def close(self, timeout: Optional[float] = None): | ||
# Called by HttpProtocol at the end of connection_task | ||
# If we've upgraded to websocket, we do our own closing | ||
if self.websocket is not None: | ||
# Note, we don't want to use websocket.close() | ||
# That is used for user's application code to send a | ||
# websocket close packet. This is different. | ||
self.websocket.end_connection(1001) | ||
else: | ||
super().close() | ||
|
||
def close_if_idle(self): | ||
# Called by Sanic Server when shutting down | ||
# If we've upgraded to websocket, shut it down | ||
if self.websocket is not None: | ||
if self.websocket.connection.state in (CLOSING, CLOSED): | ||
return True | ||
elif self.websocket.loop is not None: | ||
self.websocket.loop.create_task(self.websocket.close(1001)) | ||
else: | ||
self.websocket.end_connection(1001) | ||
else: | ||
return super().close_if_idle() | ||
|
||
async def websocket_handshake( | ||
self, request, subprotocols=Optional[Sequence[str]] | ||
): | ||
# let the websockets package do the handshake with the client | ||
try: | ||
if subprotocols is not None: | ||
# subprotocols can be a set or frozenset, | ||
# but ServerConnection needs a list | ||
subprotocols = list(subprotocols) | ||
ws_conn = ServerConnection( | ||
max_size=self.websocket_max_size, | ||
subprotocols=subprotocols, | ||
state=OPEN, | ||
logger=error_logger, | ||
) | ||
resp: "http11.Response" = ws_conn.accept(request) | ||
except Exception: | ||
msg = ( | ||
"Failed to open a WebSocket connection.\n" | ||
"See server log for more information.\n" | ||
) | ||
raise ServerError(msg, status_code=500) | ||
if 100 <= resp.status_code <= 299: | ||
rbody = "".join( | ||
[ | ||
"HTTP/1.1 ", | ||
str(resp.status_code), | ||
" ", | ||
resp.reason_phrase, | ||
"\r\n", | ||
] | ||
) | ||
rbody += "".join(f"{k}: {v}\r\n" for k, v in resp.headers.items()) | ||
if resp.body is not None: | ||
rbody += f"\r\n{resp.body}\r\n\r\n" | ||
else: | ||
rbody += "\r\n" | ||
await super().send(rbody.encode()) | ||
else: | ||
raise ServerError(resp.body, resp.status_code) | ||
|
||
self.websocket = WebsocketImplProtocol( | ||
ws_conn, | ||
ping_interval=self.websocket_ping_interval, | ||
ping_timeout=self.websocket_ping_timeout, | ||
close_timeout=self.websocket_timeout, | ||
) | ||
loop = ( | ||
request.transport.loop | ||
if hasattr(request, "transport") | ||
and hasattr(request.transport, "loop") | ||
else None | ||
) | ||
await self.websocket.connection_made(self, loop=loop) | ||
return self.websocket |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Empty file.
Oops, something went wrong.