一个普通的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
    }
    // ...
}