lambda-http for AWS Lambda in Rust

Docs

lambda-http is an abstraction that takes payloads from different services and turns them into http objects, making it easy to write API Gateway proxy event focused Lambda functions in Rust.

lambda-http handler is made of:

We are able to handle requests from:

Thanks to the Request type we can seamlessly handle proxy integrations without the worry to specify the specific service type.

There is also an extension for lambda_http::Request structs that provide access to API gateway and ALB features.

For example some handy extensions:

See the lambda_http::RequestPayloadExt and lambda_http::RequestExt traits for more extensions.

Examples

Here you will find a few examples to handle basic scenarios:

Reading a JSON from a body and deserialize into a structure

The code below creates a simple API Gateway proxy (HTTP, REST) that accept in input a JSON payload.

```rust use lambdahttp::{run, http::{StatusCode, Response}, servicefn, Error, IntoResponse, Request, RequestPayloadExt}; use serde::{Deserialize, Serialize}; use serde_json::json;

[tokio::main]

async fn main() -> Result<(), Error> { tracingsubscriber::fmt() .withansi(false) .withouttime() .withmaxlevel(tracingsubscriber::filter::LevelFilter::INFO) .init();

run(service_fn(function_handler)).await

}

pub async fn function_handler(event: Request) -> Result { let body = event.payload::()?;

let response = Response::builder()
    .status(StatusCode::OK)
    .header("Content-Type", "application/json")
    .body(json!({
        "message": "Hello World",
        "payload": body,
      }).to_string())
    .map_err(Box::new)?;

Ok(response)

}

[derive(Deserialize, Serialize, Debug, Clone)]

pub struct MyPayload { pub prop1: String, pub prop2: String, } ```

Reading query string parameters

```rust use lambdahttp::{run, http::{StatusCode, Response}, servicefn, Error, RequestExt, IntoResponse, Request}; use serde_json::json;

[tokio::main]

async fn main() -> Result<(), Error> { tracingsubscriber::fmt() .withansi(false) .withouttime() .withmaxlevel(tracingsubscriber::filter::LevelFilter::INFO) .init();

run(service_fn(function_handler)).await

}

pub async fn functionhandler(event: Request) -> Result { let name = event.querystringparametersref() .andthen(|params| params.first("name")) .unwraporelse(|| "stranger") .tostring();

// Represents an HTTP response
let response = Response::builder()
    .status(StatusCode::OK)
    .header("Content-Type", "application/json")
    .body(json!({
        "message": format!("Hello, {}!", name),
      }).to_string())
    .map_err(Box::new)?;

Ok(response)

} ```

Lambda Request Authorizer

Because lambda-http is an abstraction, we cannot use it for the Lambda Request Authorizer case. If you remove the abstraction, you need to handle the request/response for your service.

```rust use awslambdaevents::apigw::{ ApiGatewayCustomAuthorizerRequestTypeRequest, ApiGatewayCustomAuthorizerResponse, ApiGatewayCustomAuthorizerPolicy, IamPolicyStatement, }; use lambdaruntime::{run, servicefn, Error, LambdaEvent}; use serde_json::json;

[tokio::main]

async fn main() -> Result<(), Error> { tracingsubscriber::fmt() .withansi(false) .withouttime() .withmaxlevel(tracingsubscriber::filter::LevelFilter::INFO) .init();

run(service_fn(function_handler)).await

}

pub async fn functionhandler(event: LambdaEvent) -> Result { // do something with the event payload let methodarn = event.payload.method_arn.unwrap(); // for example we could use the authorization header if let Some(token) = event.payload.headers.get("authorization") { // do something

    return Ok(custom_authorizer_response(
        "ALLOW",
        "some_principal",
        &method_arn,
    ));
}

Ok(custom_authorizer_response(
  &"DENY".to_string(),
  "",
  &method_arn))

}

pub fn customauthorizerresponse(effect: &str, principal: &str, methodarn: &str) -> ApiGatewayCustomAuthorizerResponse { let stmt = IamPolicyStatement { action: vec!["execute-api:Invoke".tostring()], resource: vec![methodarn.toowned()], effect: Some(effect.toowned()), }; let policy = ApiGatewayCustomAuthorizerPolicy { version: Some("2012-10-17".tostring()), statement: vec![stmt], }; ApiGatewayCustomAuthorizerResponse { principalid: Some(principal.toowned()), policydocument: policy, context: json!({ "email": principal }), // https://github.com/awslabs/aws-lambda-rust-runtime/discussions/548 usageidentifier_key: None, } } ```

Passing the Lambda execution context initialization to the handler

One of the best practices is to take advantage of execution environment reuse to improve the performance of your function. Initialize SDK clients and database connections outside the function handler. Subsequent invocations processed by the same instance of your function can reuse these resources. This saves cost by reducing function run time.

```rust use awssdkdynamodb::model::AttributeValue; use chrono::Utc; use lambdahttp::{run, http::{StatusCode, Response}, servicefn, Error, RequestExt, IntoResponse, Request}; use serde_json::json;

[tokio::main]

async fn main() -> Result<(), Error> { tracingsubscriber::fmt() .withansi(false) .withouttime() .withmaxlevel(tracingsubscriber::filter::LevelFilter::INFO) .init();

let config = aws_config::from_env()
    .load()
    .await;

let dynamodb_client = aws_sdk_dynamodb::Client::new(&config);

run(service_fn(|event: Request| function_handler(&dynamodb_client, event))).await

}

pub async fn functionhandler(dynamodbclient: &awssdkdynamodb::Client, event: Request) -> Result { let table = std::env::var("TABLENAME").expect("TABLENAME must be set");

let name = event.query_string_parameters_ref()
    .and_then(|params| params.first("name"))
    .unwrap_or_else(|| "stranger")
    .to_string();

dynamodb_client
    .put_item()
    .table_name(table)
    .item("ID", AttributeValue::S(Utc::now().timestamp().to_string()))
    .item("name", AttributeValue::S(name.to_owned()))
    .send()
    .await?;

// Represents an HTTP response
let response = Response::builder()
    .status(StatusCode::OK)
    .header("Content-Type", "application/json")
    .body(json!({
        "message": format!("Hello, {}!", name),
      }).to_string())
    .map_err(Box::new)?;

Ok(response)

} ```