Cómo servir archivos estáticos en Flask

539

Entonces esto es vergonzoso. Tengo una aplicación que reuní Flasky por ahora solo sirve una página HTML estática con algunos enlaces a CSS y JS. Y no puedo encontrar en qué parte de la documentación se Flaskdescribe la devolución de archivos estáticos. Sí, podría usar, render_templatepero sé que los datos no están en plantilla. Pensé send_fileo url_forera lo correcto, pero no pude conseguir que funcionen. Mientras tanto, estoy abriendo los archivos, leyendo contenido y armando un Responsetipo MIME apropiado:

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)

¿Alguien quiere dar una muestra de código o url para esto? Sé que esto va a ser muy simple.

hughdbrown
fuente
66
¿Por qué no usar nginx u otros servidores web para servir archivos estáticos?
atupal
8
Tenga en cuenta que la forma en que realmente está "sirviendo" los archivos probablemente diferirá entre la producción (en su servidor web) y el desarrollo (en su computadora local o alguna otra área de prueba). Como han señalado algunas respuestas, probablemente NO querrá servir sus archivos estáticos con un matraz, sino que los tendrá en su propio directorio y luego tendrá su servidor web real (Apache, nginx, etc.) servidor de esos archivos directamente.
Mark Hildreth
75
"¿Por qué no usar nginx ...?" Porque cuando lo estoy ejecutando en modo desarrollador en mi computadora portátil, es bueno solo tener que ejecutar una cosa y solo una cosa. Sí, hace las cosas un poco diferentes, pero está bien.
Thanatos
1
Incluso en producción, es muy común ver esto, por supuesto con una capa de caché al frente (como Varnish o Nginx o un CDN).
Thomas Decaux

Respuestas:

644

El método preferido es usar nginx u otro servidor web para servir archivos estáticos; podrán hacerlo más eficientemente que Flask.

Sin embargo, puede usar send_from_directorypara enviar archivos desde un directorio, lo que puede ser bastante conveniente en algunas situaciones:

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()

No , no usar send_fileo send_static_filecon una ruta proporcionada por el usuario.

send_static_file ejemplo:

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
fuente
12
para admitir Windows: devuelva app.send_static_file (os.path.join ('js', ruta) .replace ('\\', '/'))
Tony BenBrahim
99
¿Puede un atacante explotar este método para examinar los archivos de origen del matraz buscando en / js / <alguna codificación inteligente de "../ yourflaskapp.py">?
akiva
30
@kiwi send_from_directoryestá diseñado para resolver ese problema de seguridad. Existe un error si la ruta conduce fuera del directorio particular.
jpmc26
10
"No utilice send_file o send_static_file con una ruta proporcionada por el usuario". ¿Por qué no?
Drew Verlee
66
@DenisV no tiene nada que ver con Python per-se, es la convención Flask para definir parámetros de URL (ver http://flask.pocoo.org/docs/0.12/api/#url-route-registrations ). En pocas palabras <path>es equivalente a <string:path>, y porque desea que Flask garantice un parámetro similar a la ruta que solicita <path:path>.
b4stien
136

Si solo desea mover la ubicación de sus archivos estáticos, entonces el método más simple es declarar las rutas en el constructor. En el siguiente ejemplo, he movido mis plantillas y archivos estáticos a una subcarpeta llamada web.

app = Flask(__name__,
            static_url_path='', 
            static_folder='web/static',
            template_folder='web/templates')
  • static_url_path=''elimina cualquier ruta anterior de la URL (es decir, la predeterminada /static).
  • static_folder='web/static'para servir cualquier archivo encontrado en la carpeta web/staticcomo archivos estáticos.
  • template_folder='web/templates' Del mismo modo, esto cambia la carpeta de plantillas.

Con este método, la siguiente URL devolverá un archivo CSS:

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

Y finalmente, aquí hay un complemento de la estructura de carpetas, donde flask_server.pyestá la instancia de Flask:

Carpetas anidadas de matraces estáticos

Richard Dunn
fuente
99
Esto es lo que funcionó para mí también. Send_from_directory simplemente no funcionó a pesar de todas las recomendaciones para ello.
GA
Funciona perfecto Muchas gracias <3.
Thuat Nguyen
¿Cómo se ve el camino? get_static_file('index.html')?
Batman el
esto funciona bien, como dijo GA, nada más funcionó para mí y esto lo solucionó todo. Muy apreciado
Andrey Starenky
81

También puede, y este es mi favorito, configurar una carpeta como ruta estática para que todos puedan acceder a los archivos.

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

Con ese conjunto puedes usar el HTML estándar:

<link rel="stylesheet" type="text/css" href="/static/style.css">
sombra nítida
fuente
44
Funciona bien si hay un archivo project/static/style.cssdisponible.
Pavel Vlasov
66
la línea "app = Flask (....)" también necesita "static_folder" para ser un parámetro
datdinhquoc
¡He estado luchando con este problema durante horas! ¡Solo me faltaba una sola discusión!
LogicalBranch
78

Estoy seguro de que encontrará lo que necesita allí: http://flask.pocoo.org/docs/quickstart/#static-files

Básicamente, solo necesita una carpeta "estática" en la raíz de su paquete, y luego puede usar url_for('static', filename='foo.bar')o vincular directamente sus archivos con http://example.com/static/foo.bar .

EDITAR : como se sugiere en los comentarios, puede usar directamente la '/static/foo.bar'ruta URL PERO la url_for() sobrecarga (en cuanto al rendimiento) es bastante baja, y usarla significa que podrá personalizar fácilmente el comportamiento después (cambiar la carpeta, cambiar la ruta URL, mueva sus archivos estáticos a S3, etc.).

b4stien
fuente
14
¿Por qué no '/static/foo.bar'directamente?
Tyler Long
3
@TylerLong tiene razón: si desea vincular a un archivo que ya está guardado en su directorio estático, puede vincularlo directamente sin ningún código de ruta.
hamx0r
42

Puedes usar esta función:

send_static_file(filename)
Función utilizada internamente para enviar archivos estáticos desde la carpeta estática al navegador.

app = Flask(__name__)
@app.route('/<path:path>')
def static_file(path):
    return app.send_static_file(path)
Mamba negro
fuente
1
Este fue el único que funcionó para mí sin un gran dolor de cabeza.
Kenny Powers
Mismo. Donde me doy cuenta de que Flash se basa en gran medida en la idea de que usaremos su sistema de plantillas, no algunos RIA donde el HTML se produce en otro lugar.
NiKo
15
ADVERTENCIA: Esta es una gran preocupación de seguridad para llamar send_static_filecon la entrada del usuario. No use esta solución en nada importante.
xApple
41

Lo que uso (y ha estado funcionando muy bien) es un directorio de "plantillas" y un directorio "estático". Coloco todos mis archivos .html / plantillas de frascos dentro del directorio de plantillas, y static contiene CSS / JS. render_template funciona bien para archivos html genéricos, que yo sepa, independientemente de la medida en que usó la sintaxis de plantillas de Flask. A continuación se muestra una llamada de muestra en mi archivo views.py.

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

Solo asegúrese de usar url_for () cuando desee hacer referencia a algún archivo estático en el directorio estático separado. Probablemente termines haciendo esto de todos modos en tus enlaces de archivos CSS / JS en html. Por ejemplo...

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

Aquí hay un enlace al tutorial informal "Frasco" canónico: muchos consejos geniales para ayudarlo a comenzar.

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

Kyle Sum
fuente
38

Un ejemplo de trabajo más simple basado en las otras respuestas es el siguiente:

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)

Con el HTML llamado index.html :

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

IMPORTANTE: Y index.html está en una carpeta llamada static , lo que significa que <projectpath>tiene el .pyarchivo y <projectpath>\statictiene el htmlarchivo.

Si desea que el servidor esté visible en la red, use app.run(debug=True, host='0.0.0.0')

EDITAR: para mostrar todos los archivos en la carpeta si se solicita, use esto

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

Lo cual es esencialmente BlackMambala respuesta, así que denles un voto positivo.

EpicPandaForce
fuente
Gracias por la importante observación!
Gleidson Cardoso da Silva
13

Para el flujo angular + repetitivo que crea el siguiente árbol de carpetas:

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

Yo uso la siguiente solución:

...
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')
...

Ayuda a redefinir la carpeta 'estática' a personalizada.

usuario1671599
fuente
basado en su respuesta, hice esto: stackoverflow.com/a/29521067/303114 aviso que usé 'add_url_rule' intead 'route' que es básicamente lo mismo
danfromisrael
7

Así que hice que las cosas funcionaran (según la respuesta @ user1671599) y quería compartirlo con ustedes.

(Espero hacerlo bien, ya que es mi primera aplicación en Python)

Hice esto -

Estructura del proyecto:

ingrese la descripción de la imagen aquí

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
fuente
para una mejor comprensión, puede leer esta respuesta: stackoverflow.com/a/23501776/303114 (que le señala la fuente en github)
danfromisrael
6

Una de las formas simples de hacerlo. ¡Salud!

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)

Ahora cree el nombre de la carpeta llamada plantillas . Agregue su archivo index.html dentro de la carpeta de plantillas

index.html

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

Estructura del proyecto

-demo.py
-templates/index.html
Maheshvirus
fuente
No leíste la pregunta. Dije expresamente que estaba al tanto de la render_templatesolución, pero que no quería hacerlo porque el archivo era estático y no tenía reemplazos: "Sí, podría usar render_template pero sé que los datos no están en plantilla".
hughdbrown
La única solución que funcionó fácilmente en Windows, ¡gracias!
Basj
4

Pensé en compartir ... este ejemplo.

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')

Esto funciona mejor y simple.

Jeevan Chaitanya
fuente
¿Puedes por favor explicar cómo funcionará esto mejor?
arsho
1
lmao cualquier otro método me dio algunos archivos molestos errores no encontrados. nice1 jeevan
Dmitri DB
3

Uso redirectyurl_for

from flask import redirect, url_for

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

Este servidor todos los archivos (css & js ...) referenciados en su html.

forzagreen
fuente
2

La forma más simple es crear una carpeta estática dentro de la carpeta principal del proyecto. Carpeta estática que contiene archivos .css.

carpeta principal

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

Imagen de la carpeta principal que contiene carpetas estáticas y de plantillas y script de matraz

matraz

from flask import Flask, render_template

app = Flask(__name__)

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

html (diseño)

<!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
fuente
2
app = Flask(__name__, static_folder="your path to static")

Si tiene plantillas en su directorio raíz, colocar la aplicación = Flask ( nombre ) funcionará si el archivo que lo contiene también está en la misma ubicación, si este archivo está en otra ubicación, deberá especificar la ubicación de la plantilla para habilitar Frasco para señalar la ubicación

Novak254
fuente
44
¿Puedes dar alguna explicación de por qué esto funciona?
economía
1
¿Cómo difiere de esta respuesta que proporciona una explicación?
Gino Mempin
He ofrecido una explicación para mi respuesta @economy
Novak254
1

Todas las respuestas son buenas, pero lo que funcionó bien para mí es simplemente usar la función simple send_filede Flask. Esto funciona bien cuando solo necesita enviar un archivo html como respuesta cuando host: port / ApiName mostrará la salida del archivo en el navegador


@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
fuente
0

   Por defecto, el matraz utiliza una carpeta de "plantillas" para contener todos sus archivos de plantilla (cualquier archivo de texto plano, pero generalmente .htmlo algún tipo de lenguaje de plantilla como jinja2) y una carpeta "estática" para contener todos sus archivos estáticos (es decir, .js .cssy tus imágenes)
   En su routes, puede usar render_template()para representar un archivo de plantilla (como digo anteriormente, por defecto se coloca en la templatescarpeta) como respuesta a su solicitud. Y en el archivo de plantilla (generalmente es un archivo similar a .html), puede usar algunos .jsy / o archivos '.css', así que supongo que su pregunta es cómo vincular estos archivos estáticos al archivo de plantilla actual.

Harvey
fuente
0

Si solo está tratando de abrir un archivo, puede usarlo app.open_resource(). Así que leer un archivo se vería algo así

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