zenet_logo

-株式会社ゼネット技術ブログ-

【AWS入門】API GatewayとLambdaでHTMLページを動的に生成してみた

 

はじめに

ゼネットシステム事業部の方です。

AWSでWebサイトを公開する場合、最もシンプルな方法はAmazon S3に静的サイトをホスティングすることです。  
しかし、S3は静的コンテンツのみを提供するため、ユーザーごとに動的に内容を変えたい場合は工夫が必要です。  

そこで今回は、API Gateway + Lambda を利用して動的にHTMLを生成する方法を紹介します。  
実際の業務では、この仕組みを応用して ユーザーごとに異なるQuickSightダッシュボードを表示する仕組みを構築しました。  

従来の構成:S3の静的サイト

S3の静的サイトホスティングは簡単ですが、動的処理はできません。

例えば、以下のような課題があります。

  • HTMLファイルは事前に用意する必要がある

  • ユーザーの入力に応じた動的コンテンツを返せない

  • 外部データベースやAPIとの連携ができない

これを解決するために、API GatewayとLambdaを組み合わせるとサーバーレスで動的なWebサイトが実現できます。

API GatewayとLambdaの仕組み

今回の構成では、S3 だけでは実現できない「ユーザーごとに異なるページや回答表示」を API Gateway と Lambda で補っています。

  • S3:基本のHTMLやCSS、JavaScriptを配信

  • API Gateway:ブラウザからのリクエストを受け付ける

  • Lambda:リクエスト内容に応じて動的にHTMLを生成

  • CloudFront:全体を統合するフロント。キャッシュ・セキュリティ・HTTPS対応を提供

この構成により、以下の役割分担が可能です:

  • 静的要素(CSS/JS/画像)は S3 + CloudFront で高速配信

  • 動的要素(ユーザーごとに異なるダッシュボードHTMLなど)は API Gateway + Lambda で柔軟に生成

といった役割分担が可能になります。

特に、動的HTMLでも一部要素(CSS/JS)はキャッシュ可能なので、パフォーマンス改善に効果的です。

Lambdaの設定とコード

LambdaはHTMLを生成して返す役割を担います。
今回はシンプルにPythonでHTMLを返すサンプルを紹介します。

from datetime import datetime

def lambda_handler(event, context):
    body = event['body']
    dob_str = body.get('dob')

    try:
        # 入力された生年月日を取得
        dob = datetime.strptime(dob_str, "%Y-%m-%d")
        # 星座と干支を判別
        western = get_western_zodiac(dob.month, dob.day)
        chinese = get_chinese_zodiac(dob.year)

        # 回答HTMLを作成
        html = (
            f'<h3>あなたの星座:{western}</h3>'
            f'<h3>あなたの干支:{chinese}</h3>'
        )
        return html
        
    except Exception as e:
        return {
            'statusCode': 400,
            'body': f"<p>エラーが発生しました:{str(e)}</p>"
        }

# 星座判別関数
def get_western_zodiac(month, day):
    if (month == 1 and day >= 20) or (month == 2 and day <= 18): return "みずがめ座"
    if (month == 2 and day >= 19) or (month == 3 and day <= 20): return "うお座"
    if (month == 3 and day >= 21) or (month == 4 and day <= 19): return "おひつじ座"
    if (month == 4 and day >= 20) or (month == 5 and day <= 20): return "おうし座"
    if (month == 5 and day >= 21) or (month == 6 and day <= 20): return "ふたご座"
    if (month == 6 and day >= 21) or (month == 7 and day <= 22): return "かに座"
    if (month == 7 and day >= 23) or (month == 8 and day <= 22): return "しし座"
    if (month == 8 and day >= 23) or (month == 9 and day <= 22): return "おとめ座"
    if (month == 9 and day >= 23) or (month == 10 and day <= 22): return "てんびん座"
    if (month == 10 and day >= 23) or (month == 11 and day <= 21): return "さそり座"
    if (month == 11 and day >= 22) or (month == 12 and day <= 21): return "いて座"
    return "やぎ座"
    
# 干支判別関数
def get_chinese_zodiac(year):
    animals = [
        "子(ねずみ)", "丑(うし)", "寅(とら)", "卯(うさぎ)", "辰(たつ)", "巳(へび)",
        "午(うま)", "未(ひつじ)", "申(さる)", "酉(とり)", "戌(いぬ)", "亥(いのしし)"
    ]
    base_year = 1900
    index = (year - base_year) % 12
    return animals[index]

CloudFrontの設定

CloudFrontをAPI Gatewayの前段に置くことで、以下のメリットが得られます。

  • 世界中のエッジロケーションからの高速配信

  • WAF連携によるセキュリティ強化

  • キャッシュによるレスポンス高速化

ディストリビューション作成
  1. CloudFrontの管理コンソールにアクセス

  2. [ディストリビューションを作成] をクリック

  3. 配信プロトコルは HTTPS を選択

オリジン作成
  1. オリジンに指定するリソースを選択

    • 例1: S3バケット を指定(静的ファイル配信用)

    • 例2: API Gatewayのエンドポイント を指定(動的API呼び出し用)

  2. 保存してディストリビューションを作成

API Gatewayの設定(CORSとMapping Template込み)📄

1. リソースとメソッドの作成

まず、API Gatewayで新規REST APIを作成し、対象のリソースに POSTメソッド を追加します。

API Gatewayリソースとメソッド作成画面

2. CORS設定

ブラウザからAPIを呼び出す場合、CORS対応 が必須です。
API Gatewayの「CORSの有効化」 から設定します。

以下のヘッダーを有効にすることで、ブラウザから安全に呼び出せるようになります。

  • Access-Control-Allow-Origin: *

  • Access-Control-Allow-Methods: POST, OPTIONS

  • Access-Control-Allow-Headers: Content-Type

CORS設定画面

もし特定の環境だけに限定したい場合は、次のように CloudFrontのURL を指定します。

  • Access-Control-Allow-Origin: https://xxxxxxx.cloudfront.net

3. マッピングテンプレート設定

(1) 統合リクエストのマッピングテンプレート

クライアントから送られたクエリパラメータを、Lambdaに渡す際にJSONへ変換します。

Content-Type: application/json

 
{ "body": $input.json('$') }
 

統合リクエストタブ画面

コンテンツタイプとテンプレート本文設定画面
(2) 統合レスポンスのマッピングテンプレート

LambdaのレスポンスをHTMLとして返す設定です。

Content-Type: text/html

 
#set($context.responseOverride.header.Content-Type = "text/html")
$input.body.replaceAll('\n', '').replaceAll('"', '')
 

統合レスポンスマッピングテンプレート設定画面
(3) ヘッダーのマッピング

さらに、レスポンスヘッダーをマッピングしてCORSを有効にします。

  • 名前:method.response.header.Access-Control-Allow-Origin

  • マッピングの値:https://xxxxxxx.cloudfront.net

ヘッダーのマッピング設定画面
(4) APIデプロイ

設定が完了したら、必ず APIをデプロイ します。
API Gatewayのメニューから「アクション → APIのデプロイ」を選び、ステージ名(例:prod)を指定すると、エンドポイントURLが発行されます。

API Gatewayデプロイ画面

API Gatewayデプロイ後のステージURL画面

例:   https://xxxxxx.execute-api.ap-northeast-1.amazonaws.com/dev/get-zodiac

このURLは、実際にフロントエンド(静的HTMLやCloudFront配信ページ)から呼び出す際に利用します。
シンプルに試す場合は、以下のようにHTMLのボタンにスクリプトを組み込むことで呼び出せます。

 
 

このようにすれば、CloudFrontでホスティングしている静的サイトからも、Lambdaを経由した動的HTMLを簡単に呼び出せるようになります。

<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <title>星座と干支占い</title>
  <style>
    body {
      font-family: 'Arial', sans-serif;
      text-align: center;
      background-color: #fffafc;
      margin: 0;
      padding: 0;
    }

    .container {
      margin-top: 100px;
    }

    h2 {
      color: #444;
      margin-bottom: 20px;
    }

    label {
      font-size: 18px;
      margin-right: 10px;
    }

    input[type="date"] {
      font-size: 16px;
      padding: 8px;
      border-radius: 6px;
      border: 1px solid #ccc;
    }

    button {
      margin-top: 20px;
      padding: 12px 30px;
      font-size: 18px;
      background-color: #ffb6c1;
      color: white;
      border: none;
      border-radius: 30px;
      cursor: pointer;
      box-shadow: 0 4px 6px rgba(0,0,0,0.1);
      transition: background-color 0.3s ease;
    }

    button:hover {
      background-color: #ff8fa3;
    }

    #result {
      display: flex;
      flex-direction: column;
      gap: 20px;
      margin: 2% auto;
      width: 22%;
      font-size: 24px;
      align-items: stretch;
    }

    #result h3 {
      margin: 0;
    }

    .error {
      color: red;
      font-weight: bold;
      margin-top: 20px;
    }

    .error::before {
      content: "⚠️ ";
    }
  </style>
</head>
<body>
  <div class="container">
    <h2>あなたの星座と干支を調べよう</h2>
    <label for="dob">生年月日:</label>
    <input type="date" id="dob" />
    <br>
    <button onclick="getZodiac()">占う</button>

    <div id="result"></div>
  </div>

  <script>
    async function getZodiac() {
      const dob = document.getElementById('dob').value;
      const resultDiv = document.getElementById('result');
      resultDiv.innerHTML = "";

      if (!dob) {
        resultDiv.innerHTML = '<div class="error">生年月日を入力してください。</div>';
        return;
      }

      try {
        const response = await fetch('https://xxxxxx.execute-api.ap-northeast-1.amazonaws.com/dev/get-zodiac', {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify({ dob })
        });

        const html = await response.text();
        resultDiv.innerHTML = html;
      } catch (err) {
        resultDiv.innerHTML = '<div class="error">占い結果の取得中にエラーが発生しました。</div>';
        console.error(err);
      }
    }
  </script>
</body>
</html>

結果

実際にブラウザからサイトにアクセスし、APIゲートウェイを呼び出すボタンを押下すると、Lambdaが生成したHTMLが返り、動的に表示されました。

CloudFrontからサイトアクセス

動的に結果生成画面

まとめ

今回の仕組のメリット:

  • 動的なHTML生成が可能(ユーザーごとに異なるページを提供できる)

  • 完全サーバーレス構成で運用コスト削減

  • CloudFront連携によりグローバルに高速配信

  • セキュアな認証制御を組み込みやすい(CORSやMapping Templateと連携可能)

S3の静的サイトと違い、サーバーを立てずに動的コンテンツを実現できるのは大きなメリットです。
今後は、この仕組みに DynamoDB や外部APIを組み合わせることで、よりリッチなWebアプリケーションへの応用も可能です。

参照資料

docs.aws.amazon.com

docs.aws.amazon.com