Home  >  Article  >  Backend Development  >  Header based versioning on golang

Header based versioning on golang

WBOY
WBOYforward
2024-02-10 19:03:08433browse

golang 上基于标头的版本控制

Header-based version control on Golang is an efficient way to manage code versions during development. By adding version information at the beginning of code files, developers can easily track code changes and version history. This version control method is not only easy to use, but also suitable for projects of all sizes. PHP editor Xigua will introduce in detail the usage and precautions of header-based version control on Golang in this article to help developers better manage and maintain code. Whether you are a beginner or an experienced developer, this article will provide you with valuable reference and guidance. Let’s explore this interesting and practical version control technology together!

Question content

I want to use gin to implement header-based version control on go. I'm thinking of using a middleware function to do this on the router.

The client will call the same api url and the version will be in a custom http header like this:

Call version 1 Get /users/12345678 Accepted version: v1

Calling version 2: Get /users/12345678 Accepted version: v2

Therefore, the router can recognize the header and call the specific version. Something like this:

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()
    }
}

Solution

Please check the code snippet below. I use reverseproxy to redirect to a given version. You need to carefully verify a given version. Otherwise it will result in recursive calls.

Note: I used two versions of /user get (/v1/user and /v2/user) .

Sample code

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)
 }
}

or

You can achieve this using the gin wrapper below.

  • Example
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
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
}

Example request

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

The above is the detailed content of Header based versioning on golang. For more information, please follow other related articles on the PHP Chinese website!

Statement:
This article is reproduced at:stackoverflow.com. If there is any infringement, please contact admin@php.cn delete