项目作者: kataras

项目描述 :
:new: API Versioning for Go
高级语言: Go
项目地址: git://github.com/kataras/versioning.git
创建时间: 2019-12-06T05:30:13Z
项目社区:https://github.com/kataras/versioning

开源协议:MIT License

下载


API Versioning (Go)

build status report card godocs donate on PayPal

Semver versioning for your APIs. It implements all the suggestions written at api-guidelines and more.

The version comparison is done by the go-version package. It supports matching over patterns like ">= 1.0, < 3" and e.t.c.

Getting started

The only requirement is the Go Programming Language.

  1. $ go get github.com/kataras/versioning

Features

  • Per route version matching, an http.Handler with “switch” cases via versioning.Map for version => handler
  • Per group versioned routes and deprecation API
  • Version matching like “>= 1.0, < 2.0” or just “2.0.1” and e.t.c.
  • Version not found handler (can be customized by simply adding the versioning.NotFound: customNotMatchVersionHandler on the Map)
  • Version is retrieved from the “Accept” and “Accept-Version” headers (can be customized through request’s context key)
  • Respond with “X-API-Version” header, if version found.
  • Deprecation options with customizable “X-API-Warn”, “X-API-Deprecation-Date”, “X-API-Deprecation-Info” headers via Deprecated wrapper.

Compare Versions

  1. // If reports whether the "version" is a valid match to the "is".
  2. // The "is" can be a version constraint like ">= 1, < 3".
  3. If(version string, is string) bool
  1. // Match reports whether the current version matches the "expectedVersion".
  2. Match(r *http.Request, expectedVersion string) bool

Example

  1. router.HandleFunc("/api/user", func(w http.ResponseWriter, r *http.Request) {
  2. if versioning.Match(r, ">= 2.2.3") {
  3. // [logic for >= 2.2.3 version of your handler goes here]
  4. return
  5. }
  6. })

Determining The Current Version

Current request version is retrieved by versioning.GetVersion(r *http.Request).

By default the GetVersion will try to read from:

  • Accept header, i.e Accept: "application/json; version=1.0"
  • Accept-Version header, i.e Accept-Version: "1.0"
  1. func handler(w http.ResponseWriter, r *http.Request){
  2. currentVersion := versioning.GetVersion(r)
  3. }

You can also set a custom version to a handler trough a middleware by setting a request context’s value.
For example:

  1. import (
  2. "context"
  3. "net/http"
  4. "github.com/kataras/versioning"
  5. )
  6. func urlParamVersion(next http.Handler) http.Handler {
  7. return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request){
  8. version := r.URL.Query().Get("v") // ?v=2.3.5
  9. if version == "" {
  10. // set a default version, e.g. 1.0
  11. version = "1.0"
  12. }
  13. r = r.WithContext(versioning.WithVersion(r.Context(), version))
  14. next.ServeHTTP(w, r)
  15. })
  16. }

Map Versions to Handlers

The versioning.NewMatcher(versioning.Map) http.Handler creates a single handler which decides what handler need to be executed based on the requested version.

  1. // middleware for all versions.
  2. func myMiddleware(next http.Handler) http.Handler {
  3. return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request){
  4. // [...]
  5. next.ServeHTTP(w, r)
  6. })
  7. }
  8. func myCustomVersionNotFound(w http.ResponseWriter, r *http.Request) {
  9. w.WriteHeader(404)
  10. fmt.Fprintf(w, "%s version not found", versioning.GetVersion(r))
  11. }
  12. router := http.NewServeMux()
  13. router.Handle("/", myMiddleware(versioning.NewMatcher(versioning.Map{
  14. // v1Handler is a handler of yuors that will be executed only on version 1.
  15. "1.0": v1Handler,
  16. ">= 2, < 3": v2Handler,
  17. versioning.NotFound: http.HandlerFunc(myCustomNotVersionFound),
  18. })))

Deprecation

Using the versioning.Deprecated(handler http.Handler, options versioning.DeprecationOptions) http.Handler function you can mark a specific handler version as deprecated.

  1. v1Handler = versioning.Deprecated(v1Handler, versioning.DeprecationOptions{
  2. // if empty defaults to: "WARNING! You are using a deprecated version of this API."
  3. WarnMessage string
  4. DeprecationDate time.Time
  5. DeprecationInfo string
  6. })
  7. router.Handle("/", versioning.NewMatcher(versioning.Map{
  8. "1.0": v1Handler,
  9. // [...]
  10. }))

This will make the handler to send these headers to the client:

  • "X-API-Warn": options.WarnMessage
  • "X-API-Deprecation-Date": options.DeprecationDate
  • "X-API-Deprecation-Info": options.DeprecationInfo

versioning.DefaultDeprecationOptions can be passed instead if you don’t care about Date and Info.

Grouping Routes By Version

Grouping routes by version is possible as well.

Using the versioning.NewGroup(version string) *versioning.Group function you can create a group to register your versioned routes.
The versioning.RegisterGroups(r *http.ServeMux, versionNotFoundHandler http.Handler, groups ...*versioning.Group) must be called in the end in order to register the routes to a specific StdMux.

  1. router := http.NewServeMux()
  2. // version 1.
  3. usersAPIV1 := versioning.NewGroup(">= 1, < 2")
  4. usersAPIV1.HandleFunc("/api/users", func(w http.ResponseWriter, r *http.Request) {
  5. if r.Method != http.MethodGet {
  6. http.Error(w, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed)
  7. return
  8. }
  9. w.Write([]byte("v1 resource: /api/users handler"))
  10. })
  11. usersAPIV1.HandleFunc("/api/users/new", func(w http.ResponseWriter, r *http.Request) {
  12. if r.Method != http.MethodPost {
  13. http.Error(w, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed)
  14. return
  15. }
  16. w.Write([]byte("v1 resource: /api/users/new post handler"))
  17. })
  18. // version 2.
  19. usersAPIV2 := versioning.NewGroup(">= 2, < 3")
  20. usersAPIV2.HandleFunc("/api/users", func(w http.ResponseWriter, r *http.Request) {
  21. if r.Method != http.MethodPost {
  22. http.Error(w, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed)
  23. return
  24. }
  25. w.Write([]byte("v2 resource: /api/users handler"))
  26. })
  27. usersAPIV2.HandleFunc("/api/users", func(w http.ResponseWriter, r *http.Request) {
  28. if r.Method != http.MethodPost {
  29. http.Error(w, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed)
  30. return
  31. }
  32. w.Write([]byte("v2 resource: /api/users post handler"))
  33. })
  34. versioning.RegisterGroups(router, versioning.NotFoundHandler, usersAPIV1, usersAPIV2)

A middleware can be registered, using the methods we learnt above, i.e by using the versioning.Match in order to detect what code/handler you want to be executed when “x” or no version is requested.

Deprecation for Group

Just call the Group#Deprecated(versioning.DeprecationOptions) on the group you want to notify your API consumers that this specific version is deprecated.

  1. userAPIV1 := versioning.NewGroup("1.0").Deprecated(versioning.DefaultDeprecationOptions)

For a more detailed technical documentation you can head over to our godocs. And for executable code you can always visit the _examples repository’s subdirectory.

License

kataras/versioning is free and open-source software licensed under the MIT License.