FLASK簡單入門

假定你已經安裝好了 Flask。若是沒有,請跳轉到 安裝 章節。css

一個最小的應用

一個最小的 Flask 應用看起來會是這樣:html

from flask import Flask app = Flask(__name__) @app.route('/') def hello_world(): return 'Hello World!' if __name__ == '__main__': app.run() 

把它保存爲 hello.py (或是相似的),而後用 Python 解釋器來運行。 確保你的應用文件名不是 flask.py ,由於這將與 Flask 自己衝突。python

$ python hello.py
 * Running on http://127.0.0.1:5000/

如今訪問 http://127.0.0.1:5000/ ,你會看見 Hello World 問候。git

那麼,這段代碼作了什麼?github

  1. 首先,咱們導入了 Flask 類。這個類的實例將會是咱們的 WSGI 應用程序。
  2. 接下來,咱們建立一個該類的實例,第一個參數是應用模塊或者包的名稱。 若是你使用單一的模塊(如本例),你應該使用 __name__ ,由於模塊的名稱將會因其做爲單獨應用啓動仍是做爲模塊導入而有不一樣( 也便是 '__main__' 或實際的導入名)。這是必須的,這樣 Flask 才知道到哪去找模板、靜態文件等等。詳情見 Flask的文檔。
  3. 而後,咱們使用 route() 裝飾器告訴 Flask 什麼樣的URL 能觸發咱們的函數。
  4. 這個函數的名字也在生成 URL 時被特定的函數採用,這個函數返回咱們想要顯示在用戶瀏覽器中的信息。
  5. 最後咱們用 run() 函數來讓應用運行在本地服務器上。 其中 if __name__ =='__main__': 確保服務器只會在該腳本被 Python 解釋器直接執行的時候纔會運行,而不是做爲模塊導入的時候。

欲關閉服務器,按 Ctrl+C。web

外部可訪問的服務器sql

若是你運行了這個服務器,你會發現它只能從你本身的計算機上訪問,網絡中其它任何的地方都不能訪問。在調試模式下,用戶能夠在你的計算機上執行任意 Python 代碼。所以,這個行爲是默認的。shell

若是你禁用了 debug 或信任你所在網絡的用戶,你能夠簡單修改調用 run() 的方法使你的服務器公開可用,以下:flask

app.run(host='0.0.0.0') 

這會讓操做系統監聽全部公網 IP。api

調試模式

雖然 run() 方法適用於啓動本地的開發服務器,可是你每次修改代碼後都要手動重啓它。這樣並不夠優雅,並且 Flask 能夠作到更好。若是你啓用了調試支持,服務器會在代碼修改後自動從新載入,並在發生錯誤時提供一個至關有用的調試器。

有兩種途徑來啓用調試模式。一種是直接在應用對象上設置:

app.debug = True app.run() 

另外一種是做爲 run 方法的一個參數傳入:

app.run(debug=True) 

兩種方法的效果徹底相同。

注意

儘管交互式調試器在容許 fork 的環境中沒法正常使用(也即在生產服務器上正常使用幾乎是不可能的),但它依然容許執行任意代碼。這使它成爲一個巨大的安全隱患,所以它 絕對不能用於生產環境 。

運行中的調試器截圖:

screenshot of debugger in action

想用其它的調試器? 參見 調試器操做 。

路由

現代 Web 應用的 URL 十分優雅,易於人們辨識記憶,這一點對於那些面向使用低速網絡鏈接移動設備訪問的應用特別有用。若是能夠不訪問索引頁,而是直接訪問想要的那個頁面,他們多半會笑逐顏開而再度光顧。

如上所見, route() 裝飾器把一個函數綁定到對應的 URL 上。

這裏是一些基本的例子:

@app.route('/') def index(): return 'Index Page' @app.route('/hello') def hello(): return 'Hello World' 

可是,不只如此!你能夠構造含有動態部分的 URL,也能夠在一個函數上附着多個規則。

變量規則

要給 URL 添加變量部分,你能夠把這些特殊的字段標記爲 <variable_name> , 這個部分將會做爲命名參數傳遞到你的函數。規則能夠用 <converter:variable_name> 指定一個可選的轉換器。這裏有一些不錯的例子:

@app.route('/user/<username>') def show_user_profile(username): # show the user profile for that user return 'User %s' % username @app.route('/post/<int:post_id>') def show_post(post_id): # show the post with the given id, the id is an integer return 'Post %d' % post_id 

轉換器有下面幾種:

int 接受整數
float 同 int ,可是接受浮點數
path 和默認的類似,但也接受斜線

惟一 URL / 重定向行爲

Flask 的 URL 規則基於 Werkzeug 的路由模塊。這個模塊背後的思想是基於 Apache 以及更早的 HTTP 服務器主張的先例,保證優雅且惟一的 URL。

以這兩個規則爲例:

@app.route('/projects/') def projects(): return 'The project page' @app.route('/about') def about(): return 'The about page' 

雖然它們看起來着實類似,但它們結尾斜線的使用在 URL 定義 中不一樣。 第一種狀況中,指向 projects 的規範 URL 尾端有一個斜線。這種感受很像在文件系統中的文件夾。訪問一個結尾不帶斜線的 URL 會被 Flask 重定向到帶斜線的規範 URL 去。

然而,第二種狀況的 URL 結尾不帶斜線,相似 UNIX-like 系統下的文件的路徑名。訪問結尾帶斜線的 URL 會產生一個 404 「Not Found」 錯誤。

這個行爲使得在遺忘尾斜線時,容許關聯的 URL 接任工做,與 Apache 和其它的服務器的行爲並沒有二異。此外,也保證了 URL 的惟一,有助於避免搜索引擎索引同一個頁面兩次。

構造 URL

若是 Flask 能匹配 URL,那麼 Flask 能夠生成它們嗎?固然能夠。你能夠用 url_for()來給指定的函數構造 URL。它接受函數名做爲第一個參數,也接受對應 URL 規則的變量部分的命名參數。未知變量部分會添加到 URL 末尾做爲查詢參數。這裏有一些例子:

>>> from flask import Flask, url_for >>> app = Flask(__name__) >>> @app.route('/') ... def index(): pass ... >>> @app.route('/login') ... def login(): pass ... >>> @app.route('/user/<username>') ... def profile(username): pass ... >>> with app.test_request_context(): ... print url_for('index') ... print url_for('login') ... print url_for('login', next='/') ... print url_for('profile', username='John Doe') ... / /login /login?next=/ /user/John%20Doe 

(這裏也用到了 test_request_context() 方法,下面會解釋。即便咱們正在經過 Python 的 shell 進行交互,它依然會告訴 Flask 要表現爲正在處理一個請求。請看下面的解釋。 環境局部變量 )

爲何你要構建 URL 而非在模板中硬編碼?這裏有三個絕妙的理由:

  1. 反向構建一般比硬編碼的描述性更好。更重要的是,它容許你一次性修改 URL, 而不是處處邊找邊改。
  2. URL 構建會轉義特殊字符和 Unicode 數據,免去你不少麻煩。
  3. 若是你的應用不位於 URL 的根路徑(好比,在 /myapplication 下,而不是 / ), url_for() 會妥善處理這個問題。

HTTP 方法

HTTP (與 Web 應用會話的協議)有許多不一樣的訪問 URL 方法。默認狀況下,路由只回應 GET 請求,可是經過 route() 裝飾器傳遞 methods 參數能夠改變這個行爲。這裏有一些例子:

@app.route('/login', methods=['GET', 'POST']) def login(): if request.method == 'POST': do_the_login() else: show_the_login_form() 

若是存在 GET ,那麼也會替你自動地添加 HEAD,無需干預。它會確保遵守 HTTP RFC(描述 HTTP 協議的文檔)處理 HEAD 請求,因此你能夠徹底忽略這部分的 HTTP 規範。一樣,自從 Flask 0.6 起, 也實現了 OPTIONS 的自動處理。

你不知道一個 HTTP 方法是什麼?沒必要擔憂,這裏會簡要介紹 HTTP 方法和它們爲何重要:

HTTP 方法(也常常被叫作「謂詞」)告知服務器,客戶端想對請求的頁面  些什麼。下面的都是很是常見的方法:

GET
瀏覽器告知服務器:只  獲取 頁面上的信息併發給我。這是最經常使用的方法。
HEAD
瀏覽器告訴服務器:欲獲取信息,可是隻關心  消息頭 。應用應像處理  GET 請求同樣來處理它,可是不分發實際內容。在 Flask 中你徹底無需 人工 干預,底層的 Werkzeug 庫已經替你打點好了。
POST
瀏覽器告訴服務器:想在 URL 上  發佈 新信息。而且,服務器必須確保 數據已存儲且僅存儲一次。這是 HTML 表單一般發送數據到服務器的方法。
PUT
相似  POST 可是服務器可能觸發了存儲過程屢次,屢次覆蓋掉舊值。你可 能會問這有什麼用,固然這是有緣由的。考慮到傳輸中鏈接可能會丟失,在 這種 狀況下瀏覽器和服務器之間的系統可能安全地第二次接收請求,而 不破壞其它東西。由於  POST它只觸發一次,因此用  POST 是不可能的。
DELETE
刪除給定位置的信息。
OPTIONS
給客戶端提供一個敏捷的途徑來弄清這個 URL 支持哪些 HTTP 方法。 從 Flask 0.6 開始,實現了自動處理。

有趣的是,在 HTML4 和 XHTML1 中,表單只能以 GET 和 POST 方法提交到服務器。可是 JavaScript 和將來的 HTML 標準容許你使用其它全部的方法。此外,HTTP 最近變得至關流行,瀏覽器再也不是惟一的 HTTP 客戶端。好比,許多版本控制系統就在使用 HTTP。

靜態文件

動態 web 應用也會須要靜態文件,一般是 CSS 和 JavaScript 文件。理想情況下, 你已經配置好 Web 服務器來提供靜態文件,可是在開發中,Flask 也能夠作到。 只要在你的包中或是模塊的所在目錄中建立一個名爲 static 的文件夾,在應用中使用 /static 便可訪問。

給靜態文件生成 URL ,使用特殊的 'static' 端點名:

url_for('static', filename='style.css') 

這個文件應該存儲在文件系統上的 static/style.css 。

模板渲染

用 Python 生成 HTML 十分無趣,並且至關繁瑣,由於你必須手動對 HTML 作轉義來保證應用的安全。爲此,Flask 配備了 Jinja2 模板引擎。

你可使用 render_template() 方法來渲染模板。你須要作的一切就是將模板名和你想做爲關鍵字的參數傳入模板的變量。這裏有一個展現如何渲染模板的簡例:

from flask import render_template @app.route('/hello/') @app.route('/hello/<name>') def hello(name=None): return render_template('hello.html', name=name) 

Flask 會在 templates 文件夾裏尋找模板。因此,若是你的應用是個模塊,這個文件夾應該與模塊同級;若是它是一個包,那麼這個文件夾做爲包的子目錄:

狀況 1: 模塊:

/application.py
/templates
    /hello.html

狀況 2: 包:

/application
    /__init__.py
    /templates
        /hello.html

關於模板,你能夠發揮 Jinja2 模板的所有實例。更多信息請見 Jinja2 模板文檔 。

這裏有一個模板實例:

<!doctype html> <title>Hello from Flask</title> {% if name %} <h1>Hello {{ name }}!</h1> {% else %} <h1>Hello World!</h1> {% endif %} 

在模板裏,你也能夠訪問 request 、 session 和 g [1] 對象, 以及 get_flashed_messages() 函數。

模板繼承讓模板用起來至關順手。如欲瞭解繼承的工做機理,請跳轉到 模板繼承 模式的文檔。最起碼,模板繼承能使特定元素 (好比頁眉、導航欄和頁腳)能夠出如今全部的頁面。

自動轉義功能默認是開啓的,因此若是 name 包含 HTML ,它將會被自動轉義。若是你能信任一個變量,而且你知道它是安全的(例如一個模塊把 Wiki 標記轉換爲 HTML),你能夠用 Markup 類或 |safe 過濾器在模板中把它標記爲安全的。在 Jinja 2 文檔中,你會看到更多的例子。

這裏是一個 Markup 類如何使用的簡單介紹:

>>> from flask import Markup >>> Markup('<strong>Hello %s!</strong>') % '<blink>hacker</blink>' Markup(u'<strong>Hello &lt;blink&gt;hacker&lt;/blink&gt;!</strong>') >>> Markup.escape('<blink>hacker</blink>') Markup(u'&lt;blink&gt;hacker&lt;/blink&gt;') >>> Markup('<em>Marked up</em> &raquo; HTML').striptags() u'Marked up \xbb HTML' 

在 0.5 版更改: 自動轉義再也不在全部模板中啓用。下列擴展名的模板會觸發自動轉義:.html 、 .htm 、.xml 、 .xhtml 。從字符串加載的模板會禁用自動轉義。

[1] 不肯定 g 對象是什麼?它容許你按需存儲信息, 查看( g )對象的文檔和 在 Flask 中使用 SQLite 3 的文檔以獲取更多信息。

訪問請求數據

對於 Web 應用,與客戶端發送給服務器的數據交互相當重要。在 Flask 中由全局的 request 對象來提供這些信息。若是你有必定的 Python 經驗,你會好奇,爲何這個對象是全局的,爲何 Flask 還能保證線程安全。答案是環境做用域:

環境局部變量

內幕

若是你想理解其工做機制及如何利用環境局部變量實現自動化測試,請閱讀此節,不然可跳過。

Flask 中的某些對象是全局對象,但卻不是一般的那種。這些對象其實是特定環境的局部對象的代理。雖然很拗口,但實際上很容易理解。

想象一下處理線程的環境。一個請求傳入,Web 服務器決定生成一個新線程( 或者別的什麼東西,只要這個底層的對象能夠勝任併發系統,而不只僅是線程)。 當 Flask 開始它內部的請求處理時,它認定當前線程是活動的環境,並綁定當前的應用和 WSGI 環境到那個環境上(線程)。它的實現很巧妙,能保證一個應用調用另外一個應用時不會出現問題。

因此,這對你來講意味着什麼?除非你要作相似單元測試的東西,不然你基本上能夠徹底無視它。你會發現依賴於一段請求對象的代碼,因沒有請求對象沒法正常運行。解決方案是,自行建立一個請求對象而且把它綁定到環境中。單元測試的最簡單的解決方案是:用 test_request_context() 環境管理器。結合 with 聲明,綁定一個測試請求,這樣你才能與之交互。下面是一個例子:

from flask import request with app.test_request_context('/hello', method='POST'): # now you can do something with the request until the # end of the with block, such as basic assertions: assert request.path == '/hello' assert request.method == 'POST' 

另外一種多是:傳遞整個 WSGI 環境給 request_context() 方法:

from flask import request with app.request_context(environ): assert request.method == 'POST' 

請求對象

API 章節對請求對象做了詳盡闡述(參見 request ),所以這裏不會贅述。此處寬泛介紹一些最經常使用的操做。首先從 flask 模塊裏導入它:

from flask import request 

當前請求的 HTTP 方法可經過 method 屬性來訪問。經過:attr:~flask.request.form 屬性來訪問表單數據( POST 或 PUT 請求提交的數據)。這裏有一個用到上面提到的那兩個屬性的完整實例:

@app.route('/login', methods=['POST', 'GET']) def login(): error = None if request.method == 'POST': if valid_login(request.form['username'], request.form['password']): return log_the_user_in(request.form['username']) else: error = 'Invalid username/password' # the code below is executed if the request method # was GET or the credentials were invalid return render_template('login.html', error=error) 

當訪問 form 屬性中的不存在的鍵會發生什麼?會拋出一個特殊的 KeyError 異常。你能夠像捕獲標準的 KeyError 同樣來捕獲它。 若是你不這麼作,它會顯示一個 HTTP 400 Bad Request 錯誤頁面。因此,多數狀況下你並不須要干預這個行爲。

你能夠經過 args 屬性來訪問 URL 中提交的參數 ( ?key=value ):

searchword = request.args.get('q', '') 

咱們推薦用 get 來訪問 URL 參數或捕獲 KeyError ,由於用戶可能會修改 URL,向他們展示一個 400 bad request 頁面會影響用戶體驗。

欲獲取請求對象的完整方法和屬性清單,請參閱 request 的文檔。

文件上傳

用 Flask 處理文件上傳很簡單。只要確保你沒忘記在 HTML 表單中設置enctype="multipart/form-data" 屬性,否則你的瀏覽器根本不會發送文件。

已上傳的文件存儲在內存或是文件系統中一個臨時的位置。你能夠經過請求對象的 files屬性訪問它們。每一個上傳的文件都會存儲在這個字典裏。它表現近乎爲一個標準的 Python file 對象,但它還有一個 save() 方法,這個方法容許你把文件保存到服務器的文件系統上。這裏是一個用它保存文件的例子:

from flask import request @app.route('/upload', methods=['GET', 'POST']) def upload_file(): if request.method == 'POST': f = request.files['the_file'] f.save('/var/www/uploads/uploaded_file.txt') ... 

若是你想知道上傳前文件在客戶端的文件名是什麼,你能夠訪問 filename 屬性。但請記住, 永遠不要信任這個值,這個值是能夠僞造的。若是你要把文件按客戶端提供的文件名存儲在服務器上,那麼請把它傳遞給 Werkzeug 提供的 secure_filename() 函數:

from flask import request from werkzeug import secure_filename @app.route('/upload', methods=['GET', 'POST']) def upload_file(): if request.method == 'POST': f = request.files['the_file'] f.save('/var/www/uploads/' + secure_filename(f.filename)) ... 

一些更好的例子,見 上傳文件 模式。

Cookies

你能夠經過 cookies 屬性來訪問 Cookies,用響應對象的 set_cookie 方法來設置 Cookies。請求對象的 cookies 屬性是一個內容爲客戶端提交的全部 Cookies 的字典。若是你想使用會話,請不要直接使用 Cookies,請參考 會話 一節。在 Flask 中,已經注意處理了一些 Cookies 安全細節。

讀取 cookies:

from flask import request @app.route('/') def index(): username = request.cookies.get('username') # use cookies.get(key) instead of cookies[key] to not get a # KeyError if the cookie is missing. 

存儲 cookies:

from flask import make_response @app.route('/') def index(): resp = make_response(render_template(...)) resp.set_cookie('username', 'the username') return resp 

可注意到的是,Cookies 是設置在響應對象上的。因爲一般視圖函數只是返回字符串,以後 Flask 將字符串轉換爲響應對象。若是你要顯式地轉換,你可使用 make_response()函數而後再進行修改。

有時候你想設置 Cookie,但響應對象不能醋在。這能夠利用 延遲請求回調 模式實現。

爲此,也能夠閱讀 關於響應 。

重定向和錯誤

你能夠用 redirect() 函數把用戶重定向到其它地方。放棄請求並返回錯誤代碼,用 abort() 函數。這裏是一個它們如何使用的例子:

from flask import abort, redirect, url_for @app.route('/') def index(): return redirect(url_for('login')) @app.route('/login') def login(): abort(401) this_is_never_executed() 

這是一個至關無心義的例子由於用戶會從主頁重定向到一個不能訪問的頁面 (401 意味着禁止訪問),可是它展現了重定向是如何工做的。

默認狀況下,錯誤代碼會顯示一個黑白的錯誤頁面。若是你要定製錯誤頁面, 可使用 errorhandler() 裝飾器:

from flask import render_template @app.errorhandler(404) def page_not_found(error): return render_template('page_not_found.html'), 404 

注意 render_template() 調用以後的 404 。這告訴 Flask,該頁的錯誤代碼是 404 ,即沒有找到。默認爲 200,也就是一切正常。

關於響應

視圖函數的返回值會被自動轉換爲一個響應對象。若是返回值是一個字符串, 它被轉換爲該字符串爲主體的、狀態碼爲 200 OK``的 、 MIME 類型是 ``text/html 的響應對象。Flask 把返回值轉換爲響應對象的邏輯是這樣:

  1. 若是返回的是一個合法的響應對象,它會從視圖直接返回。
  2. 若是返回的是一個字符串,響應對象會用字符串數據和默認參數建立。
  3. 若是返回的是一個元組,且元組中的元素能夠提供額外的信息。這樣的元組必須是 (response, status, headers) 的形式,且至少包含一個元素。 status 值會覆蓋狀態代碼, headers 能夠是一個列表或字典,做爲額外的消息標頭值。
  4. 若是上述條件均不知足, Flask 會假設返回值是一個合法的 WSGI 應用程序,並轉換爲一個請求對象。

若是你想在視圖裏操縱上述步驟結果的響應對象,可使用 make_response() 函數。

譬如你有這樣一個視圖:

@app.errorhandler(404) def not_found(error): return render_template('error.html'), 404 

你只須要把返回值表達式傳遞給 make_response() ,獲取結果對象並修改,而後再返回它:

@app.errorhandler(404) def not_found(error): resp = make_response(render_template('error.html'), 404) resp.headers['X-Something'] = 'A value' return resp 

會話

除請求對象以外,還有一個 session 對象。它容許你在不一樣請求間存儲特定用戶的信息。它是在 Cookies 的基礎上實現的,而且對 Cookies 進行密鑰簽名。這意味着用戶能夠查看你 Cookie 的內容,但卻不能修改它,除非用戶知道簽名的密鑰。

要使用會話,你須要設置一個密鑰。這裏介紹會話如何工做:

from flask import Flask, session, redirect, url_for, escape, request app = Flask(__name__) @app.route('/') def index(): if 'username' in session: return 'Logged in as %s' % escape(session['username']) return 'You are not logged in' @app.route('/login', methods=['GET', 'POST']) def login(): if request.method == 'POST': session['username'] = request.form['username'] return redirect(url_for('index')) return '''  <form action="" method="post">  <p><input type=text name=username>  <p><input type=submit value=Login>  </form>  ''' @app.route('/logout') def logout(): # remove the username from the session if it's there session.pop('username', None) return redirect(url_for('index')) # set the secret key. keep this really secret: app.secret_key = 'A0Zr98j/3yX R~XHH!jmN]LWX/,?RT' 

這裏提到的 escape() 能夠在你模板引擎外作轉義(如同本例)。

如何生成強壯的密鑰

隨機的問題在於很難判斷什麼是真隨機。一個密鑰應該足夠隨機。你的操做系統能夠基於一個密鑰隨機生成器來生成漂亮的隨機值,這個值能夠用來作密鑰:

>>> import os >>> os.urandom(24) '\xfd{H\xe5<\x95\xf9\xe3\x96.5\xd1\x01O<!\xd5\xa2\xa0\x9fR"\xa1\xa8' 

把這個值複製粘貼進你的代碼中,你就有了密鑰。

使用基於 cookie 的會話需注意: Flask 會將你放進會話對象的值序列化至 Cookies。若是你發現某些值在請求之間並無持久存在,然而確實已經啓用了 Cookies,但也沒有獲得明確的錯誤信息。這時,請檢查你的頁面響應中的 Cookies 的大小,並與 Web 瀏覽器所支持的大小對比。

消息閃現

反饋,是良好的應用和用戶界面的重要構成。若是用戶得不到足夠的反饋,他們極可能開始厭惡這個應用。 Flask 提供了消息閃現系統,能夠簡單地給用戶反饋。 消息閃現系統一般會在請求結束時記錄信息,並在下一個(且僅在下一個)請求中訪問記錄的信息。展示這些消息一般結合要模板佈局。

使用 flash() 方法能夠閃現一條消息。要操做消息自己,請使用get_flashed_messages() 函數,而且在模板中也可使用。完整的例子見 消息閃現 部分。

日誌記錄

0.3 新版功能.

有時候你會處於這樣一種境地,你處理的數據本應該是正確的,但實際上不是。 好比,你會有一些向服務器發送請求的客戶端代碼,但請求顯然是畸形的。這多是用戶篡改了數據,或是客戶端代碼的粗製濫造。大多數狀況下,正常地返回 400 Bad Request 就能夠了,可是有時候不能這麼作,而且要讓代碼繼續運行。

你可能依然想要記錄下,是什麼不對勁。這時日誌記錄就派上了用場。從 Flask 0.3 開始,Flask 就已經預置了日誌系統。

這裏有一些調用日誌記錄的例子:

app.logger.debug('A value for debugging') app.logger.warning('A warning occurred (%d apples)', 42) app.logger.error('An error occurred') 

附帶的 logger 是一個標準日誌類 Logger ,因此更多信息請查閱 logging 的文檔 。

整合 WSGI 中間件

若是你想給你的應用添加 WSGI 中間件,你能夠封裝內部 WSGI 應用。例如如果你想用 Werkzeug 包中的某個中間件來應付 lighttpd 中的 bugs ,能夠這樣作:

from werkzeug.contrib.fixers import LighttpdCGIRootFix app.wsgi_app = LighttpdCGIRootFix(app.wsgi_app) 

部署到 Web 服務器

準備好部署你的 Flask 應用了?你能夠當即部署到託管平臺來圓滿完成快速入門,如下廠商均向小項目提供免費的方案:

託管 Flask 應用的其它選擇:

若是你有本身的主機,而且準備本身託管,參見 部署選擇 章節。

相關文章
相關標籤/搜索