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
|
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 |
|
| 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_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) |
|
| GRACEFUL_SHUTDOWN_TIMEOUT | 15.0 | How long to wait to force close non-idle connection (sec) |
|
||||||
+---------------------------+-------------------+-----------------------------------------------------------------------------+
|
+---------------------------+-------------------+-----------------------------------------------------------------------------+
|
||||||
| ACCESS_LOG | True | Disable or enable access log |
|
| 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_MAX_QUEUE = 32
|
||||||
app.config.WEBSOCKET_READ_LIMIT = 2 ** 16
|
app.config.WEBSOCKET_READ_LIMIT = 2 ** 16
|
||||||
app.config.WEBSOCKET_WRITE_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.
|
Find more in ``Configuration`` section.
|
||||||
|
|
|
@ -24,6 +24,8 @@ DEFAULT_CONFIG = {
|
||||||
"WEBSOCKET_MAX_QUEUE": 32,
|
"WEBSOCKET_MAX_QUEUE": 32,
|
||||||
"WEBSOCKET_READ_LIMIT": 2 ** 16,
|
"WEBSOCKET_READ_LIMIT": 2 ** 16,
|
||||||
"WEBSOCKET_WRITE_LIMIT": 2 ** 16,
|
"WEBSOCKET_WRITE_LIMIT": 2 ** 16,
|
||||||
|
"WEBSOCKET_PING_TIMEOUT": 20,
|
||||||
|
"WEBSOCKET_PING_INTERVAL": 20,
|
||||||
"GRACEFUL_SHUTDOWN_TIMEOUT": 15.0, # 15 sec
|
"GRACEFUL_SHUTDOWN_TIMEOUT": 15.0, # 15 sec
|
||||||
"ACCESS_LOG": True,
|
"ACCESS_LOG": True,
|
||||||
"FORWARDED_SECRET": None,
|
"FORWARDED_SECRET": None,
|
||||||
|
|
|
@ -14,11 +14,13 @@ from ipaddress import ip_address
|
||||||
from signal import SIG_IGN, SIGINT, SIGTERM, Signals
|
from signal import SIG_IGN, SIGINT, SIGTERM, Signals
|
||||||
from signal import signal as signal_func
|
from signal import signal as signal_func
|
||||||
from time import time
|
from time import time
|
||||||
|
from typing import Type
|
||||||
|
|
||||||
from httptools import HttpRequestParser # type: ignore
|
from httptools import HttpRequestParser # type: ignore
|
||||||
from httptools.parser.errors import HttpParserError # type: ignore
|
from httptools.parser.errors import HttpParserError # type: ignore
|
||||||
|
|
||||||
from sanic.compat import Header, ctrlc_workaround_for_windows
|
from sanic.compat import Header, ctrlc_workaround_for_windows
|
||||||
|
from sanic.config import Config
|
||||||
from sanic.exceptions import (
|
from sanic.exceptions import (
|
||||||
HeaderExpectationFailed,
|
HeaderExpectationFailed,
|
||||||
InvalidUsage,
|
InvalidUsage,
|
||||||
|
@ -844,6 +846,7 @@ def serve(
|
||||||
app.asgi = False
|
app.asgi = False
|
||||||
|
|
||||||
connections = connections if connections is not None else set()
|
connections = connections if connections is not None else set()
|
||||||
|
protocol_kwargs = _build_protocol_kwargs(protocol, app.config)
|
||||||
server = partial(
|
server = partial(
|
||||||
protocol,
|
protocol,
|
||||||
loop=loop,
|
loop=loop,
|
||||||
|
@ -852,6 +855,7 @@ def serve(
|
||||||
app=app,
|
app=app,
|
||||||
state=state,
|
state=state,
|
||||||
unix=unix,
|
unix=unix,
|
||||||
|
**protocol_kwargs,
|
||||||
)
|
)
|
||||||
asyncio_server_kwargs = (
|
asyncio_server_kwargs = (
|
||||||
asyncio_server_kwargs if asyncio_server_kwargs else {}
|
asyncio_server_kwargs if asyncio_server_kwargs else {}
|
||||||
|
@ -948,6 +952,21 @@ def serve(
|
||||||
remove_unix_socket(unix)
|
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:
|
def bind_socket(host: str, port: int, *, backlog=100) -> socket.socket:
|
||||||
"""Create TCP server socket.
|
"""Create TCP server socket.
|
||||||
:param host: IPv4, IPv6 or hostname may be specified
|
:param host: IPv4, IPv6 or hostname may be specified
|
||||||
|
|
|
@ -35,6 +35,8 @@ class WebSocketProtocol(HttpProtocol):
|
||||||
websocket_max_queue=None,
|
websocket_max_queue=None,
|
||||||
websocket_read_limit=2 ** 16,
|
websocket_read_limit=2 ** 16,
|
||||||
websocket_write_limit=2 ** 16,
|
websocket_write_limit=2 ** 16,
|
||||||
|
websocket_ping_interval=20,
|
||||||
|
websocket_ping_timeout=20,
|
||||||
**kwargs
|
**kwargs
|
||||||
):
|
):
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
|
@ -45,6 +47,8 @@ class WebSocketProtocol(HttpProtocol):
|
||||||
self.websocket_max_queue = websocket_max_queue
|
self.websocket_max_queue = websocket_max_queue
|
||||||
self.websocket_read_limit = websocket_read_limit
|
self.websocket_read_limit = websocket_read_limit
|
||||||
self.websocket_write_limit = websocket_write_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
|
# timeouts make no sense for websocket routes
|
||||||
def request_timeout_callback(self):
|
def request_timeout_callback(self):
|
||||||
|
@ -119,6 +123,8 @@ class WebSocketProtocol(HttpProtocol):
|
||||||
max_queue=self.websocket_max_queue,
|
max_queue=self.websocket_max_queue,
|
||||||
read_limit=self.websocket_read_limit,
|
read_limit=self.websocket_read_limit,
|
||||||
write_limit=self.websocket_write_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
|
# Following two lines are required for websockets 8.x
|
||||||
self.websocket.is_client = False
|
self.websocket.is_client = False
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import asyncio
|
import asyncio
|
||||||
import logging
|
import logging
|
||||||
import sys
|
import sys
|
||||||
|
from unittest.mock import patch
|
||||||
|
|
||||||
from inspect import isawaitable
|
from inspect import isawaitable
|
||||||
|
|
||||||
|
@ -148,6 +149,35 @@ def test_app_enable_websocket(app, websocket_enabled, enable):
|
||||||
assert app.websocket_enabled == True
|
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):
|
def test_handle_request_with_nested_exception(app, monkeypatch):
|
||||||
|
|
||||||
err_msg = "Mock Exception"
|
err_msg = "Mock Exception"
|
||||||
|
|
Loading…
Reference in New Issue
Block a user