この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。
Introduction
この記事やこの記事では、
AWS Lambda + Momentoの基本的な使い方について紹介してきました。
本稿では、任意のデータを指定時間キャッシュして、
期限付きデータを実現する簡単なサンプルを実装してみます。
Environment
- OS : MacOS 12.4
- rust : 1.61.0
- Momento CLI : 0.20.1
Moment CLIとAWSアカウントはセットアップ済みとします。
また、以前の記事(これとこれ)で作成した
momento-lambdaプロジェクトを元に解説します。
Setup
今回使用するキャッシュ(my_content_cache)は事前にCLIで作成しておきましょう。
% momento cache create --name my_content_cache
Create Momento Example
Cargo.tomlの[dependencies]に下記crateを追加します。
quanta = "0.10.0"
momento = "0.6.0"
once_cell = "1.12.0"
aws-config = "0.15.0"
aws-sdk-secretsmanager = "0.15.0"
serde_json = "1.0.82"
serde_derive = "1.0.138"
serde = "1.0.138"
クライアント初期化
まずはMomentoクライアントの初期化をします。
クライアント初期化は1回ですむように、
static変数としてOnceCellをつかって定義します。
また、MomentoのトークンはAWS Secrets Managerに保存したものを取得します。
const MOMENTO_SECRET_ID: &str = "accounts/MomentoAuthToken";
const SECRET_REGION:&str ="ap-northeast-1";
static momento_client: OnceCell<Mutex<RefCell<SimpleCacheClient>>> = OnceCell::new();
/// クライアントの初期化
async fn init_client() -> Result<SimpleCacheClient,Error> {
let auth_token = get_momento_auth_token().await?;
let item_default_ttl_seconds = 60;
Ok(SimpleCacheClientBuilder::new(
auth_token,
NonZeroU64::new(item_default_ttl_seconds).unwrap(),
)?.build())
}
/// AWS Secrets Managerに保存したトークン取得
async fn get_momento_auth_token() -> Result<String,Error> {
let shared_config = aws_config::from_env().region(Region::new(SECRET_REGION)).load().await;
let client = Client::new(&shared_config);
let resp = client.get_secret_value().secret_id(MOMENTO_SECRET_ID).send().await?;
let json = resp.secret_string().unwrap_or("No value!").to_string();
let v: Value = serde_json::from_str(&json)?;
return Ok(v["MOMENTO_AUTH_TOKEN"].as_str().unwrap().to_string());
}
init_client関数はmain関数の中で呼び出します。
#[tokio::main]
async fn main() -> Result<(), Error> {
let cli:SimpleCacheClient = init_client().await?;
let _ = momento_client.set(Mutex::new(RefCell::new(cli)));
run(service_fn(function_handler)).await
}
キャッシュ登録&取得の実装
次にキャッシュの登録と取得を実装します。
function_handler内でクエリパラメータactionをチェックし、
saveであればBodyから取得したデータをキャッシュ登録、
getであればクエリパラメータ:keyを使ってキャッシュからデータ取得を行います。
/// キャッシュ保存用構造体
#[derive(Debug,Serialize,Deserialize,Default)]
struct PutParams {
key:String,
ttl_sec:u64,
content_type: String,
content: String
}
async fn function_handler(event: Request) -> Result<impl IntoResponse, Error> {
//キャッシュ名定義
let cache_name = String::from("my_content_cache");
//Momento Client取得
let tmp_cache_client = momento_client.get().unwrap().lock().unwrap();
let mut cache_client = tmp_cache_client.borrow_mut();
//エラー時のJSON
let error_json = r#"{"content_type":"text/html","content":"cache Not found"}"#;
//クエリパラメータ
let param_map = event.query_string_parameters();
let result = match param_map.first("action") {
Some("save") => {
println!("action : save");
//body(json) to Struct
let put_params: PutParams = serde_json::from_slice(event.body().as_ref()).unwrap();
//struct to json(String)
let put_params_str: String =serde_json::to_string(&put_params).unwrap();
// save to momento
let key = put_params.key;
match cache_client.set(&cache_name, key.clone(), put_params_str.clone(), NonZeroU64::new(put_params.ttl_sec)).await {
Ok(_) => {}
Err(err) => {
eprintln!("{}", err);
}
};
Response::builder()
.status(200)
.header("content-type", "applicatiom/json")
.body("{result:'ok'}".to_string())
.map_err(Box::new)?
},
Some("get") => {
println!("action : get");
let key = param_map.first("key").unwrap_or("none");
println!("key : {:?}",key);
let result_value = match cache_client.get(&cache_name, key.clone()).await {
Ok(r) => match r.result {
MomentoGetStatus::HIT => String::from_utf8(r.value).unwrap(),
_ => error_json.to_string(),
},
Err(err) => {
eprintln!("{}", err);
error_json.to_string()
}
};
let v: Value = serde_json::from_str(&result_value)?;
Response::builder()
.status(200)
.header("content-type", v["content_type"].as_str().unwrap().to_string())
.body(v["content"].as_str().unwrap().to_string())
.map_err(Box::new)?
},
_ => {
Response::builder()
.status(200)
.header("content-type", "text/html")
.body("other request!".to_string())
.map_err(Box::new)?
}
};
Ok(result)
}
デプロイ&動作確認
プログラムの修正ができたらビルド&デプロイをします。
# ビルド
% cargo lambda build
# デプロイ時にサイズに関連するエラーが出た場合 --releaseをつければ回避できるかも
#アップロード
% cargo lambda deploy --enable-function-url momento-lambda --iam-role <IAM_ROLE_ARN>
? function arn: arn:aws:・・・・・・
? function url: https://yourlambdaurl.lambda-url.your-region.on.aws/
動作確認はcurlを使用します。
適当なキャッシュ登録用json(test.json)を用意します。
{"key":"my_key","ttl_sec":30,"content_type":"text/html","content":"<html><head><title>Href Example</title></head><body><h1>Href Example</h1><p><a href='https://dev.classmethod.jp/'> DevelopersIO </a> ClassMethod's 'I've tried it' technology media.</p></body></html>"}
ここではキャッシュのキーを「my_key」、TTLを30秒に設定してHTMLを記述しています。
test.jsonを指定して、LambdaにPOST。
curl -X POST 'https://yourlambdaurl.lambda-url.your-region.on.aws/' -d @test.json
これで30秒有効なHTMLコンテンツがキャッシュに登録されました。
登録してから30秒以内であれば、
↓のURLにアクセスすると有効なHTMLが帰ってきます。
https://yourlambdaurl.lambda-url.your-region.on.aws/?action=get&key=my_key
jsonでcontent typeも設定できるので、
HTML以外でも保存できるはずです。(サイズ制限はありますが)