Our serverless function currently responds with static HTML, but we usually want our functions to do something based on user input.
We can use the query string to accept the name of the user and format that into our HTML result.
The RequestExt extensions trait contains a number of functions we can use to get query string or other variables.
Bring RequestExt into scope to access the extra functions.
use lambda_http::{
http::header::CONTENT_TYPE, run, service_fn, Body,
Error, Request, RequestExt, Response,
};
and right at the top of our function_handler, we can use query_string_parameters_ref to access a shared reference to the map of query parameters: An Option<&QueryMap>. The QueryMap type comes from the query_map` crate.
let who = event
.query_string_parameters_ref()
.and_then(|params| params.first("name"))
.unwrap_or("world");
QueryMap::first is going to return us an Option as well, since its possible the user didn’t pass in the query parameter.
So we can use Option::and_then to call .first if there’s a QueryMap and return None otherwise.
.first will get the first value for this string in the QueryMap. We only expect a single value, so this is good for us.
This will get us to unwrap_or, which will be None if we didn’t get a QueryMap OR if we didn’t get the query parameter from the user.
unwrap_or lets us set a value if the Option is None, and unwrap the value if it’s Some.
This makes who a string slice.
Formatting HTML
Its important to note at this point that passing in user data unfiltered to an HTML string is a security vulnerability. While we intend to only work with names, users can pass anything they want into the query string, including script tags, which would then be executed if we passed it through without escaping the input.
To escape the input, we can use the html-escape crate.
❯ cargo add html-escape
Updating crates.io index
Adding html-escape v0.2.13 to dependencies.
Features:
+ std
Updating crates.io index
Then we’ll bring encode_text into scope.
use html_escape::encode_text;
and use html_escape to encode the text we place into our h1 tag, and use format! to get the value in the right place.
let html = format!(
"<html><body><h1>hello {}!</h1></body></html>",
encode_text(who)
);
While not detrimental to our program, the .to_string in our Response builder is now redundant, and can be removed, leaving Text(html) instead of Text(html.to_string()).
let resp = Response::builder()
.status(200)
.header(CONTENT_TYPE, "text/html")
.body(Body::Text(html))?;
Running cargo lambda watch in one terminal boots up our local server.
❯ cargo lambda watch
INFO invoke server listening on 127.0.0.1:9000
and we can invoke the fixture we saved earlier, which already includes a query string name with the value me.
❯ cargo lambda invoke serverless-intro-netlify --data-file ./fixture.json
{
"statusCode": 200,
"headers":
{
"content-type": "text/html"
},
"multiValueHeaders":
{
"content-type":
[
"text/html"
]
},
"body": "<html><body><h1>hello me!</h1></body></html>",
"isBase64Encoded": false
}
You can go into the fixture.json and modify queryStringParameters and multiValueQueryStringParameters to have a different name value if you want to keep testing locally.
After deploying, we can use the function URL with ?name=chris to get the formatted html result.