Golang之Gin框架源碼解讀——第四章

Gin是使用Go語言編寫的高性能的web服務框架,根據官方的測試,性能是httprouter的40倍左右。要使用好這套框架呢,首先我們就得對這個框架的基本結構有所瞭解,所以我將從以下幾個方面來對Gin的源碼進行解讀。

  • 第一章:Gin是如何儲存和映射URL路徑到相應的處理函數的
  • 第二章:Gin中間件的設計思想及其實現
  • 第三章:Gin是如何解析客戶端發送請求中的參數的
  • 第四章:Gin是如何將各類格式(JSON/XML/YAML等)數據解析返回的

Gin Github官方地址

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})
}

這三個方法,都是調用了ContextRender方法,那麼我們繼續追蹤:

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,這個方法就不剖析了,涵蓋點有點多,這個方法的大致作用就是根據文件的信息,設置了響應頭部信息,然後再將文件輸出到響應流中

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章