類載入器 虛擬機設計團隊把類載入階段中的“通過一個類的全限定名來獲取描述此類的二進位位元組流”這個動作放到Java虛擬機外部去實現,以便讓應用程式自己決定如何去獲取所需要的類。實現這個動作的代碼模塊稱為“類載入器”。 類載入器可以說是Java語言的一項創新,也是Java語言流行的重要原因之一,它最初是 ...
1. 引言
本文將從Web應用程式處理請求時需要用戶信息,同時HTTP又是無狀態協議這個矛盾點出發。從該問題出發,簡單描述瞭解決該問題的Token
機制,進而引出Cookie
的實現方案。
基於此我們將詳細描述Cookie
的規範,然後詳細描述具體的實現方式,進一步描述Gin
框架對Cookie
操作提供的API
,最終提供了一個詳細的代碼實現。
我們還將詳細描述Gin
框架提供API
的實現原理,幫助用戶更好得使用這兩個API
。
2. 問題引入
在 如何使用Gin搭建一個Go Web應用程式 一文中,我們已經瞭解瞭如何使用Gin
搭建一個簡單的Web應用程式。然而,在現實的Web應用程式中,大部分功能都是需要用戶的身份信息才能處理。舉例來說,在一個視頻網站查看用戶最近觀看記錄,如果缺少用戶身份信息,此時將無法對請求進行處理。
但是HTTP協議的設計,是無狀態的,也就是每次請求都是獨立的。基於此,應該有一套機制,能夠在用戶身份認證成功後,給用戶分配一個Token
,後續用戶在每次請求時,都攜帶上該Token
,使得伺服器能夠從請求中獲取用戶信息,解決HTTP無狀態問題。大概流程如下:
上面流程中,需要服務端按照某個協議,向客戶端返回Token
;客戶端通過該協議,成功解析出服務端返回的Token
,然後在每次請求中攜帶該Token
。然後伺服器端再根據協議,從中解析出Token
信息,獲取請求用戶信息。
當前常用的有Cookie
,Jwt
,OAuth2.0
等標準,其各有優缺點。其中Cookie
是一種存儲在客戶端瀏覽器中的數據。服務端可以通過設置HTTP響應頭將Token
存儲在Cookie
當中,併在後續請求中從Cookie
中讀取Token
。而JWT
則是一種基於JSON格式的安全令牌,可用於在客戶端和服務端之間傳遞信息。
之前,我們在 一文讀懂Cookie 中,已經瞭解Cookie
的相關內容。基於此,我們這次使用Cookie
來實現上述所說的流程,按照Cookie
的規範來實現Token
的返回和請求中Token
的解析。
3. 實現
3.1 Cookie規範說明
這裡我們對HTTP協議中的Cookie
規範再補充一下,這裡分為兩部分,第一部分是服務端如何向客戶端發送 Cookie
,第二部分是客戶端向服務端發送請求時如何攜帶Cookie
信息。
對於服務端向客戶端發送Cookie
的手段,HTTP協議存在一個Set-Cookie
的頭部欄位,伺服器可以通過Set-Cookie
頭部欄位將Cookie
發送給客戶端。例如下麵這個例子:
Set-Cookie: username=abc; expires=Wed, 09 Jun 2023 10:18:14 GMT; path=/
在這個例子中,伺服器設置了一個名為username
的Cookie
,它的值是abc
,過期時間是2023年6月9日,路徑為/
。瀏覽器在接收到該Cookie
時,便將其保存起來。
客戶端請求時攜帶Cookie
的方式,則是通過HTTP協議中的Cookie
頭部欄位,客戶端可以通過該頭部欄位攜帶信息給伺服器端,比如下麵這個例子:
Cookie: sessionid=1234
在這個例子中,HTTP請求中攜帶了一個name
為sessionid
,value
為 1234
的 Cookie
。當伺服器端接收到該HTTP
請求後,從中解析出Cookie
的信息,然後基於此實現後續的流程。
3.2 實現說明
回看上述流程,主要分為兩個大部分: 客戶端和伺服器端。在客戶端部分,關鍵任務包括保存瀏覽器返回的Cookie
信息以及在請求時攜帶Cookie
信息給伺服器。對於伺服器端,則是在通過身份校驗之後,能夠按照規範客戶端返回Cookie
,併在接收到請求時,能夠正確解析出請求中的 Cookie
信息,識別出用戶信息。
對於客戶端部分,在瀏覽器接收到HTTP響應時,如果響應體中有Set-Cookie
頭部欄位,瀏覽器會自動保存Cookie
信息;客戶端發起請求時,需要將 Cookie
信息傳遞給伺服器。此時瀏覽器會自動攜帶通過校驗的Cookie
。如果通過校驗,此時會在HTTP請求頭中攜帶Cookie
信息給服務端,下麵是一個大概的校驗流程:
在整個流程中,客戶端保存Token
信息和在請求時攜帶Token
信息這兩部分工作,瀏覽器已經幫我們實現了。剩下的工作集中在服務端的,主要涉及按照Cookie
的規範給客戶端返回用戶標識,併在接收到客戶端請求時從HTTP請求中讀取Cookie
以獲取到用戶的信息。與Cookie
相關的詳細內容可以參考文章一文讀懂Cookie。
因此下麵我們需要做的兩件事情,其一,伺服器需要按照Cookie
的規範往客戶端發送Cookie
的內容;其次,伺服器在處理請求時,需要從HTTP請求頭中讀取出Cookie
的信息,成功識別用戶身份。
Gin
框架中提供了一些API
,能夠幫助我們在服務端,按照Cookie
規範給客戶端發送Cookie
信息,同時也有API
能夠幫助我們解析Cookie
的信息。下麵我們先來瞭解相關的API
,然後再基於這些API
,搭建一個能夠自動識別用戶信息的 Web
應用程式。
3.3 API說明
3.3.1 SetCookie
gin.Context
對象中的 SetCookie
方法用於向客戶端返迴響應的同時,在Set-Cookie
頭部攜帶Cookie
信息。下麵是該方法的詳細說明:
func (c *Context) SetCookie(name, value string, maxAge int, path, domain string, secure, httpOnly bool)
name
:cookie 的名稱(必須)。value
:cookie 的值(必須)。maxAge
:cookie 的過期時間,以秒為單位。如果為負數,則表示會話 cookie(在瀏覽器關閉之後刪除),如果為零,則表示立即刪除 cookie(可選,預設值為-1)。path
:cookie 的路徑。如果為空字元串,則使用當前請求的 URI 路徑作為預設值(可選,預設值為空字元串)。domain
:cookie 的功能變數名稱。如果為空字元串,則不設置功能變數名稱(可選,預設值為空字元串)。secure
:指定是否僅通過 HTTPS 連接發送 cookie。如果為 true,則僅通過 HTTPS 連接發送 cookie;否則,使用 HTTP 或 HTTPS 連接都可以發送 cookie(可選,預設值為 false)。httpOnly
:指定 cookie 是否可通過 JavaScript 訪問。如果為 true,則無法通過 JavaScript 訪問 cookie;否則,可以通過 JavaScript 訪問 cookie(可選,預設值為 true)。
在處理函數中,通過調用SetCookie
方法,便可以向客戶端發送一個HTTP cookie。這裡舉一個代碼示例,來幫助讀者更好得理解該API
,下麵舉一個代碼示例,如下:
func main() {
router := gin.Default()
router.GET("/set-cookie", func(c *gin.Context) {
c.SetCookie("user", "john", 3600, "/", "", false, true)
c.String(http.StatusOK, "cookie set successfully")
})
router.Run(":8080")
}
在這個示例中,使用 SetCookie
方法設置一個名為user
的 cookie。這個 cookie 的值是john
,在 1 小時後過期。該代碼還設置了路徑為“/”以及HttpOnly屬性為true。
下麵啟動該伺服器,客戶端向服務端發送請求,請求路徑為/set-cookie
,上面的處理函數將會被執行,然後我們來看其響應內容:
# 1. 發送請求
curl -i http://localhost:8080/set-cookie
# 2. 返迴響應
HTTP/1.1 200 OK
Content-Type: text/plain; charset=utf-8
Set-Cookie: user=john; Path=/; Max-Age=3600; HttpOnly
Date: Sun, 20 Aug 2023 07:39:15 GMT
Content-Length: 23
cookie set successfully
查看上面第6行,可以看到,我們通過SetCookie
方法,成功設置了一個Cookie
,然後以在HTTP頭部的形式返回。
3.1.2 Cookie方法
往客戶端返回Cookie
後,瀏覽器會將Cookie
保存起來,然後在下次請求時將Cookie
跟隨請求一起發送給伺服器端。
在HTTP無狀態協議的情況下,我們使用Cookie
來識別用戶信息,此時伺服器端需要正確解析出HTTP 頭部中Cookie
的信息,Gin
框架中的gin.Context
提供了Cookie
方法,方便我們獲取到Cookie
的信息。下麵是該方法的定義說明:
func (c *Context) Cookie(name string) (string, error)
使用Cookie
方法可以獲取指定名稱的Cookie值,如果不存在指定名字的Cookie
,此時將會返回錯誤。下麵給一個簡單示例代碼的說明:
func main() {
router := gin.Default()
// 定義路由
router.GET("/cookie", func(c *gin.Context) {
// 獲取名為 "username" 的 cookie
cookie, err := c.Cookie("username")
if err != nil {
// 如果 cookie 不存在,則返回錯誤信息
c.JSON(http.StatusBadRequest, gin.H{"error": "Bad request"})
return
}
// 在響應中返回 cookie 值
c.JSON(http.StatusOK, gin.H{"username": cookie})
})
router.Run(":8080")
}
在上述示例中,我們定義了一個 /cookie
路由,使用 c.Cookie("username")
方法來獲取名為 username
的 Cookie 值。如果 Cookie 不存在,則返回一個錯誤響應。否則,我們將在響應中返回 Cookie 的值。
下麵我們通過curl
命令來對/cookie
請求,通過 -b
標識來攜帶cookie
值:
# -v, --verbose 這個參數會打開curl的詳細模式,輸出一些額外的信息,包括HTTP請求和響應頭信息。
curl -b -v -b "username=hello cookie;" http://localhost:8080/cookie
下麵我們來看具體的請求體和響應體的內容:
GET /cookie HTTP/1.1
Host: localhost:8080
User-Agent: curl/7.79.1
Accept: */*
Cookie: username=hello cookie;
可以看到,我們請求體攜帶了Cookie
欄位,Cookie
的名稱為 username
,我們前面伺服器端便是嘗試獲取名為 username
的 Cookie,下麵我們看請求的響應體,看是否成功解析了HTTP 請求 Cookie的內容:
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Date: Sun, 20 Aug 2023 08:12:45 GMT
Content-Length: 27
{"username":"hello cookie"}
可以看到,服務端程式通過Cookie
方法成功解析了HTTP請求頭部中Cookie
欄位的值,然後將解析的結果正常返回客戶端。
3.4 代碼實現
下麵我們來搭建一個基於Cookie
實現用戶身份驗證的Web
應用程式,首先需要一個登錄頁面,用於驗證用戶身份信息,驗證通過後,我們將通過Cookie
給客戶端返回一個 Token
。
同時,我們還需要創建一個頁面,需要驗證用戶身份信息,在驗證過程中,我們會檢查用戶請求中是否攜帶Cookie
,同時Cookie
中攜帶的數據是否正確,基於此實現用戶身份的驗證。下麵是一個簡單代碼的示例:
func main() {
route := gin.Default()
route.GET("/login", func(c *gin.Context) {
// HTTP 響應中攜帶 Cookie
// Set cookie {"label": "ok" }, maxAge 30 seconds.
c.SetCookie("label", "ok", 30, "/", "localhost", false, true)
c.String(200, "Login success!")
})
route.GET("/home", func(c *gin.Context) {
// 獲取 name = label 的 Cookie 的 value
if cookie, err := c.Cookie("label"); err == nil {
// 判斷 Cookie的value 是否滿足預期
if cookie == "ok" {
c.JSON(200, gin.H{"data": "Your home page"})
}
} else {
c.JSON(http.StatusForbidden, gin.H{"error": "Forbidden with no cookie"})
}
})
route.Run(":8080")
}
首先是一個/login
請求路由,通過SetCookie
方法給客戶端返回Cookie
,基於此返回用戶Token
。
然後/home
路由的處理,則是通過gin.Context
中Cookie
方法獲取到HTTP請求頭部中Cookie
的信息 ,然後驗證Cookie
中的value是否滿足預期。
這個是一個簡單的代碼示例,比如用戶身份認證機制等,則需要自行完善,這裡不再完整展示。
4. 原理
下麵將簡單描述gin.Context
對象中SetCookie
方法和Cookie
方法的實現原理,幫助讀者更好使用這兩個API
。
4.1 SetCookie方法
SetCookie
方法的實現原理如下,首先,SetCookie
方法會創建一個http.Cookie
對象,並設置其名稱、值、路徑、功能變數名稱、過期時間等屬性。例如,以下代碼創建了一個名為sessionid
的Cookie
:
cookie := &http.Cookie{
Name: "sessionid",
Value: "1234",
Expires: time.Now().Add(24 * time.Hour),
Path: "/",
Domain: "",
Secure: false,
HttpOnly:true,
}
接下來,將上述Cookie
對象轉換為字元串格式,並設置到HTTP響應頭的Set-Cookie
欄位中。代碼實現如下:
func SetCookie(w ResponseWriter, cookie *Cookie) {
if v := cookie.String(); v != "" {
w.Header().Add("Set-Cookie", v)
}
}
這裡第三行將Cookie
存儲到Header
對象當中,Header
是專門用於存儲HTTP響應頭部的信息。調用Add
方法時,會根據指定的Key
,在 Header
對象中查找相應的值列表。如果這個鍵不存在,則會在 Header
對象中創建一個新的值列表;否則,會在已有的值列表末尾添加新的值,大概流程如下:
在返回HTTP響應時,會遍歷Header
對象,填充HTTP響應頭部信息,然後返回給客戶端,比如上面Header
生成的HTTP響應頭部如下:
Set-Cookie: v1
Set-Cookie: v2
Agent: Windows
SetCookie
方法便是通過上述所說流程,將Cookie
的信息設置到HTTP響應體頭部當中去,然後返回給客戶端。
4.2 Cookie方法
在調用 Cookie()
方法時,系統會首先檢查請求頭部中是否包含名為 Cookie
的欄位。如果該欄位不存在,則返回空字元串。
如果請求頭部中包含 Cookie
欄位,同時Cookie
的name
為調用Cookie()
方法指定的值,則系統會解析該欄位並將其轉換為一個 http.Cookie
對象。這個對象包含了所有的 Cookie
屬性,例如名稱、值、路徑、過期時間、功能變數名稱等等。最後,返迴轉換後的http.Cookie
對象中值,大概流程如下:
總的來說,Cookie()
方法的實現原理比較簡單,它只是通過查找 HTTP 請求頭部中的 Cookie 信息,並將其轉換為 http.Cookie
對象來獲取請求中特定 Cookie 值。
5. 總結
在本文中,我們深入探討了Web應用程式在處理用戶信息時所面臨的挑戰,特別是在HTTP協議作為無狀態協議的背景下。我們從這一矛盾出發,介紹瞭解決方案中的Token
機制,並引出了基於Cookie
的實現方案。
我們詳細闡述了Cookie
的規範,包括服務端如何發送Cookie
以及客戶端如何在請求中攜帶Cookie
信息。
我們進一步深入探討了具體的實現方式,並介紹了Gin
框架提供的API
,這些API
使得在服務端按照Cookie
規範發送和解析Cookie變得更加容易。通過一個實際的代碼示例,我們演示瞭如何使用這些API來構建一個基於Cookie實現用戶身份驗證的Web應用程式。
在探討API
的使用之餘,我們也深入剖析了Gin框架提供的API的實現原理,為讀者提供了更深層次的理解。
基於此,完成了對Gin中Cookie支持的介紹,希望對你有所幫助。