flask中session


Flask session 概念:
程序可以把數據存儲在用戶會話中,用戶會話是-種私有存儲,默認情況下,它會保存在客戶端cookie中。Flask提供了session 對
象來操作用戶會話。session 是基於cookie實現, 保存在服務端的鍵值對(形式為 {隨機字符串:‘xxxxxx’}), 同時在瀏覽器中的cookie中也對應一相同的隨機字符串,用來再次請求的 時候驗證;

注意 :Flask中的session是存在瀏覽器中 默認key是session(加密的cookie),使用session時要設置一個密鑰app. secret_ key

操作session就像操作python 中的字典一樣,我們可以使用session[‘user’]獲取值,也可以使用session. get(‘user’)獲取值。

因為session就像字典一樣所以,操作它的時候有兩種方法:

(1)result = session[‘username’] :如果內容不存在,將會報異常

(2)result = session.get(‘username’) :如果內容不存在,將返回None(推薦用法)

所以,使用第二種方法獲取session較好。

下面的代碼中展示了session的設置、讀取、刪除、清除

from flask import Flask, session
from datetime import timedelta
import os
 
app = Flask(__name__)
app.config['SECRET_KEY'] = os.urandom(24) #設置密鑰
app.config['PERMANENT_SESSION_LIFETIME'] = timedelta(days=7)  # 配置7天有效
 
 
# 設置session
@app.route('/')
def set():
    session['username'] = 'zhangvalue'  # 設置“字典”鍵值對
    session.permanent = True  # 設置session的有效時間,長期有效,一個月的時間有效,
    # 具體看上面的配置時間具體的,沒有上面設置的時間就是一個月有效
    print(app.config['SECRET_KEY'])
    return 'success'
 
 
# 讀取session
@app.route('/get')
def get():
    # 第一種session獲取如果不存在會報錯
    # session['username']
    # 推薦使用session.get('username')
    # session.get('username')
    return session.get('username')
 
 
# 刪除session
@app.route('/delete/')
def delete():
    print(session.get('username'), session.pop('username', None))
    # 或者 session['username'] = False
    print(session.get('username'))
    return 'success'
 
 
# 清除session中所有數據
@app.route('/clear')
def clear():
    print(session.get('username'))
    # 清除session中所有數據
    session.clear
    print(session.get('username'))
    return 'success'
 
 
if __name__ == '__main__':
    app.run(host='0.0.0.0', port='5010')

 在獲取session的時候如果如果使用第一種方法,不存在經就會報錯,因為不存在key為username2

KeyError: 'username2'

設置session的過期時間

如果沒有指定session的過期時間,那么默認是瀏覽器關閉后就自動結束。

session.permanent = True在flask下則可以將有效期延長至一個月。下面有方法可以配置具體多少天的有效期。

如果沒有指定session的過期時間,那么默認是瀏覽器關閉后就自動結束

如果設置了session的permanent屬性為True,那么過期時間是31天。

可以通過給app.config設置PERMANENT_SESSION_LIFETIME來更改過期時間,這個值的數據類型是datetime.timedelay類型。

一種更先進的配置有效期的方法:(比如配置7天有效)

1.引入包:from datetime import timedelta

2.配置有效期限:app.config[‘PERMANENT_SESSION_LIFETIME’] = timedelta(days=7) # 配置7天有效

3.設置:session.permanent = True

再次認識session
flask的session是基於cookie的會話保持。簡單的原理即:

當客戶端進行第一次請求時,客戶端的HTTP request(cookie為空)到服務端,服務端創建session,視圖函數根據form表單填寫session,請求結束時,session內容填寫入response的cookie中並返回給客戶端,客戶端的cookie中便保存了用戶的數據。

當同一客戶端再次請求時, 客戶端的HTTP request中cookie已經攜帶數據,視圖函數根據cookie中值做相應操作(如已經攜帶用戶名和密碼就可以直接登陸)。

在 flask 中使用 session 也很簡單,只要使用 from flask import session 導入這個變量,在代碼中就能直接通過讀寫它和 session 交互。

from flask import Flask, session, escape, request
 
app = Flask(__name__)
app.secret_key = 'please-generate-a-random-secret_key'
 
 
@app.route("/")
def index():
    if 'username' in session:
        return 'hello, {}\n'.format(escape(session['username']))
    return 'hello, stranger\n'
 
 
@app.route("/login", methods=['POST'])
def login():
    session['username'] = request.form['username']
    return 'login success'
 
 
if __name__ == '__main__':
    app.run(host='0.0.0.0', port=5000, debug=True)

 

上面這段代碼模擬了一個非常簡單的登陸邏輯,用戶訪問 POST /login 來登陸,后面訪問頁面的時候 GET /,會返回該用戶的名字。

flask中session使用非常簡單,但是實現原理卻沒那么簡單

二、請求第一次來時,session是什么時候生成的?存放在哪里? 

我們已經知道session是一個LocalProxy()對象: 

current_app = LocalProxy(_find_app)
request = LocalProxy(partial(_lookup_req_object, 'request'))
session = LocalProxy(partial(_lookup_req_object, 'session'))
g = LocalProxy(partial(_lookup_app_object, 'g'))

 客戶端的請求進來時,會調用app.wsgi_app():

 

    def wsgi_app(self, environ, start_response):
        ctx = self.request_context(environ)
        error = None
        try:
            try:
                ctx.push()
                # 尋找視圖函數,並執行
                # 獲取返回值 response
                response = self.full_dispatch_request()

 此時,會生成一個ctx,其本質是一個RequestContext對象:

class RequestContext(object):
    def __init__(self, app, environ, request=None):
        self.app = app
        if request is None:
            request = app.request_class(environ)
        self.request = request
        self.url_adapter = app.create_url_adapter(self.request)
        self.flashes = None
        self.session = None

 

在RequestContext 對象中定義了session,且初值為None。

接着繼續看wsgi_app函數中,ctx.push()函數:

    def push(self):
        app_ctx = _app_ctx_stack.top
        if app_ctx is None or app_ctx.app != self.app:
            app_ctx = self.app.app_context()
            app_ctx.push()
            self._implicit_app_ctx_stack.append(app_ctx)
        else:
            self._implicit_app_ctx_stack.append(None)
 
        if hasattr(sys, 'exc_clear'):
            sys.exc_clear()
 
        _request_ctx_stack.push(self)
 
        if self.session is None:
            session_interface = self.app.session_interface
            self.session = session_interface.open_session(
                self.app, self.request
            )
 
            if self.session is None:
                self.session = session_interface.make_null_session(self.app)

 

主要看后半部分代碼。判斷session是否為空,我在RequestContext 中看到session初值為空.

在 Flask 中,所有和 session 有關的調用,都是轉發到 self.session_interface 的方法調用上(這樣用戶就能用自定義的 session_interface 來控制 session 的使用)。而默認的 session_inerface 有默認值:

session_interface = SecureCookieSessionInterface()

 看SecureCookieSession:

class SecureCookieSession(CallbackDict, SessionMixin):
    modified = False
    accessed = False
 
    def __init__(self, initial=None):
        def on_update(self):
            self.modified = True
            self.accessed = True
 
        super(SecureCookieSession, self).__init__(initial, on_update)
 
    def __getitem__(self, key):
        self.accessed = True
        return super(SecureCookieSession, self).__getitem__(key)
 
    def get(self, key, default=None):
        self.accessed = True
        return super(SecureCookieSession, self).get(key, default)
 
    def setdefault(self, key, default=None):
        self.accessed = True
        return super(SecureCookieSession, self).setdefault(key, default)

看其繼承關系,其實就是一個特殊的字典。到此我們知道了session就是一個特殊的字典,調用SecureCookieSessionInterface類的open_session()創建,並保存在ctx中,即RequestContext對象中。但最終由session = LocalProxy(…, ‘session’)對象代為管理,到此,在視圖函數中就可以導入session並使用了。

三、當請求第二次來時,session生成的是什么?

當請求第二次到來時,與第一次的不同就在open_session()那個val判斷處,此時cookies不為空, 獲取cookie的有效時長,如果cookie依然有效,通過與寫入時同樣的簽名算法將cookie中的值解密出來並寫入字典並返回中,若cookie已經失效,則仍然返回’空字典’。

四、特殊的SecureCookieSession字典有那些功能?如何實現的?

默認的 session 對象是 SecureCookieSession,這個類就是一個基本的字典,外加一些特殊的屬性,比如 permanent(flask 插件會用到這個變量)、modified(表明實例是否被更新過,如果更新過就要重新計算並設置 cookie,因為計算過程比較貴,所以如果對象沒有被修改,就直接跳過)。

怎么知道實例的數據被更新過呢? SecureCookieSession 是基於 werkzeug/datastructures:CallbackDict 實現的,這個類可以指定一個函數作為 on_update 參數,每次有字典操作的時候(setitem、delitem、clear、popitem、update、pop、setdefault)會調用這個函數。

SecureCookieSession:

class SecureCookieSession(CallbackDict, SessionMixin):
 
    modified = False
    accessed = False
 
    def __init__(self, initial=None):
        def on_update(self):  
            self.modified = True
            self.accessed = True
        #將on_update()傳遞給CallbackDict
        super(SecureCookieSession, self).__init__(initial, on_update)
 
    def __getitem__(self, key):
        self.accessed = True
        return super(SecureCookieSession, self).__getitem__(key)
 
    def get(self, key, default=None):
        self.accessed = True
        return super(SecureCookieSession, self).get(key, default)
 
    def setdefault(self, key, default=None):
        self.accessed = True
        return super(SecureCookieSession, self).setdefault(key, default)

繼承的 CallbackDict:

class CallbackDict(UpdateDictMixin, dict):
 
    def __init__(self, initial=None, on_update=None):
        dict.__init__(self, initial or ())
        self.on_update = on_update
 
    def __repr__(self):
        return '<%s %s>' % (
            self.__class__.__name__,
            dict.__repr__(self)
        )

CallbackDict又繼承UpdateDictMixin:  

 

class UpdateDictMixin(object):
    on_update = None
 
    def calls_update(name):
        def oncall(self, *args, **kw):
            rv = getattr(super(UpdateDictMixin, self), name)(*args, **kw)
            if self.on_update is not None:
                self.on_update(self)
            return rv
        oncall.__name__ = name
        return oncall
 
    def setdefault(self, key, default=None):
        modified = key not in self
        rv = super(UpdateDictMixin, self).setdefault(key, default)
        if modified and self.on_update is not None:
            self.on_update(self)
        return rv
 
    def pop(self, key, default=_missing):
        modified = key in self
        if default is _missing:
            rv = super(UpdateDictMixin, self).pop(key)
        else:
            rv = super(UpdateDictMixin, self).pop(key, default)
        if modified and self.on_update is not None:
            self.on_update(self)
        return rv
 
    __setitem__ = calls_update('__setitem__')
    __delitem__ = calls_update('__delitem__')
    clear = calls_update('clear')
    popitem = calls_update('popitem')
    update = calls_update('update')
    del calls_update

由UpdateDictMixin()可知,對session進行改動會調用pop, __setitem__等方法,同時就會調用on_update()方法,從而修改

modify,security的值。

五、簽名算法:

都獲取 cookie 數據的過程中,最核心的幾句話是:

s = self.get_signing_serializer(app)
val = request.cookies.get(app.session_cookie_name)
data = s.loads(val, max_age=max_age)
 
return self.session_class(data)

其中兩句都和 s 有關,signing_serializer 保證了 cookie 和 session 的轉換過程中的安全問題。如果 flask 發現請求的 cookie 被篡改了,它會直接放棄使用。

我們繼續看 get_signing_serializer 方法:

def get_signing_serializer(self, app):
    if not app.secret_key:
        return None
    signer_kwargs = dict(
        key_derivation=self.key_derivation,
        digest_method=self.digest_method
    )
    return URLSafeTimedSerializer(app.secret_key,
        salt=self.salt,
        serializer=self.serializer,
        signer_kwargs=signer_kwargs)

我們看到這里需要用到很多參數:

secret_key:密鑰。這個是必須的,如果沒有配置 secret_key 就直接使用 session 會報錯

salt:為了增強安全性而設置一個 salt 字符串(可以自行搜索“安全加鹽”了解對應的原理)

serializer:序列算法

signer_kwargs:其他參數,包括摘要/hash算法(默認是 sha1)和 簽名算法(默認是 hmac)

URLSafeTimedSerializer 是 itsdangerous 庫的類,主要用來進行數據驗證,增加網絡中數據的安全性。itsdangerours提供了多種 Serializer,可以方便地進行類似 json 處理的數據序列化和反序列的操作。至於具體的實現,因為篇幅限制,就不解釋了。

六、session什么時候寫入cookie中?session的生命周期?

前面的幾個問題實際上都發生在wsgi_app()前兩句函數中,主要就是ctx.push()函數中,下面看看wsgi_app()后面干了嘛:

    def wsgi_app(self, environ, start_response):
  
        ctx = self.request_context(environ)
        error = None
        try:
            try:
		# ctx.push函數是前半部分最重要的一個函數
		# 生成request和session並將二者保存到RequestContext()對象ctxz中
		# 最后將ctx,push到LocalStack()對象_request_ctx_stack中
                ctx.push()
                # 尋找視圖函數,並執行
                response = self.full_dispatch_request()
            except Exception as e:
                error = e
                response = self.handle_exception(e)
            except:
                error = sys.exc_info()[1]
                raise
            return response(environ, start_response)
        finally:
            if self.should_ignore_error(error):
                error = None
            # 最后, 將自己請求在local中的數據清除
            ctx.auto_pop(error)

看full_dispatch_request:

    def full_dispatch_request(self):
        #執行before_first_request
        self.try_trigger_before_first_request_functions()
        try:
            # 觸發request_started 信號
            request_started.send(self)
            # 調用before_request
            rv = self.preprocess_request()
            if rv is None:
                #執行視圖函數
                rv = self.dispatch_request()
        except Exception as e:
            rv = self.handle_user_exception(e)
        return self.finalize_request(rv)

前半部分就在執行flask鈎子,before_first_request, before_request以及信號,接着執行視圖函數生成rv,我們主要看finalize_request(rv):  

   def finalize_request(self, rv, from_error_handler=False):
        response = self.make_response(rv)
        try:
            response = self.process_response(response)
            request_finished.send(self, response=response)
        except Exception:
            if not from_error_handler:
                raise
            self.logger.exception('Request finalizing failed with an '
                                  'error while handling an error')
        return response

首先根據rv生成response。再執行process_response:  

   def process_response(self, response):
        ctx = _request_ctx_stack.top
        bp = ctx.request.blueprint
        funcs = ctx._after_request_functions
        if bp is not None and bp in self.after_request_funcs:
            funcs = chain(funcs, reversed(self.after_request_funcs[bp]))
        if None in self.after_request_funcs:
            funcs = chain(funcs, reversed(self.after_request_funcs[None]))
        for handler in funcs:
            response = handler(response)
        if not self.session_interface.is_null_session(ctx.session):
            self.session_interface.save_session(self, ctx.session, response)
        return response

 前半部分主要執行flask的鈎子,看后面,判斷,session是否為空,如果不為空,則執行save_session(): 

 

    def save_session(self, app, session, response):
        domain = self.get_cookie_domain(app)
        path = self.get_cookie_path(app)
 
        # If the session is modified to be empty, remove the cookie.
        # If the session is empty, return without setting the cookie.
        if not session:
            if session.modified:
                response.delete_cookie(
                    app.session_cookie_name,
                    domain=domain,
                    path=path
                )
 
            return
 
        # Add a "Vary: Cookie" header if the session was accessed at all.
        if session.accessed:
            response.vary.add('Cookie')
 
        if not self.should_set_cookie(app, session):
            return
 
        httponly = self.get_cookie_httponly(app)
        secure = self.get_cookie_secure(app)
        samesite = self.get_cookie_samesite(app)
        expires = self.get_expiration_time(app, session)
        val = self.get_signing_serializer(app).dumps(dict(session))
        response.set_cookie(
            app.session_cookie_name,
            val,
            expires=expires,
            httponly=httponly,
            domain=domain,
            path=path,
            secure=secure,
            samesite=samesite
        )

總結:

至此,flask內置session的機制便講解完畢,session的實現是依賴與flask的上下文管理,因此先弄清楚flask上下文,再來看session就比較容易理解。其主要的就是SecureCookieSessionInterface對象的open_session()與save_session() 。open_session在請求剛進來時執行,完成session對象的創建(就是一特殊字典),在視圖函數中完成對session的賦值操作,save_session()在視圖函數執行完后,生成response后執行,將session寫入response的cookie中。

當然,flask內置session無法滿足生產需求。因為將session數據全部保存在cookie中不安全且cookie存儲數據量有限,但flask-session組件幫我們實現了將數據保存在服務器’‘數據庫’'中而只將sessionID保存在cookie中

session.load()和session.get()區別
get先到緩存中去查如果沒有就到DB中去查(即馬上發出sql語句)總之如果你確定DB中有這個對象就用load()不確定就用get()這樣效率會高

 

  

  

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM