Go Web:Cookie

Cookie用來解決http協議無狀態的問題。html

首先,在服務端生成Cookie,而後在http響應header中設置Set-Cookie字段,客戶端會讀取到Set-Cookie字段後,會將cookie信息存儲起來,下次繼續訪問服務端時,會在http請求中設置Cookie字段併發送給服務端,服務端能夠解析這個Cookie字段,從而知道這個客戶端以前已經和本身有過會話(上下文),而後再執行相應的邏輯代碼。瀏覽器

Cookie分爲兩種類型:session cookie和persistent cookie。cookie

  • Session Cookie也稱爲臨時Cookie,客戶端只會將cookie數據存儲在http client進程的內容中,不會保存到磁盤文件中(或其它存儲設備),瀏覽器關閉(或者說http client進程退出)的時候,cookie就刪除了
  • persistent cookie是持久化cookie,瀏覽器退出也不刪除,而是根據服務端發送cookie時設置的過時時長判斷cookie是否過時,只要cookie還有效,客戶端就會攜帶cookie訪問服務端
$ go doc http.cookie

type Cookie struct {
        Name  string
        Value string

        Path       string    // optional
        Domain     string    // optional
        Expires    time.Time // optional
        RawExpires 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 seconds
        MaxAge   int
        Secure   bool
        HttpOnly bool
        Raw      string
        Unparsed []string // Raw text of unparsed attribute-value pairs
}


func (c *Cookie) String() string

一個Cookie表明一個http cookie。服務端能夠設置多個Set-Cookie字段發送給客戶端。session

Name和Value分別設置這個cookie的key/value。必定要有至少一個能惟一區分客戶端的ID類的value。併發

Expires指定cookie到何時過時,是一個時間值。當指定爲過去的時間值時,表示這個cookie已通過期。curl

MaxAge也用來設置cookie何時過時,MaxAge爲負數或等於0表示當即過時,MaxAge大於0表示過多少秒以後過時。函數

MaxAge和Expires均可以設置cookie持久化時的過時時長,Expires是老式的過時方法,若是能夠,應該使用MaxAge設置過時時間,但有些老版本的瀏覽器不支持MaxAge。若是要支持全部瀏覽器,要麼使用Expires,要麼同時使用MaxAge和Expires。post

Path和Domain設置訪問哪些路徑或域名範圍的主機時應該攜帶這個cookie。若是不設置,則訪問全部路徑、該Domain下的主機都攜帶cookie。測試

cookie.Path("/WEB16");
    表明訪問WEB16應用中的任何資源都攜帶cookie
cookie.Path("/WEB16/cookietest");
    表明訪問WEB16中的cookietest時才攜帶cookie信息
cookie.Domain(".foo.com");
    這對foo.com域下的全部主機都生效(如www.foo.com),但不包括子域www.abc.foo.com

Secure和HttpOnly字段爲cookie提供一些保護機制。這兩個cookie屬性的介紹,參見:ui

Cookie有一個String()方法,用來將Cookie實例轉換成字符串。轉化成字符串以後就能夠直接設置在Header中。

例如,下面是登陸youtube的時候,對方發送給個人cookie:

設置Cookie併發送給客戶端

package main

import (
    "fmt"
    "net/http"
)

func setCookie(w http.ResponseWriter, r *http.Request) {
    // 定義兩個cookie
    c1 := http.Cookie{
        Name:  "first_cookie",
        Value: "Go Programming",
    }
    c2 := http.Cookie{
        Name:     "second_cookie",
        Value:    "Go Web Programming",
        HttpOnly: true,
    }
    // 設置Set-Cookie字段
    w.Header().Set("Set-Cookie", c1.String())
    w.Header().Add("Set-Cookie", c2.String())
    fmt.Fprintf(w, "%s\n%s\n", c1.String(), c2.String())
}

func main() {
    server := http.Server{
        Addr: "127.0.0.1:8080",
    }
    http.HandleFunc("/set_cookie", setCookie)
    server.ListenAndServe()
}

訪問http://127.0.0.1:8080/set_cookie時,查看Header將顯式Set-Cookie字段。

$ curl -i http://127.0.0.1:8080/set_cookie
HTTP/1.1 200 OK
Set-Cookie: first_cookie="Go Programming"
Set-Cookie: second_cookie="Go Web Programming"; HttpOnly
Date: Tue, 27 Nov 2018 10:12:44 GMT
Content-Length: 75
Content-Type: text/plain; charset=utf-8

first_cookie="Go Programming"
second_cookie="Go Web Programming"; HttpOnly

http包提供了一個SetCookie()函數,能夠直接用來設置Set-Cookie字段。

func SetCookie(w ResponseWriter, cookie *Cookie)

注意,第二個字段是指針類型的Cookie

修改前面的示例,使用SetCookie()函數發送Set-Cookie字段:

func setCookie(w http.ResponseWriter, r *http.Request) {
    c1 := http.Cookie{
        Name:  "first_cookie",
        Value: "Go Programming",
    }
    c2 := http.Cookie{
        Name:     "second_cookie",
        Value:    "Go Web Programming",
        HttpOnly: true,
    }
    http.SetCookie(w, &c1)
    http.SetCookie(w, &c2)
}

取得客戶端攜帶的cookie

因爲客戶端發起請求時,若是攜帶cookie,是直接放在Request的Cookie Header中的。因此,能夠經過Request取得客戶端攜帶的cookie信息。固然,也能夠經過Request的方法Cookie()或Cookies()取得cookie信息。

func (r *Request) Cookie(name string) (*Cookie, error)
func (r *Request) Cookies() []*Cookie
  • Cookie(Name)只取某個cookie
  • Cookies()取全部的cookie

下面是經過Request Header的方式取Cookie的示例:

package main

import (
    "fmt"
    "net/http"
)

func setCookie(w http.ResponseWriter, r *http.Request) {
    c1 := http.Cookie{
        Name:  "first_cookie",
        Value: "Go Programming",
    }
    c2 := http.Cookie{
        Name:     "second_cookie",
        Value:    "Go Web Programming",
        HttpOnly: true,
    }
    http.SetCookie(w, &c1)
    http.SetCookie(w, &c2)
}

func getCookie(w http.ResponseWriter, r *http.Request) {
    cookie := r.Header.Get("Cookie")
    fmt.Fprintf(w, "%s\n", cookie)
}

func main() {
    server := http.Server{
        Addr: "127.0.0.1:8080",
    }
    http.HandleFunc("/set_cookie", setCookie)
    http.HandleFunc("/get_cookie", getCookie)
    server.ListenAndServe()
}

在訪問http://127.0.0.1:8080/set_cookie以後不要關閉瀏覽器,再次訪問http://127.0.0.1:8080/get_cookie,將輸出:

first_cookie="Go Programming"; second_cookie="Go Web Programming"

或者,使用curl記錄cookie,並下次訪問時讀取cookie:

$ curl -c a.cookie http://127.0.0.1:8080/set_cookie
$ curl -b a.cookie http://127.0.0.1:8080/get_cookie
first_cookie="Go Programming"; second_cookie="Go Web Programming"

下面是改用Request的Cookie()和Cookies()方法取cookie:

func getCookie(w http.ResponseWriter, r *http.Request) {
    cookie, err := r.Cookie("first_cookie")
    if err != nil {
        fmt.Fprintf(w, "Cat't get Cookie")
    }
    cookies := r.Cookies()
    fmt.Fprintf(w, "%s\n%s\n", cookie, cookies)
}

訪問結果:

$ curl -c a.cookie http://127.0.0.1:8080/set_cookie
$ curl -b a.cookie http://127.0.0.1:8080/get_cookie
first_cookie="Go Programming"
[first_cookie="Go Programming" second_cookie="Go Web Programming"]

設置cookie過時示例:發送臨時消息

有時候可能想要讓客戶端的某些操做只顯示一次相關消息,例如post一篇帖子失敗後,應該顯示失敗信息,但下次再訪問不該該再顯示這些失敗信息。

經過設置cookie過時的技巧,能夠實現一些一次性操做。設置cookie過時的方式是設置MaxAge爲負數或0,爲了兼容全部瀏覽器,能夠設置Expires爲過去的一段時間。

下面的示例中,將一段數據使用URL格式編碼後做爲flash cookie的值。當客戶端訪問set_message的時候,就會在http Client進程中保存這段cookie。再訪問show_message的時候,handler解析客戶端攜帶的cookie,並設置一個Set-Cookie字段,這個字段的做用是使以前保存的cookie過時。而後輸出解碼後客戶端攜帶的cookie的值。再次刷新show_message,將獲得不一樣的輸出結果。

package main

import (
    "encoding/base64"
    "fmt"
    "net/http"
    "time"
)

func set_message(w http.ResponseWriter, r *http.Request) {
    msg := []byte("Hello World")
    cookie := http.Cookie{
        Name:  "flash",
        Value: base64.URLEncoding.EncodeToString(msg),
    }
    http.SetCookie(w, &cookie)
}

func show_message(w http.ResponseWriter, r *http.Request) {
    cookie, err := r.Cookie("flash")
    if err != nil {
        if err == http.ErrNoCookie {
            fmt.Fprintln(w, "no messages to show")
        }
    } else {
        expire_cookie := http.Cookie{
            Name:    "flash",
            MaxAge:  -1,
            Expires: time.Unix(1, 0),
        }
        http.SetCookie(w, &expire_cookie)
        value, _ := base64.URLEncoding.DecodeString(cookie.Value)
        fmt.Fprintln(w, string(value))
    }
}
func main() {
    server := http.Server{
        Addr: "127.0.0.1:8080",
    }
    http.HandleFunc("/set_message", set_message)
    http.HandleFunc("/show_message", show_message)
    server.ListenAndServe()
}

使用curl測試。注意,首先訪問set_message的時候,保存cookie到b.cookie文件。再訪問show_message的時候,也要帶上-c b.cookie將已保存的cookie設置爲過時,以後再訪問show_message就會出現預期的結果:

$ curl -c b.cookie http://127.0.0.1:8080/set_message
$ curl -b b.cookie -c b.cookie http://127.0.0.1:8080/show_message
Hello World

$ curl -b b.cookie -c b.cookie http://127.0.0.1:8080/show_message
no messages to show
相關文章
相關標籤/搜索