发布于 2026-01-06 9 阅读
0

Go DEV 的全球展示与讲解挑战赛(由 Mux 呈现):展示你的项目!

在 Go 中对 API 进行版本控制

由 Mux 主办的 DEV 全球展示挑战赛:展示你的项目!

你的 API 迟早需要有版本号/v1/v2例如GitHub API)。
为了在 Go 中实现这一点,我将使用gorilla/mux路由,并假设你已经有一个可以正常运行的Go环境。

我们将使用以下main.go文件创建一个新项目:

package main

import (
    "flag"
    "net/http"

    "github.com/gorilla/mux"
)

var (
    port = flag.String("port", "8080", "port")
)

func main() {
    flag.Parse()
    var router = mux.NewRouter()
    var api = router.PathPrefix("/api").Subrouter()
    api.NotFoundHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        w.WriteHeader(http.StatusNotFound)
    })
    http.ListenAndServe(":"+*port, router)
}

简而言之,我们创建了一个新的路由器,并为其配备了一个完善的子路由器来处理/api作为版本化路由基础的路由。
路由将显示为类似 `/etc/routes/` /api/v1/endpoint/api/v2/endpoint`/etc/routes/` 等格式。

此外,我们还定义了一个附加到子路由器的未找到处理程序,该处理程序只会返回一个状态码。

请注意,我们将在以下各种返回代码中了解在每个时刻执行的例程。

在此步骤中,我们可以将中间件附加到子路由器,以打印当前请求的路由。

api.Use(func(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        log.Println(r.RequestURI)
        next.ServeHTTP(w, r)
    })
})

现在我们准备添加第一个版本的 API。

var api1 = api.PathPrefix("/v1").Subrouter()
api1.HandleFunc("/status", func(w http.ResponseWriter, r *http.Request) {
    w.WriteHeader(http.StatusOK)
})
api1.NotFoundHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    w.WriteHeader(http.StatusForbidden)
})

与创建子路由器的方式相同/api,添加/v1子路由器及其对应的未找到处理程序。请注意,此子路由器的根目录是api`subrouter` 而不是 `main` router。此外,我们还定义了名为 `<endpoint_name>` 的端点的处理函数/status。类似地,我们可以创建 `<endpoint_name>` /v2。只需粘贴此代码并将 1 替换为 2,我们的代码就变成了

func main() {
    flag.Parse()
    var router = mux.NewRouter()
    var api = router.PathPrefix("/api").Subrouter()
    api.NotFoundHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        w.WriteHeader(http.StatusNotFound)
    })
    api.Use(func(next http.Handler) http.Handler {
        return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
            log.Println(r.RequestURI)
            next.ServeHTTP(w, r)
        })
    })
    var api1 = api.PathPrefix("/v1").Subrouter()
    api1.HandleFunc("/status", func(w http.ResponseWriter, r *http.Request) {
        w.WriteHeader(http.StatusOK)
    })
    api1.NotFoundHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        w.WriteHeader(http.StatusForbidden)
    })
    var api2 = api.PathPrefix("/v2").Subrouter()
    api2.HandleFunc("/status", func(w http.ResponseWriter, r *http.Request) {
        w.WriteHeader(http.StatusAccepted)
    })
    api2.NotFoundHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        w.WriteHeader(http.StatusNoContent)
    })
    http.ListenAndServe(":"+*port, router)
}

我们已准备好测试我们的 API。为此,请运行项目并使用curl.

curl -I 'localhost:8080/api/'
HTTP/1.1 403 Not Found

curl -I 'localhost:8080/api/v1/'
HTTP/1.1 404 Forbidden

curl -I 'localhost:8080/api/v1/status'
HTTP/1.1 200 OK

curl -I 'localhost:8080/api/v2/'
HTTP/1.1 204 No Content

curl -I 'localhost:8080/api/v2/status'
HTTP/1.1 200 Accepted

测试看起来没问题,但我们的 API 缺少身份验证。第一个想法是简单地使用一个带有令牌的MatcherFunc,以及以下代码行。

var api = router.PathPrefix("/api").Subrouter()

变得

var api = router.MatcherFunc(func(r *http.Request, rm *mux.RouteMatch) bool {
    return r.Header.Get("x-auth-token") == "admin"
}).PathPrefix("/api").Subrouter()

再次测试

curl -I 'localhost:8080/api/v1/status' -H "x-auth-token: admin"
HTTP/1.1 202 OK

curl -I 'localhost:8080/api/v1/status' -H "x-auth-token: notadmin"
HTTP/1.1 404 Not Found

不太好,如果我输入错误的密码,就会收到not found错误代码。这本身没什么问题,但我希望看到我没有被授权的提示。所以,我们会把身份验证代码移到中间件中,最终的代码如下所示:

package main

import (
    "flag"
    "net/http"
    "log"

    "github.com/gorilla/mux"
)

var (
    port = flag.String("port", "8080", "port")
)

func main() {
    flag.Parse()
    var router = mux.NewRouter()
    var api = router.PathPrefix("/api").Subrouter()
    api.NotFoundHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        w.WriteHeader(http.StatusNotFound)
    })
    api.Use(func(next http.Handler) http.Handler {
        return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
            if r.Header.Get("x-auth-token") != "admin" {
                w.WriteHeader(http.StatusUnauthorized)
                return
            }
            log.Println(r.RequestURI)
            next.ServeHTTP(w, r)
        })
    })
    var api1 = api.PathPrefix("/v1").Subrouter()
    api1.HandleFunc("/status", func(w http.ResponseWriter, r *http.Request) {
        w.WriteHeader(http.StatusOK)
    })
    api1.NotFoundHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        w.WriteHeader(http.StatusForbidden)
    })
    var api2 = api.PathPrefix("/v2").Subrouter()
    api2.HandleFunc("/status", func(w http.ResponseWriter, r *http.Request) {
        w.WriteHeader(http.StatusAccepted)
    })
    api2.NotFoundHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        w.WriteHeader(http.StatusNoContent)
    })
    http.ListenAndServe(":"+*port, router)
}

期末考试

curl -I 'localhost:8080/api/v1/status' -H "x-auth-token: admin"
HTTP/1.1 200 OK

curl -I 'localhost:8080/api/v1/status' -H "x-auth-token: notadmin"
HTTP/1.1 401 Unauthorized

太棒了,我们成功了!我们实现了带有身份验证功能的版本化 API。请查看GitHub 上的项目

享受。

文章来源:https://dev.to/geosoft1/versioning-your-api-in-go-1g4h