Создание бессерверной контактной формы для вашего статического сайта

Опубликовано: 2022-03-10
Краткое резюме ↬ С ​​помощью этой статьи вы, наконец, сможете изучить основы API-интерфейсов Amazon Web Services (AWS) Lambda и Simple Email Service (SES), которые помогут вам создать собственную почтовую программу статического сайта на Serverless Framework. Давайте начнем!

Генераторы статических сайтов представляют собой быструю и простую альтернативу системам управления контентом (CMS), таким как WordPress. Нет настройки сервера или базы данных, только процесс сборки и простой HTML, CSS и JavaScript. К сожалению, без сервера легко быстро выйти за их пределы. Например, при добавлении контактной формы.

С появлением бессерверной архитектуры добавление контактной формы на ваш статический сайт больше не должно быть причиной для перехода на CMS. Можно получить лучшее из обоих миров: статический сайт с бессерверной серверной частью для контактной формы (которую вам не нужно поддерживать). Возможно, лучше всего то, что на сайтах с низким трафиком, таких как портфолио, высокие лимиты многих бессерверных провайдеров делают эти услуги совершенно бесплатными!

В этой статье вы познакомитесь с основами API-интерфейсов Amazon Web Services (AWS) Lambda и Simple Email Service (SES) для создания собственного почтового модуля статического сайта на основе Serverless Framework. Полный сервис будет принимать данные формы, отправленные из запроса AJAX, обращаться к конечной точке Lambda, анализировать данные для построения параметров SES, отправлять адрес электронной почты и возвращать ответ нашим пользователям. Я проведу вас через настройку Serverless в первый раз посредством развертывания. Это займет меньше часа, так что давайте начнем!

Статическая форма сайта, отправляющая сообщение на конечную точку Lambda и возвращающая ответ пользователю.
Статическая форма сайта, отправляющая сообщение на конечную точку Lambda и возвращающая ответ пользователю.
Еще после прыжка! Продолжить чтение ниже ↓

Настройка

Существуют минимальные предпосылки для начала работы с бессерверной технологией. Для наших целей это просто Node Environment с Yarn, Serverless Framework и учетной записью AWS.

Настройка проекта

Веб-сайт Serverless Framework. Полезно для установки и документирования.
Веб-сайт Serverless Framework. Полезно для установки и документирования.

Мы используем Yarn для установки Serverless Framework в локальный каталог.

  1. Создайте новый каталог для размещения проекта.
  2. Перейдите в каталог в интерфейсе командной строки.
  3. Запустите yarn init , чтобы создать файл package.json для этого проекта.
  4. Запустите yarn add serverless , чтобы установить фреймворк локально.
  5. Запустите yarn serverless create --template aws-nodejs --name static-site-mailer чтобы создать шаблон службы Node и назвать его static-site-mailer .

Наш проект настроен, но мы ничего не сможем сделать, пока не настроим наши сервисы AWS.

Настройка учетной записи Amazon Web Services, учетных данных и простой службы электронной почты

Страница регистрации Amazon Web Services, которая включает щедрый бесплатный уровень, что позволяет нашему проекту быть полностью бесплатным.
Страница регистрации Amazon Web Services, которая включает щедрый бесплатный уровень, что позволяет нашему проекту быть полностью бесплатным.

Serverless Framework записал пошаговое видео по настройке учетных данных AWS, но я также перечислил шаги здесь.

  1. Зарегистрируйте учетную запись AWS или войдите в нее, если она у вас уже есть.
  2. В строке поиска AWS найдите «IAM».
  3. На странице IAM нажмите «Пользователи» на боковой панели, затем кнопку «Добавить пользователя».
  4. На странице «Добавить пользователя» дайте пользователю имя — подойдет что-то вроде «без сервера». Установите флажок «Программный доступ» в разделе «Тип доступа», затем нажмите «Далее».
  5. На экране разрешений щелкните вкладку «Прикрепить существующие политики напрямую», найдите «AdministratorAccess» в списке, проверьте его и нажмите «Далее».
  6. На экране просмотра вы должны увидеть свое имя пользователя с «Программный доступ» и «AdministratorAccess», а затем создать пользователя.
  7. Экран подтверждения показывает пользователю «Идентификатор ключа доступа» и «Секретный ключ доступа», они понадобятся вам для предоставления доступа к Serverless Framework. В интерфейсе командной строки введите yarn sls config credentials --provider aws --key YOUR_ACCESS_KEY_ID --secret YOUR_SECRET_ACCESS_KEY , заменив YOUR_ACCESS_KEY_ID и YOUR_SECRET_ACCESS_KEY ключами на экране подтверждения.

Ваши учетные данные настроены, но пока мы находимся в консоли AWS, давайте настроим Simple Email Service.

  1. Нажмите «Главная страница консоли» в левом верхнем углу, чтобы вернуться домой.
  2. На главной странице в строке поиска AWS выполните поиск «Simple Email Service».
  3. На главной странице SES нажмите «Адреса электронной почты» на боковой панели.
  4. На странице со списком адресов электронной почты нажмите кнопку «Подтвердить новый адрес электронной почты».
  5. В диалоговом окне введите свой адрес электронной почты и нажмите «Подтвердить этот адрес электронной почты».
  6. Через несколько секунд вы получите электронное письмо со ссылкой для подтверждения адреса. Нажмите на ссылку, чтобы завершить процесс.

Теперь, когда наши учетные записи созданы, давайте взглянем на файлы шаблонов Serverless.

Настройка бессерверной среды

Запуск serverless create создает два файла: handler.js, который содержит функцию Lambda, и serverless.yml, который является файлом конфигурации для всей бессерверной архитектуры. В файле конфигурации вы можете указать столько обработчиков, сколько хотите, и каждый из них будет сопоставлен с новой функцией, которая может взаимодействовать с другими функциями. В этом проекте мы создадим только один обработчик, но в полной бессерверной архитектуре у вас будет несколько различных функций службы.

Файловая структура по умолчанию, сгенерированная из Serverless Framework, содержащая handler.js и serverless.yml.
Файловая структура по умолчанию, сгенерированная из Serverless Framework, содержащая handler.js и serverless.yml.

В handler.js вы увидите одну экспортированную функцию с именем hello . На данный момент это основная (и единственная) функция. Он, как и все обработчики Node, принимает три параметра:

  • event
    Это можно рассматривать как входные данные для функции.
  • context object
    Он содержит информацию о времени выполнения лямбда-функции.
  • callback
    Необязательный параметр для возврата информации вызывающей стороне.
 // handler.js 'use strict'; module.exports.hello = (event, context, callback) => { const response = { statusCode: 200, body: JSON.stringify({ message: 'Go Serverless v1.0! Your function executed successfully!', input: event, }), }; callback(null, response); };

В нижней части hello есть обратный вызов. Это необязательный аргумент для возврата ответа, но если он не вызывается явно , он неявно возвращает значение null . Обратный вызов принимает два параметра:

  • Ошибка ошибка
    Для предоставления информации об ошибках, когда происходит сбой самой Lambda. При успешном выполнении Lambda в этот параметр следует передать null .
  • Результат объекта
    Для предоставления объекта ответа. Он должен быть совместим с JSON.stringify . Если в поле ошибки есть параметр, это поле игнорируется.

Наш статический сайт отправит данные формы в теле события, а обратный вызов вернет ответ, который увидит наш пользователь.

В serverless.yml вы увидите название сервиса, информацию о провайдере и функции.

 # serverless.yml service: static-site-mailer provider: name: aws runtime: nodejs6.10 functions: hello: handler: handler.hello 
Как имена функций в serverless.yml сопоставляются с handler.js.
Как имена функций в serverless.yml сопоставляются с handler.js.

Заметили сопоставление между функцией hello и обработчиком? Мы можем назвать наш файл и использовать что угодно, и пока он соответствует конфигурации, он будет работать. Давайте переименуем нашу функцию в staticSiteMailer .

 # serverless.yml functions: staticSiteMailer: handler: handler.staticSiteMailer
 // handler.js module.exports.staticSiteMailer = (event, context, callback) => { ... };

Функциям Lambda требуется разрешение на взаимодействие с другой инфраструктурой AWS. Прежде чем мы сможем отправить электронное письмо, нам нужно разрешить SES сделать это. В serverless.yml в provider.iamRoleStatements добавьте разрешение.

 # serverless.yml provider: name: aws runtime: nodejs6.10 iamRoleStatements: - Effect: "Allow" Action: - "ses:SendEmail" Resource: ["*"]

Поскольку нам нужен URL-адрес для нашего действия с формой, нам нужно добавить события HTTP в нашу функцию. В serverless.yml мы создаем путь, указываем метод как post и устанавливаем для CORS значение true для безопасности.

 functions: staticSiteMailer: handler: handler.staticSiteMailer events: - http: method: post path: static-site-mailer cors: true

Наши обновленные файлы serverless.yml и handler.js должны выглядеть так:

 # serverless.yml service: static-site-mailer provider: name: aws runtime: nodejs6.10 functions: staticSiteMailer: handler: handler.staticSiteMailer events: - http: method: post path: static-site-mailer cors: true provider: name: aws runtime: nodejs6.10 iamRoleStatements: - Effect: "Allow" Action: - "ses:SendEmail" Resource: ["*"]
 // handler.js 'use strict'; module.exports.staticSiteMailer = (event, context, callback) => { const response = { statusCode: 200, body: JSON.stringify({ message: 'Go Serverless v1.0! Your function executed successfully!', input: event, }), }; callback(null, response); };

Наша бессерверная архитектура настроена, поэтому давайте развернем ее и протестируем. Вы получите простой ответ JSON.

 yarn sls deploy --verbose yarn sls invoke --function staticSiteMailer { "statusCode": 200, "body": "{\"message\":\"Go Serverless v1.0! Your function executed successfully!\",\"input\":{}}" } 
Возвращаемый ответ от вызова нашей новой бессерверной функции.
Возвращаемый ответ от вызова нашей новой бессерверной функции.

Создание HTML-формы

Входные данные нашей лямбда-функции и выходные данные формы должны совпадать, поэтому, прежде чем мы создадим функцию, мы создадим форму и зафиксируем ее выходные данные. Мы сохраняем простоту с полями имени, электронной почты и сообщения. Мы добавим действие формы после того, как развернем нашу бессерверную архитектуру и получим наш URL-адрес, но мы знаем, что это будет POST-запрос, поэтому мы можем добавить его. В конце формы мы добавляем тег абзаца для отображения ответные сообщения пользователю, которые мы обновим при обратном вызове отправки.

 <form action="{{ SERVICE URL }}" method="POST"> <label> Name <input type="text" name="name" required> </label> <label> Email <input type="email" name="reply_to" required> </label> <label> Message: <textarea name="message" required></textarea> </label> <button type="submit">Send Message</button> </form> <p></p>

Для захвата вывода мы добавляем в форму обработчик отправки, превращаем параметры формы в объект и отправляем строковый JSON в нашу функцию Lambda. В функции Lambda мы используем JSON.parse() для чтения наших данных. В качестве альтернативы вы можете использовать Serialize или строку запроса jQuery для отправки и анализа параметров формы в виде строки запроса, но JSON.stringify() и JSON.parse() являются нативными.

 (() => { const form = document.querySelector('form'); const formResponse = document.querySelector('js-form-response'); form.onsubmit = e => { e.preventDefault(); // Prepare data to send const data = {}; const formElements = Array.from(form); formElements.map(input => (data[input.name] = input.value)); // Log what our lambda function will receive console.log(JSON.stringify(data)); }; })();

Идите вперед и отправьте свою форму, а затем запишите вывод консоли. Далее мы будем использовать его в нашей лямбда-функции.

Запись данных формы в журнал консоли.
Запись данных формы в журнал консоли.

Вызов лямбда-функций

Особенно во время разработки нам нужно проверить, делает ли наша функция то, что мы ожидаем. Serverless Framework предоставляет команды invoke и invoke local команд для запуска вашей функции из реальной среды и среды разработки соответственно. Обе команды требуют переданного имени функции, в нашем случае staticSiteMailer .

 yarn sls invoke local --function staticSiteMailer

Чтобы передать фиктивные данные в нашу функцию, создайте новый файл с именем data.json с захваченным выводом консоли под ключом body в объекте JSON. Это должно выглядеть примерно так:

 // data.json { "body": "{\"name\": \"Sender Name\",\"reply_to\": \"[email protected]\",\"message\": \"Sender message\"}" }

Чтобы вызвать функцию с локальными данными, передайте аргумент --path вместе с путем к файлу.

 yarn sls invoke local --function staticSiteMailer --path data.json 
Обновленный ответ, возвращаемый нашей бессерверной функцией, когда мы передаем ей данные JSON.
Обновленный ответ, возвращаемый нашей бессерверной функцией, когда мы передаем ей данные JSON.

Вы увидите ответ, аналогичный предыдущему, но ключ input будет содержать событие, которое мы имитировали. Давайте используем наши фиктивные данные для отправки электронного письма с помощью Simple Email Service!

Отправка электронной почты с помощью простой службы электронной почты

Мы собираемся заменить функцию staticSiteMailer вызовом частной функции sendEmail . А пока вы можете закомментировать или удалить код шаблона и заменить его на:

 // hander.js function sendEmail(formData, callback) { // Build the SES parameters // Send the email } module.exports.staticSiteMailer = (event, context, callback) => { const formData = JSON.parse(event.body); sendEmail(formData, function(err, data) { if (err) { console.log(err, err.stack); } else { console.log(data); } }); };

Сначала мы анализируем event.body для захвата данных формы, а затем передаем их частной функции sendEmail . sendEmail отвечает за отправку электронной почты, а функция обратного вызова вернет ответ об ошибке или успехе с err или data . В нашем случае мы можем просто зарегистрировать ошибку или данные, так как через мгновение мы заменим их обратным вызовом Lambda.

Amazon предоставляет удобный SDK, aws-sdk , для подключения своих сервисов к функциям Lambda. Многие из их услуг, в том числе SES, являются его частью. Добавляем его в проект с помощью yarn add aws-sdk и импортируем в топ файл обработчика.

 // handler.js const AWS = require('aws-sdk'); const SES = new AWS.SES();

В нашей частной функции sendEmail мы строим параметры SES.sendEmail из проанализированных данных формы и используем обратный вызов для возврата ответа вызывающей стороне. Параметры требуют в качестве объекта следующее:

  • Источник
    Адрес электронной почты, с которого SES отправляет .
  • Ответить на адреса
    Массив адресов электронной почты, добавленный в ответ на поле в письме.
  • Назначения
    Объект, который должен содержать хотя бы один ToAddresses , CcAddresses или BccAddresses . Каждое поле принимает массив адресов электронной почты, соответствующих полям «Кому», « Копия » и « Скрытая копия » соответственно.
  • Сообщение
    Объект, который содержит Body и Subject .

Поскольку formData является объектом, мы можем напрямую вызывать поля формы, например formData.message , создавать параметры и отправлять их. Мы передаем ваш адрес электронной почты, подтвержденный SES, в Source и Destination.ToAddresses . Пока электронная почта подтверждена, вы можете передавать сюда что угодно, включая разные адреса электронной почты. Мы выдергиваем наши reply_to , message и name из нашего объекта formData , чтобы заполнить поля ReplyToAddresses и Message.Body.Text.Data .

 // handler.js function sendEmail(formData, callback) { const emailParams = { Source: '[email protected]', // SES SENDING EMAIL ReplyToAddresses: [formData.reply_to], Destination: { ToAddresses: ['[email protected]'], // SES RECEIVING EMAIL }, Message: { Body: { Text: { Charset: 'UTF-8', Data: `${formData.message}\n\nName: ${formData.name}\nEmail: ${formData.reply_to}`, }, }, Subject: { Charset: 'UTF-8', Data: 'New message from your_site.com', }, }, }; SES.sendEmail(emailParams, callback); }

SES.sendEmail отправит электронное письмо, и наш обратный вызов вернет ответ. Вызов локальной функции отправит электронное письмо на ваш подтвержденный адрес.

 yarn sls invoke local --function staticSiteMailer --path data.json 
Возвращаемый ответ от SES.sendEmail в случае успеха.
Возвращаемый ответ от SES.sendEmail случае успеха.

Возврат ответа от обработчика

Наша функция отправляет электронное письмо с помощью командной строки, но наши пользователи не будут с ней взаимодействовать. Нам нужно вернуть ответ на отправку формы AJAX. Если это не удается, мы должны вернуть соответствующий код состояния, а err.message statusCode В случае успеха достаточно 200 statusCode , но мы также вернем ответ почтовой программы в теле. В staticSiteMailer мы создаем наши данные ответа и заменяем нашу функцию обратного вызова sendEmail на обратный вызов Lambda.

 // handler.js module.exports.staticSiteMailer = (event, context, callback) => { const formData = JSON.parse(event.body); sendEmail(formData, function(err, data) { const response = { statusCode: err ? 500 : 200, headers: { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': 'https://your-domain.com', }, body: JSON.stringify({ message: err ? err.message : data, }), }; callback(null, response); }); };

Наш обратный вызов Lambda теперь возвращает как сообщения об успехе, так и сообщения об ошибках из SES.sendEmail . Мы строим ответ с проверками, присутствует ли err , чтобы наш ответ был последовательным. Сама функция обратного вызова Lambda передает null в поле аргумента ошибки, а ответ — вторым. Мы хотим передавать ошибки дальше, но если сама Lambda выйдет из строя, ее обратный вызов будет неявно вызван с ответом об ошибке.

В headers вам нужно будет заменить Access-Control-Allow-Origin на ваш собственный домен. Это предотвратит использование вашего сервиса другими доменами и, возможно, выставление счетов за AWS на ваше имя! И я не рассказываю об этом в этой статье, но можно настроить Lambda для использования вашего собственного домена. Вам понадобится сертификат SSL/TLS, загруженный в Amazon. Команда Serverless Framework написала фантастическое руководство о том, как это сделать.

При вызове локальной функции теперь будет отправлено электронное письмо и возвращен соответствующий ответ.

 yarn sls invoke local --function staticSiteMailer --path data.json 
Возвращаемый ответ от нашей бессерверной функции, содержащий в теле ответ возврата SES.sendEmail.
Возвращаемый ответ от нашей бессерверной функции, содержащий в теле ответ возврата SES.sendEmail.

Вызов лямбда-функции из формы

Наша служба завершена! Чтобы развернуть его, запустите yarn sls deploy -v . После его развертывания вы получите URL-адрес, который выглядит примерно так: https://r4nd0mh45h.execute-api.us-east-1.amazonaws.com/dev/static-site-mailer , который вы можете добавить в действие формы. Далее мы создаем запрос AJAX и возвращаем ответ пользователю.

 (() => { const form = document.querySelector('form'); const formResponse = document.querySelector('js-form-response'); form.onsubmit = e => { e.preventDefault(); // Prepare data to send const data = {}; const formElements = Array.from(form); formElements.map(input => (data[input.name] = input.value)); // Log what our lambda function will receive console.log(JSON.stringify(data)); // Construct an HTTP request var xhr = new XMLHttpRequest(); xhr.open(form.method, form.action, true); xhr.setRequestHeader('Accept', 'application/json; charset=utf-8'); xhr.setRequestHeader('Content-Type', 'application/json; charset=UTF-8'); // Send the collected data as JSON xhr.send(JSON.stringify(data)); // Callback function xhr.onloadend = response => { if (response.target.status === 200) { // The form submission was successful form.reset(); formResponse.innerHTML = 'Thanks for the message. I'll be in touch shortly.'; } else { // The form submission failed formResponse.innerHTML = 'Something went wrong'; console.error(JSON.parse(response.target.response).message); } }; }; })();

В обратном вызове AJAX мы проверяем код состояния с помощью response.target.status . Если это что-то отличное от 200 , мы можем показать пользователю сообщение об ошибке, в противном случае сообщите ему, что сообщение было отправлено. Поскольку наша Lambda возвращает строковый JSON, мы можем проанализировать текст сообщения с помощью JSON.parse(response.target.response).message . Особенно полезно регистрировать ошибку.

Вы должны иметь возможность отправить форму полностью со своего статического сайта!

Статическая форма сайта, отправляющая сообщение на конечную точку Lambda и возвращающая ответ пользователю.
Статическая форма сайта, отправляющая сообщение на конечную точку Lambda и возвращающая ответ пользователю.

Следующие шаги

Добавить контактную форму в статику легко с помощью Serverless Framework и AWS. В нашем коде есть возможности для улучшения, например, добавление проверки формы с помощью приманки, предотвращение вызовов AJAX для недопустимых форм и улучшение UX при ответе, но этого достаточно для начала. Вы можете увидеть некоторые из этих улучшений в репозитории почтовой программы статического сайта, который я создал. Я надеюсь, что вдохновил вас попробовать Serverless самостоятельно!