目的
AWS LambdaでAPIっぽいことをするにはlambda_httpを使うことになるんですが、HTTPの低レイヤーを相手にすることが多くて辛いです。
何かいいものが無いかと調べていたら、数日前にaxum-aws-lambdaというものが出来ていたのでこれを使ってみました(この記事を書いた日からだと27日前でした)。
コード
lambda_httpだけでやってみる。
Cargo.toml
[package]
name = "web"
version = "0.1.0"
edition = "2021"
[dependencies]
lambda_http = "0.5.2"
serde_json = "1"
tokio = { version = "1", features = ["full"] }
main.rs
use lambda_http::{
http::{response::Builder, Method, StatusCode},
service_fn, Body, Error, Request, RequestExt, Response,
};
use serde_json::json;
#[tokio::main]
async fn main() -> Result<(), Error> {
lambda_http::run(service_fn(func)).await?;
Ok(())
}
async fn func(request: Request) -> Result<Response<Body>, Error> {
let unmatch = not_found();
match request.uri().path() {
"/name" => match request.method() {
&Method::GET => execute_get(&request).await,
&Method::POST => execute_post(&request).await,
_ => unmatch,
},
_ => unmatch,
}
}
fn not_found() -> Result<Response<Body>, Error> {
let builder = Builder::new().status(StatusCode::NOT_FOUND);
let json = json!({});
Ok(builder.body(Body::Text(json.to_string()))?)
}
async fn execute_get(request: &Request) -> Result<Response<Body>, Error> {
let params = request.query_string_parameters();
let name = params.first("name").unwrap_or("World");
let builder = Builder::new().status(StatusCode::OK);
let json = json!({
"type": "get",
"message": format!("{} World!", name)
});
Ok(builder.body(Body::Text(json.to_string()))?)
}
async fn execute_post(request: &Request) -> Result<Response<Body>, Error> {
let json: serde_json::Value = match request.body() {
Body::Text(text) => serde_json::from_str(text).unwrap_or(serde_json::Value::Null),
_ => serde_json::Value::Null,
};
let builder = Builder::new().status(StatusCode::OK);
let json = json!({
"type": "post",
"message": format!("{} World!", json["name"].as_str().unwrap_or("World"))
});
Ok(builder.body(Body::Text(json.to_string()))?)
}
辛いところ
- ルーティングするためにパスやメソッドを分析
- not foundの準備
- クエリーパラメーターを分解
- リクエストBodyの解析
- レスポンスを作る
aws-lambda-axumを使う
Cargo.toml
[package]
name = "web"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
axum = "0.5.11"
axum-aws-lambda = "0.1.0"
lambda_http = "0.5.2"
serde = { version = "1", features = ["derive"] }
serde_json = "1"
tokio = { version = "1", features = ["full"] }
main.rs
use axum::{Router, routing::{get, post}, response::IntoResponse, Json, extract::Query};
use serde::Deserialize;
use serde_json::json;
#[tokio::main]
async fn main() {
let app = Router::new()
.route("/", get(execute_get))
.route("/", post(execute_post))
;
let app = lambda_http::tower::ServiceBuilder::new()
.layer(axum_aws_lambda::LambdaLayer::default())
.service(app);
lambda_http::run(app).await.unwrap();
}
#[derive(Deserialize)]
pub struct GetParameter {
name: String,
}
async fn execute_get(Query(params): Query<GetParameter>) -> impl IntoResponse {
Json(json!({
"type": "get",
"message": format!("{} Worled!", params.name)
}))
}
#[derive(Deserialize)]
pub struct PostParameter {
name: String,
}
async fn execute_post(Json(params): Json<PostParameter>) -> impl IntoResponse {
Json(json!({
"type": "post",
"message": format!("{} Worled!", params.name)
}))
}
まとめ
辛いとこまとめて解決できました。
lambda_httpもaxumもtower-layerを利用いるので相性が良いようです。
axum-aws-lambdaのサンプルでは他のtower-layerも使っていて、CORSなどの機能を簡単に追加しています。
自分もtower-cookieを利用していますが問題無く使えています。