为您的静态站点构建无服务器联系表

已发表: 2022-03-10
快速总结↬在本文的帮助下,您最终将能够学习 Amazon Web Services (AWS) Lambda 和简单电子邮件服务 (SES) API 的基础知识,以帮助您在无服务器框架上构建自己的静态站点邮件程序。 让我们开始吧!

静态站点生成器为 WordPress 等内容管理系统 (CMS) 提供了一种快速而简单的替代方案。 没有服务器或数据库设置,只有构建过程和简单的 HTML、CSS 和 JavaScript。 不幸的是,没有服务器,很容易很快达到他们的极限。 例如,在添加联系表格时。

随着无服务器架构的兴起,向静态站点添加联系表单不再是切换到 CMS 的理由。 两全其美是可能的:一个静态站点,带有用于联系表单的无服务器后端(您不需要维护)。 也许最重要的是,在像投资组合这样的低流量站点中,许多无服务器提供商的高限制使这些服务完全免费!

在本文中,您将了解 Amazon Web Services (AWS) Lambda 和简单电子邮件服务 (SES) API 的基础知识,以便在无服务器框架上构建您自己的静态站点邮件程序。 完整的服务将获取从 AJAX 请求提交的表单数据,访问 Lambda 端点,解析数据以构建 SES 参数,发送电子邮件地址,并为我们的用户返回响应。 我将指导您通过部署首次设置无服务器。 它应该需要不到一个小时才能完成,所以让我们开始吧!

静态站点表单,将消息发送到 Lambda 端点并向用户返回响应。
静态站点表单,将消息发送到 Lambda 端点并向用户返回响应。
跳跃后更多! 继续往下看↓

配置

开始使用无服务器技术的先决条件很少。 就我们而言,它只是一个带有 Yarn、无服务器框架和 AWS 账户的节点环境。

设置项目

无服务器框架网站。对安装和文档很有用。
无服务器框架网站。 对安装和文档很有用。

我们使用 Yarn 将无服务器框架安装到本地目录。

  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 注册页面,其中包括慷慨的免费套餐,使我们的项目完全免费。

无服务器框架记录了设置 AWS 凭证的视频演练,但我也在此处列出了这些步骤。

  1. 注册一个 AWS 账户,如果您已有账户,请登录。
  2. 在 AWS 搜索栏中,搜索“IAM”。
  3. 在 IAM 页面上,单击侧栏上的“用户”,然后单击“添加用户”按钮。
  4. 在 Add user 页面上,给用户一个名字——像“serverless”这样的名字是合适的。 检查访问类型下的“程序访问”,然后单击下一步。
  5. 在权限屏幕上,单击“直接附加现有策略”选项卡,在列表中搜索“AdministratorAccess”,选中它,然后单击下一步。
  6. 在审查屏幕上,您应该会看到您的用户名、“Programmatic access”和“AdministratorAccess”,然后创建用户。
  7. 确认屏幕显示用户“访问密钥 ID”和“秘密访问密钥”,您需要这些来为无服务器框架提供访问权限。 在您的 CLI 中,键入yarn sls config credentials --provider aws --key YOUR_ACCESS_KEY_ID --secret YOUR_SECRET_ACCESS_KEY ,将YOUR_ACCESS_KEY_IDYOUR_SECRET_ACCESS_KEY替换为确认屏幕上的密钥。

您的凭证现在已配置,但是当我们在 AWS 控制台中时,让我们设置简单电子邮件服务。

  1. 单击左上角的控制台主页回家。
  2. 在主页的 AWS 搜索栏中,搜索“Simple Email Service”。
  3. 在 SES 主页上,单击侧栏中的“电子邮件地址”。
  4. 在电子邮件地址列表页面上,单击“验证新电子邮件地址”按钮。
  5. 在对话窗口中,输入您的电子邮件地址,然后单击“验证此电子邮件地址”。
  6. 您很快就会收到一封电子邮件,其中包含用于验证地址的链接。 单击链接以完成该过程。

现在我们已经创建了帐户,让我们来看看 Serverless 模板文件。

设置无服务器框架

运行serverless create会创建两个文件:handler.js 包含 Lambda 函数,serverless.yml 是整个 Serverless 架构的配置文件。 在配置文件中,您可以指定任意数量的处理程序,每个处理程序都将映射到可以与其他函数交互的新函数。 在这个项目中,我们将只创建一个处理程序,但在完整的无服务器架构中,您将拥有服务的多种不同功能。

从包含 handler.js 和 serverless.yml 的无服务器框架生成的默认文件结构。
从包含 handler.js 和 serverless.yml 的无服务器框架生成的默认文件结构。

在 handler.js 中,您会看到一个名为hello的导出函数。 这是当前的主要(也是唯一)功能。 它与所有 Node 处理程序一起采用三个参数:

  • event
    这可以被认为是函数的输入数据。
  • context object
    这包含 Lambda 函数的运行时信息。
  • 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 表单

我们的 Lambda 函数输入和表单输出需要匹配,因此在构建函数之前,我们将构建表单并捕获其输出。 我们使用名称、电子邮件和消息字段保持简单。 一旦我们部署了无服务器架构并获得了 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()来读取我们的数据。 或者,您可以使用 jQuery 的 Serialize 或 query-string 将表单参数作为查询字符串发送和解析,但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)); }; })();

继续并提交您的表单,然后捕获控制台输出。 接下来我们将在 Lambda 函数中使用它。

在控制台日志中捕获表单数据。
在控制台日志中捕获表单数据。

调用 Lambda 函数

特别是在开发过程中,我们需要测试我们的功能是否符合我们的预期。 无服务器框架提供了invokeinvoke local命令来分别从实时开发环境中触发您的函数。 这两个命令都需要传递函数名,在我们的例子中staticSiteMailer

 yarn sls invoke local --function staticSiteMailer

要将模拟数据传递到我们的函数中,请创建一个名为data.json的新文件,其中包含在 JSON 对象内的body键下捕获的控制台输出。 它应该看起来像:

 // 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 发送电子邮件!

使用简单电子邮件服务发送电子邮件

我们将调用一个私有的sendEmail函数来替换staticSiteMailer函数。 现在您可以注释掉或删除模板代码并将其替换为:

 // 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负责发送邮件,回调函数将返回失败或成功响应,带有errdata 。 在我们的例子中,我们可以简单地记录错误或数据,因为稍后我们将用 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发送的电子邮件地址。
  • 回复地址
    添加到电子邮件字段回复中的电子邮件地址数组。
  • 目的地
    必须包含至少一个ToAddressesCcAddressesBccAddresses的对象。 每个字段都有一个电子邮件地址数组,分别对应于toccbcc字段。
  • 信息
    包含BodySubject的对象。

由于formData是一个对象,我们可以像formData.message一样直接调用我们的表单字段,构建我们的参数并发送它。 我们将您的SES 验证电子邮件传递给SourceDestination.ToAddresses 。 只要电子邮件经过验证,您就可以在此处传递任何内容,包括不同的电子邮件地址。 我们从formData对象中提取reply_tomessagename来填写ReplyToAddressesMessage.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 表单提交的响应。 如果失败,我们应该返回适​​当的statusCode以及err.message 。 当它成功时, 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 证书上传到亚马逊。 Serverless Framework 团队写了一篇很棒的教程来介绍如何做到这一点。

调用本地函数现在将发送一封电子邮件并返回适当的响应。

 yarn sls invoke local --function staticSiteMailer --path data.json 
来自我们的无服务器函数的返回响应,在正文中包含 SES.sendEmail 返回响应。
来自我们的无服务器函数的返回响应,在正文中包含 SES.sendEmail 返回响应。

从表单调用 Lambda 函数

我们的服务很完善! 要部署它,请运行yarn sls deploy -v 。 部署后,您将获得一个类似于https://r4nd0mh45h.execute-api.us-east-1.amazonaws.com/dev/static-site-mailer的 URL,您可以将其添加到表单操作中。 接下来,我们创建 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 端点并向用户返回响应。

下一步

使用无服务器框架和 AWS 可以轻松地将联系表单添加到您的静态中。 我们的代码还有改进的空间,比如使用蜜罐添加表单验证、防止 AJAX 调用无效表单以及改进响应时的 UX,但这足以开始。 您可以在我创建的静态站点邮件程序存储库中看到其中的一些改进。 我希望我已经启发了您自己尝试无服务器!