昨天中午去高科大取兩本 Flask 的預約書 :
# Flask web development / (2014) (Oreilly)
# Mastering Flask / (2015) (Packt)
其中 Oreilly 這本我覺得寫得很淺顯易懂, 適合給初學者當學習教材, 也是社群最推薦的自學書籍. 以下測試以這本書中的範例為主. 我借到的是第一版, 此書已在 2018 出第二版 :
Source : 博客來
作者 Miguel 是住在美國奧勒岡州的資深工程師, 原先在部落格寫 Flask 教學大受歡迎, 後來歐萊里找他合作將部落格內容出版為這本書. 有強國人將其部落格翻譯成簡體中文, 還有學習筆記 :
# Flask 教程,第一部分:Hello,World!
# Flask 学习笔记
參考 :
# [分享] Flask 網路開發經典書籍: Flask Web Development
除了這本書之外, 本篇測試也參考了如下書籍 :
# Flask : Building Python Web Services (Packt, 2017)
# Learning Flask Framework (Packt, 2015)
# Flask blueprints (Packt, 2015)
學過 Django 之後再來學 Flask 覺得簡單多了, 因為在 Flask 中 views.py 與 urls.py 合在一起, 不需要兩邊配合. 當然 Django 好處是一應俱全, 適合需要快速開發的中大型專案, 但技術上的選擇彈性較小; 而 Flask 則適合小型專案或需快速實作的網站雛形 (Prototyping), Flask 擁有較大自由度, 但技術配合要求較高, 用來架構中大型專案挑戰性也較高.
如果沒時間學 Django 又要很快看到雛形的話就用 Flask, 快又有效. 有人比喻說 Django 相當於 Ruby on Rails; 而 Flask 則相當於 Ruby 的輕量級網頁框架 Sinatra.
關於 Flask 簡介如下 :
- Flask 是以 Python 實作的輕量級網站框架 (micro framework), 它是一個極小化 (minimalistic)網站架構 , 只實作了核心的網頁應用程式功能 (包含 routing 路由功能), 而將較進階的功能 (例如認證與資料庫 ORM) 交給擴充套件 (extensions).
- 源自一個 Python 社群 Pocoo 成員 Armin Ronacher (原作者) 的一個愚人節玩笑, Flask 的風格受 Ruby 的 Sinatra 影響很大.
- Flask 基本上由負責 HTTP 路由的 Werkzeug 工具集與負責 HTML 網頁生成的 Jinja2 模板引擎組成, 另外還包含了 MarkupSafe 字串處理函式庫, 以及用來保存 Session 與 Cookie 資料的資料安全序列化函式庫 ItsDangerous.
- Flask 基本上是以 WSGI 介面與 Jinja2 模板為基礎, 內建一個實作 WSGI 伺服器與路由功能的 Werkzeug 工具集, 核心非常輕巧, 沒有預設的資料庫與表單驗證工具, 使用者的選擇性大, 內建的 Jinja2 網頁模板甚至可以替換成其他模板引擎例如 Mako. Flask 大部分功能靠延伸套件 (extension) 擴增.
- Flask 具有 HTTP 請求剖析與彈性的回應處理, 支援 session 管理與安全性 cookies 等功能.
- Flask 不是完整的 MTV 結構, 因為它只實作了 T (Template) 與 V (View) 的功能, 沒有實作 M (Model) 資料庫部分.
# https://www.python.org/dev/peps/pep-0333/
WSGI 協定是 Python 網頁應用程式與伺服器溝通的介面, 來自客戶端的請求會被伺服器以 WSGI 協定封裝後傳送給應用程式, 而應用程式的回應也會用 WSGI 回傳給伺服器, 運作架構如下 :
實作 WSGI 協定的框架事實上具有兩個介面, 一個是面向 Web 伺服器的介面 (Server side), 另外一個是面向應用程式的介面, 架構圖如下 :
Flask 內建的 Werkzeug 伺服器即實作了 WSGI 協定介面, 同時它本身也是一個實作 HTTP 的 Web 伺服器, 可直接與客戶端溝通, 但其效能只能做為開發使用. 實際運營使用例如 Nginx 或 Apache 等伺服器, 它們也都支援 WSGI 介面.
Flask 的相關網站如下 :
# https://palletsprojects.com/p/flask/ (官網)
# https://flask.palletsprojects.com/en/1.1.x/ (教學文件)
# https://github.com/pallets/flask (原始碼)
# https://pypi.org/project/Flask/ (Pypi 發布)
# https://flaskbook.com/ (範例檔)
# http://docs.jinkan.org/docs/flask/quickstart.html (Flask 快速入門)
一. 安裝 Flask 套件 :
在 Windows 下安裝 Flask :
pip3 install Flask
C:\Users\Tony>pip3 install Flask
Collecting Flask
Downloading https://files.pythonhosted.org/packages/9b/93/628509b8d5dc749656a9641f4caf13540e2cdec85276964ff8f43bbb1d3b/Flask-1.1.1-py2.py3-none-any.whl (94kB)
Requirement already satisfied: click>=5.1 in c:\python36\lib\site-packages (from Flask) (6.7)
Collecting Jinja2>=2.10.1 (from Flask)
Downloading https://files.pythonhosted.org/packages/1d/e7/fd8b501e7a6dfe492a433deb7b9d833d39ca74916fa8bc63dd1a4947a671/Jinja2-2.10.1-py2.py3-none-any.whl (124kB)
Collecting Werkzeug>=0.15 (from Flask)
Downloading https://files.pythonhosted.org/packages/d1/ab/d3bed6b92042622d24decc7aadc8877badf18aeca1571045840ad4956d3f/Werkzeug-0.15.5-py2.py3-none-any.whl (328kB)
Collecting itsdangerous>=0.24 (from Flask)
Downloading https://files.pythonhosted.org/packages/76/ae/44b03b253d6fade317f32c24d100b3b35c2239807046a4c953c7b89fa49e/itsdangerous-1.1.0-py2.py3-none-any.whl
Collecting MarkupSafe>=0.23 (from Jinja2>=2.10.1->Flask)
Downloading https://files.pythonhosted.org/packages/b9/82/833c7714951bff8f502ed054e6fbd8bd00e083d1fd96de6a46905cf23378/MarkupSafe-1.1.1-cp36-cp36m-win_amd64.whl
Installing collected packages: MarkupSafe, Jinja2, Werkzeug, itsdangerous, Flask
Found existing installation: Werkzeug 0.14.1
Uninstalling Werkzeug-0.14.1:
Successfully uninstalled Werkzeug-0.14.1
Successfully installed Flask-1.1.1 Jinja2-2.10.1 MarkupSafe-1.1.1 Werkzeug-0.15.5 itsdangerous-1.1.0
可見 Flask 中的兩個主角就是 Werkzeug 網頁伺服器與 jinja2 模板引擎. 安裝好後匯入 flask 套件, 檢視版本與套件內容如下 :
>>> import flask
>>> flask.__version__
'1.1.1'
>>> type(flask)
<class 'module'>
>>> dir(flask)
['Blueprint', 'Config', 'Flask', 'Markup', 'Request', 'Response', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__path__', '__spec__', '__version__', '_app_ctx_stack', '_compat', '_request_ctx_stack', 'abort', 'after_this_request', 'app', 'appcontext_popped', 'appcontext_pushed', 'appcontext_tearing_down', 'before_render_template', 'blueprints', 'cli', 'config', 'copy_current_request_context', 'ctx', 'current_app', 'escape', 'flash', 'g', 'get_flashed_messages', 'get_template_attribute', 'globals', 'got_request_exception', 'has_app_context', 'has_request_context', 'helpers', 'json', 'json_available', 'jsonify', 'logging', 'make_response', 'message_flashed', 'redirect', 'render_template', 'render_template_string', 'request', 'request_finished', 'request_started', 'request_tearing_down', 'safe_join', 'send_file', 'send_from_directory', 'session', 'sessions', 'signals', 'signals_available', 'stream_with_context', 'template_rendered', 'templating', 'url_for', 'wrappers']
其中 config 類別用來設定網站, request 類別用來處理客戶端的要求, 而 Flask 類別則是整個 Flask 套件的核心, 用 Flask 類別所建立的應用程式物件 (application instance) 就是網站運作的中央註冊站 (central registry), 舉凡 view 回應處理函數, URL 路由, 模板設定等等都是透過此物件實例.
建立一個 Flask 網站首先須從 flask 套件匯入 Flask 類別, 再呼叫 Flask() 建構子, 它會傳回一個 Flask 物件 (application instance), 用 dir() 可顯示其所提供的豐富方法, 這些方法即是讓 Flask 網站應用程式運作的工具集. Flask() 的傳入參數 __name__ 代表此應用程式名稱, 用來讓伺服器知道應用程式的位置, 並以此做為存取網站其他資源如靜態檔或模板位置之參考點.
>>> from flask import Flask
>>> app = Flask(__name__) #app 是任意取的, 慣例都用 app
>>> type(app)
<class 'flask.app.Flask'>
>>> dir(app)
['__call__', '__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', '_before_request_lock', '_blueprint_order', '_find_error_handler', '_get_exc_class_and_code', '_got_first_request', '_register_error_handler', '_static_folder', '_static_url_path', 'add_template_filter', 'add_template_global', 'add_template_test', 'add_url_rule', 'after_request', 'after_request_funcs', 'app_context', 'app_ctx_globals_class', 'auto_find_instance_path', 'before_first_request', 'before_first_request_funcs', 'before_request', 'before_request_funcs', 'blueprints', 'cli', 'config', 'config_class', 'context_processor', 'create_global_jinja_loader', 'create_jinja_environment', 'create_url_adapter', 'debug', 'default_config', 'dispatch_request', 'do_teardown_appcontext', 'do_teardown_request', 'endpoint', 'env', 'error_handler_spec', 'errorhandler', 'extensions', 'finalize_request', 'full_dispatch_request', 'get_send_file_max_age', 'got_first_request', 'handle_exception', 'handle_http_exception', 'handle_url_build_error', 'handle_user_exception', 'has_static_folder', 'import_name', 'inject_url_defaults', 'instance_path', 'iter_blueprints', 'jinja_env', 'jinja_environment', 'jinja_loader', 'jinja_options', 'json_decoder', 'json_encoder', 'log_exception', 'logger', 'make_config', 'make_default_options_response', 'make_null_session', 'make_response', 'make_shell_context', 'name', 'open_instance_resource', 'open_resource', 'open_session', 'permanent_session_lifetime', 'preprocess_request', 'preserve_context_on_exception', 'process_response', 'propagate_exceptions', 'raise_routing_exception', 'register_blueprint', 'register_error_handler', 'request_class', 'request_context', 'response_class', 'root_path', 'route', 'run', 'save_session', 'secret_key', 'select_jinja_autoescape', 'send_file_max_age_default', 'send_static_file', 'session_cookie_name', 'session_interface', 'shell_context_processor', 'shell_context_processors', 'should_ignore_error', 'static_folder', 'static_url_path', 'subdomain_matching', 'teardown_appcontext', 'teardown_appcontext_funcs', 'teardown_request', 'teardown_request_funcs', 'template_context_processors', 'template_filter', 'template_folder', 'template_global', 'template_test', 'templates_auto_reload', 'test_cli_runner', 'test_cli_runner_class', 'test_client', 'test_client_class', 'test_request_context', 'testing', 'trap_http_exception', 'try_trigger_before_first_request_functions', 'update_template_context', 'url_build_error_handlers', 'url_default_functions', 'url_defaults', 'url_map', 'url_map_class', 'url_rule_class', 'url_value_preprocessor', 'url_value_preprocessors', 'use_x_sendfile', 'view_functions', 'wsgi_app']
建立 Flask 物件後只要為來自客戶端 (例如瀏覽器) 的各種可能 URL 指配對應之回應處理函數 (稱為 view function) 以便做出適當的回應即可.
二. 用 Flask 建置 Hello 網站 :
開發 Flask 網頁應用程式的基本架構非常簡單 :
- 建立一個 Flask 物件 (習慣上通常取名為 app)
- 呼叫 app.route() 定義 URL, 並利用 Python 裝飾器 @ (decorator) 註冊 (register) 一個回應處理函數 (稱為 view 函數), 也就是將 URL 與 view 函數綁定在一起.
- 呼叫 app.run() 啟動內建的網頁伺服器服務
# Python Decorator 入門教學
不過要注意的是, 透過 app.run() 來啟動內建的伺服器只能用在開發階段 (因為它的效能不高, 只能應付少數流量), 實際運營時應改用正式的伺服器, 例如 uWSGI. 為了讓網頁伺服器只能透過 python xxx.py 啟動, 避免被其他 Python 模組 import 時啟動, 通常會將 app.run() 放在 if __name__ == __main__ 區塊中, 例如 :
測試 1 : 回應純文字 Hello World!
#hello.py
from flask import Flask
app = Flask(__name__)
@app.route("/")
def hello():
return "Hello World!"
if __name__ == "__main__":
app.run(debug=True)
此程式中的裝飾器 @app.route('/') 就是在為 URL '/' (根目錄) 註冊一個 view (回應處理) 函數, 也就是緊接著定義的 hello() 函數. 事實上比較傳統的做法是用 Flask 物件的 add_url_rule() 方法, 它有三個參數, 第一個是 URL, 第二個是端點名稱, 第三個是 view 函數 :
def hello():
return "Hello World!"
app.add_url_rule("/", "hello", hello)
裝飾器的好處就是簡潔.
另外值得一提的是, Flask 的 view 函數不需要像 Django 那樣傳入 request 參數, Flask 是用全域變數 request 來處理請求訊息.
將上面程式存成 hello.py 後用 python 執行, 這會啟動 Flask 的開發伺服器 :
D:\test>python hello.py
* Serving Flask app "hello" (lazy loading)
* Environment: production
WARNING: This is a development server. Do not use it in a production deployment.
Use a production WSGI server instead.
* Debug mode: on
* Restarting with stat
* Debugger is active!
* Debugger PIN: 194-439-956
* Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
命令提示字元視窗出現兩筆 GET 請求訊息, 第一筆為我們的瀏覽動作所觸發之請求, 第二筆是瀏覽器自行向伺服器發出的網頁小圖示 favicon.ico 的請求 :
127.0.0.1 - - [02/Aug/2019 16:43:46] "GET / HTTP/1.1" 200 -
127.0.0.1 - - [02/Aug/2019 16:43:46] "GET /favicon.ico HTTP/1.1" 404 -
上面測試 1 程式中, app.run() 傳入參數 debug=True 會開啟偵錯功能, 這有兩個效果, 一是當程式有錯誤會在網頁中輸出除錯訊息以便研判錯誤出在哪裡. 二是當程式有改變時會自動重新啟動伺服器. 當發生錯誤時, 訊息除了回應給客戶端, 同時會輸出到 Console 上.
除了可在 app.run() 中開啟偵錯功能外, 也可以用 debug 屬性或 config[], 例如 :
app.debug=True 或
app.config['DEBUG']=True
除了回應純文字外, 當然也可以回應 HTML 字串 (這才是常態), 例如 :
測試 2 : 回應 HTML 格式的 Hello World!
#hello.py
from flask import Flask
app = Flask(__name__)
@app.route("/")
def hello():
return "<h1>Hello World!</h1>"
if __name__ == "__main__":
app.run(debug=True)
此例 hello() 回應了一個 HTML 碼, 將 Hello World! 放在 h1 標籤裡面, 使其成為字體粗大的標題 :
當然 app 最好是回應一個完整的網頁架構 (即有 doctype 宣告, html, body 等標籤元素) 較好, 不過大部分的瀏覽器容錯性很高, 都能接受 HTML 結構不完整的回應.
Flask 內建的 Werkzeug 開發伺服器預設監聽 port 5000, 但可以在呼叫 app.run() 時傳入 port 參數來改變, 例如 :
測試 3 : 指定 host 與 port
from flask import Flask
app = Flask(__name__)
app.debug=True
@app.route("/")
def hello():
return "<h1><i>Hello World!<i></h1>"
if __name__ == "__main__":
app.run(host='127.0.0.1', port=8080)
此例將開啟偵錯改用 app.debug 屬性指定, 而 app.run() 則傳入 host='127.0.0.1' 與 port=8080 兩參數, 因此在用瀏覽器連線伺服器時要改為存取 8080 埠 (改為 127.0.0.1 表示只限本機可以存取).
Flask 內建的 Werkzeug 開發伺服器預設監聽網址 127.0.0.1 的 port 5000, 亦即只能讓本機的客戶端連線而已, 如果要讓本機以外的主機能連線此網站, 必須指定網址為 0.0.0.0, 這表示本機不論被指派的 IP 為何, 區網中的其他主機都可以拜訪, 這可以在呼叫 app.run() 時傳入 host 參數來改變, 網站正式運營時一定要傳入 port='0.0.0.0' 並取消 debug.
Flask 的 view (回應處理) 函數可以被多個 URL 要求註冊, 或者說, 多個 URL 要求可以綁定到同一個 view 函數上, 只要將這些 URL 的裝飾器串接在一起即可, 例如上面的 hello.py 修改為下面測試 4 :
測試 4 : 多個 URL 註冊同一個 view 函數
#hello.py
from flask import Flask
app = Flask(__name__)
app.debug=True
@app.route("/")
@app.route("/hello1")
@app.route("/hello2")
def hello():
return "<h1>Hello World!</h1>"
if __name__ == "__main__":
app.run(host='127.0.0.1', port=8080)
此程式為 "/", "/hello1", 以及 "/hello2" 三個 URL 註冊了同一個 view 函數 hello, 所以在瀏覽器上拜訪這三個 URL 時都會得到相同的回應網頁 :
但是, 如果拜訪未註冊 view 函數的任何 URL 都會被 Flask 回應一個預設的 404 Not found 網頁 :
三. 用 URL 傳遞變數 :
上面的 hello.py 程式都只有固定的 URL 字串, Flask 支援透過傳遞 URL 變數來達到動態網頁的目的. 傳遞 URL 變數有兩種方式 :
首先來看 RESTful 的範例, 將上面的 hello.py 修改為如下 :
測試 5 : 傳遞 URL 變數 : RESTful (單一變數)
#hello.py
from flask import Flask
app = Flask(__name__)
app.debug=True
@app.route("/")
def hello():
return "<h1>Hello World!</h1>"
@app.route("/hello/<name>")
def hello_name(name):
return "<h1>Hello, %s</h1>" % name
if __name__ == "__main__":
app.run(host='127.0.0.1', port=8080)
此程式註冊 view 函數 hello_name(name) 的 URL 攜帶一個變數 name, 預設為字串. 回應字串也可以用 format() 來格式化 :
return "<h1>Hello, {}</h1>".format(name)
以 URL=localhost:8080/Tony 拜訪此網頁結果如下 :
只要在 URL 後面附帶不同變數, 網頁中也會顯示那個變數.
如果有多個變數可以在 URL 中用 forward slash ("/") 隔開 :
/app/param1/param2/param3 ...
其中第一個是 app 名稱, 後面的都是參數.
在 view 函數中必須傳入同數目之參數 :
def app(param1, param2, param3, ...):
例如 :
測試 6 : 傳遞 URL 變數 : RESTful (多個變數)
#hello.py
from flask import Flask
app = Flask(__name__)
app.debug=True
@app.route("/")
def hello():
return "<h1>Hello World!</h1>"
@app.route("/hello/<name>/<surname>") #傳入兩個參數
def hello_name(name, surname):
return "<h1>Hello, {} {}</h1>".format(name, surname)
if __name__ == "__main__":
app.run(host='127.0.0.1', port=8080)
此程式為攜帶兩個參數的 URL 註冊了兩個參數的 view 函數 hello_name, 注意, 參數在 route() 的 URL 與 view 函數中的順序不需要一樣, 因為它是根據名稱對應的, 亦即若寫成 def(surname, name) 也是可以的. 在瀏覽器中輸入如下網址 :
參考 :
# Python Web Flask 實戰開發教學 - 簡介與環境建置
# FLASK HELLO WORLD APP WITH APACHE WSGI
# How can I get the named parameters from a URL using Flask?
# Flask Is Not Your Production Server
# How do you get a query string on Flask?
# Python Decorator 入門教學
# Flask Essentials – Handling 400 and 404 requests in Flask REST API
測試 4 : 多個 URL 註冊同一個 view 函數
#hello.py
from flask import Flask
app = Flask(__name__)
app.debug=True
@app.route("/")
@app.route("/hello1")
@app.route("/hello2")
def hello():
return "<h1>Hello World!</h1>"
if __name__ == "__main__":
app.run(host='127.0.0.1', port=8080)
此程式為 "/", "/hello1", 以及 "/hello2" 三個 URL 註冊了同一個 view 函數 hello, 所以在瀏覽器上拜訪這三個 URL 時都會得到相同的回應網頁 :
但是, 如果拜訪未註冊 view 函數的任何 URL 都會被 Flask 回應一個預設的 404 Not found 網頁 :
三. 用 URL 傳遞變數 :
上面的 hello.py 程式都只有固定的 URL 字串, Flask 支援透過傳遞 URL 變數來達到動態網頁的目的. 傳遞 URL 變數有兩種方式 :
- RESTful : /hello/Tony/Huang
- QueryString : /hello?firstname=Tony&secondname=Huang
首先來看 RESTful 的範例, 將上面的 hello.py 修改為如下 :
測試 5 : 傳遞 URL 變數 : RESTful (單一變數)
#hello.py
from flask import Flask
app = Flask(__name__)
app.debug=True
@app.route("/")
def hello():
return "<h1>Hello World!</h1>"
@app.route("/hello/<name>")
def hello_name(name):
return "<h1>Hello, %s</h1>" % name
if __name__ == "__main__":
app.run(host='127.0.0.1', port=8080)
此程式註冊 view 函數 hello_name(name) 的 URL 攜帶一個變數 name, 預設為字串. 回應字串也可以用 format() 來格式化 :
return "<h1>Hello, {}</h1>".format(name)
以 URL=localhost:8080/Tony 拜訪此網頁結果如下 :
只要在 URL 後面附帶不同變數, 網頁中也會顯示那個變數.
如果有多個變數可以在 URL 中用 forward slash ("/") 隔開 :
/app/param1/param2/param3 ...
其中第一個是 app 名稱, 後面的都是參數.
在 view 函數中必須傳入同數目之參數 :
def app(param1, param2, param3, ...):
例如 :
測試 6 : 傳遞 URL 變數 : RESTful (多個變數)
#hello.py
from flask import Flask
app = Flask(__name__)
app.debug=True
@app.route("/")
def hello():
return "<h1>Hello World!</h1>"
@app.route("/hello/<name>/<surname>") #傳入兩個參數
def hello_name(name, surname):
return "<h1>Hello, {} {}</h1>".format(name, surname)
if __name__ == "__main__":
app.run(host='127.0.0.1', port=8080)
此程式為攜帶兩個參數的 URL 註冊了兩個參數的 view 函數 hello_name, 注意, 參數在 route() 的 URL 與 view 函數中的順序不需要一樣, 因為它是根據名稱對應的, 亦即若寫成 def(surname, name) 也是可以的. 在瀏覽器中輸入如下網址 :
localhost:8080/hello/Tony/Huang
結果如下 :
除了使用 URL 本身來傳遞變數, 還可以用查詢字串 (query string) 來傳遞, 此功能在 Flask 裡由 request 這個全域物件處理, Flask 會將來自客戶端的 HTTP 請求封裝在 request 物件中, 其主要成員如下表 :
使用 request 前須先匯入 :
from flask import request
呼叫 request.args.get() 即可取得帶在 URL 後面查詢字串中的變數, 例如 :
測試 7 : 傳遞 URL 變數 : Query string (多個變數)
#hello.py
from flask import Flask, request
app = Flask(__name__)
app.debug=True
@app.route("/")
def hello():
return "<h1>Hello World!</h1>"
@app.route("/hello/")
def hello_name():
name=request.args.get("name")
surname=request.args.get("surname")
return "<h1>Hello, {} {}</h1>".format(name, surname)
if __name__ == "__main__":
app.run(host='127.0.0.1', port=8080)
注意, 應用程式名稱後面一定要有斜線 "/", 否則會找不到路由. 但在瀏覽器輸入網址時, 不論有無加 / 都可以, 瀏覽器最後都都會顯示有 / 的 :
localhost:8080/hello?name=Tony&surname=Huang 或
localhost:8080/hello/?name=Tony&surname=Huang
結果如下 :
以上不論是用 RESTfull 或 Query string 傳遞變數, Flask 取得的都是 str 字串型態之資料, 如果是內容是數值, 則需先經 int() 或 float() 等函數轉換才能進行計算.
Flask 在用 RESTful 方式傳遞變數時可以指定變數的資料類型, 這樣就可以不需要額外的型態轉換.
除了使用 URL 本身來傳遞變數, 還可以用查詢字串 (query string) 來傳遞, 此功能在 Flask 裡由 request 這個全域物件處理, Flask 會將來自客戶端的 HTTP 請求封裝在 request 物件中, 其主要成員如下表 :
flask.request 成員 | 說明 |
method | HTTP 請求方法 (str), 如 'GET','POST', 'PUT' 等 |
path | URL 路徑, 例如 /hello |
headers | HTTP 請求標頭, request.headers.get('attr') 傳回標頭屬性值 (str) |
args | URL 傳遞參數 (查詢字串), request.args.get('param') 傳回參數值 (str) |
form | 表單內容 (dict), 例如 request.form['pwd'] 取得表單中 name=pwd 之值 |
files | 取得上傳檔案之暫存位置, 例如 request.files['the_file'] |
cookies | HTTP cookies, request.cookies.get('username') 傳回參數值 (str) |
使用 request 前須先匯入 :
from flask import request
呼叫 request.args.get() 即可取得帶在 URL 後面查詢字串中的變數, 例如 :
測試 7 : 傳遞 URL 變數 : Query string (多個變數)
#hello.py
from flask import Flask, request
app = Flask(__name__)
app.debug=True
@app.route("/")
def hello():
return "<h1>Hello World!</h1>"
@app.route("/hello/")
def hello_name():
name=request.args.get("name")
surname=request.args.get("surname")
return "<h1>Hello, {} {}</h1>".format(name, surname)
if __name__ == "__main__":
app.run(host='127.0.0.1', port=8080)
注意, 應用程式名稱後面一定要有斜線 "/", 否則會找不到路由. 但在瀏覽器輸入網址時, 不論有無加 / 都可以, 瀏覽器最後都都會顯示有 / 的 :
localhost:8080/hello?name=Tony&surname=Huang 或
localhost:8080/hello/?name=Tony&surname=Huang
結果如下 :
以上不論是用 RESTfull 或 Query string 傳遞變數, Flask 取得的都是 str 字串型態之資料, 如果是內容是數值, 則需先經 int() 或 float() 等函數轉換才能進行計算.
Flask 在用 RESTful 方式傳遞變數時可以指定變數的資料類型, 這樣就可以不需要額外的型態轉換.
在網頁設計中最常用的 HTTP 方法是 GET 與 POST, 在用裝飾器 @app.route() 設定路由時可以傳入一個 methods 參數 (這是字串串列) 來指定 HTTP 方法, 預設是 GET (故以上測試均使用 GET), 例如 :
@app.route('/', methods=['POST']) # 只受理 POST 請求
@app.route('/', methods=['GET', 'POST']) # 同時受理 GET 與 POST 請求
例如 :
測試 8 : 指定 HTTP 方法 : 使用 route() 的 methods 參數
#hello.py
from flask import Flask, request
app = Flask(__name__)
app.debug=True
@app.route("/", methods=["GET", "POST"]) # GET 與 POST 皆可受理
def hello():
return f"<h1>Hello World!({request.method})</h1>"
if __name__ == "__main__":
app.run(host='127.0.0.1', port=8080)
此程式使用 flask.request.mothod 屬性取得客戶端的 HTTP 請求方法, 然後將其嵌入 f 字串中回應客戶端, 首先直接在瀏覽器網址列輸入 127.0.0.1:8080, 這樣是用 GET 方法, 結果如下 :
測試 POST 的方法可以用網頁表單設定 method="post" 來達成, 寫一個簡單網頁如下 :
<!DOCTYPE html>
<html>
<head>
<title></title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
</head>
<body>
<form method="post" action="http://127.0.0.1:8080">
<button type="submit">Hello (POST)</button>
</form>
</body>
</html>
表單 form 裡面只有一個按鈕, method 設為 post, 而 action 則設為上述程式執行後的網址與埠號, 將此網頁儲存後用瀏覽器開啟, 然後按其中的按鈕就會用 POST 方法提交表單 :
測試 POST 方法也可以利用第三方的 requests 套件 :
>>> import requests
>>> res=requests.post("http://127.0.0.1:8080")
>>> res.text
'<h1>Hello World!(POST)</h1>'
Flask 2.0 版之後, Flask 物件增加 get() 與 post() 方法可單獨處理 GET 與 POST 方法 :
@app.get('/') # 只受理 GET 請求
def hello():
pass
@app.post('/') # 只受理 POST 請求
def hello():
pass
如果要同時處理 GET 與 POST 方法, 可以連續用 @app.get() 與 @app.post() 來裝飾函式, 效果就跟呼叫 route() 時傳入 methods=['GET', 'POST']) 參數一樣 :
@app.get('/')
@app.post('/')
def hello():
pass
例如 :
測試 9 : 指定 HTTP 方法 : 使用 get() 與 post()
#hello.py
from flask import Flask, request
app = Flask(__name__)
app.debug=True
@app.get("/") # 處理 GET 請求
@app.post("/") # 處理 POST 請求
def hello():
return f"<h1>Hello World!({request.method})</h1>"
if __name__ == "__main__":
app.run()
結果與上面用 @app.route() 相同. 注意, 如果程式中只定義 GET 處理函式或 POST 函式, 則對於另一個未定義處理函式的請求將回應 405 (Method not allowed), 例如 :
測試 10 : 指定 HTTP 方法 : 不允許的請求方法 (405)
#hello.py
from flask import Flask, request
app = Flask(__name__)
app.debug=True
@app.get("/") # 處理 GET 請求
def hello():
return f"<h1>Hello World!({request.method})</h1>"
if __name__ == "__main__":
app.run()
此程式只定義了 GET 請求之處理函式, 如果對它提出 POST 請求將得到 405 回應 :
>>> import requests
>>> res=requests.post("http://127.0.0.1:3000")
>>> res
<Response [405]>
>>> res.text
'<!doctype html>\n<html lang=en>\n<title>405 Method Not Allowed</title>\n<h1>Method Not Allowed</h1>\n<p>The method is not allowed for the requested URL.</p>\n'
參考 :
# Python Web Flask 實戰開發教學 - 簡介與環境建置
# FLASK HELLO WORLD APP WITH APACHE WSGI
# How can I get the named parameters from a URL using Flask?
# Flask Is Not Your Production Server
# How do you get a query string on Flask?
# Python Decorator 入門教學
# Flask Essentials – Handling 400 and 404 requests in Flask REST API
2022-09-17 補充 :
前陣子上了高啟昌老師的 Flask 課程, 其 GitHub 的課程檔案如下 :
2024-02-23 補充 :
今天找到張宗彥的 iT 邦幫忙文章值得參考 :
沒有留言:
張貼留言