為您的靜態站點構建無服務器聯繫表

已發表: 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,但這足以開始。 您可以在我創建的靜態站點郵件程序存儲庫中看到其中的一些改進。 我希望我已經啟發了您自己嘗試無服務器!