Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add Allow header to 405 responses as required by RFC 7231 #1436

Merged
merged 5 commits into from Jan 26, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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
]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Super. 🌟


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