Python Flask 返回响应后执行函数

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/48994440/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-19 18:57:23  来源:igfitidea点击:

Execute a function after Flask returns response

pythonmultithreadingflask

提问by Brandon Wang

I have some code that needs to execute afterFlask returns a response. I don't think it's complex enough to set up a task queue like Celery for it. The key requirement is that Flask must return the response to the client before running this function. It can't wait for the function to execute.

我有一些代码需要Flask 返回响应执行。我认为为它设置像 Celery 这样的任务队列还不够复杂。关键要求是 Flask 必须在运行此函数之前将响应返回给客户端。它不能等待函数执行。

There are some existing questions about this, but none of the answers seem to address running a task after the response is sent to the client, they still execute synchronously and then the response is returned.

有一些关于此的现有问题,但似乎没有一个答案解决在将响应发送到客户端后运行任务的问题,它们仍然同步执行,然后返回响应。

回答by Brandon Wang

The long story short is that Flask does not provide any special capabilities to accomplish this. For simple one-off tasks, consider Python's multithreading as shown below. For more complex configurations, use a task queue like RQ or Celery.

长话短说,Flask 没有提供任何特殊功能来实现这一点。对于简单的一次性任务,请考虑 Python 的多线程,如下所示。对于更复杂的配置,请使用任务队列,如 RQ 或 Celery。

Why?

为什么?

It's important to understand the functions Flask provides and why they do notaccomplish the intended goal. All of these are useful in other cases and are good reading, but don't help with background tasks.

了解 Flask 提供的功能以及它们为何无法实现预期目标非常重要。所有这些在其他情况下都很有用并且很好阅读,但对后台任务没有帮助。

Flask's after_requesthandler

Flask 的after_request处理程序

Flask's after_requesthandler, as detailed in this pattern for deferred request callbacksand this snippet on attaching different functions per request, will pass the request to the callback function. The intended use case is to modify the request, such as to attach a cookie.

烧瓶的after_request处理程序,如在详细描述该模式为延迟请求回调上安装每个请求不同的功能这个片段,将请求传递给回调函数。预期用例是修改 request,例如附加 cookie。

Thus the request will wait around for these handlers to finish executing because the expectation is that the request itself will change as a result.

因此,请求将等待这些处理程序完成执行,因为期望请求本身会因此而改变。

Flask's teardown_requesthandler

Flask 的teardown_request处理程序

This is similar to after_request, but teardown_requestdoesn't receive the requestobject. So that means it won't wait for the request, right?

这类似于after_request,但teardown_request不接收request对象。所以这意味着它不会等待请求,对吗?

This seems like the solution, as this answer to a similar Stack Overflow questionsuggests. And since Flask's documentation explains that teardown callbacks are independent of the actual requestand do not receive the request context, you'd have good reason to believe this.

这似乎是解决方案,正如对类似 Stack Overflow 问题的回答所暗示的那样。由于 Flask 的文档解释了拆卸回调独立于实际请求并且不接收请求上下文,因此您有充分的理由相信这一点。

Unfortunately, teardown_requestis still synchronous, it just happens at a later part of Flask's request handling when the request is no longer modifiable. Flask will still wait for teardown functionsto complete before returning the response, as this list of Flask callbacks and errorsdictates.

不幸的是,teardown_request它仍然是同步的,当请求不再可修改时,它只会发生在 Flask 请求处理的后期部分。Flask在返回响应之前仍将等待拆卸函数完成,正如此 Flask 回调和错误列表所指示的那样。

Flask's streaming responses

Flask 的流响应

Flask can stream responses by passing a generator to Response(), as this Stack Overflow answer to a similar questionsuggests.

Flask 可以通过将生成器传递给 来流式传输响应Response(),正如Stack Overflow 对类似问题的回答所暗示的那样。

With streaming, the client doesbegin receiving the response before the request concludes. However, the request still runs synchronously, so the worker handling the request is busy until the stream is finished.

使用流式传输,客户端在请求结束之前开始接收响应。但是,请求仍然是同步运行的,因此处理请求的工作线程很忙,直到流结束。

This Flask pattern for streamingincludes some documentation on using stream_with_context(), which is necessary to include the request context.

这个用于流的 Flask 模式包括一些关于 using 的文档stream_with_context(),这是包含请求上下文所必需的。

So what's the solution?

那么有什么解决办法呢?

Flask doesn't offer a solution to run functions in the background because this isn't Flask's responsibility.

Flask 不提供在后台运行函数的解决方案,因为这不是 Flask 的责任。

In most cases, the best way to solve this problem is to use a task queue such as RQ or Celery. These manage tricky things like configuration, scheduling, and distributing workers for you.This is the most common answer to this type of question because it is the most correct, and forces you to set things up in a way where you consider context, etc. correctly.

在大多数情况下,解决这个问题的最好方法是使用任务队列,例如 RQ 或 Celery。它们为您管理诸如配置、调度和分配工作器之类的棘手事情。这是此类问题的最常见答案,因为它是最正确的,并迫使您以考虑上下文等的方式进行设置。正确。

If you need to run a function in the background and don't want to set up a queue to manage this, you can use Python's built in threadingor multiprocessingto spawn a background worker.

如果您需要在后台运行一个函数并且不想设置一个队列来管理它,您可以使用 Python 的内置功能threadingmultiprocessing生成一个后台工作程序。

You can't access requestor others of Flask's thread locals from background tasks, since the request will not be active there. Instead, pass the data you need from the view to the background thread when you create it.

您无法request从后台任务访问或其他 Flask 的线程本地人,因为请求在那里不会处于活动状态。相反,当您创建它时,将您需要的数据从视图传递到后台线程。

@app.route('/start_task')
def start_task():
    def do_work(value):
        # do something that takes a long time
        import time
        time.sleep(value)

    thread = Thread(target=do_work, kwargs={'value': request.args.get('value', 20)})
    thread.start()
    return 'started'

回答by Matthew Story

Flask is a WSGI appand as a result it fundamentally cannot handle anything after the response. This is why no such handler exists, the WSGI app itself is responsible only for constructing the response iterator object to the WSGI server.

Flask 是一个WSGI 应用程序,因此它从根本上无法处理响应之后的任何事情。这就是为什么不存在这样的处理程序的原因,WSGI 应用程序本身只负责构造对 WSGI 服务器的响应迭代器对象。

A WSGI serverhowever (like gunicorn) can very easily provide this functionality, but tying the application to the server is a very bad idea for a number of reasons.

一个WSGI服务器然而(如gunicorn)可以很容易提供这种功能,但捆绑应用程序服务器是有很多原因一个非常糟糕的主意。

For this exact reason, WSGI provides a spec for Middleware, and Werkzeug provides a number of helpers to simplify common Middleware functionality. Among them is a ClosingIteratorclass which allows you to hook methods up to the closemethod of the response iterator which is executed after the request is closed.

正是出于这个原因,WSGI 为Middleware提供了规范,而 Werkzeug 提供了许多帮助程序来简化常见的 Middleware 功能。其中有一个ClosingIterator类,它允许您将方法挂接到close请求关闭后执行的响应迭代器的方法上。

Here's an example of a naive after_responseimplementation done as a Flask extension:

这是一个after_response作为 Flask 扩展完成的简单实现的示例:

import traceback
from werkzeug.wsgi import ClosingIterator

class AfterResponse:
    def __init__(self, app=None):
        self.callbacks = []
        if app:
            self.init_app(app)

    def __call__(self, callback):
        self.callbacks.append(callback)
        return callback

    def init_app(self, app):
        # install extension
        app.after_response = self

        # install middleware
        app.wsgi_app = AfterResponseMiddleware(app.wsgi_app, self)

    def flush(self):
        for fn in self.callbacks:
            try:
                fn()
            except Exception:
                traceback.print_exc()

class AfterResponseMiddleware:
    def __init__(self, application, after_response_ext):
        self.application = application
        self.after_response_ext = after_response_ext

    def __call__(self, environ, after_response):
        iterator = self.application(environ, after_response)
        try:
            return ClosingIterator(iterator, [self.after_response_ext.flush])
        except Exception:
            traceback.print_exc()
            return iterator

You can use this extension like this:

你可以像这样使用这个扩展:

import flask
app = flask.Flask("after_response")
AfterResponse(app)

@app.after_response
def say_hi():
    print("hi")

@app.route("/")
def home():
    return "Success!\n"

When you curl "/" you'll see the following in your logs:

当您 curl "/" 时,您将在日志中看到以下内容:

127.0.0.1 - - [24/Jun/2018 19:30:48] "GET / HTTP/1.1" 200 -
hi

This solves the issue simply without introducing either threads (GIL??) or having to install and manage a task queue and client software.

这解决了这个问题,无需引入线程(GIL??),也无需安装和管理任务队列和客户端软件。

回答by Paul Brackin

Middleware Solution for Flask Blueprints

Flask 蓝图的中间件解决方案

This is the same solution proposed by Matthew Story (which is the perfect solution IMHO - thanks Matthew), adapted for Flask Blueprints. The secret sauce here is to get hold of the app context using the current_app proxy. Read up herefor more information (http://flask.pocoo.org/docs/1.0/appcontext/)

这与 Matthew Story 提出的解决方案相同(恕我直言,这是完美的解决方案 - 感谢 Matthew),适用于 Flask 蓝图。这里的秘诀是使用 current_app 代理获取应用程序上下文。在这里阅读更多信息(http://flask.pocoo.org/docs/1.0/appcontext/

Let's assume the AfterThisResponse & AfterThisResponseMiddleware classes are placed in a module at .utils.after_this_response.py

让我们假设 AfterThisResponse 和 AfterThisResponseMiddleware 类放置在 .utils.after_this_response.py 的模块中

Then where the Flask object creation occurs, you might have, eg...

然后在 Flask 对象创建发生的地方,你可能有,例如......

__init__.py

__init__.py

from api.routes import my_blueprint
from .utils.after_this_response import AfterThisResponse

app = Flask( __name__ )
AfterThisResponse( app )
app.register_blueprint( my_blueprint.mod )

And then in your blueprint module...

然后在您的蓝图模块中...

a_blueprint.py

a_blueprint.py

from flask import Blueprint, current_app

mod = Blueprint( 'a_blueprint', __name__, url_prefix=URL_PREFIX )

@mod.route( "/some_resource", methods=['GET', 'POST'] )
def some_resource():
    # do some stuff here if you want

    @current_app.after_this_response
    def post_process():
        # this will occur after you finish processing the route & return (below):
        time.sleep(2)
        print("after_response")

    # do more stuff here if you like & then return like so:
    return "Success!\n"

回答by Dmitrii

Thanks to Matthew Storyand Paul Brackin, but I needed to change their proposals. So the working solution is:

感谢Matthew StoryPaul Brackin,但我需要更改他们的建议。所以工作解决方案是:

.
├── __init__.py
├── blueprint.py
└── library.py
# __init__.py
from flask import Flask
from .blueprint import bp
from .library import AfterResponse

app = Flask(__name__)

with app.app_context():
    app.register_blueprint(bp, url_prefix='/')
    AfterResponse(app)
# blueprint.py
from flask import Blueprint, request, current_app as app
from time import sleep

bp = Blueprint('app', __name__)


@bp.route('/')
def root():
    body = request.json

    @app.after_response
    def worker():
        print(body)
        sleep(5)
        print('finished_after_processing')

    print('returned')
    return 'finished_fast', 200
# library.py
from werkzeug.wsgi import ClosingIterator
from traceback import print_exc


class AfterResponse:
    def __init__(self, application=None):
        self.functions = list()
        if application:
            self.init_app(application)    

    def __call__(self, function):
        self.functions.append(function)

    def init_app(self, application):
        application.after_response = self
        application.wsgi_app = AfterResponseMiddleware(application.wsgi_app, self)

    def flush(self):
        while self.functions:
            try:
                self.functions.pop()()
            except Exception:
                print_exc()


class AfterResponseMiddleware:
    def __init__(self, application, after_response_ext):
        self.application = application
        self.after_response_ext = after_response_ext

    def __call__(self, environ, after_response):
        iterator = self.application(environ, after_response)
        try:
            return ClosingIterator(iterator, [self.after_response_ext.flush])
        except Exception:
            print_exc()
            return iterator

The source code can be found here

源代码可以在这里找到

回答by augustomen

The signal request_finishedreceives a Responseinstance as parameter. Any after-processing can be done by connecting to that signal.

信号request_finished接收一个Response实例作为参数。任何后处理都可以通过连接到该信号来完成。

From https://flask-doc.readthedocs.io/en/latest/signals.html:

https://flask-doc.readthedocs.io/en/latest/signals.html

def log_response(sender, response, **extra):
    sender.logger.debug('Request context is about to close down.  '
                        'Response: %s', response)

from flask import request_finished
request_finished.connect(log_response, app)

Obs: In case of error, the signal got_request_exceptioncan be used instead.

Obs:如果出现错误,got_request_exception可以使用信号代替。

回答by Muhammad Usman

You can use this code i have tried it.It works.

你可以使用我试过的这个代码。它有效。

this code will print the string "message". after the 3 second ,from the scheduling time. You can change the time your self according to you requirement.

此代码将打印字符串“消息”。3秒后,从调度时间开始。您可以根据自己的需要更改自己的时间。

import time, traceback
import threading

def every(delay,message, task):
  next_time = time.time() + delay
  time.sleep(max(0, next_time - time.time()))
  task(message)

def foo(message):
  print(message+"  :foo", time.time())



def main(message):
    threading.Thread(target=lambda: every(3,message, foo)).start()

main("message")