gin原始碼解讀3-gin牛逼的context
Gin封裝的最好的地方就是context和對response的處理. github的README的介紹,基本就是對這兩個東西的解釋. 本篇文章主要解釋context的使用方法, 以及其設計原理
為什麼要將Request的處理封裝到Context中
在閱讀gin的原始碼時, 請求的處理是使用type HandlerFunc func(*Context)來處理的. 也就是
func(context *gin.Context) {
context.String(http.StatusOK, "some post")
}
引數是gin.Context, 但是檢視原始碼發現其實gin.Context在整個框架處理的地方只有下面這段:
func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) {
c := engine.pool.Get().(*Context)
c.writermem.reset(w)
c.Request = req
c.reset()
engine.handleHTTPRequest(c)
engine.pool.Put(c)
}
那為什麼還要利用Context來處理呢. gin的context實現了的context.Context Interface.
經過檢視context.Context相關資料, Context的最佳運用場景就是對Http的處理. 封裝成Conetxt另外的好處就是WithCancel, WithDeadline, WithTimeout, WithValue這些context包衍生的子Context就可以直接來使用.
gin.Context設計
這個模組比較簡單, 就是從gin.Context中Set Key-Value, 以及各種個樣的Get方法, 如GetBool, GetString等
實現這些功能也很簡單, 其實就是一個map
// Keys is a key/value pair exclusively for the context of each request.
Keys map[string]interface{}
Input Data
這個模組相當重要了, gin的README基本上都在介紹這個模組的用法.
Param (我自己的叫法: 路由變數)
gin的標準叫法是Parameters in path. restful風格api如/user/john, 這個路由在gin裡面是/user/:name, 要獲取john就需要使用Param函式
name := context.Param("name")
這個方法實現也很簡單, 就是在tree.go裡面根據路由相關規則解析出來然後賦值給gin.Context的Params.
Query
/welcome?firstname=Jane&lastname=Doe這樣一個路由, first, last即是Querystring parameters, 要獲取他們就需要使用Query相關函式.
c.Query("first") // Jane
c.Query("last") // Doe
當然還有其他相關函式:
QueryMap
DefaultQuery 這個預設值的實現更加簡單, 當QueryString中不包含這個值, 直接返回填入的值
這些方法是的實現是利用net/http的Request的方法實現的
PostForm
FormFile
對於檔案相關的操作, 一般生產情況下不建議這樣使用, 因為把檔案上傳到伺服器磁碟, 還得磁碟相關的監控. 我覺得最好利用雲服務商相關的物件儲存, 如:阿里雲OSS, 七牛雲物件儲存, AWS的物件儲存等來做檔案的相關操作
Bind
內建的有json, xml, protobuf, form, query, yaml. 這些Bind極大的減少我們自己去解析各種個樣的資料格式, 提高我們的開發速度
Bind的實現都在gin/binding裡面. 這些內建的Bind都實現了Binding介面, 主要是Bind()函式.
context.BindJSON() 支援MIME為application/json的解析
context.BindXML() 支援MIME為application/xml的解析
context.BindYAML() 支援MIME為application/x-yaml的解析
context.BindQuery() 只支援QueryString的解析, 和Query()函式一樣
context.BindUri() 只支援路由變數的解析
Context.Bind() 支援所有的型別的解析, 這個函式儘量還是少用(當QueryString, PostForm, 路由變數在一塊同時使用時會產生意想不到的效果), 目前測試Bind不支援路由變數的解析, Bind()函式的解析比較複雜, 這部分程式碼後面再看
Response
對Header的支援
- Header
- GetHeader
這裡的Header是寫到Response裡面的Header. 對於客戶端發的請求的Header可以通過context.Request.Header.Get("Content-Type")獲取
context.Header("jwt", "123456") // 寫到Response的Header
clientIp := context.GetHeader("X-Forwarded-For") // 從Request中獲取Header
fmt.Println(clientIp)
Cookie
提供對session, cookie的支援
[cookie和session](https://www.cnblogs.com/mayanan/p/15672208.html)
render
做api常用到的其實就是gin封裝的各種render. 目前支援的有:
func (c *Context) JSON(code int, obj interface{})
func (c *Context) Protobuf(code int, obj interface{})
func (c *Context) YAML(code int, obj interface{}) ...
當然我們可以自定義渲染, 只要實現func (c *Context) Render(code int, r render.Render)即可.
這裡我們常用的是一個方法是: gin.H{"error": 111}. 這個結構相當實用, 各種render都支援. 其實這個結構很簡單就是type H map[string]interface{}, 當我們要從map轉換各種各樣結構時, 不妨參考gin這裡的程式碼
Context說到這裡基本就說完了, 這裡介紹的方法都是開發中特別實用的方法. context的程式碼實現也特別有條理, 建議可以看看這部分程式碼