FastAPI GET endpoint returns "405 method not allowed" response
Asked Answered
D

2

5

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

Debit answered 12/3, 2023 at 7:55 Comment(3)
Does this answer your question? FastAPI rejecting POST request from javascript code but not from a 3rd party request application (insomnia)Motherinlaw
Please have a look at related answers here, as well as here and here.Motherinlaw
@Motherinlaw thanks. all your suggestions here are pertaining to PUT request being passed as GET. My request is GET to begin with and despite that i am seeing issues.Debit
M
3

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

Cretate two different functions with a decorator refering to the request method for which they should be called, i.e., @app.head() and @app.get(), or, if the logic of the function is the same for both the request methods, then simply add a decorator for each request method that you would like the endpoint to support to the same function. For instance:

from fastapi import FastAPI

app = FastAPI()

@app.head('/')
@app.get('/')
async def main():
    return {'msg': '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 {'msg': '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''

In both solutions, if you would like to distinguish between the methods used to call the endpoint, you could use the .method attribute of the Request object. For instance:

from fastapi import FastAPI, Request

app = FastAPI()
 
@app.api_route('/', methods=['GET', 'HEAD'])
async def main(request: Request):
    if request.method == 'GET':
        print('GET method was used')
    elif request.method == 'HEAD':
        print('HEAD method was used')
    
    return {'msg': 'Hello World'}

Note 1: In the test example using Python requests above, please avoid calling the json() method on the response when performing a HEAD request, as a response to a HEAD method request does not normally have a body, and it would raise an exception, if attempted calling r.json()—see this answer for more details. Instead, use print(r.content), as demonstrated in the example above on HEAD requests, if you would like to check whether or not there is a response body.

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

Motherinlaw answered 12/3, 2023 at 18:8 Comment(0)
U
1

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().

Unknit answered 12/3, 2023 at 8:48 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.