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.
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.
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.
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.
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.
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
.
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.
Recommended Posts