1. Qiita
  2. 投稿
  3. GAE

GAEにgolangでlinebotをデプロイする

  • 4
    いいね
  • 2
    コメント

背景

以下を参考にさせてもらいました。
LINE Developer Trialを GAE/Go で始める #linedevday

ソース

fk2000/linebot-gae-golang

GAE環境の準備

  1. GAE/Go SDKをダウンロードする
  2. Cloud Consoleで新しいプロジェクトを作成してprojectIDをメモする

LINE Developers設定

MessageAPIを始める

  1. LINE BUSINESS CENTERでMessageAPIを始める
  2. Bot設定から[APIを利用する]ボタンを押して有効したのち、bot設定を以下のように設定する
    項目 設定
    Webhook送信 利用する
    Botのグループトーク参加 利用する
    自動応答メッセージ 利用しない(デフォルトは利用する)
    友だち追加時あいさつ 利用する
  3. LINE Developersで「Channel Secret」と「Channel Access Token」をメモする1
  4. 同じ画面の下の方に、EDITとあるので編集画面に行きWebhookにhttps://<projectID>.appspot.com/callbackと設定する

SDKをインストール

go getでインストール

$ go get -u github.com/line/line-bot-sdk-go/linebot
$ go get -u github.com/joho/godotenv
$ go get -u google.golang.org/appengine

GAEにdeployするための、app.yamlを作成

$ mkdir linebot;cd linebot
$ vi app.yaml

<projectID>にprojectIDを入力してください。

app.yaml
python:
application: <projectID>
version: 1
runtime: go
api_version: go1

handlers:
- url: /task.*
  script: _go_app
  login: admin
  secure: always
- url: /.*
  script: _go_app
  secure: always

line.envを作成する

「Channel Secret」と「Channel Access Token」を環境変数として入力します。

このファイルの取り扱いには注意が必要です。ソースコードをgithubで保管する場合は.gitignoreに/line.envと記入してpushされないようにしてください。

line.env
LINE_BOT_CHANNEL_SECRET=[Your secret]
LINE_BOT_CHANNEL_TOKEN=[Your Channel Token]

app.goを作成する

LINEでテキストを送信すると「ok」と返すだけのコードです。
※ソースコードの解説は省きます。

app.go
package main

import (
    "encoding/base64"
    "encoding/json"
    "net/http"
    "net/url"
    "os"

    "google.golang.org/appengine"
    "google.golang.org/appengine/log"
    "google.golang.org/appengine/taskqueue"
    "google.golang.org/appengine/urlfetch"

    "golang.org/x/net/context"

    "github.com/joho/godotenv"
    "github.com/line/line-bot-sdk-go/linebot"
    "github.com/line/line-bot-sdk-go/linebot/httphandler"
)

var botHandler *httphandler.WebhookHandler

func init() {
    err := godotenv.Load("line.env")
    if err != nil {
        panic(err)
    }
    botHandler, err = httphandler.New(
        os.Getenv("LINE_BOT_CHANNEL_SECRET"),
        os.Getenv("LINE_BOT_CHANNEL_TOKEN"),
    )
    botHandler.HandleEvents(handleCallback)

    http.Handle("/callback", botHandler)
    http.HandleFunc("/task", handleTask)
}

func newLINEBot(c context.Context) (*linebot.Client, error) {
    return botHandler.NewClient(
        linebot.WithHTTPClient(urlfetch.Client(c)),
    )
}

// newContext は appengine.NewContext を短く書くための関数
func newContext(r *http.Request) context.Context {
    return appengine.NewContext(r)
}

// logf は log.Infof を短く書くための関数
func logf(c context.Context, format string, args ...interface{}) {
    log.Infof(c, format, args...)
}

// errorf は log.Errorf を短く書くための関数
func errorf(c context.Context, format string, args ...interface{}) {
    log.Errorf(c, format, args...)
}

// Webhook を受け取って TaskQueueに詰める関数
func handleCallback(evs []*linebot.Event, r *http.Request) {
    c := newContext(r)
    ts := make([]*taskqueue.Task, len(evs))
    for i, e := range evs {
        j, err := json.Marshal(e)
        if err != nil {
            errorf(c, "json.Marshal: %v", err)
            return
        }
        data := base64.StdEncoding.EncodeToString(j)
        t := taskqueue.NewPOSTTask("/task", url.Values{"data": {data}})
        ts[i] = t
    }
    taskqueue.AddMulti(c, ts, "")
}

// 受け取ったメッセージを処理する関数
func handleTask(w http.ResponseWriter, r *http.Request) {
    c := newContext(r)
    data := r.FormValue("data")
    if data == "" {
        errorf(c, "No data")
        return
    }

    j, err := base64.StdEncoding.DecodeString(data)
    if err != nil {
        errorf(c, "base64 DecodeString: %v", err)
        return
    }

    e := new(linebot.Event)
    err = json.Unmarshal(j, e)
    if err != nil {
        errorf(c, "json.Unmarshal: %v", err)
        return
    }

    bot, err := newLINEBot(c)
    if err != nil {
        errorf(c, "newLINEBot: %v", err)
        return
    }

    logf(c, "EventType: %s\nMessage: %#v", e.Type, e.Message)

    m := linebot.NewTextMessage("ok")
    if _, err = bot.ReplyMessage(e.ReplyToken, m).WithContext(c).Do(); err != nil {
        errorf(c, "ReplayMessage: %v", err)
        return
    }

    w.WriteHeader(200)
}

デプロイする

$ goapp deploy

もしここで、

ERROR appcfg.py:2411 An error occurred processing file '': HTTP Error 403: Forbidden Unexpected HTTP status 403. Aborting.

と出たら、ホームディレクトリに.appcfg_oauth2_tokensが生成されているためなので、このファイルを削除してdeployを再実行してください。うまくいくとブラウザが開き、googleアカウントでログインする画面が表示されます。

ログインするとデプロイ処理が進みます。Compile failedが出る場合はソースコードに誤りがあります。直して再デプロイしてください。Deployment successful.と表示されたら成功です。

GCP Consoleでロギングを確認。

スクリーンショット 2016-10-18 13.04.00.png

「!!」アイコンの行を展開すると、最下段に

ReplayMessage: linebot: APIError 403 Access to this API denied due to the following reason: Your ip address [Your IP address] is not allowed to access this API. Please add your IP to the IP whitelist in the developer center.

と表示されているのがわかります。

これはLINE DevelopersのServer IP Whitelistに[Your IP address]が登録されていないために、GCPからLINE APIの使用が許可されていないことを表すエラーメッセージとなります。

IP AddressをServer IP Whitelistに登録

10月18日時点の状態です。現在は制限が無く、IP Whitelistへの追加は不要かもしれません。

LINE DevelopersのServer IP WhitelistにGCPで確認したIP Addressを追加します。2

line_dev.png

LINEで友達登録しテキスト送信

スクリーンショット 2016-10-18 13.16.56.png


  1. Messaging APIになってから、「Channel ID」と「MID」は使用しなくなりました。 

  2. IP Addressは複数あるので/24か/30のレンジで登録します。