Add websocket ping variables (#1906)
* Add config params for websocket ping_timeout & ping_interval * Include changelog * Pass websocket config values to WebSocketProtocol init, test * Linting * Improve docs Co-authored-by: shawnhill <shawn.hill@equipmentshare.com>
This commit is contained in:
parent
0072fd1573
commit
58e4087d4b
3
changelogs/1904.feature.rst
Normal file
3
changelogs/1904.feature.rst
Normal file
|
@ -0,0 +1,3 @@
|
|||
Adds WEBSOCKET_PING_TIMEOUT and WEBSOCKET_PING_INTERVAL configuration values
|
||||
|
||||
Allows setting the ping_interval and ping_timeout arguments when initializing `WebSocketCommonProtocol`.
|
|
@ -98,7 +98,7 @@ The config files are regular Python files which are executed in order to load th
|
|||
Builtin Configuration Values
|
||||
----------------------------
|
||||
|
||||
Out of the box there are just a few predefined values which can be overwritten when creating the application.
|
||||
Out of the box there are just a few predefined values which can be overwritten when creating the application. Note that websocket configuration values will have no impact if running in ASGI mode.
|
||||
|
||||
+---------------------------+-------------------+-----------------------------------------------------------------------------+
|
||||
| Variable | Default | Description |
|
||||
|
@ -123,6 +123,10 @@ Out of the box there are just a few predefined values which can be overwritten w
|
|||
+---------------------------+-------------------+-----------------------------------------------------------------------------+
|
||||
| WEBSOCKET_WRITE_LIMIT | 2^16 | High-water limit of the buffer for outgoing bytes |
|
||||
+---------------------------+-------------------+-----------------------------------------------------------------------------+
|
||||
| WEBSOCKET_PING_INTERVAL | 20 | A Ping frame is sent every ping_interval seconds. |
|
||||
+---------------------------+-------------------+-----------------------------------------------------------------------------+
|
||||
| WEBSOCKET_PING_TIMEOUT | 20 | Connection is closed when Pong is not received after ping_timeout seconds |
|
||||
+---------------------------+-------------------+-----------------------------------------------------------------------------+
|
||||
| GRACEFUL_SHUTDOWN_TIMEOUT | 15.0 | How long to wait to force close non-idle connection (sec) |
|
||||
+---------------------------+-------------------+-----------------------------------------------------------------------------+
|
||||
| ACCESS_LOG | True | Disable or enable access log |
|
||||
|
|
|
@ -51,5 +51,9 @@ You could setup your own WebSocket configuration through ``app.config``, like
|
|||
app.config.WEBSOCKET_MAX_QUEUE = 32
|
||||
app.config.WEBSOCKET_READ_LIMIT = 2 ** 16
|
||||
app.config.WEBSOCKET_WRITE_LIMIT = 2 ** 16
|
||||
app.config.WEBSOCKET_PING_INTERVAL = 20
|
||||
app.config.WEBSOCKET_PING_TIMEOUT = 20
|
||||
|
||||
These settings will have no impact if running in ASGI mode.
|
||||
|
||||
Find more in ``Configuration`` section.
|
||||
|
|
|
@ -24,6 +24,8 @@ DEFAULT_CONFIG = {
|
|||
"WEBSOCKET_MAX_QUEUE": 32,
|
||||
"WEBSOCKET_READ_LIMIT": 2 ** 16,
|
||||
"WEBSOCKET_WRITE_LIMIT": 2 ** 16,
|
||||
"WEBSOCKET_PING_TIMEOUT": 20,
|
||||
"WEBSOCKET_PING_INTERVAL": 20,
|
||||
"GRACEFUL_SHUTDOWN_TIMEOUT": 15.0, # 15 sec
|
||||
"ACCESS_LOG": True,
|
||||
"FORWARDED_SECRET": None,
|
||||
|
|
|
@ -14,11 +14,13 @@ from ipaddress import ip_address
|
|||
from signal import SIG_IGN, SIGINT, SIGTERM, Signals
|
||||
from signal import signal as signal_func
|
||||
from time import time
|
||||
from typing import Type
|
||||
|
||||
from httptools import HttpRequestParser # type: ignore
|
||||
from httptools.parser.errors import HttpParserError # type: ignore
|
||||
|
||||
from sanic.compat import Header, ctrlc_workaround_for_windows
|
||||
from sanic.config import Config
|
||||
from sanic.exceptions import (
|
||||
HeaderExpectationFailed,
|
||||
InvalidUsage,
|
||||
|
@ -844,6 +846,7 @@ def serve(
|
|||
app.asgi = False
|
||||
|
||||
connections = connections if connections is not None else set()
|
||||
protocol_kwargs = _build_protocol_kwargs(protocol, app.config)
|
||||
server = partial(
|
||||
protocol,
|
||||
loop=loop,
|
||||
|
@ -852,6 +855,7 @@ def serve(
|
|||
app=app,
|
||||
state=state,
|
||||
unix=unix,
|
||||
**protocol_kwargs,
|
||||
)
|
||||
asyncio_server_kwargs = (
|
||||
asyncio_server_kwargs if asyncio_server_kwargs else {}
|
||||
|
@ -948,6 +952,21 @@ def serve(
|
|||
remove_unix_socket(unix)
|
||||
|
||||
|
||||
def _build_protocol_kwargs(
|
||||
protocol: Type[HttpProtocol], config: Config
|
||||
) -> dict:
|
||||
if hasattr(protocol, "websocket_timeout"):
|
||||
return {
|
||||
"max_size": config.WEBSOCKET_MAX_SIZE,
|
||||
"max_queue": config.WEBSOCKET_MAX_QUEUE,
|
||||
"read_limit": config.WEBSOCKET_READ_LIMIT,
|
||||
"write_limit": config.WEBSOCKET_WRITE_LIMIT,
|
||||
"ping_timeout": config.WEBSOCKET_PING_TIMEOUT,
|
||||
"ping_interval": config.WEBSOCKET_PING_INTERVAL,
|
||||
}
|
||||
return {}
|
||||
|
||||
|
||||
def bind_socket(host: str, port: int, *, backlog=100) -> socket.socket:
|
||||
"""Create TCP server socket.
|
||||
:param host: IPv4, IPv6 or hostname may be specified
|
||||
|
|
|
@ -35,6 +35,8 @@ class WebSocketProtocol(HttpProtocol):
|
|||
websocket_max_queue=None,
|
||||
websocket_read_limit=2 ** 16,
|
||||
websocket_write_limit=2 ** 16,
|
||||
websocket_ping_interval=20,
|
||||
websocket_ping_timeout=20,
|
||||
**kwargs
|
||||
):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
@ -45,6 +47,8 @@ class WebSocketProtocol(HttpProtocol):
|
|||
self.websocket_max_queue = websocket_max_queue
|
||||
self.websocket_read_limit = websocket_read_limit
|
||||
self.websocket_write_limit = websocket_write_limit
|
||||
self.websocket_ping_interval = websocket_ping_interval
|
||||
self.websocket_ping_timeout = websocket_ping_timeout
|
||||
|
||||
# timeouts make no sense for websocket routes
|
||||
def request_timeout_callback(self):
|
||||
|
@ -119,6 +123,8 @@ class WebSocketProtocol(HttpProtocol):
|
|||
max_queue=self.websocket_max_queue,
|
||||
read_limit=self.websocket_read_limit,
|
||||
write_limit=self.websocket_write_limit,
|
||||
ping_interval=self.websocket_ping_interval,
|
||||
ping_timeout=self.websocket_ping_timeout,
|
||||
)
|
||||
# Following two lines are required for websockets 8.x
|
||||
self.websocket.is_client = False
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import asyncio
|
||||
import logging
|
||||
import sys
|
||||
from unittest.mock import patch
|
||||
|
||||
from inspect import isawaitable
|
||||
|
||||
|
@ -148,6 +149,35 @@ def test_app_enable_websocket(app, websocket_enabled, enable):
|
|||
assert app.websocket_enabled == True
|
||||
|
||||
|
||||
@patch("sanic.app.WebSocketProtocol")
|
||||
def test_app_websocket_parameters(websocket_protocol_mock, app):
|
||||
app.config.WEBSOCKET_MAX_SIZE = 44
|
||||
app.config.WEBSOCKET_MAX_QUEUE = 45
|
||||
app.config.WEBSOCKET_READ_LIMIT = 46
|
||||
app.config.WEBSOCKET_WRITE_LIMIT = 47
|
||||
app.config.WEBSOCKET_PING_TIMEOUT = 48
|
||||
app.config.WEBSOCKET_PING_INTERVAL = 50
|
||||
|
||||
@app.websocket("/ws")
|
||||
async def handler(request, ws):
|
||||
await ws.send("test")
|
||||
|
||||
try:
|
||||
# This will fail because WebSocketProtocol is mocked and only the call kwargs matter
|
||||
app.test_client.get("/ws")
|
||||
except:
|
||||
pass
|
||||
|
||||
websocket_protocol_call_args = websocket_protocol_mock.call_args
|
||||
ws_kwargs = websocket_protocol_call_args[1]
|
||||
assert ws_kwargs["max_size"] == app.config.WEBSOCKET_MAX_SIZE
|
||||
assert ws_kwargs["max_queue"] == app.config.WEBSOCKET_MAX_QUEUE
|
||||
assert ws_kwargs["read_limit"] == app.config.WEBSOCKET_READ_LIMIT
|
||||
assert ws_kwargs["write_limit"] == app.config.WEBSOCKET_WRITE_LIMIT
|
||||
assert ws_kwargs["ping_timeout"] == app.config.WEBSOCKET_PING_TIMEOUT
|
||||
assert ws_kwargs["ping_interval"] == app.config.WEBSOCKET_PING_INTERVAL
|
||||
|
||||
|
||||
def test_handle_request_with_nested_exception(app, monkeypatch):
|
||||
|
||||
err_msg = "Mock Exception"
|
||||
|
|
Loading…
Reference in New Issue
Block a user