浅析gin
一个普通的gin应用
package main
import "github.com/gin-gonic/gin"
type person struct {
Name string `form:"name"`
}
func main(){
r := gin.Default() // 内含请求日志middleware
hello := r.Group("/hello") // 路由命名空间
hello.GET("/world", func(c *gin.Context){ // 路由
p := person{}
c.Bind(&p) // 参数绑定
c.JSON(200, gin.H{ // 响应
"msg": "hello, " + p.Name,
})
})
r.Run() // 启动
}
// http://localhost:8080/hello/world?name=jack
// {"msg":"hello, jack"}
初始化
可以使用New初始化一个空白的服务器。如果使用Default的话,会在此基础上,预置Logger和Recovery两个中间件
func New() *Engine {
debugPrintWARNINGNew()
engine := &Engine{
RouterGroup: RouterGroup{
Handlers: nil,
basePath: "/",
root: true,
},
// ...
}
engine.RouterGroup.engine = engine
engine.pool.New = func() interface{} {
return engine.allocateContext()
}
return engine
}
func Default() *Engine {
debugPrintWARNINGDefault()
engine := New()
engine.Use(Logger(), Recovery())
return engine
}
中间件
可以通过RouterGroup.Use(或者Engine.Use)方法设置middleware
func (group *RouterGroup) Use(middleware ...HandlerFunc) IRoutes {
group.Handlers = append(group.Handlers, middleware...)
return group.returnObj()
}
因为RouterGroup.handle(GET、POST……会调用到的)会在设置路由时,复制当前RouterGroup已定义过的middleware,所以后来的Use所增加的middleware不会影响前面的handle
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()
}
middleware函数一般需要调用context.Next或context.AbortWithStatus。
Next会调用下层middleware。如果某层没有调用Next也没有调用Abort,那么上层也会调用更下一层的middleware。没有调用Next的层,无法获得下层结果。
Abort用于提早返回,不执行下层middleware和handler。
func (c *Context) Next() {
c.index++
for c.index < int8(len(c.handlers)) {
c.handlers[c.index](c)
c.index++
}
}
func (c *Context) AbortWithStatus(code int) {
c.Status(code)
c.Writer.WriteHeaderNow()
c.Abort()
}
func (c *Context) Abort() {
c.index = abortIndex
}
除了使用RouterGroup.Use,还可以在定义Group(命名空间)和定义handler(路由终端)时,增加middleware,他们实际上都是调用RouterGroup.combineHandlers
命名空间
一个Engine会有一个默认的Group,路由是"/"。调用Engine.Group(其实就是RouterGroup.Group),会返回一个新的RouterGroup
type RouterGroup struct {
Handlers HandlersChain
basePath string
engine *Engine
root bool
}
func (group *RouterGroup) Group(relativePath string, handlers ...HandlerFunc) *RouterGroup {
return &RouterGroup{
Handlers: group.combineHandlers(handlers),
basePath: group.calculateAbsolutePath(relativePath),
engine: group.engine,
}
}
新Group的middlewares会继承原Group的middlewares,,假设原有m1,新Group有m2,则处理顺序:m1 -> m2 -> handler -> m2 -> m1
func (group *RouterGroup) combineHandlers(handlers HandlersChain) HandlersChain {
finalSize := len(group.Handlers) + len(handlers)
if finalSize >= int(abortIndex) {
panic("too many handlers")
}
mergedHandlers := make(HandlersChain, finalSize)
copy(mergedHandlers, group.Handlers)
copy(mergedHandlers[len(group.Handlers):], handlers)
return mergedHandlers
}
新Group的basePath是原Group的basePath拼接上relativePath
func (group *RouterGroup) calculateAbsolutePath(relativePath string) string {
return joinPaths(group.basePath, relativePath)
}
func joinPaths(absolutePath, relativePath string) string {
if relativePath == "" {
return absolutePath
}
finalPath := path.Join(absolutePath, relativePath)
if lastChar(relativePath) == '/' && lastChar(finalPath) != '/' {
return finalPath + "/"
}
return finalPath
}
新Group的root为false,可能是为了避免在链式调用GET、POST……时,把下级命名空间的路由定义到默认命名空间上
例如: route.Group(“/hello”).GET(“/world”, func(){}).GET(“/bye”, func(){})
func (group *RouterGroup) returnObj() IRoutes {
if group.root {
return group.engine
}
return group
}
参数绑定
除了调用Context.Param获取url上的某一个参数,我们可以使用Context.Bind或Context.ShouldBind 把一堆参数(url上的或body里的)赋值到一个struct里
问:Bind 与 ShouldBind 有何区别?
同:它们都会调用binding.Default,根据content-type匹配一个参数处理器(均实现Binding接口),然后调用Binding.Bind方法,将请求参数绑定到struct或map中
异:绑定参数出错时,Bind会设置code为400(不能更改),并中止后续中间件的调用;但ShouldBind不会
问:参数容器的类型?
如果是GET请求,默认会调用formBinding,容器可以是struct、map[string]string、map[string][]string。struct的字段需要以`form:"xxx"`注解
如果不是GET,则根据content-type匹配一个参数处理器(匹配不到会默认用formBinding)。以jsonBinding为例,容器可以是struct(字段以`json:"xxx"`注解)、map(键为string,值类型与参数类型相同)
容器需要以指针形式传递给Bind函数
问:什么时候会绑定出错返回400?
例如类型不匹配的时候。例如重复读取body导致EOF的时候。
问:怎样重复读取body而不报EOF
EOF是因为body的读取默认是流式的,读完没法再读
使用ShouldBindBodyWith(obj interface{}, bb binding.BindingBody) (err error),它会将body存于Context中,每次调用都会重头读取该缓存
需要自己指定参数处理器,目前只有json、msgpack、protobuf、xml、yaml实现了binding.BindingBody接口(formBinding是从url读取参数的)
问:BindUri 是什么
定义路由时,可以/:name/:id形式,然后struct字段用`uri:"name"`、`uri:"id"`注解来获取参数

返回响应
生成响应时调用的context.HTML、context.JSON等便利方法,实际上都是调用context.Render

context.Render接收两个参数:http code和render.Render实现类
render.Render实现类需实现Render和WriteContentType接口,往http.ResponseWriter写入响应
启动服务器
Engine.Run实际上是调用了http.ListenAndServe这一便利方法
func (engine *Engine) Run(addr ...string) (err error) {
// ...
err = http.ListenAndServe(address, engine)
return
}
以下为http包的代码。
Engine会被组合到http.Server的Handler字段之中(要求实现ServeHTTP接口),http.Server的newConn方法创建的http.conn对象也会引用http.Server,因此serverHandler{c.server}.ServeHTTP(w, w.req)会调用到Engine.ServeHTTP。
而请求的并发处理,还是由http包使用go net.Conn.serve实现,gin只是为路由定义、参数处理提供方便而已。
func ListenAndServe(addr string, handler Handler) error {
server := &Server{Addr: addr, Handler: handler}
return server.ListenAndServe()
}
func (srv *Server) ListenAndServe() error {
// ...
ln, err := net.Listen("tcp", addr)
// ...
return srv.Serve(ln)
}
func (srv *Server) Serve(l net.Listener) error {
// ...
for {
rw, err := l.Accept()
// ...
c := srv.newConn(rw)
c.setState(c.rwc, StateNew, runHooks) // before Serve can return
go c.serve(connCtx)
}
}
func (c *conn) serve(ctx context.Context) {
// ...
for {
w, err := c.readRequest(ctx)
// ...
serverHandler{c.server}.ServeHTTP(w, w.req)
w.cancelCtx()
if c.hijacked() {
return
}
w.finishRequest()
if !w.shouldReuseConnection() {
if w.requestBodyLimitHit || w.closedRequestBodyEarly() {
c.closeWriteAndWait()
}
return
}
// ...
}
}
Engine对ServeHTTP的实现如下:从pool中产生或重用一个Context,给其填充http.ResponseWriter和http.Request,然后让handleHTTPRequest处理该Context,最后将Context的空间回收
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)
}
Context的处理过程如下就是先从路由树中找出对应GET、POST……的methodTree,然后将该methodTree之前定义middleware赋值到Context上,再调用Context.Next,执行层层middleware直至最终handler
func (engine *Engine) handleHTTPRequest(c *Context) {
httpMethod := c.Request.Method
// ...
t := engine.trees
for i, tl := 0, len(t); i < tl; i++ {
if t[i].method != httpMethod {
continue
}
root := t[i].root
// Find route in tree
value := root.getValue(rPath, c.params, unescape)
if value.params != nil {
c.Params = *value.params
}
if value.handlers != nil {
c.handlers = value.handlers
c.fullPath = value.fullPath
c.Next()
c.writermem.WriteHeaderNow()
return
}
// ....
break
}
// ...
}