Gin: 路由

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):将方法、路径绑定到处理器,其中handlehttprouter.Handle
    • GET/POST/PUT/DELETE/PATCH/OPTIONS/HEAD(path string, handle Handle)Handle()的快捷方法
    • Handler()/HandlerFunc()net/http的适配器
  • 路径参数捕获:路径中使用:可以捕获,例如/users/:uid,将不包含路径分隔符的路径参数捕获并赋值给uid
    • 使用httprouter.HandleHandle方法的签名就是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()/...

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

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

      1
      2
      3
      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-urlencodedmultipart/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()的快捷函数
  • 对于JSONXMLProtoBuf等数据,ShouldBind()只能调用一次,需要使用ShouldBindBodyWith()方法

分组路由

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

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

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

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

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

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

    1
    2
    3
    4
    5
    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

  • 1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    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
    }
    })
  • 常用的配置项和一般的网页应用一样:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    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本身直接使用StdoutStderr,如果需要集成zap等,直接替换掉默认的日志记录器即可:

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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    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 }