15 unstable releases (5 breaking)

0.7.3 Jan 13, 2023
0.7.2 Dec 17, 2022
0.7.1 Oct 28, 2022
0.6.0 Jul 19, 2022
0.1.0 Jan 25, 2019

#9 in HTTP server

Download history 4310/week @ 2022-10-11 4578/week @ 2022-10-18 5498/week @ 2022-10-25 5014/week @ 2022-11-01 5466/week @ 2022-11-08 5640/week @ 2022-11-15 4382/week @ 2022-11-22 4532/week @ 2022-11-29 4932/week @ 2022-12-06 4997/week @ 2022-12-13 4053/week @ 2022-12-20 2665/week @ 2022-12-27 4529/week @ 2023-01-03 5645/week @ 2023-01-10 6184/week @ 2023-01-17 5437/week @ 2023-01-24

22,200 downloads per month
Used in fewer than 15 crates

Apache-2.0

150KB
2.5K SLoC

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:

  • Request - Represents an HTTP request
  • IntoResponse - Future that will convert an [IntoResponse] into an actual [LambdaResponse]

We are able to handle requests from:

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

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

For example some handy extensions:

  • query_string_parameters - Return pre-parsed http query string parameters, parameters provided after the ? portion of a url associated with the request
  • path_parameters - Return pre-extracted path parameters, parameter provided in url placeholders /foo/{bar}/baz/{boom} associated with the request
  • payload - Return the Result of a payload parsed into a serde Deserializeable type

Examples

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

  • Reading a JSON from a body and deserialise into a structure
  • Reading querystring parameters
  • Lambda Request Authorizer
  • Passing the Lambda execution context initialisation to the handler

Reading a JSON from a body and deserialise into a structure

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

use http::Response;
use lambda_http::{run, http::StatusCode, service_fn, Error, IntoResponse, Request, RequestExt};
use serde::{Deserialize, Serialize};
use serde_json::json;

#[tokio::main]
async fn main() -> Result<(), Error> {
    tracing_subscriber::fmt()
        .with_ansi(false)
        .without_time()
        .with_max_level(tracing_subscriber::filter::LevelFilter::INFO)
        .init();

    run(service_fn(function_handler)).await
}

pub async fn function_handler(event: Request) -> Result<impl IntoResponse, Error> {
    let body = event.payload::<MyPayload>()?;

    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 querystring parameters

use http::Response;
use lambda_http::{run, http::StatusCode, service_fn, Error, IntoResponse, Request, RequestExt};
use serde_json::json;

#[tokio::main]
async fn main() -> Result<(), Error> {
    tracing_subscriber::fmt()
        .with_ansi(false)
        .without_time()
        .with_max_level(tracing_subscriber::filter::LevelFilter::INFO)
        .init();

    run(service_fn(function_handler)).await
}

pub async fn function_handler(event: Request) -> Result<impl IntoResponse, Error> {
    let name = event.query_string_parameters()
        .first("name")
        .unwrap_or_else(|| "stranger")
        .to_string();

    // 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.

use aws_lambda_events::apigw::{
    ApiGatewayCustomAuthorizerRequestTypeRequest, ApiGatewayCustomAuthorizerResponse, ApiGatewayCustomAuthorizerPolicy, IamPolicyStatement,
};
use lambda_runtime::{run, service_fn, Error, LambdaEvent};
use serde_json::json;

#[tokio::main]
async fn main() -> Result<(), Error> {
    tracing_subscriber::fmt()
        .with_ansi(false)
        .without_time()
        .with_max_level(tracing_subscriber::filter::LevelFilter::INFO)
        .init();

    run(service_fn(function_handler)).await
}

pub async fn function_handler(event: LambdaEvent<ApiGatewayCustomAuthorizerRequestTypeRequest>) -> Result<ApiGatewayCustomAuthorizerResponse, Error> {
    // do something with the event payload
    let method_arn = 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 custom_authorizer_response(effect: &str, principal: &str, method_arn: &str) -> ApiGatewayCustomAuthorizerResponse {
    let stmt = IamPolicyStatement {
        action: vec!["execute-api:Invoke".to_string()],
        resource: vec![method_arn.to_owned()],
        effect: Some(effect.to_owned()),
    };
    let policy = ApiGatewayCustomAuthorizerPolicy {
        version: Some("2012-10-17".to_string()),
        statement: vec![stmt],
    };
    ApiGatewayCustomAuthorizerResponse {
        principal_id: Some(principal.to_owned()),
        policy_document: policy,
        context: json!({ "email": principal }), // https://github.com/awslabs/aws-lambda-rust-runtime/discussions/548
        usage_identifier_key: None,
    }
}

Passing the Lambda execution context initialisation 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.

use aws_sdk_dynamodb::model::AttributeValue;
use chrono::Utc;
use http::Response;
use lambda_http::{run, http::StatusCode, service_fn, Error, IntoResponse, Request, RequestExt};
use serde_json::json;

#[tokio::main]
async fn main() -> Result<(), Error> {
    tracing_subscriber::fmt()
        .with_ansi(false)
        .without_time()
        .with_max_level(tracing_subscriber::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 function_handler(dynamodb_client: &aws_sdk_dynamodb::Client, event: Request) -> Result<impl IntoResponse, Error> {
    let table = std::env::var("TABLE_NAME").expect("TABLE_NAME must be set");

    let name = event.query_string_parameters()
        .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)
}

Dependencies

~11–18MB
~390K SLoC