Python 在Flask返回响应后执行函数

Python 在Flask返回响应后执行函数,python,multithreading,flask,Python,Multithreading,Flask,我有一些代码需要在Flask返回响应后执行。我认为它不够复杂,无法为它设置一个类似芹菜的任务队列。关键要求是Flask必须在运行此函数之前将响应返回给客户端。它不能等待函数执行 关于这一点,存在一些问题,但没有一个答案能够解决在将响应发送到客户端之后运行任务的问题,它们仍然同步执行,然后返回响应 长话短说,Flask不提供任何特殊功能来实现这一点。对于简单的一次性任务,请考虑Python的多线程,如下所示。对于更复杂的配置,请使用RQ或芹菜之类的任务队列 为什么? 理解Flask提供的功

我有一些代码需要在Flask返回响应后执行。我认为它不够复杂,无法为它设置一个类似芹菜的任务队列。关键要求是Flask必须在运行此函数之前将响应返回给客户端。它不能等待函数执行

关于这一点,存在一些问题,但没有一个答案能够解决在将响应发送到客户端之后运行任务的问题,它们仍然同步执行,然后返回响应


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

为什么? 理解Flask提供的功能以及为什么它们不能实现预期目标是很重要的。所有这些在其他情况下都是有用的,并且是很好的阅读材料,但对背景任务没有帮助

请求后烧瓶的
处理程序 Flask的
after_request
处理程序(如和中所述)将把请求传递给回调函数。预期的用例是修改请求,例如附加cookie

因此,请求将等待这些处理程序完成执行,因为预期结果是请求本身将发生更改

烧瓶的
拆卸请求
处理程序 这类似于请求后的
,但
拆卸请求
不接收
请求
对象。这意味着它不会等待请求,对吗

正如建议的那样,这似乎是解决办法。由于Flask的文档解释了这一点,并且没有收到请求上下文,所以您有充分的理由相信这一点

不幸的是,
teardown_请求
仍然是同步的,它只是在Flask请求处理的后期发生,此时请求不再可修改。Flask仍将按照指示等待拆卸功能完成,然后返回响应

Flask的流式响应 Flask可以通过将生成器传递到
Response()
,来流式传输响应,正如建议的那样

通过流式处理,客户端确实在请求结束之前开始接收响应。但是,请求仍然同步运行,因此处理请求的工作人员在流完成之前都很忙

包括一些有关将
stream\u与\u context()一起使用的文档,这是包含请求上下文所必需的

那么解决办法是什么呢? Flask不提供在后台运行函数的解决方案,因为这不是Flask的责任

在大多数情况下,解决此问题的最佳方法是使用任务队列,如RQ或芹菜。这些管理诸如配置、调度和分发工作人员等棘手的问题。这是最常见的问题,因为它是最正确的,并迫使你在考虑上下文等方面正确地设置问题。

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

您无法从后台任务访问
请求
或Flask的线程局部变量的其他部分,因为该请求在那里不会处于活动状态。相反,在创建视图时,将所需的数据从视图传递到后台线程

@app.route(“/start\u task”)
def start_任务():
def do_工作(值):
#做一些需要很长时间的事情
导入时间
时间。睡眠(值)
thread=thread(target=do_work,kwargs={'value':request.args.get('value',20)})
thread.start()
返回“已启动”
烧瓶是一个容器,因此它在响应后基本上无法处理任何事情。这就是为什么不存在这样的处理程序,WSGI应用程序本身只负责构造WSGI服务器的响应迭代器对象

但是(like)可以很容易地提供此功能,但是将应用程序绑定到服务器是一个非常糟糕的主意,原因有很多

正是出于这个原因,WSGI提供了一个规范,Werkzeug提供了许多帮助工具来简化常见的中间件功能。其中有一个类,允许您将方法连接到响应迭代器的
close
方法,该方法在请求关闭后执行

下面是一个简单的
after_响应
实现的示例,作为一个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
您可以像这样使用此扩展:

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

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

@app.route("/")
def home():
    return "Success!\n"
当您使用curl“/”时,您将在日志中看到以下内容:

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

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

您可以使用我已经尝试过的代码。它很有效

此代码将打印字符串“message”。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")

烧瓶蓝图中间件解决方案

这是Matthew Story提出的相同解决方案(这是IMHO的完美解决方案-谢谢Matthew),适用于烧瓶蓝图。这里的秘诀是使用当前的应用程序代理获取应用程序上下文。请阅读以了解更多信息()

让我们假设PostHisResponse和PostHisResponseMiddle类位于.utils.after\u this\u response.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 )
然后在蓝图模块中

a_.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"
感谢和,但我需要改变他们的提议。 因此,工作解决方案是:

.
├── __init__.py
├── blueprint.py
└── library.py
初始化
从烧瓶进口烧瓶
从.蓝图导入bp
from.library导入AfterResponse
app=烧瓶(名称)
使用app.app_context():
应用程序。
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)
@app.route('/')
def index():
    # Do your pre-response work here
    msg = 'Hello World!'
    @flask.after_this_request
    def add_close_action(response):
        @response.call_on_close
        def process_after_request():
            # Do your post-response work here
            time.sleep(3.0)
            print('Delayed: ' + msg)
        return response
    return msg
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from flask_login import LoginManager
import dir
import time
from flask_mail import Mail
from flask_cors import CORS
import flask_excel as excel
    # init SQLAlchemy so we can use it later in our models
dbb = SQLAlchemy()
def create_app():
     app = Flask(__name__)
     from .bp_route_1 import auth as bp_route_1_blueprint
     app.register_blueprint(bp_route_1_blueprint)
     CORS(app)
     return app
from flask import Blueprint, request, redirect, Response, url_for, abort, flash, render_template, \
    copy_current_request_context
from . import dbb 
from .models import #Import Models
from threading import Thread
bp_route_1 = Blueprint('bp_route_1', __name__)

@bp_route_1.route('/wehooks', methods=['POST'])
def route_1_wehooks_post():
    @copy_current_request_context #to copy request
    def foo_main():
        # insert your code here
        do_long_time_webhook(request)
    Thread(target=foo_main).start()
    print("do Webhook by Thread")
    return Response(status=200)
def do_long_time_webhook(request):
    try:
        data = request.get_data()
        print(data)
        #do long tim function for webhook data

    except Exception as e:
        print('Dont do webhook', e)