1
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

GoでのWeb Framework選択時にルーティングの問題でGinを諦めてしまった人へ

Last updated at Posted at 2022-01-08

前置き

久しぶりに記事を書いています。(結構雑にですが)
なんで書く気になったかというと、あまりに嬉しい修正が入っていたことに気が付いたからです。
恥ずかしながら結構前のアップデートだったようですが、12月中頃に気付きました・・・

GoでAPIサーバを作成する際にWAFの選択肢は様々だと思います。
go-swaggerを使ってみようかなんて考えたり、EchoGinってどっちが良いんだろうと悩んだり。

ちなみに僕は過去に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

第一階層を/pingPathParametersで受け付けたそれ以外にしたかったのですが衝突してしまい起動すらできません。
この問題は第一階層だけに限った話ではなく同一階層で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を見てみるのも良いかと思います。

1
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?