Skip to content

Commit

Permalink
add Allow header to 405 responses as required by RFC 7231 (#1436)
Browse files Browse the repository at this point in the history
  • Loading branch information
adriangb committed Jan 26, 2022
1 parent 0856392 commit 79812bb
Show file tree
Hide file tree
Showing 4 changed files with 15 additions and 4 deletions.
10 changes: 8 additions & 2 deletions starlette/endpoints.py
Expand Up @@ -17,6 +17,11 @@ def __init__(self, scope: Scope, receive: Receive, send: Send) -> None:
self.scope = scope
self.receive = receive
self.send = send
self._allowed_methods = [
method
for method in ("GET", "HEAD", "POST", "PUT", "PATCH", "DELETE", "OPTIONS")
if getattr(self, method.lower(), None) is not None
]

def __await__(self) -> typing.Generator:
return self.dispatch().__await__()
Expand All @@ -43,9 +48,10 @@ async def method_not_allowed(self, request: Request) -> Response:
# If we're running inside a starlette application then raise an
# exception, so that the configurable exception handler can deal with
# returning the response. For plain ASGI apps, just return the response.
headers = {"Allow": ", ".join(self._allowed_methods)}
if "app" in self.scope:
raise HTTPException(status_code=405)
return PlainTextResponse("Method Not Allowed", status_code=405)
raise HTTPException(status_code=405, headers=headers)
return PlainTextResponse("Method Not Allowed", status_code=405, headers=headers)


class WebSocketEndpoint:
Expand Down
7 changes: 5 additions & 2 deletions starlette/routing.py
Expand Up @@ -250,10 +250,13 @@ def url_path_for(self, name: str, **path_params: typing.Any) -> URLPath:

async def handle(self, scope: Scope, receive: Receive, send: Send) -> None:
if self.methods and scope["method"] not in self.methods:
headers = {"Allow": ", ".join(self.methods)}
if "app" in scope:
raise HTTPException(status_code=405)
raise HTTPException(status_code=405, headers=headers)
else:
response = PlainTextResponse("Method Not Allowed", status_code=405)
response = PlainTextResponse(
"Method Not Allowed", status_code=405, headers=headers
)
await response(scope, receive, send)
else:
await self.app(scope, receive, send)
Expand Down
1 change: 1 addition & 0 deletions tests/test_endpoints.py
Expand Up @@ -40,6 +40,7 @@ def test_http_endpoint_route_method(client):
response = client.post("/")
assert response.status_code == 405
assert response.text == "Method Not Allowed"
assert response.headers["allow"] == "GET"


def test_websocket_endpoint_on_connect(test_client_factory):
Expand Down
1 change: 1 addition & 0 deletions tests/test_routing.py
Expand Up @@ -161,6 +161,7 @@ def test_router(client):
response = client.post("/")
assert response.status_code == 405
assert response.text == "Method Not Allowed"
assert set(response.headers["allow"].split(", ")) == {"HEAD", "GET"}

response = client.get("/foo")
assert response.status_code == 404
Expand Down

0 comments on commit 79812bb

Please sign in to comment.