FastAPI GET endpoint returns "405 method not allowed" response

Question:

A GET endpoint in FastAPI is returning correct result, but returns 405 method not allowed when curl -I is used. This is happening with all the GET endpoints. As a result, the application is working, but health check on application from a load balancer is failing.

Any suggestions what could be wrong?

code

@app.get('/health')
async def health():
    """
    Returns health status
    """
    return JSONResponse({'status': 'ok'})

result

curl http://172.xx.xx.xx:8080

return header

curl -I http://172.xx.xx.xx:8080

Asked By: toing

||

Answers:

FastAPI has a bug. It allows a GET method on that path, not a HEAD method.

The curl -I option is equivalent to the --head. It sends an HTTP HEAD request.

Because the FastAPI server does not support the HEAD method, only the GET it correctly responds with a HTTP-405.

There is a workaround – to add manually defined @app.head().

Answered By: John Williams

The curl -I option (which is used in the example you provided) is the same as using curl --head and performrs an HTTP HEAD request, in order to fetch the headers only (not the body/content of the resource):

The HTTP HEAD method requests the headers that would be
returned if the HEAD request’s URL was instead requested with the
HTTP GET method. For example, if a URL might produce a large
download, a HEAD request could read its Content-Length header
to check the filesize without actually downloading the file.

The requested resource/endpoint you are trying to call supports only GET requests; hence, the 405 Method Not Allowed response status code, which indicates that the server knows the request method, but the target resource doesn’t support this method.

To demonstrate this, have a look at the example below:

from fastapi import FastAPI

app = FastAPI()

@app.get('/')
async def main():
    return {'Hello': 'World'}

Test using Python requests (similar result is obtained using curl -I http://127.0.0.1:8000)

import requests

# Making a GET request
# r = requests.get('http://127.0.0.1:8000')
  
# Making a HEAD request
r = requests.head('http://127.0.0.1:8000')
  
# check status code for response received
print(r.status_code, r.reason)
  
# print headers of request
print(r.headers)
  
# checking if request contains any content
print(r.content)

Output (indicating by the allow response header which request methods are supported by the requested resource):

405 Method Not Allowed
{'date': 'Sun, 12 Mar 2023', 'server': 'uvicorn', 'allow': 'GET', 'content-length': '31', 'content-type': 'application/json'}
b''

If, instead, you performed a GET request (in order to issue a GET request in the example above, uncomment the line for GET request and comment the one for HEAD request, or in curl use curl http://127.0.0.1:8000), the response would be as follows:

200 OK
{'date': 'Sun, 12 Mar 2023', 'server': 'uvicorn', 'content-length': '17', 'content-type': 'application/json'}
b'{"Hello":"World"}'

Solutions

To make a FastAPI endpoint supporting more than one HTTP request methods (e.g., both GET and HEAD requests), the following solutions are available.

Solution 1

Add a decorator for each request method that you would like the endpoint to support. For instance:

from fastapi import FastAPI

app = FastAPI()

@app.head('/')
@app.get('/')
async def main():
    return {'Hello': 'World'}

Solution 2

Use the @app.api_route() decorator, which allows you to define the set of supported request methods for the endpoint. For example:

from fastapi import FastAPI

app = FastAPI()
 
@app.api_route('/', methods=['GET', 'HEAD'])
async def main():
    return {'Hello': 'World'}

Output

Both solutions above would respond as follows (when a HEAD request is issued by a client):

200 OK
{'date': 'Sun, 12 Mar 2023', 'server': 'uvicorn', 'content-length': '17', 'content-type': 'application/json'}
b''

Note that 405 Method Not Allowed response may also be caused by other reasons—see related answers here and here, as well as here and here.

Answered By: Chris