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