With Flask, serving static content
how can I configure flask to serve index.html or index.html
when the user requests the folder... like "http://example.com/xxx/"
(as the default behavoiur in Apache/IIS/nginx/etc.)
You can use send_from_directory to send files from a directory, which can be pretty convenient in some situations:
#app.route('/somepath/<path:path>')
def send_html(path):
return send_from_directory('some_folder', path)
Related
I want to restrict files to be available to logged in users, but otherwise return a 403 error or similar. For example a user should be able to view/download /static/data/example.csv only if they're logged in.
I know how to control the actual displaying of the files using Flask-Login if they're not logged in, but not how to block access to the file if they visit the link directly in their browser.
Flask adds a static route to serve static files. When you're in production, you typically "short circuit" this route so that Nginx serves the files before the request ever gets to your app. Instead of adding this "short circuit", leave it out and let Flask handle the requests. Overwrite the static route with one that is wrapped by Flask-Login's login_required.
from flask_login import login_required
app.view_functions['static'] = login_required(app.send_static_file)
This is typically overkill though, since you want truly static files to be served no matter what so that pages look right to non-logged in users (otherwise the CSS wouldn't even be sent for the login page). Instead, "short circuit" the static folder to be served by Nginx, and define a route that will serve protected files from some other directory, such as the instance folder. See flask.send_from_directory.
import os
from flask import send_from_directory
from flask_login import login_required
#app.route('/protected/<path:filename>')
#login_required
def protected(filename):
return send_from_directory(
os.path.join(app.instance_path, 'protected'),
filename
)
This will serve files from the directory "protected" in the instance folder to logged in users only. Other restrictions could also be added, such as only allowing certain users access to certain files. Similar to the static path, you can generate a url to a file with:
url_for('protected', filename='data/example.csv')
I have a flask application where a user's profile image is stored. I originally stored the images in the static directory like so:
application.py
templates/
static/userdata/user/icon.png
Though I don't think this is a good idea because it is not good practice to modify the static directory in production.
I tried making a new userdata folder at root like so:
application.py
templates/
static/
userdata/user/icon.png
Though when I try to access the file with Jinja and HTML,
<img src="/userdata/user/icon.png">
the image does not show. Why is this?
Thanks, in advance.
Use the url_for function
.html
<img src="{{ url_for('userdata', filename='/user/icon.png')}}">
.py
from flask import send_file
#route('/userdata/<filename:filename>')
def get_user_data_files(filename):
return send_file(app.config['USER_DATA_FOLDER'] + filename)
I have a single page app (SPA) that I am running off of Google App Engine (GAE). GAE does three things:
Serve the index.html file
Serve the static files (JS, CSS, etc.)
Serve the dynamic files (images, text, etc. via REST)
I use the following app.yaml configuration.
handlers:
- url: /app
static_dir: app
- url: /.*
script: main.app
My understanding is that this should match any requests going to the /app folder, which would serve my static files. All the REST services and the main index page would then be caught by the /.* and processed by main.py
However, I see the following behavior:
If I remove the /app handler, I can successfully serve the index.html (via Jinja templating) and the REST services (such as localhost/subjects/). However, I cannot see the static files (as expected).
If I add the /app handler, the index.html file does not serve and gives an "Internal Server Error" IOError(errno.EACCES, 'file not accessible', filename). However, when I request a static file such as: "localhost/app/app.js", this succeeds.
Is there something that I am missing here? I do not understand why the two would conflict.
Thank you!
Notes:
Google App Engine 1.8.9, Python 2.7, Developing locally
EDIT:
Here is the Python code I am using to serve the page
path = os.path.join(os.path.dirname(__file__), 'app')
jinja_environment = jinja2.Environment(loader=jinja2.FileSystemLoader(path))
class MainHandler(webapp2.RequestHandler):
def get(self):
self.response.headers['Content-Type'] = 'text/html'
template_values = {}
template = jinja_environment.get_template('index.html')
self.response.out.write(template.render(template_values))
My directory structure is as follows:
/
main.py
app.yaml & etc.
app
index.html
app.js
Module A
moduleA.tpl.html
moduleA.js
Edit 2:
I moved index.html to the root directory (/), and then used the following python code:
path = os.path.dirname(__file__)
jinja_environment = jinja2.Environment(loader=jinja2.FileSystemLoader(path))
It (very interestingly) appears that the "double mapping" of the index.html to the Jinja template and to the static directory files caused a problem. I wonder what is the best practice way to do this. I use Jinja for one reason: adding the (GAE generated) login/logout link to the index.html file. Other than that, there is no reason for using it.
Python is by default unable to access files or directories marked as static in App Engine. You can add application_readable: true to your handler mapping to enable this.
Another solution would be to move the index.html away from the static folder since it in fact isn't a static file but instead is a jinja template for Python.
Related question: Read a file on App Engine with Python?
Documentation for application_readable
application_readable
Optional. By default, files declared in static file handlers are uploaded as static data and are only served to end users, they cannot be read by an application. If this field is set to true, the files are also uploaded as code data so your application can read them. Both uploads are charged against your code and static data storage resource quotas.
Ok it might explains scenario #2, how do you access the index.html? If you add /app to the handler and your request url is something like /app/index.html it will serve from the static dir.
On your scenario seems your url does not contain /app, therefore it goes to the 2nd handler rule which is going to main.app.
However since you put the html inside the /app, appengine treats it as static file. If you want to serve the file from jinja template, you should not put it in static_dir
Jinja templates (or Django, Mako etc) has no requirement to be sat in a publicly accessible folder. They are always called via a handler in the application and compiled in the application before serving to the user.
It is common practice to put these in a /templates directory. There is no reference to this directory in app.yaml, it is purely used internally to serve the templates. Check out some of the boilerplate apps in github (search 'gae boilerplate'). This one is minimal and uses Jinja so may be a good example for you https://github.com/SoulAuctioneer/notvanillae
This question already has answers here:
Static files in Flask - robot.txt, sitemap.xml (mod_wsgi)
(10 answers)
Closed 8 years ago.
I've read on quiet a few places that serving static files should be left to the server, for example in a couple of the answers on this SO question. But I use the OpenShift PaaS, and can't figure out how to modify the .htaccess file there.
I came across this piece of code that serves the sitemap from a template. I did that on my app for both the sitemap, and robots.txt, like so -
#app.route("/sitemap.xml")
def sitemap_xml():
response= make_response(render_template("sitemap.xml"))
response.headers['Content-Type'] = 'application/xml'
return response
#app.route("/robots.txt")
def robots_txt():
return render_template("robots.txt")
Is there any harm in this, or is my approach okay?
Put robots.txt and sitemap.xml into your app's static directory and define this view:
from flask import Flask, request, send_from_directory
#app.route('/robots.txt')
#app.route('/sitemap.xml')
def static_from_root():
return send_from_directory(app.static_folder, request.path[1:])
Flask has built in support for serving static files.
Make a /static directory and put your files there. Then, when you instantiate Flask, specify the static_url_path parameter:
app = Flask(__name__, static_url_path='/')
The default is to serve static files from the /static/ path, but you want them served from / so they are where expected.
See the Flask API Docs for more info.
In addition to overhead and unnecessary code, the problem with your approach is if / when one of the files you want to serve contains something that looks like a template tag to render_template -- you can cause a rendering error. If you were to read the file into memory (once, not inside the method) then use that string as the body of the response without calling render_template, you would at least avoid that problem.
The best way is to set static_url_path to root url
from flask import Flask
app = Flask(__name__, static_folder='static', static_url_path='')
I am developing a SAAS application using Flask and I want users to be able to create/upload/use their own custom templates. This is how my directory structure is right now :
/flaskapp
/application.py
/static
/style.css
/templates (site & admin templates goes here)
/hello.html
/userdata
/user1
/template1
hello.html
/template2
hello.html
/user2
/template1
hello.html
/template2
hello.html
I am able to serve user specified templates using a solution found through this stackoverflow question : How to dynamically select template directory to be used in flask? but how do I serve static files from template directory. Instead of serving static files from /flaskapp/static/ I want to serve static files using /flaskapp/userdata/<user>/<current-template>/static/ directory where and will be determined dynamically at run time. How to do this?
Presumably you're using a web server in front of Flask. One way to solve this (which I generally use when using Apache + mod_wsgi for custom WSGI apps) is to just serve the directory straight from disk via the web server. On Apache, I just use an Alias directive for this.
If you want to vary the file served under a given URL per user, you would have to pipe the file through Flask. You'd have to figure out how to properly route the request; after that, you might be able to use wsgi.file_wrapper to send the correct file (though I'm not sure how you'd get at this through Flask).
This is what I use to serve files from whatever system directory.
The app searches a specific directory for a specific requested file:
when someone accesses http:/host/where/to/serve/files/example_file.txt the application will try to return example_file.txt as attachment, if the file doesn't exist it will return a 404.
You can adjust this and build the DIRECTORY_TO_SERVE_PATH with your user variables.
You should also validate the files to serve if you have any restrictions because this returns any file that exists in the path.
import os
from flask import abort, send_from_directory
DIRECTORY_TO_SERVE_PATH = '/where/files/are/in/disk/'
#app.route('/where/to/serve/files/<path:filename>')
def download_file(filename):
if os.path.exists(DIRECTORY_TO_SERVE_PATH + filename):
return send_from_directory(DIRECTORY_TO_SERVE_PATH, filename, as_attachment=True)
else:
abort(404)