Go入門!?LineAPIとGOでオウム返しbotを作る
以下のサイトを参考にしながら、LineAPIとGolangで何かを作るのを目標に一進一退で1日頑張ってみる。
まとめ
この記事で以下のことができるようになります。
- Line Massage APIを使って
- AWS APIGW + EC2 の環境で
- Go言語を使って
- オウム返しするLineBOTが作れる
今回の前提となっている私の開発環境は、mem. golangに入門してみた時にやったことを参照。
参考サイトメモ
Go + LINE Messaging APIで東京メトロ運行情報botを作る
Go言語を使用して簡単なLineBotを作る
Goアプリケーションを初めてawsにデプロイした話
ざっくりとした手順
- Line周りの準備
- LineSDKをローカル環境にインストール
- SDKのサンプルソースを3行書き換え
- deploy先となるEC2インスタンスを用意
- ソースのビルドとEC2への配布
- AWS APIGWの設定
- はまり対策・・・
では、上記の順に実施していきます。
Lineアカウントの準備
LineAPIを使うためには、Lineビジネスアカウントが必要、ということなので、作る。
- Line for Business公式からアカウントを開設。今回はメールアドレスで未認証アカウントを作った。
- 開設し、ログイン後に右上の設定ボタンからたどり着く画面で、Messaging APIを選択して、個人のLineアカウントと連携(止むを得ず)
- Line Official account Managerにログイン
- Line Developpersでシークレットキーとアクセストークンを確認(後で使う)
Line SDKのインストール
Line Botのサンプルアプリがgitに公開されているため、それを利用する。
go getでgitからダウンロード。今回はローカルの開発環境に。
$ go get github.com/line/line-bot-sdk-go
ソースの修正
サンプルコードを修正する。
github.com/line/line-bot-sdk-go/examples/echo_bot/server.go
の
25−32行目にあるシークレットキーとトークンを、上記で準備したアカウントのものに置き換える。
- 修正前
func main() {
bot, err := linebot.New(
os.Getenv("CHANNEL_SECRET"),
os.Getenv("CHANNEL_TOKEN"),
)
if err != nil {
log.Fatal(err)
}
- 修正後
func main() {
bot, err := linebot.New(
"8b3-----------256",
"TPr----------FU=",
)
if err != nil {
log.Fatal(err)
}
58行目のポートを変更する。
- 変更前
// This is just sample code.
// For actual use, you must support HTTPS by using `ListenAndServeTLS`, a reverse proxy or something else.
if err := http.ListenAndServe(":"+os.Getenv("PORT"), nil); err != nil {
log.Fatal(err)
}
- 変更後
// This is just sample code.
// For actual use, you must support HTTPS by using `ListenAndServeTLS`, a reverse proxy or something else.
if err := http.ListenAndServe(":8080", nil); err != nil {
log.Fatal(err)
}
EC2インスタンスの用意
今回は試しにAWS EC2で動かしてみる。
Goはクロスコンパイルが簡単にできるらしいので。
まずは簡単なアプリhello
を動かして、環境に問題ないことを確認する。
主な流れは以下。
- AmazonLinuxの無料枠でインスタンス(t2.micro)を作る
- 8080ポートを開ける
- アプリ
hello
をビルド - ビルド済みソースをEC2へ配置
- ブラウザから起動確認。
Helloソースのビルド
以下のHelloソースを作る
package main
import (
"fmt"
"net/http"
)
func handler(writer http.ResponseWriter, request *http.Request) {
fmt.Fprintf(writer, "Hello World, %s ", request.URL.Path[1:])
}
func main() {
http.HandleFunc("/", handler)
http.ListenAndServe(":8080", nil)
}
EC2用にビルド
$ GOOS=linux GOARCH=amd64 go build hello.go
EC2へ配置
ビルドするとhello
という実行ファイルができるのでこれをEC2へアップする。
参考までアップロードコマンドをメモ。
scp -i [秘密鍵] [転送するファイルのパス] [EC2ユーザー名]@[パブリックDNS]:[コピー先のパス]
実際のアップロード
scp -i ~/Desktop/aws/xxx.pem ./hello ec2-user@ecxxxxxxxxxx.ap-northeast-1.compute.amazonaws.com:/home/ec2-user
ブラウザから確認
ブラウザから以下にアクセス。
画面が出ればOK。
サンプルソースのコンパイルと配布
では、EC2側の環境の確認ができたので、先ほど修正したサンプルアプリを配布する。
まずコンパイル。
$ GOOS=linux GOARCH=amd64 go build server.go
そしてアップロード
$ scp -i ~/Desktop/aws/xxxxxxx.pem ./server ec2-user@ec2-xxxxxxxx.ap-northeast-1.compute.amazonaws.com:/home/ec2-user
と・・・ここまでやって、LineのWebhookがsslのみ対応ということがわかった。。
そして、オレオレ証明書も無理とのこと。。。
AWS APIGWを使う
解決策としてAWSのAPIGWをI/Fとして使うことにした。
- API名:LineBot-goEC2で作成
- アクション>リソースの作成から
/callback
のリソースを作成 - メソッド
POST
を作る - POSTのメソッドリクエストは変更なし
- 認証:なし
- リクエストの検証:なし
- API キーの必要性:false
- 統合リクエストにEC2への転送設定を入れる
- 統合タイプ:http
- HTTPメソッド:POST
- エンドポイントURL:EC2のURL:8080/callback(
http://ec2-xxxxxxxx.ap-northeast-1.compute.amazonaws.com:8080/callback
) - コンテンツの処理:パススルー
- アクション>APIのデプロイ でAPIをprod環境へデプロイ
上記設定ののち、できたステージ(https://xxxxxxxx/prod
)をLineに登録。
- Messaging API:Webhook URLに登録
https://xxxxxxxx:443/prod/callback
- 念のため、443をつけておく
これだけでは動かない。。。
上記でlineのBot情報を参照しLineからコメントをしてみるも動かず。。。
ここでかなりはまりました。。。
APIGWでリクエストヘッダー含む全てをログ出力するようにしてデバッグ。
結果、わかったことは・・・
APIGWがリクエスト転送する際にリクエストヘッダーを書き換えている。(知っている人には常識なのかも)なので、リクエストヘッダーが以下のように変化しています。
- LINE -> APIGW のリクエストヘッダ
(xxxxxxxxxxxxxx) Method request headers: {X-Line-Signature=1+WxxxxxxxxxxxxxxSA=, User-Agent=LineBotWebhook/1.0, X-Forwarded-Proto=https, X-Forwarded-For=xxxxxxxxxxxxxx, Host=xxxxxxxxxxxxxx.ap-northeast-1.amazonaws.com, X-Forwarded-Port=443, X-Amzn-Trace-Id=Root=1-5xxxxxxxxxxxxxx716, accept=*/*, Content-Type=application/json;charset=UTF-8}
- APIGW -> EC2 のリクエストヘッダ
(xxxxxxxxxxxxxx) Endpoint request headers: {x-amzn-apigateway-api-id=xxxxxxxxxxxxxx, Accept=application/json, User-Agent=AmazonAPIGateway_xxxxxxxxxxxxxx, X-Amzn-Trace-Id=Root=1-5xxxxxxxxxxxxxx716, Content-Type=application/json}
上記の通りX-Line-Signature=1+WxxxxxxxxxxxxxxSA=
部分が消されてしまいます。LineBotのサンプルプログラムとSDKでは、このX-Line-Signature
を使って署名の検証をしているので、この検証の部分でエラー扱いされて、常にレスポンス400を返すようになっていました。。
参考:LINE Messaging APIでX-Line-Signatureの署名検証を行う(AzureFunctions/Node.js)
修正・対策方法
このエラーハンドリングをしていたのは、webhook.go
でした。
この38−42行目をコメントアウトすることで一旦回避。
/*
if !validateSignature(channelSecret, r.Header.Get("X-Line-Signature"), body) {
return nil, ErrInvalidSignature
}
*/
完全に一時的な回避策ですので、本来はAPIGW側でリクエストヘッダを引き継ぐ処理などを入れるのだと思います。どなたかわかれば教えてください。
完成!!
ということで、修正したモジュールを再配置したらオウム返しBOTができました!!
APIGWのログ解析の設定ができず、ここだけで半日くらいかかりました。。。
このあたりを参考に。
新機能】Amazon API Gateway でアクセスログを記録する #reinvent
余談
半日くらいはまっていたののもう一つの理由に作ったGoプログラムがログを出さないというのもありました。
暫定として標準出力にログを出すコードを埋め込んだ。
これは割と便利だったのでメモ。
以下の感じでmainにベタ貼りして使っていました。
colog.SetDefaultLevel(colog.LDebug)
colog.SetMinLevel(colog.LTrace)
colog.SetFormatter(&colog.StdFormatter{
Colors: true,
Flag: log.Ldate | log.Ltime | log.Lshortfile,
})
colog.Register()
log.Printf("info: start server")