Chalice を使って AWS Lambda 上に LINE Bot 用の Webhook を作成する

# はじめに

掲題通りです。
多くの記事では LINE Bot の応答を返すサーバーは Heroku の上に構築されていますが、API Gateway + AWS Lambda を利用してサンプルを構築してみたのでそのメモになります。


## コンテンツ

以下の方法で作成した Chalice の line-bot プロジェクト内で動作を確認。
LINEの設定などで分からない点は、末尾の参考URLなどを参照ください。

```bash
$ pipenv install --python 3.7
$ pipenv install chalice line-bot-api
$ pipenv run chalice new-project line-api
```

ここで作成された `app.py` , `requirements.txt` , `.chalice/config.json` を以下の通り修正します。
なお、ソースコードは [line-bot-apiのGithub](https://github.com/line/line-bot-sdk-python) から持ってきた「ユーザーの発言と全く同じものを応答するbotの実装」になります。

```python:app.py
#!/usr/bin/python 
# -*- coding: utf-8 -*-

import os
import re
import logging

from chalice import Chalice
from chalice import BadRequestError

from linebot import LineBotApi, WebhookHandler
from linebot.exceptions import InvalidSignatureError
from linebot.models import MessageEvent, TextMessage, TextSendMessage

logger = logging.getLogger()
app = Chalice(app_name='line-api')
handler = WebhookHandler(os.environ.get('LINE_CHANNEL_SECRET'))
linebot = LineBotApi(os.environ.get('LINE_CHANNEL_ACCESS_TOKEN'))

@app.route('/callback', methods=['POST'])
def callback():
    try:
        request = app.current_request

        # get X-Line-Signature header value
        signature = request.headers['x-Line-Signature']

        # get request body as text
        body = request.raw_body.decode('utf8')

        # handle webhook body
        handler.handle(body, signature)
    except Exception as err:
        logger.exception(err)
        raise BadRequestError('Invalid signature. Please check your channel access token/channel secret.')

    return 'OK'


def _valid_reply_token(event):
    '''
    Webhook のテスト時には reply token が 0 のみで構成されているので、
    その時に False を返します
    '''
    return not re.match('^0+$', event.reply_token)


@handler.add(MessageEvent, message=TextMessage)
def reply_for_text_message(event):
    ''' テキストメッセージを受け取った場合の応答 '''
    if not _valid_reply_token(event):
        return

    linebot.reply_message(
        event.reply_token,
        TextSendMessage(text=event.message.text))
```

```text:requirements.txt
line-bot-sdk==1.16.0
```

```json:.chalice/config.json
{
  "version": "2.0",
  "app_name": "line-api",
  "stages": {
    "dev": {
      "api_gateway_stage": "api",
      "environment_variables": {
        "LINE_CHANNEL_SECRET": "チャネルシークレットを入力",
        "LINE_CHANNEL_ACCESS_TOKEN": "チャネルアクセストークン（ロングターム）を入力"
      }
    }
  }
}

```

上記の内容を書き込んだ後、`pipenv run chalice deploy` にて自分のAWSアカウント上にデプロイを行う。

```bash
$ pipenv run chalice deploy
Creating deployment package.
Updating policy for IAM role: line-api-dev
Updating lambda function: line-api-dev
Updating rest API
Resources deployed:
  - Lambda ARN: arn:aws:lambda:ap-northeast-1:************:function:line-api-dev
  - Rest API URL: https://********.execute-api.ap-northeast-1.amazonaws.com/api/
```

この後、ここでデプロイした API URL の `/callback` を、この場合は `https://********.execute-api.ap-northeast-1.amazonaws.com/api/callback` をLINE Messaging API の Webhook URL に設定・検証します。
検証の結果「成功」が返ってくれば正常に設定できていますので、LINE で自分のチャネルと会話をして、自分の発言をオウム返ししてくれるか否かを確認してください。


## 実装していて引っかかった点

- request コンテンツの取り方が Flask と Chalice で異なる
    - Flask: `from flask import request` してから `request.headers` や `request.get_data()` で利用する
    - Chalice: `app.current_request` に入っている。 `app.current_request.headers` や `app.current_request.raw_body` でコンテンツを参照する
- Webhook の検証結果が正常に終了せず、"ボットサーバーから200以外のHTTPステータスコードが返されました" と出てしまう
    - LINE Bot の Webhook URL を書き換えてすぐ検証した場合、新しいURLが反映されていないことがあった
    - 検証時に利用されるリプライトークンは 0 埋めのものが返されてしまうので、Githubのサンプルの通りにやると検証はOKにならない
    - 詳しくは [Qiita - LINE DevelopesのWebhook URLの接続確認でエラーが出る件について](https://qiita.com/q_masa/items/c9db3e8396fb62cc64ed) で説明されている通り、検証時の場合は何もせずに処理を打ち切ればよい
        - このプログラムでは `_valid_reply_token` 関数で判定
 
# 参考

LINE Developers内でチャネルを作成・設定したり、トークンを作るところは別のやってみた記事で説明されていますので、それらの記事も合わせてご参照下さい。

- [Qiita - PythonでLine botを作ってみた](https://qiita.com/kro/items/67f7510b36945eb9689b)
- [Qiita - LINE DevelopesのWebhook URLの接続確認でエラーが出る件について](https://qiita.com/q_masa/items/c9db3e8396fb62cc64ed)
- [Github - LINE Messaging API SDK for Python](https://github.com/line/line-bot-sdk-python)