Gin
是使用Go語言編寫的高性能的web
服務框架,根據官方的測試,性能是httprouter
的40倍左右。要使用好這套框架呢,首先我們就得對這個框架的基本結構有所瞭解,所以我將從以下幾個方面來對Gin
的源碼進行解讀。
- 第一章:
Gin
是如何儲存和映射URL
路徑到相應的處理函數的 - 第二章:
Gin
中間件的設計思想及其實現 - 第三章:
Gin
是如何解析客戶端發送請求中的參數的 - 第四章:
Gin
是如何將各類格式(JSON/XML/YAML
等)數據解析返回的
Gin是如何將各類格式(JSON/XML/YAML等)數據解析返回的
在看完前面三章之後,我們大致瞭解了Gin
是如何工作、如果接收客戶端請求的,但是對於一個Web
框架還有一個最重要的特性的就是處理完請求後返回相應的數據。圍繞着這一點,我們來看下Gin
是如何解析各類數據結構的。
老規矩,我們還是以官方的示例代碼來逐步分析:
func main() {
router := gin.Default()
router.POST("/something", func(c *gin.Context) {
c.JSON(200, gin.H{
"status": "posted",
"message": "message",
"nick": "nick",
})
c.XML(200,gin.H{
"status": "posted",
"nick": "nick",
})
c.String(http.StatusOK, "hello world")
})
if err := router.Run();err != nil {
log.Println("something error");
}
}
上面這個示例中我們返回了三種常見格式的數據JSON/XML/String
,進入這三個方法我們可以看到:
func (c *Context) JSON(code int, obj interface{}) {
c.Render(code, render.JSON{Data: obj})
}
func (c *Context) XML(code int, obj interface{}) {
c.Render(code, render.XML{Data: obj})
}
func (c *Context) String(code int, format string, values ...interface{}) {
c.Render(code, render.String{Format: format, Data: values})
}
這三個方法,都是調用了Context
的Render
方法,那麼我們繼續追蹤:
func (c *Context) Render(code int, r render.Render) {
c.Status(code)
if !bodyAllowedForStatus(code) {
r.WriteContentType(c.Writer)
c.Writer.WriteHeaderNow()
return
}
if err := r.Render(c.Writer); err != nil {
panic(err)
}
}
func (c *Context) Status(code int) {
c.Writer.WriteHeader(code)
}
可以看到這個方法就是一個標準的代理模式
,Render
負責傳入代理對象,COntext
負責執行代理對象的render.Render
接口方法,來向c.Writer
寫出數據。我們先來看一下render.Render
接口的原型:
type Render interface {
//負責寫出用戶指定的數據
Render(http.ResponseWriter) error
//負責向頭部寫出ContentType的值
WriteContentType(w http.ResponseWriter)
}
我們以JSON
爲例,來看一下該數據類型的實現方法是怎樣的:
func (r JSON) Render(w http.ResponseWriter) (err error) {
if err = WriteJSON(w, r.Data); err != nil {
panic(err)
}
return
}
//這個就是JSON格式的ContentType
var jsonContentType = []string{"application/json; charset=utf-8"}
var jsonpContentType = []string{"application/javascript; charset=utf-8"}
var jsonAsciiContentType = []string{"application/json"}
func WriteJSON(w http.ResponseWriter, obj interface{}) error {
writeContentType(w, jsonContentType)
//這裏調用的是GO SDK中的序列化函數獲取的字節數組
jsonBytes, err := json.Marshal(obj)
if err != nil {
return err
}
_, err = w.Write(jsonBytes)
return err
}
//這個函數就是將指定的ContentType,設置到響應的頭部
func writeContentType(w http.ResponseWriter, value []string) {
header := w.Header()
if val := header["Content-Type"]; len(val) == 0 {
header["Content-Type"] = value
}
}
//注意這個函數和調用那個函數的首字母大小寫
func (r JSON) WriteContentType(w http.ResponseWriter) {
writeContentType(w, jsonContentType)
}
整個過程很簡單,沒有什麼需要多說的,總體概況就兩點:寫出字節流
和寫出頭部信息
Gin框架支持的數據格式
要想查看Gin
支持哪些數據格式,只需要哪些數據格式實現了Render
接口,不過Gin
官方已經在Render
包中爲我們羅列出來了:
var (
_ Render = JSON{}
_ Render = IndentedJSON{}
_ Render = SecureJSON{}
_ Render = JsonpJSON{}
_ Render = XML{}
_ Render = String{}
_ Render = Redirect{}
_ Render = Data{}
_ Render = HTML{}
_ HTMLRender = HTMLDebug{}
_ HTMLRender = HTMLProduction{}
_ Render = YAML{}
_ Render = Reader{}
_ Render = AsciiJSON{}
_ Render = ProtoBuf{}
)
使用方法也很明瞭:
router.POST("/upload", func(c *gin.Context) {
//假如我要返回json數據調用JSON方法即可
c.JSON(200, gin.H{
"status": "posted",
"message": "message",
"nick": "nick",
})
//假如我要返回XML數據調用XML方法即可
c.XML(200,gin.H{
"status": "posted",
"nick": "nick",
})
//其他類似
})
除了上面的常用數據格式之外,還有就是返回文件流,這個調用的是c.File("filepath")
,這個方法的原型是這樣的:
// 這個功能是由http包提供的
func (c *Context) File(filepath string) {
http.ServeFile(c.Writer, c.Request, filepath)
}
不僅是c.File()
方法,而且靜態文件路由router.Static("/assets", "./assets")
也是最終調用的http.ServeFile
,這個方法就不剖析了,涵蓋點有點多,這個方法的大致作用就是根據文件的信息,設置了響應頭部信息,然後再將文件輸出到響應流中。