Rust 作為一種高效能且安全的程式語言,結合 WebAssembly 技術,為前端開發提供了新的可能性。本文從建立互動式元件開始,逐步深入 Rust 和 WebAssembly 的整合應用。後端部分則涵蓋了使用 Actix-web 框架構建 REST API 和 Web 伺服器的實務技巧,包含 API 端點設計、錯誤處理、日誌記錄等。同時也探討了非同步程式設計的重要性,比較了多行程、多執行緒和非同步/等待機制在處理併發請求上的優劣,並提供靜態檔案佈署的最佳實務建議,以提升 Web 應用程式的效能和可維護性。
高效能 Web 前端:使用 WebAssembly 的進階應用
在前面的章節中,我們已經探討瞭如何使用 Rust 編譯到 WebAssembly 並在網頁上執行。本文將深入介紹如何利用 Rust 和 WebAssembly 構建高效能的前端應用。
建立互動式元件
首先,我們需要建立一個按鈕元件,當點選時會觸發回撥函式。與 Hello World 範例中的按鈕類別似,下面的程式碼建立了一個可嵌入到更大應用中的 Button 功能元件。透過元件的屬性,你可以傳入按鈕文字、要傳送到回撥函式的名稱以及要觸發的回撥函式。
按鈕元件實作
#[derive(Properties, PartialEq)]
struct ButtonProp {
text: String,
name: String,
on_click: Callback<String>
}
#[function_component(Button)]
fn delete_button(button: &ButtonProp) -> Html {
let on_click = {
let name = button.name.clone();
let callback = button.on_click.clone();
move |_| {
callback.emit(name.clone())
}
};
html! {
<div>
<button onclick={on_click}>
{ button.text.clone() }
</button>
</div>
}
}
使用按鈕元件
接下來,我們修改 cat 函式,使其接受回撥函式,並為每個貓新增按鈕。
fn cat(cat: &CatDetails, callback: Callback<String>) -> Html {
html! {
<article class="cat">
<h3>{ format!( "{}", cat.name )}</h3>
<Button text={"Delete".to_string()}
name={cat.name.clone()}
on_click={callback}
/>
<img src={format!(
"data:image;base64,{}",
general_purpose::STANDARD.encode(&cat.image)
)} />
</article>
}
}
內容解密:
ButtonProp結構體定義了Button元件的屬性,包括按鈕文字、名稱和點選回撥函式。delete_button函式根據ButtonProp建立按鈕元件,並在點選時發出名稱給回撥函式。cat函式使用Button元件,並為每個貓專案新增刪除按鈕。
WebAssembly 替代方案
WebAssembly 是一個多功能的平台,因此有許多不同的工具和框架專注於不同的主題。本章介紹的工具大多由 Rust 和 WebAssembly 工作組維護。
其他前端框架
- Darco:受 Elm 和 Redux 啟發。
- Percy:支援同構網頁應用,意味著相同的程式碼在伺服器端和客戶端執行。
- Seed:受 Elm、React 和 Redux 啟發。
- Smithy:根據 Rust 的前端框架。
WebAssembly 的其他應用
WebAssembly 不僅限於瀏覽器。理論上,Wasm 執行時幾乎可以嵌入任何地方。一些有趣的例子包括:
- 作為後端網頁伺服器
- 驅動 Istio 外掛
- 在物聯網裝置上執行
- 控制機器人
使用Rust建構REST API與Web伺服器
在前一章中,我們成功使用Rust建立了一個前端網頁應用。現在,我們將探討如何使用Rust在後端建構REST API與Web伺服器。隨著網路技術的發展,後端開發語言如Java、PHP、Python、Ruby、Node.js和Go等已經相當成熟。然而,Rust近年來在後端開發領域逐漸受到歡迎,主要歸功於其在安全性、平行性和低階控制方面的優勢。
為何選擇Rust進行後端開發?
Rust提供了多項獨特的優勢,使其成為後端開發的理想選擇:
- 安全性:Rust的強大型別系統和借用檢查器可以有效防止空指標錯誤和使用後釋放(use-after-free)等記憶體損壞問題。透過編譯時檢查,Rust可以避免許多執行時漏洞。
- 平行性:Rust的「無畏平行」設計使得處理大量平行請求變得更加容易。其強大的async/await語法也使得非同步I/O操作更加便捷。
- 低階控制:Rust允許開發者對CPU和記憶體進行精細控制,從而最大限度地發揮伺服器硬體的效能。
建構REST API
REST API是一種定義良好的網路通訊標準,允許客戶端透過HTTP請求與伺服器交換資訊。其核心特點是無狀態性,即每個請求的回應僅取決於該請求的引數,而不受客戶端或其他先前請求的影響。
在本章中,我們將建構一個RESTful API,該API將提供以下功能:
API端點功能列表
- 以JSON格式傳回資料函式庫中的貓咪資訊列表
- 提供前端HTML和JavaScript程式碼以呼叫API並顯示貓咪資訊
- 為貓咪資訊列表API端點編寫整合測試
- 新增一個API端點以支援POST請求新增貓咪資訊至資料函式庫
- 新增一個API端點以根據貓咪ID傳回該貓咪的詳細資訊(JSON格式)
- 對輸入的ID進行驗證,若無效則傳回400 Bad Request回應
開始建構Web伺服器與REST API
首先,我們需要設定一個新的Rust專案,並新增必要的依賴套件。接下來,我們將逐步實作上述列出的API端點功能,並確保其正確性和穩定性。
程式碼範例1:新增依賴套件
[dependencies]
actix-web = "4"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
內容解密:
在Cargo.toml檔案中,我們新增了actix-web作為Web框架,serde和serde_json用於JSON序列化和反序列化。
程式碼範例2:建立Web伺服器
use actix_web::{web, App, HttpResponse, HttpServer, Responder};
async fn get_cats() -> impl Responder {
HttpResponse::Ok().body("Hello, cats!")
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
HttpServer::new(|| {
App::new()
.route("/cats", web::get().to(get_cats))
})
.bind("127.0.0.1:8080")?
.run()
.await
}
內容解密:
我們使用actix-web建立了一個簡單的Web伺服器,並定義了一個/cats端點,當接收到GET請求時傳回"Hello, cats!"。HttpServer::new用於建立伺服器例項,並將其繫結至127.0.0.1:8080。App::new()建立了一個新的應用程式例項,並透過.route()方法註冊了/cats端點。
使用 Actix-web 框架建立後端 API 服務
在開發後端 API 服務時,我們需要考慮多個重要功能,例如自定義錯誤處理、記錄日誌、啟用 HTTPS 等。這些功能對於建立一個安全且可靠的後端服務至關重要。在本章中,我們將使用 Actix-web 框架(版本 4)作為我們的網頁框架。Actix-web 專注於接收和回應 HTTP 請求,並且支援多種資料格式,例如 JSON 和 HTML。
建立一個簡單的 Actix-web 應用程式
首先,我們需要建立一個新的 Cargo 專案,並將 Actix-web 新增為依賴項。執行以下命令:
$ cargo new catdex-api
$ cd catdex-api
$ cargo add actix-web
接下來,我們需要在 Cargo.toml 檔案中檢查 Actix-web 的版本號是否正確。
[package]
name = "catdex-api"
# ...
[dependencies]
actix-web = "4.3.1"
編寫 Hello World 程式
現在,讓我們開啟 src/main.rs 檔案,並將以下程式碼複製到其中:
use actix_web::{web, App, HttpResponse, HttpServer, Responder};
async fn hello() -> impl Responder {
HttpResponse::Ok().body("Hello world")
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
println!("Listening on port 8080");
HttpServer::new(|| {
App::new()
.route("/hello", web::get().to(hello))
})
.bind("127.0.0.1:8080")?
.run()
.await
}
程式碼解析:
- 我們定義了一個名為
hello的非同步函式,它傳回一個實作Responder特性的型別。在這個例子中,我們傳回一個 HTTP 回應,狀態碼為 200,內容為 “Hello world”。 - 在
main函式中,我們使用HttpServer建立了一個新的 HTTP 伺服器,並將其繫結到127.0.0.1:8080。 - 我們使用
App結構體來建立一個新的應用程式例項,並定義了一個路由/hello,當使用者存取這個路徑時,將會呼叫hello處理函式。
內容解密:
async fn hello()定義了一個非同步函式,這使得我們的程式能夠非同步處理請求,提高了伺服器的效率。HttpResponse::Ok().body("Hello world")建立了一個 HTTP 回應,狀態碼為 200,內容為 “Hello world”。HttpServer::new(|| { ... })建立了一個新的 HTTP 伺服器,並使用一個閉包來建立App例項。這使得伺服器能夠在多個工作執行緒中執行,提高了可擴充套件性。
非同步程式設計的重要性
在現代網頁伺服器開發中,非同步程式設計是一個非常重要的概念。當我們呼叫一個阻塞函式時,整個執行緒會被阻塞,直到函式傳回。但是,如果函式是非同步的,它會立即傳回一個 Future,而不是阻塞執行緒。這使得我們的程式能夠非同步等待 Future 的完成,並且在等待期間執行其他任務。
這對於建立高效的網頁伺服器至關重要。現代網頁伺服器通常需要同時處理大量的客戶端請求。如果伺服器一次只處理一個請求,並且在等待 I/O 操作時阻塞,那麼它基本上是在做無用功。透過使用非同步程式設計,我們可以提高伺服器的效率和可擴充套件性。
使用 Actix-web 開發 Web 應用程式
在現代 Web 開發中,處理多個客戶端請求是一項基本需求。為了滿足這一需求,開發者可以採用多種技術,例如使用多個行程(processes)、多個執行緒(threads),或是利用非同步/等待(async/await)機制。
多行程與多執行緒的比較
- 多行程:每個行程獨立執行,能夠處理一個客戶端請求。然而,由於行程間的切換成本較高,因此在處理大量客戶端請求時可能會面臨效能瓶頸。
- 多執行緒:執行緒是行程中的一個執行單元,多個執行緒可以分享同一行程的資源,因此建立和切換執行緒的成本低於行程。這使得多執行緒在處理多個客戶端請求時比多行程更具效率。
非同步/等待機制
非同步/等待機制允許在單一執行緒中處理多個客戶端請求。當伺服器等待客戶端的網路 I/O 操作完成時,可以將執行權交給其他客戶端,從而提高伺服器的平行處理能力。這種機制避免了執行緒切換的開銷,因此在處理大量網路 I/O 操作時具有更好的效能。
建構 Actix-web 專案
Actix-web 是一個根據 Rust 語言的 Web 框架,它支援非同步/等待機制,能夠高效地處理客戶端請求。建構 Actix-web 專案的核心工作是定義處理請求的函式,並將這些函式對映到特定的路由上。
簡單的 Hello World 範例
首先,我們可以建立一個簡單的 Hello World 範例。透過執行 cargo run 命令,可以啟動一個 Web 伺服器,並在瀏覽器中存取 http://127.0.0.1:8080/hello 以檢視輸出結果。
提供靜態檔案
在實際的 Web 應用程式中,通常需要提供靜態檔案,如 HTML、CSS 和圖片等。Actix-web 提供了 actix-files 套件來支援靜態檔案的提供。
設定靜態檔案目錄
首先,建立一個名為 static 的目錄,並在其中新增所需的靜態檔案,如 index.html 和 css/index.css。然後,在 Cargo.toml 中新增 actix-files 套件的依賴。
提供靜態檔案的程式碼範例
use actix_files::NamedFile;
use actix_web::{web, App, HttpServer, Result};
async fn index() -> Result<NamedFile> {
Ok(NamedFile::open("./static/index.html")?)
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
println!("Listening on port 8080");
HttpServer::new(|| {
App::new()
.route("/", web::get().to(index))
})
.bind("127.0.0.1:8080")?
.run()
.await
}
內容解密:
actix_files::NamedFile:用於開啟並提供靜態檔案。async fn index():定義了一個非同步函式,用於處理對根路由/的 GET 請求,並傳回index.html檔案。HttpServer::new():建立了一個新的 HTTP 伺服器,並將其繫結到127.0.0.1:8080。App::new().route("/", web::get().to(index)):定義了根路由/的處理函式為index。
最佳實踐:靜態資源的最佳佈署策略
在生產環境中,通常建議將靜態資源(如 HTML、CSS、JavaScript 檔案)佈署在專門的靜態檔案伺服器(如 Nginx)上,或使用 CDN(內容傳遞網路)進行快取。這樣可以實作以下好處:
- 提高快取效率:透過 CDN 可以實作靜態資源的全球快取,減少使用者存取延遲。
- 獨立擴充套件:靜態資源伺服器和 API 伺服器可以獨立擴充套件,提高系統的整體彈性。
- 簡化佈署和維護:分離靜態和動態內容的佈署,可以簡化系統的佈署和維護流程。