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