最近看了一些同窗的面經,發現不管什麼技術崗位,仍是會問到 Session 和 Cookie 的區別。html
全部學技術的同窗都知道 Session 和 Cookie 函數怎麼用,知道 Session 和 Cookie 的區別就是 Session 是儲存在服務端的,Cookie 是存儲在瀏覽器的。前端
可是其實是什麼東西,一些剛學習技術的同窗估計仍是模糊,我剛學 PHP 的時候,這種感受特別明顯。PHP 中 Session 和 Cookie 的操做只要操做 $_COOKIE
和 $_SESSION
數組就能夠了,並且操做方式和功能如出一轍,搞得我一臉懵逼。python
最後,仍是本身實現了一個 Session 操做類才恍然大悟,實質上就是兩個不一樣的存儲對象嘛。web
Cookie 的誕生是爲了能讓無狀態的 HTTP 報文帶上一些特殊的數據,讓服務端可以辨識請求的身份。面試
對於 Cookie 的概念就很少說了,Cookie 說簡單點就是瀏覽器上的一個 key-value 存儲對象,經過開發者工具直接看到 Cookie 的內容(F12)redis
寫入數據方式shell
Cookie 寫入數據的方式是經過 HTTP 返回報文 Header 部分 Set-Cookie
字段來設置,一個帶有寫 Cookie 指令的的 HTTP 返回報文以下數據庫
HTTP/1.1 200 OK Set-Cookie: SESSIONID=e13179a6-2378-11e9-ac30-fa163eeeaea1; Path=/ Transfer-Encoding: chunked Date: Tue, 29 Jan 2019 07:12:09 GMT Server: localhost
上述報文 Set-Cookie
指示瀏覽器設置 key
爲 SESSIONID
,value
爲 e13179a6-2378-11e9-ac30-fa163eeeaea1
的 Cookie後端
獲取數據方式數組
瀏覽器在發送請求的時候會檢查當前域已經設置的 Cookie,在 HTTP 請求報文 Header 部分的 Cookie
字段裏面帶上 Cookie
的信息。下面捉取了一段 HTTP 報文
GET http://10.0.1.24:23333/ HTTP/1.1 Host: 10.0.1.24:23333 Connection: keep-alive Pragma: no-cache Cache-Control: no-cache Upgrade-Insecure-Requests: 1 User-Agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.77 Safari/537.36 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8 Accept-Encoding: gzip, deflate Accept-Language: zh-CN,zh;q=0.9,en;q=0.8 Cookie: SESSIONID=e13179a6-2378-11e9-ac30-fa163eeeaea1
從最後的 Cookie
字段看到,瀏覽器請求時帶上了 key
爲 SESSIONID
,value
爲 e13179a6-2378-11e9-ac30-fa163eeeaea1
的數據,後端直接解析 HTTP 報文就能獲取 Cookie
的內容。
Session 在代碼裏面的語意是記錄客戶端狀態的一個存儲對象,是同一個客戶端請求共享的數組。這個存儲對象能夠是文件、緩存系統、數據庫。
如今假設要使用 redis 來實現 Session 功能,那麼就要求瀏覽器每次請求都要帶一個相同的字符串做爲身份信息,對應 redis 的 key,redis value 則爲 Session 數組序列化的內容。
那麼如何讓瀏覽器每次請求都帶一個身份信息呢,這就是 Session 和 Cookie 的關係,經過 Cookie 傳遞這個身份信息。流程以下
Set-Cookie
字段,內容帶上 UUID...
既然知道了 Session 的原理,咱們手動實現一個 Session 操做類,採用文件保存的方式。http 框架採用 web.py
,安裝方式以下
pip install web.py
咱們要實現的這個類就叫 Session
class Session: def __init__(self): self.session_id = None # session 數組 self._items = dict() self._load()
咱們全部 session 文件放在 sessions 目錄下,文件名爲對應的 session id,內容爲 Session 數組序列化的字符串。在初始化對象的時候經過獲取名爲 SESSIONID 的 Cookie,若是沒有就生成一個新的。
def _load(self): SESSIONID = web.cookies().get('SESSIONID', None) if not SESSIONID: SESSIONID = uuid.uuid() self.session_id = SESSIONID self._loadFromDisk()
獲取到 SESSIONID 後,檢查 sessions 目錄下有沒有對應的文件,若是有就讀取並反序列化
def _loadFromDisk(self): """ 從文件加載 SESSION """ file = './sessions/%s' % self.session_id if os.path.exists(file): f = open(file, 'rb') self._items = pickle.load(f) f.close()
獲取 Session 部分完成了,接下來就是保存 Session,咱們要把 SESSIONID 寫進 Cookie 裏面,這樣才能在下次請求時獲取到對應的 SESSIONID
def _setSessionCookie(self): """ Session id 寫入 cookie """ web.setcookie('SESSIONID', self.session_id)
最後把 Session 的內容保存到文件裏面
def _saveToDisk(self): """ 保存 SESSION 到文件 """ f = open('./sessions/%s' % self.session_id, 'wb') pickle.dump(self._items, f) f.close()
咱們新建一個文件,叫 server.py
,寫入測試代碼
#!/usr/bin/env python # -*- coding: utf-8 -*- # 測試 session import web import time from session import Session urls = ( '/', 'index' ) app = web.application(urls, globals()) class index: def GET(self): session = Session() if 'login_time' not in session: session['login_time'] = int(time.time()) return 'login time: %s' % session['login_time'] if __name__ == "__main__": app.run()
在同一目錄新建 session.py
文件,寫入 Session
類代碼
#!/usr/bin/env python # -*- coding: utf-8 -*- # Session import os import web import uuid try: import cPickle as pickle except ImportError: import pickle class Session: __instance = None def __new__(cls): """ 單例模式 """ if cls.__instance is None: cls.__instance = object.__new__(cls) return cls.__instance else: return cls.__instance def __init__(self): self.session_id = None # session 數組 self._items = dict() self._load() def __contains__(self, key): return key in self._items def __getitem__(self, key): return self._items.get(key, None) def __setitem__(self, key, value): self._items[key] = value return True def __delitem__(self, key): if key in self._items: del self._items[key] return True def __del__(self): """ 析構函數,結束請求時執行 """ self._setSessionCookie() self._saveToDisk() def _load(self): SESSIONID = web.cookies().get('SESSIONID', None) if not SESSIONID or SESSIONID is None: SESSIONID = uuid.uuid() self.session_id = SESSIONID self._loadFromDisk() def _loadFromDisk(self): """ 從文件加載 SESSION """ file = './sessions/%s' % self.session_id if os.path.exists(file): f = open(file, 'rb') self._items = pickle.load(f) f.close() def _setSessionCookie(self): """ Session id 寫入 cookie """ web.setcookie('SESSIONID', self.session_id) def _saveToDisk(self): """ 保存 SESSION 到文件 """ f = open('./sessions/%s' % self.session_id, 'wb') pickle.dump(self._items, f) f.close()
再在同一目錄新建 sessions 目錄,存放咱們的 Session 文件
mkdir sessions
啓動服務
[service@chengqm mysession]$ python server.py 23333 http://0.0.0.0:23333/
瀏覽器發起請求
查看 Cookie
查看 Session 文件內容
[service@chengqm mysession]$ cat sessions/e13179a6-2378-11e9-ac30-fa163eeeaea1 (dp1 S'login_time' p2 I1548749002 s.
能夠屢次刷新和更換瀏覽器測試,測試結果是符合咱們對 Session 的預期,簡陋版 Session 類功能就算實現了。
這是一道面試題,當年居然能用這個問題問倒過一些朋友,仍是有些意思的
從前面能夠知道,SESSIONID 是經過 Cookie 來傳遞,若是 Cookie 禁止了,還能獲取 SESSIONID 嗎? 答案是能夠的
既然 Cookie 禁止了,那麼咱們就能夠用參數的方法傳遞 SESSIONID,後端返回的時候,增長一個返回參數,叫 SESSIONID,而後前端存儲到 localstorage 裏面
前端請求的時候,去 localstorage 獲取SESSIONID,在請求參數裏面增長這一個參數
後端 Session 處理,先嚐試從 Cookie 中獲取 SESSIONID,若是獲取不到,再嘗試從請求參數中獲取 SESSIONID
這樣,就算禁止 Cookie 也是能獲取 Session 的。
最後,咱們得出 Session 和 Cookie 區別和聯繫
區別
聯繫