2019年8月2日 星期五

Python 學習筆記 : 用 Flask 架站 (一) 請求處理

上週在測試 MicroPython on ESP32 的 picoweb 架站框架時在文件中讀到, 原來 picoweb 其實就是嵌入式設備上的 Flask, 實際用過 picoweb 後讓我對 Flask 有了好感, 似乎比 Django 要簡單些.

昨天中午去高科大取兩本 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 簡介如下 :
  1. Flask 是以 Python 實作的輕量級網站框架 (micro framework), 它是一個極小化 (minimalistic)網站架構 , 只實作了核心的網頁應用程式功能 (包含 routing 路由功能), 而將較進階的功能 (例如認證與資料庫 ORM) 交給擴充套件 (extensions).
  2. 源自一個 Python 社群 Pocoo 成員 Armin Ronacher (原作者) 的一個愚人節玩笑, Flask 的風格受 Ruby 的 Sinatra 影響很大.
  3. Flask 基本上由負責 HTTP 路由的 Werkzeug 工具集與負責 HTML 網頁生成的 Jinja2 模板引擎組成, 另外還包含了 MarkupSafe 字串處理函式庫, 以及用來保存 Session 與 Cookie 資料的資料安全序列化函式庫 ItsDangerous.
  4. Flask 基本上是以 WSGI 介面與 Jinja2 模板為基礎, 內建一個實作 WSGI 伺服器與路由功能的 Werkzeug 工具集, 核心非常輕巧, 沒有預設的資料庫與表單驗證工具, 使用者的選擇性大, 內建的 Jinja2 網頁模板甚至可以替換成其他模板引擎例如 Mako. Flask 大部分功能靠延伸套件 (extension) 擴增. 
  5. Flask 具有 HTTP 請求剖析與彈性的回應處理, 支援 session 管理與安全性 cookies 等功能. 
  6. Flask 不是完整的 MTV 結構, 因為它只實作了 T (Template) 與 V (View) 的功能, 沒有實作 M (Model) 資料庫部分. 
WSGI 是 Python 標準的網頁伺服器介面協定, 定義於 PEP333, 參考 :

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 網頁應用程式的基本架構非常簡單 :
  1. 建立一個 Flask 物件 (習慣上通常取名為 app)
  2. 呼叫 app.route() 定義 URL, 並利用 Python 裝飾器 @ (decorator) 註冊 (register) 一個回應處理函數 (稱為 view 函數), 也就是將 URL 與 view 函數綁定在一起.
  3. 呼叫 app.run() 啟動內建的網頁伺服器服務
此架構利用了 Python 設計模式 (design pattern) 中的裝飾器, 所謂的裝飾器是一種特殊的函數 (function), 特別之處是其傳入參數為一個函數, 傳回值也是同一個函數, Python 使用 @ 符號作為裝飾器的語法糖 (syntax sugar), 關於裝飾器參考 :

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)


可見 Flask 內建的 Werkzeug 開發伺服器預設會監聽本機 localhost (127.0.0.1) 的 5000 埠, 亦即只限本機連線, 不接受外部存取. 開啟瀏覽器連線 127.0.0.1:5000 即顯示 Hello World! 網頁 :




命令提示字元視窗出現兩筆 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 變數有兩種方式 :
  1. RESTful : /hello/Tony/Huang   
  2. QueryString : /hello?firstname=Tony&secondname=Huang    
在 RESTfull 方式中, 變數是 URL 的一部分, 以階層式目錄串接在 app 名稱後面, 因此在 route() 中要定義各參數之順序. 而 QueryString 方式則是在 URL 後面以 "?" 起始, 各變數以 "&" 串接的 key=value 字串.

首先來看 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 物件中, 其主要成員如下表 :


 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'


2022-09-17 補充 :

前陣子上了高啟昌老師的 Flask 課程, 其 GitHub 的課程檔案如下 :



2024-02-23 補充 : 

今天找到張宗彥的 iT 邦幫忙文章值得參考 :


沒有留言:

張貼留言