返回文章列表

Rust 構建無伺服器 REST API

本文介紹如何使用 Rust 語言結合 Actix-web 框架,並利用 AWS Lambda 與 DynamoDB 建構安全、可擴充套件的無伺服器 REST API。文章涵蓋日誌記錄、錯誤處理、HTTPS 設定、SSL/TLS 加密連線設定,以及使用 AWS Rust SDK

Web 開發 後端開發

在現代 Web 開發中,使用 Rust 構建高效能且安全的後端服務已成為趨勢。本文將探討如何結合 Actix-web 框架及 AWS Lambda 和 DynamoDB,開發兼具效能與可擴充套件性的無伺服器 REST API。我們將逐步講解如何設定日誌記錄和錯誤處理機制,確保應用程式在生產環境的穩定性。同時,文章也會詳細說明如何設定 HTTPS 和 SSL/TLS 加密連線,保障資料傳輸的安全性。最後,我們將引導讀者使用 AWS Rust SDK,將應用程式佈署至 AWS Lambda,並與 DynamoDB 整合,實作完整的無伺服器架構。

啟用日誌記錄與錯誤處理

在建構API伺服器時,日誌記錄(Logging)與錯誤處理(Error Handling)是至關重要的環節。適當的日誌記錄可以幫助開發者追蹤問題、除錯程式碼,並在生產環境中監控系統的行為。

使用Logger中介軟體

Actix-web框架提供了一個內建的Logger中介軟體,可以用來記錄HTTP請求的日誌。首先,需要在Cargo.toml中新增env_loggerlog crate:

[dependencies]
env_logger = "0.9.1"
log = "0.4.14"

然後,在main函式中初始化env_logger,並將Logger中介軟體新增到Actix-web應用程式中:

#[actix_web::main]
async fn main() -> std::io::Result<()> {
    env_logger::init();
    // ...
    HttpServer::new(move || {
        App::new()
            .wrap(Logger::default())
            // ...
    })
    // ...
}

這樣,當你執行應用程式並傳送請求時,就可以看到類別似以下的日誌輸出:

[2022-12-19T17:52:41Z INFO actix_web::middleware::logger]
127.0.0.1 "GET /api/cats HTTP/1.1" 200
712 "-" "Mozilla/5.0 (X11; Linux x86_64)
AppleWebKit/537.36 (KHTML, like Gecko)
Chrome/107.0.0.0 Safari/537.36" 0.002274

內容解密:

  1. env_logger::init();:初始化日誌記錄器,使應用程式能夠輸出日誌。
  2. .wrap(Logger::default()):將Logger中介軟體新增到Actix-web應用程式中,用於記錄HTTP請求的日誌。
  3. 日誌輸出的格式包括請求的時間、客戶端IP、HTTP方法、路徑、協定版本、狀態碼、回應大小、使用者代理字串和處理時間等資訊。

自定義日誌記錄

除了使用Logger中介軟體之外,還可以在程式碼中自定義日誌記錄。可以使用log crate提供的宏,如error!()warn!()info!()debug!()trace!(),在特定的程式碼位置輸出日誌。

use log::{error, info, warn};

async fn cats_endpoint(
    pool: web::Data<DbPool>,
) -> Result<HttpResponse, UserError> {
    // ...
    .map_err(|_| {
        error!("Blocking Thread Pool Error");
        UserError::UnexpectedError
    })?
    // ...
}

內容解密:

  1. error!()warn!()info!():分別用於輸出錯誤、警告和資訊級別的日誌。
  2. 在錯誤處理的地方使用這些宏,可以輸出有用的資訊,幫助除錯和監控系統。

啟用HTTPS

為了在生產環境中安全地提供API服務,需要使用HTTPS協定。HTTPS透過TLS(傳輸層安全性協定)加密客戶端和伺服器之間的通訊。

生成自簽名證書

為了演示,可以生成自簽名證書。首先,需要安裝openssl

sudo apt install openssl

然後,執行以下命令生成證書和私鑰:

openssl req -x509 -newkey rsa:4096 \
    -keyout key.pem \
    -out cert.pem \
    -days 365 \
    -sha256 \
    -subj "/CN=localhost"

內容解密:

  1. openssl req -x509 -newkey rsa:4096:生成一個新的X.509證書和RSA私鑰,私鑰的長度為4096位。
  2. -keyout key.pem -out cert.pem:分別指定私鑰和證書的輸出檔案。
  3. -days 365:證書的有效期為365天。
  4. -sha256:使用SHA-256演算法進行簽名。
  5. -subj "/CN=localhost":指定證書的主體名稱為localhost

透過上述步驟,可以為本地開發環境生成自簽名證書。當佈署到生產環境時,應該從可信的證書頒發機構(CA)取得證書。

在 Actix-web 中啟用 SSL/TLS 加密連線

在現代的網路應用程式中,安全性是不可或缺的一部分。為了保護使用者資料的安全,網站需要使用 SSL/TLS 加密連線。本文將介紹如何在 Actix-web 中啟用 SSL/TLS 加密連線。

產生 SSL/TLS 證書和私鑰

要啟用 SSL/TLS 加密連線,首先需要產生 SSL/TLS 證書和私鑰。可以使用 OpenSSL 工具來產生這些檔案。

openssl req -x509 -newkey rsa:2048 -nodes -keyout key.pem -out cert.pem -days 365 -subj "/C=TW/ST=Taiwan/L=Taipei/O=Example/CN=example.com"

內容解密:

  • openssl req:產生證書請求的命令。
  • -x509:表示產生自簽證書。
  • -newkey rsa:2048:產生新的私鑰,採用 RSA 演算法,金鑰長度為 2048 位元。
  • -nodes:不對私鑰進行加密。
  • -keyout key.pem:指定私鑰的輸出檔案名稱。
  • -out cert.pem:指定證書的輸出檔案名稱。
  • -days 365:證書的有效期限為 365 天。
  • -subj "/C=TW/ST=Taiwan/L=Taipei/O=Example/CN=example.com":指定證書的主體資訊。

移除私鑰密碼

產生的私鑰檔案 key.pem 預設會被密碼保護。如果希望在啟動伺服器時不需要輸入密碼,可以移除私鑰密碼。

openssl rsa -in key.pem -out key-no-password.pem

內容解密:

  • openssl rsa:處理 RSA 私鑰的命令。
  • -in key.pem:輸入被密碼保護的私鑰檔案。
  • -out key-no-password.pem:輸出移除密碼後的私鑰檔案。

設定 Actix-web 使用 SSL/TLS

要在 Actix-web 中啟用 SSL/TLS,需要進行以下步驟:

  1. 安裝必要的函式庫:sudo apt install libssl-dev
  2. Cargo.toml 中新增 openssl crate,並啟用 Actix-web 的 openssl 功能。
[dependencies]
actix-web = { version = "4.3.1", features = ["openssl"] }
openssl = "0.10.49"

內容解密:

  • actix-web = { version = "4.3.1", features = ["openssl"] }:啟用 Actix-web 的 openssl 功能,以支援 SSL/TLS 連線。
  • openssl = "0.10.49":新增 openssl crate,以處理 SSL/TLS 相關功能。

修改程式碼以使用 SSL/TLS

最後,需要修改 Actix-web 的程式碼,以使用 SSL/TLS 連線。

use openssl::ssl::{SslAcceptor, SslFiletype, SslMethod};

#[actix_web::main]
async fn main() -> std::io::Result<()> {
    // 設定證書和私鑰
    let mut builder = SslAcceptor::mozilla_intermediate(SslMethod::tls()).unwrap();
    builder.set_private_key_file("key-no-password.pem", SslFiletype::PEM).unwrap();
    builder.set_certificate_chain_file("cert.pem").unwrap();

    // 建立 HTTP 伺服器
    HttpServer::new(move || {
        App::new()
            // ...
    })
    .bind_openssl("127.0.0.1:8080", builder)?
    .run()
    .await
}

內容解密:

  • SslAcceptor::mozilla_intermediate(SslMethod::tls()):建立一個支援 Mozilla 中級相容性的 SSL/TLS acceptor。
  • builder.set_private_key_file("key-no-password.pem", SslFiletype::PEM):設定私鑰檔案。
  • builder.set_certificate_chain_file("cert.pem"):設定證書鏈檔案。
  • .bind_openssl("127.0.0.1:8080", builder)?:將 HTTP 伺服器繫結到 127.0.0.1:8080,並啟用 SSL/TLS 連線。

其他 Rust 網路框架選擇

除了 Actix-web 之外,Rust 社群還有許多其他優秀的網路框架可供選擇,例如 Rocket、Warp、Gotham 等。這些框架各有其特點和優勢,可以根據專案需求進行選擇。

使用 Amazon AWS Rust SDK 邁向無伺服器架構

在前一章中,您建立了一個能夠提供靜態網頁和 REST API 的伺服器端網頁應用程式。這在您將應用佈署在自己的機器上並且只進行低流量測試時運作良好。但是,當您需要讓您的網站公開存取時,管理伺服器就變成了一件麻煩事。傳統上,您必須購買實體伺服器並在其上執行您的應用程式。您必須處理 IT 管理的各個方面,例如保持作業系統和系統函式庫的更新,確保故障硬體被替換,以及在斷電時保持伺服器的供電。除非您有龐大的預算和維運團隊,否則這不是一件有趣的工作。

無伺服器運算的優勢

如果您不想自己處理這些麻煩,有很多公司讓您可以外包這項工作並使用他們的伺服器。第三方網頁託管和虛擬私人伺服器(VPS)服務已經存在很長時間了。如今,您還可以選擇許多基礎設施即服務(IaaS)和平台即服務(PaaS)供應商。他們為您管理伺服器,並提供不同程度的抽象化,以便您可以專注於您的應用程式。無伺服器運算將這一理念推到了極致。透過無伺服器運算,您只需編寫處理業務邏輯的函式。硬體、作業系統和語言執行時都由服務供應商處理。您還可以將它們連線到受管理的資料函式庫、訊息佇列和檔案儲存,這些也都完全由服務供應商管理。無伺服器運算對於擴充套件也非常好,讓使用者可以從每天幾毫秒使用幾兆位元組的記憶體到執行每秒數千次操作的全球規模的網頁應用程式。

本章目標

在本章中,您將使用 Amazon Web Service(AWS)的旗艦無伺服器平台 AWS Lambda 作為計算服務。您還將使用 DynamoDB,一個由 AWS 提供的完全受管理的 NoSQL 資料函式庫。AWS 為 AWS Lambda 提供了一個 Rust 執行時,並為 Rust 提供了一個軟體開發工具包(SDK),以便您可以透過程式控制 AWS 服務。

逐步建構無伺服器 REST API

  1. 使用 Rust 執行時在 AWS Lambda 上執行 Rust 程式碼
  2. 使用 AWS 無伺服器應用程式模型(AWS SAM)及其易於使用的範本建立 REST API 端點
  3. 使用 lambda_http 套件處理來自 AWS API Gateway 的 API 請求
  4. 使用 AWS SDK for Rust 將資料寫入 DynamoDB 以建立新貓
  5. 從 DynamoDB 讀取所有貓的資料
  6. 直接將圖片上傳到物件儲存服務 S3
  7. 從 S3 提供前端服務
  8. 啟用跨源資源分享(CORS),以便前端可以存取不同網域下的 API

什麼是 AWS Lambda?

AWS Lambda 是一個允許您在不組態伺服器的情況下執行程式碼的服務。AWS 管理底層硬體、網路、作業系統和執行時。作為開發者,您只需上傳一段程式碼,它就會自動執行和擴充套件。Lambda 函式可以手動觸發(透過網頁控制檯或 AWS 命令列介面(AWS CLI))或由其他 AWS 服務產生的事件觸發。對於 REST API,通常使用 API Gateway 或應用程式負載平衡器來處理請求並觸發 Lambda。AWS Lambda 使開發者擺脫了組態和管理伺服器的麻煩,因此他們可以專注於程式碼。您只需為您消耗的計算時間(以 100 毫秒為單位)付費,因此,如果您的函式處於閒置狀態,您無需支付任何費用。Lambda 也可以自動擴充套件。如果您使用 Lambda 為 REST API 提供動力,它可以在流量高時自動啟動更多 Lambda 例項。

註冊 AWS 帳戶

由於您將在 Amazon Web Service(AWS)上執行您的服務,因此您需要註冊一個帳戶。請在瀏覽器中存取 https://aws.amazon.com,並點選「建立 AWS 帳戶」按鈕。按照步驟註冊帳戶。您可能需要在過程中提供信用卡。

AWS 在您首次註冊時提供一年免費級別的服務(使用限制適用)。這涵蓋了您將要使用的大多數服務:Lambda、DynamoDB 和 S3。因此,您應該能夠以最小或無成本執行大多數範例。但是,請記得在完成測試後清理所有資源,以防止任何意外的帳單。

使用 Rust 在 AWS Lambda 上執行

AWS Lambda 提供多種語言執行時,如 Java、Go、PowerShell、Node.js、C#、Python 和 Ruby。它還提供了一個執行時 API,以便您可以建立自定義執行時。AWS 已發布了一個實驗性的 Rust 執行時,使用這一執行時機制,因此您可以在 Lambda 上執行 Rust 程式碼。

值得注意的是,支援 AWS Lambda 的底層技術是 Firecracker 虛擬機器。有趣的是,Firecracker 虛擬機器是用 Rust 編寫的。因此,即使您用其他語言編寫 Lambda,您的程式碼仍然由 Rust 提供支援。該專案由 AWS 以開源專案的形式發布。您可以透過存取其 GitHub 倉函式庫:https://github.com/firecracker-microvm/firecracker 為其做出貢獻。

// 這是一個簡單的 Rust 程式碼範例,用於在 AWS Lambda 上執行
use lambda_http::{run, service_fn, Body, Error, Request, RequestExt, Response};

async fn function_handler(event: Request) -> Result<Response<Body>, Error> {
    // 解壓縮請求並檢查其屬性
    let who = event
        .query_string_parameters_ref()
        .first("name")
        .unwrap_or("world");

    // 傳回一個包含問候訊息的回應
    let message = format!("Hello {who}, this is an AWS Lambda HTTP request");
    let resp = Response::builder()
        .status(200)
        .header("content-type", "text/html")
        .body(message.into())
        .map_err(Box::new)?;
    Ok(resp)
}

async fn main() -> Result<(), Error> {
    run(service_fn(function_handler)).await
}

內容解密:

  1. 使用 lambda_http 套件處理 HTTP 請求和回應。
  2. function_handler 函式處理傳入的請求,並根據查詢引數傳回一個問候訊息。
  3. main 函式是程式的入口點,使用 run 函式啟動 Lambda 函式,並將 function_handler 作為處理函式。

本章將探討如何使用 Amazon AWS Rust SDK 建構無伺服器 REST API,並將其佈署在公開網路上。透過本章的學習,您將能夠掌握使用 AWS Lambda 和 DynamoDB 建構可擴充套件和高效的無伺服器應用的技能。