在 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