37

For one request alone i need to execute a function after sending the response to the client. Because the function takes time and that ends up in connection timeout Socket error: [Errno 32] Broken pipe

Is there a way in Flask to execute function after returning the request

Mark Hildreth
  • 42,023
  • 11
  • 120
  • 109
naga4ce
  • 1,527
  • 3
  • 17
  • 21
  • 3
    What, exactly, do you want your function to do after the request finishes? If it's write to a log, that's one thing, but if you want to return data to the user, there is no way to do that if the connection has indeed timed out. This isn't a Flask thing, this is just an HTTP thing; the browser, web server, or anything in between can close the connection. If your request is taking too long, consider having it run in a separate process, with the first request kicking it off, and your app automatically (or the user manually) checking the status of the task periodically through other requests. – Mark Hildreth Aug 06 '13 at 21:28

6 Answers6

22

I will expose my solution.

You can use threads to compute anything after returned something in your function called by a flask route.

import time
from threading import Thread
from flask import request, Flask
app = Flask(__name__)


class Compute(Thread):
    def __init__(self, request):
        Thread.__init__(self)
        self.request = request

    def run(self):
        print("start")
        time.sleep(5)
        print(self.request)
        print("done")


@app.route('/myfunc', methods=["GET", "POST"])
def myfunc():
        thread_a = Compute(request.__copy__())
        thread_a.start()
        return "Processing in background", 200
Alekos
  • 368
  • 2
  • 8
16

You can try use streaming. See next example:

import time
from flask import Flask, Response

app = Flask(__name__)

@app.route('/')
def main():
    return '''<div>start</div>
    <script>
        var xhr = new XMLHttpRequest();
        xhr.open('GET', '/test', true);
        xhr.onreadystatechange = function(e) {
            var div = document.createElement('div');
            div.innerHTML = '' + this.readyState + ':' + this.responseText;
            document.body.appendChild(div);
        };
        xhr.send();
    </script>
    '''

@app.route('/test')
def test():
    def generate():
        app.logger.info('request started')
        for i in range(5):
            time.sleep(1)
            yield str(i)
        app.logger.info('request finished')
        yield ''
    return Response(generate(), mimetype='text/plain')

if __name__ == '__main__':
    app.run('0.0.0.0', 8080, True)

All magic in this example in genarator where you can start response data, after do some staff and yield empty data to end your stream.

For ditails look at http://flask.pocoo.org/docs/patterns/streaming/.

tbicr
  • 24,790
  • 12
  • 81
  • 106
  • 1
    This is really clever. With this, a user doesn't even have to stay on the webpage for it to continue processing in the background because the browser already ended the connection. The program is connecting to its self. Really clever stuff. Two thumbs up! – Malachi Bazar Aug 25 '18 at 17:54
11

A more general solution than the flask iterator solution is to write a WSGI middleware that adds a callback to the response close method. Here we use the werkzeug ClosingIterator helper and a flask app extension to achieve this:

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 then use your after_response decorator like this:

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

@app.after_response
def after():
    time.sleep(2)
    print("after_response")

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

When you curl this, you'll see that it responds immediately and curl closes, then 2s later your "after" message appears in the logs:

127.0.0.1 - - [25/Jun/2018 15:41:51] "GET / HTTP/1.1" 200 -
after_response

This answer is summarized from my answers here and here.

Matthew Story
  • 3,573
  • 15
  • 26
  • The callback function will run without any context though, as the request context and app context have both been torn down at this point. I found a usable solution by creating a closure before teardown and using that for the callback. Answered here: https://stackoverflow.com/a/63080968/78903 – Kiran Jonnalagadda Aug 07 '20 at 12:54
7

There is no Flask-native way to accomplish this. after_request will still run before returning the response to the client, not after.

Here is a discussion of the problem and some solutions.

Brandon Wang
  • 3,701
  • 7
  • 31
  • 36
4

You can use the after_request decorator or customize to create a after_this_request which only works for that particular request.

Take a look at this snippet http://flask.pocoo.org/snippets/53/

codegeek
  • 32,236
  • 12
  • 63
  • 63
  • 12
    Thanks yash for quick reply! after_this_request also waits for the function defined to get completed and then returns the response is there any way to send the response first and then execute the function – naga4ce Aug 06 '13 at 14:52
  • 1
    is there any way to set the requestconnection timeout or response connection timeout in flask ? – naga4ce Aug 06 '13 at 15:07
3

You can defer route specific actions with limited context by combining after_this_request and response.call_on_close. Note that request and response context won't be available but the route function context remains available. So you'll need to copy any request/reponse data you'll need into local variables for deferred access.

@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
VoteCoffee
  • 4,692
  • 1
  • 41
  • 44