从Flask返回HTTP状态代码而不返回"

时间:2017-03-24 18:50:42

标签: python flask gitlab

上下文

  • 我有一个名为"server.py"的服务器,用作GitLab的提交后webhook。
  • "server.py"内,有一个长时间运行的过程(约40秒)

SSCCE

#!/usr/bin/env python

import time
from flask import Flask, abort, jsonify

debug = True

app = Flask(__name__)


@app.route("/", methods=['POST'])
def compile_metadata():
    # the long running process...
    time.sleep(40)
    # end the long running process
    return jsonify({"success": True})

if __name__ == "__main__":
    app.run(host='0.0.0.0', port=8082, debug=debug, threaded=True)

问题陈述

GitLab的webhooks期望快速返回返回代码。因为我的webhook在40秒左右后返回; GitLab发送重试,在循环中发送长时间运行的进程,直到GitLab尝试了太多次。

问题

我能否将Flask的状态代码返回给GitLab,但仍然运行我的长时间运行过程?

我尝试过添加以下内容:

...
def compile_metadata():
    abort(200)
    # the long running process
    time.sleep(40)

abort()仅支持失败代码。

我也尝试过使用@after_this_request

@app.route("/", methods=['POST'])
def webhook():
    @after_this_request
    def compile_metadata(response):
        # the long running process...
        print("Starting long running process...")
        time.sleep(40)
        print("Process ended!")
        # end the long running process
    return jsonify({"success": True})

通常情况下,flask只从python的return语句返回一个状态代码,但我显然不能在长时间运行的进程之前使用它,因为它将从函数中逃脱。

注意:我实际上并没在我的代码中使用time.sleep(40)。这只适用于子孙后代和SSCCE。它将返回相同的结果

3 个答案:

答案 0 :(得分:0)

compile_metadata生成一个线程来处理长时间运行的任务,然后立即返回结果代码(即,不等待线程完成)。确保对可以生成的并发线程数包含一些限制。

对于稍微强大且可扩展的解决方案,请考虑基于消息队列的排序解决方案,如celery

对于记录,一个简单的解决方案可能如下:

import time
import threading
from flask import Flask, abort, jsonify

debug = True

app = Flask(__name__)

def long_running_task():
    print 'start'
    time.sleep(40)
    print 'finished'

@app.route("/", methods=['POST'])
def compile_metadata():
    # the long running process...
    t = threading.Thread(target=long_running_task)
    t.start()
    # end the long running process
    return jsonify({"success": True})

if __name__ == "__main__":
    app.run(host='0.0.0.0', port=8082, debug=debug, threaded=True)

答案 1 :(得分:0)

当您想快速从服务器返回响应并且仍然做一些耗时的工作时,通常应该使用某种共享存储(例如Redis)来快速存储所需的所有内容,然后返回状态代码。因此,请求可以很快得到处理。

并有一个单独的服务器来例行该语义作业队列来执行耗时的工作。然后,在工作完成后将其从队列中删除。也许也将最终结果存储在共享存储中。这是正常的方法,并且可以很好地扩展。例如,如果您的作业队列增长太快而无法跟上单个服务器的速度,则可以添加更多服务器来处理该共享队列。

但是,即使您不需要可伸缩性,它也是理解,实现和调试的非常简单的设计。如果您的请求负载意外增加,则仅表示您的单独服务器可能整夜都在忙碌。而且您可以放心,如果关闭服务器,则不会丢失任何未完成的工作,因为它们在共享存储中是安全的。

但是,如果您拥有一台服务器来做所有事情,在后台异步执行长时间运行的任务,我想也许只是要确保后台工作是这样发生的:

1. Create a Xib of UIView and design it what ever you want.
2. Create a UIView class
3. Change the fileOwner of Your UIView XIB to the created UIView class
4.  Create an Outlet of ContentView in YourView.h
 @property (strong, nonatomic) IBOutlet UIView *contentView;
5. Add the following codes in YouView.m

 -(instancetype)initWithCoder:(NSCoder *)aDecoder
{
    self = [super initWithCoder:aDecoder];
    if(self)
    {
        [self customInit];
    }
    return self;
}

-(instancetype)initWithFrame:(CGRect)frame
{
    self = [super initWithFrame:frame];
    if(self)
    {
        [self customInit];
    }
    return  self;
}

-(void) customInit
{
    [[NSBundle mainBundle]loadNibNamed:@"View" owner:self options:nil];
    [self addSubview:self.contentView];
    self.contentView.frame = self.bounds;
}

6. Use this UIView in your ViewController what ever you want.

YourView *YourViewObj = [[YourView alloc] initWithFrame:CGRectMake(0, 0, 100,100)];
[self.view addSubview:YourViewObj];

不是这样的:

------------ Serving Responses
    ----     Background Work

否则,如果服务器正在后台执行某些工作块,则可能会不响应新请求,具体取决于耗时的工作花费了多长时间(即使请求负载很少)。但是,如果客户超时并重试,我认为您仍然可以避免双重工作。但是,失去未完成的工作并不安全。

答案 2 :(得分:-1)

我可以使用multiprocessing.dummy.Pool来实现这一目标。在使用threading.Thread后,它被证明无益,因为Flask仍会等待线程完成(即使使用t.daemon = True

我实现了在长时间运行的任务之前返回状态代码的结果,如:

#!/usr/bin/env python

import time
from flask import Flask, jsonify, request
from multiprocessing.dummy import Pool

debug = True

app = Flask(__name__)
pool = Pool(10)


def compile_metadata(data):
    print("Starting long running process...")
    print(data['user']['email'])
    time.sleep(5)
    print("Process ended!")


@app.route('/', methods=['POST'])
def webhook():
    data = request.json
    pool.apply_async(compile_metadata, [data])
    return jsonify({"success": True}), 202


if __name__ == "__main__":
    app.run(host='0.0.0.0', port=8082, debug=debug, threaded=True)