在 Rust 中定義 Lambda 函式處理常式 - AWS Lambda

在 Rust 中定義 Lambda 函式處理常式

注意

Rust 執行期用戶端是實驗性套件。它可能會發生變更,僅用於評估目的。

Lambda 函數處理常式是您的函數程式碼中處理事件的方法。當有人呼叫您的函數時,Lambda 會執行處理常式方法。函數會執行,直到處理常式傳回回應、結束或逾時為止。

本頁介紹了如何在 Rust 中使用 Lambda 函式處理常式,包括專案初始化、命名慣例及最佳實務。本頁還提供了一個 Rust Lambda 函式範例,該函式會取得訂單的相關資訊、產生文字檔案收據,並將此檔案放入 Amazon Simple Storage Service (S3) 儲存貯體。如需有關編寫函式後如何部署的詳細資訊,請參閱使用 .zip 封存檔部署 Rust Lambda 函數

設定 Rust 處理常式專案

使用 Rust 開發 Lambda 函式時,流程涉及編寫程式碼、編譯程式碼,以及將編譯的成品部署至 Lambda。在 Rust 中設定 Lambda 處理常式專案,最簡單的方法便是使用 AWS Lambda Runtime for Rust。儘管名稱類似,但 AWS Lambda Runtime for Rust 的性質,不同於 Lambda 為 Python、Java 或 Node.js 所提供的受管執行時期。AWS Lambda Runtime for Rust 實則為一個 crate (lambda_runtime),支援以 Rust 編寫 Lambda 函式,並與 AWS Lambda 的執行環境對接。

使用下列命令來安裝 AWS Lambda Runtime for Rust:

cargo install cargo-lambda

成功安裝 cargo-lambda 之後,使用下列命令來初始化新的 Rust Lambda 函式處理常式專案:

cargo lambda new example-rust

當您執行此命令時,命令列介面 (CLI) 會詢問幾個有關 Lambda 函式的問題:

  • HTTP 函式:如果您想要透過 API Gateway函式 URL 調用函式,請回答。否則,請回答。在本頁的範例程式碼中,由於我們使用自訂 JSON 事件來調用函式,因此回答

  • 事件類型:如果您想要使用預先定義的事件結構來調用函式,請選取正確的預期事件類型。否則,請將此選項保留空白。在本頁的範例程式碼中,由於我們使用自訂 JSON 事件來調用函式,因此將此選項保留空白。

命令成功執行後,輸入專案的主目錄:

cd example-rust

此命令會在 src 目錄中產生 generic_handler.rs 檔案和 main.rs 檔案。generic_handler.rs 可用於自訂泛型事件處理常式。main.rs 檔案包含應用程式的主要邏輯。Cargo.toml 檔案包含有關套件的中繼資料,並列出其外部相依項。

Lambda 函式程式碼範例

以下 Rust Lambda 函式程式碼範例會取得訂單的相關資訊、產生文字檔案收據,並將此檔案放入 Amazon S3 儲存貯體。

範例 main.rs Lambda 函數
use aws_sdk_s3::{Client, primitives::ByteStream}; use lambda_runtime::{run, service_fn, Error, LambdaEvent}; use serde::{Deserialize, Serialize}; use serde_json::Value; use std::env; #[derive(Deserialize, Serialize)] struct Order { order_id: String, amount: f64, item: String, } async fn function_handler(event: LambdaEvent<Value>) -> Result<String, Error> { let payload = event.payload; // Deserialize the incoming event into Order struct let order: Order = serde_json::from_value(payload)?; let bucket_name = env::var("RECEIPT_BUCKET") .map_err(|_| "RECEIPT_BUCKET environment variable is not set")?; let receipt_content = format!( "OrderID: {}\nAmount: ${:.2}\nItem: {}", order.order_id, order.amount, order.item ); let key = format!("receipts/{}.txt", order.order_id); let config = aws_config::load_defaults(aws_config::BehaviorVersion::latest()).await; let s3_client = Client::new(&config); upload_receipt_to_s3(&s3_client, &bucket_name, &key, &receipt_content).await?; Ok("Success".to_string()) } async fn upload_receipt_to_s3( client: &Client, bucket_name: &str, key: &str, content: &str, ) -> Result<(), Error> { client .put_object() .bucket(bucket_name) .key(key) .body(ByteStream::from(content.as_bytes().to_vec())) // Fixed conversion .content_type("text/plain") .send() .await?; Ok(()) } #[tokio::main] async fn main() -> Result<(), Error> { run(service_fn(function_handler)).await }

main.rs 檔案包含以下程式碼區段:

  • use 陳述式:用於匯入 Lambda 函式所需的 Rust Crate 與方法。

  • #[derive(Deserialize, Serialize)]:定義此 Rust 結構體中預期輸入事件的結構。

  • async fn function_handler(event: LambdaEvent<Value>) -> Result<String, Error>:這是主要處理常式方法,其中包含應用程式的主要邏輯。

  • async fn upload_receipt_to_s3 (...):此為主要 function_handler 方法所參照的協助程式方法。

  • #[tokio::main]:此為標記 Rust 程式進入點的巨集。其還會設定 Tokio 執行時期,允許 main() 方法使用 async/await 並以非同步方式執行。

  • async fn main() -> Result<(), Error>main() 函式是程式碼的進入點。我們會在其中將 function_handler 指定為主要處理常式方法。

下列 Cargo.toml 為此函式的配套檔案。

[package] name = "example-rust" version = "0.1.0" edition = "2024" [dependencies] aws-config = "1.5.18" aws-sdk-s3 = "1.78.0" lambda_runtime = "0.13.0" serde = { version = "1", features = ["derive"] } serde_json = "1" tokio = { version = "1", features = ["full"] }

若要讓此函式正常運作,其執行角色必須允許 s3:PutObject 動作。此外,請確保定義 RECEIPT_BUCKET 環境變數。成功調用後,Amazon S3 儲存貯體應包含收據檔案。

Rust 處理常式的有效類別定義

大多數情況下,在 Rust 中定義的 Lambda 處理常式簽章將具有下列格式:

async fn function_handler(event: LambdaEvent<T>) -> Result<U, Error>

對於此處理常式:

  • 此處理常式名稱為 function_handler

  • 處理常式的單一輸入參數為 event,類型為 LambdaEvent<T>

    • LambdaEvent 是來自 lambda_runtime 套件的包裝函式。借助此包裝函式,您可以存取內容物件,其中包含 Lambda 特定的中繼資料,例如調用的請求 ID。

    • T 是反序列化事件類型。例如,這可以是 serde_json::Value,允許處理常式接收所有泛型 JSON 輸入。或者,如果函式需要預先定義的特定輸入類型,這可以是 ApiGatewayProxyRequest 等類型。

  • 處理常式的傳回類型為 Result<U, Error>

    • U 是反序列化輸出類型。U 必須實作 serde::Serialize 特徵,Lambda 才能將傳回值轉換為 JSON 格式。舉例而言,U 可以是簡單類型,例如 Stringserde_json::Value 或自訂結構體 (只要實作了 Serialize)。當程式碼到達 Ok(U) 陳述式時,表示執行成功,且函式會傳回類型為 U 的值。

    • 當程式碼遇到錯誤 (即 Err(Error)) 時,函式會在 Amazon CloudWatch 中記錄錯誤,並傳回類型為 Error 的錯誤回應。

在我們的範例中,處理常式簽章如下所示:

async fn function_handler(event: LambdaEvent<Value>) -> Result<String, Error>

其他有效的處理常式簽章將會具有下列功能:

  • 省略 LambdaEvent 包裝函式:如果省略 LambdaEvent,則會失去函式中 Lambda 內容物件的存取權。以下為此類簽章的範例:

    async fn handler(event: serde_json::Value) -> Result<String, Error>
  • 使用單位類型作為輸入:對於 Rust,您可以使用單位類型來表示空白輸入。這通常用於具有定期排程調用的函式。以下為此類簽章的範例:

    async fn handler(_: ()) -> Result<Value, Error>

處理常式命名慣例

Rust 中的 Lambda 處理常式沒有嚴格的命名限制。雖然您可以為處理常式使用任何名稱,但 Rust 中的函式名稱通常採用 snake_case

對於較小的應用程式,例如在此範例中,您可以使用單一的 main.rs 檔案來包含所有程式碼。對於較大的專案,main.rs 應該包含函式的進入點,但您可透過額外的檔案將程式碼拆分為邏輯模組。例如,您可能擁有下列檔案結構:

/example-rust │── src/ │ ├── main.rs # Entry point │ ├── handler.rs # Contains main handler │ ├── services.rs # [Optional] Back-end service calls │ ├── models.rs # [Optional] Data models │── Cargo.toml

定義和存取輸入事件物件

JSON 是 Lambda 函數最常見的標準輸入格式。在此範例中,函數預期輸入類似以下內容:

{ "order_id": "12345", "amount": 199.99, "item": "Wireless Headphones" }

在 Rust 中,您可透過結構體定義預期輸入事件的結構。在此範例中,我們透過定義以下結構體來表示 Order

#[derive(Deserialize, Serialize)] struct Order { order_id: String, amount: f64, item: String, }

此 struct 符合預期的輸入形狀。在此範例中,#[derive(Deserialize, Serialize)] 巨集會自動產生用於序列化和反序列化的程式碼。這意味著我們可透過 serde_json::from_value() 方法,將泛型輸入 JSON 類型反序列化至結構體中。這在處理常式的前幾行中得以曾現:

async fn function_handler(event: LambdaEvent<Value>) -> Result<String, Error> { let payload = event.payload; // Deserialize the incoming event into Order struct let order: Order = serde_json::from_value(payload)?; ... }

然後,您可以存取該物件的欄位。例如,使用 order_id 可以從原始輸入擷取 order.order_id 的值。

預先定義的輸入事件類型

aws_lambda_events 套件中有許多預先定義的可用輸入事件類型。例如,若要使用 API Gateway 調用函式,需包含下列匯入:

use aws_lambda_events::event::apigw::ApiGatewayProxyRequest;

然後,確保主要處理常式使用下列簽章:

async fn handler(event: LambdaEvent<ApiGatewayProxyRequest>) -> Result<String, Error> { let body = event.payload.body.unwrap_or_default(); ... }

如需有關其他預先定義輸入事件類型的詳細資訊,請參閱 aws_lambda_events crate

存取和使用 Lambda 內容物件

Lambda 內容物件 包含有關調用、函數以及執行環境的資訊。在 Rust 中,LambdaEvent 包裝函式包含內容物件。例如,您可以使用內容物件,透過下列程式碼擷取目前調用的請求 ID:

async fn function_handler(event: LambdaEvent<Value>) -> Result<String, Error> { let request_id = event.context.request_id; ... }

如需內容物件的詳細資訊,請參閱使用 Lambda 內容物件擷取 Rust 函數資訊

在處理常式中使用 適用於 Rust 的 AWS SDK

通常,您會使用 Lambda 函式與其他 AWS 資源互動或對其進行更新。與這些資源互動的最簡單方法便是使用 適用於 Rust 的 AWS SDK

若要將 SDK 相依項新增至函式,請在 Cargo.toml 檔案中納入這些相依相。建議僅新增函式所需的程式庫。在先前的範例程式碼中,我們使用了 aws_sdk_s3::Client。在 Cargo.toml 檔案中,您可以透過在 [dependencies] 區段下新增以下行來納入此相依項:

aws-sdk-s3 = "1.78.0"
注意

這可能並非最新版本。請為應用程式選擇適當的版本。

然後,直接在程式碼中匯入相依項:

use aws_sdk_s3::{Client, primitives::ByteStream};

隨後,範例程式碼會初始化 Amazon S3 用戶端,如下所示:

let config = aws_config::load_defaults(aws_config::BehaviorVersion::latest()).await; let s3_client = Client::new(&config);

初始化 SDK 用戶端之後,您即可用其與其他 AWS 服務互動。範例程式碼會在 upload_receipt_to_s3 協助程式函式中呼叫 Amazon S3 PutObject API。

存取環境變數

在處理常式程式碼中,您可以使用 env::var 方法參考任何環境變數。在此範例中,我們使用以下程式碼來參考定義的 RECEIPT_BUCKET 環境變數:

let bucket_name = env::var("RECEIPT_BUCKET") .map_err(|_| "RECEIPT_BUCKET environment variable is not set")?;

使用共用狀態

您可以宣告獨立於 Lambda 函數處理常式程式碼的共用變數。這些變數可協助您在函數接收任何事件之前在 初始化階段 過程中載入狀態資訊。例如,您可以透過更新 main 函式和處理常式簽章,將本頁的程式碼修改為在初始化 Amazon S3 用戶端時使用共用狀態:

async fn function_handler(client: &Client, event: LambdaEvent<Value>) -> Result<String, Error> { ... upload_receipt_to_s3(client, &bucket_name, &key, &receipt_content).await?; ... } ... #[tokio::main] async fn main() -> Result<(), Error> { let shared_config = aws_config::from_env().load().await; let client = Client::new(&shared_config); let shared_client = &client; lambda_runtime::run(service_fn(move |event: LambdaEvent<Request>| async move { handler(&shared_client, event).await })) .await

Rust Lambda 函數的程式碼最佳實務

請遵循下列清單中的準則,在建置 Lambda 函數時使用最佳編碼實務:

  • 區隔 Lambda 處理常式與您的核心邏輯。能允許您製作更多可測單位的函式。

  • 最小化依存項目的複雜性。偏好更簡易的框架,其可快速在執行環境啟動時載入。

  • 將部署套件最小化至執行時期所必要的套件大小。這能減少您的部署套件被下載與呼叫前解壓縮的時間。

請利用執行環境重新使用來改看函式的效能。在函式處理常式之外初始化 SDK 用戶端和資料庫連線,並在本機快取 /tmp 目錄中的靜態資產。由您函式的相同執行個體處理的後續叫用可以重複使用這些資源。這可藉由減少函數執行時間來節省成本。

若要避免叫用間洩漏潛在資料,請不要使用執行環境來儲存使用者資料、事件,或其他牽涉安全性的資訊。如果您的函式依賴無法存放在處理常式內記憶體中的可變狀態,請考慮為每個使用者建立個別函式或個別函式版本。

使用 Keep-Alive 指令維持持續連線的狀態。Lambda 會隨著時間的推移清除閒置連線。叫用函數時嘗試重複使用閒置連線將導致連線錯誤。若要維護持續連線,請使用與執行階段相關聯的 keep-alive (保持啟用) 指令。如需範例,請參閱在 Node.js 中重複使用 Keep-Alive 的連線

使用環境變數將操作參數傳遞給您的函數。例如,如果您正在寫入到 Amazon S3 儲存貯體,而非對您正在寫入的儲存貯體名稱進行硬式編碼,請將儲存貯體名稱設定為環境變數。

避免在 Lambda 函數中使用遞迴調用,其中函數會調用自己或啟動可能再次調用函數的程序。這會導致意外的函式呼叫量與升高的成本。若您看到意外的調用數量,當更新程式碼時,請立刻將函數的預留並行設為 0,以調節對函數的所有調用。

請勿在您的 Lambda 函數程式碼中使用未記錄的非公有 API。對於 AWS Lambda 受管執行時間,Lambda 會定期將安全性和函數更新套用至 Lambda 的內部 API。這些內部 API 更新可能是向後不相容的,這會導致意外結果,例如若您的函數依賴於這些非公有 API,則叫用失敗。請參閱 API 參考查看公開可用 API 的清單。

撰寫等冪程式碼。為函數撰寫等冪程式碼可確保採用相同方式來處理重複事件。程式碼應正確驗證事件並正常處理重複的事件。如需詳細資訊,請參閱 How do I make my Lambda function idempotent? (如何讓 Lambda 函數等冪?)。