uwsgi: Send http response and continue execution

Question:

From the uwsgi documentation:

def application(env, start_response):
    start_response('200 OK', [('Content-Type','text/html')])
    return [b"Hello World"]

Is it possible to respond to http request(close http connection) and continue execution flow(without any usage of threads/queues/external services etc)?
like this:

def application(env, start_response):
    start_response('200 OK', [('Content-Type','text/html')])
    end_response(b"Hello World")
    #HTTP connection is closed
    #continue execution..
Asked By: Alex

||

Answers:

Unfortunately, there is no way to continue the code execution after you have returned the response. It would be much easier if you use multithreading but if not you can workaround it in Flask by adding an AJAX call to your HTML response which will send a POST request to one of the server extra route whose handler function will be the execution code you want after returning the response. Here’s one of the possible approach using Flask:

myflaskapp.py

from flask import Flask, render_template_string
import time

app = Flask(__name__)

@app.route('/run', methods=['POST'])
def run():
    # this is where you put your "continue execution..." code
    # below code is used to test if it runs after HTTP connection close
    time.sleep(8)
    print('Do something')
    return ''

@app.route('/')
def index():
    return render_template_string('''
            Hello World!
            <script src="//ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
            <script>
            $(function() {
                $.ajax({
                    type: "POST",
                    url: "{{ url_for('run') }}"
                });
            })
            </script>
            ''')

if __name__ == "__main__":
    app.run(host='0.0.0.0')

You can run the server on port 9091 with the command:

uwsgi --http 127.0.0.1:9091 --wsgi-file myflaskapp.py --callable app

To test if it is working or not, you can go to the address localhost:9091. If everything works well, you should see that the page is loaded immediately while the terminal will only print out Do something after 8 seconds have passed, indicating the function run executes after the HTTP connection is closed.

Answered By: VietHTran

TL;DR – if you’re using Django, you can skip to the end of the answer.

Yes, there is a way to do this. You can hook on to a .close() method of the object returned by your application callable (or alternatively, wsgi.file_wrapper returned through the env.

Check PEP333, specifically the server side spec part: https://peps.python.org/pep-0333/#the-server-gateway-side:

    result = application(environ, start_response)
    try:
        for data in result:
            if data:    # don't send headers until body appears
                write(data)
        if not headers_sent:
            write('')   # send headers now if body was empty
    finally:
        if hasattr(result, 'close'):
            result.close()

As you can see, result.close() will be called at the very end, by which point the data is already sent.

This is, of course, just some reference code, and it does not deal with upstream and terminating the connection before continuing execution. But well-behaved wsgi servers, such as uwsgi and (presumably) gunicorn, do.
They will signal to the upstream that the request has finished sending by closing the socket (or whatever the upstream protocol requires), and then call .close().

If you are using Django, you are already set, because it has request_finished signal. Here’s how it works, it hooks to .close() as described above:

https://github.com/django/django/blob/57c7220280db19dc9dda0910b90cf1ceac50c66f/django/http/response.py#L323

Answered By: thule
Categories: questions Tags: ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.