AWS lambdaがRubyに対応したのでLINE Botを動かしてみた

一昨日(2018年11月29日)にAWS LambdaでRubyがサポートされることがアナウンスされました。
https://aws.amazon.com/jp/blogs/compute/announcing-ruby-support-for-aws-lambda/

今までお手軽にRuby実装のLINE Bot(Messaging API)を動かす環境としては、
Herokuが最有力候補だと思っていたのですが、
AWS Lambdaで動くとなると、Cold Start時のレスポンスタイムが改善されることや、
24時間実行されるような環境でも非常に低価格で実行できることなど嬉しいことがたくさんあります。
なので、今回はAWS LambdaのRubyでLINE Botのを動かす方法を解説していきたいと思います。

この記事の想定読者は、AWSを少し触ったこともあるし、LINE Botも1回は作ったこともあるという方です。
LINE Botのアカウントの作り方や、LINE Bot SDKの使い方などについてはこの記事では軽くしか触れません。

## 開発環境の構築

### AWS CLIのインストール

今回はLambdaへのデプロイにNodejsのServerlessを使用したいと思います。
https://github.com/serverless/serverless
ServerlessでAWS Lambdaにデプロイするためには、
ローカルにAWS CLIがインストールされていて、ログインしていることが必要です。

まずはじめにAWS CLIをインストールしましょう。
https://docs.aws.amazon.com/ja_jp/cli/latest/userguide/installing.html
pipを使っている方は以下のコマンドでインストール可能です。

```bash
$ pip install awscli --upgrade --user
```

次にログインが必要です。
https://docs.aws.amazon.com/ja_jp/cli/latest/userguide/cli-chap-getting-started.html

```bash
$ aws configure
```

### serverlessのインストール

次にserverlessのインストールを行います。
serverlessコマンドがAWS LambdaのRubyに対応したのは、今日(2018年12月1日)のことなので、
**おそらく皆さんアップデートorインストールが必要です。**

```bash
$ npm install -g serverless
```

## Serverless初期設定

とりあえず動かしたいんや！という方はこちらにリポジトリを用意しておいたので、
これをCloneして、[Channel Secretを取得する](#channel-secretを取得する)まで読み飛ばしてください。
https://github.com/toduq/line-bot-ruby-lambda-sample
この章には実装の注意点が書いてあるだけです。

まずはじめに雛形を作ります。 `handler.rb` や、 `serverless.yml` が作成されると思います。

```
$ serverless create --template aws-ruby
```

[LINE Bot SDK Ruby](https://github.com/line/line-bot-sdk-ruby)のReadmeに書いているサンプルコードを参考に書いていくんですが、
今回はsinatraを使わないのと、bodyやheaderの受け取り方が違うので、そのあたりを注意して実装します。

```ruby:handler.rb
require 'json'
require 'line/bot'

def client
  @client ||= Line::Bot::Client.new { |config|
    config.channel_secret = ENV["LINE_CHANNEL_SECRET"]
    config.channel_token = ENV["LINE_CHANNEL_TOKEN"]
  }
end

def webhook(event:, context:)
  signature = event['headers']['X-Line-Signature']
  body = event['body']
  unless client.validate_signature(body, signature)
    puts 'signature_error' # for debug
    return {statusCode: 400, body: JSON.generate('signature_error')}
  end

  events = client.parse_events_from(body)
  events.each do |event|
    case event
    when Line::Bot::Event::Message
      case event.type
      when Line::Bot::Event::MessageType::Text
        client.reply_message(event['replyToken'], {
          type: 'text',
          text: "Your message is\n" + event.message['text']
        })
      end
    end
  end

  {statusCode: 200, body: JSON.generate('done')}
end
```

`serverless.yml` は、HTTPのwebhookが受け取れるようにeventを設定するのと、環境変数を渡すように修正します。
お好みでregionやmemorySizeも設定してください。

```yml:serverless.yml
service: line-bot-lambda-ruby # NOTE: update this with your service name

provider:
  name: aws
  runtime: ruby2.5
  memorySize: 256
  region: ap-northeast-1
  environment:
    LINE_CHANNEL_SECRET: _replace_with_yours_
    LINE_CHANNEL_TOKEN: _replace_with_yours_

functions:
  webhook:
    handler: handler.webhook
    events:
      - http:
          path: /webhook
          method: post
```

GemfileでLINE Bot SDK Rubyを指定します。

```ruby:Gemfile
source "https://rubygems.org"

git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }

gem "line-bot-api"
```


## Channel Secretを取得する

Botを作っていない方は、LINE Developersから作りましょう。
https://developers.line.biz/ja/services/messaging-api/

LINE DevelopersからChannel SecretとChannel Tokenというのを取得します。

こんな感じの文字列です。

```
LINE_CHANNEL_SECRET: f733394bc6294020f3d0c27f5af8ba50
LINE_CHANNEL_TOKEN: yvw6MNPDi34Ha9d3g65wn420QWp//aR2RyCLyrrabEISo1Ziu以下略...
```

これを serverless.yml に書き込みます。

```yml:serverless.yml
  environment:
    LINE_CHANNEL_SECRET: f733394bc6294020f3d0c27f5af8ba50
    LINE_CHANNEL_TOKEN: yvw6MNPDi34Ha9d3g65wn420QWp//aR2RyCLyrrabEISo1Ziu以下略...
```

## いざDeploy！

Serverlessコマンドはローカルのファイルをzipにして、S3経由でAWS Lambdaを更新するという仕様のため、
ローカルのディレクトリに `bundle install` の結果が必要です。
ネイティブライブラリを使用するようなGemではMacやWindowsで `bundle install` したものを
そのままAWS Lambdaで動かすのは無理かもしれませんが、
今回はLINE Bot SDK Rubyだけなので大丈夫でした。

```bash
$ bundle install --path vendor/bundle
```

そして、serverlessでデプロイ！
Lambdaと一緒に、API Gatewayのルーティングなども設定してくれるのでとっても便利です。

```bash
$ serverless deploy

...省略...

endpoints:
  POST - https://di2fhs9dnds0.execute-api.ap-northeast-1.amazonaws.com/dev/webhook

...省略...
```

エンドポイントが作成されたと思うので、これを先程のLINE DevelopersのWebhook URLというのに登録します。

これで準備は完了なので、LINEからメッセージを送ってみましょう。
こんな感じにメッセージが帰ってきたらバッチリです :tada:

![Screenshot_20181201-204032_01.png](https://qiita-image-store.s3.amazonaws.com/0/51799/c02797f6-442e-cb74-f0f9-e2d7211fe1bc.png)

Cold Startしたときでも、Herokuみたいにタイムアウトすることなく2秒以内にレスポンスが帰ってくるので、
非常に快適なBot開発ライフが送れると思います :star2: 

## 動かなかったときのよくある原因

まずは `serverless logs -f webhook` してみましょう。ログが見れます。

### Channel SecretやChannel Tokenを変更し忘れている

`signature_error` という文字列が出ていると思います。
ちゃんと、変更できているか確認してみてください。

### bundle installできていない

`--path vendor/bundle` をつけて実行したかもう一度確認してみてください。