AWS LambdaだけでHTML入力フォームを作る

AWS Lambdaを使用して、HTMLで申請フォームを作成します。 HTMLのフォームから、テキストなどをPOSTできるようになります。 マッピングテンプレートやS3は使用せず、SAMとLambdaのみを使用します。 使用言語はPythonです。
こんばんは、時雨風です。
最近、Lambdaを使用して簡単な見える化システムを作成しました。機械のステータスを表示するものです。ソーラーパネルの発電量が表示される看板のようなものとイメージしていただければと思います。
DynamoDBとLambda、S3とCloudFrontを使用してバックエンドとフロントエンドを作成するところまでは順調でした。しかし、そこで問題が発生しました。
「お客様(ご高齢の社長)が操作して確認できるようにしてほしい」
え!?
「Web APIで送信し、curlを使ってパラメーターを設定してから送信してほしい」 などと言っても絶対に通じません。
そこで考えたのが、簡易的なHTML入力フォームからの送信でした。
最も重要なのは「簡易的」という点です。 想定していなかった作業なので、残り時間がありませんでした。
そして時雨風はせっせとHTMLフォームを作成し始めるのでした…
はじまり、はじまり〜
こちらが完成したものです。 解説では一部抜粋して いるだけなので、こちらを見ると理解が深まります。プロジェクトのテンプレートとしても使用できます。
https://github.com/tokiukaze/lambda-html-form
使用するもの
ついつい長々と書いてしまいましたが、前提知識のドキュメントを読めば理解できると思います。 こだわりポイントとしては、入力フォームはHTMLのみでJavaScriptを使用しないこと、AWS API GatewayのマッピングテンプレートやS3を使用しないことです。あくまで簡易的なものなので、余計なものは削ぎ落とします。でもFaviconは必要です。はい。
PCにインストールされ、設定されている必要があるもの
Visual Studio Code コードエディター
Python 拡張機能 VSCodeのPython拡張プラグイン
AWS SAM CLI AWS SAMをコマンドから操作するアプリケーション
使用ライブラリ
aws-lambda-powertools Lambdaで簡単な記述ができるようにするライブラリ
使用しないもの
前提知識
AWSを操作するので、以下の2つの内容はある程度理解している必要があります。
サーバーレスコードを実行する方法 – アマゾン ウェブ サービス (AWS)
クイックスタート: アプリケーションの公開 - AWS Serverless Application Repository
解説
ここから解説に移りますが、まずはローカルで動かした方が理解しやすいと思うので、ぜひ試してみてください。
ローカルで動かす
サンプルプロジェクトをローカルで動かします。
gitとaws sam cliのインストールが必要です。
git clone https://github.com/tokiukaze/lambda-html-form.git
cd lambda-html-form
sam build && sam local start-api
すると以下の表示が出ます。
Running PythonPipBuilder:ResolveDependencies
Running PythonPipBuilder:CopySource
Build Succeeded
Built Artifacts : .aws-sam/build
Built Template : .aws-sam/build/template.yaml
~~~~省略~~~~
その状態でブラウザからURLにアクセスします。 http://127.0.0.1:3000/form 以下のような画面が表示されます。
HTMLフォームに適当に入力し、「送信」をすると送信した内容がそのまま表示されます。
実装
ここからはソースコードの解説を行います。
まずはindex.htmlを作成します。 主に使用するのは、inputタグです。
index.html 一部抜粋
<body>
<form method="post">
<input type="date" name="input1" />
<br />
<input type="checkbox" name="input2" />
<br />
<input type="text" name="input3" />
<br />
<input type="number" name="input4" />
<br />
<select name="pets" id="input5">
<option value="">--Please choose an option--</option>
<option value="dog">Dog</option>
<option value="cat">Cat</option>
<option value="hamster">Hamster</option>
<option value="parrot">Parrot</option>
<option value="spider">Spider</option>
<option value="goldfish">Goldfish</option>
</select>
<br />
<input type="submit" />
</form>
</body>
上記のHTMLを配信するメソッドを定義します。 app.py
@app.get("/form")
def get_form() -> Response:
"""HTMLを読み込み返却します。
/index.htmlを読み込み返却しています。
index.htmlには、入力フォームのサンプルが記述してあります。
"""
html_path = Path("index.html")
with open(html_path, "r") as f:
file = f.read()
return Response(status_code=200, content_type=content_types.TEXT_HTML, body=file)
フォームデータを受信するAPIを作成します。
@app.post("/form")
def post_form() -> Response:
"""HTMLフォームからのデータを受信します。
受信されたデータは解析した後、Webページに表示しています。
"""
body_data: dict[str, Any] = html_form_split(app.current_event.body)
# テキストが日本語の場合はURLデコードを行う
body_data["input3"] = unquote(body_data.get("input3", ""))
# dictをstrに変換する。日本語を含む想定のため、文字コードに注意する。
res: str = json.dumps(body_data, ensure_ascii=False)
# 日本語に対応するためcharsetを設定
return Response(
status_code=200,
content_type=content_types.TEXT_PLAIN + "; charset=utf-8",
body=res,
)
def html_form_split(body: str) -> dict[str, Any]:
"""フォームから送信されたbodyをパースし、辞書型で返却します。
Args:
body (str): フォームから送信されたbody
Returns:
dict[str, Any]: パースされたデータ
"""
keys = []
values = []
for data in body.split("&"):
pair = data.split("=")
keys.append(pair[0])
values.append(pair[1])
return dict(zip(keys, values))
HTMLからは、input1=value1&input2=value2のようなbodyが送信されます。
HTMLのフォームの仕様については、以下をご覧ください。
html_form_split関数を実装し、HTMLから送信されるデータをパースし、使いやすいように辞書型にしています。
送信されたテキストに日本語が含まれると、送信されたデータはURLエンコードされてしまいます。
URLデコードで文字に戻す必要があります。unquote()が該当します。
その後、テキストとしてフォームから送信されたデータを返却しています。
まとめ
LambdaのみでHTMLのフォームからデータを送信することができるようになりました。 会社には「Reactは甘え。HTML/CSS/JSだけでサイトを作るのが至高」という冗談を言う先輩がいますが、今回のような高度なことが必要とされないケースではその通りだと思います。
ここまで、お読み いただきありがとうございました。