AWSの Lambda を使うと、クラウド上でコードを実行できる。
さらにAWSの API Gateway を使うと、外部(ブラウザとかPowerShellとか)からLambdaのコードを呼び出して実行することができるようになる。
API Gateway経由でLambdaの関数を呼ぶ、といった構図である。
さて、このたび、Gemini先生に基礎から教わりながらいろいろやってみたところ
「Lambda の関数をJavaで作り、それを API Gateway 経由で呼び出す」ことに成功した。しかも完全無料で。
当記事にその手順を残してみたい。
(1) Mavenプロジェクト作成
拡張子が .java のファイルひとつだけでは、Lambda関数は作れない。
専用のプロジェクトが必要である。
今回はMavenプロジェクト(設定ファイル pom.xml とフォルダ構成 src/main/java からなる、定番のプロジェクト形式)でやることにした。
普段からIntelliJを使っているので、IntelliJでMavenプロジェクトを新規作成した。
Mavenプロジェクトを新規作成する際には プロジェクト名/GroupId/ArtifactId を決めなければならないが、適当に下記のようにした。
- プロジェクト名
MyLambdaStudyProject - GroupId
org.example - ArtifactId
MyLambdaStudyProject
Mavenプロジェクトをひとつ作ってしまえば、その中に複数のLambda関数をどんどん量産していける。
(2) Javaでコーディング
今回は学習目的なので、とりあえず「ユーザからPOSTリクエストが送られてきたら、そのbody部に入っている中身をそのまま返す」という超シンプルなものを作ることにした。
関数は API Gateway を経由して呼び出されるが、このとき、どうやら API Gateway はPOSTリクエストの全文をjson形式に変換(?)したうえで関数にお届けするらしい。
というわけで、飛んできたjsonの "body" の中身をそのまま返すコードをJavaで書けばよいことになる。
src/main/java の下に handler というパッケージを作り、その中に ShowBodyApiHandler クラス、つまり ShowBodyApiHandler.java を作った。
package handler;
import com.amazonaws.services.lambda.runtime.Context;
import com.amazonaws.services.lambda.runtime.RequestHandler;
import java.util.Map;
public class ShowBodyApiHandler implements RequestHandler<Map<String, Object>, String> {
/**
* 受け取ったJSONの "body" の値をそのまま返す
*/
@Override
public String handleRequest(Map<String, Object> input, Context context) {
// API Gatewayから届くJSONの "body" 部分だけを取り出す
String bodyContent = (String) input.get("body");
// ログにも出力しておく(CloudWatch用)
context.getLogger().log("Received body: " + bodyContent);
// 結果
if (bodyContent == null) {
return "bodyは空でした(GETリクエストなどの可能性があります)";
}
return "受信したbodyの中身はこれです: " + bodyContent;
}
}
上記のコードはGeminiに書いてもらった。良い時代になったものだ。
(3) AWSにアップロードする用のjarファイルを作る
作ったMavenプロジェクトをひとまとめにしたjarファイルを作る。
IntelliJならボタンひとつでMavenの「package」コマンドを実行することができる。
「package」コマンドを実行すると、プロジェクトフォルダ( src フォルダがある場所)にある target フォルダの中にjarファイルが作成された。
今回はプロジェクト名が MyLambdaStudyProject なので、jarファイル名は MyLambdaStudyProject-1.0-SNAPSHOT.jar となった。
(4) AWSにサインイン
私の場合、AWSのアカウントを作成したあとに "my-admin" という名前のIAMユーザーを作成しておいた。
IAMユーザーとは、ルートユーザー(なんでもできる神アカウント)とは異なり、最低限のポリシー(できる操作)だけが付与されたユーザーのこと。
- コンソールサインイン画面に行く
- アカウントIDとIAMユーザー名とパスワードを入力して「Sign in」をクリック
※アカウントIDは12桁の数字
※IAMユーザー名は "my-admin" - MFAコードを入力して「Sign in」をクリック
※私の場合、GoogleChromeの拡張機能「Authenticator」を使っており、それに表示される6桁のコードがMFAコードとなる
IAMユーザーを作成する前はルートユーザーでサインインしていた。その手順はこちら:
- コンソールサインイン画面に行く
- 「Sign in using root user email」をクリック
- Root User(IAM User ではないほう)を選択し、メールアドレスを入力して「Next」をクリック
- パスワードを入力して「Sign in」をクリック
- MFAコードを入力して「Sign in」をクリック
(5) Lambda関数を新規作成
- Lambdaの画面に行く
- 「関数を作成」をクリック
- 「一から作成」を選択
- 関数名を入力。今回は showBody とした
- ランタイムを選択。今回は Java 17 とした
- ほかはデフォルトのまま「関数を作成」をクリック
これで showBody という名前の関数が作成された。
showBody の詳細画面(…という呼び方でいいのかな?)を開くと、
「コード」「テスト」「モニタリング」などのタブが並んでいて、いろいろ情報が表示されている。
(6) さっきのjarファイルをアップロード
「コード」タブを開き、下記の手順でjarファイルをアップロードした。
- 「アップロード元」をクリック
- 「.zip または.jar ファイル」をクリック
- さきほどのjarファイルをアップロードして「保存」
(7) ランタイム設定
ランタイム設定も「コード」タブにある。
この設定をやっておかないと、AWSは「どのパッケージの、どのクラスの、どのメソッドを実行すればいいの?」と迷ってしまう。
- ランタイム設定の枠の「編集」をクリック
- 「ハンドラ」に handler.ShowBodyApiHandler::handleRequest と入力
- 「保存」
ハンドラは {パッケージ名}.{クラス名}::{メソッド名} の書式になっている。
(8) S3を見に行く許可をLambdaに付与 ※参考程度
AWSには S3 というファイル置き場サービスがある。
S3に配置したファイルを読み込んで使うような挙動をするLambda関数の場合、S3を見に行く許可が必要となる。
今回のJavaコードではS3を見に行くようなことはしていないので設定不要だが、もし設定するとしたら手順は下記のとおり。
- 「設定」タブを開く(「コード」タブではない)
- 左メニューの「アクセス権限」をクリック
- 「実行ロール」の下にあるロール名をクリック
- 「許可を追加」
- 「ポリシーをアタッチ」
- 「AmazonS3ReadOnlyAccess」にチェックを入れて「許可を追加」
(9) API Gateway と連携
作った関数を外部から呼び出して実行できるようにする。
- 「トリガーを追加」をクリック
- トリガーの設定で、プルダウンから「API Gateway」を選択
- 「新規 API を作成」を選択
- 「HTTP API」を選択
- セキュリティは「オープン」を選択(今回は学習目的なのでオープンでいい)
- 「追加」
- 「トリガーを追加」ボタンのすぐ上にある「API Gateway」をクリック
- エンドポイントURLが表示されるので控えておく(あとで必要)
(10) API Gateway の設定
今回は学習用として、よそのWebサイトからのアクセスを全て受け入れる設定にしたい。
- 「トリガーを追加」ボタンのすぐ上にある「API Gateway」をクリック
- 「API Gateway:」のすぐ隣にある、今回作ったAPIの名前をクリック(API Gatewayの画面に飛ぶ)
- 左メニューの「Develop」の中にある「CORS」をクリック
- 右上の「設定」をクリック
- 「Access-Control-Allow-Origin」に「*」を記入して「追加」(これで全てのサイトを許可)
- 「Access-Control-Allow-Headers」に「Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token」を記入して「追加」(これらはよく使われるもの。このようにカンマ区切りで書く)
- 「Access-Control-Allow-Methods」で POST, GET, OPTIONS を選択
- 「保存」
- 左メニューの「Deploy」の中にある「Stages」をクリック
- 「default」を選択
- 「自動デプロイ」が有効になっていることを確認
- 左メニューの「Develop」の中にある「Routes」をクリック
- 今回の関数名は「showBody」なので「/showBody」があることを確認
- 「/showBody」の下を確認(ANYだけがあるはずだが、実はANYは不要で、POSTとOPTIONSが必要なので、このあと整えていく)
- 「ANY」「削除」をクリック(確認ダイアログが出るので「削除」)
- 「作成」「POST」「/showBody」「作成」をクリック
- 「作成」「OPTIONS」「/showBody」「作成」をクリック
- 左メニューの「Develop」の中にある「Integrations」をクリック
- 「POST」を選択して「既存の統合を選択する」のプルダウンから当てはまるLambda関数を選択
- 「統合をアタッチする」
(11) テストしてみる
「テスト」タブを使うと動作確認できる。
- 「新しいイベントを作成」をクリック
- 「イベント名」を適当に入力( test1 とか)
- 「イベント JSON」に入力データを記述
- 「保存」
- 「テスト」をクリックし、成功と表示されれば成功
今回作った関数は、json形式のデータの "body" の中身をそのまま返すものであった。
そこでまず、"body" を含めたjson形式のデータを入力してみた。
{
"body": "piyopiyo"
}
すると
"受信したbodyの中身はこれです: piyopiyo"
という結果が表示された。問題なさそう。
別の入力データ例として、下記のように "body" の中身もjson形式にしてみた。jsonの中にjson、まるでマトリョーシカである。ちなみにダブルクォーテーションの前にはバックスラッシュを付けてエスケープ処理をしている。
{
"body": "{\"num\": 123, \"text\": \"piyopiyo\"}"
}
"受信したbodyの中身はこれです: {\"num\": 123, \"text\": \"piyopiyo\"}"
と表示された。
(12) 外部から呼び出してみる
せっかく API Gateway と連携させたので、AWSの外部からPOSTリクエストを投げてみた。
POSTリクエストを投げる処理をJavaScript等で実装するのも面白そうだが、今回はサクッと試したいので、PowerShellで投げることにした。
PowerShellでPOSTリクエストを投げるコマンドは下記のとおり。
Invoke-RestMethod -Uri "今回のエンドポイントURL" `
-Method Post `
-ContentType "application/json" `
-Body '{"num": 123, "text": "piyopiyo"}'
POSTリクエストを投げたところ、PowerShellの画面上に
受信したbodyの中身はこれです: {"num": 123, "text": "piyopiyo"}
という結果が表示された。問題なさそう。
今回は練習として、ユーザが用意したデータをそのまま返すだけの関数を作ったが、とりあえず動くものはできた。jsonデータを扱うこともできたので、応用が利きそうだ。
Lambdaのおかげで「Javaで書いたコードをネット上でいつでも動かせる状態にできた」という体験ができたのも大きい。しかも無料で。
Lambda や API Gateway のことを正直何も知らない状態からのスタートだったので、細かいところまで丁寧に教えてくれたGemini先生には感謝の気持ちでいっぱいである。