こんにちは、@Manabu です。
前回、JavaScript用のAWS SDKでS3のデータに対して、登録・取得を行う方法について紹介しました。
そのとき、IAMユーザーのアクセスキーを使う必要があり、セキュリティ面での不安があったため、API GatewayとLambdaでバックエンドを実装し、安心できる方法を検討することにしました。
諸々紹介します。
はじめに
まず、今回検討している構成について紹介します。
今回は、WebサイトでS3の静的Webホスティング、バックエンドでAPI Gateway+Lambdaを使用して、DB用のS3からデータを登録したり取得します。
登録と取得用の機構はそれぞれ作成しますので、2つになります。
API Gateway + Lambdaの作成方法
上記、二つの作成方法は、以前紹介しているので今回の記事では割愛します。
Pythonのコードだけ紹介しますので、そちらで試してみてください。
AWS SDK for JavaScript v2を使用する方法
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側でどんな処理をしているのかユーザー側にはわからないし、権限も適切に分散できると思うので、おすすめです。