Как создать бессерверную форму для бессерверного сайта

Сочетание S3 и CloudFront позволяет легко и недорого разместить статический сайт. Но вот вопрос: что происходит, когда вы запрашиваете HTML-форму. Рассмотрим реализацию простого бессерверного бэкенда для этой цели!

Введение

AWS S3 и Cloudfront предлагают экономичный и масштабируемый способ для размещения статического сайта, например целевой страницы (англ. landing page) для стартапа или страницы подписки на новостные рассылки. Однако хотелось бы узнать, как реализовать форму без сервера. 

В данной статье мы создадим бессерверный бэкенд для формы, задействуя AWS Lambda и API Gateway. Так мы активируем отправку электронного письма с заполненной информацией из формы на предустановленный email-адрес с помощью AWS SES.

Предварительные требования 

Перед работой с кодом Lambda необходимо настроить среду разработки и аккаунт AWS с учетом следующих моментов.

  1. Инструменты локальной разработки AWS Serverless Application Model (сокр. SAM, Модель бессерверного приложения AWS) устанавливаются в соответствии с инструкциями.
  2. Сервис электронной почты AWS SES настраивается не в “песочнице”, а в режиме продакшн. Домен проверяется в соответствии с инструкциями
  3. Имеется сайт с HTML-формой, настраиваемой для отправки данных в конечную точку шлюза API, создание которого будет рассмотрено в дальнейшем материале статьи. 

Реализация 

Для развертывания Lambda и сопутствующих ресурсов используется SAM. 

Начинаем со структуры каталога. Она включает: 1) директорию исходных файлов приложения; 2) директорию скриптов, предназначенных для развертывания приложения; 3) файлы верхнего уровня, например template.yaml, применяемые SAM.

my-serverless-form/
├── cmd/
│ └── deploy.sh
├── src/
| ├── package.json
│ └── index.js
├── .gitignore
└── template.yaml

template.yaml выглядит так:

AWSTemplateFormatVersion: "2010-09-09"
Transform: AWS::Serverless-2016-10-31
Description: >
serverless-form

SAM project for serverless-form

Parameters:
LambdaVersion:
Default: "not-specified"
Description: "What is the current code version?"
Type: String

Resources:
FormSubmissionFunction:
Type: AWS::Serverless::Function
Properties:
CodeUri: src/
Handler: index.handler
Runtime: nodejs12.x
MemorySize: 128
Timeout: 3
Environment:
Variables:
LAMBDA_VERSION: !Ref LambdaVersion
Policies:
- AWSLambdaBasicExecutionRole
- Version: "2012-10-17" # Policy Document
Statement:
- Effect: Allow
Action:
- ses:SendEmail
- ses:SendRawEmail
Resource: "*"
Events:
APIRoot:
Type: Api
Properties:
Path: /
Method: POST
RestApiId: !Ref FormSubmissionApi

FormSubmissionApi:
Type: AWS::Serverless::Api
Properties:
StageName: Prod
Cors:
AllowMethods: "'POST'"
AllowOrigin: "'https://my-domain.com'"
AllowHeaders: "'x-requested-with'"

Outputs:
FormSubmissionApi:
Description: "API Gateway endpoint URL"
Value: !Sub "https://${FormSubmissionApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/"
FormSubmissionFunction:
Description: "Lambda Function ARN"
Value: !GetAtt FormSubmissionFunction.Arn

Данный шаблон определяет 2 вида ресурсов.

  1. FormSubmissionFunction  —  Lambda, которая при выполнении отправляет электронное письмо. 
  2. FormSubmissionApi  —  шлюз API, получающий публичные запросы из интернета и запускающий Lambda. 

Код лямбда-функции указывается в файле index.js, как показано в примере: 

const aws = require("aws-sdk"); // Библиотека для взаимодействия с ресурсами AWS
const ses = new aws.SES({ region: "us-east-1" });

exports.handler = async (eventObject, context, callback) => {
// Запись лога с данными события для удобства отладки
console.log(
"Received event:",
JSON.stringify(eventObject, context, callback)
);

const emailParams = {
Destination: {
ToAddresses: ["[email protected]"],
},
Message: {
Body: {
Text: {
Data:
JSON.stringify(eventObject, context, callback) // TODO: Format your data for email here
},
},

Subject: { Data: "Contact Us Website Form" },
},
Source: "[email protected]",
};

// Отправка электронного письма
await ses.sendEmail(emailParams).promise();

// Ответ API
const response = {
statusCode: 204,
headers: {
"Access-Control-Allow-Headers": "Content-Type",
"Access-Control-Allow-Origin": "https://my-domain.com",
"Access-Control-Allow-Methods": "OPTIONS,POST",
},
isBase64Encoded: false,
body: null,
};

return response;
};

Мы отправляем электронное письмо с помощью SES, содержащего данные из eventObject. Этот объект включает в себя тело и заголовок запроса, запускающего выполнение Lambda. Вы можете форматировать тело электронного письма по своему усмотрению. 

Для парсинга данных формы в Lambda рекомендую следующую библиотеку. Это модуль nodejs, который выполняет парсинг многокомпонентной формы, содержащей файлы и поля из объекта события AWS Lambda. Он отлично справляется с парсингом двоичных и текстовых файлов. 

Для развертывания Lambda запускаем файл script.sh и следуем инструкциям на экране: 

#!/bin/bash

# Выход в случае неудачного выполнения любой команды
set -e

script_path=$( cd "$(dirname "${BASH_SOURCE[0]}")" ; pwd -P )

sam deploy \
--template-file ${script_path}/../template.yaml \
--stack-name serverless-website-form-production \
--capabilities CAPABILITY_IAM \
--guided

Заключение

Мы рассмотрели способ создания бессерверного бэкенда, используемого для приема входящих данных формы (или других данных из запросов API) и отправки электронного письма, содержащего эти данные. 

Перед развертыванием подобных бессерверных приложений в продакшн целесообразно реализовать инструмент reCaptcha (или его аналоги). Он не позволяет ботам обнаружить конечную точку и отправить ложные данные. Кому хочется платить за время выполнения Lambda, потраченное на отправку электронных писем от ботов?! 

Читайте также:

Читайте нас в TelegramVK и Яндекс.Дзен


Перевод статьи Paulo Carvalho: Create a Serverless Form for a Serverless Website

Предыдущая статья3 способа мониторинга изменений лог-файлов в Java
Следующая статьяКак подключиться к MongoDB с помощью Node.js