为您的静态站点构建无服务器联系表
已发表: 2022-03-10静态站点生成器为 WordPress 等内容管理系统 (CMS) 提供了一种快速而简单的替代方案。 没有服务器或数据库设置,只有构建过程和简单的 HTML、CSS 和 JavaScript。 不幸的是,没有服务器,很容易很快达到他们的极限。 例如,在添加联系表格时。
随着无服务器架构的兴起,向静态站点添加联系表单不再是切换到 CMS 的理由。 两全其美是可能的:一个静态站点,带有用于联系表单的无服务器后端(您不需要维护)。 也许最重要的是,在像投资组合这样的低流量站点中,许多无服务器提供商的高限制使这些服务完全免费!
在本文中,您将了解 Amazon Web Services (AWS) Lambda 和简单电子邮件服务 (SES) API 的基础知识,以便在无服务器框架上构建您自己的静态站点邮件程序。 完整的服务将获取从 AJAX 请求提交的表单数据,访问 Lambda 端点,解析数据以构建 SES 参数,发送电子邮件地址,并为我们的用户返回响应。 我将指导您通过部署首次设置无服务器。 它应该需要不到一个小时才能完成,所以让我们开始吧!
配置
开始使用无服务器技术的先决条件很少。 就我们而言,它只是一个带有 Yarn、无服务器框架和 AWS 账户的节点环境。
设置项目
我们使用 Yarn 将无服务器框架安装到本地目录。
- 创建一个新目录来托管项目。
- 导航到命令行界面中的目录。
- 运行
yarn init
为这个项目创建一个package.json
文件。 - 运行
yarn add serverless
在本地安装框架。 - 运行
yarn serverless create --template aws-nodejs --name static-site-mailer
创建一个 Node 服务模板并将其命名为static-site-mailer
。
我们的项目已设置,但在设置 AWS 服务之前我们将无法执行任何操作。
设置 Amazon Web Services 帐户、凭证和简单电子邮件服务
无服务器框架记录了设置 AWS 凭证的视频演练,但我也在此处列出了这些步骤。
- 注册一个 AWS 账户,如果您已有账户,请登录。
- 在 AWS 搜索栏中,搜索“IAM”。
- 在 IAM 页面上,单击侧栏上的“用户”,然后单击“添加用户”按钮。
- 在 Add user 页面上,给用户一个名字——像“serverless”这样的名字是合适的。 检查访问类型下的“程序访问”,然后单击下一步。
- 在权限屏幕上,单击“直接附加现有策略”选项卡,在列表中搜索“AdministratorAccess”,选中它,然后单击下一步。
- 在审查屏幕上,您应该会看到您的用户名、“Programmatic access”和“AdministratorAccess”,然后创建用户。
- 确认屏幕显示用户“访问密钥 ID”和“秘密访问密钥”,您需要这些来为无服务器框架提供访问权限。 在您的 CLI 中,键入
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 控制台中时,让我们设置简单电子邮件服务。
- 单击左上角的控制台主页回家。
- 在主页的 AWS 搜索栏中,搜索“Simple Email Service”。
- 在 SES 主页上,单击侧栏中的“电子邮件地址”。
- 在电子邮件地址列表页面上,单击“验证新电子邮件地址”按钮。
- 在对话窗口中,输入您的电子邮件地址,然后单击“验证此电子邮件地址”。
- 您很快就会收到一封电子邮件,其中包含用于验证地址的链接。 单击链接以完成该过程。
现在我们已经创建了帐户,让我们来看看 Serverless 模板文件。
设置无服务器框架
运行serverless create
会创建两个文件:handler.js 包含 Lambda 函数,serverless.yml 是整个 Serverless 架构的配置文件。 在配置文件中,您可以指定任意数量的处理程序,每个处理程序都将映射到可以与其他函数交互的新函数。 在这个项目中,我们将只创建一个处理程序,但在完整的无服务器架构中,您将拥有服务的多种不同功能。
在 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
注意到 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 函数
特别是在开发过程中,我们需要测试我们的功能是否符合我们的预期。 无服务器框架提供了invoke
和invoke 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
您会看到与之前类似的响应,但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
负责发送邮件,回调函数将返回失败或成功响应,带有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的对象。 每个字段都有一个电子邮件地址数组,分别对应于to 、 cc和bcc字段。 - 信息
包含Body和Subject的对象。
由于formData
是一个对象,我们可以像formData.message
一样直接调用我们的表单字段,构建我们的参数并发送它。 我们将您的SES 验证电子邮件传递给Source
和Destination.ToAddresses
。 只要电子邮件经过验证,您就可以在此处传递任何内容,包括不同的电子邮件地址。 我们从formData
对象中提取reply_to
、 message
和name
来填写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
从处理程序返回响应
我们的函数使用命令行发送电子邮件,但这不是我们的用户与之交互的方式。 我们需要返回对 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
从表单调用 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
解析正文消息。 记录错误特别有用。
您应该能够完全从您的静态站点提交表单!
下一步
使用无服务器框架和 AWS 可以轻松地将联系表单添加到您的静态中。 我们的代码还有改进的空间,比如使用蜜罐添加表单验证、防止 AJAX 调用无效表单以及改进响应时的 UX,但这足以开始。 您可以在我创建的静态站点邮件程序存储库中看到其中的一些改进。 我希望我已经启发了您自己尝试无服务器!