在Flask中使用Response对象时无法捕获异常

时间:2018-11-26 15:13:43

标签: python flask werkzeug

我正在尝试使用Response对象时捕获异常。我的用例是我要像official documentation中所述流数据,并且在获取要流的数据时可能会出现异常:

# test.py
from flask import Flask, Response, jsonify
import werkzeug

app = Flask(__name__)

@app.route("/")
def hello():
    def generate():    
        raise Exception                                                                                                                                                                                                                                           
        for i in range(0,10): 
            yield '1'    

    try:
        return Response(generate())
    except Exception:
        return jsonify("error") # expected this but instead 500 server error is returned

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

然后我运行服务器并请求数据时,我希望看到“错误”,但会显示Flask发出的“内部服务器错误”消息。

FLASK_APP=test.py flask run
curl http://localhost:5000/

<!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>

以某种方式引发异常,因为我们可以在stdout中看到它,但是不会被Flask捕获。 我还试图通过设置werkzeug服务器的passthrough_errors选项来确保不捕获异常。然后在Werkzeug中不会捕获异常,例如herehere。不幸的是,这没有帮助在上面的Flask应用程序中捕获它:

app.run(passthrough_errors=True)

flask的输出--version:

Flask 1.0.2
Python 3.7.0 (default, Aug 22 2018, 15:22:33) 
[Clang 9.1.0 (clang-902.0.39.2)]

更新: 当我不使用流媒体时,也会发生此问题:

from flask import Flask, Response, jsonify

app = Flask(__name__)

@app.route("/")
def hello():
    try:
        return Response(2)
    except Exception as error:
        return jsonify({'error': error})

if __name__ == '__main__':
    app.run(passthrough_errors=False) 

在Flask端引发TypeError,因为Integer不可迭代,但未捕获到异常。

FLASK_APP=test.py flask run
curl http://localhost:5000/

<!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>

3 个答案:

答案 0 :(得分:0)

这与发生器功能有关,与烧瓶无关。

def check_positive(x):
    if isinstance(x, (datetime.timedelta, np.timedelta64)):
        return x.total_seconds() > 0
    else:
        return x > 0

将返回一个生成器,而不是数字1或异常! (尝试打印def generate(): raise Exception yield '1' )。

还尝试将generate()更改为yield,并检查差异。

但是现在解决方案:

return

答案 1 :(得分:0)

让我们更改代码,然后再次运行:

@app.route("/")
def hello():
    def generate():    
        raise Exception                                                                                                                                                                                                                                           
        yield '1'    

    try:
        resp = Response(generate())
        data = resp.data
        return resp

    except Exception:
        return jsonify("error") # expected this but instead 500 server error is returned

此代码将正确返回“错误”,前一个代码也“正确”引发500服务器错误。要了解原因,您必须考虑执行流程。在您的初始代码中,return Response(generate())将立即返回而不执行生成器函数。消费者(在这种情况下可能是werkzeug)将尝试读取data或类似的属性,这将导致生成器运行。到那时,您的函数已经执行,并且当然不会发生任何异常,因为您将返回包装在Response中的生成器。希望这会有所帮助。

更新

上面的代码仅演示了该问题,并说明了为什么原始代码未捕获异常。如果在流恕我直言的中间出现错误,则应用程序应给出500个服务器错误并死亡。但是,将异常移至生成器将使您捕获此类错误:

@app.route("/")
def hello():
    def generate():
        try:
            raise Exception('some error')                                                                                                                                                                                                                                           
            for i in range(0,10):
                yield '1'
        except Exception as e:
                yield str(e)

    return Response(generate())

而且您不能使用jsonify,因为生成器正在应用程序上下文之外使用。

答案 2 :(得分:0)

如果您来自网络搜索,是因为生成器中的异常未在客户端呈现,那是因为响应中的生成器模式实际上是HTTP分块流。这种技术导致一个HTTP响应被分块发送到客户端。首先返回HTTP响应代码(例如200表示成功)。因此,如果以下任何块触发代码中的异常,则Flask无法将其返回给浏览器。