Gin路由

httprouter

特点

  • Gin的路由基于julienschmidt/httprouter,是一个轻量级的路由组建(总共三个文件一千余行代码)
  • httprouter只提供路由功能,标准库中的HandleFunc()依赖ServeMux来路由,而ServeMux针对前缀匹配采用遍历的方式匹配,效率较差
  • httprouter提供:请求方法和路径分离,路由算法优化(基于基数树)因此一次路由极快,路径模糊匹配,路径参数捕获
  • 那么httprouter.Router就可以作为一个前端控制器直接传递给http.ListenAndServe(path, handler)了

API

  • httprouter.New():获取路由器对象
  • *httprouter.Router提供一系列方法绑定路由(仅仅处理路由)
    • Handle(method, path string, handle Handle):将方法、路径绑定到处理器,其中handle为httprouter.Handle
    • GET/POST/PUT/DELETE/PATCH/OPTIONS/HEAD(path string, handle Handle):Handle()的快捷方法
    • Handler()/HandlerFunc():net/http的适配器
  • 路径参数捕获:路径中使用:可以捕获,例如/users/:uid,将不包含路径分隔符的路径参数捕获并赋值给uid
    • 使用httprouter.Handle:Handle方法的签名就是func(http.ResponseWriter, *http.Request, Params),直接使用Params参数来访问
    • 使用http的处理器API:则适配器会将参数挂到*http.Request.Context()中,通过httprouter.ParamsFromContext(Context)获取Params对象
  • 还可以使用*捕获包含路径分隔符的路径参数,因此它们只能放在最后,例如/*userinfo
  • 当模糊匹配和精确匹配发生冲突时,按精确匹配大于:大于*的顺序进行匹配
  • Params提供ByName(string)获取键对应的值

Gin的参数解析

  • gin.Default(...OptionFunc) *Engine:获取一个*gin.Engine,默认配置即可,可以通过选项模式修改配置

  • Gin支持路由参数,和httprouter的使用方法类似

    • *gin.Engine对象提供和*httprouter.Router类似的方法:Handle()/GET()/...

    • 路径匹配规则类似:精确匹配、:匹配、*匹配

    • 区别在于处理器函数的类型是HandlerFunc:func(*gin.Context)

      go
      func(http.ResponseWriter, *http.Request, Params)
      
      func(*gin.Context)

      将参数替换成了上下文

    • 因此获取参数也变成了:*gin.Context.Param(string)

  • Gin还支持捕获URL参数:

    • *gin.Context.Query(string):获取GET参数的值,若无返回空
    • *gin.Context.DefaultQuery(string, string):允许自定义默认值
  • Gin还支持捕获请求体参数

    • *gin.Context.PostForm(string):可以解析表单参数application/x-www-form-urlencoded与multipart/form-data
    • *gin.Context.ShouldBind(any):自动选择解析器,并赋值给传入的参数,因此传入的参数应该是指针,不会进行错误处理
    • *gin.Context.Bind(any):是对ShouldBind()的封装,但如果参数解析错误则会自动返回400错误
    • *gin.Context.MustBindWith(any, binding.Binding):指定解析器,和Bind()一样会自动返回400错误
    • BindJSON()/BindQuery()/BindYAML()/BindXML()等:MustBindWith()的快捷函数
  • 对于JSON、XML、ProtoBuf等数据,ShouldBind()只能调用一次,需要使用ShouldBindBodyWith()方法

分组路由

  • Gin提供*gin.Engine.Group(relativePath string, handlers ...HandlerFunc) *RouterGroup方法分组

  • relativePath的捕获语法与httprouter一致,相当于分组所表示的前缀

    可以表示相对路径(即开头不带/),则相对于Engine等价于相对于根路径,相对于RouterGroup等价于相对于路由组的路径

  • handlers会在子路由处理器执行之前执行,它们不是relativePath对应的处理器,如果需要则应该在分组内绑定一个""路由

  • *RouterGroup的方法和*Engine注册路由类似,此外它本身也可以注册新的路由组

  • 为了规范,在注册路由组下的路由时,通常用{}括住:

    go
    g := e.Group("v1")
    {
     g.GET("/xxx", func(c *gin.Context) {
     })
    }

错误处理器

  • 针对404路由响应(没有对应的路由),可以自定义处理器:*gin.Engine.NoRoute(HandlerFunc)
  • 针对405路由响应(有对应的路由但是没有匹配的请求方法),可以使用*gin.Engine.NoMethod(HandlerFunc)
  • gin本身没有十分完整的捕获错误、统一处理,不像spring-mvc的@ControllerAdvice,但是可以通过中间件实现
  • 但是可以使用中间件,gin的中间件引入非常灵活,扩展性很高,完全可以实现类似统一错误处理器的效果

CORS中间件

  • 官方提供了cors的中间件github.com/gin-contrib/cors

  • go
    cors.New(cors.Config{
     type Config struct {
      AllowAllOrigins bool
    
      // AllowOrigins is a list of origins a cross-domain request can be executed from.
      // If the special "*" value is present in the list, all origins will be allowed.
      // Default value is []
      AllowOrigins []string
    
      // AllowOriginFunc is a custom function to validate the origin. It takes the origin
      // as an argument and returns true if allowed or false otherwise. If this option is
      // set, the content of AllowOrigins is ignored.
      AllowOriginFunc func(origin string) bool
    
      // Same as AllowOriginFunc except also receives the full request context.
      // This function should use the context as a read only source and not
      // have any side effects on the request, such as aborting or injecting
      // values on the request.
      AllowOriginWithContextFunc func(c *gin.Context, origin string) bool
    
      // AllowMethods is a list of methods the client is allowed to use with
      // cross-domain requests. Default value is simple methods (GET, POST, PUT, PATCH, DELETE, HEAD, and OPTIONS)
      AllowMethods []string
    
      // AllowPrivateNetwork indicates whether the response should include allow private network header
      AllowPrivateNetwork bool
    
      // AllowHeaders is list of non simple headers the client is allowed to use with
      // cross-domain requests.
      AllowHeaders []string
    
      // AllowCredentials indicates whether the request can include user credentials like
      // cookies, HTTP authentication or client side SSL certificates.
      AllowCredentials bool
    
      // ExposeHeaders indicates which headers are safe to expose to the API of a CORS
      // API specification
      ExposeHeaders []string
    
      // MaxAge indicates how long (with second-precision) the results of a preflight request
      // can be cached
      MaxAge time.Duration
    
      // Allows to add origins like http://some-domain/*, https://api.* or http://some.*.subdomain.com
      AllowWildcard bool
    
      // Allows usage of popular browser extensions schemas
      AllowBrowserExtensions bool
    
      // Allows to add custom schema like tauri://
      CustomSchemas []string
    
      // Allows usage of WebSocket protocol
      AllowWebSockets bool
    
      // Allows usage of file:// schema (dangerous!) use it only when you 100% sure it's needed
      AllowFiles bool
    
      // Allows to pass custom OPTIONS response status code for old browsers / clients
      OptionsResponseStatusCode int
     }
    })
  • 常用的配置项和一般的网页应用一样:

    go
    import "github.com/gin-contrib/cors"
    
    func A() {
    	cors.New(cors.Config{
    		AllowOrigins: []string{"http://localhost:8000"},
    		AllowMethods: []string{"*"},
    		// 允许客户端发送的请求头
    		AllowHeaders: []string{
    			"Origin",
    			"X-Requested-With",
    			"Content-Type",
    			"Accept",
    			"Authorization",
    		},
    		// 允许客户端读取的响应头
    		ExposeHeaders: []string{
    			"Content-Length",
    			"Access-Control-Allow-Origin",
    			"Access-Control-Allow-Headers",
    			"Cache-Control",
    			"Content-Language",
    			"Content-Type",
    		},
    		// 允许携带 cookie
    		AllowCredentials: true,
    	})
    }

日志重定向

  • gin本身直接使用Stdout和Stderr,如果需要集成zap等,直接替换掉默认的日志记录器即可:

    go
    gin.DefaultWriter = sugar.Writer()
    gin.DefaultErrorWriter = sugar.Writer()
  • 如果不想使用zap的sugar,可以手动实现一个适配器

    go
    type zapWriter struct {
     logger *zap.Logger
    }
    
    func (w *zapWriter) Write(p []byte) (n int, err error) {
     w.logger.Info(string(p))
     return len(p), nil
    }
    
    gin.DefaultWriter = &zapWriter{ logger:logger }