cookie
Web開發中一個很重要的議題就是如何做好用戶的整個瀏覽過程的控制,因為HTTP協議是無狀態的,所以用戶的每一次請求都是無狀態的,我們不知道在整個Web操作過程中哪些連接與該用戶有關,我們應該如何來解決這個問題呢?Web里面經典的解決方案是cookie和session,cookie機制是一種客戶端機制,把用戶數據保存在客戶端,而session機制是一種服務器端的機制,服務器使用一種類似于散列表的結構來保存信息,每一個網站訪客都會被分配給一個唯一的標志符,即sessionID,它的存放形式無非兩種:要么經過url傳遞,要么保存在客戶端的cookies里。當然,你也可以將Session保存到數據庫里,這樣會更安全,但效率方面會有所下降。
?
一、cookie的介紹
session和cookie是網站瀏覽中較為常見的兩個概念,也是比較難以辨析的兩個概念,但它們在瀏覽需要認證的服務頁面以及頁面統計中卻相當關鍵。我們先來了解一下cookie怎么來的?考慮這樣一個問題:
如何抓取一個訪問受限的網頁?如新浪微博好友的主頁,個人微博頁面等。
顯然,通過瀏覽器,我們可以手動輸入用戶名和密碼來訪問頁面,而所謂的“抓取”,其實就是使用程序來模擬完成
同樣的工作,因此我們需要了解“登陸”過程中到底發生了什么。
當用戶來到微博登陸頁面,輸入用戶名和密碼之后點擊“登錄”后瀏覽器將認證信息POST給遠端的服務器,服務器執行驗證邏輯,如果驗證通過,則瀏覽器會跳轉到登錄用戶的微博首頁,在登錄成功后,服務器如何驗證我們對其他受限制頁面的訪問呢?因為HTTP協議是無狀態的,所以很顯然服務器不可能知道我們已經在上一次的HTTP請求中通過了驗證。當然,最簡單的解決方案就是所有的請求里面都帶上用戶名和密碼,這樣雖然可行,但大大加重了服務器的負擔(對于每個request都需要到數據庫驗證),也大大降低了用戶體驗(每個頁面都需要重新輸入用戶名密碼,每個頁面都帶有登錄表單)。既然直接在請求中帶上用戶名與密碼不可行,那么就只有在服務器或客戶端保存一些類似的可以代表身份的信息了,所以就有了cookie與session。
cookie,簡而言之就是在本地計算機保存一些用戶操作的歷史信息(當然包括登錄信息),并在用戶再次訪問該站點時瀏覽器通過HTTP協議將本地cookie內容發送給服務器,從而完成驗證,或繼續上一步操作。
cookie的原理圖:
Cookie是由瀏覽器維持的,存儲在客戶端的一小段文本信息,伴隨著用戶請求和頁面在Web服務器和瀏覽器之間傳遞。用戶每次訪問站點時,Web應用程序都可以讀取cookie包含的信息。瀏覽器設置里面有cookie隱私數據選項,打開它,可以看到很多已訪問網站的cookies,如下圖所示:
?
?
?
cookie是有時間限制的,根據生命期不同分成兩種:會話cookie和持久cookie;
如果不設置過期時間,則表示這個cookie生命周期為從創建到瀏覽器關閉止,只要關閉瀏覽器窗口,cookie就消失了。這種生命期為瀏覽會話期的cookie被稱為會話cookie。會話cookie一般不保存在硬盤上而是保存在內存里。
如果設置了過期時間(setMaxAge(606024)),瀏覽器就會把cookie保存到硬盤上,關閉后再次打開瀏覽器,這些cookie依然有效直到超過設定的過期時間。存儲在硬盤上的cookie可以在不同的瀏覽器進程間共享,比如兩個IE窗口。而對于保存在內存的cookie,不同的瀏覽器有不同的處理方式。
?
二、Go使用cookie
Go語言中通過net/http包中的SetCookie來設置:
http.SetCookie(w ResponseWriter, cookie *Cookie)
w表示需要寫入的response,cookie是一個struct,讓我們來看一下cookie對象是怎么樣的
type Cookie struct {Name stringValue stringPath string // optionalDomain string // optionalExpires time.Time // optionalRawExpires string // for reading cookies only// MaxAge=0 means no 'Max-Age' attribute specified.// MaxAge<0 means delete cookie now, equivalently 'Max-Age: 0'// MaxAge>0 means Max-Age attribute present and given in secondsMaxAge intSecure boolHttpOnly boolRaw stringUnparsed []string // Raw text of unparsed attribute-value pairs }
我們來看一個例子,如何設置cookie,以及獲取cookie
package mainimport ("net/http""fmt" )func main() {http.HandleFunc("/setcookie", SetCookie)http.HandleFunc("/getcookie", GetCookie)http.ListenAndServe("localhost:8080", nil)}func SetCookie(w http.ResponseWriter, r *http.Request) {//設置cookiecookie := http.Cookie{Name: "name", Value: "hanru", Path: "/", MaxAge: 60}http.SetCookie(w, &cookie)w.Write([]byte("write cookie ok"))}//Go讀取cookie func GetCookie(w http.ResponseWriter, r *http.Request) {cookie2, _ := r.Cookie("name")fmt.Fprint(w, cookie2)//還有另外一種讀取方式//for _, cookie := range r.Cookies() {// fmt.Fprint(w, cookie.Name)//} }
運行服務器后,打開瀏覽器輸入地址:http://127.0.0.1:8080/setcookie
運行結果如下:
?
?
?在chrome瀏覽器中執行:http://127.0.0.1:8080/setcookie, 觸發go服務執行設置cookie的動作, 瀏覽器收到這個信息后, 真正執行設置cookie, 在chrome中可查(chrome://settings/content/cookies目錄中的localhost域名中查), 如下:
?
?
可以看到,過期時間是60s, 60s后再次查的時候, 就沒有name對應的cookie項了。
接下來,我們獲取一下cookie,要在設置cookie的60s內進行獲取,否則就取不到了。
然后打開瀏覽器,輸入網址:http://127.0.0.1:8080/getcookie
運行效果如下:
我們等cookie過期消失, 然后再來玩抓包。 好,現在沒有name對應的cookie了, 我們連續兩次執行:http://127.0.0.1:8080/setcookie, 服務端tcpdump抓包,兩次的請求和響應依次如下:
第一次:
第二次:
?
可以看到,第一次請求時, 瀏覽器發出的請求中不帶cookie, 因為瀏覽器壓根就沒有這項cookie, 在go服務端的回包中攜帶了Set-Cookie首部, 瀏覽器收到這個信息后,將其寫入到瀏覽器本地, 形成cookie。
第二次請求時(因cookie的過期時間設置為了60s, 故必須在60s內發起第二次請求), 此時, 瀏覽器的http請求中自動攜帶了cookie信息,go服務端也能收到這個信息。
由此可見, cookie不過是瀏覽器端的一個環境變量而已, 僅此而已。cookie值存在于瀏覽器所在的本地電腦中。 而所謂的服務端設置cookie, 其實是服務端給瀏覽器發送對應的設置cookie信息, 由瀏覽器來真正執行設置cookie的操作, 存于本地電腦磁盤中。
?
轉載自https://www.chaindesk.cn/witbook/17/265,謝謝韓老師