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 = {