API Gateway+Lambda編!JavaScriptでS3のデータを登録・取得する方法

AWS
この記事は約15分で読めます。

こんにちは、@Manabu です。

前回、JavaScript用のAWS SDKでS3のデータに対して、登録・取得を行う方法について紹介しました。

そのとき、IAMユーザーのアクセスキーを使う必要があり、セキュリティ面での不安があったため、API GatewayとLambdaでバックエンドを実装し、安心できる方法を検討することにしました。

諸々紹介します。

はじめに

まず、今回検討している構成について紹介します。

今回は、WebサイトでS3の静的Webホスティング、バックエンドでAPI Gateway+Lambdaを使用して、DB用のS3からデータを登録したり取得します。

登録と取得用の機構はそれぞれ作成しますので、2つになります。

API Gateway + Lambdaの作成方法

上記、二つの作成方法は、以前紹介しているので今回の記事では割愛します。

AWS Lambda、API Gateway、SESを使って静的サイト化からメール送信フォームを作成!

Pythonのコードだけ紹介しますので、そちらで試してみてください。

AWS SDK for JavaScript v2を使用する方法

AWS SDK for JavaScript v2を使用して、直接S3にデータの登録と取得を行っている記事もありますので、そちらも参考にしてください。

AWS SDK for JavaScript v2を使ってみた!S3への登録と取得

Webページの作成

S3でWebサイトを公開するので、HTMLとJSでページを作成します。

APIを使用して、S3からデータを登録し、登録データを取得して表示させる処理になります。

以下のソースを使用します。

HTML(index.html)

<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>掲示板 - スレッド登録</title>
</head>
<body>
<h1>新しいスレッドを登録</h1>
<form id="registerForm">
<label for="threadTitle">スレッドタイトル:</label>
<input type="text" id="threadTitle" name="threadTitle" required><br><br>
<label for="threadContent">内容:</label>
<textarea id="threadContent" name="threadContent" required></textarea><br><br>
<button type="submit">登録</button>
</form>

<h1>スレッド一覧</h1>
<div id="threadContainer">
<!-- スレッドがここに表示されます -->
</div>

<script src="script.js"></script>
</body>
</html>

JS(script.js)

document.getElementById('registerForm').addEventListener('submit', function(event) {
  event.preventDefault();

  const threadTitle = document.getElementById('threadTitle').value;
  const threadContent = document.getElementById('threadContent').value;

  // API GatewayのエンドポイントURL
  const apiUrl = 'https://rdi7tetsegaa.execute-api.ap-northeast-1.amazonaws.com/default/SaveFunction';

  // データをAPI Gatewayに送信
  fetch(apiUrl, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({ title: threadTitle, content: threadContent }),
    mode: 'cors', // CORSモードでリクエストを送信
  })
  .then(response => response.json())
  .then(data => {
    alert('スレッドが登録されました!');
    document.getElementById('registerForm').reset();
  })
  .catch((error) => {
    console.error('Error:', error);
    alert('スレッドの登録に失敗しました。');
  });
});

document.addEventListener('DOMContentLoaded', function () {
  const threadContainer = document.getElementById('threadContainer');

  // APIエンドポイントのURLを指定
  const apiUrl = 'https://c34weaweraj4.execute-api.ap-northeast-1.amazonaws.com/default/GetFunction'; // 実際のAPI URLに変更

  // APIからデータを取得
  fetch(apiUrl, {
    method: 'GET',
    headers: {
      'Content-Type': 'application/json',
    },
  })
  .then(response => response.json())
  .then(data => {
    // スレッドデータをHTMLに表示
    if (data.threads && data.threads.length > 0) {
      data.threads.forEach(thread => {
        const threadElement = document.createElement('div');
        threadElement.className = 'thread';
        threadElement.innerHTML = `
          <h2>${thread.title}</h2>
          <p>${thread.content}</p>
        `;
        threadContainer.appendChild(threadElement);
      });
    } else {
      threadContainer.innerHTML = '<p>スレッドが見つかりませんでした。</p>';
    }
  })
  .catch(error => {
    console.error('Error fetching threads:', error);
    threadContainer.innerHTML = '<p>スレッドの取得に失敗しました。</p>';
  });
});

データの登録

データ登録用のAPI GatewayとLambda関数を作成します。

Pythonのソースは以下を使用します。

import json
import boto3
import time # timeモジュールをインポート

s3 = boto3.client('s3')

def lambda_handler(event, context):
  print('Received event:', json.dumps(event))

  try:
    # CORSのプリフライトリクエストに対応
    if event['httpMethod'] == 'OPTIONS':
      return {
        'statusCode': 200,
        'headers': {
        'Access-Control-Allow-Origin': 'https://test.it-slroom.blog',
        'Access-Control-Allow-Methods': 'POST, OPTIONS',
        'Access-Control-Allow-Headers': 'Content-Type',
      },
      'body': ''
    }

    # POSTリクエストの処理
    if event['httpMethod'] == 'POST':
      body = json.loads(event['body'])
      thread_title = body.get('title', '')
      thread_content = body.get('content', '')

      # S3にデータを保存
      bucket_name = 'test-s3-db' # 実際のバケット名に変更
      key = f'threads/{int(time.time())}.json' # タイムスタンプを使用したキー
      s3.put_object(
        Bucket=bucket_name,
        Key=key,
        Body=json.dumps({'title': thread_title, 'content': thread_content}),
        ContentType='application/json'
      )

      return {
        'statusCode': 200,
        'headers': {
          'Access-Control-Allow-Origin': 'https://test.it-slroom.blog',
          'Access-Control-Allow-Methods': 'POST, OPTIONS',
          'Access-Control-Allow-Headers': 'Content-Type',
        },
        'body': json.dumps({'message': 'Thread saved successfully'})
      }

    # その他のメソッドに対するレスポンス
    return {
      'statusCode': 405,
      'headers': {
        'Access-Control-Allow-Origin': 'https://test.it-slroom.blog',
        'Access-Control-Allow-Methods': 'POST, OPTIONS',
        'Access-Control-Allow-Headers': 'Content-Type',
       },
       'body': json.dumps({'message': 'Method Not Allowed'})
    }

  except Exception as e:
    print('Error occurred:', str(e))
    return {
      'statusCode': 500,
      'headers': {
        'Access-Control-Allow-Origin': 'https://test.it-slroom.blog',
        'Access-Control-Allow-Methods': 'POST, OPTIONS',
        'Access-Control-Allow-Headers': 'Content-Type',
      },
      'body': json.dumps({'message': f'An error occurred: {str(e)}'})
    }

こちらの処理で、S3にjsonファイルが作成されます。

データの取得

続いて、登録したS3のJsonデータを取得するAPI GatewayとLambda関数を作成します。

Pythonのソースは以下を使用します。

import json
import boto3

s3 = boto3.client('s3')

def lambda_handler(event, context):
  print('Received event:', json.dumps(event))

  try:
    # GETリクエストの処理
    if event['httpMethod'] == 'GET':
    bucket_name = 'test-s3-db' # S3のバケット名に変更
    prefix = 'threads/' # 保存されたスレッドのプレフィックス

    # S3からオブジェクトのリストを取得
    response = s3.list_objects_v2(Bucket=bucket_name, Prefix=prefix)

    # バケットが空の場合の処理
    if 'Contents' not in response:
      return {
        'statusCode': 200,
        'headers': {
          'Access-Control-Allow-Origin': '*', # 必要に応じて特定のオリジンに変更
          'Access-Control-Allow-Methods': 'GET, OPTIONS',
          'Access-Control-Allow-Headers': 'Content-Type',
        },
        'body': json.dumps({'threads': []}) # 空のスレッドリスト
      }

      # 各オブジェクトの内容を取得
      threads = []
      for obj in response['Contents']:
        key = obj['Key']
        obj_response = s3.get_object(Bucket=bucket_name, Key=key)
        thread_data = json.loads(obj_response['Body'].read().decode('utf-8'))
        threads.append(thread_data)

      return {
        'statusCode': 200,
        'headers': {
          'Access-Control-Allow-Origin': '*', # 必要に応じて特定のオリジンに変更
          'Access-Control-Allow-Methods': 'GET, OPTIONS',
          'Access-Control-Allow-Headers': 'Content-Type',
        },
        'body': json.dumps({'threads': threads})
      }

    # CORSのプリフライトリクエストに対応
    if event['httpMethod'] == 'OPTIONS':
      return {
        'statusCode': 200,
        'headers': {
          'Access-Control-Allow-Origin': '*', # 必要に応じて特定のオリジンに変更
          'Access-Control-Allow-Methods': 'GET, OPTIONS',
          'Access-Control-Allow-Headers': 'Content-Type',
        },
        'body': ''
      }

    return {
      'statusCode': 405,
      'headers': {
        'Access-Control-Allow-Origin': '*',
        'Access-Control-Allow-Methods': 'GET, OPTIONS',
        'Access-Control-Allow-Headers': 'Content-Type',
      },
      'body': json.dumps({'message': 'Method Not Allowed'})
    }

  except Exception as e:
    print('Error occurred:', str(e))
    return {
      'statusCode': 500,
      'headers': {
        'Access-Control-Allow-Origin': '*',
        'Access-Control-Allow-Methods': 'GET, OPTIONS',
        'Access-Control-Allow-Headers': 'Content-Type',
      },
      'body': json.dumps({'message': f'An error occurred: {str(e)}'})
    }

こちらでデータの取得ができます。

まとめ

前回紹介したSDKでは、セキュリティ的な不安がありました。

今回の紹介したバックエンドの方法では、Lambda側でどんな処理をしているのかユーザー側にはわからないし、権限も適切に分散できると思うので、おすすめです。