Comment servir des fichiers statiques dans Flask

539

C'est donc gênant. J'ai une application que j'ai jeté ensemble Flasket pour l'instant, elle ne sert qu'une seule page HTML statique avec des liens vers CSS et JS. Et je ne trouve pas où dans la documentation Flaskdécrit le retour de fichiers statiques. Oui, je pourrais utiliser render_templatemais je sais que les données ne sont pas modélisées. J'aurais pensé send_fileou url_forc'était la bonne chose, mais je ne pouvais pas les faire fonctionner. En attendant, j'ouvre les fichiers, je lis le contenu et je mets en place un Responsemimetype approprié:

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)

Quelqu'un veut donner un exemple de code ou une URL pour cela? Je sais que ça va être très simple.

Hughdbrown
la source
6
Pourquoi ne pas utiliser nginx ou d'autres serveurs Web pour servir un fichier statique.
atupal
8
N'oubliez pas que la façon dont vous "servez" les fichiers variera probablement entre la production (sur votre serveur Web) et le développement (sur votre ordinateur local ou dans une autre zone de test). Comme certaines réponses l'ont souligné, vous ne voudrez probablement PAS servir vos fichiers statiques avec une fiole, mais plutôt les avoir dans leur propre répertoire, puis votre serveur Web réel (Apache, nginx, etc.) servira ces fichiers directement.
Mark Hildreth
Copie
bebbi
75
"Pourquoi ne pas utiliser nginx ..." Parce que lorsque je l'exécute en mode développeur sur mon ordinateur portable, c'est bien de n'avoir besoin que d'exécuter une seule chose, et une seule chose. Oui, cela rend les choses un peu différentes, mais ça va.
Thanatos
1
Même en production, il est très courant de voir cela, avec bien sûr une couche de cache en face (comme Varnish ou Nginx ou un CDN).
Thomas Decaux

Réponses:

644

La méthode préférée consiste à utiliser nginx ou un autre serveur Web pour servir des fichiers statiques; ils pourront le faire plus efficacement que Flask.

Cependant, vous pouvez utiliser send_from_directorypour envoyer des fichiers à partir d'un répertoire, ce qui peut être assez pratique dans certaines situations:

from flask import Flask, request, send_from_directory

# set the project root directory as the static folder, you can set others.
app = Flask(__name__, static_url_path='')

@app.route('/js/<path:path>')
def send_js(path):
    return send_from_directory('js', path)

if __name__ == "__main__":
    app.run()

Ne pas utiliser send_fileou send_static_fileavec un chemin fourni par l' utilisateur.

send_static_file exemple:

from flask import Flask, request
# set the project root directory as the static folder, you can set others.
app = Flask(__name__, static_url_path='')

@app.route('/')
def root():
    return app.send_static_file('index.html')
atupal
la source
12
pour prendre en charge Windows: renvoyer app.send_static_file (os.path.join ('js', path) .replace ('\\', '/'))
Tony BenBrahim
9
un attaquant peut-il exploiter cette méthode pour parcourir les fichiers sources de la fiole en parcourant le type de / js / <un encodage intelligent de "../ yourflaskapp.py">?
akiva
30
@kiwi send_from_directoryest conçu pour résoudre ce problème de sécurité. Il existe une erreur si le chemin mène à l'extérieur du répertoire particulier.
jpmc26
10
"N'utilisez pas send_file ou send_static_file avec un chemin fourni par l'utilisateur." pourquoi pas?
Drew Verlee
6
@DenisV cela n'a rien à voir avec Python en soi, c'est la convention Flask pour définir les paramètres URL (voir http://flask.pocoo.org/docs/0.12/api/#url-route-registrations ). En résumé, cela <path>équivaut à <string:path>, et parce que vous voulez que Flask assure un paramètre de type chemin que vous demandez <path:path>.
b4stien
136

Si vous souhaitez simplement déplacer l'emplacement de vos fichiers statiques, la méthode la plus simple consiste à déclarer les chemins dans le constructeur. Dans l'exemple ci-dessous, j'ai déplacé mes modèles et fichiers statiques dans un sous-dossier appelé web.

app = Flask(__name__,
            static_url_path='', 
            static_folder='web/static',
            template_folder='web/templates')
  • static_url_path=''supprime tout chemin précédent de l'URL (c'est-à-dire la valeur par défaut /static).
  • static_folder='web/static'pour servir tous les fichiers trouvés dans le dossier en web/statictant que fichiers statiques.
  • template_folder='web/templates' de même, cela modifie le dossier des modèles.

En utilisant cette méthode, l'URL suivante renverra un fichier CSS:

<link rel="stylesheet" type="text/css" href="/css/bootstrap.min.css">

Et enfin, voici un aperçu de la structure des dossiers, où se flask_server.pytrouve l'instance Flask:

Dossiers de fioles statiques imbriqués

Richard Dunn
la source
9
C'est aussi ce qui a fonctionné pour moi. Send_from_directory n'a tout simplement pas fonctionné malgré toutes les recommandations.
GA
Fonctionne parfaitement. Merci beaucoup <3.
Thuat Nguyen
À quoi ressemble le chemin? get_static_file('index.html')?
Batman
cela fonctionne bien, comme l'a dit GA, rien d'autre n'a fonctionné pour moi et cela a tout réglé. Très apprécié
Andrey Starenky
81

Vous pouvez également, et c'est mon préféré, définir un dossier comme chemin statique afin que les fichiers à l'intérieur soient accessibles pour tout le monde.

app = Flask(__name__, static_url_path='/static')

Avec cet ensemble, vous pouvez utiliser le HTML standard:

<link rel="stylesheet" type="text/css" href="/static/style.css">
ombre à paupières
la source
4
Fonctionne bien si un fichier est project/static/style.cssdisponible.
Pavel Vlasov
6
la ligne "app = Flask (....)" a besoin que "static_folder" soit aussi un paramètre
datdinhquoc
Je lutte avec ce problème depuis des heures! Je ne manquais qu'un seul argument!
LogicalBranch
78

Je suis sûr que vous y trouverez ce dont vous avez besoin: http://flask.pocoo.org/docs/quickstart/#static-files

Fondamentalement, vous avez juste besoin d'un dossier "statique" à la racine de votre package, puis vous pouvez utiliser url_for('static', filename='foo.bar')ou directement un lien vers vos fichiers avec http://example.com/static/foo.bar .

EDIT : Comme suggéré dans les commentaires, vous pouvez utiliser directement le '/static/foo.bar'chemin URL MAIS les url_for() frais généraux (en termes de performances) sont assez faibles, et en l'utilisant, vous pourrez facilement personnaliser le comportement par la suite (changer le dossier, changer le chemin URL, déplacer vos fichiers statiques vers S3, etc.).

b4stien
la source
14
Pourquoi pas '/static/foo.bar'directement?
Tyler Long
3
@TylerLong a raison - si vous souhaitez créer un lien vers un fichier qui est déjà enregistré dans votre répertoire statique, vous pouvez le lier directement sans aucun code de route.
hamx0r
42

Vous pouvez utiliser cette fonction:

send_static_file(filename)
Fonction utilisée en interne pour envoyer des fichiers statiques du dossier statique vers le navigateur.

app = Flask(__name__)
@app.route('/<path:path>')
def static_file(path):
    return app.send_static_file(path)
Black Mamba
la source
1
C'était le seul qui fonctionnait pour moi sans un gros mal de tête.
Kenny Powers
Même. Là où je me rends compte, Flash s'appuie fortement sur l'idée que nous allons utiliser leur système de modèles, pas un RIA où le HTML est produit ailleurs.
NiKo
15
AVERTISSEMENT: il s'agit d'un énorme problème de sécurité à appeler send_static_fileavec une entrée utilisateur. N'utilisez pas cette solution dans quelque chose d'important.
xApple
41

Ce que j'utilise (et cela fonctionne très bien) est un répertoire "modèles" et un répertoire "statique". Je place tous mes fichiers .html / modèles de flacons dans le répertoire des modèles et statique contient CSS / JS. render_template fonctionne très bien pour les fichiers html génériques à ma connaissance, quelle que soit la mesure dans laquelle vous avez utilisé la syntaxe de création de modèles de Flask. Voici un exemple d'appel dans mon fichier views.py.

@app.route('/projects')
def projects():
    return render_template("projects.html", title = 'Projects')

Assurez-vous simplement d'utiliser url_for () lorsque vous voulez référencer un fichier statique dans le répertoire statique séparé. Vous finirez probablement par le faire de toute façon dans vos liens de fichiers CSS / JS en html. Par exemple...

<script src="{{ url_for('static', filename='styles/dist/js/bootstrap.js') }}"></script>

Voici un lien vers le didacticiel informel "canonique" de Flask - plein de bons conseils ici pour vous aider à démarrer.

http://blog.miguelgrinberg.com/post/the-flask-mega-tutorial-part-i-hello-world

Kyle Sum
la source
38

Un exemple de travail le plus simple basé sur les autres réponses est le suivant:

from flask import Flask, request
app = Flask(__name__, static_url_path='')

@app.route('/index/')
def root():
    return app.send_static_file('index.html')

if __name__ == '__main__':
  app.run(debug=True)

Avec le HTML appelé index.html :

<!DOCTYPE html>
<html>
<head>
    <title>Hello World!</title>
</head>
<body>
    <div>
         <p>
            This is a test.
         </p>
    </div>
</body>
</html>

IMPORTANT: Et index.html est dans un dossier appelé statique , ce <projectpath>qui signifie a le .pyfichier et <projectpath>\statica le htmlfichier.

Si vous souhaitez que le serveur soit visible sur le réseau, utilisez app.run(debug=True, host='0.0.0.0')

EDIT: pour afficher tous les fichiers du dossier si demandé, utilisez ce

@app.route('/<path:path>')
def static_file(path):
    return app.send_static_file(path)

Ce qui est essentiellement BlackMambala réponse, alors donnez-leur une note positive.

EpicPandaForce
la source
Merci pour cette observation importante!
Gleidson Cardoso da Silva
13

Pour un flux angulaire + passe-partout qui crée l'arborescence de dossiers suivante:

backend/
|
|------ui/
|      |------------------build/          <--'static' folder, constructed by Grunt
|      |--<proj           |----vendors/   <-- angular.js and others here
|      |--     folders>   |----src/       <-- your js
|                         |----index.html <-- your SPA entrypoint 
|------<proj
|------     folders>
|
|------view.py  <-- Flask app here

J'utilise la solution suivante:

...
root = os.path.join(os.path.dirname(os.path.abspath(__file__)), "ui", "build")

@app.route('/<path:path>', methods=['GET'])
def static_proxy(path):
    return send_from_directory(root, path)


@app.route('/', methods=['GET'])
def redirect_to_index():
    return send_from_directory(root, 'index.html')
...

Il permet de redéfinir le dossier «statique» sur personnalisé.

user1671599
la source
en fonction de votre réponse, j'ai fait ceci: stackoverflow.com/a/29521067/303114 remarque que j'ai utilisé 'add_url_rule' intead 'route' qui est fondamentalement la même
danfromisrael
7

J'ai donc fait fonctionner les choses (basé sur la réponse @ user1671599) et je voulais le partager avec vous.

(J'espère que je le fais bien puisque c'est ma première application en Python)

J'ai fait ça -

Structure du projet:

entrez la description de l'image ici

server.py:

from server.AppStarter import AppStarter
import os

static_folder_root = os.path.join(os.path.dirname(os.path.abspath(__file__)), "client")

app = AppStarter()
app.register_routes_to_resources(static_folder_root)
app.run(__name__)

AppStarter.py:

from flask import Flask, send_from_directory
from flask_restful import Api, Resource
from server.ApiResources.TodoList import TodoList
from server.ApiResources.Todo import Todo


class AppStarter(Resource):
    def __init__(self):
        self._static_files_root_folder_path = ''  # Default is current folder
        self._app = Flask(__name__)  # , static_folder='client', static_url_path='')
        self._api = Api(self._app)

    def _register_static_server(self, static_files_root_folder_path):
        self._static_files_root_folder_path = static_files_root_folder_path
        self._app.add_url_rule('/<path:file_relative_path_to_root>', 'serve_page', self._serve_page, methods=['GET'])
        self._app.add_url_rule('/', 'index', self._goto_index, methods=['GET'])

    def register_routes_to_resources(self, static_files_root_folder_path):

        self._register_static_server(static_files_root_folder_path)
        self._api.add_resource(TodoList, '/todos')
        self._api.add_resource(Todo, '/todos/<todo_id>')

    def _goto_index(self):
        return self._serve_page("index.html")

    def _serve_page(self, file_relative_path_to_root):
        return send_from_directory(self._static_files_root_folder_path, file_relative_path_to_root)

    def run(self, module_name):
        if module_name == '__main__':
            self._app.run(debug=True)
danfromisrael
la source
pour une meilleure compréhension, vous pouvez lire cette réponse: stackoverflow.com/a/23501776/303114 (qui vous indique la source dans github)
danfromisrael
6

L'une des façons les plus simples de le faire. À votre santé!

demo.py

from flask import Flask, render_template
app = Flask(__name__)

@app.route("/")
def index():
   return render_template("index.html")

if __name__ == '__main__':
   app.run(debug = True)

Créez maintenant un nom de dossier appelé modèles . Ajoutez votre index.html fichier à l' intérieur des modèles dossier

index.html

<!DOCTYPE html>
<html>
<head>
    <title>Python Web Application</title>
</head>
<body>
    <div>
         <p>
            Welcomes You!!
         </p>
    </div>
</body>
</html>

Structure du projet

-demo.py
-templates/index.html
Maheshvirus
la source
vous n'avez pas lu la question. J'ai dit expressément que j'étais au courant de la render_templatesolution mais que je ne voulais pas le faire car le fichier était statique sans remplacement: "Oui, je pourrais utiliser render_template mais je sais que les données ne sont pas modélisées."
hughdbrown
La seule solution qui fonctionnait facilement sous Windows, merci!
Basj
4

Pensée à partager .... cet exemple.

from flask import Flask
app = Flask(__name__)

@app.route('/loading/')
def hello_world():
    data = open('sample.html').read()    
    return data

if __name__ == '__main__':
    app.run(host='0.0.0.0')

Cela fonctionne mieux et simplement.

Jeevan Chaitanya
la source
Pouvez-vous expliquer comment cela fonctionnera mieux?
arsho le
1
lmao toutes les autres méthodes m'ont donné quelques erreurs de fichier ennuyeux non trouvées. nice1 jeevan
Dmitri DB
3

Utiliser redirecteturl_for

from flask import redirect, url_for

@app.route('/', methods=['GET'])
def metrics():
    return redirect(url_for('static', filename='jenkins_analytics.html'))

Celui-ci sert tous les fichiers (css & js ...) référencés dans votre html.

forzagreen
la source
2

La manière la plus simple est de créer un dossier statique dans le dossier principal du projet. Dossier statique contenant des fichiers .css.

dossier principal

/Main Folder
/Main Folder/templates/foo.html
/Main Folder/static/foo.css
/Main Folder/application.py(flask script)

Image du dossier principal contenant les dossiers statiques et modèles et le script de la fiole

ballon

from flask import Flask, render_template

app = Flask(__name__)

@app.route("/")
def login():
    return render_template("login.html")

html (mise en page)

<!DOCTYPE html>
<html>
    <head>
        <title>Project(1)</title>
        <link rel="stylesheet" href="/static/styles.css">
     </head>
    <body>
        <header>
            <div class="container">
                <nav>
                    <a class="title" href="">Kamook</a>
                    <a class="text" href="">Sign Up</a>
                    <a class="text" href="">Log In</a>
                </nav>
            </div>
        </header>  
        {% block body %}
        {% endblock %}
    </body>
</html>

html

{% extends "layout.html" %}

{% block body %}
    <div class="col">
        <input type="text" name="username" placeholder="Username" required>
        <input type="password" name="password" placeholder="Password" required>
        <input type="submit" value="Login">
    </div>
{% endblock %}
ESbros
la source
2
app = Flask(__name__, static_folder="your path to static")

Si vous avez des modèles dans votre répertoire racine, placer l'application = Flask ( nom ) fonctionnera si le fichier qui le contient se trouve également au même emplacement, si ce fichier est dans un autre emplacement, vous devrez spécifier l'emplacement du modèle à activer Flacon pour pointer vers l'emplacement

Novak254
la source
4
Pouvez-vous expliquer pourquoi cela fonctionne?
économie
1
En quoi diffère-t-elle de cette réponse qui fournit une explication?
Gino Mempin
J'ai offert une explication pour ma réponse @economy
Novak254
1

Toutes les réponses sont bonnes, mais ce qui a bien fonctionné pour moi, c'est simplement d'utiliser la fonction simple send_filede Flask. Cela fonctionne bien lorsque vous avez juste besoin d'envoyer un fichier html comme réponse lorsque host: port / ApiName affichera la sortie du fichier dans le navigateur


@app.route('/ApiName')
def ApiFunc():
    try:
        return send_file('some-other-directory-than-root/your-file.extension')
    except Exception as e:
        logging.info(e.args[0])```
Binoy S Kumar
la source
0

   Par défaut, flask utilise un dossier "templates" pour contenir tous vos fichiers de template (n'importe quel fichier en texte brut, mais généralement .htmlou une sorte de langage de template tel que jinja2) & un dossier "statique" pour contenir tous vos fichiers statiques (ie .js .csset vos images).
   Dans votre routes, vous pouvez utiliser render_template()pour rendre un fichier de modèle (comme je le dis ci-dessus, par défaut, il est placé dans le templatesdossier) comme réponse à votre demande. Et dans le fichier de modèle (c'est généralement un fichier de type .html), vous pouvez utiliser certains .jsfichiers et / ou `.css ', donc je suppose que votre question est de savoir comment lier ces fichiers statiques au fichier de modèle actuel.

Harvey
la source
0

Si vous essayez simplement d'ouvrir un fichier, vous pouvez utiliser app.open_resource(). La lecture d'un fichier ressemblerait donc à quelque chose

with app.open_resource('/static/path/yourfile'):
      #code to read the file and do something
Chaitanya Shivade
la source