はじめてのAWS Lambdaを用いたLINEbot作成！

## 概要

サーバーレスコンピューティングを実現するAWSサービスであるAWS Lambdaを勉強するため、LINEbotアカウントを作成するために使うMessaging APIとHTTPリクエストを送るためのPythonプログラムを用いて、簡単なLINEbotシステムの構築を行います。

## AWS Lambda

一般的に、インターネット上でプログラムを実行させるためにはインターネット上にサーバーを準備し、実行環境やプログラムを用意する必要があります。この場合、プログラムを実行するためにサーバー構築やインフラに関する知識や操作の手間が必要です。

これに対し、サーバーやインフラを準備する事なくプログラムをデプロイし実行することを**サーバーレスコンピューティング**と呼ばれています。AWS Lambda（ラムダと呼びます）はサーバーレスコンピューティングを実現するAWSのサービスです。AWS Lambdaを利用することで、エンジニアはプログラム開発のみに専念し、インターネット上でプログラムの実行が可能になります。

### AWS Lambdaのメリット
Lambdaの利用により、以下の利点があります。

- エンジニアはサーバーやインフラ等の実行環境に関する準備が**不要**であるため、プログラム開発に専念できる
- プログラムが**実行されている時間**だけに課金される
- AWSの他サービスで発生したイベントをトリガーとしてプログラムを実行することが可能
- Python以外にもNode.js、Ruby、Java、Golang、.NETが利用が可能

## Messaging API

LINE公式が提供するチャットボットを作成するためのAPIです。Messaging APIを使用することで、LINEとボットサーバー間でメッセージデータの交換を行うことができます。

## 全体構成図

![env.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/682975/c51c0404-391b-427c-c9f8-1f942451c73a.jpeg)


### チャットボット通信の流れ

1. ユーザーが、LINEbotアカウントにメッセージを送信する
2. LINEプラットフォームからボットサーバー（今回はAWS）へWebhookイベント(HTTPリクエスト)が送信される
3. Webhookの内容に応じてボットサーバーからユーザにLINEプラットフォームを介して応答メッセージが帰る

今回は、LINEからWebhookを受け取り、おみくじの結果を返すプログラムをPythonで作成し、AWS Lambdaを用いてボットサーバーとして実行させます。

## LINE側の設定

### Botアカウントの作成

以下のサイトを参考にBotアカウントを作成します。

https://developers.line.biz/ja/docs/messaging-api/getting-started/

:::note warn
アカウント作成後、「Messaging API設定」タブで「応答メッセージ」と「あいさつメッセージ」の設定が「無効」になっていることを確認してください。
:::

## AWS Lambdaの設定

1. AWS Lambdaの画面から「関数の作成」をクリックし、任意の名前、ランタイムをPython3.9として関数を作成します。

    ![lambda_make.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/682975/19a15be7-420a-6af4-2951-4c928d75ac7c.png)
    
2. 上記で作成した関数の詳細画面を開き、「トリガーを追加」をクリックし、API Gatewayと紐付けます。なお、APIタイプはHTTP API、セキュリティはOpen（ネットワークの任意の場所からアクセス可能）にします。
    ![trigger.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/682975/ef8ea04f-31dd-7ad7-1bdf-1ef4a5ade8a4.png)

3. 以下の画像の通りAPI Gatewayと紐付ける事ができれば完了です。
    

    ![lambda_result.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/682975/e54531d3-346e-45b1-721b-f0fce737e011.png)


### API Gateway

API GatewayはAWS上で構築したアプリケーションのインターフェースとなるAPI（Application Programming Interface）を公開・管理するサービスです。今回は、APIでHTTPリクエストを受け取った時にLambda関数を実行できるよう、トリガーとしてAPI Gatewayを指定しています。

## コードのデプロイ

関数の詳細画面の「コード」タブでコードを記述し、デプロイして反映させます。

```python
import datetime
import json
import os
import random
import re
from urllib import request, error

# チャネルアクセストークン（長期）
CHANNEL_ACCESS_TOKEN = os.getenv("channelAccessToken")

# リクエスト送付先
URL = 'https://api.line.me/v2/bot/message/reply'

# AWS Lambdaで実行される関数（関数ハンドラ）
def Lambda_handler(event, context):
    for message_object in json.loads(event['body'])['events']:
        # 応答用リクエストヘッダの作成
        headers = {
            'Content-Type': 'application/json',
            'Authorization': 'Bearer ' + CHANNEL_ACCESS_TOKEN
        }

        # 応答用リクエストボディの作成
        body = {
            # 応答用トークン
            'replyToken':message_object['replyToken'],
            # 応答メッセージ
            'messages':[
                {
                    'type':'text',
                    'text':'あなたの今日の運勢は' + omikuji(message_object['source']['userId']) + 'です！！',
                },
            ],
        }

        # POSTリクエスト送信
        try:
            reply_request = request.Request(URL, data=json.dumps(body).encode('utf-8'), headers=headers)
            with request.urlopen(reply_request) as res:
                body = res.read()

        except error.HTTPError as err:
            # HTTPステータスコードの4xx番または5xx番が帰ってきた場合
            print(err.code)

        except error.URLError as err:
            # HTTP通信に失敗したとき
            print(err.reason)

    return {
        'statusCode': 200,
        'body': json.dumps('Hello from Lambda!')
    }

# おみくじの結果を決定する関数
def omikuji(userId :str) -> str:
    omikuji = ["大吉", "吉", "中吉", "小吉", "末吉", "凶", "大凶"]

    # userIdと日付の数字を用いて乱数用シードを作成
    d_today = datetime.date.today()
    # ユーザID内に含まれる数字を抽出し、連結した値と年月日の各値を足した値をseedとした
    seed = int(re.sub(r"\D", "", userId)) + int(d_today.year) + int(d_today.month) + int(d_today.day)
    random.seed(seed)

    # 乱数を用いておみくじの結果を返す
    return omikuji[random.randrange(len(omikuji))]
```

### Lambda関数を使用する際の注意点

- イベント発生時にAWS Lambdaにより呼び出される関数（関数ハンドラ）とその関数が記述されているファイル名は予め指定されています。任意の値に変更する場合はLambdaのランタイム設定から関数ハンドラを指定します。

| 名前 | デフォルト名 |
| --- | --- |
| デフォルトファイル名 | lambda_function |
| デフォルト関数名 | Lambda_handler |

- イベント発生時、lambdaは以下の2つの変数を関数ハンドラに引数として渡します。コード上で2つの変数を引数として取得できるよう定義する必要があります。

| パラメータ | 説明 |
| --- | --- |
| event | Lambdaのトリガーとなったイベント情報が入ります。呼び出し元サービスに応じて内容が変化し、API Gatewayを使用した場合のHTTPリクエストの中身もこの変数から取得することができます。 |
|  context  | 実行中のLambda関数に関するランタイム情報が入ります。 |

- 関数ハンドラ内のreturn構文で値を返すときはjson形式で返す必要があります。json形式ではない場合、ランタイムエラーが発生します。

## チャネルアクセストークンの設定

チャネルアクセストークンは、Messaging APIを呼び出すときに必要となる文字列です。リクエストヘッダに記載する必要がありますが、セキュリティの観点からコード上に書かず、環境変数として読み込みます。

LINE Developersに移動し、BotアカウントのMessaging API設定下部に「チャネルアクセストークン（長期）」の項目があります。この部分にある文字列をコピーします。

AWSコンソールに移動し、Lambda関数の設定から環境変数を選択し、環境変数を追加します。

| キー | 値 |
| --- | --- |
| channelAccessToken | チャネルアクセストークン（長期）からコピーした文字列 |


![param.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/682975/cf47671d-6918-4ff9-ff44-aadfce58351e.png)


## Webhook用URLの設定

API Gatewayをトリガーとして設定が完了すると、APIエンドポイントが作成されます。このAPIエンドポイントに対してHTTPリクエストを送信するとトリガーが発火しLambda関数が実行されます。
また、このAPIエンドポイントがLINEプラットフォームがWebhookとして送信する宛先URLになります。

![apiendpoint.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/682975/6a535f78-d485-07ff-72f9-15cda304669e.png)


LINE Developersに移動し、BotアカウントのMessaging API設定内のWebhook URLにAPIエンドポイントのURLを入力します。入力後、Webhookの利用をONに変更し、「検証」ボタンをクリックし図のように成功と表示されればOKです。

![webhook_test.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/682975/ff497399-2b69-53af-d872-d3fad9a9e667.png)


## LINEで動作確認

以上ですべての設定が完了しました。実際にBotアカウントに対してメッセージを送信すると、メッセージの内容によらずおみくじの結果が帰ってくると思います！

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/682975/0fab6be3-4263-d89a-a53a-b777f15cd29c.png" width="40%">


補足：おみくじの内容は乱数を用いて決定しています。乱数作成にあたり、seed（乱数作成時のベースとなる値）をユーザIDと日付を用いて決定しているため、１日を通して同じ結果が表示されます。

## 上手く行かないとき

AWS Lambdaが動作した時のログはすべて保存され、AWS上のサービス監視ツールであるAmazon CloudWatchを通じて参照する事ができます。

Lambda関数の詳細ページ内のモニタリングタブからログを確認する事ができます。

![log.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/682975/1ab06504-b522-9085-8a22-30d3edfa5e81.png)


## まとめ

今回はAWS LambdaとMessaging APIを用いて簡単なシステムを構築しました。AWS Lambdaを用いることで、サーバーやインフラを準備する事なく、ソースコードだけでオンラインアプリケーションを作成する事ができました。

またAWS Lambdaを利用する際のソースコードに関しても制約も少なく、使用する変数もわかりやすい内容であるため、使用に対する障壁も低いと感じました。

## 参考文献

https://developers.line.biz/ja/reference/messaging-api/

https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/python-handler.html

https://docs.python.org/ja/3.9/library/urllib.html