Skip to content
Closed
Show file tree
Hide file tree
Changes from all 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
10 changes: 8 additions & 2 deletions src/websockets/asyncio/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
from types import TracebackType
from typing import Any, Callable, Mapping, cast

from ..exceptions import InvalidHeader
from ..exceptions import InvalidHandshake, InvalidHeader, InvalidMessage
from ..extensions.base import ServerExtensionFactory
from ..extensions.permessage_deflate import enable_server_permessage_deflate
from ..frames import CloseCode
Expand Down Expand Up @@ -204,7 +204,13 @@ async def handshake(
# response that rejects the handshake.

if self.protocol.handshake_exc is not None:
raise self.protocol.handshake_exc
# Suppress re-raise for unsupported HTTP methods (HEAD, POST, etc.)
# — the connection is already being closed cleanly by send_eof().
if not (
isinstance(self.protocol.handshake_exc, InvalidMessage)
and isinstance(self.protocol.handshake_exc.__cause__, InvalidHandshake)
):
raise self.protocol.handshake_exc

def process_event(self, event: Event) -> None:
"""
Expand Down
6 changes: 4 additions & 2 deletions src/websockets/http11.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
from typing import Callable

from .datastructures import Headers
from .exceptions import SecurityError
from .exceptions import InvalidHandshake, SecurityError
from .version import version as websockets_version


Expand Down Expand Up @@ -148,7 +148,9 @@ def parse(
f"unsupported protocol; expected HTTP/1.1: {d(request_line)}"
)
if method != b"GET":
raise ValueError(f"unsupported HTTP method; expected GET; got {d(method)}")
raise InvalidHandshake(
f"unsupported HTTP method; expected GET; got {d(method)}"
)

# RFC 9110 defers the definition of URIs to RFC 3986, which allows only
# a subset of ASCII. Non-ASCII IRIs must be UTF-8 then percent-encoded.
Expand Down
12 changes: 12 additions & 0 deletions src/websockets/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -552,6 +552,18 @@ def parse(self) -> Generator[None]:
"did not receive a valid HTTP request"
)
self.handshake_exc.__cause__ = exc
if isinstance(exc, InvalidHandshake):
# Send HTTP 405 for unsupported methods — matching
# gorilla/websocket (StatusMethodNotAllowed) and ws
# (405 abortHandshake). send_response() already
# closes the connection for non-101 responses, so
# skip the common handler below.
response = self.reject(
405, "Only GET is supported.\n"
)
self.send_response(response)
yield
return
self.send_eof()
self.parser = self.discard()
next(self.parser) # start coroutine
Expand Down
10 changes: 8 additions & 2 deletions src/websockets/sync/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
from types import TracebackType
from typing import Any, Callable, Mapping, cast

from ..exceptions import InvalidHeader
from ..exceptions import InvalidHandshake, InvalidHeader, InvalidMessage
from ..extensions.base import ServerExtensionFactory
from ..extensions.permessage_deflate import enable_server_permessage_deflate
from ..frames import CloseCode
Expand Down Expand Up @@ -186,7 +186,13 @@ def handshake(
# response that rejects the handshake.

if self.protocol.handshake_exc is not None:
raise self.protocol.handshake_exc
# Suppress re-raise for unsupported HTTP methods (HEAD, POST, etc.)
# — the connection is already being closed cleanly by send_eof().
if not (
isinstance(self.protocol.handshake_exc, InvalidMessage)
and isinstance(self.protocol.handshake_exc.__cause__, InvalidHandshake)
):
raise self.protocol.handshake_exc

def process_event(self, event: Event) -> None:
"""
Expand Down
4 changes: 2 additions & 2 deletions tests/test_http11.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from websockets.datastructures import Headers
from websockets.exceptions import SecurityError
from websockets.exceptions import InvalidHandshake, SecurityError
from websockets.http11 import *
from websockets.http11 import parse_headers
from websockets.streams import StreamReader
Expand Down Expand Up @@ -61,7 +61,7 @@ def test_parse_unsupported_protocol(self):

def test_parse_unsupported_method(self):
self.reader.feed_data(b"OPTIONS * HTTP/1.1\r\n\r\n")
with self.assertRaises(ValueError) as raised:
with self.assertRaises(InvalidHandshake) as raised:
next(self.parse())
self.assertEqual(
str(raised.exception),
Expand Down