Irohabook
0
2464

Goのginはルーティングに難がある:conflicts with existing wildcardのエラーが出たらさっさと別のライブラリを使おう

ginのルーティングには致命的な欠点がある。Djangoなどの成熟したフレームワークに慣れている人は、ginの独特なルールに違和感を持つだろう。問題は次の三つ。

  • ドメイン直下のページを認識しない
  • 正規表現(のような表現)で衝突する二つ以上の表現を区別できない
  • パラメーターからスラッシュを自動的に削除する

ginのルーティングでエラーが起きると、次のログが表示される。

conflicts with existing wildcard

ドメイン直下のページを認識しない

最大の問題は

/terms
/legal
/sample-page

といった直下のページを認識しないこと。つまり下のコードは機能しない。

r.GET("/:path", views.GetPage)

termsもlegalもsample-pageもGetPageという関数で表示されるはずだ。しかしこの直感は当たらない。その原因はstaticディレクトリの読みこみと関係している可能性がある。staticを読みこむときは

r.Use(static.Serve("/static", static.LocalFile("./static", true)))

などとするが、これが上の表現と衝突する。実際、staticの読みこみをしないとエラーは出ない。

本来一つの表現が二つ以上の表現に分かれているとエラーになる

postのうちsampleだけ別の関数で表示したいとする。

r.GET("/post/sample", views.GetPostSample)
r.GET("/post/:id", views.GetPost)

sampleも:idに当てはまるため、この二つは衝突する。この場合分けはginの想定外で解決法はない。順番を入れかえても無駄である。Djangoは上から順番に当てはめて、当てはまるものがあったらそれ以上は進まないという原則がある。ginにはない。

とはいえこの問題は簡単に解決できる。

r.GET("/post/:path", views.GetPost)

としてGetPost内でパラメーターの場合分けをすればいい。しかしこの方法は根本的な解決になっていない。

パラメーターからスラッシュを自動的に削除する

スラッシュで2回以上区切られたパスを一つの変数とみなすことはない。

/post/sample

というパスは

r.GET("/:path", views.GetPost)

を通らない。ginのパスに入る文字列からスラッシュを除いている可能性がある。

これらの問題から発生する問題

上の三つは、それ自体は大きな問題といえない。個別に対処して一つずつ解決できる。しかしwebアプリケーションが少しでも複雑になると、ginの制約によってページのURLを決めるはめになる。

WordPressのようなアプリケーションを考えてみよう。postがあり、postのcategoryがあり、categoryのアーカイブページがある。postとcategoryアーカイブのURLはどのように表現すればいいか?

/:postname
/category/:categoryname

上の表現はエラーになる。一番目のpostnameは静的ファイルのstaticと衝突するからだ。

次の表現はapiとして見た場合もわかりやすいが、もちろんエラーになる。

/:categoryname
/:categoryname/:postname

結局次の表現に落ちつく。

/post/:postname
/category/:categoryname

このURLはpostがcategoryにぶら下がるという構造を示していない。

技術上の制約でURLを決めてもいいというなら、この問題をこれ以上考える必要はない。ginは全体のわずかな歯車にすぎず、サービスも含めた全体の設計でもっとも小さなピースだろう。URLの設計はサービスの根幹に当たるため、ginの吐きだすエラーにふりまわされてもナンセンスだ。

何年も前からこの問題はあちこちで指摘されているが、いまだに修正されていない。ルーティングの問題を無視するためにはginでなく、ルーティングしか機能のないchiなどを使えばいい。

次の記事

HTTP