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

目錄

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相關配置項文檔://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

文檔: //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

運行結果: