Fastapi: Automatically support HEAD method for all GET routes, as Starlette does

Created on 22 Jul 2020  路  5Comments  路  Source: tiangolo/fastapi

First check

  • [x] I added a very descriptive title to this issue.
  • [x] I used the GitHub search to find a similar issue and didn't find it.
  • [x] I searched the FastAPI documentation, with the integrated search.
  • [x] I already searched in Google "How to X in FastAPI" and didn't find any information.
  • [x] I already read and followed all the tutorial in the docs and didn't find an answer.
  • [x] I already checked if it is not related to FastAPI but to Pydantic.
  • [x] I already checked if it is not related to FastAPI but to Swagger UI.
  • [x] I already checked if it is not related to FastAPI but to ReDoc.
  • [x] After submitting this, I commit to:

    • Read open issues with questions until I find 2 issues where I can help someone and add a comment to help there.

    • Or, I already hit the "watch" button in this repository to receive notifications and I commit to help at least 2 people that ask questions in the future.

    • Implement a Pull Request for a confirmed bug.

Example

Here's a self-contained minimal, reproducible, example with my use case:

from fastapi import FastAPI
import pytest
from fastapi.testclient import TestClient

app = FastAPI()

@app.get("/")
def read_root():
    return {"Hello": "World"}

client = TestClient(app)

def test_index_head():
    response = client.head("/")
    assert response.status_code == 200

Description

  • The above test fails because the HEAD request returns a 405.
  • This is counterintuitive.

The solution you would like

  • To better support the HTTP standard, all routes that handle GET methods should automatically handle HEAD methods, too.
  • This is similar to what Starlette's router already does: https://github.com/encode/starlette/issues/45
  • This should happen without requiring the developer to do any additional work.

Describe alternatives you've considered

  • It's currently possible to do this manually by adding @app.head and a helper method for each route, but it's cumbersome:
@app.head("/")
def read_root_head():
    return Response()
  • It could also be done using middleware, but that would incur a performance overhead.

Environment

  • OS: [e.g. Linux / Windows / macOS]: Linux
  • FastAPI Version [e.g. 0.3.0]: 0.60.1
  • Python version: 3.8.5
confirmed enhancement

Most helpful comment

Not so, if you look at the related commit at https://github.com/encode/starlette/pull/132/files in starlette/routing.py, you can see Starlette adds HEAD methods automatically for every Route that supports GET, regardless of whether they are FileResponse or not.

Many web frameworks do this by default too, e.g. Flask (https://flask.palletsprojects.com/en/1.1.x/quickstart/#http-methods) and Django. It makes sense to do that because a server should respond to an HTTP HEAD request as defined in the spec, see e.g.:

https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/HEAD

"The HTTP HEAD method requests the headers that are returned if the specified resource would be requested with an HTTP GET method. Such a request can be done before deciding to download a large resource to save bandwidth, for example."

Clients use this for cache invalidation. The server just needs to run the route method, respond with the headers but discard the response body. Ideally, FastAPI would automatically add an OPTIONS method handler, too. This would help make FastAPI a first class web framework.

This is safe even for a dynamic API, because to follow REST principles, HTTP GET routes should always be idempotent and have no side-effects (as should HEAD, PUT, DELETE, OPTIONS and TRACE). Only HTTP POSTs are allowed to have side effects (see https://tools.ietf.org/html/rfc7231#section-4.2.2).

This behavior could be controlled by a flag defaulting to true.

All 5 comments

encode/starlette#45 is only about static FileResponse. I don't know how a default should be set for a dynamic API.

Not so, if you look at the related commit at https://github.com/encode/starlette/pull/132/files in starlette/routing.py, you can see Starlette adds HEAD methods automatically for every Route that supports GET, regardless of whether they are FileResponse or not.

Many web frameworks do this by default too, e.g. Flask (https://flask.palletsprojects.com/en/1.1.x/quickstart/#http-methods) and Django. It makes sense to do that because a server should respond to an HTTP HEAD request as defined in the spec, see e.g.:

https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/HEAD

"The HTTP HEAD method requests the headers that are returned if the specified resource would be requested with an HTTP GET method. Such a request can be done before deciding to download a large resource to save bandwidth, for example."

Clients use this for cache invalidation. The server just needs to run the route method, respond with the headers but discard the response body. Ideally, FastAPI would automatically add an OPTIONS method handler, too. This would help make FastAPI a first class web framework.

This is safe even for a dynamic API, because to follow REST principles, HTTP GET routes should always be idempotent and have no side-effects (as should HEAD, PUT, DELETE, OPTIONS and TRACE). Only HTTP POSTs are allowed to have side effects (see https://tools.ietf.org/html/rfc7231#section-4.2.2).

This behavior could be controlled by a flag defaulting to true.

working on this

Thanks @matthewlloyd for the very clear argument and especially for the self-contained example, that helps a lot.

Indeed, here in the spec: https://tools.ietf.org/html/rfc7231#section-4.3.2 it says:

The server SHOULD send the same header fields in response to a HEAD request as it would have sent if the request had been a GET [...]

And the OpenAPI spec doesn't say anything contradicting it, so FastAPI should support it.

I think ideally, it should handle HEAD requests automatically for GET requests, without adding them to the OpenAPI schema. I think HEAD operations should be added to OpenAPI only when explicitly declared in the code (ideally).


Thanks @victorphoenix3 for working on this! Let me know if you need any help with it.

As a side note, today, when making a HEAD request, you get a 405 returned, which according to spec*, should include an "Allow" header. I'm not if this is FastAPI or an underlying library returning this 405 - but maybe it should be changed as well.

Was this page helpful?
0 / 5 - 0 ratings