Implemented file download with Python + Bottle

Bottle is Python's lightweight web application framework. In a web application, a file may be generated on the server side and downloaded. This time I will write about the implementation of file download in Bottle.

Static file directory

In Web applications, I think that you often create a directory that collects images / CSS / JS files such as / static /, but in such cases you can not create request handlers one by one with the route function, so It is convenient to use the static_file function of bottle as follows.

In this example, accessing http: // localhost: 8080 / static / hoge.txt will deliver the contents of hoge.txt in ./static as seen from the current directory at runtime.

In the production deployment of many web applications, I think that the directory that collects static files is usually delivered directly from nginx or apache. However, if you support Python apps written in bottle, you can read static files at the time of local development. (Compared to our company)

# -*- coding: utf-8 -*-
from bottle import route, run, static_file


@route('/static/<file_path:path>')
def static(file_path):
    return static_file(file_path, root='./static')


run(host='localhost', port=8080)

Looking at the bottle source, it seems that the major file formats are inferred by the mimetypes package by extension. is. Common files such as images, css and zip / gz files are likely to have the correct mimetype in the Content-Type HTTP header.

I want it to be downloaded instead of being displayed ...

Let's set the download option of the static_file function to True.

# -*- coding: utf-8 -*-
from bottle import route, run, static_file


@route('/static/<file_path:path>')
def static(file_path):
    return static_file(file_path, root='./static', download=True)


run(host='localhost', port=8080)

If you want to separate the file name to be read and the file name to be saved by the browser, set the file name to be saved in the browser in the download option. Details will be described later in the "Static_file function arguments / options" section.

Pattern 1. A small file that fits in memory

You can use the static_file function as it is.

# -*- coding: utf-8 -*-
from bottle import route, run, static_file


@route('/sample_image.png')
def sample_image():
    return static_file('./my_sample_image.png', root='.')


run(host='localhost', port=8080)

If implemented with HTTPResponse, it would be as follows. It seems to be useful when you need to edit the contents of the file at runtime and do tricky things such as distribution. However, if you just want to download an existing file, it's basically easy and reliable to use the static_file function. You can use the static_file function to take care of details such as estimating the mimetype and creating a 404 response when the file does not exist.

# -*- coding: utf-8 -*-
from bottle import route, run, template, response


@route('/sample_image.png')
def sample_image():
    response.content_type = 'image/png'
    with open('./my_sample_image.png', 'rb') as fh:
        content = fh.read()
    response.set_header('Content-Length', str(len(content)))
    return content


run(host='localhost', port=8080)

Now when you access http: //localhost:8080/sample_image.png, the image will be displayed.

The handler for the HTTP request called from Bottle uses the property that the return value of the handler becomes the body of the response as it is. The HTTP response header is changed by changing the response object of the bottle package as it is. Personally, I prefer to explicitly assemble and return an HTTPResponse object like this:

# -*- coding: utf-8 -*-
from bottle import route, run, template, HTTPResponse


@route('/sample_image.png')
def sample_image():
    with open('./my_sample_image.png', 'rb') as fh:
        content = fh.read()
    resp = HTTPResponse(status=200, body=content)
    resp.content_type = 'image/png'
    resp.set_header('Content-Length', str(len(content)))
    return resp


run(host='localhost', port=8080)

If the return value of the handler is HTTPResponse, the handler for the HTTP request called from Bottle will return the response according to the contents of the HTTPResponse object. For details on HTTPResponse, please refer to Mastering Bottle's Request / Response object.

Pattern 2. Large files that don't fit in memory

Large files that don't fit in memory need to be read from the file little by little and written to the socket. Even in such cases, static_file can be used. static_file is not a response in which the entire contents of the file are read, but an implementation that internally reads and distributes the file little by little.

After investigating, it seems that when the Bottle handler becomes a generator, each element generated by the generator is sent to the client as a chunked fragment of the body. Even in the [source] of static_file (https://github.com/bottlepy/bottle/blob/master/bottle.py#L2725), the content is read little by little by the generator function _file_iter_range and it becomes the response. And it seems.

If you implement it yourself, it will be as follows.

# -*- coding: utf-8 -*-
from bottle import route, run, template, response


@route('/sample_image.png')
def sample_image():
    response.content_type = 'image/png'
    bufsize = 1024  # 1KB
    with open('./my_sample_image.png', 'rb') as fh:
        while True:
            buf = fh.read(bufsize)
            if len(buf) == 0:
                break  # EOF
            yield buf


run(host='localhost', port=8080)

You don't have to remember this method because there is a static_file? There is also a story, I think there can be an implementation that reads from the database on the fly without going through a file, processes it into a CSV file format, and sends it to the client. In fact, I found out how to do it in such a case, so I hope it helps someone else.

If you want to implement the HTTPResponse object in the form of return, set the generator to the body of the HTTPResponse object.

# -*- coding: utf-8 -*-
from bottle import route, run, template, HTTPResponse


@route('/sample_image.png')
def sample_image():
    resp = HTTPResponse(status=200)
    resp.content_type = 'image/png'

    def _file_content_iterator():
        bufsize = 1024  # 1KB
        with open('./my_sample_image.png', 'rb') as fh:
            while True:
                buf = fh.read(bufsize)
                if len(buf) == 0:
                    break  # EOF
                yield buf

    resp.body = _file_content_iterator()
    return resp


run(host='localhost', port=8080)

However, this method may also be a little unpleasant because it is difficult to understand at first glance, such as defining a function inside a function. This is your favorite.

The implementation of the generator may be confusing to those who are new to Python, but I wrote an article about Python iterators / generators Python iterators and generators. I tried it, so please have a look if you like.

I want to download it instead of displaying it inline

It's more about HTTP than about Bottle, but you can do this with the Content-Disposition header. If you are using the static_file function, you can set the download option to True as described above. Here, I will introduce the implementation of the interaction using HTTPResponse.

# -*- coding: utf-8 -*-
from bottle import route, run, template, response


@route('/sample_image.png')
def sample_image():
    response.content_type = 'image/png'
    with open('./my_sample_image.png', 'rb') as fh:
        content = fh.read()
    response.set_header('Content-Length', str(len(content)))
    download_fname = 'hoge.png'
    response.set_header('Content-Disposition', 'attachment; filename="%s"' % download_fname.encode('utf-8'))
    return content


run(host='localhost', port=8080)

Now when you access http: //localhost:8080/sample_image.png, the image will be saved with the file name hoge.png.

Explanation of arguments and options of static_file function

The signature of the static_file function is as follows.

def static_file(filename, root,
                mimetype='auto',
                download=False,
                charset='UTF-8'):
    ....

-- filename: The name of the file in root that contains the content you want to deliver. --root: The directory containing the file filename that contains the content you want to deliver. -- mimetype: Optional. By default, it is automatically guessed by the mimetypes package. --download: Optional. If you want to download in the form of saving dialog or automatically saving in the download folder, specify True or the name when saving on the client side as a character string. False for in-line display. The default is False -- charset: Optional. If mimetype starts with text / or is ʻapplication / javascript,; charset = (value of charset) is added to the Content-Type` HTTP header.

Reference link

Recommended Posts

Implemented file download with Python + Bottle
Download csv file with python
Read CSV file with python (Download & parse CSV file)
Download the file in Python
BASIC authentication with Python bottle
Draw netCDF file with python
Implemented SMO with Python + NumPy
Extract the xz file with python
[Python] Write to csv file with Python
[Automation with python! ] Part 1: Setting file
Output to csv file with Python
Create an Excel file with Python3
Download the file deployed with appcfg.py
Download python
[Automation with python! ] Part 2: File operation
Creating a simple PowerPoint file with Python
Exclusive control with lock file in Python
HTTP split download guy made with Python
Check the existence of the file with python
Quickly create an excel file with Python #python
Download Japanese stock price data with python
Let's read the RINEX file with Python ①
Download files on the web with Python
[Python] A quick web application with Bottle!
Create Excel file with Python + similarity matrix
Record with Python → Save file (sounddevice + wave)
Easily download mp3 / mp4 with python and youtube-dl!
Download the file with PHP [Under construction]
Overwrite download file for python selenium Chrome
I made a configuration file with Python
[Automation] Read mail (msg file) with Python
Upload & download wav file to X server (X-Server) by FTP with Python
Script python file
FizzBuzz with Python3
Scraping with Python
Statistics with python
Scraping with Python
Python with Go
Twilio with Python
Integrate with Python
Play with 2016-Python
Python file processing
Download the file by specifying the download destination with Python & Selemiun & Chrome (Windows version)
AES256 with python
Tested with Python
python starts with ()
with syntax (Python)
Bingo with python
Zundokokiyoshi with python
Excel with Python
Microcomputer with Python
Cast with python
Split mol2 file with python (-> 2016.04.17 Also supports sdf file)
nginxparser: Try parsing nginx config file with Python
How to read a CSV file with Python 2/3
Automatically search and download YouTube videos with Python
[Python] How to read excel file with pandas
Convert svg file to png / ico with Python
Read table data in PDF file with Python
[Python] Implemented automation in excel file copying work
Develop Windows apps with Python 3 + Tkinter (exe file)