0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

API Gateway + Lambda + S3で画像・PDFアップロードAPIを作ってみた

Posted at

はじめに

画像をS3バケットにアップロードする場合であれば、AWS CLIコマンドの実行でアップロードできると思いますが、APIコールする形でS3にアップロードできないか気になったので試してみました。
本記事では、API Gatewway ⇒ Lambda ⇒ S3バケットのリソースを構築し、base64にフォーマットした画像をAPIコール時のリクエストbodyに乗せてAPIを叩いてみようと思います。

AWS CLIでS3へファイルアップロードする場合
aws s3 cp 画像ファイル.jpg s3://バケット名/画像ファイル.jpg

環境

  • Python:3.13
  • Postman:11.43.3
  • git bash

ゴール

PostmanからAPIをコールしS3に画像がアップロードされてることを確認する。
リクエストbodyにはbase64形式にフォーマットした3つの画像形式(jpeg、png、webp)を載せる。
APIの叩き方はcurl、Postman、ブラウザのfetch apiの3つでやってみます。

1. Lambdaの作成

APIからのリクエストを処理してS3にファイルをアップロードするLambda関数を作成します。
以下の設定も行っています。

  • IAMに追加するアクション:"s3:PutObject"
  • 環境変数(S3_BUCKET_NAME)にバケット名設定
lambda_function.py
import json
import base64
import boto3
import os

from request_processor import process_request
from file_processor import process_file
from s3_uploader import upload_to_s3
from response_generator import create_response


def lambda_handler(event, context):
    # イベントが文字列の場合は JSON にパース
    if isinstance(event, str):
        event = json.loads(event)

    try:
        # リクエスト処理
        body = process_request(event)
        
        # ファイル処理
        file_name, file_data, content_type = process_file(body)
        
        # S3アップロード
        bucket_name = upload_to_s3(file_name, file_data, content_type)
        
        # 成功レスポンス
        return create_response(200, {
            'message': 'アップロード成功'
        })
        
    except ValueError as e:
        # バリデーションエラー
        print(f'バリデーションエラー: {str(e)}')
        return create_response(400, {'error': str(e)})

    except Exception as e:
        # その他のエラー
        print(f'サーバーエラー: {str(e)}')
        return create_response(500, {'error': f'サーバーエラー: {str(e)}'})

lambda_handlerから呼び出してる関数は以下のスクリプトに記載してます。

request_processor.py
request_processor.py
import json
import base64


def process_request(event):
    # イベントのログ出力
    print(f"イベント内容: {json.dumps(event)}")

    # リクエストボディを取得
    body = event.get('body', '')
    print(f"リクエストボディ: {body}")
    
    # デコード
    if event.get('isBase64Encoded', False):
        body = base64.b64decode(body).decode('utf-8')
        print(f"Base64エンコードされたボディをデコード後: {body}")

    # リクエストが文字列の場合はJSONに変換
    if isinstance(body, str) and body:
        body = json.loads(body)
    print(f"デコード後のボディ: {json.dumps(body)}")

    return body
file_processor.py
file_processor.py
import base64


def process_file(body):
    # リクエストからファイル名とファイルデータを取得
    file_name = body.get('file_name', '')
    file_data = body.get('file_data', '')
    print(f"ファイル名: {file_name}")

    # 入力検証
    if not file_name:
        raise ValueError('ファイル名が指定されていません')
    
    if not file_data:
        raise ValueError('ファイルデータが指定されていません')
    
    # Base64エンコードされたデータをデコード
    decoded_data = base64.b64decode(file_data)
    
    # ファイルの種類を判断
    content_type = 'image/jpeg'
    if file_name.lower().endswith('.png'):
        content_type = 'image/png'
    elif file_name.lower().endswith('.webp'):
        content_type = 'image/webp'

    return file_name, decoded_data, content_type
s3_uploader.py
s3_uploader
import boto3
import os


def upload_to_s3(file_name, file_data, content_type):
    # S3バケット名
    bucket_name = os.environ.get('S3_BUCKET_NAME')
    
    # S3クライアントを作成
    s3_client = boto3.client('s3')
    
    # S3にファイルをアップロード
    print(f"S3にアップロード中: バケット名={bucket_name}, ファイル名={file_name}, コンテンツタイプ={content_type}")
    s3_client.put_object(
        Bucket=bucket_name,
        Key=file_name,
        Body=file_data,
        ContentType=content_type
    )
    
    return bucket_name
response_generator.py
response_generator.py
import json

def create_response(status_code, body):
    return {
        'statusCode': status_code,
        'headers': {
            'Content-Type': 'application/json',
            'Access-Control-Allow-Origin': '*'
        },
        'body': json.dumps(body)
    }

2. API Gateway

次に、Lambdaを呼び出すためのREST APIを作成します。

  • メソッドタイプ:POST

  • 統合タイプ: Lambda 関数

  • Lambda

    • 1. Lambdaの作成で作成したLambdaを選択
    • プロキシ統合を選択
  • バイナリサポートの設定

    • image/jpeg
    • image/png
    • image/webp

image.png

今回は検証なのでCORSで許可するオリジンは全て許可(* で設定)しておきます。
APIのデプロイ後、ステージからエンドポイントURLが確認できるのでメモしておきます。

3. 動作の確認

ここまでできたらメモしたAPIのエンドポイントをPostmanからPOSTメソッドでコールしてみます。

リクエストBodyに渡す用の画像を準備

今回アップロードする画像は次のものです。

画像

textExtractionSheet.png

以下のコマンドで画像をbase64形式にフォーマットします。
コマンドの実行にはgit bashを利用します。

# JPEG画像の場合
base64 -w 0 test-image.jpg > test-image-base64.txt

# PNG画像の場合
base64 -w 0 test-image.png > test-image-base64.txt

# WebP画像の場合
base64 -w 0 test-image.webp > test-image-base64.txt

PostmanでAPIコールする場合

Postmanを使って画像をアップロードしてみます。
アップロードする画像はjpg形式のものです。
Postメソッドを作成します。

  • 「Headers」タブで以下を設定
    • Content-Type:application/json
  • 「Body」タブを選択し、「raw」を選択、JSONを選択して以下のような内容を入力
Body
{
  "file_name": "test-profile.jpg",
  "file_data": "base64形式に変換した値"
}

Sendを押下してAPIをコールすると、200で返ってくるのが確認できました。

image.png

S3バケットを確認するとjpeg画像アップロードできていました。

image.png

curlでAPIコールする場合

コマンドラインからcurlを使ってAPIをコールする場合は以下のようにします。
アップロードする画像はpng形式のものです。
リクエストBodyを記載したJSONをファイルを作成しそれをcurlで指定してAPIコールします。

request.json
{
    "file_name": "test-profile2.png",
    "file_data": "base64形式に変換した値"
}
curl -X POST \
  APIのエンドポイントURL \
  -H "Content-Type: application/json" \
  -d @request.json

S3バケットを確認するとpng画像アップロードできていました。

image.png

chromeのfetch APIからアップロードする場合

次はchromeブラウザのfetch APIを使ってアップロードしてみます。
スタイリングはtaildwind cssを使用しています。

ブラウザから画像選択+fetch apiをコールするための簡易的なHTMLを作成します。

index.html
index.html
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>画像アップロード</title>
    <script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>
</head>
<body class="p-4">
    <div class="max-w-md mx-auto bg-white p-6 rounded shadow">
        <h1 class="text-xl font-bold mb-4">画像アップロード</h1>
        
        <input class="mb-4 w-full" type="file" id="fileInput">
        
        <button id="uploadButton" class="w-full bg-blue-500 text-white py-2 px-4 rounded">
            アップロード
        </button>
        
        <div id="status" class="mt-4"></div>
    </div>

    <script>
        // APIエンドポイント
        const API_ENDPOINT = 'APIエンドポイントURL';
        
        document.getElementById('uploadButton').addEventListener('click', () => {
            const file = document.getElementById('fileInput').files[0];
            if (!file) return;
            
            const reader = new FileReader();
            reader.readAsDataURL(file);
            
            reader.onload = () => {
                // Base64データ部分のみを抽出
                const base64Data = reader.result.split(',')[1];
                
                fetch(API_ENDPOINT, {
                    method: 'POST',
                    headers: { 'Content-Type': 'application/json' },
                    body: JSON.stringify({
                        file_name: file.name,
                        file_data: base64Data
                    })
                })
                .then(response => response.json())
                .then(data => {
                    document.getElementById('status').textContent = 'アップロード成功';
                });
            };
        });
    </script>
</body>
</html>

vscodeのLive ServerでWebサーバを起動し画面を立ち上げます。

image.png

ファイルを選択からWebp画像を選択しアップロードボタンを押下すると、数秒待った後にアップロード成功の文言が表示されました。

image.png

S3バケットを確認するとWebP画像アップロードできてました。

image.png

PDFファイルをアップロードする場合

PDFファイルも同様にAPIを通じてS3にアップロードできるのか気になったので追加で確認してみました。

Lambdaの修正

Lambda関数のfile_processor.pyにPDFのContent-Typeを追加します。

# ファイルの種類を判断
content_type = 'image/jpeg'
if file_name.lower().endswith('.png'):
    content_type = 'image/png'
elif file_name.lower().endswith('.webp'):
    content_type = 'image/webp'
+ # PDFのContent-Typeを追加
+ elif file_name.lower().endswith('.pdf'):
+     content_type = 'application/pdf'

API Gatewayの修正

API GatewayのバイナリサポートにPDFのMIMEタイプを追加します。

  • application/pdf

image.png

PDFをbase64にフォーマット

以下のようにしてPDFをbase64にフォーマットします。

base64 -w 0 test-document.pdf > test-document-base64.txt

リクエストBodyは以下のようにrequest.jsonに記載しておきます。
base64形式に変換された値をtest-document-base64.txtからコピーして張り付けておきます。

request.json
{
  "file_name": "test-document.pdf",
  "file_data": "base64形式に変換した値"
}

APIコール

今回はcurlでAPIをコールしてみます。

curl -X POST \
  APIのエンドポイントURL \
  -H "Content-Type: application/json" \
  -d @request.json

S3バケットを確認してみる

S3バケットを確認するとPDFもアップロードできてました。

image.png

4. 終わりに

今回試してみた限りではうまくいっていたので、ペイロードのサイズがオーバーしてしまう事象に遭遇はしなかったですが、Lambdaのペイロードサイズについてデベロッパーガイドに記載がありました。

リクエストとレスポンスにそれぞれ 6 MB (同期)
参考:https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/gettingstarted-limits.html

あまりに大きいデータを渡すときには注意しようと思います。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?