API GatewayとLambdaとS3でHTTPメソッドに応じて応答を変えた静的なWebサイトを作成し、EventBridgeとLambdaで定期的にリクエストを送信する

この記事は以下のハッカソンに参加した際に作ったプロダクトの一部の作り方を示したものです。

https://moicen-forest.connpass.com/event/292192/

# 作るもの
病院の診察予約を模擬した単純なWebサイトを作成します。実際にはCSRFトークンなど、もう少し複雑になることを考慮する必要があります。
- GET: 現在の呼び出し番号を記載したページ
- POST: 予約完了のページ
- インターネットからHTTPSでアクセスでき、メソッドによって応答を変える

# S3に設置するhtmlファイル
ChatGPTにイメージを伝えてhtmlファイルのコードを出力してもらい、項目や表示内容などを微調整しました。
## 呼び出し番号ページ
呼び出し番号を記載したページ。
![スクリーンショット 2023-09-23 18.28.57.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2628523/832e7fdc-eb37-412a-89ca-ece1b8e76189.png)

```html:wait.html
<!DOCTYPE html>
<html lang="ja">

<head>
  <meta charset="UTF-8">
  <title>診察の順番待ち</title>
  <style>
    body {
      font-family: 'Arial', sans-serif;
      background-color: #f4f4f4;
      margin: 0;
      padding: 0;
    }

    header {
      background-color: #50b3a2;
      color: white;
      text-align: center;
      padding: 1em 0;
    }

    ul {
      padding: 0;
      margin: 0;
      list-style: none;
    }

    ul li {
      background: #ffffff;
      margin-bottom: 1px;
      padding: 0.5em;
      text-align: center;
    }

    .calling {
      background-color: #ffcccb;
    }
  </style>
</head>

<body>
  <header>
    <h1>診察の順番</h1>
  </header>
  <main>
    <ul>
      <li class="calling">1番 - 現在呼び出し中</li>
      <li>2番</li>
      <li>3番</li>
      <li>4番</li>
      <li>5番</li>
    </ul>
  </main>
</body>

</html>
```
## 予約完了ページ
![スクリーンショット 2023-09-24 13.53.24.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2628523/0b796200-269e-8c54-0900-ca16786d0033.png)
```html:yoyaku.html
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>予約完了</title>
<style>
  body {
    font-family: 'Arial', sans-serif;
    background-color: #f4f4f4;
    margin: 0;
    padding: 0;
  }
  .container {
    width: 50%;
    margin: auto;
  }
  header {
    background: #50b3a2;
    color: white;
    text-align: center;
    padding: 1em 0;
  }
  .notification {
    padding: 1em;
    background: #e3e3e3;
    margin: 1em 0;
  }
</style>
</head>
<body>
<div class="container">
  <header>
    <h1>予約完了</h1>
  </header>
  <div class="notification">
    <p>受付番号: <strong>5</strong></p>
    <p>予約番号: <strong>ABCDE</strong></p>
  </div>
</div>
</body>
</html>
```
# S3に設置しLambda経由でAPI Gatewayから呼び出す
## S3バケットにhtmlを設置
S3バケットを作成しファイルを設置します
![スクリーンショット 2023-09-23 18.16.10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2628523/8e6f260a-7ee9-5fa3-bdb8-5943a02995c0.png)
![スクリーンショット 2023-09-23 18.26.10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2628523/c2d8657a-8c9b-3907-4c1c-19925bf57bdf.png)
## 表示するページごとにLambdaを作成
S3バケットからhtmlを取得しHTTPレスポンスを生成するLambdaを作成します。
![スクリーンショット 2023-09-24 1.05.43.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2628523/3073bb18-3db2-9b53-6775-113162e95906.png)
```python:getWaitPage
import boto3
from botocore.exceptions import NoCredentialsError, PartialCredentialsError

s3_client = boto3.client('s3')
BUCKET_NAME = 'your backet name'

def lambda_handler(event, context):
    try:
        response = s3_client.get_object(Bucket=BUCKET_NAME, Key='wait.html')
        content = response['Body'].read().decode('utf-8')
        return {
            'statusCode': 200,
            'body': content,
            'headers': {
                'Content-Type': 'text/html'
            }
        }
    except Exception as e:
        return {
            'statusCode': 500,
            'body': str(e)
        }
```
## API GatewayでAPIを作成
APIを作成します。
![スクリーンショット 2023-09-24 2.07.37.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2628523/7eddb9e1-b362-7927-cd32-004e8b4465a5.png)
作成したAPIにルートからGETとPOSTを登録し、作成したLambdaをそれぞれ紐づけます。
![スクリーンショット 2023-10-29 19.05.26.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2628523/9bcff229-16fd-cb3a-8639-e03d494e30e6.png)
![スクリーンショット 2023-10-29 19.07.23.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2628523/ca1d71a3-80e7-f64d-3d22-e1f677b85d06.png)
API Gatewayのエンドポイントにブラウザからアクセスし、S3に設置したhtmlが表示されることを確認しましょう。

# EventBridgeでLambdaを定期実行
## 定期実行するLambdaを作成
ここでは、POSTリクエストを送る例を記載しています。
```python:eventbridge-yoyaku
import json
from urllib import request, parse

def lambda_handler(event, context):
    url = "your api url"
    
    # POSTリクエストに送信するデータ
    data = {}
    
    data = parse.urlencode(data).encode()
    req = request.Request(url, data=data, headers={'Content-Type': 'application/x-www-form-urlencoded'})
    
    try:
        with request.urlopen(req) as response:
            response_body = response.read().decode()
            status_code = response.getcode()
    except Exception as e:
        return {
            'statusCode': 500,
            'body': str(e)
        }
    
    return {
        'statusCode': status_code,
        'body': response_body
    }
```
テストを実行してWebページが取得できていることを確認します。
![スクリーンショット 2023-10-29 19.22.56.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2628523/fde44714-127f-69bd-2329-0dcb54361b1c.png)

## EventBridgeのスケジュールを作成
スケジュールを作成します。
![スクリーンショット 2023-09-24 14.01.26.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2628523/0eeb59d8-06c7-3356-d702-65c47545229c.png)
トリガーを設定します。cron書式でかけます。例ではハッカソンのプレゼン期間中だけ5分ごとに動作するようにしています。
![スクリーンショット 2023-09-24 14.12.41.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2628523/52d2b94f-c92f-bb61-c67b-234636bcbaaf.png)
作成したLambdaを実行するようにします。
![スクリーンショット 2023-10-29 19.16.52.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2628523/6e15eb6a-9509-8a9e-50b3-92c5862893ab.png)

# まとめ
予約サイトの模擬サイトをAWSで作り、LambdaをEventBridgeで定期実行させました。Lambdaの中でDynamoDBと連携させ、予約の実行タイミングを登録したり、予約後に予約番号を保存したりすると便利に使えるかと思います。

ただし実際にプログラムでWebサイトにスクレイピングや予約操作の自動実行を行うときは、Webサイトの利用規約や負荷などを考慮し、実施しても問題ないことを慎重に確かめてから実施してください。