如果请求中止,则停止处理 Flask 路由

Posted

技术标签:

【中文标题】如果请求中止,则停止处理 Flask 路由【英文标题】:Stop processing Flask route if request aborted 【发布时间】:2013-09-01 21:31:55 【问题描述】:

我有一个烧瓶 REST 端点,它执行一些 cpu 密集型图像处理并需要几秒钟才能返回。通常,此端点被调用,然后被客户端中止。在这些情况下,我想取消处理。我怎样才能在烧瓶中做到这一点?

在 node.js 中,我会这样做:

req.on('close', function()
  //some handler
);

我希望 flask 有类似的东西,或者一个同步方法 (request.isClosed()),我可以在处理过程中的某些时间点检查它,如果它关闭则返回,但我找不到。

我想过发送一些东西来测试连接是否仍然打开,如果失败则捕获异常,但似乎 Flask 缓冲了所有输出,因此在处理完成并尝试返回结果之前不会抛出异常:

已建立的连接被主机中的软件中止

如果客户中止了他们的请求,我该如何取消我的处理?

【问题讨论】:

“被客户端中止”是什么意思?客户端是否尝试关闭连接?还是他们到达了另一个终点?我认为没有办法使用 Flask 来执行此操作,尽管您当然可以对端点进行编码以允许它们停止处理(使用数据库来跟踪这些任务并让长期运行的任务检查该数据库例如停止标志)。 他们关闭了连接。每次他们更改某些选项(复选框)时,我都会重新生成一个图像:如果他们选中一个新框,则会触发一个请求,如果他们在第一个请求完成之前立即选中第二个框,则初始请求会被取消(jqXHR.abort() ) 并使用新选项制作一个新选项。我想过从客户端发出一个额外的请求来写一些“wascancelled”文件/数据库记录(正如你所建议的那样),但这看起来很丑陋。 php有connection_aborted(),nodejs有req.on('close'),我希望flask有类似的东西。 您可能会发现this e-mail thread 很有趣。它讨论了 WSGI 应用程序如何检测连接何时关闭。我相信它类似于connection_aborted() 在 PHP 中的工作方式:您不能简单地调用它并确定连接是否已关闭。相反,您需要先尝试发回数据(您可以使用streaming 来完成)。 我对 Flask 不是很了解,所以无法帮助您确定客户端何时关闭了连接。但是,如果您可以确定这一点,您是否考虑过使用multiprocessing 在单独的进程中进行图像处理。当连接关闭时,您可以调用process.terminate() 结束该过程。 【参考方案1】:

据我所知,您无法知道连接是否在执行期间被客户端关闭,因为服务器没有测试连接在执行期间是否打开。我知道您可以在 Flask 应用程序中创建自定义 request_handler,以检测在处理请求后连接是否“断开”。

例如:

from flask import Flask
from time import sleep
from werkzeug.serving import WSGIRequestHandler


app = Flask(__name__)


class CustomRequestHandler(WSGIRequestHandler):

    def connection_dropped(self, error, environ=None):
        print 'dropped, but it is called at the end of the execution :('


@app.route("/")
def hello():
    for i in xrange(3):
        print i
        sleep(1)
    return "Hello World!"

if __name__ == "__main__":
    app.run(debug=True, request_handler=CustomRequestHandler) 

也许您想进行更多调查,并且当您的自定义 request_handler 在请求到来时创建时,您可以在 __init__ 中创建一个线程,每秒检查连接状态以及检测到连接的状态关闭(check this thread)然后停止图像处理。但我认为这有点复杂:(。

【讨论】:

刚刚试了一下,并没有触发connection_dropped()。我只找到了IOError: [Errno 32] Broken pipe 的踪迹。【参考方案2】:

您的问题有一个潜在的... hacky 解决方案。 Flask has the ability to stream content back to the user via a generator。 hacky 部分将流式传输空白数据以检查连接是否仍然打开,然后当您的内容完成时,生成器可以生成实际图像。您的生成器可以检查处理是否完成并返回None"" 或其他任何未完成的处理。

from flask import Response

@app.route('/image')
def generate_large_image():
    def generate():
        while True:
            if not processing_finished():
                yield ""
            else:
                yield get_image()
    return Response(generate(), mimetype='image/jpeg')

我不知道如果客户端关闭连接你会得到什么异常,但我愿意打赌它的error: [Errno 32] Broken pipe

【讨论】:

但是哪里引发了异常?怎么抓到? 套接字库中会产生错误。当您尝试将数据发送到不再打开的套接字并发生超时时,就会生成它。 来自这个问题/答案:***.com/questions/31265050/…。看起来无法捕获 Broken pipe 异常(或者至少没有您的响应函数)。 您可以在循环内捕获GeneratorExit 异常。当连接关闭时,Flask 会停止监听你的生成器,因此会引发异常。 我在 Windows 上,这个解决方案对我不起作用。客户端时不会产生错误,例如在他们的浏览器中按“停止”。使用waitress.serve(app, send_bytes=1) 运行并产生一个非空字符串“q”而不是空字符串。客户端可以看到单个“q”字符进入流式传输,但是当在客户端浏览器中按“停止”时,flask 应用程序继续认为它正在成功发送“q”。使用 req.on('close', () => ) 的等效 node.js 代码会立即检测到浏览器的“停止”按钮按下。【参考方案3】:

我只是试图在一个项目中做同样的事情,我发现在我的 uWSGI 和 nginx 堆栈中,当客户端的流响应被中断时,会发生以下错误

SIGPIPE: writing to a closed pipe/socket/fd (probably the client disconnected) on request
uwsgi_response_write_body_do(): Broken pipe [core/writer.c line 404] during GET
IOError: write error

我可以使用普通的旧 tryexcept,如下所示

    try:
        for chunk in iter(process.stdout.readline, ''):
            yield chunk
        process.wait()
    except:
        app.logger.debug('client disconnected, killing process')
        process.terminate()
        process.wait()

这给了我:

    使用 Flask 的生成器功能即时传输数据 取消连接时没有僵尸进程

【讨论】:

请描述完整的例子,这段代码应该放在哪里,流程是什么?

以上是关于如果请求中止,则停止处理 Flask 路由的主要内容,如果未能解决你的问题,请参考以下文章

flask笔记(七):简单路由设置

Flask源码复习之路由

中止路由并继续.NET中的原始请求[重复]

Flask 源码分析-基本工作流程

Flask框架从入门到精通之路由

tips for Flask