Gin
封裝的最好的地方就是context
和對response的處理。本篇文章主要解釋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
主要由下面幾部分組成:
Metadata Management (我自己叫法:Key-Value)
這個模塊比較簡單, 就是從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 := c.Param("name")
這個方法實現也很簡單, 就是在tree.go
里面根據路由相關規則解析出來然后賦值給gin.Context
的Params
.
handlers, params, tsr := root.getValue(path, c.Params, unescape)
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
對於POST
, PUT
等這些能夠傳遞參數Body的請求, 要獲取其參數, 需要使用PostForm
POST /user/1 { "name":manu, "message":this_is_great }
name := c.PostForm("name") message := c.PostForm("message")
其他相關函數
- DefaultPostForm
這些相關的方法是實現還是利用net/http
的Request
的方法實現的
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")
獲取
Cookie
提供對session, cookie的支持
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這里的代碼