Question
So this is embarrassing. I've got an application that I threw together in
Flask
and for now it is just serving up a single static HTML page with some
links to CSS and JS. And I can't find where in the documentation Flask
describes returning static files. Yes, I could use render_template
but I
know the data is not templatized. I'd have thought send_file
or url_for
was the right thing, but I could not get those to work. In the meantime, I am
opening the files, reading content, and rigging up a Response
with
appropriate mimetype:
import os.path
from flask import Flask, Response
app = Flask(__name__)
app.config.from_object(__name__)
def root_dir(): # pragma: no cover
return os.path.abspath(os.path.dirname(__file__))
def get_file(filename): # pragma: no cover
try:
src = os.path.join(root_dir(), filename)
# Figure out how flask returns static files
# Tried:
# - render_template
# - send_file
# This should not be so non-obvious
return open(src).read()
except IOError as exc:
return str(exc)
@app.route('/', methods=['GET'])
def metrics(): # pragma: no cover
content = get_file('jenkins_analytics.html')
return Response(content, mimetype="text/html")
@app.route('/', defaults={'path': ''})
@app.route('/<path:path>')
def get_resource(path): # pragma: no cover
mimetypes = {
".css": "text/css",
".html": "text/html",
".js": "application/javascript",
}
complete_path = os.path.join(root_dir(), path)
ext = os.path.splitext(path)[1]
mimetype = mimetypes.get(ext, "text/html")
content = get_file(complete_path)
return Response(content, mimetype=mimetype)
if __name__ == '__main__': # pragma: no cover
app.run(port=80)
Someone want to give a code sample or url for this? I know this is going to be dead simple.
Answer
In production, configure the HTTP server (Nginx, Apache, etc.) in front of
your application to serve requests to /static
from the static folder. A
dedicated web server is very good at serving static files efficiently,
although you probably won't notice a difference compared to Flask at low
volumes.
Flask automatically creates a /static/<path:filename>
route that will serve
any filename
under the static
folder next to the Python module that
defines your Flask app. Use url_for
to link to static files:
url_for('static', filename='js/analytics.js')
You can also use
send_from_directory
to serve files from a directory in your own route. This takes a base directory
and a path, and ensures that the path is contained in the directory, which
makes it safe to accept user-provided paths. This can be useful in cases where
you want to check something before serving the file, such as if the logged in
user has permission.
from flask import send_from_directory
@app.route('/reports/<path:path>')
def send_report(path):
return send_from_directory('reports', path)
Do not use send_file
or send_static_file
with a user-supplied path.
This will expose you to directory traversal
attacks.
send_from_directory
was designed to safely handle user-supplied paths under
a known directory, and will raise an error if the path attempts to escape the
directory.
If you are generating a file in memory without writing it to the filesystem,
you can pass a BytesIO
object to
send_file
to serve
it like a file. You'll need to pass other arguments to send_file
in this
case since it can't infer things like the file name or content type.