【问题标题】:Global error handler for any exception任何异常的全局错误处理程序
【发布时间】:2015-06-02 15:11:33
【问题描述】:

有没有办法添加一个全局包罗万象的错误处理程序,我可以在其中将响应更改为通用 JSON 响应?

我不能使用got_request_exception 信号,因为它不允许修改响应 (http://flask.pocoo.org/docs/0.10/signals/)。

相比之下,所有信号处理程序都以未定义的顺序执行并且不修改任何数据。

我宁愿不包装 app.handle_exception 函数,因为这感觉像是内部 API。我想我在追求类似的东西:

 @app.errorhandler()
 def handle_global_error(e):
     return "Global error"

注意errorhandler 不带任何参数,这意味着它将捕获所有没有附加特定错误处理程序的异常/状态代码。我知道我可以使用errorhandler(500)errorhandler(Exception) 来捕获异常,但如果我使用abort(409) 为例,它仍然会返回一个HTML 响应。

【问题讨论】:

    标签: python error-handling flask http-error


    【解决方案1】:

    可以为非常通用的基类注册错误处理程序,例如HTTPException 甚至Exception。但是,请注意,这些会比您预期的更多。

    例如,HTTPException 的错误处理程序可能有助于将默认 HTML 错误页面转换为 JSON。但是,此处理程序将触发您不直接导致的事情,例如路由期间的 404 和 405 错误。请务必仔细设计您的处理程序,以免丢失有关 HTTP 错误的信息。

    from flask import Flask, abort, jsonify, json
    from werkzeug.exceptions import HTTPException
    
    
    app = Flask('test')
    app.config['JSON_SORT_KEYS'] = False
    
    
    @app.errorhandler(HTTPException)
    def handle_exception(e):
        """Return JSON instead of HTML for HTTP errors."""
        # start with the correct headers and status code from the error
        response = e.get_response()
        # replace the body with JSON
        response.data = json.dumps({
            "error": {
                "code": e.code,
                "name": e.name,
                "description": e.description,
            }
        })
        print(response.data) 
        response.content_type = "application/json"
        return response
    
    
    @app.route('/')
    def index():
        abort(409)
    
    
    @app.route('/aloha')
    def aloha():
        abort(400, "I'm not in the mood to talk!")
    
    
    app.run(port=1234)
    
    

    输出:

    Exception 的错误处理程序对于更改所有错误(甚至未处理的错误)向用户呈现的方式似乎很有用。但是,这类似于 except Exception:在 Python 中,它将捕获 所有 否则未处理的错误,包括所有 HTTP 状态代码。

    在大多数情况下,为更具体的异常注册处理程序会更安全。由于HTTPException 实例是有效的 WSGI 响应,因此您也可以直接传递它们。

    from werkzeug.exceptions import HTTPException
    
    @app.errorhandler(Exception)
    def handle_exception(e):
        # pass through HTTP errors
        if isinstance(e, HTTPException):
            return e
    
        # now you're handling non-HTTP exceptions only
        return render_template("500_generic.html", e=e), 500
    

    错误处理程序仍然尊重异常类层次结构。如果您同时为HTTPExceptionException 注册处理程序,则Exception 处理程序将不会处理HTTPException 子类,因为HTTPException 处理程序更具体。

    【讨论】:

      【解决方案2】:

      如果异常不起作用,您可以尝试app.register_error_handler(或以非装饰方式使用app.errorhandler

      来源:https://github.com/pallets/flask/issues/1837

      【讨论】:

        【解决方案3】:

        基于 Plain (non-HTML) error pages in REST api

        我想在不更改任何代码的情况下返回 json,所以我只是在我的代码顶部添加了以下内容

        @app.errorhandler(500)
        def error_500(exception):
            return jsonify({"error": str(exception)}), 500, {'Content-Type': 'application/json'}
        
        @app.errorhandler(400)
        def error_400(exception):
            return jsonify({"error": str(exception)}), 400, {'Content-Type': 'application/json'}
        

        【讨论】:

          【解决方案4】:

          在 Flask >=0.12 中实现这一点的一种更简洁的方法是为每个 Werkzeug 异常显式注册处理程序:

          from flask import jsonify
          from werkzeug.exceptions import HTTPException, default_exceptions
          
          app = Flask('test')
          
          def handle_error(error):
              code = 500
              if isinstance(error, HTTPException):
                  code = error.code
              return jsonify(error='error', code=code)
          
          for exc in default_exceptions:
              app.register_error_handler(exc, handle_error)
          

          【讨论】:

            【解决方案5】:

            你可以使用@app.errorhandler(Exception):

            演示(HTTPException 检查确保状态码被保留):

            from flask import Flask, abort, jsonify
            from werkzeug.exceptions import HTTPException
            
            app = Flask('test')
            
            @app.errorhandler(Exception)
            def handle_error(e):
                code = 500
                if isinstance(e, HTTPException):
                    code = e.code
                return jsonify(error=str(e)), code
            
            @app.route('/')
            def index():
                abort(409)
            
            app.run(port=1234)
            

            输出:

            $ http get http://127.0.0.1:1234/
            HTTP/1.0 409 CONFLICT
            Content-Length: 31
            Content-Type: application/json
            Date: Sun, 29 Mar 2015 17:06:54 GMT
            Server: Werkzeug/0.10.1 Python/3.4.3
            
            {
                "error": "409: Conflict"
            }
            
            $ http get http://127.0.0.1:1234/notfound
            HTTP/1.0 404 NOT FOUND
            Content-Length: 32
            Content-Type: application/json
            Date: Sun, 29 Mar 2015 17:06:58 GMT
            Server: Werkzeug/0.10.1 Python/3.4.3
            
            {
                "error": "404: Not Found"
            }
            

            如果您还想覆盖 Flask 中的默认 HTML 异常(以便它们也返回 JSON),请在 app.run 之前添加以下内容:

            from werkzeug.exceptions import default_exceptions
            for ex in default_exceptions:
                app.register_error_handler(ex, handle_error)
            

            对于较旧的 Flask 版本(

            from werkzeug import HTTP_STATUS_CODES
            for code in HTTP_STATUS_CODES:
                app.register_error_handler(code, handle_error)
            

            【讨论】:

            • 这太奇怪了。我正在使用 Flask 0.10.1 将您的代码复制粘贴到 Python shell 中,当使用 curl 请求这些端点时,我仍然会遇到 HTML 错误。你用的是什么版本的 Flask?
            • 谢谢,将其标记为已回答,因为它将在 Flask 的下一个版本中工作。也许你应该编辑你的答案,说它在当前版本中不起作用,这样发现这个问题的人就不会感到困惑?
            • 这在 0.11.1 中不起作用。它不会捕获 405 错误。我必须明确写@app.errorhandler(405)
            • 确实Exception 不再起作用了。这是一个倒退。他们为什么改变它?
            • @Houman 我认为 Flask 带有用于最常见 HTTP 错误的默认错误处理程序;因此,如果您的应用程序引发这些 HTTP 错误之一,则选择“最具体”的错误处理程序(而不是非常通用的 Exception)。我可以通过用 JSON 替换所有默认错误处理程序来解决这个问题(见编辑)
            【解决方案6】:

            这是 Flask 0.12 兼容的,并且是解决问题的一个很好的解决方案(它允许以 JSON 或任何其他格式呈现错误)

            from functools import wraps
            from flask import Flask, redirect, jsonify
            app = Flask(__name__)
            
            def get_http_exception_handler(app):
                """Overrides the default http exception handler to return JSON."""
                handle_http_exception = app.handle_http_exception
                @wraps(handle_http_exception)
                def ret_val(exception):
                    exc = handle_http_exception(exception)    
                    return jsonify({'code':exc.code, 'message':exc.description}), exc.code
                return ret_val
            
            # Override the HTTP exception handler.
            app.handle_http_exception = get_http_exception_handler(app)
            

            https://github.com/pallets/flask/issues/671#issuecomment-12746738

            【讨论】:

              【解决方案7】:

              远非优雅,但以下方法可以将HTTPException 的所有子类绑定到单个错误处理程序:

              from flask import jsonify
              from werkzeug.exceptions import HTTPException
              
              def handle_error(error):
                  code = 500
                  if isinstance(error, HTTPException):
                      code = error.code
                  return jsonify(error='error', code=code)
              
              for cls in HTTPException.__subclasses__():
                  app.register_error_handler(cls, handle_error)
              

              【讨论】:

              • 这段代码运行良好,但有一个缺点,可能会返回 http 状态 200,响应 json 代码为 500 和错误“X”。
              • 我通过将返回行更改为 json.dumps({'success': False, 'error': str(error)}), 500 来纠正这个问题
              猜你喜欢
              • 1970-01-01
              • 2014-04-17
              • 2012-12-31
              • 2011-05-19
              • 2011-08-31
              • 2010-12-05
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多