flask-restful / flask-restful

Simple framework for creating REST APIs
http://flask-restful.readthedocs.io
BSD 3-Clause "New" or "Revised" License
6.83k stars 1.04k forks source link

`HTTPException` is unhandled in `Flask-RESTful==0.3.6` #783

Open remeika opened 6 years ago

remeika commented 6 years ago

Hi there! It seems to me like werkzeug.exceptions.HTTPException is not handled correctly in Flask-RESTful>0.3.3

Would love to know what I am doing wrong in the following application:

from flask import Flask
from flask_restful import Resource, Api
from werkzeug.exceptions import HTTPException

class ApiError(HTTPException):
    def __init__(self, message=None):
        self.message = message
        self.code = 400

ERRORS = {
    'ApiError': {
        'message': "A user with that username already exists.",
        'status': 409,
    }
}

class Fail(Resource):
    def get(self):
        raise ApiError("This will fail weirdly")

app = Flask(__name__)
api = Api(app, errors=ERRORS)
api.add_resource(Fail, '/fail')

Works as expected with Flask-RESTful==0.3.3

When run in the with Flask-RESTful 0.3.3 environment, it returns the expected response:

Working requirements file:

Flask==1.0.2
Flask-RESTful==0.3.3

I receive the expected response:

$ curl http://127.0.0.1:5000/fail
{"status": 409, "message": "A user with that username already exists."}

Fails with Flask-RESTful==0.3.6

Failing requirements file:

Flask==1.0.2
Flask-RESTful==0.3.6

Unexpected response:

$ curl http://127.0.0.1:5000/fail
{"message": "Internal Server Error"}

Trackback:

[2018-10-16 23:01:02,261] ERROR in app: Exception on /fail [GET]
Traceback (most recent call last):
  File ".../lib/python2.7/site-packages/flask/app.py", line 2292, in wsgi_app
    response = self.full_dispatch_request()
  File ".../lib/python2.7/site-packages/flask/app.py", line 1815, in full_dispatch_request
    rv = self.handle_user_exception(e)
  File ".../lib/python2.7/site-packages/flask_restful/__init__.py", line 273, in error_router
    return original_handler(e)
  File ".../lib/python2.7/site-packages/flask/app.py", line 1695, in handle_user_exception
    assert exc_value is e
AssertionError

Note that Flask-RESTful==0.3.6 works if I use the builtin Exception class instead of HTTPException above. Any ideas on how I could work around this to continue using HTTPException?

Thanks! James

thuitaw commented 5 years ago

Check this out @remeika I suspect this two issues are related >> https://github.com/flask-restful/flask-restful/issues/685 I suspect

labeneator commented 5 years ago

0.3.7 (via b47780a) has been released. Please verify that the bug has been fixed and update this issue.

svorcan commented 5 years ago

I had same problem with 0.3.6 and after upgrade to 0.3.7 I get following HTML output instead of expected JSON response:

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
<title>None Unknown Error</title>
<h1>Unknown Error</h1>
<p></p>
viveksoundrapandi commented 5 years ago

I tried replicating the issue, couldnt replicate. Can we close the issue?

remeika commented 5 years ago

@viveksoundrapandi @labeneator I can still reproduce this error with Flask-RESTful==0.3.7. When running in python 2, I get the same traceback I noted above:

File ".../virtualenvs/flask-restful-test/lib/python2.7/site-packages/flask/app.py", line 2292, in wsgi_app
    response = self.full_dispatch_request()
  File ".../virtualenvs/flask-restful-test/lib/python2.7/site-packages/flask/app.py", line 1815, in full_dispatch_request
    rv = self.handle_user_exception(e)
  File ".../virtualenvs/flask-restful-test/lib/python2.7/site-packages/flask_restful/__init__.py", line 269, in error_router
    return original_handler(e)
  File ".../virtualenvs/flask-restful-test/lib/python2.7/site-packages/flask/app.py", line 1695, in handle_user_exception
    assert exc_value is e
AssertionError

When running in python 3, I get a different error:

File "/usr/local/lib/python3.7/site-packages/flask/app.py", line 2446, in wsgi_app
    response = self.full_dispatch_request()
  File "/usr/local/lib/python3.7/site-packages/flask/app.py", line 1952, in full_dispatch_request
    return self.finalize_request(rv)
  File "/usr/local/lib/python3.7/site-packages/flask/app.py", line 1967, in finalize_request
    response = self.make_response(rv)
  File "/usr/local/lib/python3.7/site-packages/flask/app.py", line 2116, in make_response
    rv = self.response_class.force_type(rv, request.environ)
  File "/usr/local/lib/python3.7/site-packages/werkzeug/wrappers/base_response.py", line 269, in force_type
    response = BaseResponse(*_run_wsgi_app(response, environ))
  File "/usr/local/lib/python3.7/site-packages/werkzeug/wrappers/base_response.py", line 26, in _run_wsgi_app
    return _run_wsgi_app(*args)
  File "/usr/local/lib/python3.7/site-packages/werkzeug/test.py", line 1119, in run_wsgi_app
    app_rv = app(environ, start_response)
  File "/usr/local/lib/python3.7/site-packages/werkzeug/exceptions.py", line 193, in __call__
    response = self.get_response(environ)
  File "/usr/local/lib/python3.7/site-packages/werkzeug/exceptions.py", line 179, in get_response
    if self.response is not None:
AttributeError: 'ApiError' object has no attribute 'response'
alexissavin commented 4 years ago

Hello there,

I do reproduce the same issue using blueprints, flask-restful 0.3.6 or 0.3.7 combined with flask_jwt_extended and associated decorator.

The basic app looks like this:

App:

import os
from flask import Flask, jsonify, request
from flask_jwt_extended import JWTManager, create_access_token, jwt_required
from flask_restful import Resource, Api

def init_app():
  config_name = os.environ.get('FLASK_CONFIG', 'dev')

  app = Flask(__name__)

  app.config.from_object('config_' + config_name)

  jwt = JWTManager(app)

  @app.route('/login', methods=['POST'])
  def login():
    if not request.is_json:
      return jsonify({"msg": "Missing JSON in request"}), 400

    username = request.json.get('username', None)
    password = request.json.get('password', None)
    if not username:
      return jsonify({"msg": "Missing username parameter"}), 400
    if not password:
      return jsonify({"msg": "Missing password parameter"}), 400

    if username != 'test' or password != 'test':
      return jsonify({"msg": "Bad username or password"}), 401

    # Identity can be any data that is json serializable
    access_token = create_access_token(identity=username)
    return jsonify(access_token=access_token), 200

  # API object handler
  from .entities import entities_pb
  app.register_blueprint(entities_pb)

  return app

Blueprint:

from flask import Blueprint
from flask_jwt_extended import jwt_required, get_jwt_identity
from flask_restful import Api, Resource, url_for

entities_pb = Blueprint('entities', __name__)
entities = Api(entities_pb)

class EntitiesList(Resource):
  method_decorators = [jwt_required]

  def get(self):
    return {
      'entity1': 'entity1 name',
      'entity2': 'entity2 name',
      'entity3': 'entity3 name'
    }

  def post(self):
    args = parser.parse_args()
    return {'entity': 'entity name'}, 200

entities.add_resource(EntitiesList, '/entities')

This result in the following error as soon as the authentication token is not provided:

python manage.py runserver
 * 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)
[2019-12-30 15:03:59,313] ERROR in app: Exception on /entities [GET]
Traceback (most recent call last):
  File "/home/asavin/Repositories/mordekaiser/venv/lib/python3.7/site-packages/flask/app.py", line 1949, in full_dispatch_request
    rv = self.dispatch_request()
  File "/home/asavin/Repositories/mordekaiser/venv/lib/python3.7/site-packages/flask/app.py", line 1935, in dispatch_request
    return self.view_functions[rule.endpoint](**req.view_args)
  File "/home/asavin/Repositories/mordekaiser/venv/lib/python3.7/site-packages/flask_restful/__init__.py", line 458, in wrapper
    resp = resource(*args, **kwargs)
  File "/home/asavin/Repositories/mordekaiser/venv/lib/python3.7/site-packages/flask/views.py", line 89, in view
    return self.dispatch_request(*args, **kwargs)
  File "/home/asavin/Repositories/mordekaiser/venv/lib/python3.7/site-packages/flask_restful/__init__.py", line 573, in dispatch_request
    resp = meth(*args, **kwargs)
  File "/home/asavin/Repositories/mordekaiser/venv/lib/python3.7/site-packages/flask_jwt_extended/view_decorators.py", line 107, in wrapper
    verify_jwt_in_request()
  File "/home/asavin/Repositories/mordekaiser/venv/lib/python3.7/site-packages/flask_jwt_extended/view_decorators.py", line 32, in verify_jwt_in_request
    jwt_data, jwt_header = _decode_jwt_from_request(request_type='access')
  File "/home/asavin/Repositories/mordekaiser/venv/lib/python3.7/site-packages/flask_jwt_extended/view_decorators.py", line 314, in _decode_jwt_from_request
    raise NoAuthorizationError(errors[0])
flask_jwt_extended.exceptions.NoAuthorizationError: Missing Authorization Header
127.0.0.1 - - [30/Dec/2019 15:03:59] "GET /entities HTTP/1.1" 500 -

Surprisingly, activating the debug in Flask configuration file trigger the proper behavior:

python manage.py runserver
 * 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: on
 * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
 * Restarting with stat
 * Debugger is active!
 * Debugger PIN: 255-986-277
127.0.0.1 - - [30/Dec/2019 15:41:40] "GET /entities HTTP/1.1" 401 -

Current work around that seems to work in my case is inspired by https://github.com/flask-restful/flask-restful/issues/280:

from flask import Blueprint
from flask_jwt_extended import jwt_required, get_jwt_identity
from flask_restful import Api, Resource, url_for

class FixedApi(Api):
  def error_router(self, original_handler, e):
    return original_handler(e)

entities_bp = Blueprint('entities', __name__)
entities = FixedApi(entities_bp)

class EntitiesList(Resource):
  method_decorators = [jwt_required]

  def get(self):
    return {
      'entity1': 'entity1 name',
      'entity2': 'entity2 name',
      'entity3': 'entity3 name'
    }

  def post(self):
    args = parser.parse_args()
    return {'entity': 'entity name'}, 200

entities.add_resource(EntitiesList, '/entities')

Hope this help in the resolution of this issue.

Kind regards

alexissavin commented 4 years ago

Hello,

Digging into this issue a little bit more, I believe that at least in my case the issue is within the flask-restful error_router method which is not handling correctly all raised errors.

I'm currently using a different work-around excluding JWT raised errors:

class FixedApi(Api):
  def error_router(self, original_handler, e):
    if not isinstance(e, PyJWTError) and not isinstance(e, JWTExtendedException) and self._has_fr_route():
      try:
        return self.handle_error(e)
      except Exception:
        pass  # Fall through to original handler
    return original_handler(e)

I hope this will help.

Kind regards

Adityashaw commented 4 years ago

Can we know when will this be fixed?

akipham15 commented 1 year ago

Hello,

Digging into this issue a little bit more, I believe that at least in my case the issue is within the flask-restful error_router method which is not handling correctly all raised errors.

I'm currently using a different work-around excluding JWT raised errors:

class FixedApi(Api):
  def error_router(self, original_handler, e):
    if not isinstance(e, PyJWTError) and not isinstance(e, JWTExtendedException) and self._has_fr_route():
      try:
        return self.handle_error(e)
      except Exception:
        pass  # Fall through to original handler
    return original_handler(e)

I hope this will help.

Kind regards

save my life, thanks