- HTTP無狀態(tài)協(xié)議
HTTP本身是一種無狀態(tài)的連接協(xié)議,因此每個(gè)請(qǐng)求或響應(yīng)都是獨(dú)立的。服務(wù)端每次處理完一個(gè)客戶端請(qǐng)求之后就會(huì)斷開連接,所以每次請(qǐng)求或響應(yīng)與之前和之后的請(qǐng)求或響應(yīng)是沒有任何關(guān)系的。換句話說,HTTP自身不具備保存之前發(fā)送過的請(qǐng)求或響應(yīng)的狀態(tài)。
由于HTTP無狀態(tài)的特性,在Web應(yīng)用中跟蹤用戶狀態(tài)的方法可分為四種:
- 建立包含有跟蹤數(shù)據(jù)的隱藏字段
- 重寫包含額外參數(shù)的URL
- 使用持續(xù)的Cookie
- 使用服務(wù)端的Session
Cookie和Session是Web應(yīng)用比較常見的概念,它們的目的都是為了克服HTTP協(xié)議的無狀態(tài)問題。
Cookie
Cookie是一小段由客戶端保存在本地的文本文件,可記錄用戶ID、密碼、瀏覽過的網(wǎng)頁、瀏覽時(shí)間等信息。當(dāng)客戶端再次訪問同一個(gè)網(wǎng)站時(shí),客戶端會(huì)把請(qǐng)求連同Cookie一起發(fā)送給服務(wù)端,服務(wù)端通過檢查Cookie以辨認(rèn)用戶狀態(tài)。
Cookie機(jī)制
Cookie是由服務(wù)端生成后發(fā)送給User-Agent(比如瀏覽器),User-Agent會(huì)將Cookie的key/value保存到本地磁盤上指定目錄下的文本文件內(nèi)。當(dāng)下次請(qǐng)求相同網(wǎng)站時(shí)會(huì)發(fā)送該Cookie給i服務(wù)器,不過前提是瀏覽器必須提前設(shè)置了啟用Cookie。
Cookie的key/value鍵和值可以由服務(wù)端自行定義,這樣服務(wù)端可知道對(duì)應(yīng)用戶是否合法以及是否需要重新登錄等。服務(wù)器可設(shè)置或讀取Cookie中包含的信息,借此來維護(hù)用戶更服務(wù)端會(huì)話的狀態(tài)。
Cookie是HTTP協(xié)議頭的一部分,用戶瀏覽器和服務(wù)器之間傳遞數(shù)據(jù)。當(dāng)客戶端第一次向服務(wù)器發(fā)起請(qǐng)求時(shí),服務(wù)端會(huì)創(chuàng)建一個(gè)Cookie,并通過HTTP響應(yīng)頭中的Set-Cookie屬性將Cookie信息返回給客戶端,并通知客戶端保存。

Cookie的傳遞流程
- 客戶端首次發(fā)送請(qǐng)求,此時(shí)請(qǐng)求報(bào)文中沒有Cookie信息。
GET /index HTTP/1.1
Host: www.baidu.com
- 服務(wù)端收到請(qǐng)求后生成Cookie信息,同時(shí)發(fā)送響應(yīng)報(bào)文。
HTTP/1.1 200 OK
Server: Apache
<Set-Cookie: sid=57111807181018; path=/;expires=Web,10-OCT-12 07:12:20 GMT>
Content-Type: text/plain; charset=UTF-8
- 客戶端再次向服務(wù)端發(fā)起請(qǐng)求報(bào)文,此時(shí)會(huì)自動(dòng)發(fā)送保存的Cookie信息。
GET /image/ HTTP1.1
HOST: www.baidu.com
Cookie: sid=57111807181018
Cookie中主要包含NAME(名稱)、path(路徑)、domain(域名)、expires(有效期)、max-age(過期時(shí)間)等屬性。
Cookie是針對(duì)單個(gè)域名domain的,因此不同域名之間的Cookie是相互獨(dú)立的。
通過設(shè)置Cookie的maxAge屬性可以設(shè)置Cookie的過期時(shí)間,若不設(shè)置maxAge則被成為會(huì)話Cookie,會(huì)話Cookie的生命周期從瀏覽器打開到關(guān)閉為止,主要關(guān)閉瀏覽器窗口,會(huì)話Cookie就會(huì)消失。會(huì)話Cookie一般保存在內(nèi)存而非磁盤。
通過設(shè)置過期時(shí)間(setMaxAge(606024)),瀏覽器會(huì)將Cookie保存在本地磁盤中,對(duì)于關(guān)閉后重新打開的瀏覽器,這些Cookie依舊有效。
Cookie過期時(shí)間的設(shè)置方式
cookie.setMaxAge(0); //不記錄Cookie
cookie.setMaxAge(-1);//會(huì)話級(jí)Cookie,關(guān)閉瀏覽器后立即失效。
cookie.setMaxAge(60 * 60);//過期時(shí)間設(shè)置為1小時(shí)
Go Cookie
Go語言標(biāo)準(zhǔn)庫net/http中定義了Cookie的數(shù)據(jù)結(jié)構(gòu),用于表示一個(gè)出現(xiàn)在HTTP響應(yīng)頭Set-Cookie的值或HTTP請(qǐng)求頭中Cookie的值。
Cookie的數(shù)據(jù)結(jié)構(gòu)
type Cookie struct{
Name string
Value string
Path string
Domain string
Expires time.Time
RawExpires string
MaxAge int
Secure bool
HttpOnly bool
Raw string
Unparsed []string
}
| 字段 | 描述 |
|---|---|
| Expires | Cookie過期時(shí)間,使用絕對(duì)時(shí)間,比如2020/12/21 00:00:00。 |
| MaxAge | Cookie的最大生存秒數(shù)時(shí)長,使用相對(duì)時(shí)間,比如300秒。 |
| Secure | Cookie是否需要安全傳輸,當(dāng)為真時(shí)表示只有HTTPS才會(huì)傳輸該Cookie。 |
| HttpOnly | Cookie是否能夠被JavaScripit讀取其值 |
| Unparsed | 表示未解析的鍵值對(duì)的原始文本 |
沒有設(shè)置Expires字段的Cookie稱為會(huì)話Cookie或臨時(shí)Cookie,這種Cookie在瀏覽器窗口關(guān)閉時(shí)會(huì)自動(dòng)刪除。設(shè)置了Expires字段的Cookie稱為持久Cookie,這種Cookie會(huì)一直存在,直到指定時(shí)間到期或手動(dòng)刪除。
Expires和MaxAge都可以用于設(shè)置Cookie的過期時(shí)間,Expires字段設(shè)置的Cookie在指定時(shí)間點(diǎn)過期,MaxAge字段設(shè)置的是Cookie自創(chuàng)建之后能夠存活多少秒。雖然HTTP 1.1中廢棄了Expires并推薦使用MaxAge代替,但幾乎所有瀏覽器仍然支持Expires。微軟的IE6/IE7/IE8并不支持MaxAge。為了更好地移植性,優(yōu)先推薦使用Expires。
最大存活期MaxAge取值范圍
| MaxAge | 描述 |
|---|---|
| 0 | 表示尚未設(shè)置Max-Age屬性 |
| <0 | 表示立即刪除Cookie,等價(jià)于Max-Age:0。 |
| >0 | 表示存在Max-Age屬性,且單位為秒。 |
獲取Cookie
Request對(duì)象中擁有兩個(gè)獲取Cookie的方法和一個(gè)添加Cookie的方法
- 解析并返回請(qǐng)求的Cookie頭設(shè)置的所有Cookie
func (r *Request) Cookies() []*Cookie
- 返回請(qǐng)求中名為
name的Cookie,若未找到則會(huì)返回nil和ErrNoCookie。
func (r *Request) Cookie(name string) (*Cookie, error)
例如:
cookie := http.Cookie{Name:"username", Value:"junchow", Expires:expiration}
- 添加Cookie可通過
AddCookie方法向請(qǐng)求中添加一個(gè)Cookie
func (r *Request) AddCookie(c *Cookie)
設(shè)置Cookie
Go語言中設(shè)置Cookie是通過net/http標(biāo)準(zhǔn)包中的SetCookie函數(shù)實(shí)現(xiàn)的,它會(huì)在responseWriter的頭域中添加Set-Cookie頭,其值為cookie。
http.SetCookie(responseWriter, &cookie)
例如:在HTTP響應(yīng)中通過設(shè)置Set-Cookie頭新增Cookie并將其發(fā)送給客戶端瀏覽器
package main
import (
"fmt"
"net/http"
"time"
)
func indexHandler(responseWriter http.ResponseWriter, request *http.Request) {
//獲取Cookie
cookie,err := request.Cookie("sessionid")
if cookie == nil && err != nil {
expires := time.Now().Add(time.Hour)
//設(shè)置Cookie
cookie := &http.Cookie{
Name:"sessionid",
Value:"1jl3dndo0ex82kal",
MaxAge:60*60,
Expires: expires,
Domain:"127.0.0.1:9090",
Path:"/",
HttpOnly:true,
}
http.SetCookie(responseWriter, cookie)
}else{
//cookie:(*http.Cookie)(nil), err:http: named cookie not present
fmt.Printf("cookie:%#v, err:%v\n", cookie, err)
}
responseWriter.Write([]byte("hello"))
}
func main() {
http.HandleFunc("/", indexHandler)
http.ListenAndServe("0.0.0.0:9090", nil)
}
查看請(qǐng)求
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,zh-TW;q=0.7
Cache-Control: max-age=0
Connection: keep-alive
Cookie: sessionid=1jl3dndo0ex82kal
Host: 127.0.0.1:9090
sec-ch-ua: "Chromium";v="88", "Google Chrome";v="88", ";Not A Brand";v="99"
sec-ch-ua-mobile: ?0
Sec-Fetch-Dest: document
Sec-Fetch-Mode: navigate
Sec-Fetch-Site: none
Sec-Fetch-User: ?1
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.104 Safari/537.36
例如:直接通過HTTP響應(yīng)頭Set-Cookie來設(shè)置Cookie
package main
import (
"fmt"
"net/http"
"net/url"
)
func indexHandler(responseWriter http.ResponseWriter, request *http.Request) {
cookie := http.Cookie{
Name:"account",
Value:url.QueryEscape("junchow"),
HttpOnly:true,
}
responseWriter.Header().Add("Set-Cookie", cookie.String())
fmt.Fprintln(responseWriter, "hello world")
}
func main() {
http.HandleFunc("/", indexHandler)
http.ListenAndServe("0.0.0.0:9090", nil)
}
查看HTTP響應(yīng)頭
Content-Length: 12
Content-Type: text/plain; charset=utf-8
Date: Fri, 22 Jan 2021 19:44:31 GMT
Set-Cookie: account=junchow; HttpOnly