これなに?
LINEで、メッセージをオウム返しして返答するだけのチャットボット(良くあるやつ)。
Lambda+Go言語で、フレームワーク(serverless
)を利用しないサンプルコードが見つからなかったので投稿。
環境
Windows環境で開発・コンパイル(build)して、AWS LambdaのWeb画面でZIPファイルをuploadする、というスタイルで構築。
開発・コンパイル環境
- Windows 10 64bit
- go1.11.9.windows-amd64
- 記事執筆時点の最新は1.12.4だけれど、後述の通りbuildがうまくいかなかったので、1.11.9を利用
- フレームワークは特に利用していない
- IDE:Visual Studio Code
- 今回の記事に関係ないので、詳細割愛。
実行環境
- LINE Messaging API
- AWS Lambda(言語:Go 1.x)
- Amazon API Gateway
コード
LINE Messaging APIの設定、Lambda・API Gateway・CloudWatchの詳細な設定は特にややこしいところがないので割愛。
- Amazon API Gatewayから受け取ったイベントは、LINEのSDKを利用せずにパースしている。
- SDKを使った方がよかったのかもしれないが、GO+Lambda環境用のちょうどよいサンプルが見つからず、SDKの使い方がよくわからなかったため、結局自力でJSONをパース(自動生成されたコードだけど)。
- 本当はCHANNELSECRETとACCESSTOKENのチェックをしないとダメだけど、動作検証用なのでいったん割愛。
- main()のreturnが、Amazon API Gateway経由でLINEに返る。ここは200 OKを返せばOK。
- リプライメッセージはLINE SDKの
linebot.ReplyMessage()
を利用している。 - Lambdaの環境変数として、CHANNELSECRETとACCESSTOKENを設定している。
package main
import (
"encoding/json"
"fmt"
"log"
"os"
"github.com/aws/aws-lambda-go/events"
"github.com/aws/aws-lambda-go/lambda"
"github.com/line/line-bot-sdk-go/linebot"
)
// API Gatewayから受け取ったevents.APIGatewayProxyRequestのBody(JSON)をパースする
// https://app.quicktype.io/ に、実際に受け取ってたJSONメッセージを張り付けて、コード自動生成。
// ▼▼▼ https://app.quicktype.io/で自動生成したコード:ここから ▼▼▼
func UnmarshalLineRequest(data []byte) (LineRequest, error) {
var r LineRequest
err := json.Unmarshal(data, &r)
return r, err
}
func (r *LineRequest) Marshal() ([]byte, error) {
return json.Marshal(r)
}
type LineRequest struct {
Events []Event `json:"events"`
Destination string `json:"destination"`
}
type Event struct {
Type string `json:"type"`
ReplyToken string `json:"replyToken"`
Source Source `json:"source"`
Timestamp int64 `json:"timestamp"`
Message Message `json:"message"`
}
type Message struct {
Type string `json:"type"`
ID string `json:"id"`
Text string `json:"text"`
}
type Source struct {
UserID string `json:"userId"`
Type string `json:"type"`
}
// ▲▲▲ https://app.quicktype.io/で自動生成したコード:ここまで ▲▲▲
// Handler
// fmt.Printlnやlog.Fatalは、CloudWatchのログで確認可能
func Handler(request events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error) {
// 受け取ったJSONメッセージをログに書き込む(デバッグ用)
fmt.Println("*** body")
fmt.Println(request.Body)
// JSONデコード
fmt.Println("*** JSON decode")
myLineRequest, err := UnmarshalLineRequest([]byte(request.Body))
if err != nil {
log.Fatal(err)
}
// ボットの定義
fmt.Println("*** linebot new")
bot, err := linebot.New(
os.Getenv("CHANNELSECRET"),
os.Getenv("ACCESSTOKEN"),
)
if err != nil {
log.Fatal(err)
}
// リプライ実施
fmt.Println("*** reply")
var tmpReplyMessage string
tmpReplyMessage = "回答:" + myLineRequest.Events[0].Message.Text
if _, err = bot.ReplyMessage(myLineRequest.Events[0].ReplyToken, linebot.NewTextMessage(tmpReplyMessage)).Do(); err != nil {
log.Fatal(err)
}
// 終了
fmt.Println("*** end")
return events.APIGatewayProxyResponse{Body: request.Body, StatusCode: 200}, nil
}
func main() {
lambda.Start(Handler)
}
buildコマンド (PowerShell)
> $env:GOOS="linux"
> go env GOOS
linux
> go build -o line01 line01.go
> build-lambda-zip.exe -o line01.zip line01
実行結果
ハマったポイント
Go言語もVisual Studio Codeも初めてだったので、かなり初歩的だけれども。。
①Go言語用のLambdaパッケージ(github.com/aws/aws-lambda-go/lambda
)がbuild時に読み込まれない
事象
- go1.12 (Windows)
$GOPATH = C:\users\45lb_plates\go
$GOROOT = C:\Go
-
go get -u github.com/aws/aws-lambda-go/lambda
は実行済み
という環境でbuildを実行すると、以下のエラーとなる。
> go build -o line line.go
line.go:4:8: cannot find package "github.com/aws/aws-lambda-go/lambda" in any of:
C:\Go\src\github.com\aws\aws-lambda-go\lambda (from $GOROOT)
C:\users\45lb_plates\go\src\github.com\aws\aws-lambda-go\lambda (from $GOPATH)
exit status 1
Process exiting with code: 1
解決策
go1.12を諦め、go1.11.9をインストールしなおしたらあっさり解決。
- 実際にパッケージ
github.com/aws/aws-lambda-go/lambda
がインストールされていたのはC:\Users\45lb_plates\go\pkg\mod\github.com\aws\aws-lambda-go@v1.10.0\lambda
だった。$GOROOT\pkg\mod
配下にインストールされるのは、go1.12からの仕様?らしい。 - 一方、build時に読み込もうとしているフォルダは
$GOROOT\src
配下で、ここが不整合となっている。 - go1.12で、どうやったらbuild時に
$GOROOT\pkg\mod
を読み込ませるのかわからなかった。環境変数GO111MODULE
をon
にしたりoff
にしたりは試してみたが、改善せず。
②Windows環境でLinux用のクロスコンパイルができない
事象
コンパイル対象のOSをSETコマンドでWindowsからLinuxへ変更したいが、されない。
> set GOOS=linux
> go env GOOS
Windows
解決策
Visual Studio Codeのターミナルが「PowerShell」になっていたため、SETコマンドを受け付けてなかった。
SETコマンドはDOSプロンプトで環境変数を変更するコマンド。PowerShellでSETコマンドを打ってもエラーは出ないが、環境変数は変わらない。(Goのクロスコンパイルについて検索すると、みんなSETコマンドだったので全然気づかなかった。。)
PoweShell用の環境変数を変更するコマンドを改めて実行し、解決。
> $env:GOOS="linux"
> go env GOOS
linux
参考にした資料・利用したツール等
-
[Qiita] Golang + lambda + lineBot(serverless)
- 当初このページを参考にしていたが、
serverless
フレームワークを使わない場合は、コード等々が全然違くなることに途中で気づく。最終的にはコードは参照せず。
- 当初このページを参考にしていたが、
-
quicktype
- JSONメッセージを張り付けると、パースするコードを自動生成してくれるWEBツール
- LINE Messaging APIから送られてくるJSONメッセージをパースするコードを生成するのに使用
-
[Qiita]Go言語でJSONを扱う、[Qiita] Goで値が文字列のJSONを構造体にパースする
- JSONについてまとまってました。結局最終的には自分でコードを書かず、上記のquicktypeでコードを自動生成。
参考:Lambda受け取ったJSONメッセージを確認するには
LINE API GatewayからAWS API Gateway経由でLambda受け取ったJSONメッセージを確認する方法。
このJSONメッセージをquicktypeに張り付けると、JSONメッセージをパースするコードが自動生成される。便利!
package main
import (
"fmt"
"github.com/aws/aws-lambda-go/events"
"github.com/aws/aws-lambda-go/lambda"
)
func Handler(request events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error) {
fmt.Println(request.Body)
return events.APIGatewayProxyResponse{Body: request.Body, StatusCode: 200}, nil
}
func main() {
lambda.Start(Handler)
}
{
"events": [
{
"type": "message",
"replyToken": "704xxxx4024cxxxx9c86xxxxcdf0xxxx",
"source": {
"userId": "U8845xxxx971bxxxx6f98xxxxa428xxxx",
"type": "user"
},
"timestamp": 1599999999999,
"message": {
"type": "text",
"id": "9809999999999",
"text": "てすと"
}
}
],
"destination": "Uc082xxxxe925xxxx0a19xxxx5e31xxxx"
}
おわり。