day93:flask:Cookie&Session&請求鉤子&捕獲錯誤&上下文&Flask-Script

Poke發表於2020-11-19

目錄

1.HTTP的會話控制

2.Cookie

3.Session

4.請求鉤子

5.捕獲錯誤

6.上下文:context

7.Flask-Script

1.HTTP的會話控制

1.什麼是會話控制?

所謂的會話,就是客戶端瀏覽器和服務端網站之間一次完整的互動過程.

會話的開始是在使用者通過瀏覽器第一次訪問服務端網站開始.

會話的結束時在使用者通過關閉瀏覽器以後,與服務端斷開.

所謂的會話控制,就是在客戶端瀏覽器和服務端網站之間,進行多次http請求響應之間,記錄、跟蹤和識別使用者的資訊而已。

2.會話控制出現的原因

因為 http 是一種無狀態協議,瀏覽器請求伺服器是無狀態的。

無狀態:指一次使用者請求時,瀏覽器、伺服器無法知道之前這個使用者做過什麼,每次請求都是一次新的請求。

無狀態原因:瀏覽器與伺服器是使用 socket 套接字進行通訊的,伺服器將請求結果返回給瀏覽器之後,會關閉當前的 socket 連線,而且伺服器也會在處理頁面完畢之後銷燬頁面物件。

3.會話控制的應用場景

有時需要保持下來使用者瀏覽的狀態,比如使用者是否登入過,瀏覽過哪些商品等

實現狀態保持主要有兩種型別:

  • 在客戶端儲存資訊使用url Cookietoken令牌[jwt.csrf,oauth]

  • 在伺服器端儲存資訊使用Session

2.Cookie

1.Cookie是由伺服器端生成,傳送給客戶端瀏覽器,瀏覽器會將Cookie的key/value儲存,下次請求同一網站時就傳送該Cookie給伺服器(前提是瀏覽器設定為啟用cookie)。Cookie的key/value可以由伺服器端自己定義。

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

session相關配置項文件:https://dormousehole.readthedocs.io/en/latest/config.html?highlight=session_cookie_path

1.對於敏感、重要的資訊,建議要儲存在伺服器端,不能儲存在瀏覽器中,比如使用者名稱、餘額、等級、驗證碼等資訊

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. 通過異常類進行異常捕獲

  • errorhandler 裝飾器

    • 註冊一個錯誤處理程式,當程式丟擲指定錯誤狀態碼的時候,就會呼叫該裝飾器所裝飾的方法

  • 引數:

    • 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)。

  1. application 指的就是當你呼叫app = Flask(__name__)建立的這個物件app

  2. request 指的是每次http請求發生時,WSGI server(比如gunicorn)呼叫Flask.__call__()之後,在Flask物件內部建立的Request物件;

  3. application 表示用於響應WSGI請求的應用本身,request 表示每次http請求;

  4. application的生命週期大於request,一個application存活期間,可能發生多次http請求,所以,也就會有多個request

1.請求上下文(request context)

思考:在檢視函式中,如何取到當前請求的相關資料?比如:請求地址,請求方式,cookie等等

在 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)

它的字面意思是 應用上下文,但它不是一直存在的,它只是request context 中操作當前falsk應用物件 app 的代理(人),所謂local proxy。它的作用主要是幫助 request 獲取當前的flask應用相關的資訊,它是伴 request 而生,隨 request 而滅的。

應用上下文物件有:current_app,g

1.current_app

應用程式上下文,用於儲存應用程式中的變數,可以通過current_app.name列印當前app的名稱,也可以在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

文件: https://flask-script.readthedocs.io/en/latest/

這個模組的作用可以讓我們通過終端來控制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

執行結果:

 

相關文章