flask-restx icon indicating copy to clipboard operation
flask-restx copied to clipboard

405 Method Not Allowed on a namespace route give Internal Server Error

Open atselvan opened this issue 4 years ago • 5 comments

Hi All,

I am getting a weird issue! I am new to python and the flask + flast-restx framework. Can someone please help me understand what I am doing wrong here.

Code01

from flask import Flask, jsonify, request
from flask_restx import Resource, Api

app = Flask(__name__)
api = Api(
            app, version='1.0', title='TodoMVC API',
            description='A simple TodoMVC API',
         )
#ns = api.namespace('todos', description='TODO operations')


@app.errorhandler(404)
def page_not_found(e):
    msg = f"The requested URL path {request.path} was not found on the server."
    return jsonify(error=msg), 404


@app.errorhandler(405)
def method_not_allowed(e):
    msg = f"The requested method '{request.method}' is not allowed."
    return jsonify(error=msg), 405


@api.route('/todos')
class RepositoryApi(Resource):
    '''Shows a list of all todos, and lets you POST to add new tasks'''

    @api.doc('list_todos')
    def get(self):
        '''List all tasks'''
        return "Get done!", 200

    @api.doc('create_todo')
    def post(self):
        '''Create a new task'''
        return "Post done!", 201


if __name__ == "__main__":
    app.run()

If I execute above code with below curl command:

curl --location --request PUT 'http://127.0.0.1:5000/todos'

I get:

{"message":"The method is not allowed for the requested URL."}

Which is what I expect!

Code02

from flask import Flask, jsonify, request
from flask_restx import Resource, Api

app = Flask(__name__)
api = Api(
            app, version='1.0', title='TodoMVC API',
            description='A simple TodoMVC API',
         )

ns = api.namespace('todos', description='TODO operations')


@app.errorhandler(404)
def page_not_found(e):
    msg = f"The requested URL path {request.path} was not found on the server."
    return jsonify(error=msg), 404


@app.errorhandler(405)
def method_not_allowed(e):
    msg = f"The requested method '{request.method}' is not allowed."
    return jsonify(error=msg), 405


@ns.route('/')
class RepositoryApi(Resource):
    '''Shows a list of all todos, and lets you POST to add new tasks'''

    @ns.doc('list_todos')
    def get(self):
        '''List all tasks'''
        return "Get done!", 200

    @ns.doc('create_todo')
    def post(self):
        '''Create a new task'''
        return "Post done!", 201


if __name__ == "__main__":
    app.run()

The only delta between Code01 and Code02 is the use of a namespace to implement the /todos path

when I execute Code02 and then run the same curl command I end up with a internal server error:

curl --location --request PUT 'http://127.0.0.1:5000/todos'
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
<title>500 Internal Server Error</title>
<h1>Internal Server Error</h1>
<p>The server encountered an internal error and was unable to complete your request. Either the server is overloaded or there is an error in the application.</p>

and it generates the below stack trace

Error Messages/Stack Trace

* Serving Flask app "app" (lazy loading)
 * Environment: production
   WARNING: This is a development server. Do not use it in a production deployment.
   Use a production WSGI server instead.
 * Debug mode: off
 * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
127.0.0.1 - - [21/Jun/2020 19:49:52] "PUT /todos HTTP/1.1" 500 -
Error on request:
Traceback (most recent call last):
  File "/Users/atselvan/workspace/code/py/pynxrm/venv/lib/python3.7/site-packages/flask/app.py", line 1950, in full_dispatch_request
    rv = self.dispatch_request()
  File "/Users/atselvan/workspace/code/py/pynxrm/venv/lib/python3.7/site-packages/flask/app.py", line 1926, in dispatch_request
    self.raise_routing_exception(req)
  File "/Users/atselvan/workspace/code/py/pynxrm/venv/lib/python3.7/site-packages/flask/app.py", line 1908, in raise_routing_exception
    raise request.routing_exception
  File "/Users/atselvan/workspace/code/py/pynxrm/venv/lib/python3.7/site-packages/flask/ctx.py", line 350, in match_request
    result = self.url_adapter.match(return_rule=True)
  File "/Users/atselvan/workspace/code/py/pynxrm/venv/lib/python3.7/site-packages/werkzeug/routing.py", line 1940, in match
    raise MethodNotAllowed(valid_methods=list(have_match_for))
werkzeug.exceptions.MethodNotAllowed: 405 Method Not Allowed: The method is not allowed for the requested URL.

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/Users/atselvan/workspace/code/py/pynxrm/venv/lib/python3.7/site-packages/flask_restx/api.py", line 599, in _should_use_fr_error_handler
    adapter.match()
  File "/Users/atselvan/workspace/code/py/pynxrm/venv/lib/python3.7/site-packages/werkzeug/routing.py", line 1940, in match
    raise MethodNotAllowed(valid_methods=list(have_match_for))
werkzeug.exceptions.MethodNotAllowed: 405 Method Not Allowed: The method is not allowed for the requested URL.

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/Users/atselvan/workspace/code/py/pynxrm/venv/lib/python3.7/site-packages/werkzeug/routing.py", line 1873, in match
    rv = rule.match(path, method)
  File "/Users/atselvan/workspace/code/py/pynxrm/venv/lib/python3.7/site-packages/werkzeug/routing.py", line 911, in match
    raise RequestPath(path)
werkzeug.routing.RequestPath: /todos/

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/Users/atselvan/workspace/code/py/pynxrm/venv/lib/python3.7/site-packages/flask/app.py", line 2447, in wsgi_app
    response = self.full_dispatch_request()
  File "/Users/atselvan/workspace/code/py/pynxrm/venv/lib/python3.7/site-packages/flask/app.py", line 1952, in full_dispatch_request
    rv = self.handle_user_exception(e)
  File "/Users/atselvan/workspace/code/py/pynxrm/venv/lib/python3.7/site-packages/flask_restx/api.py", line 634, in error_router
    if self._has_fr_route():
  File "/Users/atselvan/workspace/code/py/pynxrm/venv/lib/python3.7/site-packages/flask_restx/api.py", line 614, in _has_fr_route
    if self._should_use_fr_error_handler():
  File "/Users/atselvan/workspace/code/py/pynxrm/venv/lib/python3.7/site-packages/flask_restx/api.py", line 603, in _should_use_fr_error_handler
    rule, _ = adapter.match(method=valid_route_method, return_rule=True)
  File "/Users/atselvan/workspace/code/py/pynxrm/venv/lib/python3.7/site-packages/werkzeug/routing.py", line 1878, in match
    query_args,
werkzeug.routing.RequestRedirect: 308 Permanent Redirect: None

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/Users/atselvan/workspace/code/py/pynxrm/venv/lib/python3.7/site-packages/flask_restx/api.py", line 599, in _should_use_fr_error_handler
    adapter.match()
  File "/Users/atselvan/workspace/code/py/pynxrm/venv/lib/python3.7/site-packages/werkzeug/routing.py", line 1940, in match
    raise MethodNotAllowed(valid_methods=list(have_match_for))
werkzeug.exceptions.MethodNotAllowed: 405 Method Not Allowed: The method is not allowed for the requested URL.

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/Users/atselvan/workspace/code/py/pynxrm/venv/lib/python3.7/site-packages/werkzeug/routing.py", line 1873, in match
    rv = rule.match(path, method)
  File "/Users/atselvan/workspace/code/py/pynxrm/venv/lib/python3.7/site-packages/werkzeug/routing.py", line 911, in match
    raise RequestPath(path)
werkzeug.routing.RequestPath: /todos/

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/Users/atselvan/workspace/code/py/pynxrm/venv/lib/python3.7/site-packages/werkzeug/serving.py", line 323, in run_wsgi
    execute(self.server.app)
  File "/Users/atselvan/workspace/code/py/pynxrm/venv/lib/python3.7/site-packages/werkzeug/serving.py", line 312, in execute
    application_iter = app(environ, start_response)
  File "/Users/atselvan/workspace/code/py/pynxrm/venv/lib/python3.7/site-packages/flask/app.py", line 2464, in __call__
    return self.wsgi_app(environ, start_response)
  File "/Users/atselvan/workspace/code/py/pynxrm/venv/lib/python3.7/site-packages/flask/app.py", line 2450, in wsgi_app
    response = self.handle_exception(e)
  File "/Users/atselvan/workspace/code/py/pynxrm/venv/lib/python3.7/site-packages/flask_restx/api.py", line 634, in error_router
    if self._has_fr_route():
  File "/Users/atselvan/workspace/code/py/pynxrm/venv/lib/python3.7/site-packages/flask_restx/api.py", line 614, in _has_fr_route
    if self._should_use_fr_error_handler():
  File "/Users/atselvan/workspace/code/py/pynxrm/venv/lib/python3.7/site-packages/flask_restx/api.py", line 603, in _should_use_fr_error_handler
    rule, _ = adapter.match(method=valid_route_method, return_rule=True)
  File "/Users/atselvan/workspace/code/py/pynxrm/venv/lib/python3.7/site-packages/werkzeug/routing.py", line 1878, in match
    query_args,
werkzeug.routing.RequestRedirect: 308 Permanent Redirect: None

Environment

Python version: 3.7.3 pip packages: aniso8601==8.0.0 attrs==19.3.0 click==7.1.2 Flask==1.1.2 flask-restx==0.2.0 importlib-metadata==1.6.1 itsdangerous==1.1.0 Jinja2==2.11.2 jsonschema==3.2.0 MarkupSafe==1.1.1 pyrsistent==0.16.0 pytz==2020.1 six==1.15.0 Werkzeug==1.0.1 zipp==3.1.0

Regards, Allan Selvan

atselvan avatar Jun 21 '20 18:06 atselvan

Hi, I'm also facing this issue. Here's the stack trace:

2020-12-24 05:49:37,644 - api.restplus - ERROR - 405 Method Not Allowed: The method is not allowed for the requested URL. Traceback (most recent call last): File "/usr/lib/python3.8/site-packages/flask/app.py", line 1950, in full_dispatch_request rv = self.dispatch_request() File "/usr/lib/python3.8/site-packages/flask/app.py", line 1926, in dispatch_request self.raise_routing_exception(req) File "/usr/lib/python3.8/site-packages/flask/app.py", line 1908, in raise_routing_exception raise request.routing_exception File "/usr/lib/python3.8/site-packages/flask/ctx.py", line 350, in match_request result = self.url_adapter.match(return_rule=True) File "/usr/lib/python3.8/site-packages/werkzeug/routing.py", line 1940, in match raise MethodNotAllowed(valid_methods=list(have_match_for)) werkzeug.exceptions.MethodNotAllowed: 405 Method Not Allowed: The method is not allowed for the requested URL. 2020-12-24 05:49:37,647 - app - ERROR - Exception on /health/datadump [GET] Traceback (most recent call last): File "/usr/lib/python3.8/site-packages/flask/app.py", line 1950, in full_dispatch_request rv = self.dispatch_request() File "/usr/lib/python3.8/site-packages/flask/app.py", line 1926, in dispatch_request self.raise_routing_exception(req) File "/usr/lib/python3.8/site-packages/flask/app.py", line 1908, in raise_routing_exception raise request.routing_exception File "/usr/lib/python3.8/site-packages/flask/ctx.py", line 350, in match_request result = self.url_adapter.match(return_rule=True) File "/usr/lib/python3.8/site-packages/werkzeug/routing.py", line 1940, in match raise MethodNotAllowed(valid_methods=list(have_match_for)) werkzeug.exceptions.MethodNotAllowed: 405 Method Not Allowed: The method is not allowed for the requested URL.

During handling of the above exception, another exception occurred:

Traceback (most recent call last): File "/usr/lib/python3.8/site-packages/flask/app.py", line 2447, in wsgi_app response = self.full_dispatch_request() File "/usr/lib/python3.8/site-packages/flask/app.py", line 1952, in full_dispatch_request rv = self.handle_user_exception(e) File "/usr/lib/python3.8/site-packages/flask_restx/api.py", line 638, in error_router return original_handler(f) File "/usr/lib/python3.8/site-packages/flask/app.py", line 1821, in handle_user_exception reraise(exc_type, exc_value, tb) File "/usr/lib/python3.8/site-packages/flask/_compat.py", line 39, in reraise raise value File "/usr/lib/python3.8/site-packages/flask_restx/api.py", line 636, in error_router return self.handle_error(e) File "/usr/lib/python3.8/site-packages/flask_restx/api.py", line 698, in handle_error default_data["message"] = default_data.get("message", str(e)) AttributeError: 'Response' object has no attribute 'get'

mjmadhu avatar Dec 24 '20 05:12 mjmadhu

I had the same issue! Any news?

rdji20 avatar Jan 21 '21 04:01 rdji20

Faced the same issue. When using flask_restx with namespaces, what should return a 405 (Method Not Allowed) ends up returning a 500 (Internal Server Error) because of redirection within werkzeug and trailing slashes (or lack thereof) (triggering 308).

Turns out werkzeug has a strict_slashes parameter you can use to circumvent the issue.

Rules that end with a slash are “branches”, others are “leaves”. If strict_slashes is enabled (the default), visiting a branch URL without a trailing slash will redirect to the URL with a slash appended.
Ref : https://werkzeug.palletsprojects.com/en/1.0.x/routing/

app = Flask(__name__)
app.url_map.strict_slashes = False

Adjusting this parameter changes the entire behavior of your application. Use with care.

MaximeMaxime avatar Jan 26 '21 21:01 MaximeMaxime

I got the same error and solve with this impl: https://stackoverflow.com/a/57921890 the ExtendedAPI allow to control the exception come from erkzeug.exceptions.MethodNotAllowed:.

mh4x0f avatar Jun 30 '22 13:06 mh4x0f

app = Flask(__name__)
app.url_map.strict_slashes = False```

is work it  Turns out werkzeug has a strict_slashes  parameter you can use to circumvent the issue.👍

nandasafiqalfiansyah avatar Jan 05 '24 00:01 nandasafiqalfiansyah