Jak podawać pliki statyczne we Flask

539

To zawstydzające. Mam aplikację, w którą włączyłem Flaski na razie obsługuje ona tylko jedną statyczną stronę HTML z linkami do CSS i JS. I nie mogę znaleźć, gdzie w dokumentacji Flaskopisuje się zwracanie plików statycznych. Tak, mógłbym użyć, render_templateale wiem, że dane nie są szablonowane. Myślałbym send_filelub url_forbył słuszny, ale nie mogłem zmusić ich do działania. W międzyczasie otwieram pliki, czytam zawartość i przygotowuję Responseodpowiedni typ mimetalu:

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)

Ktoś chce podać do tego przykładowy kod lub adres URL? Wiem, że to będzie bardzo proste.

Hughdbrown
źródło
6
Dlaczego nie wykorzystać nginx lub innych serwerów WWW do obsługi plików statycznych?
atupal
8
Należy pamiętać, że sposób, w jaki faktycznie „serwujesz” pliki, prawdopodobnie będzie się różnić między produkcją (na serwerze WWW) a programowaniem (na komputerze lokalnym lub w innym obszarze testowym). Jak zauważyły ​​niektóre odpowiedzi, prawdopodobnie NIE będziesz chciał podawać plików statycznych z flask, ale zamiast tego mieć je we własnym katalogu, a następnie mieć swój serwer internetowy (Apache, nginx itp.) Bezpośrednio na tych serwerach.
Mark Hildreth,
75
„Dlaczego nie użyć nginx…” Ponieważ kiedy uruchamiam go w trybie programisty na moim laptopie, miło jest po prostu uruchomić jedną rzecz i tylko jedną. Tak, robi to trochę inaczej, ale nie ma sprawy.
Thanatos,
1
Nawet w produkcji bardzo często to widać, oczywiście z warstwą pamięci podręcznej z przodu (taką jak Varnish lub Nginx lub CDN).
Thomas Decaux,

Odpowiedzi:

642

Preferowaną metodą jest użycie nginx lub innego serwera WWW do obsługi plików statycznych; będą w stanie to zrobić wydajniej niż Flask.

Możesz jednak użyć send_from_directorydo wysłania plików z katalogu, co może być dość wygodne w niektórych sytuacjach:

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

Czy nie używać send_filelub send_static_fileze ścieżką przez użytkownika.

send_static_file przykład:

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
źródło
12
do obsługi systemu Windows: zwróć plik app.send_static_file (os.path.join ('js', ścieżka) .replace ('\\', '/'))
Tony BenBrahim
9
czy osoba atakująca może wykorzystać tę metodę do przeglądania plików źródłowych kolby, przeglądając coś w rodzaju / js / <jakieś sprytne kodowanie pliku „../ yourflaskapp.py”>?
akiva
30
@kiwi send_from_directoryma na celu rozwiązanie tego problemu bezpieczeństwa. Istnieje możliwość błędu, jeśli ścieżka prowadzi do określonego katalogu.
jpmc26
10
„Nie używaj pliku send_file lub send_static_file ze ścieżką podaną przez użytkownika.” Dlaczego nie?
Drew Verlee
6
@DenisV nie ma to nic wspólnego z samym Pythonem, to konwencja Flask do definiowania parametrów URL (patrz http://flask.pocoo.org/docs/0.12/api/#url-route-registrations ). W skrócie <path>jest to równoważne <string:path>, a ponieważ chcesz, aby Flask zapewniał parametr podobny do ścieżki, o który prosisz <path:path>.
b4stien
135

Jeśli chcesz tylko przenieść lokalizację plików statycznych, najprostszą metodą jest zadeklarowanie ścieżek w konstruktorze. W poniższym przykładzie przeniosłem moje szablony i pliki statyczne do podfolderu o nazwie web.

app = Flask(__name__,
            static_url_path='', 
            static_folder='web/static',
            template_folder='web/templates')
  • static_url_path=''usuwa poprzednią ścieżkę z adresu URL (tj. domyślną /static).
  • static_folder='web/static'aby wyświetlać pliki znalezione w folderze web/staticjako pliki statyczne.
  • template_folder='web/templates' podobnie zmienia to folder szablonów.

Przy użyciu tej metody następujący adres URL zwróci plik CSS:

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

I na koniec, oto skrót struktury folderów, gdzie flask_server.pyjest instancja Flask:

Zagnieżdżone statyczne foldery z kolb

Richard Dunn
źródło
9
To też działało dla mnie. Send_from_directory po prostu nie działał pomimo wszystkich rekomendacji.
GA
Działa idealnie. Dziękuję bardzo <3.
Thuat Nguyen,
Jak wygląda ścieżka? get_static_file('index.html')?
Batman
to działa dobrze, jak powiedział GA, nic innego nie działało dla mnie i to wszystko naprawiło. Bardzo doceniam
Andrey Starenky
81

Możesz także - i to jest moje ulubione - ustawić folder jako ścieżkę statyczną, aby pliki wewnątrz były dostępne dla wszystkich.

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

Za pomocą tego zestawu możesz używać standardowego kodu HTML:

<link rel="stylesheet" type="text/css" href="/static/style.css">
Cień
źródło
4
Działa dobrze, jeśli jest project/static/style.cssdostępny plik .
Pavel Vlasov,
6
wiersz „app = Flask (....)” również potrzebuje parametru „static_folder”, aby był parametrem
datdinhquoc
Walczę z tym problemem od wielu godzin! Brakowało mi tylko jednego argumentu!
LogicalBranch
78

Jestem pewien, że znajdziesz tam to, czego potrzebujesz: http://flask.pocoo.org/docs/quickstart/#static-files

Zasadniczo potrzebujesz tylko „statycznego” folderu w katalogu głównym pakietu, a następnie możesz użyć url_for('static', filename='foo.bar')lub bezpośrednio połączyć się z plikami za pomocą http://example.com/static/foo.bar .

EDYCJA : Jak sugerowano w komentarzach, możesz bezpośrednio użyć '/static/foo.bar'ścieżki URL, ALE url_for() narzut (pod względem wydajności) jest dość niski, a użycie go oznacza, że ​​będziesz mógł później łatwo dostosować zachowanie (zmień folder, zmień ścieżkę URL, przenieś swoje pliki statyczne do S3 itp.).

b4stien
źródło
14
Dlaczego nie '/static/foo.bar'bezpośrednio?
Tyler Long,
3
@TylerLong ma rację - jeśli chcesz połączyć się z plikiem, który jest już zapisany w katalogu statycznym, możesz połączyć się bezpośrednio z nim bez żadnego kodu trasy.
hamx0r
42

Możesz użyć tej funkcji:

send_static_file(filename)
Funkcja używana wewnętrznie do wysyłania plików statycznych z folderu statycznego do przeglądarki.

app = Flask(__name__)
@app.route('/<path:path>')
def static_file(path):
    return app.send_static_file(path)
Czarna Mamba
źródło
1
To był jedyny, który działał dla mnie bez dużego bólu głowy.
Kenny Powers
Podobnie. Kiedy zdaję sobie sprawę, że Flash w dużej mierze opiera się na pomyśle, że użyjemy ich systemu szablonów, a nie niektórych RIA, w których HTML jest produkowany gdzie indziej.
NiKo,
15
OSTRZEŻENIE: Jest to poważny problem z bezpieczeństwem, do którego można zadzwonić send_static_filepo wprowadzeniu przez użytkownika. Nie używaj tego rozwiązania do niczego ważnego.
xApple
41

Używam (i działa świetnie) katalogu „szablony” i katalogu „statycznego”. Wszystkie moje pliki .html / szablony kolby umieszczam w katalogu szablonów, a statyczny zawiera CSS / JS. Render_template działa dobrze dla ogólnych plików HTML zgodnie z moją wiedzą, niezależnie od tego, w jakim stopniu użyłeś składni szablonów Flask. Poniżej znajduje się przykładowe wywołanie w moim pliku views.py.

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

Upewnij się tylko, że używasz url_for (), jeśli chcesz odwoływać się do jakiegoś pliku statycznego w oddzielnym katalogu statycznym. Prawdopodobnie i tak to zrobisz w linkach do plików CSS / JS w html. Na przykład...

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

Oto link do „kanonicznego” nieformalnego samouczka Flask - tutaj znajdziesz wiele świetnych wskazówek, które pomogą Ci zejść na ziemię.

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

Kyle Sum
źródło
38

Najprostszym działającym przykładem opartym na innych odpowiedziach jest:

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)

Z kodem HTML o nazwie index.html :

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

WAŻNE: A index.html w folderze o nazwie statyczne , czyli <projectpath>ma .pyplik i <projectpath>\staticma htmlpliku.

Jeśli chcesz, aby serwer był widoczny w sieci, użyj app.run(debug=True, host='0.0.0.0')

EDYCJA: Aby wyświetlić wszystkie pliki w folderze na żądanie, użyj tego

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

Która jest zasadniczo BlackMambaodpowiedzią, więc daj im głos.

EpicPandaForce
źródło
Dzięki za ważną obserwację!
Gleidson Cardoso da Silva
13

Dla przepływu kątowego + kształtki kotła, który tworzy drzewo kolejnych folderów:

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

Korzystam z następującego rozwiązania:

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

Pomaga przedefiniować folder „statyczny” na niestandardowy.

użytkownik1671599
źródło
w oparciu o twoją odpowiedź zrobiłem to: stackoverflow.com/a/29521067/303114 zauważam, że użyłem 'add_url_rule' intead 'route', która jest w zasadzie taka sama
danfromisrael
7

Więc sprawiłem, że wszystko działało (na podstawie odpowiedzi @ user1671599) i chciałem się z wami podzielić.

(Mam nadzieję, że robię to dobrze, ponieważ jest to moja pierwsza aplikacja w Pythonie)

Ja to zrobiłem -

Struktura projektu:

wprowadź opis zdjęcia tutaj

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
źródło
dla lepszego zrozumienia możesz przeczytać tę odpowiedź: stackoverflow.com/a/23501776/303114 (która prowadzi do źródła w github)
danfromisrael
6

Jeden z prostych sposobów. Twoje zdrowie!

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)

Teraz utwórz nazwę folderu o nazwie szablony . Dodaj plik index.html do folderu szablonów

index.html

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

Struktura projektu

-demo.py
-templates/index.html
Maheshvirus
źródło
nie przeczytałeś pytania. Wyraźnie powiedziałem, że jestem świadomy render_templaterozwiązania, ale nie chciałem tego robić, ponieważ plik był statyczny bez żadnych zamian: „Tak, mógłbym użyć render_template, ale wiem, że dane nie są szablonowane”.
hughdbrown
Jedyne rozwiązanie, które z łatwością działało w systemie Windows, dzięki!
Basj
4

Pomysł dzielenia się ... tym przykładem.

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

Działa to lepiej i prosto.

Jeevan Chaitanya
źródło
Czy możesz wyjaśnić, w jaki sposób będzie to działać lepiej?
arsho
1
lmao co druga metoda dała mi irytujące błędy znalezienia pliku. nice1 jeevan
Dmitri DB
3

Użyj redirectiurl_for

from flask import redirect, url_for

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

To serweruje wszystkie pliki (css i js ...), do których odwołuje się twój HTML.

forzagreen
źródło
2

Najprostszym sposobem jest utworzenie folderu statycznego w głównym folderze projektu. Folder statyczny zawierający pliki .css.

główny folder

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

Obraz głównego folderu zawierającego foldery statyczne i szablony oraz skrypt kolby

kolba

from flask import Flask, render_template

app = Flask(__name__)

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

HTML (układ)

<!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
źródło
2
app = Flask(__name__, static_folder="your path to static")

Jeśli masz szablony w katalogu głównym, umieszczenie app = Flask ( nazwa ) będzie działać, jeśli plik, który ją zawiera, również znajduje się w tej samej lokalizacji, jeśli ten plik znajduje się w innej lokalizacji, musisz określić lokalizację szablonu, aby włączyć Kolba, aby wskazać lokalizację

Novak254
źródło
4
Czy możesz podać wyjaśnienie, dlaczego to działa?
gospodarka
1
Czym różni się od tej odpowiedzi, która zawiera wyjaśnienie?
Gino Mempin
Podałem wyjaśnienie mojej odpowiedzi @economy
Novak254
1

Wszystkie odpowiedzi są dobre, ale dla mnie dobrze działała tylko prosta funkcja send_fileFlask. Działa to dobrze, gdy wystarczy wysłać plik HTML jako odpowiedź, gdy host: port / ApiName wyświetli dane wyjściowe pliku w przeglądarce


@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
źródło
0

   Domyślnie kolba użyciu folderu „Szablony” zawiera wszystkie pliki szablonu (dowolny plik zwykły tekst, ale zwykle .htmlalbo jakiś język szablonów, takich jak jinja2) i folder „statyczny”, aby zawierać wszystkie pliki statyczne (tzn .js .cssi twoje obrazy).
   W twoim routes, możesz użyć render_template()do renderowania pliku szablonu (jak mówię powyżej, domyślnie jest on umieszczony w templatesfolderze) jako odpowiedź na twoje żądanie. A w pliku szablonu (zwykle jest to plik podobny do .html), możesz użyć niektórych .jsplików i / lub `.css ', więc myślę, że twoje pytanie dotyczy sposobu połączenia tych plików statycznych z bieżącym plikiem szablonu.

Harvey
źródło
0

Jeśli tylko próbujesz otworzyć plik, możesz użyć app.open_resource(). Czytanie pliku wyglądałoby więc mniej więcej tak

with app.open_resource('/static/path/yourfile'):
      #code to read the file and do something
Chaitanya Shivade
źródło