目錄python
在Flask的框架中,本身已經封裝了 cookie的respons,request 有存儲就有讀取及刪除,那麼就拿購物車來舉例算法
在咱們登錄的時候會有以前在購物車存放的物品。也就是說在一個地方爲咱們保存了這些數據。前提有一個是要你登錄以後才能看到本身的購物車數據庫
cookie對應的是client session對應的是server。 也就是說,要在服務器上登陸你對應的帳戶,才能看到你本身在購物車添加的物品。可是django
物品那麼多,不能都存在服務器上吧,因此通常cookie都存在本身的計算機上,只是找不到而已,這裏就不說了。一些簡單的原理實現一下。json
首先來看cookie的簡單存儲,讀數據,刪除數據怎麼實現flask
# cookie相關的操做,依賴於make_response庫,調用cookie依賴request from flask import Flask, make_response, request app = Flask(__name__) app.config.from_pyfile('config.ini') # cookie存在client裏。存在本身電腦的我的當資料裏 # 存cookie的方法 @app.route('/setcookie') def set_cookie(): resp = make_response('存儲cookie') # 使用set方法,來存儲 key-values 形式的數據 resp.set_cookie('productname','衛生紙') return resp # 獲取,取到數據,調用cookie的方法 @app.route('/getcookie') def get_cookie(): # 經過request模塊的cookies屬性的方法指定Key 來調用value resp = request.cookies.get('productname') return resp # 刪除cookie的方法 @app.route('/delcookie') def del_cookie(): # 經過make_response對象內置的delete_cookie方法來指定key來刪除vaule resp = make_response('刪除cookie') resp.delete_cookie('productname') return resp if __name__ == "__main__": app.run()
在使用session以前必須如今設置一下密鑰瀏覽器
app.secret_key="asdas" #值隨便
除請求對象以外,還有一個 session 對象。它容許你在不一樣請求間存儲特定用戶的信息。它是在 Cookies 的基礎上實現的,而且對 Cookies 進行密鑰簽名要使用會話,你須要設置一個密鑰。 (app.session_interface
對象)安全
設置:session['username'] = 'xxx' # 在django中發什麼三件事, # 1 生成一個隨機的字符串 # 2 往數據庫存 # 3 寫入cookie返回瀏覽器 # 在flask中他沒有數據庫,但session是怎樣實現的? # 生成一個密鑰寫入這個cookie,而後下次請求的時候,經過這個cookie解密,而後賦值給session #咱們經過app.session_interface來查看 刪除:session.pop('username', None)
save_session
是app.session_interface
中的函數,他的參數以下:服務器
參數 | 做用 |
---|---|
key | 鍵 |
value | 值 |
max_age=None | 超時時間 cookie須要延續的時間(以秒爲單位)若是參數是 None`,這個cookie會延續到瀏覽器關閉爲止| |expires=None|超時時間(IE requires expires, so set it if hasn't been already.)| |path='/'|Cookie生效的路徑,/ 表示根路徑,特殊的:根路徑的cookie能夠被任何url的頁面訪問,瀏覽器只會把cookie回傳給帶有該路徑的頁面,這樣能夠避免將cookie傳給站點中的其餘的應用。| |domain=None|Cookie生效的域名 你可用這個參數來構造一個跨站cookie。如, domain=".example.com"所構造的cookie對下面這些站點都是可讀的:<br /> www.example.com、 www2.example.com 和 an.other.sub.domain.example.com `。若是該參數設置爲 None ,cookie只能由設置它的站點讀取 |
secure=False | 瀏覽器將經過HTTPS來回傳cookie |
httponly=False | 只能http協議傳輸,沒法被JavaScript獲取(不是絕對,底層抓包能夠獲取到也能夠被覆蓋) |
在flask請求上下文和應用上下文中已經知道session是一個LocalProxy()
對象:cookie
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, session=None): self.app = app if request is None: request = app.request_class(environ) self.request = request self.url_adapter = None try: self.url_adapter = app.create_url_adapter(self.request) except HTTPException as e: self.request.routing_exception = e self.flashes = None self.session = session
在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() # 把ctx對象加入到Local()對象中 _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()
執行SecureCookieSessionInterface.open_session()
來生成默認session對象:
def open_session(self, app, request): # 獲取session簽名的算法 s = self.get_signing_serializer(app) # 若是爲空 直接返回None if s is None: return None val = request.cookies.get(app.session_cookie_name) # 若是val爲空,即request.cookies爲空 if not val: return self.session_class() max_age = total_seconds(app.permanent_session_lifetime) try: data = s.loads(val, max_age=max_age) return self.session_class(data) except BadSignature: return self.session_class()
請求第一次來時,request.cookies
爲空,即返回self.session_class()
:
session_class = SecureCookieSession
看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並使用了。
當請求第二次到來時,與第一次的不一樣就在open_session()那個val判斷處,此時cookies不爲空, 獲取cookie的有效時長,若是cookie依然有效,經過與寫入時一樣的簽名算法將cookie中的值解密出來並寫入字典並返回中,若cookie已經失效,則仍然返回空字典
。
默認的 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 處理的數據序列化和反序列的操做。
前面的幾個問題實際上都發生在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 )
save_session()比較簡單,且有註釋,便再也不講解,主要就是將session寫入response.set_cookie
中。這樣便完成session的寫入response工做,並由response返回至客戶端。
再請求結束時會執行wsgi_app()
的finally:ctx.auto_pop(error)
函數,將與對應請求相關的request,session清除,session生命週期便結束。
至此,flask內置session的機制便講解完畢,session的實現是依賴與flask的上下文管理,所以先弄清楚flask上下文,再來看session就比較容易理解。其主要的就是SecureCookieSessionInterface
對象的open_session()
與save_session()
。open_session
在請求剛進來時執行,完成session對象的建立(就是一特殊字典),在視圖函數中完成對session的賦值操做,save_session()
在視圖函數執行完後,生成response後執行,將session寫入response的cookie中。