AWS Lambdaを利用したLINEbotハンズオン〜S3に写真保存〜

はじめに

今回はLINE APIを利用した「S3に写真保存」botの作成をしていきます。

動きとしてはLINEのMessaging APIを利用して写真を送信します、写真をきっかけとしてAWS API GatewayがLambdaを起動させて、Lambdaに記述されているコードの動作(S3に写真を保存して保存された場合返信する)を行います

制作意図

9月に結婚式を挙げた友人用に考えた構築のハンズオンです(一部異なる点ありますが、ほぼほぼ)。
結婚式当日に参加した人がQRコードからLINEのお友達登録をしてもらうことで、当日撮影した写真を枚数・保存期間を気にせずS3に投げ込み、別途構築した2人のオリジナルHPから投げ込んだ写真を全員が閲覧できることを考えました。ちなみに後日でも、LINEから写真を投げるだけでOK。
実際の構築では動画・メッセージも送れるようにしていましたが、今回は写真だけに絞ってハンズオンしていきます。

構成図

スクリーンショット 2021-11-19 17.24.14.png

実装

1.LINE Developersでプロバイダーの登録(省略)

1.1 前回記事『AWS Lambdaを利用したLINEbotハンズオン〜鸚鵡返し〜』における『1.LINE Developersでプロバイダーの登録』部分を参照ください

AWS Lambdaを利用したLINEbotハンズオン〜鸚鵡返し〜

1.2 作成完了画面

スクリーンショット 2021-11-17 7.59.18.png

2.作業ディレクトリ作成(ローカル環境)

LINE公式ドキュメントより、LINE APIのSDK(ソフトウェア開発キット)のPythonをダウンロードする
※注意書きにもあるようにダウンロードは必須ではありませんが、構築が容易になるので、このQiita記事ではダウンロードしています

スクリーンショット 2021-11-06 8.45.52.png

2.1 作業ディレクトリ作成とSDKインストール

tetutetu214@mbp projects % mkdir 20211117_S3_SavePhotos
tetutetu214@mbp projects % cd 20211117_S3_SavePhotos 
tetutetu214@mbp 20211117_S3_SavePhotos % python -m pip install line-bot-sdk -t .

2.2 boto3をインストール

AWS S3のファイルを操作するために『boto3』をインストールをしていきます

tetutetu214@mbp 20211117_S3_SavePhotos % pip install boto3

2.3 「lambda_function.py」ファイルを作成する

フォルダのなかに下記ファイル「lambda_function.py」を作成する
※このファイル名以外だと読み込まれないので注意する

スクリーンショット 2021-11-18 7.54.05.png

import os
import sys
import logging

#boto3を利用できるようにimport
import boto3

from linebot import (LineBotApi, WebhookHandler)
from linebot.models import (MessageEvent, ImageMessage,TextSendMessage)
from linebot.exceptions import (LineBotApiError, InvalidSignatureError)

logger = logging.getLogger()
logger.setLevel(logging.ERROR)

#1.LINEBOTと繋げるための記述
# 環境変数からline botのチャンネルアクセストークンとシークレットを読み込む
channel_secret = os.getenv('LINE_CHANNEL_SECRET', None)
channel_access_token = os.getenv('LINE_CHANNEL_ACCESS_TOKEN', None)

#無いならエラー
if channel_secret is None:
    logger.error('Specify LINE_CHANNEL_SECRET as environment variable.')
    sys.exit(1)
if channel_access_token is None:
    logger.error('Specify LINE_CHANNEL_ACCESS_TOKEN as environment variable.')
    sys.exit(1)

# apiとhandlerの生成(チャンネルアクセストークンとシークレットを渡す)
line_bot_api = LineBotApi(channel_access_token)
handler = WebhookHandler(channel_secret)

######################################################
# boto3を利用してS3連携
# バケット名は各自命名したものを利用する
s3 = boto3.client("s3")
bucket = "XXXXXXXXXXXX"
######################################################

#2.イベントからLINEBOT署名とボディ内容を受け取る
# Lambdaのメインファンクション
def lambda_handler(event, context):

    # 検証用のx-line-signatureヘッダー
    signature = event["headers"]["x-line-signature"]
    body = event["body"]

    # リターン値の設定
    ok_json = {"isBase64Encoded": False,
               "statusCode": 200,
               "headers": {},
               "body": ""}

    error_json = {"isBase64Encoded": False,
                  "statusCode": 403,
                  "headers": {},
                  "body": "Error"}

# 画像保存(messageがImageMessageの場合の挙動)
    @handler.add(MessageEvent, message=ImageMessage)
    def message(line_event):

        # メッセージIDを抽出
        message_id = line_event.message.id

        # 画像ファイルを抽出(画像が大きい場合分割する)
        message_content = line_bot_api.get_message_content(message_id)
        content = bytes()
        for chunk in message_content.iter_content():
            content += chunk

        # 保存する画像ファイル名
        key = "2021/" + message_id + '.jpg'
        new_key = message_id[-3:]
        s3.put_object(Bucket=bucket, Key=key, Body=content)

        # 画像保存するとメッセージの返信
        line_bot_api.reply_message(line_event.reply_token,TextSendMessage(text='写真の保存に成功!'))

#例外処理としての動作
    try:
        handler.handle(body, signature)
    except LineBotApiError as e:
        logger.error("Got exception from LINE Messaging API: %s\n" % e.message)
        for m in e.error.details:
            logger.error("  %s: %s" % (m.property, m.message))
        return error_json
    except InvalidSignatureError:
        return error_json

    return ok_json

2.4 ファイルをzip化する

上述したファイルも含めて圧縮していく
スクリーンショット 2021-11-18 8.07.09.png

zipの名前は自分で管理しやすいようにネーミングする

3.S3の設定(AWSコンソール)

3.1 S3バケットの作成

1 S3コンソール画面よりバケットの作成を押下する
スクリーンショット 2021-11-19 8.13.11.png
2 設定をしていく(今回はバケット名のみ記述してデフォルト)
スクリーンショット 2021-11-19 8.14.01.png
3 バケットが作成されたのを確認する
スクリーンショット 2021-11-19 8.14.42.png

※前後していますが『2.3 「lambda_function.py」ファイルを作成する』ファイル内のバケット名は、こちらの手順で作成したバケットのことです。

4.Lambdaの設定(AWSコンソール)

1.前回記事『AWS Lambdaを利用したLINEbotハンズオン〜鸚鵡返し〜における『3.Lambdaの設定(AWSコンソール)の登録』』
部分を参照ください

※LINEとの環境変数まで設定を終えている状態

スクリーンショット 2021-11-19 8.42.45.png

 5.AWS API Gatewayを設定する

LINEからのWebhook(イベント発生時、指定したURLにPOSTリクエストで写真データを送信する仕組み)を受け取るためにAPI Gatewayを設定する

5.1 トリガーを作成する

1.Lambda画面からトリガーを追加を押下する
スクリーンショット 2021-11-19 8.42.45.png

2.トリガーの設定「API Gateway」を選択する
APIタイプはREST APIで、セキュリティはオープンを選択する

スクリーンショット 2021-11-19 9.06.55.png

「HTTP API」の方で構築したかったのですが、設定の方法が分からなかったため「REST API」を選択しています

5.2 構築が完了したことを確認する

スクリーンショット 2021-11-19 9.28.43.png

5.3 API Gatewayの設定

1.API Gatewayの名前を押下して画面を遷移する
スクリーンショット 2021-11-19 9.28.43.png

2.『アクション』を押下して『メソッドの作成』を選択する
スクリーンショット 2021-11-19 9.51.19.png

3.『アクション』から『POST』を選択する
スクリーンショット 2021-11-19 10.00.25.png

4.セットアップ
4-1.Lambdaプロキシにチェック(API Gateway側でパラメータをよしなにしてくれる程度の知識)
4-2.Lambda関数ですが遷移直後は、同リージョンにLambda関数がありませんとコメントが出たので、関数のARNから値をいれたら関数を選ぶことができるようになりました

スクリーンショット 2021-11-19 10.16.16.png

5.既存で作成されていた『ANY』を削除
スクリーンショット 2021-11-19 10.26.50.png

5.4 API Gateway メソッドリクエストの設定

1.『メソッドリクエスト』を押下する
スクリーンショット 2021-11-19 10.47.18.png

2.『メソッドリクエスト』の設定をしていく
2-1.Messaging APIリファレンスに従って、リクエストの検証をプルダウンから選択して保存する
2-2.『HTTPリクエストヘッダー』に署名の文言を入力

スクリーンショット 2021-11-19 11.19.42.png

※保存等のボタンはなく左上『メソッドの実行』から戻ります

3.『メソッドリクエスト』の設定が完了したことを確認する
スクリーンショット 2021-11-19 11.28.17.png

5.5 API のデプロイをする

1.『アクション』から『APIのデプロイ』を選択する
スクリーンショット 2021-11-19 11.38.41.png

2.デプロイの『ステージ』を選択して『デプロイ』を押下する
スクリーンショット 2021-11-19 11.39.00.png

3.自動的に『ステージ』へ遷移して、デプロイしていることが確認できる
スクリーンショット 2021-11-19 11.42.58.png

6.Lambdaの詳細設定(AWSコンソール)

6.1 API Gateway作成中に作られたAnyのAPI Gateway削除

スクリーンショット 2021-11-19 11.51.31.png

※きっと良い方法があるはずですが解決しなかったので、作ってから削除しました

6.2 APIからリソースを操作するためのロール作成

1.『設定』の『アクセス制限』から『ロール名』を押下してIAMへ遷移
スクリーンショット 2021-11-19 15.01.15.png

2.画面中央辺りの『ポリシーをアタッチします』を押下
スクリーンショット 2021-11-19 15.01.53.png

3.API GatewayからAPI操作を呼び出すための制御に関しての権限をLambdaに付与する
スクリーンショット 2021-11-19 15.26.55.png

4.LambdaからS3にオブジェクトを保存するための権限をLambdaに付与する
スクリーンショット 2021-11-19 16.18.13.png

5.ロールにポリシーがアタッチされていることを確認する
スクリーンショット 2021-11-19 16.21.17.png

6.Lambdaの画面でもロールの確認をすることができる
スクリーンショット 2021-11-19 16.22.35.png

7.LINE DevelopersにAWSの設定を登録する

7.1 作成されたAPIエンドポイントへのURLをLINE Developersに登録をする

スクリーンショット 2021-11-19 15.38.56.png

7.2 WebhookにURLをCOPYする

LINE Developersの「Messaging API設定」の「Webhook設定」にAPI Gatewayの「APIエンドポイント」のURLをCOPYする
COPYして更新後に表示される、Webhookの利用をON(緑色)にする
スクリーンショット 2021-11-06 16.02.16.png

7.3「あいさつメッセージ」の「編集」を押下して、詳細設定の__

「応答メッセージ」「オフ」にして、「Webhook」「オン」にする

スクリーンショット 2021-11-06 16.02.29.png

8 挙動の確認

8.1 QRコードから友達登録の遷移

QRコードを読み取ると、お友達登録の画面へ遷移して登録することができる
スクリーンショット 2021-11-19 16.32.40.png

8.2 LINE画面挙動の確認

LINE画面でメッセージを送信して、写真の保存が成功すると『写真の保存に成功!』というメッセージが返される
スクリーンショット 2021-11-19 16.41.03.png

8.3 S3に画面が保管されているかの確認

作成しているバケットに写真が保存されていることがタイムスタンプからも確認できます
スクリーンショット 2021-11-19 16.44.19.png

8.4 CloudWatchロググループでも確認する

1.IAM権限の際に触れませんでしたが、LambdaにCloudWatchの権限も付与されているので、CloudWatchでログの確認をすることができます
スクリーンショット 2021-11-19 16.48.35.png

2.ログイベントで2度LINE APIを利用して送付して保存されていることが確認できます
スクリーンショット 2021-11-19 16.53.18.png

9.CloudWatchロググループでエラーの挙動を確認する

1.こちらは作成中に表示されたエラーです、『PutObject』などから権限がないのかなと考えIAM周辺を確認するきっかけとなりました

スクリーンショット 2021-11-19 16.56.58.png

最後に

長丁場になりましたが、なんとかLINEから写真を送信してS3へ画像を保存することは出来ました。
『まだまだ分からない、なんとなくやってしまっている』部分も多々ありますが、作っていかなければ理解も出来ないかと思って構築しています。「ベストプラクティは「こうじゃ!」」「ちょっ、セキュアじゃねぇよ!」などありお時間があれば、なんとも厚顔無恥なお願いですがご指摘お願いします。

参照

Qiita記事
Boto3 で S3 のオブジェクトを操作する(高レベルAPIと低レベルAPI)
API Gateway + Lambda プロキシ結合の使用有無による違い
白いマスクから怪人マスクへ、AWSでサーバレスLINE写真処理アプリの開発記

ドキュメント
Boto3 ドキュメント

Last modified: 2021-11-19

Author