我确实有一个简单的 Restful 应用程序,带有 Flask-Restful
from flask import Flask
from flask_restful import Api
app = Flask(__name__)
...
api = Api(app)
api.add_resource(ContactList, "/contacts")
if __name__ == '__main__':
from object_SQLAlchemy import db
db.init_app(app)
app.run(port=5000)
class Contact(Resource):
parser = reqparse.RequestParser()
parser.add_argument(
'contact_no',
type=str,
required=True,
help="This field cannot be left blank"
)
@throttling.Throttle("10/m", strategy=2)
def get(self, name):
contact = Contacts.findbyname(name)
if contact:
return contact.json()
return {"message": "Contact does not exist."}, 404
“get”方法用我的节流实现进行装饰(https://github.com/scgbckbone/RESTAPI/blob/master/resources/utils/throtdling.py)。重要的是节流装饰器在某些情况下会引发异常 - 最重要的是当达到限制时。我希望能够捕获该异常并返回一些合理的 json 消息。
但是以下都不起作用:
from ..app_alchemy import api, app
@api.errorhandler(Exception)
def handle_error(e):
return {"error": str(e)}
@app.errorhandler(500)
def handle_error_app(e):
return {"error": str(e.args[0])}
@app.handle_exception(Exception)
def handle_it_app(e):
return {"error": str(e.args[0])}
@api.handle_exception(Exception)
def handle_it(e):
return {"error": str(e.args[0])}
我仍然收到默认消息
{"message": "Internal Server Error"}
我是否正确使用了错误处理程序,或者问题是否与装饰器的使用有关?我真的不知道。
我重写了flask_restulf.Api并重写了里面的handle_error()和error_router()。 APIException 是 HttpException 的子类,UnknownException 是 APIException 的子类。我也想尝试使用Sentry作为异常记录器,但是Sentry似乎不能很好地与flask_restful配合使用,并且它还会影响多处理部分,所以我仍在寻找记录器的解决方案。
class Api_modified(Api):
def error_router(self, original_handler, e):
"""This function decides whether the error occured in a flask-restful
endpoint or not. If it happened in a flask-restful endpoint, our
handler will be dispatched. If it happened in an unrelated view, a UnknownException
will be returned.
In the event that the error occurred in a flask-restful endpoint but
the local handler can't resolve the situation, the router will return a
UnknownException.
:param original_handler: the original Flask error handler for the app
:type original_handler: function
:param e: the exception raised while handling the request
:type e: Exception
"""
if self._has_fr_route():
try:
return self.handle_error(e)
except Exception:
pass
else:
if isinstance(e,APIException):
return e.get_response()
else:
return UnknownException("未知原因错误,具体原因请查询日志").get_response()
def handle_error(self, e):
"""Error handler for the API transforms a raised exception into a Flask
response, with the appropriate HTTP and body.The status code is always 200, and
the message and result in the body varies.
:param e: the raised Exception object
:type e: Exception
"""
got_request_exception.send(current_app._get_current_object(), exception=e)
if not isinstance(e, HTTPException) and current_app.propagate_exceptions:
exc_type, exc_value, tb = sys.exc_info()
if exc_value is e:
raise UnknownException("未知原因错误,具体原因请查询日志")
else:
raise UnknownException("未知原因错误,具体原因请查询日志")
headers = Headers()
if isinstance(e, HTTPException):
if e.response is not None:
# If HTTPException is initialized with a response, then return e.get_response().
# This prevents specified error response from being overridden.
# eg. HTTPException(response=Response("Hello World"))
resp = e.get_response()
return resp
code = e.code
default_data = {
'message': getattr(e, 'description', http_status_message(code))
}
headers = e.get_response().headers
else:
resp = UnknownException("未知原因错误,具体原因请查询日志").get_response()
return resp
# Werkzeug exceptions generate a content-length header which is added
# to the response in addition to the actual content-length header
# https://github.com/flask-restful/flask-restful/issues/534
remove_headers = ('Content-Length',)
for header in remove_headers:
headers.pop(header, None)
data = getattr(e, 'data', default_data)
if code and code >= 500:
exc_info = sys.exc_info()
if exc_info[1] is None:
exc_info = None
current_app.log_exception(exc_info)
error_cls_name = type(e).__name__
if error_cls_name in self.errors:
custom_data = self.errors.get(error_cls_name, {})
code = custom_data.get('status', 500)
data.update(custom_data)
if code == 406 and self.default_mediatype is None:
# if we are handling NotAcceptable (406), make sure that
# make_response uses a representation we support as the
# default mediatype (so that make_response doesn't throw
# another NotAcceptable error).
supported_mediatypes = list(self.representations.keys())
fallback_mediatype = supported_mediatypes[0] if supported_mediatypes else "text/plain"
resp = self.make_response(
data,
code,
headers,
fallback_mediatype = fallback_mediatype
)
else:
resp = self.make_response(data, code, headers)
if code == 401:
resp = self.unauthorized(resp)
return resp