วิธีการให้บริการไฟล์คงที่ใน Flask


539

ดังนั้นนี่น่าอาย ฉันมีแอปพลิเคชันที่ฉันได้รวมเข้าด้วยกันFlaskและตอนนี้มันเป็นเพียงการให้บริการเพจ HTML แบบสแตติกเดียวที่มีลิงก์ไปยัง CSS และ JS และฉันไม่สามารถหาได้ที่ไหนในเอกสารFlaskอธิบายการคืนไฟล์ ใช่ฉันสามารถใช้render_templateแต่ฉันรู้ว่าข้อมูลไม่ได้ถูกทำให้เป็นเทมเพลต ฉันคิดsend_fileหรือurl_forเป็นสิ่งที่ถูกต้อง แต่ฉันไม่สามารถทำงานได้ ในระหว่างนี้ฉันกำลังเปิดไฟล์อ่านเนื้อหาและจัดการResponseกับ 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)

มีคนต้องการให้ตัวอย่างโค้ดหรือ URL เพื่อสิ่งนี้ ฉันรู้ว่านี่จะเป็นเรื่องง่าย


6
ทำไมไม่ใช้ nginx หรือเว็บเซิร์ฟเวอร์อื่น ๆ เพื่อให้บริการไฟล์คงที่
atupal

8
โปรดทราบว่าวิธีการที่คุณ "ให้บริการ" ไฟล์อาจแตกต่างกันระหว่างการผลิต (บนเว็บเซิร์ฟเวอร์ของคุณ) และการพัฒนา (บนเครื่องคอมพิวเตอร์ของคุณหรือพื้นที่ทดสอบอื่น ๆ ) ตามที่บางคำตอบได้ชี้ให้เห็นคุณอาจจะไม่ต้องการให้บริการไฟล์สแตติกของคุณกับขวด แต่แทนที่จะมีไว้ในไดเรกทอรีของตัวเองแล้วมีเว็บเซิร์ฟเวอร์ของคุณ (Apache, nginx และอื่น ๆ ) ไฟล์เหล่านั้นโดยตรง
Mark Hildreth


75
"ทำไมไม่ใช้ nginx ... " เพราะเมื่อฉันใช้งานในโหมดนักพัฒนาซอฟต์แวร์บนแล็ปท็อปของฉันมันก็ดีที่จะต้องทำงานอย่างใดอย่างหนึ่งและสิ่งเดียวเท่านั้น ใช่มันทำให้เรื่องแตกต่างกันเล็กน้อย แต่ก็โอเค
Thanatos

1
แม้ในการผลิตเป็นเรื่องธรรมดามากที่จะเห็นสิ่งนี้โดยแน่นอนว่ามีเลเยอร์แคชไว้ด้านหน้า (เช่น Varnish หรือ Nginx หรือ CDN)
Thomas Decaux

คำตอบ:


643

วิธีที่ต้องการคือการใช้ nginx หรือเว็บเซิร์ฟเวอร์อื่นเพื่อให้บริการไฟล์คงที่; พวกเขาจะสามารถทำได้อย่างมีประสิทธิภาพมากกว่า Flask

อย่างไรก็ตามคุณสามารถใช้send_from_directoryเพื่อส่งไฟล์จากไดเรกทอรีซึ่งค่อนข้างสะดวกในบางสถานการณ์:

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

ไม่ได้ใช้send_fileหรือsend_static_fileมีเส้นทางที่ผู้ใช้จัด

send_static_file ตัวอย่าง:

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

12
เพื่อรองรับ Windows: คืน app.send_static_file (os.path.join ('js', path) .replace ('\\', '/'))
Tony BenBrahim

9
ผู้โจมตีสามารถใช้ประโยชน์จากวิธีนี้เพื่อเรียกดูไฟล์ต้นฉบับของขวดโดยการเรียกดู / js / <การเข้ารหัสที่ฉลาดบางอย่างของ "../ yourflaskapp.py">?
akiva

30
@kiwi send_from_directoryถูกออกแบบมาเพื่อแก้ปัญหาความปลอดภัย มีข้อผิดพลาดถ้าเส้นทางนำไปสู่นอกไดเรกทอรีเฉพาะ
jpmc26

10
"อย่าใช้ send_file หรือ send_static_file กับเส้นทางที่ผู้ใช้กำหนด" ทำไมจะไม่ล่ะ?
Drew Verlee

6
@DenisV ไม่มีส่วนเกี่ยวข้องกับ Python ต่อ se เป็นระเบียบการ Flask สำหรับการกำหนดพารามิเตอร์ URL (ดูhttp://flask.pocoo.org/docs/0.12/api/#url-route-registrations ) สรุป<path>เทียบเท่ากับและเพราะคุณต้องการขวดเพื่อให้แน่ใจว่าเส้นทางเหมือนพารามิเตอร์ที่คุณขอ<string:path> <path:path>
b4stien

135

หากคุณต้องการย้ายตำแหน่งของไฟล์สแตติกวิธีที่ง่ายที่สุดคือการประกาศพา ธ ใน Constructor webในตัวอย่างด้านล่างที่ผมได้ย้ายแม่แบบและไฟล์ของฉันคงเป็นโฟลเดอร์ย่อยที่เรียกว่า

app = Flask(__name__,
            static_url_path='', 
            static_folder='web/static',
            template_folder='web/templates')
  • static_url_path=''ลบเส้นทางก่อนหน้าจาก URL (เช่นค่าเริ่มต้น/static)
  • static_folder='web/static'เพื่อให้บริการไฟล์ใด ๆ ที่พบในโฟลเดอร์ web/staticเป็นไฟล์คงที่
  • template_folder='web/templates' ในทำนองเดียวกันการเปลี่ยนแปลงโฟลเดอร์แม่แบบ

ใช้วิธีนี้ URL ต่อไปนี้จะคืนไฟล์ CSS:

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

และสุดท้ายนี่คือโครงสร้างโฟลเดอร์อย่างรวดเร็วซึ่งflask_server.pyเป็นตัวอย่างของ Flask:

โฟลเดอร์กระติกน้ำคงที่ซ้อนกัน


9
นี่คือสิ่งที่ทำงานสำหรับฉันเช่นกัน Send_from_directory ไม่ทำงานแม้จะมีคำแนะนำทั้งหมดก็ตาม
GA

ทำงานได้สมบูรณ์แบบ ขอบคุณมาก <3
Thuat Nguyen

เส้นทางมีลักษณะอย่างไร get_static_file('index.html')?
แบทแมน

ทำงานได้ดีเช่น GA กล่าวไม่มีอะไรทำงานให้ฉันและสิ่งนี้แก้ไขได้ทั้งหมด ชื่นชมมาก
Andrey Starenky

81

นอกจากนี้คุณยังสามารถตั้งค่าโฟลเดอร์เป็นเส้นทางแบบคงที่เพื่อให้ทุกคนสามารถเข้าถึงไฟล์ภายในได้

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

ด้วยชุดนั้นคุณสามารถใช้ HTML มาตรฐาน:

<link rel="stylesheet" type="text/css" href="/static/style.css">

4
ทำงานได้ดีถ้ามีไฟล์project/static/style.cssที่มีอยู่
Pavel Vlasov

6
บรรทัด "app = Flask (.... )" ต้องการ "static_folder" เพื่อเป็นพารามิเตอร์ด้วย
datdinhquoc

ฉันดิ้นรนกับปัญหานี้มาหลายชั่วโมงแล้ว! ฉันแค่โต้เถียงเดียวเท่านั้น!
LogicalBranch

78

ฉันแน่ใจว่าคุณจะพบสิ่งที่คุณต้องการ: http://flask.pocoo.org/docs/quickstart/#static-files

โดยทั่วไปคุณเพียงแค่ต้องโฟลเดอร์ "คงที่" ที่รากของแพคเกจของคุณและจากนั้นคุณสามารถใช้url_for('static', filename='foo.bar')หรือเชื่อมโยงไปยังไฟล์ของคุณโดยตรงกับhttp://example.com/static/foo.bar

แก้ไข : ตามที่แนะนำในความคิดเห็นที่คุณสามารถใช้'/static/foo.bar'เส้นทาง URL โดยตรงแต่ url_for()ค่าใช้จ่าย (ประสิทธิภาพฉลาด) ค่อนข้างต่ำและการใช้หมายความว่าคุณจะสามารถปรับแต่งพฤติกรรมในภายหลังได้อย่างง่ายดาย (เปลี่ยนโฟลเดอร์เปลี่ยนเส้นทาง URL ย้ายไฟล์สแตติกของคุณไปที่ S3 ฯลฯ )


14
ทำไมไม่'/static/foo.bar'โดยตรง
Tyler Long

3
@TylerLong ถูกต้อง - หากคุณต้องการเชื่อมโยงไปยังไฟล์ที่ถูกบันทึกไว้ในไดเรกทอรีคงที่ของคุณคุณสามารถเชื่อมโยงไปยังไฟล์โดยตรงโดยไม่ต้องมีรหัสเส้นทาง
hamx0r

42

คุณสามารถใช้ฟังก์ชั่นนี้:

send_static_file(filename)
ฟังก์ชั่นที่ใช้ภายในเพื่อส่งไฟล์คงที่จากโฟลเดอร์คงที่ไปยังเบราว์เซอร์

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

1
นี่เป็นสิ่งเดียวที่ทำงานให้ฉันโดยไม่ต้องปวดหัวมาก
Kenny Powers

เหมือนกัน. ที่ฉันรู้ว่า Flash พึ่งพาอย่างหนักกับแนวคิดที่ว่าเราจะใช้ระบบเทมเพลตของพวกเขาไม่ใช่ RIA ที่ผลิต HTML ที่อื่น
NiKo

15
คำเตือน: นี่เป็นข้อกังวลด้านความปลอดภัยอย่างมากที่จะโทรหาsend_static_fileโดยอินพุตของผู้ใช้ อย่าใช้โซลูชันนี้ในสิ่งที่สำคัญ
xApple

41

สิ่งที่ฉันใช้ (และใช้งานได้ดี) คือไดเรกทอรี "แม่แบบ" และไดเรกทอรี "คงที่" ฉันวางไฟล์. html / เทมเพลต Flask ทั้งหมดไว้ในไดเรกทอรีของเทมเพลตและคงมี CSS / JS render_template ทำงานได้ดีสำหรับไฟล์ html ทั่วไปตามความรู้ของฉัน ด้านล่างนี้เป็นตัวอย่างการโทรในไฟล์ views.py ของฉัน

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

เพียงตรวจสอบให้แน่ใจว่าคุณใช้ url_for () เมื่อคุณต้องการอ้างอิงไฟล์สแตติกบางไฟล์ในไดเรกทอรีคงที่แยกกัน คุณอาจจะลงเอยด้วยการทำสิ่งนี้ต่อไปในลิงก์ไฟล์ CSS / JS ในรูปแบบ html เช่น ...

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

ต่อไปนี้เป็นลิงก์ไปยังบทช่วยสอน "มาตรฐาน" Flask อย่างไม่เป็นทางการ - เคล็ดลับดีๆมากมายที่นี่เพื่อช่วยให้คุณทำงานได้อย่างราบรื่น

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


38

ตัวอย่างการทำงานที่ง่ายที่สุดโดยยึดตามคำตอบอื่น ๆ คือ:

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)

ด้วย HTML ที่เรียกว่าindex.html :

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

สำคัญ:และindex.htmlอยู่ในโฟลเดอร์ที่ชื่อว่าstaticหมายถึง<projectpath>มี.pyไฟล์และ<projectpath>\staticมีhtmlไฟล์

หากคุณต้องการให้เซิร์ฟเวอร์ปรากฏบนเครือข่ายให้ใช้ app.run(debug=True, host='0.0.0.0')

แก้ไข:สำหรับการแสดงไฟล์ทั้งหมดในโฟลเดอร์หากมีการร้องขอให้ใช้สิ่งนี้

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

ซึ่งเป็นBlackMambaคำตอบของหลักดังนั้นให้ upvote พวกเขา


ขอบคุณสำหรับการสังเกตที่สำคัญ!
Gleidson Cardoso da Silva

13

สำหรับการไหลเชิงมุม + boilerplate ซึ่งสร้างแผนผังโฟลเดอร์ถัดไป:

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

ฉันใช้วิธีแก้ปัญหาต่อไปนี้:

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

ช่วยกำหนดโฟลเดอร์ 'คงที่' ใหม่เพื่อกำหนดเอง


ตามคำตอบของคุณฉันทำสิ่งนี้: stackoverflow.com/a/29521067/303114 ข้อสังเกตฉันใช้ 'add_url_rule' intead 'route' ซึ่งเป็นพื้นฐานเดียวกัน
danfromisrael

7

ดังนั้นฉันจึงทำงานได้ (ตาม @ user1671599 คำตอบ) และต้องการแบ่งปันกับคุณ

(ฉันหวังว่าฉันทำถูกต้องเพราะมันเป็นแอปแรกของฉันใน Python)

ฉันทำอย่างนี้ -

โครงสร้างโครงการ:

ป้อนคำอธิบายรูปภาพที่นี่

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)

เพื่อความเข้าใจที่ดีขึ้นคุณสามารถอ่านคำตอบนี้: stackoverflow.com/a/23501776/303114 (ซึ่งนำคุณไปยังแหล่งที่มาใน GitHub)
danfromisrael

6

หนึ่งในวิธีง่ายๆในการทำ ไชโย!

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)

ตอนนี้เรียกว่าสร้างชื่อโฟลเดอร์แม่แบบ เพิ่มไฟล์index.htmlของคุณไว้ในโฟลเดอร์แม่แบบ

index.html

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

โครงสร้างโครงการ

-demo.py
-templates/index.html

คุณไม่ได้อ่านคำถาม ฉันบอกอย่างชัดเจนว่าฉันรู้render_templateวิธีแก้ปัญหา แต่ไม่ต้องการทำเพราะไฟล์เป็นแบบสแตติกที่ไม่มีการแทนที่: "ใช่ฉันสามารถใช้ render_template แต่ฉันรู้ว่าข้อมูลไม่ได้ถูกทำให้เป็นเทมเพลต"
hughdbrown

ทางออกเดียวที่ทำงานได้ง่ายใน Windows ขอบคุณ!
Basj

4

ความคิดที่จะแบ่งปัน .... ตัวอย่างนี้

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

มันใช้งานได้ดีและเรียบง่าย


คุณช่วยอธิบายเพิ่มเติมได้ไหมว่ามันจะทำงานได้ดีขึ้นได้อย่างไร
arsho

1
lmao ทุกวิธีอื่นให้ฉันไฟล์ที่น่ารำคาญไม่พบข้อผิดพลาด nice1 jeevan
Dmitri DB

3

ใช้redirectและurl_for

from flask import redirect, url_for

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

เซิร์ฟเวอร์นี้ไฟล์ทั้งหมด (css & js ... ) อ้างอิงใน html ของคุณ


2

วิธีที่ง่ายที่สุดคือการสร้างโฟลเดอร์คงที่ภายในโฟลเดอร์โครงการหลัก โฟลเดอร์คงที่ที่มีไฟล์. css

โฟลเดอร์หลัก

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

ภาพของโฟลเดอร์หลักที่มีโฟลเดอร์แบบคงที่และแม่แบบและสคริปต์ขวด

ขวด

from flask import Flask, render_template

app = Flask(__name__)

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

html (เลย์เอาต์)

<!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 %}

2
app = Flask(__name__, static_folder="your path to static")

หากคุณมีแม่แบบในไดเรกทอรีรากการวางแอพ = Flask ( ชื่อ ) จะทำงานหากไฟล์ที่มีสิ่งนี้อยู่ในตำแหน่งเดียวกันหากไฟล์นี้อยู่ในตำแหน่งอื่นคุณจะต้องระบุตำแหน่งเทมเพลตเพื่อเปิดใช้งาน ขวดให้ชี้ไปที่ตำแหน่ง


4
คุณสามารถให้คำอธิบายเกี่ยวกับสาเหตุที่ใช้งานได้บ้าง
เศรษฐกิจ

1
แตกต่างจากคำตอบนี้ซึ่งให้คำอธิบายอย่างไร
Gino Mempin

ฉันเสนอคำอธิบายสำหรับคำตอบ @economy ของฉัน
Novak254

1

คำตอบทั้งหมดนั้นดี แต่สิ่งที่ใช้ได้ผลดีสำหรับฉันคือการใช้ฟังก์ชั่นง่ายๆsend_fileจาก Flask สิ่งนี้ทำงานได้ดีเมื่อคุณต้องการส่งไฟล์ html เป็นการตอบสนองเมื่อโฮสต์: พอร์ต / ApiNameจะแสดงผลลัพธ์ของไฟล์ในเบราว์เซอร์


@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])```

0

   โดยค่าเริ่มต้นขวดจะใช้โฟลเดอร์ "เทมเพลต" เพื่อเก็บไฟล์เทมเพลตทั้งหมดของคุณ (ไฟล์ข้อความธรรมดา แต่โดยทั่วไป.htmlหรือภาษาเทมเพลตบางชนิดเช่น jinja2) และโฟลเดอร์ "คงที่" เพื่อเก็บไฟล์สแตติกทั้งหมดของคุณ (เช่น.js .cssและ ภาพของคุณ)
   ในของคุณคุณroutesสามารถใช้render_template()เพื่อแสดงไฟล์เทมเพลต (ตามที่ฉันพูดข้างต้นโดยค่าเริ่มต้นมันจะอยู่ในtemplatesโฟลเดอร์) เป็นการตอบสนองคำขอของคุณ และในไฟล์เทมเพลต (มักเป็นไฟล์. html เหมือนกัน) คุณอาจใช้ไฟล์บางไฟล์.jsและ / หรือ `.css 'ดังนั้นฉันเดาว่าคำถามของคุณคือวิธีลิงก์ไฟล์สแตติกเหล่านี้กับไฟล์เทมเพลตปัจจุบัน


0

app.open_resource()หากคุณเป็นเพียงการพยายามที่จะเปิดไฟล์คุณสามารถใช้ ดังนั้นการอ่านไฟล์จะดูเหมือน

with app.open_resource('/static/path/yourfile'):
      #code to read the file and do something
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.