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這里的代碼
