gin框架介绍

为何用gin

它是一个轻量级框架,框架简单而且速度很快,它的功能用来做rust api开发已经足够

而因为它的简单我们也能很好的在它上面增加功能或再开发

gin的特性

支持中间层

一个请求过来经过 global middleware,group middleware 最后到该path的middleware处理
我们可以把处理函数放入global/middleware/group middleware的中

基于Radix tree

灾难恢复

处理请求崩溃后会在Recover函数中恢复然后返回500

更多特性请看官网

基本用法

package main

import "github.com/gin-gonic/gin"

func pingPath(c *gin.Context) {
	c.String(200, "pong")
}

func main() {
    r := gin.Default()
	r.GET("ping", pingPath)
	r.Run(":80")
}

上面的代码是什么意思呢?

先看看gin.Default的源码

func Default() *Engine {
	engine := New()
	engine.Use(Recovery(), Logger())
	return engine
}
  • New()是用默认参数构造一个engine
  • engine.Use(…)把Recovery() 和Logger()生成的函数增加值全局handler列
  • engine.Use要在所有handle的middleware绑定之前使用,否则某些绑定的path会不生效(在group的middleware之后加是可以的)

然后我们来看看Recovery干了什么

// Recovery returns a middleware that recovers from any panics and writes a 500 if there was one.
func Recovery() HandlerFunc {
	return RecoveryWithWriter(DefaultWriter)
}

func RecoveryWithWriter(out io.Writer) HandlerFunc {
	var logger *log.Logger
	if out != nil {
		logger = log.New(out, "", log.LstdFlags)
	}
	return func(c *Context) {
		defer func() {
			if err := recover(); err != nil {
				if logger != nil {
					stack := stack(3)
					logger.Printf("Panic recovery -> %s\n%s\n", err, stack)
				}
				c.AbortWithStatus(500)
			}
		}()
		c.Next()
	}
}
  • 关于recover的使用可以看http://blog.golang.org/defer-panic-and-recover
  • defer的函数会在函数结束后返回前调用
  • c.Next()就是调用下一个handler 就和Nodejs中的http库差不多
  • 大致流程就是 FuncBody -> c.Next -> defer func (一个灾难恢复就这样简单的实现了)

再然后来看看Logger

// Instances a Logger middleware that will write the logs to gin.DefaultWriter
// By default gin.DefaultWriter = os.Stdout
func Logger() HandlerFunc {
	return LoggerWithWriter(DefaultWriter)
}

// Instance a Logger middleware with the specified writter buffer.
// Example: os.Stdout, a file opened in write mode, a socket...
func LoggerWithWriter(out io.Writer) HandlerFunc {
	return func(c *Context) {
		// Start timer
		start := time.Now()
		path := c.Request.URL.Path

		// Process request
		c.Next()

		// Stop timer
		end := time.Now()
		latency := end.Sub(start)

		clientIP := c.ClientIP()
		method := c.Request.Method
		statusCode := c.Writer.Status()
		statusColor := colorForStatus(statusCode)
		methodColor := colorForMethod(method)
		comment := c.Errors.ByType(ErrorTypePrivate).String()

		fmt.Fprintf(out, "[GIN] %v |%s %3d %s| %13v | %s |%s  %s %-7s %s\n%s",
			end.Format("2006/01/02 - 15:04:05"),
			statusColor, statusCode, reset,
			latency,
			clientIP,
			methodColor, reset, method,
			path,
			comment,
		)
	}
}
  • 大致就是输出请求头的数据和处理请求所花费的时间
  • 但听说获取系统时间会有阻塞

再来看看r.GET之后发生了什么

// routergroup file
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但上面gin.Default()返回的却是是Engine

于是我们看看Engine的定义

// Engine is the framework's instance, it contains the muxer, middleware and configuration settings.
// Create an instance of Engine, by using New() or Default()
Engine struct {
	RouterGroup
	HTMLRender  render.HTMLRender
	allNoRoute  HandlersChain
	allNoMethod HandlersChain
	noRoute     HandlersChain
	noMethod    HandlersChain
	pool        sync.Pool
	trees       methodTrees
	//忽略一些太长的数据
	...

}
  • Engine 是继承于RouteGroup的
  • 其实可以把Engine当成是RouteGroup节点,Group是可以嵌套的

最后看一下handler函数的Context参数

// Context is the most important part of gin. It allows us to pass variables between middleware,
// manage the flow, validate the JSON of a request and render a JSON response for example.
type Context struct {
	writermem responseWriter
	Request   *http.Request
	Writer    ResponseWriter

	Params   Params
	handlers HandlersChain
	index    int8

	engine   *Engine
	Keys     map[string]interface{}
	Errors   errorMsgs
	Accepted []string
}

// Next should be used only inside middleware.
// It executes the pending handlers in the chain inside the calling handler.
// See example in github.
func (c *Context) Next() {
	c.index++
	s := int8(len(c.handlers))
	for ; c.index < s; c.index++ {
		c.handlers[c.index](c)
	}
}

  • 这里只展示了Context的定义和Next函数的实现,详细看源码

  • gin的运作原理大概就是这样了,之后讲gin的一些example和gin的github page上的一样

包含参数的路径

package main

import "github.com/gin-gonic/gin"
import "net/http"

func main() {
	r := gin.Default()
	// 这种写法只会匹配/user/pigeon ,/user/ 和/user就不会被匹配
	r.GET("/user/:name", func(c *gin.Context) {
		name := c.Param("name")
		c.String(http.StatusOK, "Hello %s", name)
	})
	//这种写法会匹配 /user/pigeon/ 和/user/pigeon/enter 或 /user/pigeon/to/doing/something
	r.GET("/user/:name/*action", func(c *gin.Context) {
		name := c.Param("name")
		action := c.Param("action")
		message := name + " is " + action
		c.String(http.StatusOK, message)
	})
	r.Run(":80")
}

Query字符串参数

package main

import "github.com/gin-gonic/gin"

func main() {
    router := gin.Default()
	// 注:Query函数获取的值都必定是字符串
    router.GET("/welcome", func(c *gin.Context) {
        firstname := c.DefaultQuery("firstname", "Guest")
        lastname := c.Query("lastname") // shortcut for c.Request.URL.Query().Get("lastname")

        c.String(http.StatusOK, "Hello %s %s", firstname, lastname)
    })
    router.Run(":80")
}
  • 请尝试用如下请求路径来请求: /welcome?firstname=Jane&lastname=Doe
  • 然后查看请求结果

Model binding and validation

package main

import "github.com/gin-gonic/gin"
import "net/http"

// Binding from JSON
type Login struct {
    User     string `form:"user" json:"user" binding:"required"`
    Password string `form:"password" json:"password" binding:"required"`
}

func main() {
    router := gin.Default()

    // 该例子将绑定一个拥有 user 和 password 键的JSON数据
    router.POST("/loginJSON", func(c *gin.Context) {
        var json Login
        if c.BindJSON(&json) == nil {
            if json.User == "manu" && json.Password == "123" {
                c.JSON(http.StatusOK, gin.H{"status": "you are logged in"})
            } else {
                c.JSON(http.StatusUnauthorized, gin.H{"status": "unauthorized"})
            }
        }
    })

    // Example for binding a HTML form (user=manu&password=123)
    router.POST("/loginForm", func(c *gin.Context) {
        var form Login
        // 用http头中Content-Type 的值去判定数据类型
        if c.Bind(&form) == nil {
            if form.User == "manu" && form.Password == "123" {
                c.JSON(http.StatusOK, gin.H{"status": "you are logged in"})
            } else {
                c.JSON(http.StatusUnauthorized, gin.H{"status": "unauthorized"})
            }
        }
    })

    router.Run(":80")
}

在中间层中使用goroute

package main

import "github.com/gin-gonic/gin"
import "time"
import "log"

func main() {
    r := gin.Default()

    r.GET("/long_async", func(c *gin.Context) {
        // create copy to be used inside the goroutine
        c_cp := c.Copy()
        go func() {
            // simulate a long task with time.Sleep(). 5 seconds
            time.Sleep(5 * time.Second)

            // note than you are using the copied context "c_cp", IMPORTANT
            log.Println("Done! in path " + c_cp.Request.URL.Path)
        }()
    })


    r.GET("/long_sync", func(c *gin.Context) {
        // simulate a long task with time.Sleep(). 5 seconds
        time.Sleep(5 * time.Second)

        // since we are NOT using a goroutine, we do not have to copy the context
        log.Println("Done! in path " + c.Request.URL.Path)
    })

    r.Run(":80")
}
  • goroute 中请使用只读的Context,用Context.Copy可以返回一个只读的Context

更多gin的信息可看这https://gin-gonic.github.io/gin/