diff --git a/starlette/endpoints.py b/starlette/endpoints.py index e27e4fe49..73367c257 100644 --- a/starlette/endpoints.py +++ b/starlette/endpoints.py @@ -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__() @@ -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: diff --git a/starlette/routing.py b/starlette/routing.py index a7d72cb55..84ffcb3fb 100644 --- a/starlette/routing.py +++ b/starlette/routing.py @@ -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) diff --git a/tests/test_endpoints.py b/tests/test_endpoints.py index e57d47486..9895a4559 100644 --- a/tests/test_endpoints.py +++ b/tests/test_endpoints.py @@ -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): diff --git a/tests/test_routing.py b/tests/test_routing.py index 231c581fb..dc28427ee 100644 --- a/tests/test_routing.py +++ b/tests/test_routing.py @@ -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