Imagine a pretty common use case when we have a static web site and we decided to add some computation logic to it. For example a form to submit questions, leave feedback, subscribe to emails etc.
This is a pretty trivial logic and having a dedicated server to handle such requests looks not effective.
We can use Function as a Service for this, for example AWS Lambda.
By default Lambda function will be invoked synchronously and some requests will require a cold start. We can invoke our Lambda asynchronously so that our users don’t wait until Lambda is started.
Source code of ready solution is available here. It’s working Lambda function. I will touch it partially below.
AWS Lambda is good for handling events and JSON inputs but it doesn’t support handling of traditional web content types out of the box. I will return to this issue soon.
The question is how to connect our static web site and Lambda. We can setup API Gateway to be a facade for Lambda. It will represent a web service for our web site. Under the hood Gateway will pass all requests to our Lambda function and return its response back to our web site.
API Gateway supports transformation of incoming requests and outcoming responses. We will use it to transform web content types to JSON that Lambda supports.
How to configure requests transformation?
If our submit form contains only text fields and content type is application/x-www-form-urlencoded, then we can follow these steps to setup whole process. Even if you have another content type, please read these steps to get familiar with a whole idea of tuning Gateway.
If we need to support files, then we go with multipart/form-data content type. To configure Gateway we need the following:
- Setup multipart/form-data content type in API Gateway following mentioned steps.
- Add multipart/form-data as Binary Media Type to API Settings:
How to handle form submit in Lambda function?
Handling multipart/form-data with any modern web framework is trivial, usually we have a direct mapping to POJO classes (sorry JS, Ruby, GO developers but this more or less means the same for your languages).
It’s not the case with Lambda. We have a function that is invoked by AWS and we cannot have web frameworks around it.
So we need to map multipart/form-data stuff with our POJO ourselves.
I will use Java language further. For other languages you need pretty much the same things to be done.
Parsing form data
Form data passed to our Lambda function from API Gateway is encoded as Base64. After decoding form data has the following content:
You can see that each entry is separated by Boundary. This Boundary is defined on front end side and passed to API Gateway inside “Content-Type” header. We need to know this Boundary in our Lambda function to be able to parse form data.
However Lambda function doesn’t have headers passed from our web site. We need to tune our API Gateway to pass required header to our Lambda function explicitly.
I will use already mentioned approach to configure request mapping.
This change tells API Gateway to pass Content-Type header as content-type parameter for Lambda function.
For parsing form data in Java I will use Apache Commons Fileupload via Delight FileUpload abstraction.
You can see that I pass decoded body and content type header to Delight FileUpload and then transform FileItem elements into an internal representation of data.
Further logic is trivial and I will not touch it in details here, but you can see sources always.
Looks like we have everything we need already and can run our web site to use Lambda function.
But when we run it, we see errors about CORS in browser console. Our static web site and API Gateway have different domains and so browsers detect this as major security issue.
It’s not a big deal with sophisticated hosting where we can setup CORS headers. But it is a problem for me, because I use GitHub Pages to host my web site and GitHub doesn’t support setting CORS headers.
However API Gateway can help us here too. We just need to click Enable CORS in API menu:
Please note that we need to redeploy our API after each change in it.
Email sending in AWS Lambda
I will send email with submitted form data to my personal email.
Many people use AWS Simple Email Service for sending emails from Lambda. I’m not sure if many of them understand if they really need that.
AWS SES makes sense for complex email broadcasts, but don’t forget that email sending is a trivial process and we can do it ourselves.
I use plain old Javax Mail for email sending.
Custom domain for API
It’s off topic here but you can use your own domain for API Gateway (i.e. your web site will not have requests to strange AWS endpoints).
AWS documentation requires PhD to understand how to do add custom domain :-(
Simple explanation can be found here.
Sources of Lambda function are here. Basically you can use it as your own form handler already.
Don’t hesitate to ask questions :-)