In this lesson we’ll build our first lambda function.
use lambda_http::{
http::header::CONTENT_TYPE, run, service_fn, Body,
Error, Request, Response,
};
async fn function_handler(
event: Request,
) -> Result<Response<Body>, Error> {
dbg!(event);
let html = "<html><body><h1>hello!</h1></body></html>";
let resp = Response::builder()
.status(200)
.header(CONTENT_TYPE, "text/html")
.body(Body::Text(html.to_string()))?;
Ok(resp)
}
#[tokio::main]
async fn main() -> Result<(), Error> {
run(service_fn(function_handler)).await
}
Serverless functions have two runtime behaviors that we need to care about: The code that runs the very first time the lambda is instantiated, and the code that runs every time a request is handled.
This is because Netlify (or AWS under the hood) controls how many of our functions exist at any given time. If we have a lot of traffic, then we have a lot of functions being instantiated. Once they’re instantiated, they can handle a request as many times as is necessary.
“initializing” a function is called the cold boot, and this code is the code in our main function.
Our function_handler on the other hand, will run on every request that comes in.
So if we send three requests one after the other, we would expect one cold boot sequence and three executions of the function_handler.
lambda_http::run
In main we use run and service_fn.
run starts the lambda runtime and handles converting the API Gateway event into a lambda_http::Request to pass to our request handler.
When we await run, we start an infinite loop. When each event comes in, Netlify wakes our function up, which will process the event, and after processing the event Netlify will “freeze” our function.
This means that when the next event comes in we’re still in the loop, ready to read the next event.
lambda_http::service_fn
We’d really like to be able to write regular async functions for our function handler, but lambda_http also needs that function to behave in certain ways. Specifically, run requires that the argument we’re passing to it implements Service.
To accomplish this, there is the lambda_http::Service trait. We don’t need to implement the Service trait ourselves, because we can use service_fn with an argument that is an async function, and it will handle that for us.
function_handler
This all means our function_handler can be a regular async function that returns a Result.
The argument passed to the function is a Request, which is passed to our function from the runtime.
Our handler has to return a Response or an Error, which is an alias for a Box<dyn Error> with some additional restrictions. Basically this is a way of pushing the identification of the error to runtime and allowing us to pass pretty much anything back as an error.
The Response accepts a type argument detailing the response type, which is a Body in this case.
The Request and Response types come from the http crate and are re-exported through lambda_http. While the Body type is from the aws_lambda_events crate.
This means we’re generally defining our lambda in terms of the http crate’s http::Request -> http::Response. Which is nice because this is what a lot of the Rust ecosystem uses.
Building a Response
Given this, we can use the dbg macro to debug out the Request that comes in and set up the content we’re going to send back as a string of html.
Response::builder()
.status(200)
.header(CONTENT_TYPE, "text/html")
.body(Body::Text(html.to_string()))
The Response itself we can build up using a builder-style API.
Response::builder kicks us off, returning us a Builder struct that has more functions for configuring a response.
In this case, we use
.statusto set the http status code to200.headerto set the content type header totext/html.lambda_httpre-exports thehttpcrate underlambda_http::http, so we can pull in theCONTENT_TYPEconstant, which is just something I like to do so that I don’t mis-type the header. We could also use a string here..bodyto set the body of our response, which is one of theBodyenum variants.
Body can be empty, text, or some binary content. In our case its a string of html, so we use Body::Text.
Dealing with Errors
The body function on the Builder type returns a http::Result.
This is why instead of returning the return value of body, we use ? and then use Ok right after.
? will pass the error through the From trait, converting it into something we can return from our function.
In the next lesson we’ll test our function locally.