Skip to content

Commit

Permalink
Add a respond method to server connections.
Browse files Browse the repository at this point in the history
It's an alias of the reject method of the underlying server protocol.
It makes it easier to write process_request

It's called respond because the semantics are "consider the request
as an HTTP request and create an HTTP response".

There isn't a similar alias for accept because process_request should
just return and websockets will call accept.
  • Loading branch information
aaugustin committed Aug 17, 2024
1 parent 7c8e0b9 commit 7c1d1d9
Show file tree
Hide file tree
Showing 8 changed files with 66 additions and 19 deletions.
2 changes: 1 addition & 1 deletion docs/howto/upgrade.rst
Original file line number Diff line number Diff line change
Expand Up @@ -247,7 +247,7 @@ an example::
# New implementation

def process_request(connection, request):
return connection.protocol.reject(http.HTTPStatus.OK, "OK\n")
return connection.respond(http.HTTPStatus.OK, "OK\n")

serve(..., process_request=process_request, ...)

Expand Down
2 changes: 2 additions & 0 deletions docs/reference/new-asyncio/server.rst
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ Using a connection

.. automethod:: pong

.. automethod:: respond

WebSocket connection objects also provide these attributes:

.. autoattribute:: id
Expand Down
2 changes: 2 additions & 0 deletions docs/reference/sync/server.rst
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ Using a connection

.. automethod:: pong

.. automethod:: respond

WebSocket connection objects also provide these attributes:

.. autoattribute:: id
Expand Down
23 changes: 22 additions & 1 deletion src/websockets/asyncio/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
from ..http11 import SERVER, Request, Response
from ..protocol import CONNECTING, Event
from ..server import ServerProtocol
from ..typing import LoggerLike, Origin, Subprotocol
from ..typing import LoggerLike, Origin, StatusLike, Subprotocol
from .compatibility import asyncio_timeout
from .connection import Connection

Expand Down Expand Up @@ -75,6 +75,27 @@ def __init__(
self.server = server
self.request_rcvd: asyncio.Future[None] = self.loop.create_future()

def respond(self, status: StatusLike, text: str) -> Response:
"""
Create a plain text HTTP response.
``process_request`` and ``process_response`` may call this method to
return an HTTP response instead of performing the WebSocket opening
handshake.
You can modify the response before returning it, for example by changing
HTTP headers.
Args:
status: HTTP status code.
text: HTTP response body; it will be encoded to UTF-8.
Returns:
HTTP response to send to the client.
"""
return self.protocol.reject(status, text)

async def handshake(
self,
process_request: (
Expand Down
27 changes: 14 additions & 13 deletions src/websockets/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -113,18 +113,22 @@ def accept(self, request: Request) -> Response:
"""
Create a handshake response to accept the connection.
If the connection cannot be established, the handshake response
actually rejects the handshake.
If the handshake request is valid and the handshake successful,
:meth:`accept` returns an HTTP response with status code 101.
Else, it returns an HTTP response with another status code. This rejects
the connection, like :meth:`reject` would.
You must send the handshake response with :meth:`send_response`.
You may modify it before sending it, for example to add HTTP headers.
You may modify the response before sending it, typically by adding HTTP
headers.
Args:
request: WebSocket handshake request event received from the client.
request: WebSocket handshake request received from the client.
Returns:
WebSocket handshake response event to send to the client.
WebSocket handshake response or HTTP response to send to the client.
"""
try:
Expand Down Expand Up @@ -485,11 +489,7 @@ def select_subprotocol(protocol, subprotocols):
+ ", ".join(self.available_subprotocols)
)

def reject(
self,
status: StatusLike,
text: str,
) -> Response:
def reject(self, status: StatusLike, text: str) -> Response:
"""
Create a handshake response to reject the connection.
Expand All @@ -498,14 +498,15 @@ def reject(
You must send the handshake response with :meth:`send_response`.
You can modify it before sending it, for example to alter HTTP headers.
You may modify the response before sending it, for example by changing
HTTP headers.
Args:
status: HTTP status code.
text: HTTP response body; will be encoded to UTF-8.
text: HTTP response body; it will be encoded to UTF-8.
Returns:
WebSocket handshake response event to send to the client.
HTTP response to send to the client.
"""
# If a user passes an int instead of a HTTPStatus, fix it automatically.
Expand Down
23 changes: 22 additions & 1 deletion src/websockets/sync/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
from ..http11 import SERVER, Request, Response
from ..protocol import CONNECTING, OPEN, Event
from ..server import ServerProtocol
from ..typing import LoggerLike, Origin, Subprotocol
from ..typing import LoggerLike, Origin, StatusLike, Subprotocol
from .connection import Connection
from .utils import Deadline

Expand Down Expand Up @@ -66,6 +66,27 @@ def __init__(
close_timeout=close_timeout,
)

def respond(self, status: StatusLike, text: str) -> Response:
"""
Create a plain text HTTP response.
``process_request`` and ``process_response`` may call this method to
return an HTTP response instead of performing the WebSocket opening
handshake.
You can modify the response before returning it, for example by changing
HTTP headers.
Args:
status: HTTP status code.
text: HTTP response body; it will be encoded to UTF-8.
Returns:
HTTP response to send to the client.
"""
return self.protocol.reject(status, text)

def handshake(
self,
process_request: (
Expand Down
4 changes: 2 additions & 2 deletions tests/asyncio/test_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ async def test_process_request_abort_handshake(self):
"""Server aborts handshake if process_request returns a response."""

def process_request(ws, request):
return ws.protocol.reject(http.HTTPStatus.FORBIDDEN, "Forbidden")
return ws.respond(http.HTTPStatus.FORBIDDEN, "Forbidden")

async with run_server(process_request=process_request) as server:
with self.assertRaises(InvalidStatus) as raised:
Expand All @@ -159,7 +159,7 @@ async def test_async_process_request_abort_handshake(self):
"""Server aborts handshake if async process_request returns a response."""

async def process_request(ws, request):
return ws.protocol.reject(http.HTTPStatus.FORBIDDEN, "Forbidden")
return ws.respond(http.HTTPStatus.FORBIDDEN, "Forbidden")

async with run_server(process_request=process_request) as server:
with self.assertRaises(InvalidStatus) as raised:
Expand Down
2 changes: 1 addition & 1 deletion tests/sync/test_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ def test_process_request_abort_handshake(self):
"""Server aborts handshake if process_request returns a response."""

def process_request(ws, request):
return ws.protocol.reject(http.HTTPStatus.FORBIDDEN, "Forbidden")
return ws.respond(http.HTTPStatus.FORBIDDEN, "Forbidden")

with run_server(process_request=process_request) as server:
with self.assertRaises(InvalidStatus) as raised:
Expand Down

0 comments on commit 7c1d1d9

Please sign in to comment.