Перехват исключений при использовании объекта Response во Flask не работает - PullRequest
0 голосов
/ 26 ноября 2018

Я пытаюсь поймать исключение при использовании объекта Response.Мой пример использования заключается в том, что я хочу передавать данные, как описано в официальной документации , и существует вероятность исключения, когда я получаю данные для потоковой передачи:

# 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 не ловит их.Я также попытался убедиться, что исключение не перехватывается, установив параметр passthrough_errors сервера werkzeug.Тогда исключение не перехватывается в Werkzeug, например, здесь или здесь .К сожалению, это не помогло перехватить его в приложении Flask, указанном выше:

app.run(passthrough_errors=True)

Вывод колбы - версия:

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) 

Ошибка TypeError возникает на стороне Flask, потому что 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 голосов
/ 26 ноября 2018

Давайте изменим ваш код и запустим его снова:

@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.Надеюсь, это поможет.

Обновление

Приведенный выше код только демонстрирует проблему и объясняет, почему исключение не перехватывается в исходном коде.Если у вас есть ошибка в середине вашего потока IMHO, приложение должно выдать 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, поскольку генератор используется вне контекста вашего приложения.

0 голосов
/ 27 июня 2019

В случае, если вы пришли сюда из веб-поиска, потому что ваши исключения в генераторе не обрабатываются на стороне клиента: это потому, что паттерн генераторов в ответах - это потоковая передача по HTTP.Этот метод приводит к тому, что один HTTP-ответ отправляется клиенту порциями.Код ответа HTTP (например, 200 для успеха) возвращается первым.Поэтому, если какой-либо из следующих кусков вызовет исключение в вашем коде, Flask не сможет вернуть его в браузер.

0 голосов
/ 26 ноября 2018

Это связано с функцией генератора, а не с колбой.

def generate():    
    raise Exception                                                                                                                                                                                                                                           
    yield '1'

вернет генератор, а не число 1 и не исключение!(попробуйте напечатать generate()).

Также попробуйте изменить yield на return и проверьте разницу.

Но теперь решение:

from flask import Flask, Response, jsonify
import werkzeug

app = Flask(__name__)

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

    try:
        generator = generate()
        return Response(next(generator))
    except Exception:
        return jsonify("error")

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