Python: Using Flask to stream chunked dynamic content to end users

python-logoIf you are using Flask to generate dynamic content of significant size, such as large binary images/pdf or large text-based datasets, then you need to consider streaming to minimize the memory footprint of Flask and preserve scalability.

Using an inner generate function and a yield allows Flask to return chunks of data back to the browser without the need to build an entire structure, image, or pdf in-memory.

@app.route('/large.csv')
def generate_large_csv():
    def generate():
        for row in iter_all_rows():
            yield ','.join(row) + '\n'
    return Response(generate(), mimetype='text/csv')

Prerequisites

You will need the Flask library installed to run this test.

sudo pip install Flask
# If using Ubuntu16 
sudo pip3 install Flask

Example Code

I have uploaded a sample Python Flask application to github, flask_stream_html.py.  This sample application streams chunked html to the browser, allowing the end user to specify how many blocks of data to send and the number of bytes in each block.

# download entire project
git clone https://github.com/fabianlee/python-flask-streamhtml.git
cd python-flask-streamhtml

# run streaming Flask app on port 8080
python3 ./flask-stream_html.py

Exposed URL

The application should now be running on port 8080, and you can pull down streaming content at the following URL:

# pull down streaming content
http://localhost:8080/streamhtml

# pull down 16kb
http://localhost:8080/streamhtml?nblocks=16&block_size=1024

# pull down 256kb
http://localhost:8080/streamhtml?nblocks=256&block_size=1024

You can access these URL with your local browser as well.

Client load testing

If you want to test the abilities of the server, try tweaking the parameters of the communication by changing the total size and chunk size under parallel load.

Apache Benchmark provides an easy way to perform basic load testing.  First install the package:

sudo apt-get install apache2-utils

Then run a quick test of 10 total requests with 2 concurrent parallel requests.  We set the parameters of the URL to return 16Kb total comprised of 16 chunks of 1024 bytes each.

ab -n 10 -c 2 http://localhost:8080/streamhtml?nblocks=16&block_size=1024

As another example, let’s return a larger scale test of 100 total  requests with 10 concurrent. We set the parameters of the URL to return 50Kb comprised of 100 chunks of 512 bytes each.

ab -n 100 -c 10 http://localhost:8080/streamhtml?nblocks=100&block_size=512

 

REFERENCES

Flask docs, send_file_from_directory

Flask docs, streaming contents

stackoverflow, streaming content

cloudfoundry docs, Python buildpack usage.  example runtime.txt use of vendor app dependencies download

github, CloudFoundry python-buildpack releases

github yuta-hono, cloudfoundry sample app for Flask.  example manifest, runtime, Procfile

andrey zhukov, no-cache headers in flask

pythonize, flask before and after request functions

stackoverflow, flask stream with proxy and chunk_size

werkzeug, WSGI ClosingIterator and FileWrapper helpers