day93:flask:Cookie&Session&請求鉤子&捕獲錯誤&上下文&Flask-Script
- 2020 年 11 月 19 日
- 筆記
- PythonS31-筆記, Python全棧31期-筆記
目錄
1.HTTP的會話控制
1.什麼是會話控制?
會話的開始是在用戶通過瀏覽器第一次訪問服務端網站開始.
會話的結束時在用戶通過關閉瀏覽器以後,與服務端斷開.
所謂的會話控制,就是在客戶端瀏覽器和服務端網站之間,進行多次http請求響應之間,記錄、跟蹤和識別用戶的資訊而已。
2.會話控制出現的原因
無狀態:指一次用戶請求時,瀏覽器、伺服器無法知道之前這個用戶做過什麼,每次請求都是一次新的請求。
無狀態原因:瀏覽器與伺服器是使用 socket 套接字進行通訊的,伺服器將請求結果返回給瀏覽器之後,會關閉當前的 socket 連接,而且伺服器也會在處理頁面完畢之後銷毀頁面對象。
3.會話控制的應用場景
實現狀態保持主要有兩種類型:
-
在客戶端存儲資訊使用
url
,Cookie
,token令牌[jwt.csrf,oauth]
-
在伺服器端存儲資訊使用
2.使用場景: 登錄狀態, 瀏覽歷史, 網站足跡,購物車 [不登錄也可以使用購物車]
3.Cookie是存儲在瀏覽器中的一段純文本資訊,建議不要存儲敏感資訊如密碼,因為電腦上的瀏覽器可能被其它人使用
4.Cookie基於域名安全,不同域名的Cookie是不能互相訪問的
如訪問luffy.com時向瀏覽器中寫了Cookie資訊,使用同一瀏覽器訪問baidu.com時,無法訪問到luffy.com寫的Cookie資訊
5.瀏覽器的同源策略針對cookie也有限制作用.
6.當瀏覽器請求某網站時,會將本網站下所有Cookie資訊提交給伺服器,所以在request中可以讀取Cookie資訊
1.設置cookie
設置cookie需要通過flask的Response響應對象來進行設置,由響應對象會提供了方法set_cookie給我們可以快速設置cookie資訊。
@app.route("/set_cookie") def set_cookie(): """設置cookie""" response = make_response("ok") # 格式:response.set_cookie(key="變數名",value="變數值",max_age="有效時間/秒") response.set_cookie("username","xiaoming",100) """如果cookie沒有設置過期時間,則默認過期為會話結束過期""" """cookie在客戶端中保存時,用一個站點下同變數名的cookie會覆蓋""" response.set_cookie("age","100") return response
2.獲取cookie
@app.route("/get_cookie") def get_cookie(): """獲取cookie""" print(request.cookies) print(request.cookies.get("username")) print(request.cookies.get("age")) """列印效果: {'username': 'xiaoming'} """ return ""
3.刪除cookie
@app.route("/del_cookie") def del_cookie(): """刪除cookie""" response = make_response("ok") # 把對應名稱的cookie設置為過期時間(0s),則可以實現刪除cookie response.set_cookie("username","",0) return response
3.Session
2.在伺服器端進行狀態保持的方案就是Session
3.Session依賴於Cookie,session的ID一般默認通過cookie來保存到客戶端。
4.flask中的session需要加密,所以使用session之前必須配置SECRET_KEY選項,否則報錯.
5.session的有效期默認是會話期,會話結束了,session就廢棄了。
6.如果將來希望session的生命周期延長,可以通過修改cookie中的sessionID來完成配置。
1.設置session
from flask import Flask, make_response, request,session app = Flask(__name__) class Config(): # flask中的session需要加密,所以使用session之前必須配置SECRET_KEY選項,否則報錯. SECRET_KEY = "123456asdadad" DEBUG = True app.config.from_object(Config) @app.route("/set_session") def set_session(): """設置session""" """與cookie不同,session支援python基本數據類型作為值""" session["username"] = "xiaohuihui" session["info"] = { "age":11, "sex":True, } return "ok" if __name__ == '__main__': app.run(debug=True)
2.獲取session
@app.route("/get_session") def get_session(): """獲取session""" print( session.get("username") ) print( session.get("info") ) return "ok"
3.刪除session
@app.route("/del_session") def del_session(): """刪除session""" try: del session["username"] # session.clear() # 刪除所有 except: pass return "ok"
Tip:如何查看當前flask默認支援的所有配置項
from flask import Flask, make_response, request,session app = Flask(__name__) class Config(): SECRET_KEY = "123456asdadad" DEBUG = True app.config.from_object(Config) # 查看當前flask默認支援的所有配置項 print(app.config) # 下面是flask默認支援的所有配置項 """ <Config { 'DEBUG': False, 'TESTING': False, 'PROPAGATE_EXCEPTIONS': None, 'PRESERVE_CONTEXT_ON_EXCEPTION': None, 'SECRET_KEY': None, 'PERMANENT_SESSION_LIFETIME': datetime.timedelta(31), 'USE_X_SENDFILE': False, 'LOGGER_NAME': '__main__', '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': datetime.timedelta(0, 43200), '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 """
4.請求鉤子
-
在請求開始時,建立資料庫連接;
-
在請求開始時,根據需求進行許可權校驗;
-
在請求結束時,指定數據的交互格式;
為了讓每個視圖函數避免編寫重複功能的程式碼,Flask提供了通用設置的功能,即請求鉤子。
請求鉤子是通過裝飾器的形式實現,Flask支援如下四種請求鉤子:
-
before_first_request
-
在處理第一個請求前執行[項目初始化時的鉤子]
-
-
before_request
-
在每次請求前執行
-
如果在某修飾的函數中返回了一個響應,視圖函數將不再被調用
-
-
after_request
-
如果沒有拋出錯誤,在每次請求後執行
-
接受一個參數:視圖函數作出的響應
-
在此函數中可以對響應值在返回之前做最後一步修改處理
-
需要將參數中的響應在此參數中進行返回
-
-
teardown_request:
-
在每次請求後執行
-
接受一個參數:錯誤資訊,如果有相關錯誤拋出
-
需要設置flask的配置DEBUG=False,teardown_request才會接受到異常對象。
from flask import Flask,make_response app = Flask(__name__) @app.before_first_request def first_request(): print("1. 項目啟動以後,首次被請求時,會自動執行[項目全局初始化工作]") @app.before_request def before_request(): print("2. 每次客戶端請求時,都會自動執行, 常用於記錄訪問日誌,進行許可權判斷,身份識別,訪問限流...") @app.after_request def after_request(response): print("4. 每次視圖執行以後,會自動執行") # after_request執行以後,必須要返回結果給客戶端!! return response @app.teardown_request def teardown_request(exc): print("5. after_request完成以後,如果有發生異常,在關閉DEBUG模式的情況下可以接受異常對象,進行異常的記錄,異常通知") print(exc) @app.route("/") def set_session(): print("3. 視圖執行了.......") return "ok" if __name__ == '__main__': app.run(debug=False)
第1次請求時列印:
1. 項目啟動以後,首次被請求時,會自動執行[項目全局初始化工作]
2. 每次客戶端請求時,都會自動執行, 常用於記錄訪問日誌,進行許可權判斷,身份識別,訪問限流…
3. 視圖執行了…….
4. 每次視圖執行以後,會自動執行
5. after_request完成以後,如果有發生異常,在關閉DEBUG模式的情況下可以接受異常對象,進行異常的記錄,異常通知
None
第2次請求時列印:
2. 每次客戶端請求時,都會自動執行, 常用於記錄訪問日誌,進行許可權判斷,身份識別,訪問限流…
3. 視圖執行了…….
4. 每次視圖執行以後,會自動執行
5. after_request完成以後,如果有發生異常,在關閉DEBUG模式的情況下可以接受異常對象,進行異常的記錄,異常通知
None
5.捕獲錯誤
flask中內置了app.errorhander提供給我們捕獲異常,實現一些在業務發生錯誤時的自定義處理。
1. 通過http狀態碼捕獲異常資訊
2. 通過異常類進行異常捕獲
-
-
註冊一個錯誤處理程式,當程式拋出指定錯誤狀態碼的時候,就會調用該裝飾器所裝飾的方法
-
-
參數:
-
code/exception – HTTP的錯誤狀態碼或指定異常類
1.比如統一處理狀態碼為500的錯誤給用戶友好的提示:
@app.errorhandler(500) def internal_server_error(e): return '伺服器搬家了'
2.捕獲指定系統異常類
@app.errorhandler(ZeroDivisionError) def zero_division_error(e): return '除數不能為0'
3.也可以捕獲自定義異常類
from flask import Flask app = Flask(__name__) """載入配置""" class Config(): DEBUG = True app.config.from_object(Config) """捕獲系統異常或者自定義異常""" class APIError(Exception): pass @app.route("/") def index(): raise APIError("api介面調用參數有誤!") return "個人中心,視圖執行了!!" @app.errorhandler(APIError) def error_apierror(e): return "錯誤: %s" % e if __name__ == '__main__': app.run(host="localhost",port=8080)
6.上下文:context
Flask中上下文對象:相當於一個容器,保存了 Flask 程式運行過程中的一些資訊[變數、函數、類與對象等資訊]。
Flask中有兩種上下文,請求上下文(request context)和應用上下文(application context)。
-
-
request 指的是每次
http
請求發生時,WSGI server
(比如gunicorn)調用Flask.__call__()
之後,在Flask
對象內部創建的Request
對象; -
application 表示用於響應WSGI請求的應用本身,request 表示每次http請求;
-
application的生命周期大於request,一個application存活期間,可能發生多次http請求,所以,也就會有多個
1.請求上下文(request context)
在 flask 中,可以直接在視圖函數中使用 request 這個對象進行獲取相關數據,而 request 就是請求上下文的對象,保存了當前本次請求的相關數據,請求上下文對象有:request、session
-
request
-
封裝了HTTP請求的內容,針對的是http請求。舉例:user = request.args.get(‘user’),獲取的是get請求的參數。
-
-
session
-
用來記錄請求會話中的資訊,針對的是用戶資訊。舉例:session[‘name’] = user.id,可以記錄用戶資訊。還可以通過session.get(‘name’)獲取用戶資訊。
注意!!!!:請求上下文提供的變數/屬性/方法/函數/類與對象,只能在視圖中或者被視圖調用的地方使用
2.應用上下文(application context)
應用上下文對象有:current_app,g
1.current_app
-
應用的啟動腳本是哪個文件,啟動時指定了哪些參數
-
載入了哪些配置文件,導入了哪些配置
-
連接了哪個資料庫
-
有哪些可以調用的工具類、常量
-
當前flask應用在哪個機器上,哪個IP上運行,記憶體多大
from flask import Flask,request,session,current_app,g # 初始化 app = Flask(import_name=__name__) # 聲明和載入配置 class Config(): DEBUG = True app.config.from_object(Config) # 編寫路由視圖 @app.route(rule='/') def index(): # 注意!!!!:應用上下文提供給我們使用的變數,也是只能在視圖或者被視圖調用的地方進行使用, # 但是應用上下文的所有數據來源於於app,每個視圖中的應用上下文基本一樣 print(current_app.config) # 獲取當前項目的所有配置資訊 print(current_app.url_map) # 獲取當前項目的所有路由資訊 return "<h1>hello world!</h1>" if __name__ == '__main__': # 運行flask app.run(host="0.0.0.0")
2.g變數
g 作為 flask 程式全局的一個臨時變數,充當者中間媒介的作用,我們可以通過它傳遞一些數據,g 保存的是當前請求的全局變數,不同的請求會有不同的全局變數,通過不同的thread id區別
from flask import Flask,request,session,current_app,g # 初始化 app = Flask(import_name=__name__) # 聲明和載入配置 class Config(): DEBUG = True app.config.from_object(Config) @app.before_request def before_request(): g.name = "root" def get_two_func(): name = g.name print("g.name=%s" % name) def get_one_func(): get_two_func() # 編寫路由視圖 @app.route(rule='/') def index(): # 請求上下文提供的變數/屬性/方法/函數/類與對象,只能在視圖中或者被視圖調用的地方使用 # 請求上下文裡面資訊來源於每次客戶端的請求,所以每個視圖中請求上下文的資訊都不一樣 # print(session) # 應用上下文提供給我們使用的變數,也是只能在視圖或者被視圖調用的地方進行使用, # 但是應用上下文的所有數據來源於於app,每個視圖中的應用上下文基本一樣 print(current_app.config) # 獲取當前項目的所有配置資訊 print(current_app.url_map) # 獲取當前項目的所有路由資訊 get_one_func() return "<h1>hello world!</h1>" if __name__ == '__main__': # 運行flask app.run(host="0.0.0.0")
3.關於請求上下文和應用上下文的總結
由flask提供了2種不同的上下文對象給我們開發者獲取項目或者客戶端的資訊
這些對象不需要我們進行實例化,由flask內部創建的
1. 請求上下文: request, session
2. 應用上下文: current_app, g
不管是請求上下文或者應用上下文都只能使用在視圖範圍內或者能被視圖調用的地方
如果是視圖以外地方使用,則會報錯:
RuntimeError: Working outside of application context.
解決方案:
with app.app_context():
print(g)
''' 請求上下文提供的變數/屬性/方法/函數/類與對象,只能在視圖中或者被視圖調用的地方使用 請求上下文裡面資訊來源於每次客戶端的請求,所以每個視圖中請求上下文的資訊都不一樣 ''' ''' 應用上下文提供給我們使用的變數,也是只能在視圖或者被視圖調用的地方進行使用, 但是應用上下文的所有數據來源於於app,每個視圖中的應用上下文基本一樣 '''
7.Flask-Script
這個模組的作用可以讓我們通過終端來控制flask項目的運行,類似於django的manage.py
安裝命令:
pip install flask-script
1.啟動終端腳本運行項目
from flask import Flask from flask_script import Manager app = Flask(__name__) class Config(): DEBUG = True app.config.from_object(Config) # 註冊終端腳本工具到app中 manager = Manager(app) @app.route("/") def index(): return "ok" if __name__ == '__main__': # 注意,這裡不是app對象 manager.run() # 埠和域名不寫,默認為127.0.0.1:5000 # python run.py runserver # 通過-h設置啟動域名,-p設置啟動埠 # python run.py runserver -h127.0.0.1 -p8888
2.自定義終端命令
如果我們想自定義終端命令,必須要遵從以下三點
1. 引入Command命令基類
2. 創建命令類必須直接或間接繼承Command,並在內部實現run方法,同時如果有自定義的其他參數,則必須實現__init__
3. 使用flask_script應用對象manage.add_command對命令類進行註冊,並設置調用終端別名。
from flask import Flask from flask_script import Manager, Command, Option # 1.引入command命令基類 app = Flask(__name__) class Config(): DEBUG = True app.config.from_object(Config) """基於flask_script創建自定義終端命令""" class HelloCommand(Command): # 2.1 創建命令類必須直接或間接繼承Command """命令相關的注釋""" option_list = [ Option("--name","-n",help="名稱"), Option("--num","-m",help="數量"), ] def run(self,name,num): # 2.2 在自定義終端命令內部實現run方法 print("name=%s" % name) print(num) print("命令執行了!!!") # 註冊終端腳本工具到app中 manager = Manager(app) manager.add_command("hello", HelloCommand) # 3.使用flask_script應用對象manage.add_command對命令類進行註冊,並設置調用終端別名。 @app.route("/") def index(): return "ok" if __name__ == '__main__': manager.run() # 運行該程式 # python run.py hello -n=hahaha -m=qiqiqi
運行結果:
3.自定義腳手架命令
from flask import Flask from flask_script import Manager, Command, Option # 1.引入Command命令基類 app = Flask(__name__) class Config(): DEBUG = True app.config.from_object(Config) manager = Manager(app) import os class BluePrintCommand(Command): # 2.1 創建命令類直接或間接繼承Command option_list = [ Option("--name","-n",help="藍圖名稱") ] def run(self,name=None): # 2.2 在命令類內部實現run方法 if name is None: print("藍圖名稱不能為空!") return if not os.path.isdir(name): os.mkdir(name) open("%s/views.py" % name,"w") open("%s/models.py" % name,"w") with open("%s/urls.py" % name,"w") as f: f.write("""from . import views urlpatterns = [ ] """) manager.add_command("blue", BluePrintCommand) # 3.使用應用對象.add_command對命令類進行註冊,並設置調用終端別名 @app.route("/") def index(): return "ok" if __name__ == '__main__': manager.run() # 運行程式 # python run.py blue -n=users
運行結果: