gin是go语言实现的一个http web框架,是一个类Martini的API且比其执行快40倍,gin支持插件模式加载在项目中需要的功能。在使用gin框架构建项目的时候,我们使用操作最多的就是request、response,gin为了方便使用设计了巧妙的context。
下面我们看一个简单的基于gin构建的输出json数据的接口。
func main() {
r := gin.Default()
r.GET("/ping", func(c *gin.Context) {
c.JSON(200, gin.H{
"message": "pong",
})
})
fmt.Printf("come on")
r.Run() // listen and serve on 0.0.0.0:8080
}
运行后,通过浏览器访问127.0.0.1/ping,我们看到输出
{
message: "pong"
}
使用gin构建api服务就是如此简单。下面我们剖析一下gin的重要组成和一个http请求到达,解析到输出结果的整个流程。
Engine
从官方给出的例子的第一行代码说开来:r := gin.Default()
通过这一行代码,我们初始换了一个Engine,Engine是整个gin的核心入口,承担着路由、中间件、参数等设置,是对http server的封装。
通过gin.Default(),初始化一个默认的engine实例,它包含了日志和错误自恢复插件。
// Default returns an Engine instance with the Logger and Recovery middleware already attached.
func Default() *Engine {
debugPrintWARNINGDefault()
engine := New()
//加载两个插件
engine.Use(Logger(), Recovery())
return engine
}
在Engine默认初始化方面中,调用的New()方法,我看看一下这个方法做了哪些工作:
// New returns a new blank Engine instance without any middleware attached.
// By default the configuration is:
// - RedirectTrailingSlash: true
// - RedirectFixedPath: false
// - HandleMethodNotAllowed: false
// - ForwardedByClientIP: true
// - UseRawPath: false
// - UnescapePathValues: true
func New() *Engine {
debugPrintWARNINGNew()
engine := &Engine{
RouterGroup: RouterGroup{
Handlers: nil,
basePath: "/",
root: true,
},
FuncMap: template.FuncMap{},
RedirectTrailingSlash: true,
RedirectFixedPath: false,
HandleMethodNotAllowed: false,
ForwardedByClientIP: true,
AppEngine: defaultAppEngine,
UseRawPath: false,
UnescapePathValues: true,
MaxMultipartMemory: defaultMultipartMemory,
trees: make(methodTrees, 0, 9),
delims: render.Delims{Left: "{{", Right: "}}"},
secureJsonPrefix: "while(1);",
}
engine.RouterGroup.engine = engine
engine.pool.New = func() interface{} {
return engine.allocateContext()
}
return engine
}
在上面也提到,Engine负责一系列的设置工作,通过初始化源代码可以看到,通过Engine可以进行很多默认参数的设置,在实际的项目中可以根据实际情况都默认参数进行合理设置。
比如,在使用gin通过模板进行开发时,gin默认的delims为{{xxx}},这与vue的相冲突,可以通过下面代码来改变默认配置
r.Delims("{[{", "}]}")
在示例代码中,直接调用了r.Get 这个方法,其实Engine是实现了RouterGroup的,所以我们可以通过Engine直接进行路由的配置。gin对请求路径是使用了树形结构组织的,RouterGroup是对请求路径路由树的封装,项目中所有的路由规则都是有其来组织的。每次调用Get、Post等路由方法时,都会向路有树中添加注册路由,
// GET is a shortcut for router.Handle("GET", path, handle).
func (group *RouterGroup) GET(relativePath string, handlers ...HandlerFunc) IRoutes {
return group.handle("GET", relativePath, handlers)
}
func (group *RouterGroup) handle(httpMethod, relativePath string, handlers HandlersChain) IRoutes {
absolutePath := group.calculateAbsolutePath(relativePath)
handlers = group.combineHandlers(handlers)
group.engine.addRoute(httpMethod, absolutePath, handlers)
return group.returnObj()
}
通过RouterGroup可以很方便的对接口进行分组,比如将请求接口分为不通的版本,以实现平滑的接口升级。
v1 := r.Group("/v1")
{
v1.GET("/ping",func(c *gin.Context){
c.JSON(200, gin.H{
"message": " v1 pong",
})
})
}
Context
在上面分析Engine初始化代码时,有这样一段代码:
engine.RouterGroup.engine = engine
engine.pool.New = func() interface{} {
return engine.allocateContext()
}
这段代码是对gin中另一个重要的对象Context做池化的初始化代码,通过对Context进行池化处理,可以提高很大的效率。Engine作为gin的核心和入口,底层是对http server的封装,通过Engine实现的ServeHttp方法可以看到在此处对Context进行了初始化操作
// ServeHTTP conforms to the http.Handler interface.
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)
}
下面我们针对gin.Context做详细的讲解。打开Context的源码我们看到官方对Context的描述:Context是gin框架最重要的部分,通过context可以在组件间传递变量,管理路由,验证请求的json数据和渲染输出json数据。Context贯穿着整个的http请求,保存请求流程的上下文信息。
首先我们看一下Context中定义的变量,
type Context struct {
writermem responseWriter
Request *http.Request
Writer ResponseWriter
Params Params
handlers HandlersChain
index int8
fullPath string
engine *Engine
// Keys is a key/value pair exclusively for the context of each request.
Keys map[string]interface{}
// Errors is a list of errors attached to all the handlers/middlewares who used this context.
Errors errorMsgs
// Accepted defines a list of manually accepted formats for content negotiation.
Accepted []string
// queryCache use url.ParseQuery cached the param query result from c.Request.URL.Query()
queryCache url.Values
// formCache use url.ParseQuery cached PostForm contains the parsed form data from POST, PATCH,
// or PUT body parameters.
formCache url.Values
}
Context对请求的参数、清理路径都进行了封装,可以很方便的直接或间接的操作http交互中的各种变量。
同时,Context也内置了多种响应渲染形式,在项目构建过程中可以很方便的通过一行代码就完成json、xml等数据格式的输出。
// Status sets the HTTP response code.
func (c *Context) Status(code int) {...}
// Header is a intelligent shortcut for c.Writer.Header().Set(key, value).
// It writes a header in the response.
// If value == "", this method removes the header `c.Writer.Header().Del(key)`
func (c *Context) Header(key, value string) {...}
// GetHeader returns value from request headers.
func (c *Context) GetHeader(key string) string {...}
.....
// SetCookie adds a Set-Cookie header to the ResponseWriter's headers.
// The provided cookie must have a valid Name. Invalid cookies may be
// silently dropped.
func (c *Context) SetCookie(name, value string, maxAge int, path, domain string, secure, httpOnly bool) {...}
// PureJSON serializes the given struct as JSON into the response body.
// PureJSON, unlike JSON, does not replace special html characters with their unicode entities.
func (c *Context) PureJSON(code int, obj interface{}) {...}
// XML serializes the given struct as XML into the response body.
// It also sets the Content-Type as "application/xml".
func (c *Context) XML(code int, obj interface{}) {...}
// YAML serializes the given struct as YAML into the response body.
func (c *Context) YAML(code int, obj interface{}) {...}
...
所有渲染组件最后还是通过http writer进行输出,
// Render writes the response headers and calls render.Render to render data.
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)
}
}
HTML模板渲染
gin内置了对html模板的渲染,使用方式也很简单,只需要指定模板的存放路径即可,
func main() {
router := gin.Default()
router.LoadHTMLGlob("templates/*")
//router.LoadHTMLFiles("templates/template1.html", "templates/template2.html")
router.GET("/index", func(c *gin.Context) {
c.HTML(http.StatusOK, "index.tmpl", gin.H{
"title": "Main website",
})
})
router.Run(":8080")
}
emplates/index.tmpl
<html>
<h1>
{{ .title }}
</h1>
</html>
除了上面调到的各项超强的功能为,gin也为开发者提供静态文件服务、请求参数验证、文件上传等其他强大的功能,可以通过访问
https://github.com/gin-gonic/gin
去了解认识gin。
本节完。