Flask是一個基於Python開發並且依賴jinja2模板和Werkzeug WSGI服務的一個微型框架,對於Werkzeug本質是Socket服務端,其用於接收http請求並對請求進行預處理,然后觸發Flask框架,開發人員基於Flask框架提供的功能對請求進行相應的處理,並返回給用戶,如果要返回給用戶復雜的內容時,需要借助jinja2模板來實現對模板的處理,即:將模板和數據進行渲染,將渲染后的字符串返回給用戶瀏覽器。
“微”(micro) 並不表示你需要把整個 Web 應用塞進單個 Python 文件(雖然確實可以 ),也不意味着 Flask 在功能上有所欠缺。微框架中的“微”意味着 Flask 旨在保持核心簡單而易於擴展。Flask 不會替你做出太多決策——比如使用何種數據庫。而那些 Flask 所選擇的——比如使用何種模板引擎——則很容易替換。除此之外的一切都由可由你掌握。如此,Flask 可以與您珠聯璧合。
默認情況下,Flask 不包含數據庫抽象層、表單驗證,或是其它任何已有多種庫可以勝任的功能。然而,Flask 支持用擴展來給應用添加這些功能,如同是 Flask 本身實現的一樣。眾多的擴展提供了數據庫集成、表單驗證、上傳處理、各種各樣的開放認證技術等功能。Flask 也許是“微小”的,但它已准備好在需求繁雜的生產環境中投入使用。
初始Flask框架,將會從下面11個方向進行介紹。
1. 介紹Flask、Django、Tornado框架
2. Flask快速入門
3. 配置文件
4. 路由系統
5. 模板語言
6. 請求&響應相關
7. session & cookie
8. 閃現
9. 藍圖
10. 請求擴展(django 中間件)
11. 中間件
一. 介紹Flask、Django、Tornado框架(Python的三個常用框架)
Django:重武器,大而全。內部包含了非常多組件:ORM、Form、ModelForm、緩存、Session、中間件、信號等...大型項目用Django的比較多
Flask:短小精悍,可擴展。內部沒有太多組件,但是第三方組件非常豐富。
路由比較特殊:基於裝飾器來實現,但是究其本質還是通過add_url_rule來實現。
小項目一般用Flask的比較多,同時因為Flask有非常多的第三方組件,也可以很方便的實現Django具備的功能
Tornado:異步非阻塞框架(node.js)
二. Flask快速入門
1. 安裝Flask
為了不與現行項目想沖突,通常情況下會引入一個虛擬環境,將Flask安裝在虛擬環境下,虛擬環境用virtualenv(關於virtualenv后面再專門寫一篇帖子介紹它)
為了方便快速創建,所有過程都在Pycharm里完成
(1)使用Virtualenv創建虛擬環境
(2)將Flask安裝在該虛擬環境里
2.WSGI
對於真實開發中的python web程序來說,一般會分為兩部分:服務器程序和應用程序。
服務器程序負責對socket服務器進行封裝,並在請求到來時,對請求的各種數據進行整理。
應用程序則負責具體的邏輯處理。
為了方便應用程序的開發,就出現了眾多的Web框架,例如:Django、Flask、web.py 等。不同的框架有不同的開發方式,但是無論如何,開發出的應用程序都要和服務器程序配合,才能為用戶提供服務。這樣,服務器程序就需要為不同的框架提供不同的支持。這樣混亂的局面無論對於服務器還是框架,都是不好的。對服務器來說,需要支持各種不同框架,對框架來說,只有支持它的服務器才能被開發出的應用使用。這時候,標准化就變得尤為重要。可以設立一個標准,只要服務器程序支持這個標准,框架也支持這個標准,那么他們就可以配合使用。一旦標准確定,雙方各自實現。這樣,服務器可以支持更多支持標准的框架,框架也可以使用更多支持標准的服務器。
WSGI(Web Server Gateway Interface)是一種規范,它定義了使用python編寫的web app與web server之間接口格式,實現web app與web server間的解耦。
python標准庫提供的獨立WSGI服務器稱為wsgiref。

# werkzeug示例: from werkzeug.wrappers import Request, Response @Request.application def hello(request): return Response('Hello World!') if __name__ == '__main__': from werkzeug.serving import run_simple run_simple('localhost', 4000, hello) # wsgiref示例: from wsgiref.simple_server import make_server def runserver(environ, start_response): start_response('200 OK', [('Content-Type', 'text/html')]) return [bytes('<h1>Hello, web!</h1>', encoding='utf-8'), ] # 本質的本質: import socket def handle_request(client): buf = client.recv(1024) client.send("HTTP/1.1 200 OK\r\n\r\n") client.send("Hello, Seven") def main(): sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.bind(('localhost',8000)) sock.listen(5) while True: connection, address = sock.accept() handle_request(connection) connection.close() if __name__ == '__main__': main()
3.Flask
第一個簡單的demo
# 1. 導入Flask from flask import Flask # 2. 實例化Flask對象 app = Flask(__name__) app.debug = True # 每次修改完成后,這個語句就會自動重啟服務,省去開發者手動重啟 ''' 看到這種加()的裝飾器,就要知道它里面其實執行了2個步驟: 1. 先執行app.route('/'),把返回值賦給 v, 即v = app.route('/') 2. 在執行v(), 並把hello_world作為參數傳給v,即v(hello_world) ''' # 3. 作用:將"/"和函數index的對應關系添加到路由中 @app.route('/') def index(): return 'Hello World!' if __name__ == '__main__': # 4. 監聽用戶請求 # 如果有用戶請求過來,則執行app的__call__方法 # 1. run里不寫內容,默認用用的就是本機IP,端口是5000 ;http://127.0.0.1:5000/ app.run() # 2.run里寫了IP和端口,就使用傳的IP和端口運行服務 app.run( host = 'localhost', # localhost =127.0.0.1,代表本機; 如果此處是0.0.0.0,代表任意ip的請求都是運行的 port = 9999 )
以用戶登錄作為一個簡單的demo,並一步步填充知識點:
需求一:實現一個簡單登錄頁面,如果用戶名密碼校驗成功,跳轉到百度首頁;如果校驗失敗,返回到登錄頁面,並給出錯誤提示
用戶登錄頁面
from flask import Flask,render_template,request,redirect app = Flask(__name__)
# 將構造函數的 name 參數傳給 Flask 程序,Flask 用這個參數決定程序的根目錄,以便稍后能夠找到相對於程 序根目錄的資源文件位置
客戶端(例如 Web 瀏覽器)把請求發送給 Web 服務器,Web 服務器再把請求發送給 Flask程序實例。
程序實例需要知道對每個 URL 請求運行哪些代碼,所以保存了一個 URL 到 Python 函數的映射關系。
處理 URL 和函數之間關系的程序稱為路由
在 Flask 程序中定義路由的最簡便方式,是使用程序實例提供的 app.route 修飾器,把修飾的函數注冊為路由。 # 1. app.route()中:第一個參數指明了登錄頁面的路徑,methods參數指明了可以接受的請求方式,不寫的時候默認接受GET請求 @app.route("/login", methods=['GET', 'POST']) def login(): ''' 2. 登錄函數里需要返回一個登錄頁面,需要用到render方法,在flask里導入render_template模塊 ender_template("login.html")指明了模板路徑 模板路徑不能隨便指定,Python里規定了必須寫在templates目錄下,所以需要先創建該目錄 然后再templates目錄下創建login.html頁面''' ''' 3. 請求相關的數據過來后,全部放在request里,此時需要先導入request 同時,需要判斷過來的請求是什么方式,並執行相關的操作請求邏輯 ''' if request.method =="GET": return render_template("login.html") else: # url中的請求數據全部放在query_string # request.query_string # 請求體相關的數據都放在form中,而且一定要get獲取前端傳的用戶名和密碼后才能傳給Server,這里曾經犯了一個嚴重錯誤,沒有get,數據無法傳遞給厚度,導致if邏輯沒有處理 user = request.form.get("username") pwd = request.form.get("password") ''' 4.然后就需要去數據庫里查找比對是否存在這個用戶,此處先略去數據庫的操作 ''' if user == "alex" and pwd =="123456": ''' 5.如果正確就會跳轉到登錄成功后的某個頁面,需要用到redirect方法,沒有就去導入''' return redirect("www.baidu.com") '''6. 如果用戶名不對,還是跳回登錄頁面,同時提示用戶名或者密碼錯誤; 錯誤提示信息需要在頁面展示,那login.html就需要有個變量名接受這個錯誤信息 ''' return render_template("login.html",error="輸入的用戶名或密碼錯誤") return render_template("login.html", **{"error":"用戶名和密碼錯誤"}) # **context:可以接受字典方式的參數,但是書寫要注意: # 1. 可以直接以賦值的方式寫,如error="輸入的用戶名或密碼錯誤" # 2. 也可以以字典的方式寫,但是要注意,如果以字典方式,需要在{}前加兩個**。 如上 if __name__ =="__main__": app.run()
login.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>登錄頁面</title> </head> <body> <h1>用戶登錄</h1> <form method = "post"> # 知識點:表單數據,一定要加在<form ></form>體內 <input type="text" name="username"> <input type="text" name="password"> <input type="submit" value="登 錄"> # 煩的錯誤:登錄按鈕type="submit", 但是按鈕名沒有寫成value,導致按鈕上沒有顯示登錄兩個字 <br> {{error}} # 知識點:接受后端傳送的值,需要通過花括號{{變量名}}的方式接受 </form> </body> </html>
需求二: 做一個首頁,其內容展示出一個用戶列表
首頁-用戶列表頁
from flask import Flask,render_template,request,redirect app = Flask(__name__) app.debug = True # 1. 沒有數據庫,先用一個字典作為一個用戶列表 USERS = { 1:{"name":"老大", "age":19,"gender":"男", "resume":"言語措辭間都能體會到今日頭條的謹小慎微,生怕再出現任何問題……."}, 2:{"name":"老二", "age":18,"gender":"女", "resume":"當打開這款APP之后,就會發現這跟已經“死”去的內涵段子簡直是一模一樣。更厲害的地方是,它還支持用用戶遷移內涵段子上的內容信息。"}, 3:{"name":"老三", "age":17,"gender":"男", "resume":"如果狒狒會說人話,他肯定是在說:喂…你怎么只給我吃了一口就跑了呀,我還沒吃飽啊…喂喂喂…給我回來呀!哈哈哈"}, } # 2. 定義一個首頁-用戶列表頁 @app.route("/index", methods=['GET']) # 首頁一般都是先get數據的,所以methods用get方法 # 將USERS復制給變量user_dict,前端拿到該變量將會用來循環獲取數據 def index(): return render_template("index.html", user_dict=USERS) @app.route("/login", methods=['GET', 'POST']) def login(): if request.method =="GET": return render_template("login.html") else: user = request.form.get("username") pwd = request.form.get("password") if user == "alex" and pwd =="123456": return redirect("https://www.baidu.com") return render_template("login.html", **{"error":"用戶名和密碼錯誤"}) if __name__ =="__main__": app.run()
index.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>首頁</title> </head> <body> <h1>用戶頁面</h1> <!--用戶列表是一個表格, 用<table> </table> --> <table> <!-- 后端傳給前端的是一個用戶列表,前端需要接受,並提取出需要的數據展示,這就需要用到循環了 知識點: 1. 在html里循環,需要以{% for循環條件 %}開始,以{% endfor %} 結尾 2. 循環結果展示也要以{{ }} 接受變量 3. v也是一個字典,在Flask里,字典值的獲取有三種方式: 1) v.name 以.的方式直接獲取 2) v['name'] 以索引的方式獲取 3) v.get('name') 以get的方式獲取 --> {% for k,v in user_dict.items() %} <tr> <td>{{ k }}</td> <td>{{ v.name }}</td> <td>{{ v['name'] }}</td> <td>{{ v.get('name') }}</td> <td>{{ v.age }}</td> <td>{{ v.get('gender') }}</td> <!-- 詳細信息頁,下面要用到--> <!-- a標簽是超鏈接的標志 --> <!-- 需要給詳細信息detail頁傳一個id,后端根據該id來決定顯示什么數據給前端,ID也是花括號方式接受 --> <!-- 同時,后端需要一個地址來接受前端傳的值,定義個路由"/detail/id"地址來接受 --> <td><a href="detail/{{ key }}">查看詳細信息</a></td> </tr> {% endfor %} </table> </body> </html>
需求三: 首頁內容展示出一個用戶列表,點擊詳細信息可以查看用戶的具體詳細信息
詳細信息頁
from flask import Flask,render_template,request,redirect app = Flask(__name__) app.debug = True USERS = { 1:{"name":"老大", "age":19,"gender":"男", "resume":"言語措辭間都能體會到今日頭條的謹小慎微,生怕再出現任何問題……."}, 2:{"name":"老二", "age":18,"gender":"女", "resume":"當打開這款APP之后,就會發現這跟已經“死”去的內涵段子簡直是一模一樣。更厲害的地方是,它還支持用用戶遷移內涵段子上的內容信息。"}, 3:{"name":"老三", "age":17,"gender":"男", "resume":"如果狒狒會說人話,他肯定是在說:喂…你怎么只給我吃了一口就跑了呀,我還沒吃飽啊…喂喂喂…給我回來呀!哈哈哈"}, } # 定義用戶信息詳情頁,需要根據前端傳的id決定具體展示哪個用戶的詳細信息,而且該id還需要是動態的 # Flask里動態接受,有個規則,需要用<類型:nid>的方式 # int:表示前端傳的要是一個數值類型 # nid: 表示傳的值 # 同時,前端傳值了,就需要在detail函數里接收這個值 @app.route("/detail/<int:nid>", methods=['GET']) def detail(nid): # 接收前端傳的數值類型的值 detail_info = USERS.get(nid) # USERS用戶列表里根據nid獲取值 return render_template("detail.html",info = detail_info) # info = detial_info 接受上面USERS里獲取到的值,並給前端顯示 @app.route("/index", methods=['GET']) def index(): return render_template("index.html", user_dict=USERS) @app.route("/login", methods=['GET', 'POST']) def login(): if request.method =="GET": return render_template("login.html") else: user = request.form.get("username") pwd = request.form.get("password") if user == "alex" and pwd =="123456": return redirect("https://www.baidu.com") return render_template("login.html", **{"error":"用戶名和密碼錯誤"}) if __name__ =="__main__": app.run()
detail.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <h1>詳細信息 {{info.name}}</h1> <div> {{info.text}} </div> </body> </html>
需求四:實現登錄成功后,可以正常訪問上面的頁面,所以需要引入session。(Flask里有session,可直接引入),如果不用session就要自己寫cookie了。
from flask import Flask,render_template,request,redirect,session,url_for app = Flask(__name__) app.debug = True USERS = { 1:{"name":"老大", "age":19,"gender":"男", "resume":"言語措辭間都能體會到今日頭條的謹小慎微,生怕再出現任何問題……."}, 2:{"name":"老二", "age":18,"gender":"女", "resume":"當打開這款APP之后,就會發現這跟已經“死”去的內涵段子簡直是一模一樣。更厲害的地方是,它還支持用用戶遷移內涵段子上的內容信息。"}, 3:{"name":"老三", "age":17,"gender":"男", "resume":"如果狒狒會說人話,他肯定是在說:喂…你怎么只給我吃了一口就跑了呀,我還沒吃飽啊…喂喂喂…給我回來呀!哈哈哈"}, } @app.route("/detail/<int:nid>", methods=['GET']) def detail(nid): # 校驗session,獲取值 user = session.get('user_info') # 如果得到的session沒值,不是該登錄用戶,那就重定向到登錄頁面 if not user: ''' 知識點: 一. 利用別名進行反向生成 1. redirect時跳轉到某個url,如果這個url特別長,很容易寫出錯,怎么搞? 可以用別名的方式代替,根據別名可以反向生成 別名從哪里來?你要redirect到哪個頁面,就在那個路由上起別名,用endpoint 2. 要根據別名進行反向生成,還需導入一個 url_for的模塊,url_for的作用就是進行反向生成 3. end_for也是可以接收參數的,參數是**values,比如dtail后面會接受id就可以寫成 url = url_for('l1',nid = nid)#是這么寫吧,暫定? 二. 寫一個裝飾器,代替每個函數里session校驗,進行用戶認證 ''' url = url_for('l1') # 根據別名l1反向生成/login的地址,並復制給url return redirect(url) # 重定向跳轉到url就可以 detail_info = USERS.get(nid) # 如果有值,就跳到詳情頁 return render_template("detail.html",info = detail_info) # info = detial_info 接受上面USERS里獲取到的值,並給前端顯示 @app.route("/index", methods=['GET']) def index(): # 沒有通別名的方式進行反向生成的方式跳轉 user = session.get("user_info") if not user: return redirect("/login") return render_template("index.html", user_dict=USERS) # ➕假設登錄頁url很長,給起個別名 endpoint = l1 @app.route("/login", methods=['GET', 'POST'], endpoint='l1') def login(): if request.method =="GET": return render_template("login.html") else: user = request.form.get("username") pwd = request.form.get("password") # 用戶登錄成功之前,寫上session,讓其 = 用戶名,相當於給session里的user_info鍵賦值了 # 當用戶登錄成功后,session里其實就有值了,訪問其他頁面時,每一個都需要進行判斷session # 所以,一定會想到用裝飾器的方式解決 if user == "alex" and pwd =="123456": # session賦值的動作一定是咋用戶登錄信息校驗通過后才獲取已登錄的用戶名賦值給session下的'user_info的 # 犯的錯誤:把session['user_info'] = user 寫到了if判斷的外面,導致報錯 session['user_info'] = user return redirect("https://www.baidu.com") return render_template("login.html", **{"error":"用戶名和密碼錯誤"}) if __name__ =="__main__": app.run()
裝飾器:關於上面的知識點二,完成裝飾器,需要注意的三個點
1. 裝飾器一般是兩層函數嵌套
2. 一個函數可以用多個裝飾器,當用多個裝飾器的時候,它的執行順序是怎樣的?
3. 如果url的別名重復了,是不允許的。也就是對於反向查找的名稱不允許重復。flask里通過endpoint解決的
三.Flask的配置文件
先看個簡單的配置的示例
# 配置文件 from flask import Flask app = Flask(__name__) # 兩個簡單的配置 app.debug = True app.secret_key = "34ufad342333mfkf" @app.route("/") def index(): return "Hello World" if __name__ == "__main__": app.run()
問題:Falsk里的配置文件有很多的時候,都需要寫到Python代碼里嗎?答案是否定的
1. Flask都有哪些配置文件?
flask中的配置文件是一個flask.config.Config對象(繼承字典),默認配置為: { 'DEBUG': get_debug_flag(default=False), 是否開啟Debug模式 'TESTING': False, 是否開啟測試模式 'PROPAGATE_EXCEPTIONS': None, 'PRESERVE_CONTEXT_ON_EXCEPTION': None, 'SECRET_KEY': None, 'PERMANENT_SESSION_LIFETIME': timedelta(days=31), 'USE_X_SENDFILE': False, 'LOGGER_NAME': None, 'LOGGER_HANDLER_POLICY': 'always', 'SERVER_NAME': None, 'APPLICATION_ROOT': None, 'SESSION_COOKIE_NAME': 'session', 'SESSION_COOKIE_DOMAIN': None, 'SESSION_COOKIE_PATH': None, 'SESSION_COOKIE_HTTPONLY': True, 'SESSION_COOKIE_SECURE': False, 'SESSION_REFRESH_EACH_REQUEST': True, 'MAX_CONTENT_LENGTH': None, 'SEND_FILE_MAX_AGE_DEFAULT': timedelta(hours=12), 'TRAP_BAD_REQUEST_ERRORS': False, 'TRAP_HTTP_EXCEPTIONS': False, 'EXPLAIN_TEMPLATE_LOADING': False, 'PREFERRED_URL_SCHEME': 'http', 'JSON_AS_ASCII': True, 'JSON_SORT_KEYS': True, 'JSONIFY_PRETTYPRINT_REGULAR': True, 'JSONIFY_MIMETYPE': 'application/json', 'TEMPLATES_AUTO_RELOAD': None, }
2. Flask都有哪些方式設置配置文件?有三種方式
(1)方法1

# 配置文件 from flask import Flask app = Flask(__name__) # 兩個簡單的配置文件 app.debug = True app.secret_key = "34ufad342333mfkf" # 方法1 app.config['debug'] = True app.config['secret_key'] = "2343urioeurq" # PS: 由於Config對象本質上是字典,所以還可以使用app.config.update(...) @app.route("/") def index(): return "Hello World" if __name__ == "__main__": app.run()
(2) 方法2: 以py文件的方式導入
配置文件需要寫在同級目錄下
app.config.from_pyfile("python文件名稱")
app.config.from_pyfile("python文件名稱") 如: settings.py DEBUG = True app.config.from_pyfile("settings.py")
app.config.from_envvar("環境變量名稱") 環境變量的值為python文件名稱名稱,內部調用from_pyfile方法 app.config.from_json("json文件名稱") JSON文件名稱,必須是json格式,因為內部會執行json.loads app.config.from_mapping({'DEBUG':True}) 字典格式 PS: 從sys.path中已經存在路徑開始寫 PS: settings配置文件可能會存在於兩個路徑下面:
1. settings.py文件默認路徑要放在程序root_path目錄(即根目錄下),
2. 如果instance_relative_config = True 時,settings.py 文件就要放在instance_path目錄下
(3)方法3:以文件的方式進行配置,然后導入配置文件
1. 先在同級目錄下,新建 settings.py文件,配置項需要大寫
DEBUG = True # 配置項需要大寫
2.在需要使用到配置文件的地方導入
from flask import Flask app = Flask(__name__) # 以from_pyfile方式導入配置文件 app.config.from_pyfile("settings.py") @app.route("/") def index(): return "Hello World!" if __name__ =="__main__": app.run()
(4)方法4:最常用的方式,以from_objec方式 ----推薦使用的方式
# 以from_object方式導入配置文件
app.config.from_object("python類或者類的路徑")
# 表示FalskLearn目錄下面的settings文件下面有個DevelopmentConfig類
app.config.from_object("pro_flask.settings.DevelopmentConfig")
Python類寫在settings.py配置文件里
# 生產環境默認配置 class Config(): DEBUG = False TESTING = False DATABASE_URL = "sqlite://:memory:" # 測試或開發環境繼承Config類,重寫相關配置項 class ProductionConfig(Config): DATABASE_URL = "mysql://user@localhost/foo" class DevelopmentConfig(Config): DEBUG = True class TestingConfig(Config): TESTING = True
導入配置文件示例:
from flask import Flask app = Flask(__name__) # 以from_object方式導入配置文件 # app.config.from_object("python類或者類的路徑") # 因為 settings.py文件與當前文件同級,所以settings前沒再寫它的目錄 app.config.from_object("settings.DevelopmentConfig") @app.route("/") def index(): return "Hello World!" if __name__ =="__main__": app.run()
四.路由系統
- @app.route('/user/<username>') 表示字符串
- @app.route('/post/<int:post_id>') 表示整數
- @app.route('/post/<float:post_id>') 表示小數
- @app.route('/post/<path:path>') 表示可以傳路徑
- @app.route('/login', methods=['GET', 'POST'])
常用路由系統有以上五種,所有的路由系統都是基於一下對應關系來處理:
DEFAULT_CONVERTERS = { 'default': UnicodeConverter, 'string': UnicodeConverter, 'any': AnyConverter, 'path': PathConverter, 'int': IntegerConverter, 'float': FloatConverter, 'uuid': UUIDConverter, }
(1)路由系統的本質
from flask import Flask app = Flask(__name__) app.config.from_object("settings.DevelopmentConfig") ''' 看到路由系統,分析路由系統干了什么? 第一步:先執行:decorator = app.route("/", methods= ['GET','POST'], endpoint='n1') 根據源碼,第一步執行了下面的函數,返回decorator,這就是一個閉包,閉包給誰用,誰以后執行這個函數就給誰用 def route(self, rule, **options): # 具體值與參數的對應關系: # app對象 # rule = "/" # options = {methods= ['GET','POST'], endpoint='n1'} def decorator(f): endpoint = options.pop('endpoint', None) self.add_url_rule(rule, endpoint, f, **options) return f return decorator 第二步: 第一步返回了decorator,所以相當於是: @decorator,等價於decorator(index),觸發上面route函數下面的decorator函數的運行 def decorator(f): endpoint = options.pop('endpoint', None) self.add_url_rule(rule, endpoint, f, **options) # self就是app對象。加到了路由的對應表里去了,那里有url對應的函數 return f 最后總結一下,添加路由關系的本質,其實就是最終執行 self.add_url_rule(rule, endpoint, f, **options),生成路由的對應關系 ''' # 對於endpoint還要注意: # 如果endpoint沒有寫,根據源碼可知,默認的就是函數名,即該路由對應函數,如index(endpoint = index) @app.route("/", methods= ['GET','POST'], endpoint='n1') def index(): return "Hello World!" def login(): return "登錄" # 所以根據源碼的原理,也可以按照下面的方式添加路由對應關系,就與Django類似了。但是在Flask里,還是要按照裝飾器的方式去添加路由對應關系 app.add_url_rule('/login', 'n2', login, methods= ['GET','POST']) if __name__ == "__main__": app.run()
(2)路由系統值CBV
上面的路由是一種FBV的寫法,在Flask里,還支持CBV的寫法
from flask import Flask,views app = Flask(__name__) app.config.from_object("settings.DevelopmentConfig") def auth(func): def inner(*args, **kwargs): result = func(*args, **kwargs) return result return inner # 1. 繼承自views.MethodView 采用CBV寫法時,為了簡單,都是采用繼承MethodView的方式寫的 class IndexView(views.MethodView): methods = ['GET'] decorators = [auth,] def get(self): return "Index.GET" def post(self): return "Index.POST" # view_func = 類.as_view # name="index" 指的就是endpoint # name = endpoint app.add_url_rule('/index', view_func=IndexView.as_view(name="index")) if __name__ == "__main__": app.run()
或
from flask import Flask,views app = Flask(__name__) app.config.from_object("settings.DevelopmentConfig") def auth(func): def inner(*args, **kwargs): print('before') result = func(*args, **kwargs) print('after') return result return inner # 也可以再往上繼承自View class IndexView(views.View): methods = ['GET'] decorators = [auth, ] # 如果繼承自View,就需要dispatch_request def dispatch_request(self): print('Index') return 'Index!' app.add_url_rule('/index', view_func=IndexView.as_view(name='index')) # name=endpoint if __name__ == "__main__": app.run()
(3)@app.route和app.add_url_rule參數:
A. 最常用參數
rule, URL規則 view_func, 視圖函數名稱 defaults=None, 默認值,當URL中無參數,函數需要參數時,使用defaults={'k':'v'}為函數提供參數 endpoint=None, 名稱,用於反向生成URL,即: url_for('名稱') methods=None, 允許的請求方式,如:["GET","POST"]

from flask import Flask app = Flask(__name__) # 以from_object方式導入配置文件 # app.config.from_object("python類或者類的路徑") app.config.from_object("settings.DevelopmentConfig") @app.route("/",methods=['GET','POST'],) # 1. 函數index需要參數,但是url里無參數 def index(nid): return "Hello World!" if __name__ =="__main__": app.run()

from flask import Flask app = Flask(__name__) # 2. 函數index需要參數,但是url里無參數,即可以用defaults={"k":"v"}為函數提供參數 @app.route("/",methods=['GET','POST'],defaults={"nid":888}) # 1. 函數index需要參數,但是url里無參數 def index(nid): print(nid) # 會在工作它打印nid 888 return "Hello World!" if __name__ =="__main__": app.run()
B. strict_slashes = None,表示對URL最后的 / 符號是否有嚴格要求
from flask import Flask app = Flask(__name__) # strict_slashes=False表示訪問http://127.0.0.1:5000/index 或者 http://127.0.0.1:5000/index/ 都可以 @app.route("/index",methods=['GET','POST'],strict_slashes=False) def index(): return "Hello World!" # strict_slashes=True 表示訪問http://127.0.0.1:5000/login時,login后面有沒有/, # 必須與@app.route("/login")的"/login"一致才可以 @app.route("/login",methods=['GET','POST'],strict_slashes=True) def login(): return "Hello World!" if __name__ =="__main__": app.run()
C.redirect_to = None : 重定向到指定地址
from flask import Flask app = Flask(__name__) app.Debug = True # 很多情況下,公司網站改版,都會有一段時間新老網站都會共存,很多用戶都會點收藏的老的網站地址,需要能跳轉到新的網站 # 可以用redirect_to進行重定向到新網站 @app.route("/index",methods=['GET','POST'],endpoint="n1", redirect_to="/index2") def index(): return "老網站"
# 或者 def index(): return "老網站" @app.route("/index",methods=['GET','POST'],endpoint="n1", redirect_to="index2") # 新網站 @app.route("/index2",methods=['GET','POST'],endpoint='n2') def index2(): return "新網站" if __name__ =="__main__": app.run()
D.subdomain = None: 子域名訪問
通常情況下,公司都會有個官網,比如www.baidu.com, 但是也會存在一些子域名,比如:api.baidu.com; admin.baidu.com
需要在hosts里配置上對應關系
wins: C:\Windows\System32\drivers\etc\hosts
mac: /etc/hosts
from flask import Flask app = Flask(__name__) # 子域名方式,SERVER_NAME 是必須要配置的 # body.com是域名,但是目前這個程序運行ip是127.0.0.1 dns域名解析 # 所以要在hosts里把域名和ip進行配置對應關系 app.config['SERVER_NAME']='body.com:5000' @app.route('/',subdomain='admin') def static_index(): return 'static.body.com' # 子域名如果是寫死的,訪問地址如下: # admin.body.com:5000
# 示例及運行結果

# subdomain='<domain>',<>表示類似於一個字符串形式的正則表達式,傳什么子域名就顯示什么子域名 @app.route('/dyaciton',subdomain='<domain>') def domain_indext(domain): return domain+".body.com" # 子域名如果是以變量方式傳入,訪問地址如下: # buy.body.com:5000/dyaciton # admin.body.com:500/dyaction
# 示例及運行結果

if __name__=="__main__": app.run()
(4) Flask之擴展支持正則的路由
from flask import Flask, url_for from werkzeug.routing import BaseConverter app = Flask(import_name=__name__) # 1. 寫轉換器類,類名任意寫,必須繼承自BaseConverter class RegexConverter(BaseConverter): """ 構造方法 自定義URL匹配正則表達式 regex是傳入的參數 """ def __init__(self, map, regex): super(RegexConverter, self).__init__(map) # 傳入的參數只需要賦上值就好了,其他就不用管了 self.regex = regex # 類里需要實現兩個方法:to_python 和 to_url def to_python(self, value): """ to_python的作用:路由匹配時,在正則匹配成功后,在傳給視圖函數之前,執行它,對匹配的數據進行一次校驗 :param value: :return: """ return int(value) def to_url(self, value): """ to_url的作用:使用url_for反向生成URL時,傳遞的參數經過該方法處理,返回的值用於生成URL中的參數 :param value: :return: """ val = super(RegexConverter, self).to_url(value) return val # 2. 將轉換器類RegexConverter 添加到flask的轉換器列表,即要添加到 DEFAULT_CONVERTERS
app.url_map.converters['regex'] = RegexConverter
# regex代指的就是RegexConverter類,然后加(),
# 就表示會把括號里的正則表達式\d+當作參數傳遞給類RegexConverter的第二個參數regex
@app.route('/index/<regex("\d+"):nid>')
def index(nid): print(url_for('index', nid='888')) # url_for時,先執行to_url
return 'Index' if __name__ == '__main__': app.run()
五.Flask之模板語言
1. 模板的使用
Flask使用的時Jinja2模板,所以其語法和Django無差別
2.自定義模板方法
Flask中自定義模板方法的方式和Bottle相似,創建一個函數並通過參數的形式傳入render_template.
前面的示例也涉及到了模板,下面再看個簡單的示例,傳入函數到模板里
from flask import Flask,render_template app = Flask(__name__) app.debug = True def func1(arg): return "hello" + arg @app.route("/index") def index(): return render_template("s5index.html",f=func1) # 傳入函數 if __name__=="__main__": app.run()
S5index.html頁面
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <!-- 傳入函數 --> <h1>{{f('小馬達')}}</h1> </body> </html>
from flask import Flask,render_template app = Flask(__name__) app.debug = True def func1(arg): return "<input type='text' value=%s />"%(arg,) @app.route("/index") def index(): return render_template("s5index.html",f=func1) # 傳入函數 if __name__=="__main__": app.run()
s5index.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <!-- 傳入函數 --> {{f('小馬達')}} </body> </html>
運行:http://127.0.0.1:5000/index
運行結果:
能輸出這樣,就是很好的,因為它是做了防止xss攻擊了
要想讓他免除這種設置,Django里可以再前端模板里通過管道符safe |safe, 后台也可以marksafe,同理,Flask也可以
(1)Flask前面模板里通過 |safe 做
s5index.html模板前端
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <!-- |safe 防xss攻擊 --> {{f('小馬達')|safe}} </body> </html>
運行結果:
(2)Flask后端通過 Markup做
后端通過Markup做
from flask import Flask,render_template,Markup app = Flask(__name__) app.debug = True def func1(arg): return Markup("<input type='text' value=%s />"%(arg,)) @app.route("/index") def index(): return render_template("s5index.html",f=func1) # 傳入函數 if __name__=="__main__": app.run()
s5index.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <!-- 傳入函數 --> {{f('小馬達')}} </body> </html>
運行結果:
(3)宏定義, 在html里定義, 通過關鍵字 macro關鍵字定義
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <!-- 傳入函數 --> {{f('小馬達')}} <!-- 宏定義 作用:就是定義一塊html,相當於就是一個函數 xx是函數名 --> {% macro xx(name,type='text',value='') %} <input type="{{ type }}" name="{{ name }}1" value="{{value}}"> <input type="{{ type }}" name="{{ name }}2" value="{{value}}"> <input type="{{ type }}" name="{{ name }}3" value="{{value}}"> <input type="{{ type }}" name="{{ name }}4" value="{{value}}"> {% endmacro %} <!--執行,n是name --> {{ xx('n') }} </body> </html>
運行結果:
六. Flask之請求和響應
from flask import Flask,request,render_template,redirect,jsonify,make_response app = Flask(__name__) @app.route('/login.html', methods=['GET', "POST"]) def login(): return "內容" # 請求相關信息 # request.method # request.args # request.form # request.values # request.cookies # request.headers # request.path # request.full_path # request.script_root # request.url # request.base_url # request.url_root # request.host_url # request.host # request.files # obj = request.files['the_file_name'] # obj.save('/var/www/uploads/' + secure_filename(f.filename)) # 響應相關信息 # return "字符串" # return render_template('html模板路徑',**{}) # return redirect('/index.html') # return jsonify({'k':'v'}) # 如果想設置響應頭和回顯cookie,就需要用到make_response # response = make_response(render_template('index.html')) # response = make_response("字符串") # response是flask.wrappers.Response類型 # response.delete_cookie('key') # response.set_cookie('key', 'value') # response.headers['X-Something'] = 'A value' # return response if __name__ == '__main__': app.run() ''' 示例 def index(): response = make_response("字符串") response.set_cookie response.delete_cookie('key') response.headers['X-Something'] = 'A value' return response '''
七.Falsk之session&cookie
除請求對象之外,還有一個 session 對象。它允許你在不同請求間存儲特定用戶的信息。它是在 Cookies 的基礎上實現的,並且對 Cookies 進行密鑰簽名要使用會話,你需要設置一個密鑰。
-
設置:session['username'] = 'xxx'
- 刪除:session.pop('username', None)
from flask import Flask,session app=Flask(__name__) app.debug = True # session 在使用之前,必須要有一個secret_key,用來做簽名和加密 # 記住: # 最終到瀏覽器的時候會有一個隨機字符串,還會有k1:v1,k2:v2序列化之后,再用secret_key加密簽名之后, # 返回到瀏覽器上的 app.secret_key="dfjade890sddss" app.session_interface app.route("/") def index(): # session['k1'] = 'v1'寫法很像字典,因為它就是一個繼承了字典類的對象 # flask內置的使用加密cookie(簽名cookie)來保存數據 session['k1'] = 'v1' session['k2'] = 'v2' # 刪除session session.pop('k1') return 'xxx' if __name__=="__main__": app.run()

pip3 install Flask-Session run.py from flask import Flask from flask import session from pro_flask.utils.session import MySessionInterface app = Flask(__name__) app.secret_key = 'A0Zr98j/3yX R~XHH!jmN]LWX/,?RT' app.session_interface = MySessionInterface() @app.route('/login.html', methods=['GET', "POST"]) def login(): print(session) session['user1'] = 'alex' session['user2'] = 'alex' del session['user2'] return "內容" if __name__ == '__main__': app.run() session.py #!/usr/bin/env python # -*- coding:utf-8 -*- import uuid import json from flask.sessions import SessionInterface from flask.sessions import SessionMixin from itsdangerous import Signer, BadSignature, want_bytes class MySession(dict, SessionMixin): def __init__(self, initial=None, sid=None): self.sid = sid self.initial = initial super(MySession, self).__init__(initial or ()) def __setitem__(self, key, value): super(MySession, self).__setitem__(key, value) def __getitem__(self, item): return super(MySession, self).__getitem__(item) def __delitem__(self, key): super(MySession, self).__delitem__(key) class MySessionInterface(SessionInterface): session_class = MySession container = {} def __init__(self): import redis self.redis = redis.Redis() def _generate_sid(self): return str(uuid.uuid4()) def _get_signer(self, app): if not app.secret_key: return None return Signer(app.secret_key, salt='flask-session', key_derivation='hmac') def open_session(self, app, request): """ 程序剛啟動時執行,需要返回一個session對象 """ sid = request.cookies.get(app.session_cookie_name) if not sid: sid = self._generate_sid() return self.session_class(sid=sid) signer = self._get_signer(app) try: sid_as_bytes = signer.unsign(sid) sid = sid_as_bytes.decode() except BadSignature: sid = self._generate_sid() return self.session_class(sid=sid) # session保存在redis中 # val = self.redis.get(sid) # session保存在內存中 val = self.container.get(sid) if val is not None: try: data = json.loads(val) return self.session_class(data, sid=sid) except: return self.session_class(sid=sid) return self.session_class(sid=sid) def save_session(self, app, session, response): """ 程序結束前執行,可以保存session中所有的值 如: 保存到resit 寫入到用戶cookie """ domain = self.get_cookie_domain(app) path = self.get_cookie_path(app) httponly = self.get_cookie_httponly(app) secure = self.get_cookie_secure(app) expires = self.get_expiration_time(app, session) val = json.dumps(dict(session)) # session保存在redis中 # self.redis.setex(name=session.sid, value=val, time=app.permanent_session_lifetime) # session保存在內存中 self.container.setdefault(session.sid, val) session_id = self._get_signer(app).sign(want_bytes(session.sid)) response.set_cookie(app.session_cookie_name, session_id, expires=expires, httponly=httponly, domain=domain, path=path, secure=secure)
真正用的時候,更多常用的是第三方庫 Flask_session

#!/usr/bin/env python # -*- coding:utf-8 -*- """ pip3 install redis pip3 install flask-session """ from flask import Flask, session, redirect from flask.ext.session import Session app = Flask(__name__) app.debug = True app.secret_key = 'asdfasdfasd' app.config['SESSION_TYPE'] = 'redis' from redis import Redis app.config['SESSION_REDIS'] = Redis(host='192.168.0.94',port='6379') Session(app) @app.route('/login') def login(): session['username'] = 'alex' return redirect('/index') @app.route('/index') def index(): name = session['username'] return name if __name__ == '__main__': app.run() 第三方session