cookie 你們應該都熟悉,好比說登陸某些網站一段時間後,就要求你從新登陸;再好比有的同窗很喜歡玩爬蟲技術,有時候網站就是能夠攔截住你的爬蟲,這些都和 cookie 有關。若是你明白了服務器後端對於 cookie 和 session 的處理邏輯,就能夠解釋這些現象,甚至鑽一些空子無限白嫖,待我慢慢道來。git
cookie 的出現是由於 HTTP 是無狀態的一種協議,換句話說,服務器記不住你,可能你每刷新一次網頁,就要從新輸入一次帳號密碼進行登陸。這顯然是讓人沒法接受的,cookie 的做用就比如服務器給你貼個標籤,而後你每次向服務器再發請求時,服務器就可以 cookie 認出你。github
抽象地歸納一下:一個 cookie 能夠認爲是一個「變量」,形如 name=value
,存儲在瀏覽器;一個 session 能夠理解爲一種數據結構,多數狀況是「映射」(鍵值對),存儲在服務器上。golang
注意,我說的是「一個」cookie 能夠認爲是一個變量,可是服務器能夠一次設置多個 cookie,因此有時候說 cookie 是「一組」鍵值對兒,這也能夠說得通。web
cookie 能夠在服務器端經過 HTTP 的 SetCookie 字段設置 cookie,好比我用 Go 語言寫的一個簡單服務:面試
func cookie(w http.ResponseWriter, r *http.Request) { // 設置了兩個 cookie http.SetCookie(w, &http.Cookie{ Name: "name1", Value: "value1", }) http.SetCookie(w, &http.Cookie{ Name: "name2", Value: "value2", }) // 將字符串寫入網頁 fmt.Fprintln(w, "頁面內容") }
PS:我認真寫了 100 多篇原創,手把手刷 200 道力扣題目,所有發佈在 labuladong的算法小抄,持續更新。建議收藏,按照個人文章順序刷題,掌握各類算法套路後投再入題海就如魚得水了。算法
當瀏覽器訪問對應網址時,經過瀏覽器的開發者工具查看這次 HTTP 通訊的細節,能夠看見服務器的迴應發出了兩次 SetCookie
命令:數據庫
在這以後,瀏覽器的請求中的 Cookie
字段就帶上了這兩個 cookie:編程
cookie 的做用其實就是這麼簡單,無非就是服務器給每一個客戶端(瀏覽器)打的標籤,方便服務器辨認而已。固然,HTTP 還有不少參數能夠設置 cookie,好比過時時間,或者讓某個 cookie 只有某個特定路徑才能使用等等。後端
但問題是,咱們也知道如今的不少網站功能很複雜,並且涉及不少的數據交互,好比說電商網站的購物車功能,信息量大,並且結構也比較複雜,沒法經過簡單的 cookie 機制傳遞這麼多信息,並且要知道 cookie 字段是存儲在 HTTP header 中的,就算可以承載這些信息,也會消耗不少的帶寬,比較消耗網絡資源。瀏覽器
session 就能夠配合 cookie 解決這一問題,好比說一個 cookie 存儲這樣一個變量 sessionID=xxxx
,僅僅把這一個 cookie 傳給服務器,而後服務器經過這個 ID 找到對應的 session,這個 session 是一個數據結構,裏面存儲着該用戶的購物車等詳細信息,服務器能夠經過這些信息返回該用戶的定製化網頁,有效解決了追蹤用戶的問題。
session 是一個數據結構,由網站的開發者設計,因此能夠承載各類數據,只要客戶端的 cookie 傳來一個惟一的 session ID,服務器就能夠找到對應的 session,認出這個客戶。
固然,因爲 session 存儲在服務器中,確定會消耗服務器的資源,因此 session 通常都會有一個過時時間,服務器通常會按期檢查並刪除過時的 session,若是後來該用戶再次訪問服務器,可能就會面臨從新登陸等等措施,而後服務器新建一個 session,將 session ID 經過 cookie 的形式傳送給客戶端。
那麼,咱們知道 cookie 和 session 的原理,有什麼切實的好處呢?除了應對面試,我給你說一個雞賊的用處,就是能夠白嫖某些服務。
有些網站,你第一次使用它的服務,它直接免費讓你試用,可是用一次以後,就讓你登陸而後付費繼續使用該服務。並且你發現網站彷佛經過某些手段記住了你的電腦,除非你換個電腦或者換個瀏覽器才能再白嫖一次。
PS:我認真寫了 100 多篇原創,手把手刷 200 道力扣題目,所有發佈在 labuladong的算法小抄,持續更新。建議收藏,按照個人文章順序刷題,掌握各類算法套路後投再入題海就如魚得水了。
那麼問題來了,你試用的時候沒有登陸,網站服務器是怎麼記住你的呢?這就很顯然了,服務器必定是給你的瀏覽器打了 cookie,後臺創建了對應的 session 記錄你的狀態。你的瀏覽器在每次訪問該網站的時候都會聽話地帶着 cookie,服務器一查 session 就知道這個瀏覽器已經無償使用過了,得讓它登陸付費,不能讓它繼續白嫖了。
那若是我不讓瀏覽器發送 cookie,每次都假裝成一個第一次來試用的小萌新,不就能夠不斷白嫖了麼?瀏覽器會把網站的 cookie 以文件的形式存在某些地方(不一樣的瀏覽器配置不一樣),你把他們找到而後刪除就好了。可是對於 Firefox 和 Chrome 瀏覽器,有不少插件能夠直接編輯 cookie,好比個人 Chrome 瀏覽器就用的一款叫作 EditThisCookie 的插件,這是他們官網:
這類插件能夠讀取瀏覽器在當前網頁的 cookie,點開插件能夠任意編輯和刪除 cookie。固然,偶爾白嫖一兩次還行,不鼓勵高頻率白嫖,想經常使用仍是掏錢吧,不然網站賺不到錢,就只能取消免費試用這個機制了。
以上就是關於 cookie 和 session 的簡單介紹,cookie 是 HTTP 協議的一部分,不算複雜,而 session 是能夠定製的,因此下面詳細看一下實現 session 管理的代碼架構吧。
session 的原理不難,可是具體實現它但是頗有技巧的,通常須要三個組件配合完成,它們分別是 Manager
、Provider
和 Session
三個類(接口)。
一、瀏覽器經過 HTTP 協議向服務器請求路徑 /content
的網頁資源,對應路徑上有一個 Handler 函數接收請求,解析 HTTP header 中的 cookie,獲得其中存儲的 sessionID,而後把這個 ID 發給 Manager
。
二、Manager
充當一個 session 管理器的角色,主要存儲一些配置信息,好比 session 的存活時間,cookie 的名字等等。而全部的 session 存在 Manager
內部的一個 Provider
中。因此 Manager
會把 sid
(sessionID)傳遞給 Provider
,讓它去找這個 ID 對應的具體是哪一個 session。
三、Provider
就是一個容器,最多見的應該就是一個散列表,將每一個 sid
和對應的 session 一一映射起來。收到 Manager
傳遞的 sid
以後,它就找到 sid
對應的 session 結構,也就是 Session
結構,而後返回它。
四、Session
中存儲着用戶的具體信息,由 Handler 函數中的邏輯拿出這些信息,生成該用戶的 HTML 網頁,返回給客戶端。
那麼你也許會問,爲何搞這麼麻煩,直接在 Handler 函數中搞一個哈希表,而後存儲 sid
和 Session
結構的映射不就完事兒了?
這就是設計層面的技巧了,下面就來講說,爲何分紅 Manager
、Provider
和 Session
。
先從最底層的 Session
說。既然 session 就是鍵值對,爲啥不直接用哈希表,而是要抽象出這麼一個數據結構呢?
第一,由於 Session
結構可能不止存儲了一個哈希表,還能夠存儲一些輔助數據,好比 sid
,訪問次數,過時時間或者最後一次的訪問時間,這樣便於實現想 LRU、LFU 這樣的算法。
第二,由於 session 能夠有不一樣的存儲方式。若是用編程語言內置的哈希表,那麼 session 數據就是存儲在內存中,若是數據量大,很容易形成程序崩潰,並且一旦程序結束,全部 session 數據都會丟失。因此能夠有不少種 session 的存儲方式,好比存入緩存數據庫 Redis,或者存入 MySQL 等等。
所以,Session
結構提供一層抽象,屏蔽不一樣存儲方式的差別,只要提供一組通用接口操縱鍵值對:
type Session interface { // 設置鍵值對 Set(key, val interface{}) // 獲取 key 對應的值 Get(key interface{}) interface{} // 刪除鍵 key Delete(key interface{}) }
再說 Provider
爲啥要抽象出來。咱們上面那個圖的 Provider
就是一個散列表,保存 sid
到 Session
的映射,可是實際中確定會更加複雜。咱們不是要時不時刪除一些 session 嗎,除了設置存活時間以外,還能夠採用一些其餘策略,好比 LRU 緩存淘汰算法,這樣就須要 Provider
內部使用哈希鏈表這種數據結構來存儲 session。
PS:關於 LRU 算法的奧妙,參見前文「LRU 算法詳解」。
所以,Provider
做爲一個容器,就是要屏蔽算法細節,以合理的數據結構和算法組織 sid
和 Session
的映射關係,只須要實現下面這幾個方法實現對 session 的增刪查改:
type Provider interface { // 新增並返回一個 session SessionCreate(sid string) (Session, error) // 刪除一個 session SessionDestroy(sid string) // 查找一個 session SessionRead(sid string) (Session, error) // 修改一個session SessionUpdate(sid string) // 經過相似 LRU 的算法回收過時的 session SessionGC(maxLifeTime int64) }
最後說 Manager
,大部分具體工做都委託給 Session
和 Provider
承擔了,Manager
主要就是一個參數集合,好比 session 的存活時間,清理過時 session 的策略,以及 session 的可用存儲方式。Manager
屏蔽了操做的具體細節,咱們能夠經過 Manager
靈活地配置 session 機制。
綜上,session 機制分紅幾部分的最主要緣由就是解耦,實現定製化。我在 Github 上看過幾個 Go 語言實現的 session 服務,源碼都很簡單,有興趣的朋友能夠學習學習:
https://github.com/alexedwards/scs
https://github.com/astaxie/build-web-application-with-golang
_____________