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,这个方法就不剖析了,涵盖点有点多,这个方法的大致作用就是根据文件的信息,设置了响应头部信息,然后再将文件输出到响应流中


版权声明:本文为qq_41115702原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。