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

Add websocket ping variables #1906

Merged
merged 5 commits into from
Aug 7, 2020
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions changelogs/1904.feature.rst
Original file line number Diff line number Diff line change
@@ -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`.
4 changes: 4 additions & 0 deletions docs/sanic/config.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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 |
Expand Down
1 change: 1 addition & 0 deletions docs/sanic/websocket.rst
Original file line number Diff line number Diff line change
Expand Up @@ -51,5 +51,6 @@ 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

shawnhill marked this conversation as resolved.
Show resolved Hide resolved
Find more in ``Configuration`` section.
2 changes: 2 additions & 0 deletions sanic/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@
"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,
Expand Down
19 changes: 19 additions & 0 deletions sanic/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,13 @@
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,
Expand Down Expand Up @@ -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,
Expand All @@ -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 {}
Expand Down Expand Up @@ -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
Expand Down
6 changes: 6 additions & 0 deletions sanic/websocket.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ def __init__(
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)
Expand All @@ -45,6 +47,8 @@ def __init__(
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):
Expand Down Expand Up @@ -119,6 +123,8 @@ async def websocket_handshake(self, request, subprotocols=None):
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
Expand Down
30 changes: 30 additions & 0 deletions tests/test_app.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import asyncio
import logging
import sys
from unittest.mock import patch

from inspect import isawaitable

Expand Down Expand Up @@ -148,6 +149,35 @@ async def handler(request, ws):
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"
Expand Down