検索してたどり着いたみなさん、Gin やってますか?私は最近Go X Gin X LINE bot なサービスを作ろうとして結構悩みました。この経験を共有できたらいいな、と記事を書いています。
困ったこと:router.GET("/", handler.Index) に引数がない!
Goを始めてWebアプリを作ろうとしてGinに手を出した人には共感してもらえる(かもしれない)と思うんですが、私にはとても不思議に思えることがありました。アプリのルーティングを設定する場所に、Tour of Goとかでは見たことがない書き方が現れるのです。たとえば、こんな感じですね。
func main() {
router := gin.Defualt()
...
router.GET("/", handler.Index)
...
}
素人の視点からすると、handler.Indexってなんなんだ!なんでカッコが付いてないんだ!とうならざるを得ません。そしてさらに悪いことに、LINE botを使おうとしていた私はドツボにハマることになります。LINE botはGoのためのSDKを用意していて、そのドキュメント(から類推するところ)によると最初に bot client instanceを作る必要があります。インスタンスは連発するとメモリ食いそうだから最初に呼び出したものを使いたい...
func main() {
bot, err := linebot.New(<channel secret>, <channel access token>)
...
}
うーん、困った。ハンドラーでbotのメソッドを使いたいのに、ハンドラーには引数がないからbotインスタンスを渡せない...
整理すると、
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)
の部分は、パッケージを分けて書いてあるわけですが、一方で関数リテラルで書く方法もあります。
router.GET("/", func (c *gin.Context) { ... })
一体どうなっているんだろう... ここで GET
メソッドの中身はどうなっているんだ、と調べてみます。とくにハンドラーの型がどうなっているんでしょう。
...
// 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
とはなんなのか?その定義にさかのぼってみます。
...
// HandlerFunc defines the handler used by gin middleware as return value.
type HandlerFunc func(*Context)
...
簡潔な定義です。ここで明らかになるのは、HandlerFunc
の型は関数型func(*Context)
であるということです。(型Context
はgin.goで定義される構造体です。他のパッケージから参照する場合はgin.Context
と書きますので、この記事でも2つの書き方が現れます)。どうりで、ハンドラーを関数リテラルで書くときは必ず
router.GET("/", func (c *gin.Context) { ... })
というふうに書くわけです。ここからわかることは、ハンドラーの型はfunc(*Context)
でなければならない、ということです。
さて、私たちが書きたい関数Xxx
はどんな形をしているでしょう。以下のようになって欲しいですね。
func main() {
...
bot, err := linebot.New()
...
router.GET("/", handler.Xxx(bot))
...
}
関数Xxx
の型を考えると、引数がbot *linebot.Client
であり、戻り値がfunc(*gin.Context)
でなければならない。つまり、関数Xxx
の宣言は次のようになるでしょう。
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
です。宣言されている通り、y
はint
型です。
同じように考えて、関数Xxx
の戻り値はfunc(*gin.Context)
型であるから、次のようなものがreturn
されるはずです。
func Xxx(bot *linebot.Client) func(*gin.Context) {
...
return func(c *gin.Context){ ... }
...
}
こうなれば簡単ですね。 return func(c *gin.Context){ ... }
のブロックの内部でbot
を使うことができます。
おわりに
LINE公式のGithubリポジトリーのサンプルはパッケージが1つしかありませんでしたため、パッケージを分ける技術を模索しました。次のサイトを参考にさせていただきました。最後ですがお礼申し上げます。
Ginのコードは公式リポジトリーから引用しました。
https://github.com/gin-gonic/gin
LINE bot 公式リポジトリー
https://github.com/line/line-bot-sdk-go