Cookie的由來

HTTP協議是無狀態的,這就存在一個問題。

無狀態的意思是每次請求都是獨立的,它的執行情況和結果與前面的請求和之后的請求都無直接關系,它不會受前面的請求響應情況直接影響,也不會直接影響后面的請求響應情況。

一句有意思的話來描述就是人生只如初見,對服務器來說,每次的請求都是全新的。

狀態可以理解為客戶端和服務器在某次會話中產生的數據,那無狀態的就以為這些數據不會被保留。會話中產生的數據又是我們需要保存的,也就是說要“保持狀態”。因此Cookie就是在這樣一個場景下誕生。

Cookie是什么

在 Internet 中,Cookie 實際上是指小量信息,是由 Web 服務器創建的,將信息存儲在用戶計算機上(客戶端)的數據文件。一般網絡用戶習慣用其復數形式 Cookies,指某些網站為了辨別用戶身份、進行 Session 跟蹤而存儲在用戶本地終端上的數據,而這些數據通常會經過加密處理。

Cookie的機制

Cookie是由服務器端生成,發送給User-Agent(一般是瀏覽器),瀏覽器會將Cookie的key/value保存到某個目錄下的文本文件內,下次請求同一網站時就發送該Cookie給服務器(前提是瀏覽器設置為啟用cookie)。Cookie名稱和值可以由服務器端開發自己定義,這樣服務器可以知道該用戶是否是合法用戶以及是否需要重新登錄等,服務器可以設置或讀取Cookies中包含信息,借此維護用戶跟服務器會話中的狀態。

總結一下Cookie的特點:

  1. 瀏覽器發送請求的時候,自動把攜帶該站點之前存儲的Cookie信息。
  2. 服務端可以設置Cookie數據。
  3. Cookie是針對單個域名的,不同域名之間的Cookie是獨立的。
  4. Cookie數據可以配置過期時間,過期的Cookie數據會被系統清除。

查看Cookie

我們使用Chrome瀏覽器打開一個網站,打開開發者工具查看該網站保存在我們電腦上的Cookie數據。

Go操作Cookie

標准庫net/http中定義了Cookie,它代表一個出現在HTTP響應頭中Set-Cookie的值里或者HTTP請求頭中Cookie的值的HTTP cookie

type Cookie struct {
    Name       string
    Value      string
    Path       string
    Domain     string
    Expires    time.Time
    RawExpires string
    // MaxAge=0表示未設置Max-Age屬性
    // MaxAge<0表示立刻刪除該cookie,等價於"Max-Age: 0"
    // MaxAge>0表示存在Max-Age屬性,單位是秒
    MaxAge   int
    Secure   bool
    HttpOnly bool
    Raw      string
    Unparsed []string // 未解析的“屬性-值”對的原始文本
}

設置Cookie

net/http中提供了如下SetCookie函數,它在w的頭域中添加Set-Cookie頭,該HTTP頭的值為cookie。

func SetCookie(w ResponseWriter, cookie *Cookie)

獲取Cookie

Request對象擁有兩個獲取Cookie的方法和一個添加Cookie的方法:

獲取Cookie的兩種方法:

// 解析並返回該請求的Cookie頭設置的所有cookie
func (r *Request) Cookies() []*Cookie

// 返回請求中名為name的cookie,如果未找到該cookie會返回nil, ErrNoCookie。
func (r *Request) Cookie(name string) (*Cookie, error)

添加Cookie的方法:

// AddCookie向請求中添加一個cookie。
func (r *Request) AddCookie(c *Cookie)

gin框架操作Cookie

import (
    "fmt"

    "github.com/gin-gonic/gin"
)

func main() {
    router := gin.Default()
    router.GET("/cookie", func(c *gin.Context) {
        cookie, err := c.Cookie("gin_cookie") // 獲取Cookie
        if err != nil {
            cookie = "NotSet"
            // 設置Cookie
            c.SetCookie("gin_cookie", "test", 3600, "/", "localhost", false, true)
        }
        fmt.Printf("Cookie value: %s \n", cookie)
    })

    router.Run()
}

Session

Session的由來

Cookie雖然在一定程度上解決了“保持狀態”的需求,但是由於Cookie本身最大支持4096字節,以及Cookie本身保存在客戶端,可能被攔截或竊取,因此就需要有一種新的東西,它能支持更多的字節,並且他保存在服務器,有較高的安全性。這就是Session

問題來了,基於HTTP協議的無狀態特征,服務器根本就不知道訪問者是“誰”。那么上述的Cookie就起到橋接的作用。

用戶登陸成功之后,我們在服務端為每個用戶創建一個特定的session和一個唯一的標識,它們一一對應。其中:

  • Session是在服務端保存的一個數據結構,用來跟蹤用戶的狀態,這個數據可以保存在集群、數據庫、文件中;
  • 唯一標識通常稱為Session ID會寫入用戶的Cookie中。

這樣該用戶后續再次訪問時,請求會自動攜帶Cookie數據(其中包含了Session ID),服務器通過該Session ID就能找到與之對應的Session數據,也就知道來的人是“誰”。

總結而言:Cookie彌補了HTTP無狀態的不足,讓服務器知道來的人是“誰”;但是Cookie以文本的形式保存在本地,自身安全性較差;所以我們就通過Cookie識別不同的用戶,對應的在服務端為每個用戶保存一個Session數據,該Session數據中能夠保存具體的用戶數據信息。

另外,上述所說的Cookie和Session其實是共通性的東西,不限於語言和框架。