前置き
久しぶりに記事を書いています。(結構雑にですが)
なんで書く気になったかというと、あまりに嬉しい修正が入っていたことに気が付いたからです。
恥ずかしながら結構前のアップデートだったようですが、12月中頃に気付きました・・・
GoでAPIサーバを作成する際にWAFの選択肢は様々だと思います。
go-swagger
を使ってみようかなんて考えたり、Echo
とGin
ってどっちが良いんだろうと悩んだり。
ちなみに僕は過去にGin
を選択したり、WAFというかRouterとしてgorilla/mux
を使用したりもしていました。
そしてGin
についてはRouterのPathの記述というか、ルーティングに悩まされることがありました。
いくつかそのような記事も見かけたので気になる方は検索してみてください。
上記のようにPathが柔軟に作れないことで一定数Gin
を選択肢から外してしまった方もいるのでは無いでしょうか。
今回はそんなルーティングに難があったGinでかなり問題が解決されていた部分について書いていこうと思う。
前提
- go version:
go1.17.6 darwin/arm64
- 問題があったGinのversion:
~ https://github.com/gin-gonic/gin@v1.6.3
- 問題が解消されたGinのversion:
https://github.com/gin-gonic/gin@v1.7.0 ~
今までの問題 ~v1.6.3
APIサーバなどを作成する際のルーティングで衝突してしまう問題
下記のようなルーティングを記載したとします
※GinではPathParametersを使用する際には:PathParameters
として記述する
package main
import "github.com/gin-gonic/gin"
func main() {
r := gin.Default()
r.GET("/:path", func(c *gin.Context) {
path := c.Param("path")
c.JSON(200, gin.H{"message": path})
})
r.Run()
}
このようなルーティングでは正しく機能します。
$ curl localhost:8080/ok
{"message":"ok"}
$ curl localhost:8080/sample
{"message":"sample"}
$ curl localhost:8080/test
{"message":"test"}
しかし上記のルーティングのように一番上の階層でPathParametersを使用してしまった場合
下記のようなルーティングを記述すると衝突して起動できなくなってしまっていました。
package main
import "github.com/gin-gonic/gin"
func main() {
r := gin.Default()
r.GET("/:path", func(c *gin.Context) {
path := c.Param("path")
c.JSON(200, gin.H{"message": path})
})
//追加した部分Start
r.GET("/ping", func(c *gin.Context) {
c.JSON(200, gin.H{"message": "pong"})
})
//追加した部分End
r.Run()
}
$ go run main.go
panic: 'ping' in new path '/ping' conflicts with existing wildcard ':path' in existing prefix '/:path'
- /:PathParameters
- /ping
第一階層を/ping
とPathParametersで受け付けたそれ以外
にしたかったのですが衝突してしまい起動すらできません。
この問題は第一階層だけに限った話ではなく同一階層でPathParametersと記述したPathを区別できず衝突してしまうというものでした。(というか解決される順番の問題)
できない例
- /users/:id
- /users/search
できる例
下記のようにuserとusersを分ける必要がある。
- /user/:id
- /users/search
上記の例は少し乱暴というか分ける設計で行くのが自然に見えるので分かり辛いかもしれませんが
ある程度制約が生まれてしまっていることが分かっていただければと思います。
他にも/
が入ったPathParametersの/
を自動的に削除してしまうという問題があったが殆どの原因が上記のPathParameters
が記述したPath
と衝突しまう問題と絡んでいたものなので割愛。
問題が解消された v1.7.0 ~
先ほど起動できなかったもの
package main
import "github.com/gin-gonic/gin"
func main() {
r := gin.Default()
r.GET("/:path", func(c *gin.Context) {
path := c.Param("path")
c.JSON(200, gin.H{"message": path})
})
//追加した部分Start
r.GET("/ping", func(c *gin.Context) {
c.JSON(200, gin.H{"message": "pong"})
})
//追加した部分End
r.Run()
}
問題なく起動できる
$ go run ./main.go
[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.
[GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production.
- using env: export GIN_MODE=release
- using code: gin.SetMode(gin.ReleaseMode)
[GIN-debug] GET /:path --> main.main.func1 (3 handlers)
[GIN-debug] GET /ping --> main.main.func2 (3 handlers)
[GIN-debug] Environment variable PORT is undefined. Using port :8080 by default
[GIN-debug] Listening and serving HTTP on :8080
ルーティングも問題ない
$ curl localhost:8080/test
{"message":"test"}
$ curl localhost:8080/sample
{"message":"sample"}
$ curl localhost:8080/ping
{"message":"pong"}
以上!!!
今までルーティングの問題でGinを諦めてしまった人はもう一度選択肢の中に含めても良いかもしれません。
誰かの技術選択の際に少しでも参考になれば嬉しいです。
ちなみにどんな変更だったかは検索ツリーの変更を行いワイルドカード(PathParameters)が解決される順番を前にしたとのことです。
詳しい内容は対象のPRを見てみるのも良いかと思います。