我想使用GIN在GO上实现基于头的版本控制.我正在考虑使用中间件功能在路由上执行此操作.

客户端将调用相同的API URL,并且版本将位于自定义的HTTP头上,如下所示:

调用版本1 获取/用户/12345678 接受-版本:V1

要呼叫版本2: 获取/用户/12345678 接受-版本:V2

因此,路由可以识别报头并调用特定版本.大概是这样的:

            router := gin.Default()

            v1 := router.Group("/v1")
            v1.Use(VersionMiddleware())
            v1.GET("/user/:id", func(c *gin.Context) {
                c.String(http.StatusOK, "This is the v1 API")
            })

            v2 := router.Group("/v2")
            v2.Use(VersionMiddleware())
            v2.GET("/user/:id", func(c *gin.Context) {
                c.String(http.StatusOK, "This is the v2 API")
            })

func VersionMiddleware() gin.HandlerFunc {
    return func(c *gin.Context) {
        version := c.Request.Header.Get(configuration.GetConfigValue("VersionHeader"))

        // Construct the new URL path based on the version number
        path := fmt.Sprintf("/%s%s", version, c.Request.URL.Path)

        // Modify the request URL path to point to the new version-specific endpoint
        c.Request.URL.Path = path
        c.Next()
    }
}

推荐答案

请判断下面的代码片段.我使用ReverseProxy重定向到给定的版本.您需要仔细验证给定的版本.否则,它将导致递归调用.

注意:我使用了/user GET的两个版本(/v1/user个和/v2/user个).

示例代码

package main

import (
 "net/http"
 "net/http/httputil"
 "regexp"

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



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


 v1 := router.Group("/v1")
 v1.GET("/user", func(c *gin.Context) {
  c.String(http.StatusOK, "This is the v1 API")
 })

 v2 := router.Group("/v2")
 v2.GET("/user", func(c *gin.Context) {
  c.String(http.StatusOK, "This is the v2 API")
 })

 router.Run(":8082")
}



func VersionMiddleware() gin.HandlerFunc {
    return func(c *gin.Context) {
  
  // You need to check c.Request.URL.Path whether 
  // already have a version or not, If it has a valid
  // version, return.
  regEx, _ := regexp.Compile("/v[0-9]+")
  ver := regEx.MatchString(c.Request.URL.Path)
  if ver {
   return
  }

  version := c.Request.Header.Get("Accept-version")
  
  // You need to validate  given version by the user here.
  // If version is not a valid version, return error 
  // mentioning that given version is invalid.

  director := func(req *http.Request) {
    r := c.Request
    req.URL.Scheme = "http"
    req.URL.Host = r.Host
    req.URL.Path =  "/"+ version + r.URL.Path
    }
  proxy := &httputil.ReverseProxy{Director: director}
  proxy.ServeHTTP(c.Writer, c.Request)
 }
}

您可以使用下面的包装器实现GIN.

  • 示例

package main

import (
 "net/http"

 "github.com/gin-gonic/gin"
 "github.com/udayangaac/stackoverflow/golang/75860989/ginwrapper"
)



func main() {
  engine := gin.Default()
 router := ginwrapper.NewRouter(engine)

 defaultRouter := router.Default()
 defaultRouter.Get("/profile",func(ctx *gin.Context) {

 })

 v1 := router.WithVersion("/v1")
 v1.Get("/user",func(ctx *gin.Context) {
  ctx.String(http.StatusOK, "This is the profile v1 API")
 })

 v2 := router.WithVersion("/v2")
 v2.Get("/user",func(ctx *gin.Context) {
  ctx.String(http.StatusOK, "This is the profile v2 API")
 })

 
 engine.Run(":8082")
}
  • 包装器
package ginwrapper

import (
 "fmt"
 "net/http"

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

type Router struct {
 router *gin.Engine
 versionGroups map[string]*gin.RouterGroup
}

type VersionedRouter struct {
 version string
 Router
}

func NewRouter(router *gin.Engine) *Router {
 return &Router{
  router: router,
  versionGroups: make(map[string]*gin.RouterGroup),
 }
}

func (a *Router) Default() VersionedRouter {
 return VersionedRouter{Router: *a }
}

func  (a *Router) WithVersion(version string) VersionedRouter {
 if _,ok := a.versionGroups[version]; ok {
  panic("cannot initialize same version multiple times")
 }
 a.versionGroups[version] = a.router.Group(version)
 return VersionedRouter{Router: *a,version:version }
}




func (vr VersionedRouter) Get(relativePath string, handlers ...gin.HandlerFunc)  {
 vr.handle(http.MethodGet,relativePath,handlers...)
}

// Note: You need to follow the same for other HTTP Methods.
// As an example, we can write a method for Post HTTP Method as below,
// 
//  func (vr VersionedRouter) Post(relativePath string, handlers ...gin.HandlerFunc)  {
//   vr.handle(http.MethodPost,relativePath,handlers...)
//  }





func (vr VersionedRouter)handle(method,relativePath string, handlers ...gin.HandlerFunc)  {
 if !vr.isRouteExist(method,relativePath) {
  vr.router.Handle(method,relativePath,func(ctx *gin.Context) {
   version := ctx.Request.Header.Get("Accept-version")
   if len(version) == 0 {
    ctx.String(http.StatusBadRequest,"Accept-version header is empty")
   }
   ctx.Request.URL.Path = fmt.Sprintf("/%s%s", version, ctx.Request.URL.Path)
   vr.router.HandleContext(ctx)
  })
 }

 versionedRelativePath := vr.version + relativePath
 if !vr.isRouteExist(method,versionedRelativePath) {
  vr.router.Handle(method,versionedRelativePath,handlers... )
 }
}


func (a VersionedRouter) isRouteExist(method,relativePath string) bool {
 for _,route := range a.router.Routes() {
  if route.Method == method && relativePath == route.Path {
   return true
  } 
 }
 return false
}

样例请求

  • /v1/user
curl --location 'localhost:8082/user' \
--header 'Accept-version: v1'
  • /v2/user
curl --location 'localhost:8082/user' \
--header 'Accept-version: v2'

Go相关问答推荐

使用Gorm创建自定义连接表

更改位置级别和时间戳零点Golang

ChromeDriver不存在(高朗selenium)

在Golang中Mergesort的递归/并行实现中出现死锁

JetBrains Goland,禁用突出显示测试文件

在整个SQL事务中将使用上下文作为默认设置吗?

无法获取RPC描述符

如何使redis池的等待超时

在运行时更改 Go lang slog 的日志(log)级别

Kusto Go API 从多个表查询

Golang 创建一个带有处理程序的模拟数据库并使用接口调用数据库

我如何使用 TOML fixtures 在使用 Go Buffalo 框架的开发环境中为我的数据库 seeder ?

如何处理 Go 的 firebase admin sdk 错误?

从数据库中带有 imageurl 的文件夹中获取图像,并在我的浏览器中用 golang 中的 echo 显示

有没有办法将 yaml node 添加到 golang 中现有的 yaml 文档中?

如何将具有嵌入式 struct 的 struct 展平为 json

为什么此代码在运行命令 error="exec: not started" 时出现错误?

如何在 Windows 中使用 github.com/AllenDang/giu 和 github.com/gordonklaus/portaudio 构建 GO 程序

Go:如何创建一个可以提供配置文件中描述的 url 的服务器

如何从字符串中删除多个换行符`\n`但只保留一个?