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.