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
.status
to set the http status code to200
.header
to set the content type header totext/html
.lambda_http
re-exports thehttp
crate underlambda_http::http
, so we can pull in theCONTENT_TYPE
constant, which is just something I like to do so that I don’t mis-type the header. We could also use a string here..body
to set the body of our response, which is one of theBody
enum 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.