From d8a87a5538aab0bf5b253b7ab65372ad19761149 Mon Sep 17 00:00:00 2001 From: Irfanuddin Shafi Ahmed Date: Fri, 15 Jul 2022 07:50:11 +0530 Subject: [PATCH 01/58] added logic to accept default headers in websocket --- .../protocols/websockets/websockets_impl.py | 21 ++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/uvicorn/protocols/websockets/websockets_impl.py b/uvicorn/protocols/websockets/websockets_impl.py index eea7a65bf..5638a4d34 100644 --- a/uvicorn/protocols/websockets/websockets_impl.py +++ b/uvicorn/protocols/websockets/websockets_impl.py @@ -260,13 +260,20 @@ async def asgi_send(self, message: "ASGISendEvent") -> None: self.accepted_subprotocol = cast( Optional[Subprotocol], message.get("subprotocol") ) - if "headers" in message: - self.extra_headers.extend( - # ASGI spec requires bytes - # But for compatibility we need to convert it to strings - (name.decode("latin-1"), value.decode("latin-1")) - for name, value in message["headers"] - ) + headers = list(message.get("headers", [])) + self.default_headers + _added_names = [] + for name, value in headers: + if name.lower() in _added_names: + continue + _added_names.append(name.lower()) + self.extra_headers.append((name.decode("latin-1"), value.decode("latin-1"))) +# if "headers" in message: +# self.extra_headers.extend( +# # ASGI spec requires bytes +# # But for compatibility we need to convert it to strings +# (name.decode("latin-1"), value.decode("latin-1")) +# for name, value in message["headers"] +# ) self.handshake_started_event.set() elif message_type == "websocket.close": From 170f16abbce2fae3d75afdf5711e3bbeae7de294 Mon Sep 17 00:00:00 2001 From: Irfanuddin Shafi Ahmed Date: Fri, 15 Jul 2022 07:59:59 +0530 Subject: [PATCH 02/58] added default_headers to class init --- uvicorn/protocols/websockets/websockets_impl.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/uvicorn/protocols/websockets/websockets_impl.py b/uvicorn/protocols/websockets/websockets_impl.py index 5638a4d34..b23efc305 100644 --- a/uvicorn/protocols/websockets/websockets_impl.py +++ b/uvicorn/protocols/websockets/websockets_impl.py @@ -69,6 +69,8 @@ def __init__( # Shared server state self.connections = server_state.connections self.tasks = server_state.tasks + self.default_headers = server_state.default_headers + # Connection state self.transport: asyncio.Transport = None # type: ignore[assignment] From bd28f391c858b615320e08388efacd32ceddb982 Mon Sep 17 00:00:00 2001 From: Irfanuddin Date: Fri, 15 Jul 2022 19:04:43 +0530 Subject: [PATCH 03/58] fix --- docs/settings.md | 3 +++ uvicorn/config.py | 2 ++ 2 files changed, 5 insertions(+) diff --git a/docs/settings.md b/docs/settings.md index 6205d9e7d..8ad9f9c19 100644 --- a/docs/settings.md +++ b/docs/settings.md @@ -67,6 +67,9 @@ For more nuanced control over which file modifications trigger reloads, install * `--lifespan ` - Set the Lifespan protocol implementation. **Options:** *'auto', 'on', 'off'.* **Default:** *'auto'*. * `--h11-max-incomplete-event-size ` - Set the maximum number of bytes to buffer of an incomplete event. Only available for `h11` HTTP protocol implementation. **Default:** *'16384'* (16 KB). +!!! note + Selecting `websockets` as Websocket protocol doesn't adhere to `--no-server-header` settings of Uvicorn. This is a limitation. + ## Application Interface * `--interface` - Select ASGI3, ASGI2, or WSGI as the application interface. diff --git a/uvicorn/config.py b/uvicorn/config.py index 3cb6bf3d8..41640f8c8 100644 --- a/uvicorn/config.py +++ b/uvicorn/config.py @@ -468,6 +468,8 @@ def load(self) -> None: self.http_protocol_class = self.http if isinstance(self.ws, str): + import websockets.http + websockets.http.USER_AGENT = "" ws_protocol_class = import_from_string(WS_PROTOCOLS[self.ws]) self.ws_protocol_class: Optional[Type[asyncio.Protocol]] = ws_protocol_class else: From 83a29e83d44065b9e4f70eddef86fa651bc4c431 Mon Sep 17 00:00:00 2001 From: Irfanuddin Date: Fri, 15 Jul 2022 19:05:50 +0530 Subject: [PATCH 04/58] docs: added limitation about Websockets --- docs/settings.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/settings.md b/docs/settings.md index 6205d9e7d..8ad9f9c19 100644 --- a/docs/settings.md +++ b/docs/settings.md @@ -67,6 +67,9 @@ For more nuanced control over which file modifications trigger reloads, install * `--lifespan ` - Set the Lifespan protocol implementation. **Options:** *'auto', 'on', 'off'.* **Default:** *'auto'*. * `--h11-max-incomplete-event-size ` - Set the maximum number of bytes to buffer of an incomplete event. Only available for `h11` HTTP protocol implementation. **Default:** *'16384'* (16 KB). +!!! note + Selecting `websockets` as Websocket protocol doesn't adhere to `--no-server-header` settings of Uvicorn. This is a limitation. + ## Application Interface * `--interface` - Select ASGI3, ASGI2, or WSGI as the application interface. From eabe3804fe7c2890a25161a0871650635517edf7 Mon Sep 17 00:00:00 2001 From: Irfanuddin Date: Fri, 15 Jul 2022 19:08:13 +0530 Subject: [PATCH 05/58] docs: added limitation about Websockets --- docs/settings.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/settings.md b/docs/settings.md index 8ad9f9c19..2490680c5 100644 --- a/docs/settings.md +++ b/docs/settings.md @@ -68,7 +68,7 @@ For more nuanced control over which file modifications trigger reloads, install * `--h11-max-incomplete-event-size ` - Set the maximum number of bytes to buffer of an incomplete event. Only available for `h11` HTTP protocol implementation. **Default:** *'16384'* (16 KB). !!! note - Selecting `websockets` as Websocket protocol doesn't adhere to `--no-server-header` settings of Uvicorn. This is a limitation. + On selecting `websockets` as Websocket protocol, the server headers are not disabled even if `--no-server-header` settings is passed. This is a limitation. ## Application Interface From 5ecfd076a8f473c8a4175790562ec4aa758e14a5 Mon Sep 17 00:00:00 2001 From: Irfanuddin Shafi Ahmed Date: Fri, 22 Jul 2022 12:06:12 +0530 Subject: [PATCH 06/58] Update docs/settings.md adopted change suggested. Co-authored-by: Marcelo Trylesinski --- docs/settings.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/settings.md b/docs/settings.md index 2490680c5..532b11455 100644 --- a/docs/settings.md +++ b/docs/settings.md @@ -68,7 +68,7 @@ For more nuanced control over which file modifications trigger reloads, install * `--h11-max-incomplete-event-size ` - Set the maximum number of bytes to buffer of an incomplete event. Only available for `h11` HTTP protocol implementation. **Default:** *'16384'* (16 KB). !!! note - On selecting `websockets` as Websocket protocol, the server headers are not disabled even if `--no-server-header` settings is passed. This is a limitation. + The `--no-server-header` flag doesn't have effect on the WebSockets implementations. ## Application Interface From d954ef192d0cb66744b741732eb7207a38ae73a0 Mon Sep 17 00:00:00 2001 From: Irfanuddin Date: Fri, 22 Jul 2022 14:08:10 +0530 Subject: [PATCH 07/58] docs: moved websocket limitation as per suggestion, moved the limitation to --no-server-header area --- docs/settings.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/settings.md b/docs/settings.md index 532b11455..cc14a9857 100644 --- a/docs/settings.md +++ b/docs/settings.md @@ -67,8 +67,6 @@ For more nuanced control over which file modifications trigger reloads, install * `--lifespan ` - Set the Lifespan protocol implementation. **Options:** *'auto', 'on', 'off'.* **Default:** *'auto'*. * `--h11-max-incomplete-event-size ` - Set the maximum number of bytes to buffer of an incomplete event. Only available for `h11` HTTP protocol implementation. **Default:** *'16384'* (16 KB). -!!! note - The `--no-server-header` flag doesn't have effect on the WebSockets implementations. ## Application Interface @@ -85,6 +83,9 @@ connecting IPs in the `forwarded-allow-ips` configuration. * `--server-header` / `--no-server-header` - Enable/Disable default `Server` header. * `--date-header` / `--no-date-header` - Enable/Disable default `Date` header. +!!! note + The `--no-server-header` flag doesn't have effect on the WebSockets implementations. + ## HTTPS * `--ssl-keyfile ` - SSL key file From c7546bde5859c41b6325a09ef71b31df23ed315b Mon Sep 17 00:00:00 2001 From: Marcelo Trylesinski Date: Fri, 22 Jul 2022 11:54:53 +0200 Subject: [PATCH 08/58] Update docs/settings.md --- docs/settings.md | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/settings.md b/docs/settings.md index cc14a9857..09a3db5d5 100644 --- a/docs/settings.md +++ b/docs/settings.md @@ -67,7 +67,6 @@ For more nuanced control over which file modifications trigger reloads, install * `--lifespan ` - Set the Lifespan protocol implementation. **Options:** *'auto', 'on', 'off'.* **Default:** *'auto'*. * `--h11-max-incomplete-event-size ` - Set the maximum number of bytes to buffer of an incomplete event. Only available for `h11` HTTP protocol implementation. **Default:** *'16384'* (16 KB). - ## Application Interface * `--interface` - Select ASGI3, ASGI2, or WSGI as the application interface. From f7fc7721980909947051755ad431aa7cdf292906 Mon Sep 17 00:00:00 2001 From: Irfanuddin Date: Mon, 25 Jul 2022 15:40:14 +0530 Subject: [PATCH 09/58] feat(websockets): added server header support for websockets Added logic to add server header support for websockets and also to pass default headers --- uvicorn/protocols/websockets/websockets_impl.py | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/uvicorn/protocols/websockets/websockets_impl.py b/uvicorn/protocols/websockets/websockets_impl.py index b23efc305..cf7a8eb52 100644 --- a/uvicorn/protocols/websockets/websockets_impl.py +++ b/uvicorn/protocols/websockets/websockets_impl.py @@ -268,14 +268,11 @@ async def asgi_send(self, message: "ASGISendEvent") -> None: if name.lower() in _added_names: continue _added_names.append(name.lower()) - self.extra_headers.append((name.decode("latin-1"), value.decode("latin-1"))) -# if "headers" in message: -# self.extra_headers.extend( -# # ASGI spec requires bytes -# # But for compatibility we need to convert it to strings -# (name.decode("latin-1"), value.decode("latin-1")) -# for name, value in message["headers"] -# ) + self.extra_headers.append(( + # ASGI spec requires bytes + # But for compatibility we need to convert it to strings + name.decode("latin-1"), value.decode("latin-1")) + ) self.handshake_started_event.set() elif message_type == "websocket.close": From 62c6c7e474b75c83a4e7cca65c941159efa952b7 Mon Sep 17 00:00:00 2001 From: Irfanuddin Date: Mon, 25 Jul 2022 15:52:39 +0530 Subject: [PATCH 10/58] feat(websockets-wsproto): added server header support for websockets Added logic to add server header support for websockets and also to pass default headers --- uvicorn/protocols/websockets/wsproto_impl.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/uvicorn/protocols/websockets/wsproto_impl.py b/uvicorn/protocols/websockets/wsproto_impl.py index eb8d860b3..7031edf9d 100644 --- a/uvicorn/protocols/websockets/wsproto_impl.py +++ b/uvicorn/protocols/websockets/wsproto_impl.py @@ -27,6 +27,8 @@ def __init__(self, config, server_state, _loop=None): # Shared server state self.connections = server_state.connections self.tasks = server_state.tasks + self.default_headers = server_state.default_headers + # Connection state self.transport = None @@ -133,6 +135,7 @@ def on_task_complete(self, task): def handle_connect(self, event): self.connect_event = event headers = [(b"host", event.host.encode())] + headers.extend(self.default_headers) headers += [(key.lower(), value) for key, value in event.extra_headers] raw_path, _, query_string = event.target.partition("?") self.scope = { From 49284845ad9e15cb0230e68cadf42bab5ecd1f7d Mon Sep 17 00:00:00 2001 From: Irfanuddin Shafi Ahmed Date: Mon, 25 Jul 2022 15:53:40 +0530 Subject: [PATCH 11/58] added logic to accept default headers in websocket (#1) * added logic to accept default headers in websocket * added default_headers to class init * fix * feat(websockets): added server header support for websockets Added logic to add server header support for websockets and also to pass default headers * feat(websockets-wsproto): added server header support for websockets Added logic to add server header support for websockets and also to pass default headers Co-authored-by: Irfanuddin --- docs/settings.md | 3 +++ uvicorn/config.py | 2 ++ uvicorn/protocols/websockets/websockets_impl.py | 14 ++++++++++---- uvicorn/protocols/websockets/wsproto_impl.py | 3 +++ 4 files changed, 18 insertions(+), 4 deletions(-) diff --git a/docs/settings.md b/docs/settings.md index 09a3db5d5..0e17b0504 100644 --- a/docs/settings.md +++ b/docs/settings.md @@ -67,6 +67,9 @@ For more nuanced control over which file modifications trigger reloads, install * `--lifespan ` - Set the Lifespan protocol implementation. **Options:** *'auto', 'on', 'off'.* **Default:** *'auto'*. * `--h11-max-incomplete-event-size ` - Set the maximum number of bytes to buffer of an incomplete event. Only available for `h11` HTTP protocol implementation. **Default:** *'16384'* (16 KB). +!!! note + Selecting `websockets` as Websocket protocol doesn't adhere to `--no-server-header` settings of Uvicorn. This is a limitation. + ## Application Interface * `--interface` - Select ASGI3, ASGI2, or WSGI as the application interface. diff --git a/uvicorn/config.py b/uvicorn/config.py index 3cb6bf3d8..41640f8c8 100644 --- a/uvicorn/config.py +++ b/uvicorn/config.py @@ -468,6 +468,8 @@ def load(self) -> None: self.http_protocol_class = self.http if isinstance(self.ws, str): + import websockets.http + websockets.http.USER_AGENT = "" ws_protocol_class = import_from_string(WS_PROTOCOLS[self.ws]) self.ws_protocol_class: Optional[Type[asyncio.Protocol]] = ws_protocol_class else: diff --git a/uvicorn/protocols/websockets/websockets_impl.py b/uvicorn/protocols/websockets/websockets_impl.py index eea7a65bf..cf7a8eb52 100644 --- a/uvicorn/protocols/websockets/websockets_impl.py +++ b/uvicorn/protocols/websockets/websockets_impl.py @@ -69,6 +69,8 @@ def __init__( # Shared server state self.connections = server_state.connections self.tasks = server_state.tasks + self.default_headers = server_state.default_headers + # Connection state self.transport: asyncio.Transport = None # type: ignore[assignment] @@ -260,12 +262,16 @@ async def asgi_send(self, message: "ASGISendEvent") -> None: self.accepted_subprotocol = cast( Optional[Subprotocol], message.get("subprotocol") ) - if "headers" in message: - self.extra_headers.extend( + headers = list(message.get("headers", [])) + self.default_headers + _added_names = [] + for name, value in headers: + if name.lower() in _added_names: + continue + _added_names.append(name.lower()) + self.extra_headers.append(( # ASGI spec requires bytes # But for compatibility we need to convert it to strings - (name.decode("latin-1"), value.decode("latin-1")) - for name, value in message["headers"] + name.decode("latin-1"), value.decode("latin-1")) ) self.handshake_started_event.set() diff --git a/uvicorn/protocols/websockets/wsproto_impl.py b/uvicorn/protocols/websockets/wsproto_impl.py index eb8d860b3..7031edf9d 100644 --- a/uvicorn/protocols/websockets/wsproto_impl.py +++ b/uvicorn/protocols/websockets/wsproto_impl.py @@ -27,6 +27,8 @@ def __init__(self, config, server_state, _loop=None): # Shared server state self.connections = server_state.connections self.tasks = server_state.tasks + self.default_headers = server_state.default_headers + # Connection state self.transport = None @@ -133,6 +135,7 @@ def on_task_complete(self, task): def handle_connect(self, event): self.connect_event = event headers = [(b"host", event.host.encode())] + headers.extend(self.default_headers) headers += [(key.lower(), value) for key, value in event.extra_headers] raw_path, _, query_string = event.target.partition("?") self.scope = { From d4c55c75acefbe1946ad21c0396d21a6eeaf1c2d Mon Sep 17 00:00:00 2001 From: Irfanuddin Date: Mon, 25 Jul 2022 15:59:46 +0530 Subject: [PATCH 12/58] style(websockets): formatting --- .../protocols/websockets/websockets_impl.py | 22 ++++++++----------- uvicorn/protocols/websockets/wsproto_impl.py | 1 - 2 files changed, 9 insertions(+), 14 deletions(-) diff --git a/uvicorn/protocols/websockets/websockets_impl.py b/uvicorn/protocols/websockets/websockets_impl.py index cf7a8eb52..15948f83f 100644 --- a/uvicorn/protocols/websockets/websockets_impl.py +++ b/uvicorn/protocols/websockets/websockets_impl.py @@ -26,13 +26,10 @@ if TYPE_CHECKING: from asgiref.typing import ( ASGISendEvent, - WebSocketAcceptEvent, - WebSocketCloseEvent, WebSocketConnectEvent, WebSocketDisconnectEvent, WebSocketReceiveEvent, WebSocketScope, - WebSocketSendEvent, ) @@ -53,10 +50,10 @@ class WebSocketProtocol(WebSocketServerProtocol): extra_headers: List[Tuple[str, str]] def __init__( - self, - config: Config, - server_state: ServerState, - _loop: Optional[asyncio.AbstractEventLoop] = None, + self, + config: Config, + server_state: ServerState, + _loop: Optional[asyncio.AbstractEventLoop] = None, ): if not config.loaded: config.load() @@ -71,7 +68,6 @@ def __init__( self.tasks = server_state.tasks self.default_headers = server_state.default_headers - # Connection state self.transport: asyncio.Transport = None # type: ignore[assignment] self.server: Optional[Tuple[str, int]] = None @@ -106,7 +102,7 @@ def __init__( ) def connection_made( # type: ignore[override] - self, transport: asyncio.Transport + self, transport: asyncio.Transport ) -> None: self.connections.add(self) self.transport = transport @@ -140,7 +136,7 @@ def on_task_complete(self, task: asyncio.Task) -> None: self.tasks.discard(task) async def process_request( - self, path: str, headers: Headers + self, path: str, headers: Headers ) -> Optional[HTTPResponse]: """ This hook is called to determine if the websocket should return @@ -184,7 +180,7 @@ async def process_request( return self.initial_response def process_subprotocol( - self, headers: Headers, available_subprotocols: Optional[Sequence[Subprotocol]] + self, headers: Headers, available_subprotocols: Optional[Sequence[Subprotocol]] ) -> Optional[Subprotocol]: """ We override the standard 'process_subprotocol' behavior here so that @@ -208,7 +204,7 @@ def send_500_response(self) -> None: self.handshake_started_event.set() async def ws_handler( # type: ignore[override] - self, protocol: WebSocketServerProtocol, path: str + self, protocol: WebSocketServerProtocol, path: str ) -> Any: """ This is the main handler function for the 'websockets' implementation @@ -322,7 +318,7 @@ async def asgi_send(self, message: "ASGISendEvent") -> None: raise RuntimeError(msg % message_type) async def asgi_receive( - self, + self, ) -> Union[ "WebSocketDisconnectEvent", "WebSocketConnectEvent", "WebSocketReceiveEvent" ]: diff --git a/uvicorn/protocols/websockets/wsproto_impl.py b/uvicorn/protocols/websockets/wsproto_impl.py index 7031edf9d..1a0a23c45 100644 --- a/uvicorn/protocols/websockets/wsproto_impl.py +++ b/uvicorn/protocols/websockets/wsproto_impl.py @@ -29,7 +29,6 @@ def __init__(self, config, server_state, _loop=None): self.tasks = server_state.tasks self.default_headers = server_state.default_headers - # Connection state self.transport = None self.server = None From 4c860f75325a1b59273ded8f68f0c8775c728da9 Mon Sep 17 00:00:00 2001 From: Irfanuddin Date: Mon, 25 Jul 2022 16:06:15 +0530 Subject: [PATCH 13/58] style(websockets): formatting --- uvicorn/protocols/websockets/websockets_impl.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/uvicorn/protocols/websockets/websockets_impl.py b/uvicorn/protocols/websockets/websockets_impl.py index 43da8d9a4..3207e19b2 100644 --- a/uvicorn/protocols/websockets/websockets_impl.py +++ b/uvicorn/protocols/websockets/websockets_impl.py @@ -71,7 +71,6 @@ def __init__( self.tasks = server_state.tasks self.default_headers = server_state.default_headers - # Connection state self.transport: asyncio.Transport = None # type: ignore[assignment] self.server: Optional[Tuple[str, int]] = None @@ -208,7 +207,7 @@ def send_500_response(self) -> None: self.handshake_started_event.set() async def ws_handler( # type: ignore[override] - self, protocol: WebSocketServerProtocol, path: str + self, protocol: WebSocketServerProtocol, path: str ) -> Any: """ This is the main handler function for the 'websockets' implementation @@ -268,10 +267,13 @@ async def asgi_send(self, message: "ASGISendEvent") -> None: if name.lower() in _added_names: continue _added_names.append(name.lower()) - self.extra_headers.append(( - # ASGI spec requires bytes - # But for compatibility we need to convert it to strings - name.decode("latin-1"), value.decode("latin-1")) + self.extra_headers.append( + ( + # ASGI spec requires bytes + # But for compatibility we need to convert it to strings + name.decode("latin-1"), + value.decode("latin-1"), + ) ) self.handshake_started_event.set() From 728b4b9ebf0c174394adb2f84ab379ecfdb29c32 Mon Sep 17 00:00:00 2001 From: Irfanuddin Date: Mon, 25 Jul 2022 16:08:13 +0530 Subject: [PATCH 14/58] style(websockets): formatting --- uvicorn/config.py | 1 + 1 file changed, 1 insertion(+) diff --git a/uvicorn/config.py b/uvicorn/config.py index 41640f8c8..e71212674 100644 --- a/uvicorn/config.py +++ b/uvicorn/config.py @@ -469,6 +469,7 @@ def load(self) -> None: if isinstance(self.ws, str): import websockets.http + websockets.http.USER_AGENT = "" ws_protocol_class = import_from_string(WS_PROTOCOLS[self.ws]) self.ws_protocol_class: Optional[Type[asyncio.Protocol]] = ws_protocol_class From 7a8aa998364334868be3ecfb5e88ab35f75d68c7 Mon Sep 17 00:00:00 2001 From: Irfanuddin Date: Fri, 12 Aug 2022 14:07:22 +0530 Subject: [PATCH 15/58] revert(websockets): removed work-around logic to add headers --- uvicorn/config.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/uvicorn/config.py b/uvicorn/config.py index e71212674..3cb6bf3d8 100644 --- a/uvicorn/config.py +++ b/uvicorn/config.py @@ -468,9 +468,6 @@ def load(self) -> None: self.http_protocol_class = self.http if isinstance(self.ws, str): - import websockets.http - - websockets.http.USER_AGENT = "" ws_protocol_class = import_from_string(WS_PROTOCOLS[self.ws]) self.ws_protocol_class: Optional[Type[asyncio.Protocol]] = ws_protocol_class else: From 94ddfe5bfbf9d5f8fe09b9f551b03fdb57135c23 Mon Sep 17 00:00:00 2001 From: Irfanuddin Date: Fri, 12 Aug 2022 15:43:20 +0530 Subject: [PATCH 16/58] Revert "feat(websockets-wsproto): added server header support for websockets" This reverts commit 62c6c7e4 --- uvicorn/protocols/websockets/wsproto_impl.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/uvicorn/protocols/websockets/wsproto_impl.py b/uvicorn/protocols/websockets/wsproto_impl.py index 1a0a23c45..eb8d860b3 100644 --- a/uvicorn/protocols/websockets/wsproto_impl.py +++ b/uvicorn/protocols/websockets/wsproto_impl.py @@ -27,7 +27,6 @@ def __init__(self, config, server_state, _loop=None): # Shared server state self.connections = server_state.connections self.tasks = server_state.tasks - self.default_headers = server_state.default_headers # Connection state self.transport = None @@ -134,7 +133,6 @@ def on_task_complete(self, task): def handle_connect(self, event): self.connect_event = event headers = [(b"host", event.host.encode())] - headers.extend(self.default_headers) headers += [(key.lower(), value) for key, value in event.extra_headers] raw_path, _, query_string = event.target.partition("?") self.scope = { From 49b135b525d0e1a3fd39f2bfa06b81c2973654d5 Mon Sep 17 00:00:00 2001 From: Irfanuddin Date: Fri, 12 Aug 2022 15:46:20 +0530 Subject: [PATCH 17/58] feat(wsproto): Add headers support for wsproto implementation --- uvicorn/protocols/websockets/wsproto_impl.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/uvicorn/protocols/websockets/wsproto_impl.py b/uvicorn/protocols/websockets/wsproto_impl.py index eb8d860b3..881f38aa6 100644 --- a/uvicorn/protocols/websockets/wsproto_impl.py +++ b/uvicorn/protocols/websockets/wsproto_impl.py @@ -27,6 +27,7 @@ def __init__(self, config, server_state, _loop=None): # Shared server state self.connections = server_state.connections self.tasks = server_state.tasks + self.default_headers = server_state.default_headers # Connection state self.transport = None @@ -248,7 +249,15 @@ async def send(self, message): ) self.handshake_complete = True subprotocol = message.get("subprotocol") - extra_headers = message.get("headers", []) + headers = list(message.get("headers", [])) + self.default_headers + extra_headers = [] + _added_names = [] + for name, value in headers: + if name.lower() in _added_names: + continue + _added_names.append(name.lower()) + extra_headers.append((name, value)) + extensions = [] if self.config.ws_per_message_deflate: extensions.append(PerMessageDeflate()) From f4ada86eb5fce53b17316be333f4909342591ac4 Mon Sep 17 00:00:00 2001 From: Irfanuddin Date: Fri, 12 Aug 2022 23:14:38 +0530 Subject: [PATCH 18/58] docs: added limitation about Websockets - no-date-header --no-date-header is also not compatible with Websockets --- docs/settings.md | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/docs/settings.md b/docs/settings.md index 0e17b0504..405f78aa6 100644 --- a/docs/settings.md +++ b/docs/settings.md @@ -67,9 +67,6 @@ For more nuanced control over which file modifications trigger reloads, install * `--lifespan ` - Set the Lifespan protocol implementation. **Options:** *'auto', 'on', 'off'.* **Default:** *'auto'*. * `--h11-max-incomplete-event-size ` - Set the maximum number of bytes to buffer of an incomplete event. Only available for `h11` HTTP protocol implementation. **Default:** *'16384'* (16 KB). -!!! note - Selecting `websockets` as Websocket protocol doesn't adhere to `--no-server-header` settings of Uvicorn. This is a limitation. - ## Application Interface * `--interface` - Select ASGI3, ASGI2, or WSGI as the application interface. @@ -86,7 +83,7 @@ connecting IPs in the `forwarded-allow-ips` configuration. * `--date-header` / `--no-date-header` - Enable/Disable default `Date` header. !!! note - The `--no-server-header` flag doesn't have effect on the WebSockets implementations. + The `--no-server-header` and `--no-date-header` doesn't have effect on the WebSockets implementations. ## HTTPS @@ -106,4 +103,4 @@ connecting IPs in the `forwarded-allow-ips` configuration. ## Timeouts -* `--timeout-keep-alive ` - Close Keep-Alive connections if no new data is received within this timeout. **Default:** *5*. +* `--timeout-keep-alive ` - Close Keep-Alive connections if no new data is received within this timeout. **Default:** *5*. \ No newline at end of file From 7a08ff02fe7a9769d8594cbc407460f30d7c3397 Mon Sep 17 00:00:00 2001 From: Irfanuddin Date: Fri, 12 Aug 2022 23:16:54 +0530 Subject: [PATCH 19/58] docs: added limitation about Websockets - no-date-header --no-date-header is also not compatible with Websockets --- docs/settings.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/settings.md b/docs/settings.md index 405f78aa6..3211e2b17 100644 --- a/docs/settings.md +++ b/docs/settings.md @@ -83,7 +83,7 @@ connecting IPs in the `forwarded-allow-ips` configuration. * `--date-header` / `--no-date-header` - Enable/Disable default `Date` header. !!! note - The `--no-server-header` and `--no-date-header` doesn't have effect on the WebSockets implementations. + The `--no-server-header` and `--no-date-header` flags doesn't have effect on the WebSockets implementations. ## HTTPS From 137fd3831b43509fe47a3b7265d65243cfbee43c Mon Sep 17 00:00:00 2001 From: Irfanuddin Shafi Ahmed Date: Mon, 15 Aug 2022 23:18:02 +0530 Subject: [PATCH 20/58] Update uvicorn/protocols/websockets/wsproto_impl.py Suggestion removes duplication check on header names to keep it consistent with http implementations. Co-authored-by: Marcelo Trylesinski --- uvicorn/protocols/websockets/wsproto_impl.py | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/uvicorn/protocols/websockets/wsproto_impl.py b/uvicorn/protocols/websockets/wsproto_impl.py index 881f38aa6..3b76b366f 100644 --- a/uvicorn/protocols/websockets/wsproto_impl.py +++ b/uvicorn/protocols/websockets/wsproto_impl.py @@ -249,15 +249,7 @@ async def send(self, message): ) self.handshake_complete = True subprotocol = message.get("subprotocol") - headers = list(message.get("headers", [])) + self.default_headers - extra_headers = [] - _added_names = [] - for name, value in headers: - if name.lower() in _added_names: - continue - _added_names.append(name.lower()) - extra_headers.append((name, value)) - + extra_headers = list(message.get("headers", [])) + self.default_headers extensions = [] if self.config.ws_per_message_deflate: extensions.append(PerMessageDeflate()) From e241caaffaf8bfd140bdc84c697fac7b1962a1c8 Mon Sep 17 00:00:00 2001 From: Irfanuddin Date: Tue, 16 Aug 2022 00:31:56 +0530 Subject: [PATCH 21/58] revert: removed default header logic --- uvicorn/protocols/websockets/websockets_impl.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/uvicorn/protocols/websockets/websockets_impl.py b/uvicorn/protocols/websockets/websockets_impl.py index 3207e19b2..060a6f1db 100644 --- a/uvicorn/protocols/websockets/websockets_impl.py +++ b/uvicorn/protocols/websockets/websockets_impl.py @@ -261,7 +261,7 @@ async def asgi_send(self, message: "ASGISendEvent") -> None: self.accepted_subprotocol = cast( Optional[Subprotocol], message.get("subprotocol") ) - headers = list(message.get("headers", [])) + self.default_headers + headers = list(message.get("headers", [])) _added_names = [] for name, value in headers: if name.lower() in _added_names: From eede8f4cc922444da374a149a3838969c3c1edd2 Mon Sep 17 00:00:00 2001 From: Irfanuddin Date: Tue, 16 Aug 2022 00:46:26 +0530 Subject: [PATCH 22/58] test: added test for wsproto implementation --- tests/protocols/test_websocket.py | 61 +++++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) diff --git a/tests/protocols/test_websocket.py b/tests/protocols/test_websocket.py index 904875556..64645483d 100644 --- a/tests/protocols/test_websocket.py +++ b/tests/protocols/test_websocket.py @@ -21,6 +21,7 @@ ONLY_WEBSOCKETPROTOCOL = [p for p in [WebSocketProtocol] if p is not None] WS_PROTOCOLS = [p for p in [WSProtocol, WebSocketProtocol] if p is not None] +ONLY_WS_PROTOCOL = [p for p in [WSProtocol] if p is not None] pytestmark = pytest.mark.skipif( websockets is None, reason="This test needs the websockets module" ) @@ -658,3 +659,63 @@ async def send_text(url): await send_text("ws://127.0.0.1:8000") assert frames == [b"abc", b"abc", b"abc"] + + +@pytest.mark.anyio +@pytest.mark.parametrize("ws_protocol_cls", ONLY_WS_PROTOCOL) +@pytest.mark.parametrize("http_protocol_cls", HTTP_PROTOCOLS) +async def test_default_server_headers(ws_protocol_cls, http_protocol_cls): + class App(WebSocketResponse): + async def websocket_connect(self, message): + await self.send( + {"type": "websocket.accept"} + ) + + async def open_connection(url): + async with websockets.connect(url) as websocket: + return websocket.response_headers + + config = Config(app=App, ws=ws_protocol_cls, http=http_protocol_cls, lifespan="off") + async with run_server(config): + headers = await open_connection("ws://127.0.0.1:8000") + assert (headers.get("server") == "uvicorn" and "date" in headers) + + +@pytest.mark.anyio +@pytest.mark.parametrize("ws_protocol_cls", ONLY_WS_PROTOCOL) +@pytest.mark.parametrize("http_protocol_cls", HTTP_PROTOCOLS) +async def test_no_server_headers(ws_protocol_cls, http_protocol_cls): + class App(WebSocketResponse): + async def websocket_connect(self, message): + await self.send( + {"type": "websocket.accept"} + ) + + async def open_connection(url): + async with websockets.connect(url) as websocket: + return websocket.response_headers + + config = Config(app=App, ws=ws_protocol_cls, http=http_protocol_cls, lifespan="off", server_header=False) + async with run_server(config): + headers = await open_connection("ws://127.0.0.1:8000") + assert "server" not in headers + + +@pytest.mark.anyio +@pytest.mark.parametrize("ws_protocol_cls", ONLY_WS_PROTOCOL) +@pytest.mark.parametrize("http_protocol_cls", HTTP_PROTOCOLS) +async def test_no_date_header(ws_protocol_cls, http_protocol_cls): + class App(WebSocketResponse): + async def websocket_connect(self, message): + await self.send( + {"type": "websocket.accept"} + ) + + async def open_connection(url): + async with websockets.connect(url) as websocket: + return websocket.response_headers + + config = Config(app=App, ws=ws_protocol_cls, http=http_protocol_cls, lifespan="off", date_header=False) + async with run_server(config): + headers = await open_connection("ws://127.0.0.1:8000") + assert "date" not in headers From 62cbf96b210f1c01c64b7584e3b666ef475dd504 Mon Sep 17 00:00:00 2001 From: Irfanuddin Date: Tue, 16 Aug 2022 00:55:55 +0530 Subject: [PATCH 23/58] style: fixed linting --- tests/protocols/test_websocket.py | 30 ++++++++++++++++++------------ 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/tests/protocols/test_websocket.py b/tests/protocols/test_websocket.py index 64645483d..dcc133f93 100644 --- a/tests/protocols/test_websocket.py +++ b/tests/protocols/test_websocket.py @@ -667,9 +667,7 @@ async def send_text(url): async def test_default_server_headers(ws_protocol_cls, http_protocol_cls): class App(WebSocketResponse): async def websocket_connect(self, message): - await self.send( - {"type": "websocket.accept"} - ) + await self.send({"type": "websocket.accept"}) async def open_connection(url): async with websockets.connect(url) as websocket: @@ -678,7 +676,7 @@ async def open_connection(url): config = Config(app=App, ws=ws_protocol_cls, http=http_protocol_cls, lifespan="off") async with run_server(config): headers = await open_connection("ws://127.0.0.1:8000") - assert (headers.get("server") == "uvicorn" and "date" in headers) + assert headers.get("server") == "uvicorn" and "date" in headers @pytest.mark.anyio @@ -687,15 +685,19 @@ async def open_connection(url): async def test_no_server_headers(ws_protocol_cls, http_protocol_cls): class App(WebSocketResponse): async def websocket_connect(self, message): - await self.send( - {"type": "websocket.accept"} - ) + await self.send({"type": "websocket.accept"}) async def open_connection(url): async with websockets.connect(url) as websocket: return websocket.response_headers - config = Config(app=App, ws=ws_protocol_cls, http=http_protocol_cls, lifespan="off", server_header=False) + config = Config( + app=App, + ws=ws_protocol_cls, + http=http_protocol_cls, + lifespan="off", + server_header=False, + ) async with run_server(config): headers = await open_connection("ws://127.0.0.1:8000") assert "server" not in headers @@ -707,15 +709,19 @@ async def open_connection(url): async def test_no_date_header(ws_protocol_cls, http_protocol_cls): class App(WebSocketResponse): async def websocket_connect(self, message): - await self.send( - {"type": "websocket.accept"} - ) + await self.send({"type": "websocket.accept"}) async def open_connection(url): async with websockets.connect(url) as websocket: return websocket.response_headers - config = Config(app=App, ws=ws_protocol_cls, http=http_protocol_cls, lifespan="off", date_header=False) + config = Config( + app=App, + ws=ws_protocol_cls, + http=http_protocol_cls, + lifespan="off", + date_header=False, + ) async with run_server(config): headers = await open_connection("ws://127.0.0.1:8000") assert "date" not in headers From 877a9fc9e52242d4eb464b4aa153b7e2354584fc Mon Sep 17 00:00:00 2001 From: Irfanuddin Date: Tue, 16 Aug 2022 12:50:51 +0530 Subject: [PATCH 24/58] test: added test for multiple server headers --- tests/protocols/test_websocket.py | 34 ++++++++++++++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/tests/protocols/test_websocket.py b/tests/protocols/test_websocket.py index dcc133f93..1414cd686 100644 --- a/tests/protocols/test_websocket.py +++ b/tests/protocols/test_websocket.py @@ -18,7 +18,6 @@ WebSocketProtocol = None ClientPerMessageDeflateFactory = None - ONLY_WEBSOCKETPROTOCOL = [p for p in [WebSocketProtocol] if p is not None] WS_PROTOCOLS = [p for p in [WSProtocol, WebSocketProtocol] if p is not None] ONLY_WS_PROTOCOL = [p for p in [WSProtocol] if p is not None] @@ -725,3 +724,36 @@ async def open_connection(url): async with run_server(config): headers = await open_connection("ws://127.0.0.1:8000") assert "date" not in headers + + +@pytest.mark.anyio +@pytest.mark.parametrize("ws_protocol_cls", ONLY_WS_PROTOCOL) +@pytest.mark.parametrize("http_protocol_cls", HTTP_PROTOCOLS) +async def test_multiple_server_header(ws_protocol_cls, http_protocol_cls): + class App(WebSocketResponse): + async def websocket_connect(self, message): + await self.send( + { + "type": "websocket.accept", + "headers": [ + (b"Server", b"over-ridden"), + (b"Server", b"another-value"), + ], + } + ) + + async def open_connection(url): + async with websockets.connect(url) as websocket: + return websocket.response_headers + + config = Config( + app=App, + ws=ws_protocol_cls, + http=http_protocol_cls, + lifespan="off", + ) + async with run_server(config): + headers = await open_connection("ws://127.0.0.1:8000") + assert all( + x in headers.get_all("Server") for x in ["over-ridden", "another-value"] + ) From 1cbf30cb422cd121abf9e2af6fab04d530a44000 Mon Sep 17 00:00:00 2001 From: Irfanuddin Shafi Ahmed Date: Sat, 20 Aug 2022 23:05:44 +0530 Subject: [PATCH 25/58] Feature/new (#2) Co-authored-by: Marcelo Trylesinski Co-authored-by: Irfanuddin --- uvicorn/protocols/websockets/websockets_impl.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/uvicorn/protocols/websockets/websockets_impl.py b/uvicorn/protocols/websockets/websockets_impl.py index 060a6f1db..bc6c2c577 100644 --- a/uvicorn/protocols/websockets/websockets_impl.py +++ b/uvicorn/protocols/websockets/websockets_impl.py @@ -26,13 +26,10 @@ if TYPE_CHECKING: from asgiref.typing import ( ASGISendEvent, - WebSocketAcceptEvent, - WebSocketCloseEvent, WebSocketConnectEvent, WebSocketDisconnectEvent, WebSocketReceiveEvent, WebSocketScope, - WebSocketSendEvent, ) From b061ede19972186669d33337d095b73889ba353f Mon Sep 17 00:00:00 2001 From: Irfanuddin Date: Sat, 20 Aug 2022 23:13:15 +0530 Subject: [PATCH 26/58] flake-8 corrections --- uvicorn/protocols/websockets/websockets_impl.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/uvicorn/protocols/websockets/websockets_impl.py b/uvicorn/protocols/websockets/websockets_impl.py index bc6c2c577..2b63a7fe8 100644 --- a/uvicorn/protocols/websockets/websockets_impl.py +++ b/uvicorn/protocols/websockets/websockets_impl.py @@ -26,10 +26,13 @@ if TYPE_CHECKING: from asgiref.typing import ( ASGISendEvent, + WebSocketAcceptEvent, + WebSocketCloseEvent, WebSocketConnectEvent, WebSocketDisconnectEvent, WebSocketReceiveEvent, WebSocketScope, + WebSocketSendEvent, ) @@ -352,4 +355,4 @@ async def asgi_receive( else: msg["bytes"] = data - return msg + return msg \ No newline at end of file From 02c96e483425825d1350c6ac5b33499d047df11b Mon Sep 17 00:00:00 2001 From: Irfanuddin Date: Sat, 20 Aug 2022 23:14:37 +0530 Subject: [PATCH 27/58] run black --- uvicorn/protocols/websockets/websockets_impl.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/uvicorn/protocols/websockets/websockets_impl.py b/uvicorn/protocols/websockets/websockets_impl.py index 2b63a7fe8..060a6f1db 100644 --- a/uvicorn/protocols/websockets/websockets_impl.py +++ b/uvicorn/protocols/websockets/websockets_impl.py @@ -355,4 +355,4 @@ async def asgi_receive( else: msg["bytes"] = data - return msg \ No newline at end of file + return msg From 3c6c915208ca9ee08cafaf4ddb53991b2cdf05e6 Mon Sep 17 00:00:00 2001 From: Irfanuddin Date: Sun, 21 Aug 2022 21:42:27 +0530 Subject: [PATCH 28/58] Added support for no-server-header in Websocket Protocols --- tests/protocols/test_websocket.py | 5 +++-- uvicorn/protocols/utils.py | 6 +++++- uvicorn/protocols/websockets/websockets_impl.py | 10 +++++++++- 3 files changed, 17 insertions(+), 4 deletions(-) diff --git a/tests/protocols/test_websocket.py b/tests/protocols/test_websocket.py index 1414cd686..0bad8c55e 100644 --- a/tests/protocols/test_websocket.py +++ b/tests/protocols/test_websocket.py @@ -10,6 +10,7 @@ try: import websockets + import websockets.exceptions from websockets.extensions.permessage_deflate import ClientPerMessageDeflateFactory from uvicorn.protocols.websockets.websockets_impl import WebSocketProtocol @@ -661,7 +662,7 @@ async def send_text(url): @pytest.mark.anyio -@pytest.mark.parametrize("ws_protocol_cls", ONLY_WS_PROTOCOL) +@pytest.mark.parametrize("ws_protocol_cls", WS_PROTOCOLS) @pytest.mark.parametrize("http_protocol_cls", HTTP_PROTOCOLS) async def test_default_server_headers(ws_protocol_cls, http_protocol_cls): class App(WebSocketResponse): @@ -679,7 +680,7 @@ async def open_connection(url): @pytest.mark.anyio -@pytest.mark.parametrize("ws_protocol_cls", ONLY_WS_PROTOCOL) +@pytest.mark.parametrize("ws_protocol_cls", WS_PROTOCOLS) @pytest.mark.parametrize("http_protocol_cls", HTTP_PROTOCOLS) async def test_no_server_headers(ws_protocol_cls, http_protocol_cls): class App(WebSocketResponse): diff --git a/uvicorn/protocols/utils.py b/uvicorn/protocols/utils.py index fbd4b4d5d..5f6715df6 100644 --- a/uvicorn/protocols/utils.py +++ b/uvicorn/protocols/utils.py @@ -1,6 +1,6 @@ import asyncio import urllib.parse -from typing import TYPE_CHECKING, Optional, Tuple +from typing import TYPE_CHECKING, Optional, Tuple, List if TYPE_CHECKING: from asgiref.typing import WWWScope @@ -53,3 +53,7 @@ def get_path_with_query_string(scope: "WWWScope") -> str: path_with_query_string, scope["query_string"].decode("ascii") ) return path_with_query_string + + +def get_server_header(default_headers: List[Tuple[bytes, bytes]], override: str) -> str: + return next((i for i in default_headers if i[0] in (b"Server", b"server")), [override.encode()])[-1].decode() diff --git a/uvicorn/protocols/websockets/websockets_impl.py b/uvicorn/protocols/websockets/websockets_impl.py index 060a6f1db..5b8df09d9 100644 --- a/uvicorn/protocols/websockets/websockets_impl.py +++ b/uvicorn/protocols/websockets/websockets_impl.py @@ -15,7 +15,7 @@ from uvicorn.config import Config from uvicorn.logging import TRACE_LOG_LEVEL -from uvicorn.protocols.utils import get_local_addr, get_remote_addr, is_ssl +from uvicorn.protocols.utils import get_local_addr, get_remote_addr, is_ssl, get_server_header from uvicorn.server import ServerState if sys.version_info < (3, 8): @@ -65,6 +65,13 @@ def __init__( self.app = config.loaded_app self.loop = _loop or asyncio.get_event_loop() self.root_path = config.root_path + if self.config.server_header: + self.server_header = get_server_header( + default_headers=server_state.default_headers, + override=websockets.server.USER_AGENT + ) + else: + self.server_header = None # Shared server state self.connections = server_state.connections @@ -99,6 +106,7 @@ def __init__( max_size=self.config.ws_max_size, ping_interval=self.config.ws_ping_interval, ping_timeout=self.config.ws_ping_timeout, + server_header=self.server_header, extensions=extensions, logger=logging.getLogger("uvicorn.error"), extra_headers=[], From 9b79b316b7af43b0dc2ef4436a29d57fd0c50ef2 Mon Sep 17 00:00:00 2001 From: Irfanuddin Date: Sun, 21 Aug 2022 21:45:39 +0530 Subject: [PATCH 29/58] ran black --- uvicorn/protocols/utils.py | 7 +++++-- uvicorn/protocols/websockets/websockets_impl.py | 9 +++++++-- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/uvicorn/protocols/utils.py b/uvicorn/protocols/utils.py index 5f6715df6..5b083d1c9 100644 --- a/uvicorn/protocols/utils.py +++ b/uvicorn/protocols/utils.py @@ -1,6 +1,6 @@ import asyncio import urllib.parse -from typing import TYPE_CHECKING, Optional, Tuple, List +from typing import TYPE_CHECKING, List, Optional, Tuple if TYPE_CHECKING: from asgiref.typing import WWWScope @@ -56,4 +56,7 @@ def get_path_with_query_string(scope: "WWWScope") -> str: def get_server_header(default_headers: List[Tuple[bytes, bytes]], override: str) -> str: - return next((i for i in default_headers if i[0] in (b"Server", b"server")), [override.encode()])[-1].decode() + return next( + (i for i in default_headers if i[0] in (b"Server", b"server")), + [override.encode()], + )[-1].decode() diff --git a/uvicorn/protocols/websockets/websockets_impl.py b/uvicorn/protocols/websockets/websockets_impl.py index 5b8df09d9..b24ecff9c 100644 --- a/uvicorn/protocols/websockets/websockets_impl.py +++ b/uvicorn/protocols/websockets/websockets_impl.py @@ -15,7 +15,12 @@ from uvicorn.config import Config from uvicorn.logging import TRACE_LOG_LEVEL -from uvicorn.protocols.utils import get_local_addr, get_remote_addr, is_ssl, get_server_header +from uvicorn.protocols.utils import ( + get_local_addr, + get_remote_addr, + get_server_header, + is_ssl, +) from uvicorn.server import ServerState if sys.version_info < (3, 8): @@ -68,7 +73,7 @@ def __init__( if self.config.server_header: self.server_header = get_server_header( default_headers=server_state.default_headers, - override=websockets.server.USER_AGENT + override=websockets.server.USER_AGENT, ) else: self.server_header = None From 1548e92f945d2535d8d2fef893af528cff2ce74c Mon Sep 17 00:00:00 2001 From: Irfanuddin Date: Sun, 21 Aug 2022 21:52:05 +0530 Subject: [PATCH 30/58] update requirements.txt --- requirements.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/requirements.txt b/requirements.txt index c73c9671a..014ea67e9 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,6 +5,7 @@ asgiref==3.5.2 # Explicit optionals wsproto==1.1.0 +git+https://github.com/aaugustin/websockets.git # Packaging twine==4.0.1 From 2b7267fead653e14ebe5e13c99bffde7f8f87b6c Mon Sep 17 00:00:00 2001 From: Irfanuddin Date: Sun, 21 Aug 2022 21:53:51 +0530 Subject: [PATCH 31/58] Revert "update requirements.txt" This reverts commit 1548e92f945d2535d8d2fef893af528cff2ce74c. --- requirements.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 014ea67e9..c73c9671a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,7 +5,6 @@ asgiref==3.5.2 # Explicit optionals wsproto==1.1.0 -git+https://github.com/aaugustin/websockets.git # Packaging twine==4.0.1 From 402be7b73b764f84fa9ff2edb009907c6da8d6c7 Mon Sep 17 00:00:00 2001 From: Irfanuddin Date: Sun, 21 Aug 2022 21:54:49 +0530 Subject: [PATCH 32/58] update requirements to use wip main branch from Websockets --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index bd0fd2720..cb5244ac7 100755 --- a/setup.py +++ b/setup.py @@ -51,7 +51,7 @@ def get_packages(package): extra_requirements = [ - "websockets>=10.0", + "git+https://github.com/aaugustin/websockets.git", "httptools>=0.4.0", "uvloop>=0.14.0,!=0.15.0,!=0.15.1; " + env_marker_cpython, "colorama>=0.4;" + env_marker_win, From 85c342bf88a706f9cd404d1dd9f04984d7cd7b89 Mon Sep 17 00:00:00 2001 From: Irfanuddin Date: Sun, 21 Aug 2022 21:57:42 +0530 Subject: [PATCH 33/58] update requirements to use wip main branch from Websockets --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index cb5244ac7..e8c78c858 100755 --- a/setup.py +++ b/setup.py @@ -51,7 +51,7 @@ def get_packages(package): extra_requirements = [ - "git+https://github.com/aaugustin/websockets.git", + "websockets @ git+https://github.com/aaugustin/websockets.git", "httptools>=0.4.0", "uvloop>=0.14.0,!=0.15.0,!=0.15.1; " + env_marker_cpython, "colorama>=0.4;" + env_marker_win, From f290fe7c34afa3408ba55ab18c29efbf9f5ba331 Mon Sep 17 00:00:00 2001 From: Irfanuddin Date: Wed, 24 Aug 2022 15:20:44 +0530 Subject: [PATCH 34/58] =?UTF-8?q?Merge=20remote-tracking=20branch=20'upstr?= =?UTF-8?q?eam/master'=20into=20feature/websocket=E2=80=A6=20=E2=80=A6-hea?= =?UTF-8?q?ders?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- setup.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100755 setup.py diff --git a/setup.py b/setup.py deleted file mode 100755 index e69de29bb..000000000 From dc8a5057415f876d7e44a92c0c63f593a6b0bdd1 Mon Sep 17 00:00:00 2001 From: Irfanuddin Date: Wed, 24 Aug 2022 15:26:02 +0530 Subject: [PATCH 35/58] update requirements to use wip main branch from Websockets --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index aec35a7ad..26b8b2a55 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -41,7 +41,7 @@ standard = [ "PyYAML>=5.1", "uvloop>=0.14.0,!=0.15.0,!=0.15.1; sys_platform != 'win32' and (sys_platform != 'cygwin' and platform_python_implementation != 'PyPy')", "watchfiles>=0.13", - "websockets>=10.0", + "websockets @ git+https://github.com/aaugustin/websockets.git", ] [project.scripts] From 019e7d291cb387c330fb841c9cab7ba1ed221950 Mon Sep 17 00:00:00 2001 From: Irfanuddin Date: Wed, 24 Aug 2022 18:14:16 +0530 Subject: [PATCH 36/58] wip: add allow-direct-references to support installation from Git --- pyproject.toml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 26b8b2a55..5a988c2a3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -41,7 +41,7 @@ standard = [ "PyYAML>=5.1", "uvloop>=0.14.0,!=0.15.0,!=0.15.1; sys_platform != 'win32' and (sys_platform != 'cygwin' and platform_python_implementation != 'PyPy')", "watchfiles>=0.13", - "websockets @ git+https://github.com/aaugustin/websockets.git", + "websockets>=10.0", ] [project.scripts] @@ -60,3 +60,5 @@ path = "uvicorn/__init__.py" include = [ "/uvicorn", ] +[tool.hatch.metadata] +allow-direct-references = true From 59cf6f44db7dd9d061cabbf4a79e2ff8d83dddc9 Mon Sep 17 00:00:00 2001 From: Irfanuddin Date: Wed, 24 Aug 2022 18:16:10 +0530 Subject: [PATCH 37/58] update requirements to use wip main branch from Websockets --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 5a988c2a3..12c2b433c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -41,7 +41,7 @@ standard = [ "PyYAML>=5.1", "uvloop>=0.14.0,!=0.15.0,!=0.15.1; sys_platform != 'win32' and (sys_platform != 'cygwin' and platform_python_implementation != 'PyPy')", "watchfiles>=0.13", - "websockets>=10.0", + "websockets @ git+https://github.com/aaugustin/websockets.git", ] [project.scripts] From 866f3e9c986310052b34707f6688838c879dbd43 Mon Sep 17 00:00:00 2001 From: Irfanuddin Date: Wed, 24 Aug 2022 18:22:26 +0530 Subject: [PATCH 38/58] update requirements to use wip main branch from Websockets --- pyproject.toml | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 12c2b433c..c16ceb74d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -33,6 +33,9 @@ dependencies = [ "typing-extensions;python_version < '3.8'", ] +[tool.hatch.metadata] +allow-direct-references = true + [project.optional-dependencies] standard = [ "colorama>=0.4;sys_platform == 'win32'", @@ -41,7 +44,7 @@ standard = [ "PyYAML>=5.1", "uvloop>=0.14.0,!=0.15.0,!=0.15.1; sys_platform != 'win32' and (sys_platform != 'cygwin' and platform_python_implementation != 'PyPy')", "watchfiles>=0.13", - "websockets @ git+https://github.com/aaugustin/websockets.git", + "websockets @ git+https://github.com/aaugustin/websockets.git@main", ] [project.scripts] @@ -60,5 +63,4 @@ path = "uvicorn/__init__.py" include = [ "/uvicorn", ] -[tool.hatch.metadata] -allow-direct-references = true + From f6a0872a339f0316bbaf67bf3bf7bf22efdef59d Mon Sep 17 00:00:00 2001 From: Irfanuddin Date: Wed, 24 Aug 2022 18:29:19 +0530 Subject: [PATCH 39/58] update requirements to use wip main branch from Websockets --- pyproject.toml | 3 +-- requirements.txt | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index c16ceb74d..effc4668c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -43,8 +43,7 @@ standard = [ "python-dotenv>=0.13", "PyYAML>=5.1", "uvloop>=0.14.0,!=0.15.0,!=0.15.1; sys_platform != 'win32' and (sys_platform != 'cygwin' and platform_python_implementation != 'PyPy')", - "watchfiles>=0.13", - "websockets @ git+https://github.com/aaugustin/websockets.git@main", + "watchfiles>=0.13" ] [project.scripts] diff --git a/requirements.txt b/requirements.txt index 183b908f1..2c18fd07d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,7 +5,7 @@ asgiref==3.5.2 # Explicit optionals wsproto==1.1.0 - +git+https://github.com/aaugustin/websockets.git@main # Packaging build==0.8.0 twine==4.0.1 From 9aa02c362da85aedfef72f01821a4b0efdca23ae Mon Sep 17 00:00:00 2001 From: Irfanuddin Date: Sun, 4 Sep 2022 14:21:56 +0530 Subject: [PATCH 40/58] Bump wsproto from 1.1.0 to 1.2.0 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 2c18fd07d..0ed92d7dc 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,7 +4,7 @@ asgiref==3.5.2 # Explicit optionals -wsproto==1.1.0 +wsproto==1.2.0 git+https://github.com/aaugustin/websockets.git@main # Packaging build==0.8.0 From 93af5ace0942d1f8b2c595f6c6ba7c92b5fb85de Mon Sep 17 00:00:00 2001 From: Irfanuddin Date: Sat, 10 Sep 2022 21:59:33 +0530 Subject: [PATCH 41/58] docs: amend settings.md Remove the line about limitation of `no-server-header` from docs. --- docs/settings.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/settings.md b/docs/settings.md index 3211e2b17..aa322a48c 100644 --- a/docs/settings.md +++ b/docs/settings.md @@ -83,7 +83,7 @@ connecting IPs in the `forwarded-allow-ips` configuration. * `--date-header` / `--no-date-header` - Enable/Disable default `Date` header. !!! note - The `--no-server-header` and `--no-date-header` flags doesn't have effect on the WebSockets implementations. + The `--no-date-header` flag doesn't have effect on the WebSockets implementations. ## HTTPS @@ -103,4 +103,4 @@ connecting IPs in the `forwarded-allow-ips` configuration. ## Timeouts -* `--timeout-keep-alive ` - Close Keep-Alive connections if no new data is received within this timeout. **Default:** *5*. \ No newline at end of file +* `--timeout-keep-alive ` - Close Keep-Alive connections if no new data is received within this timeout. **Default:** *5*. From 97ca6349f4098511bc6a986e6b3e0d38f100c155 Mon Sep 17 00:00:00 2001 From: Irfanuddin Date: Sun, 11 Sep 2022 19:25:26 +0530 Subject: [PATCH 42/58] style: ran isort --- uvicorn/protocols/websockets/websockets_impl.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/uvicorn/protocols/websockets/websockets_impl.py b/uvicorn/protocols/websockets/websockets_impl.py index b5bf5cbda..a30437c2a 100644 --- a/uvicorn/protocols/websockets/websockets_impl.py +++ b/uvicorn/protocols/websockets/websockets_impl.py @@ -17,10 +17,10 @@ from uvicorn.logging import TRACE_LOG_LEVEL from uvicorn.protocols.utils import ( get_local_addr, + get_path_with_query_string, get_remote_addr, get_server_header, is_ssl, - get_path_with_query_string, ) from uvicorn.server import ServerState From 666a8b75ea696d79eaaa6f2d7a15649d7a02f42f Mon Sep 17 00:00:00 2001 From: Irfanuddin Date: Wed, 14 Sep 2022 12:15:13 +0530 Subject: [PATCH 43/58] fix: removed `_added_names` logic to support arbitrary extra headers with same name. --- tests/protocols/test_websocket.py | 33 +++++++++++++++++++ .../protocols/websockets/websockets_impl.py | 4 +-- 2 files changed, 34 insertions(+), 3 deletions(-) diff --git a/tests/protocols/test_websocket.py b/tests/protocols/test_websocket.py index 0bad8c55e..bfc3670e8 100644 --- a/tests/protocols/test_websocket.py +++ b/tests/protocols/test_websocket.py @@ -758,3 +758,36 @@ async def open_connection(url): assert all( x in headers.get_all("Server") for x in ["over-ridden", "another-value"] ) + + +@pytest.mark.anyio +@pytest.mark.parametrize("ws_protocol_cls", WS_PROTOCOLS) +@pytest.mark.parametrize("http_protocol_cls", HTTP_PROTOCOLS) +async def test_multiple_arbitrary_headers_with_same_name(ws_protocol_cls, http_protocol_cls): + class App(WebSocketResponse): + async def websocket_connect(self, message): + await self.send( + { + "type": "websocket.accept", + "headers": [ + (b"Potato", b"cool-potato"), + (b"Potato", b"super-cool-potato"), + ], + } + ) + + async def open_connection(url): + async with websockets.connect(url) as websocket: + return websocket.response_headers + + config = Config( + app=App, + ws=ws_protocol_cls, + http=http_protocol_cls, + lifespan="off", + ) + async with run_server(config): + headers = await open_connection("ws://127.0.0.1:8000") + assert all( + x in headers.get_all("Potato") for x in ["cool", "super-cool"] + ) \ No newline at end of file diff --git a/uvicorn/protocols/websockets/websockets_impl.py b/uvicorn/protocols/websockets/websockets_impl.py index a30437c2a..69e582803 100644 --- a/uvicorn/protocols/websockets/websockets_impl.py +++ b/uvicorn/protocols/websockets/websockets_impl.py @@ -276,11 +276,9 @@ async def asgi_send(self, message: "ASGISendEvent") -> None: Optional[Subprotocol], message.get("subprotocol") ) headers = list(message.get("headers", [])) - _added_names = [] for name, value in headers: - if name.lower() in _added_names: + if name.lower() in [b"server"]: continue - _added_names.append(name.lower()) self.extra_headers.append( ( # ASGI spec requires bytes From 437c066fb63ae900be8b79dc9d946e8a15fe2ab4 Mon Sep 17 00:00:00 2001 From: Irfanuddin Date: Wed, 14 Sep 2022 12:17:47 +0530 Subject: [PATCH 44/58] fix: removed `_added_names` logic to support arbitrary extra headers with same name. --- tests/protocols/test_websocket.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/protocols/test_websocket.py b/tests/protocols/test_websocket.py index bfc3670e8..a505dbee4 100644 --- a/tests/protocols/test_websocket.py +++ b/tests/protocols/test_websocket.py @@ -763,7 +763,9 @@ async def open_connection(url): @pytest.mark.anyio @pytest.mark.parametrize("ws_protocol_cls", WS_PROTOCOLS) @pytest.mark.parametrize("http_protocol_cls", HTTP_PROTOCOLS) -async def test_multiple_arbitrary_headers_with_same_name(ws_protocol_cls, http_protocol_cls): +async def test_multiple_arbitrary_headers_with_same_name( + ws_protocol_cls, http_protocol_cls +): class App(WebSocketResponse): async def websocket_connect(self, message): await self.send( @@ -788,6 +790,4 @@ async def open_connection(url): ) async with run_server(config): headers = await open_connection("ws://127.0.0.1:8000") - assert all( - x in headers.get_all("Potato") for x in ["cool", "super-cool"] - ) \ No newline at end of file + assert all(x in headers.get_all("Potato") for x in ["cool", "super-cool"]) From 27bd66f0038fbe7cb1312f31b89a0af33a5b708a Mon Sep 17 00:00:00 2001 From: Irfanuddin Date: Wed, 14 Sep 2022 19:48:53 +0530 Subject: [PATCH 45/58] fix: removed `_added_names` logic to support arbitrary extra headers with same name. --- tests/protocols/test_websocket.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/protocols/test_websocket.py b/tests/protocols/test_websocket.py index a505dbee4..cefa6355b 100644 --- a/tests/protocols/test_websocket.py +++ b/tests/protocols/test_websocket.py @@ -790,4 +790,4 @@ async def open_connection(url): ) async with run_server(config): headers = await open_connection("ws://127.0.0.1:8000") - assert all(x in headers.get_all("Potato") for x in ["cool", "super-cool"]) + assert all(x in headers.get_all("Potato") for x in ["cool-potato", "super-cool-potato"]) From 50d2e9f2aabd90b643f5d6748e99adec172a6e8f Mon Sep 17 00:00:00 2001 From: Irfanuddin Date: Wed, 14 Sep 2022 19:51:28 +0530 Subject: [PATCH 46/58] fix: removed `_added_names` logic to support arbitrary extra headers with same name. --- tests/protocols/test_websocket.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/protocols/test_websocket.py b/tests/protocols/test_websocket.py index cefa6355b..d3a05bb9e 100644 --- a/tests/protocols/test_websocket.py +++ b/tests/protocols/test_websocket.py @@ -790,4 +790,6 @@ async def open_connection(url): ) async with run_server(config): headers = await open_connection("ws://127.0.0.1:8000") - assert all(x in headers.get_all("Potato") for x in ["cool-potato", "super-cool-potato"]) + assert all( + x in headers.get_all("Potato") for x in ["cool-potato", "super-cool-potato"] + ) From 52d871bb01b359393b23fcf33a167d4be1a5be43 Mon Sep 17 00:00:00 2001 From: Irfanuddin Date: Wed, 26 Oct 2022 21:38:29 +0530 Subject: [PATCH 47/58] chore: remove git link to websockets library --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index a4ce9d398..ee4ebb0c1 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,7 +5,7 @@ asgiref==3.5.2 # Explicit optionals wsproto==1.2.0 -git+https://github.com/aaugustin/websockets.git@main + # Packaging build==0.8.0 twine==4.0.1 From 0be0e7cd1c314d1476296af5fee8ce015038ee30 Mon Sep 17 00:00:00 2001 From: Irfanuddin Date: Wed, 26 Oct 2022 21:39:05 +0530 Subject: [PATCH 48/58] chore: bump websockets requirement to >=10.4 --- pyproject.toml | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index d566644e3..13a02b7b6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -34,9 +34,6 @@ dependencies = [ "typing-extensions;python_version < '3.8'", ] -[tool.hatch.metadata] -allow-direct-references = true - [project.optional-dependencies] standard = [ "colorama>=0.4;sys_platform == 'win32'", @@ -44,7 +41,8 @@ standard = [ "python-dotenv>=0.13", "PyYAML>=5.1", "uvloop>=0.14.0,!=0.15.0,!=0.15.1; sys_platform != 'win32' and (sys_platform != 'cygwin' and platform_python_implementation != 'PyPy')", - "watchfiles>=0.13" + "watchfiles>=0.13", + "websockets>=10.4", ] [project.scripts] @@ -63,4 +61,3 @@ path = "uvicorn/__init__.py" include = [ "/uvicorn", ] - From 5673fa3b6b6cbc60f23f2d142a56fc6287f9d1aa Mon Sep 17 00:00:00 2001 From: Irfanuddin Date: Wed, 26 Oct 2022 21:39:50 +0530 Subject: [PATCH 49/58] refactor: USER_AGENT is deprecated. Make changes to accommodate --- uvicorn/protocols/websockets/websockets_impl.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/uvicorn/protocols/websockets/websockets_impl.py b/uvicorn/protocols/websockets/websockets_impl.py index 69e582803..4002486d0 100644 --- a/uvicorn/protocols/websockets/websockets_impl.py +++ b/uvicorn/protocols/websockets/websockets_impl.py @@ -74,7 +74,7 @@ def __init__( if self.config.server_header: self.server_header = get_server_header( default_headers=server_state.default_headers, - override=websockets.server.USER_AGENT, + override="uvicorn" ) else: self.server_header = None From b51c8c2258089a334dc11aa3fc17b03e263e2cdb Mon Sep 17 00:00:00 2001 From: Irfanuddin Date: Wed, 26 Oct 2022 21:41:57 +0530 Subject: [PATCH 50/58] style: run black --- uvicorn/protocols/websockets/websockets_impl.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/uvicorn/protocols/websockets/websockets_impl.py b/uvicorn/protocols/websockets/websockets_impl.py index 4002486d0..814832da1 100644 --- a/uvicorn/protocols/websockets/websockets_impl.py +++ b/uvicorn/protocols/websockets/websockets_impl.py @@ -73,8 +73,7 @@ def __init__( self.root_path = config.root_path if self.config.server_header: self.server_header = get_server_header( - default_headers=server_state.default_headers, - override="uvicorn" + default_headers=server_state.default_headers, override="uvicorn" ) else: self.server_header = None From c05759a43b8264505231e73558e1c35975a8a02b Mon Sep 17 00:00:00 2001 From: Irfanuddin Date: Thu, 27 Oct 2022 01:57:49 +0530 Subject: [PATCH 51/58] tests: added test (improve coverage) --- tests/protocols/test_websocket.py | 34 ++++++++++++++++++- .../protocols/websockets/websockets_impl.py | 2 +- 2 files changed, 34 insertions(+), 2 deletions(-) diff --git a/tests/protocols/test_websocket.py b/tests/protocols/test_websocket.py index d3a05bb9e..f9c1baca9 100644 --- a/tests/protocols/test_websocket.py +++ b/tests/protocols/test_websocket.py @@ -730,7 +730,7 @@ async def open_connection(url): @pytest.mark.anyio @pytest.mark.parametrize("ws_protocol_cls", ONLY_WS_PROTOCOL) @pytest.mark.parametrize("http_protocol_cls", HTTP_PROTOCOLS) -async def test_multiple_server_header(ws_protocol_cls, http_protocol_cls): +async def test_multiple_server_header_in_ws(ws_protocol_cls, http_protocol_cls): class App(WebSocketResponse): async def websocket_connect(self, message): await self.send( @@ -793,3 +793,35 @@ async def open_connection(url): assert all( x in headers.get_all("Potato") for x in ["cool-potato", "super-cool-potato"] ) + + +@pytest.mark.anyio +@pytest.mark.parametrize("ws_protocol_cls", ONLY_WEBSOCKETPROTOCOL) +@pytest.mark.parametrize("http_protocol_cls", HTTP_PROTOCOLS) +async def test_multiple_server_header_in_websockets(ws_protocol_cls, http_protocol_cls): + class App(WebSocketResponse): + async def websocket_connect(self, message): + await self.send( + { + "type": "websocket.accept", + "headers": [ + (b"Server", b"over-ridden"), + (b"Server", b"another-value"), + ], + } + ) + + async def open_connection(url): + async with websockets.connect(url) as websocket: + return websocket.response_headers + + config = Config( + app=App, + ws=ws_protocol_cls, + http=http_protocol_cls, + lifespan="off", + ) + async with run_server(config): + headers = await open_connection("ws://127.0.0.1:8000") + assert len(headers.get_all("Server")) == 1 + assert headers.get("Server") == "uvicorn" diff --git a/uvicorn/protocols/websockets/websockets_impl.py b/uvicorn/protocols/websockets/websockets_impl.py index 814832da1..5f703386d 100644 --- a/uvicorn/protocols/websockets/websockets_impl.py +++ b/uvicorn/protocols/websockets/websockets_impl.py @@ -25,7 +25,7 @@ from uvicorn.server import ServerState if sys.version_info < (3, 8): - from typing_extensions import Literal + from typing_extensions import Literal # pragma: no cover else: from typing import Literal From b19f58a623043aa8aa5e2deab52ad38f03068ed3 Mon Sep 17 00:00:00 2001 From: Irfanuddin Date: Thu, 27 Oct 2022 02:57:11 +0530 Subject: [PATCH 52/58] tests: added test (improve coverage) --- tests/protocols/test_websocket.py | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/tests/protocols/test_websocket.py b/tests/protocols/test_websocket.py index f9c1baca9..6a48c39ee 100644 --- a/tests/protocols/test_websocket.py +++ b/tests/protocols/test_websocket.py @@ -5,6 +5,7 @@ from tests.protocols.test_http import HTTP_PROTOCOLS from tests.utils import run_server +from uvicorn import Server from uvicorn.config import Config from uvicorn.protocols.websockets.wsproto_impl import WSProtocol @@ -825,3 +826,31 @@ async def open_connection(url): headers = await open_connection("ws://127.0.0.1:8000") assert len(headers.get_all("Server")) == 1 assert headers.get("Server") == "uvicorn" + + +@pytest.mark.anyio +@pytest.mark.parametrize("ws_protocol_cls", ONLY_WEBSOCKETPROTOCOL) +@pytest.mark.parametrize("http_protocol_cls", HTTP_PROTOCOLS) +async def test_server_shutdown_when_connection_active_in_websockets( + ws_protocol_cls, http_protocol_cls +): + class App(WebSocketResponse): + async def websocket_connect(self, message): + await self.send({"type": "websocket.accept"}) + + config = Config( + app=App, + ws=ws_protocol_cls, + http=http_protocol_cls, + lifespan="off", + ) + server = Server(config=config) + cancel_handle = asyncio.ensure_future(server.serve(sockets=None)) + await asyncio.sleep(0.1) + async with websockets.connect("ws://127.0.0.1:8000"): + ws_conn = list(server.server_state.connections)[0] + ws_conn.shutdown() + assert ws_conn.ws_server.closing is True + assert ws_conn.transport.is_closing() + await server.shutdown() + cancel_handle.cancel() From 09bcd7321fdb34b9ae850fa5af211e425167000c Mon Sep 17 00:00:00 2001 From: Irfanuddin Date: Thu, 27 Oct 2022 14:06:34 +0530 Subject: [PATCH 53/58] tests: added test (improve coverage) for WSProto --- tests/protocols/test_websocket.py | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/tests/protocols/test_websocket.py b/tests/protocols/test_websocket.py index 6a48c39ee..2c2cc77ad 100644 --- a/tests/protocols/test_websocket.py +++ b/tests/protocols/test_websocket.py @@ -854,3 +854,32 @@ async def websocket_connect(self, message): assert ws_conn.transport.is_closing() await server.shutdown() cancel_handle.cancel() + + +@pytest.mark.anyio +@pytest.mark.parametrize("ws_protocol_cls", ONLY_WS_PROTOCOL) +@pytest.mark.parametrize("http_protocol_cls", HTTP_PROTOCOLS) +async def test_server_shutdown_when_connection_active_in_ws( + ws_protocol_cls, http_protocol_cls +): + class App(WebSocketResponse): + async def websocket_connect(self, message): + await self.send({"type": "websocket.accept"}) + + config = Config( + app=App, + ws=ws_protocol_cls, + http=http_protocol_cls, + lifespan="off", + ) + server = Server(config=config) + cancel_handle = asyncio.ensure_future(server.serve(sockets=None)) + await asyncio.sleep(0.1) + async with websockets.connect("ws://127.0.0.1:8000") as websocket: + ws_conn = list(server.server_state.connections)[0] + ws_conn.shutdown() + await asyncio.sleep(0.1) + assert websocket.close_code == 1012 + assert ws_conn.transport.is_closing() + await server.shutdown() + cancel_handle.cancel() From 321c8e6e611eb653dc68806f641f2b802d913ba5 Mon Sep 17 00:00:00 2001 From: Marcelo Trylesinski Date: Fri, 28 Oct 2022 11:51:08 +0200 Subject: [PATCH 54/58] Refactor feature/websocket-headers --- tests/protocols/test_websocket.py | 103 +----------------- uvicorn/protocols/utils.py | 9 +- .../protocols/websockets/websockets_impl.py | 38 +++---- uvicorn/protocols/websockets/wsproto_impl.py | 2 +- 4 files changed, 21 insertions(+), 131 deletions(-) diff --git a/tests/protocols/test_websocket.py b/tests/protocols/test_websocket.py index 2c2cc77ad..ac4f8eb96 100644 --- a/tests/protocols/test_websocket.py +++ b/tests/protocols/test_websocket.py @@ -21,8 +21,8 @@ ClientPerMessageDeflateFactory = None ONLY_WEBSOCKETPROTOCOL = [p for p in [WebSocketProtocol] if p is not None] -WS_PROTOCOLS = [p for p in [WSProtocol, WebSocketProtocol] if p is not None] ONLY_WS_PROTOCOL = [p for p in [WSProtocol] if p is not None] +WS_PROTOCOLS = [p for p in [WSProtocol, WebSocketProtocol] if p is not None] pytestmark = pytest.mark.skipif( websockets is None, reason="This test needs the websockets module" ) @@ -729,9 +729,9 @@ async def open_connection(url): @pytest.mark.anyio -@pytest.mark.parametrize("ws_protocol_cls", ONLY_WS_PROTOCOL) +@pytest.mark.parametrize("ws_protocol_cls", WS_PROTOCOLS) @pytest.mark.parametrize("http_protocol_cls", HTTP_PROTOCOLS) -async def test_multiple_server_header_in_ws(ws_protocol_cls, http_protocol_cls): +async def test_multiple_server_header(ws_protocol_cls, http_protocol_cls): class App(WebSocketResponse): async def websocket_connect(self, message): await self.send( @@ -756,9 +756,7 @@ async def open_connection(url): ) async with run_server(config): headers = await open_connection("ws://127.0.0.1:8000") - assert all( - x in headers.get_all("Server") for x in ["over-ridden", "another-value"] - ) + assert headers.get_all("Server") == ["uvicorn", "over-ridden", "another-value"] @pytest.mark.anyio @@ -791,95 +789,4 @@ async def open_connection(url): ) async with run_server(config): headers = await open_connection("ws://127.0.0.1:8000") - assert all( - x in headers.get_all("Potato") for x in ["cool-potato", "super-cool-potato"] - ) - - -@pytest.mark.anyio -@pytest.mark.parametrize("ws_protocol_cls", ONLY_WEBSOCKETPROTOCOL) -@pytest.mark.parametrize("http_protocol_cls", HTTP_PROTOCOLS) -async def test_multiple_server_header_in_websockets(ws_protocol_cls, http_protocol_cls): - class App(WebSocketResponse): - async def websocket_connect(self, message): - await self.send( - { - "type": "websocket.accept", - "headers": [ - (b"Server", b"over-ridden"), - (b"Server", b"another-value"), - ], - } - ) - - async def open_connection(url): - async with websockets.connect(url) as websocket: - return websocket.response_headers - - config = Config( - app=App, - ws=ws_protocol_cls, - http=http_protocol_cls, - lifespan="off", - ) - async with run_server(config): - headers = await open_connection("ws://127.0.0.1:8000") - assert len(headers.get_all("Server")) == 1 - assert headers.get("Server") == "uvicorn" - - -@pytest.mark.anyio -@pytest.mark.parametrize("ws_protocol_cls", ONLY_WEBSOCKETPROTOCOL) -@pytest.mark.parametrize("http_protocol_cls", HTTP_PROTOCOLS) -async def test_server_shutdown_when_connection_active_in_websockets( - ws_protocol_cls, http_protocol_cls -): - class App(WebSocketResponse): - async def websocket_connect(self, message): - await self.send({"type": "websocket.accept"}) - - config = Config( - app=App, - ws=ws_protocol_cls, - http=http_protocol_cls, - lifespan="off", - ) - server = Server(config=config) - cancel_handle = asyncio.ensure_future(server.serve(sockets=None)) - await asyncio.sleep(0.1) - async with websockets.connect("ws://127.0.0.1:8000"): - ws_conn = list(server.server_state.connections)[0] - ws_conn.shutdown() - assert ws_conn.ws_server.closing is True - assert ws_conn.transport.is_closing() - await server.shutdown() - cancel_handle.cancel() - - -@pytest.mark.anyio -@pytest.mark.parametrize("ws_protocol_cls", ONLY_WS_PROTOCOL) -@pytest.mark.parametrize("http_protocol_cls", HTTP_PROTOCOLS) -async def test_server_shutdown_when_connection_active_in_ws( - ws_protocol_cls, http_protocol_cls -): - class App(WebSocketResponse): - async def websocket_connect(self, message): - await self.send({"type": "websocket.accept"}) - - config = Config( - app=App, - ws=ws_protocol_cls, - http=http_protocol_cls, - lifespan="off", - ) - server = Server(config=config) - cancel_handle = asyncio.ensure_future(server.serve(sockets=None)) - await asyncio.sleep(0.1) - async with websockets.connect("ws://127.0.0.1:8000") as websocket: - ws_conn = list(server.server_state.connections)[0] - ws_conn.shutdown() - await asyncio.sleep(0.1) - assert websocket.close_code == 1012 - assert ws_conn.transport.is_closing() - await server.shutdown() - cancel_handle.cancel() + assert headers.get_all("Potato") == ["cool-potato", "super-cool-potato"] diff --git a/uvicorn/protocols/utils.py b/uvicorn/protocols/utils.py index 5b083d1c9..fbd4b4d5d 100644 --- a/uvicorn/protocols/utils.py +++ b/uvicorn/protocols/utils.py @@ -1,6 +1,6 @@ import asyncio import urllib.parse -from typing import TYPE_CHECKING, List, Optional, Tuple +from typing import TYPE_CHECKING, Optional, Tuple if TYPE_CHECKING: from asgiref.typing import WWWScope @@ -53,10 +53,3 @@ def get_path_with_query_string(scope: "WWWScope") -> str: path_with_query_string, scope["query_string"].decode("ascii") ) return path_with_query_string - - -def get_server_header(default_headers: List[Tuple[bytes, bytes]], override: str) -> str: - return next( - (i for i in default_headers if i[0] in (b"Server", b"server")), - [override.encode()], - )[-1].decode() diff --git a/uvicorn/protocols/websockets/websockets_impl.py b/uvicorn/protocols/websockets/websockets_impl.py index 5f703386d..01133b7e2 100644 --- a/uvicorn/protocols/websockets/websockets_impl.py +++ b/uvicorn/protocols/websockets/websockets_impl.py @@ -19,14 +19,13 @@ get_local_addr, get_path_with_query_string, get_remote_addr, - get_server_header, is_ssl, ) from uvicorn.server import ServerState -if sys.version_info < (3, 8): - from typing_extensions import Literal # pragma: no cover -else: +if sys.version_info < (3, 8): # pragma: py-gte-38 + from typing_extensions import Literal +else: # pragma: py-lt-38 from typing import Literal if TYPE_CHECKING: @@ -71,17 +70,10 @@ def __init__( self.app = config.loaded_app self.loop = _loop or asyncio.get_event_loop() self.root_path = config.root_path - if self.config.server_header: - self.server_header = get_server_header( - default_headers=server_state.default_headers, override="uvicorn" - ) - else: - self.server_header = None # Shared server state self.connections = server_state.connections self.tasks = server_state.tasks - self.default_headers = server_state.default_headers # Connection state self.transport: asyncio.Transport = None # type: ignore[assignment] @@ -111,10 +103,13 @@ def __init__( max_size=self.config.ws_max_size, ping_interval=self.config.ws_ping_interval, ping_timeout=self.config.ws_ping_timeout, - server_header=self.server_header, + server_header=None, extensions=extensions, logger=logging.getLogger("uvicorn.error"), - extra_headers=[], + extra_headers=[ + (name.decode("latin-1"), value.decode("latin-1")) + for name, value in server_state.default_headers + ], ) def connection_made( # type: ignore[override] @@ -274,17 +269,12 @@ async def asgi_send(self, message: "ASGISendEvent") -> None: self.accepted_subprotocol = cast( Optional[Subprotocol], message.get("subprotocol") ) - headers = list(message.get("headers", [])) - for name, value in headers: - if name.lower() in [b"server"]: - continue - self.extra_headers.append( - ( - # ASGI spec requires bytes - # But for compatibility we need to convert it to strings - name.decode("latin-1"), - value.decode("latin-1"), - ) + if "headers" in message: + self.extra_headers.extend( + # ASGI spec requires bytes + # But for compatibility we need to convert it to strings + (name.decode("latin-1"), value.decode("latin-1")) + for name, value in message["headers"] ) self.handshake_started_event.set() diff --git a/uvicorn/protocols/websockets/wsproto_impl.py b/uvicorn/protocols/websockets/wsproto_impl.py index 35324e87a..627e32ef8 100644 --- a/uvicorn/protocols/websockets/wsproto_impl.py +++ b/uvicorn/protocols/websockets/wsproto_impl.py @@ -254,7 +254,7 @@ async def send(self, message): ) self.handshake_complete = True subprotocol = message.get("subprotocol") - extra_headers = list(message.get("headers", [])) + self.default_headers + extra_headers = self.default_headers + list(message.get("headers", [])) extensions = [] if self.config.ws_per_message_deflate: extensions.append(PerMessageDeflate()) From da1f87f259c7854bf6ddd4c04c54babc5edb33ed Mon Sep 17 00:00:00 2001 From: Irfanuddin Date: Fri, 28 Oct 2022 19:49:53 +0530 Subject: [PATCH 55/58] remove unused imports --- tests/protocols/test_websocket.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/protocols/test_websocket.py b/tests/protocols/test_websocket.py index ac4f8eb96..8035401e3 100644 --- a/tests/protocols/test_websocket.py +++ b/tests/protocols/test_websocket.py @@ -5,7 +5,6 @@ from tests.protocols.test_http import HTTP_PROTOCOLS from tests.utils import run_server -from uvicorn import Server from uvicorn.config import Config from uvicorn.protocols.websockets.wsproto_impl import WSProtocol From 8b545ad25d0f4f8ba5f412423da748a2e6fcc7e9 Mon Sep 17 00:00:00 2001 From: Irfanuddin Date: Fri, 28 Oct 2022 19:58:22 +0530 Subject: [PATCH 56/58] add clarity in docs --- docs/settings.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/settings.md b/docs/settings.md index 079a40855..9d9ff3698 100644 --- a/docs/settings.md +++ b/docs/settings.md @@ -90,7 +90,7 @@ connecting IPs in the `forwarded-allow-ips` configuration. * `--date-header` / `--no-date-header` - Enable/Disable default `Date` header. !!! note - The `--no-date-header` flag doesn't have effect on the WebSockets implementations. + The `--no-date-header` flag doesn't have effect on the `websockets` implementation. ## HTTPS From afae8ed87fa28dd2bf237ba3f18ea3a86cd095cc Mon Sep 17 00:00:00 2001 From: Irfanuddin Shafi Ahmed Date: Fri, 28 Oct 2022 22:30:23 +0530 Subject: [PATCH 57/58] remove repeated test Co-authored-by: Marcelo Trylesinski --- tests/protocols/test_websocket.py | 31 ------------------------------- 1 file changed, 31 deletions(-) diff --git a/tests/protocols/test_websocket.py b/tests/protocols/test_websocket.py index 8035401e3..1ade0ffa9 100644 --- a/tests/protocols/test_websocket.py +++ b/tests/protocols/test_websocket.py @@ -758,34 +758,3 @@ async def open_connection(url): assert headers.get_all("Server") == ["uvicorn", "over-ridden", "another-value"] -@pytest.mark.anyio -@pytest.mark.parametrize("ws_protocol_cls", WS_PROTOCOLS) -@pytest.mark.parametrize("http_protocol_cls", HTTP_PROTOCOLS) -async def test_multiple_arbitrary_headers_with_same_name( - ws_protocol_cls, http_protocol_cls -): - class App(WebSocketResponse): - async def websocket_connect(self, message): - await self.send( - { - "type": "websocket.accept", - "headers": [ - (b"Potato", b"cool-potato"), - (b"Potato", b"super-cool-potato"), - ], - } - ) - - async def open_connection(url): - async with websockets.connect(url) as websocket: - return websocket.response_headers - - config = Config( - app=App, - ws=ws_protocol_cls, - http=http_protocol_cls, - lifespan="off", - ) - async with run_server(config): - headers = await open_connection("ws://127.0.0.1:8000") - assert headers.get_all("Potato") == ["cool-potato", "super-cool-potato"] From 1768b317c4e6b34a0ae29a9e7e6ea46f40bcaed3 Mon Sep 17 00:00:00 2001 From: Irfanuddin Date: Fri, 28 Oct 2022 22:40:11 +0530 Subject: [PATCH 58/58] remove white space --- tests/protocols/test_websocket.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/protocols/test_websocket.py b/tests/protocols/test_websocket.py index 1ade0ffa9..7b0619cae 100644 --- a/tests/protocols/test_websocket.py +++ b/tests/protocols/test_websocket.py @@ -756,5 +756,3 @@ async def open_connection(url): async with run_server(config): headers = await open_connection("ws://127.0.0.1:8000") assert headers.get_all("Server") == ["uvicorn", "over-ridden", "another-value"] - -