LoginSignup
7
2

More than 3 years have passed since last update.

GoのGinでハンドラーに引数を渡したいとき

Last updated at Posted at 2020-01-20

検索してたどり着いたみなさん、Gin やってますか?私は最近Go X Gin X LINE bot なサービスを作ろうとして結構悩みました。この経験を共有できたらいいな、と記事を書いています。

困ったこと:router.GET("/", handler.Index) に引数がない!

Goを始めてWebアプリを作ろうとしてGinに手を出した人には共感してもらえる(かもしれない)と思うんですが、私にはとても不思議に思えることがありました。アプリのルーティングを設定する場所に、Tour of Goとかでは見たことがない書き方が現れるのです。たとえば、こんな感じですね。

main.go
func main() {
    router := gin.Defualt()
    ...
    router.GET("/", handler.Index)
    ...
}

素人の視点からすると、handler.Indexってなんなんだ!なんでカッコが付いてないんだ!とうならざるを得ません。そしてさらに悪いことに、LINE botを使おうとしていた私はドツボにハマることになります。LINE botはGoのためのSDKを用意していて、そのドキュメント(から類推するところ)によると最初に bot client instanceを作る必要があります。インスタンスは連発するとメモリ食いそうだから最初に呼び出したものを使いたい...

main.go
func main() {
    bot, err := linebot.New(<channel secret>, <channel access token>)
    ...
}

うーん、困った。ハンドラーでbotのメソッドを使いたいのに、ハンドラーには引数がないからbotインスタンスを渡せない...

整理すると、

main.go
func main() {
    router := gin.Default()
    bot, err := linebot.New(~~~, ~~~)
    ...
    router.GET("/", handler.Index) //ここにbotを渡したい!!!!!
}

(本筋に関係ない部分は適当に書いてます。また、router.GET("/", handler.Index)にbotを渡すことないだろう、というツッコミは甘んじて受け入れます。メソッドはGETでもPOSTでもなんでもいいです。ルーティングも /callbackとかの方が適切かもしれません。一つの具体例として多めに見てください。ハンドラーの名前も他にあるでしょう)←長い言い訳ですが、さて、この問題どう解決すればいいでしょうか?

ハンドラーの型に注目する

router.GET("/", handler.Index)の部分は、パッケージを分けて書いてあるわけですが、一方で関数リテラルで書く方法もあります。

main.go
router.GET("/", func (c *gin.Context) { ... })

一体どうなっているんだろう... ここで GETメソッドの中身はどうなっているんだ、と調べてみます。とくにハンドラーの型がどうなっているんでしょう。

routergruoup.go
...
// GET is a shortcut for router.Handle("GET", path, handle).
func (group *RouterGroup) GET(relativePath string, handlers ...HandlerFunc) IRoutes {
    return group.handle("GET", relativePath, handlers)
}
...

ハンドラーの部分はhandlers ...HandlerFuncとなっていますね。...が前につく型はN個(Nは0を含む自然数)の引数をとることができるというルールです。詳しくは言語仕様を読んでみましょう。

では、型HandlerFuncとはなんなのか?その定義にさかのぼってみます。

gin.go
...
// HandlerFunc defines the handler used by gin middleware as return value.
type HandlerFunc func(*Context)
...

簡潔な定義です。ここで明らかになるのは、HandlerFuncの型は関数型func(*Context)であるということです。(型Contextはgin.goで定義される構造体です。他のパッケージから参照する場合はgin.Contextと書きますので、この記事でも2つの書き方が現れます)。どうりで、ハンドラーを関数リテラルで書くときは必ず

main.go
router.GET("/", func (c *gin.Context) { ... })

というふうに書くわけです。ここからわかることは、ハンドラーの型はfunc(*Context)でなければならない、ということです。

さて、私たちが書きたい関数Xxxはどんな形をしているでしょう。以下のようになって欲しいですね。

main.go
func main() {
    ...
    bot, err := linebot.New()
    ...
    router.GET("/", handler.Xxx(bot))
    ...
}

関数Xxxの型を考えると、引数がbot *linebot.Clientであり、戻り値がfunc(*gin.Context)でなければならない。つまり、関数Xxxの宣言は次のようになるでしょう。

handler.go
func Xxx(bot *linebot.Client) func(*gin.Context) {
    ...
}

型で言うとfunc(*linebot.Client) func(*gin.Context)というふうになります。へんてこりんですね。

ハンドラーの中身を考える

さて、ハンドラーの外側がわかってきたので、いよいよ中身を考えましょう。なんやこらー、とパソコンをぶん投げたくなりますが、順番に考えましょう。関数に戻り値があるということは、当然ですが、ブロックの中でreturnされるものがあります。簡単な関数の例を考えましょう。

func succeess( x int) int {
    var y int
    y = x + 1
    return y
}

少し大げさに書きましたが、この場合は戻り値はint型であり、returnされるのはyです。宣言されている通り、yint型です。

同じように考えて、関数Xxxの戻り値はfunc(*gin.Context)型であるから、次のようなものがreturnされるはずです。

handler.go
func Xxx(bot *linebot.Client) func(*gin.Context) {
    ...
    return func(c *gin.Context){ ... }
    ...
}

こうなれば簡単ですね。 return func(c *gin.Context){ ... }のブロックの内部でbotを使うことができます。

おわりに

LINE公式のGithubリポジトリーのサンプルはパッケージが1つしかありませんでしたため、パッケージを分ける技術を模索しました。次のサイトを参考にさせていただきました。最後ですがお礼申し上げます。

Handlerに引数を追加したい時 - Qiita

Ginのコードは公式リポジトリーから引用しました。
https://github.com/gin-gonic/gin

LINE bot 公式リポジトリー
https://github.com/line/line-bot-sdk-go

7
2
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
7
2