Stop processing Flask route if request aborted

20,965

Solution 1

There is a potentially... hacky solution to your problem. Flask has the ability to stream content back to the user via a generator. The hacky part would be streaming blank data as a check to see if the connection is still open and then when your content is finished the generator could produce the actual image. Your generator could check to see if processing is done and return None or "" or whatever if it's not finished.

from flask import Response

@app.route('/image')
def generate_large_image():
    def generate():
        while True:
            if not processing_finished():
                yield ""
            else:
                yield get_image()
    return Response(generate(), mimetype='image/jpeg')

I don't know what exception you'll get if the client closes the connection but I'm willing to bet its error: [Errno 32] Broken pipe

Solution 2

As far as I know you can't know if a connection was closed by the client during the execution because the server is not testing if the connection is open during the execution. I know that you can create your custom request_handler in your Flask application for detecting if after the request is processed the connection was "dropped".

For example:

from flask import Flask
from time import sleep
from werkzeug.serving import WSGIRequestHandler


app = Flask(__name__)


class CustomRequestHandler(WSGIRequestHandler):

    def connection_dropped(self, error, environ=None):
        print 'dropped, but it is called at the end of the execution :('


@app.route("/")
def hello():
    for i in xrange(3):
        print i
        sleep(1)
    return "Hello World!"

if __name__ == "__main__":
    app.run(debug=True, request_handler=CustomRequestHandler) 

Maybe you want to investigate a bit more and as your custom request_handler is created when a request comes you can create a thread in the __init__ that checks the status of the connection every second and when it detects that the connection is closed ( check this thread ) then stop the image processing. But I think this is a bit complicated :(.

Solution 3

I was just attempting to do this same thing in a project and I found that with my stack of uWSGI and nginx that when a streaming response was interrupted on the client's end that the following errors occurred

SIGPIPE: writing to a closed pipe/socket/fd (probably the client disconnected) on request
uwsgi_response_write_body_do(): Broken pipe [core/writer.c line 404] during GET
IOError: write error

and I could just use a regular old try and except like below

    try:
        for chunk in iter(process.stdout.readline, ''):
            yield chunk
        process.wait()
    except:
        app.logger.debug('client disconnected, killing process')
        process.terminate()
        process.wait()

This gave me:

  1. Instant streaming of data using Flask's generator functionality
  2. No zombie processes on cancelled connection
Share:
20,965
Crashthatch
Author by

Crashthatch

Updated on June 17, 2020

Comments

  • Crashthatch
    Crashthatch almost 4 years

    I have a flask REST endpoint that does some cpu-intensive image processing and takes a few seconds to return. Often, this endpoint gets called, then aborted by the client. In these situations I would like to cancel processing. How can I do this in flask?

    In node.js, I would do something like:

    req.on('close', function(){
      //some handler
    });
    

    I was expecting flask to have something similar, or a synchronous method (request.isClosed()) that I could check at certain points during my processing and return if it's closed, but I can't find one.

    I thought about sending something to test that the connection is still open, and catching the exception if it fails, but it seems Flask buffers all outputs so the exception isn't thrown until the processing completes and tries to return the result:

    An established connection was aborted by the software in your host machine

    How can I cancel my processing half way through if the client aborts their request?

  • Guy
    Guy over 9 years
    But where is the exception raised? How can it be captured?
  • AlexLordThorsen
    AlexLordThorsen over 9 years
    The error would be generated in the socket library. It would be generated when you attempt to send data to a socket that's no longer open and a time-out occurs.
  • Tuukka Mustonen
    Tuukka Mustonen over 7 years
    Just tried this and it didn't trigger connection_dropped(). I merely got a trace for IOError: [Errno 32] Broken pipe.
  • Hieu
    Hieu over 6 years
    From this question/answer: stackoverflow.com/questions/31265050/… . It looks like the Broken pipe exception could not be caught (or at least without your response function).
  • Victor Orletchi
    Victor Orletchi over 6 years
    please describe full example, where should be placed this code, what is process?
  • Fabian Scheidt
    Fabian Scheidt over 5 years
    You can catch the GeneratorExit exception inside the loop. Flask stops listening to your generator when the connection is closed and therefore the exception is raised.
  • JustAskin
    JustAskin about 5 years
    I'm on Windows and this solution does not work for me. No error is produced when the client e.g. presses "Stop" in their browser. Running with waitress.serve(app, send_bytes=1) and yielding a nonempty string "q" instead of empty string. The client can see the single "q" characters come in streaming, but when pressing "stop" in client browser, flask app continues to think it's successfully sending "q"s. The equivalent node.js code using req.on('close', () => {}) immediately detects browser "Stop" button press.
  • Brannon
    Brannon over 2 years
    Is there any way to do this while specifying your own headers in the Response object, where the values of those headers aren't known until after processing_finished() completes?