前言
在現代網頁開發領域中,伺服器端渲染(Server-Side Rendering,簡稱 SSR)技術因其優異的搜尋引擎最佳化(SEO)效果與首次內容繪製(First Contentful Paint)效能表現,仍然是網頁應用程式開發的重要選擇。本文將帶領您使用 Rust 程式語言的高效能網頁框架 Actix Web,搭配功能強大的 Tera 範本引擎,從零開始建構一個完整的伺服器端渲染應用程式。
透過本教學,您將學會:
- 建立 Actix Web 專案並提供靜態網頁檔案
- 整合 Tera 範本引擎進行動態內容渲染
- 實作完整的使用者表單提交與資料處理流程
- 理解 SSR 的運作原理與實際應用場景
開發環境準備
在開始之前,請確保您的開發環境已安裝以下工具:
- Rust 程式語言:版本 1.70 或以上(建議使用最新穩定版)
- Cargo:Rust 的套件管理工具(隨 Rust 安裝自動包含)
- 文字編輯器:建議使用 VS Code、IntelliJ IDEA 或 RustRover
您可以透過以下指令確認 Rust 安裝狀態:
rustc --version
cargo --version
步驟一:建立基礎專案並提供靜態網頁
我們的第一個目標是建立一個最精簡的 Actix Web 伺服器,用來提供靜態 HTML 檔案。這個步驟將幫助您理解 Actix Web 的基本架構。
1.1 初始化專案
首先,建立一個新的 Rust 專案:
cargo new actix-ssr-demo
cd actix-ssr-demo
1.2 設定專案相依套件
編輯 Cargo.toml 檔案,加入必要的相依套件:
[package]
name = "actix-ssr-demo"
version = "0.1.0"
edition = "2021"
[dependencies]
actix-web = "4.4"
actix-files = "0.6"
套件說明:
actix-web:高效能的非同步網頁框架actix-files:用於提供靜態檔案服務的中介軟體
1.3 建立靜態網頁檔案
在專案根目錄下建立 static 資料夾,並新增 index.html 檔案:
mkdir static
編輯 static/index.html:
<!DOCTYPE html>
<html lang="zh-TW">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Actix Web 靜態頁面範例</title>
<style>
body {
font-family: "Microsoft JhengHei", "Noto Sans TC", sans-serif;
max-width: 800px;
margin: 50px auto;
padding: 20px;
background-color: #f5f5f5;
}
h1 {
color: #333;
border-bottom: 3px solid #007bff;
padding-bottom: 10px;
}
</style>
</head>
<body>
<h1>歡迎使用 Actix Web</h1>
<p>這是一個由 Actix Web 框架提供的靜態 HTML 頁面。</p>
<p>您正在學習如何建立高效能的 Rust 網頁應用程式!</p>
</body>
</html>
1.4 實作伺服器程式碼
編輯 src/main.rs,實作基礎的靜態檔案伺服器:
use actix_files::Files;
use actix_web::{App, HttpServer};
#[actix_web::main]
async fn main() -> std::io::Result<()> {
println!("🚀 伺服器已啟動!");
println!("📡 正在監聽位址:http://127.0.0.1:8080");
println!("💡 按下 Ctrl+C 可停止伺服器");
HttpServer::new(|| {
App::new()
.service(
Files::new("/", "./static")
.index_file("index.html")
.show_files_listing()
)
})
.bind(("127.0.0.1", 8080))?
.run()
.await
}
程式碼詳解:
actix_files::Files:這是 Actix Web 提供的靜態檔案服務中介軟體,能將 HTTP 請求對應到檔案系統中的實際檔案。Files::new("/", "./static"):- 第一個參數
"/"表示網站的根路徑 - 第二個參數
"./static"指定對應的本機資料夾位置
- 第一個參數
.index_file("index.html"):當使用者訪問目錄路徑時(例如/),自動提供index.html檔案。.show_files_listing():允許在瀏覽器中顯示目錄結構(開發時方便,正式環境建議移除)。HttpServer::new():建立 HTTP 伺服器實例,接受一個閉包作為應用程式工廠。.bind(("127.0.0.1", 8080))?:綁定伺服器到本機的 8080 埠。
1.5 執行與測試
執行以下指令啟動伺服器:
cargo run
開啟瀏覽器,訪問 http://localhost:8080,您應該會看到剛才建立的靜態頁面。
步驟二:整合 Tera 範本引擎實現動態內容渲染
靜態頁面無法滿足大多數應用程式的需求。現在我們要引入 Tera 範本引擎,實現動態內容的生成與渲染。
2.1 加入 Tera 相依套件
更新 Cargo.toml,加入 tera 和 serde 套件:
[dependencies]
actix-web = "4.4"
actix-files = "0.6"
tera = "1.19"
serde = { version = "1.0", features = ["derive"] }
套件說明:
tera:受 Jinja2 啟發的強大範本引擎serde:Rust 的序列化與反序列化框架,用於資料轉換
2.2 建立動態範本檔案
將 static/index.html 修改為動態範本:
<!DOCTYPE html>
<html lang="zh-TW">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{ page_title }}</title>
<style>
body {
font-family: "Microsoft JhengHei", "Noto Sans TC", sans-serif;
max-width: 800px;
margin: 50px auto;
padding: 20px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
border-radius: 10px;
box-shadow: 0 10px 30px rgba(0,0,0,0.3);
}
h1 {
border-bottom: 3px solid white;
padding-bottom: 15px;
margin-bottom: 20px;
}
.info-box {
background-color: rgba(255,255,255,0.1);
padding: 15px;
border-radius: 8px;
margin-top: 20px;
}
</style>
</head>
<body>
<h1>歡迎,{{ user_name }}!</h1>
<p>這是一個由 <strong>Actix Web</strong> 與 <strong>Tera</strong> 動態渲染的頁面。</p>
<div class="info-box">
<h3>當前資訊:</h3>
<ul>
<li>訪問時間:{{ current_time }}</li>
<li>伺服器狀態:{{ server_status }}</li>
<li>頁面版本:{{ version }}</li>
</ul>
</div>
<p><em>此頁面內容由伺服器端動態生成</em></p>
</body>
</html>
範本語法說明:
{{ variable_name }}:輸出變數內容- Tera 支援條件判斷、迴圈、過濾器等進階功能
2.3 實作動態渲染的伺服器程式碼
完整重寫 src/main.rs:
use actix_web::{web, App, Error, HttpResponse, HttpServer, Result};
use tera::Tera;
use std::sync::Arc;
// 首頁處理函式
async fn index(tmpl: web::Data<Arc<Tera>>) -> Result<HttpResponse, Error> {
let mut ctx = tera::Context::new();
// 傳入動態資料
ctx.insert("page_title", "Actix Web + Tera 動態頁面");
ctx.insert("user_name", "玄貓(BlackCat)");
ctx.insert("current_time", &format!("{}", chrono::Local::now().format("%Y-%m-%d %H:%M:%S")));
ctx.insert("server_status", "運作正常");
ctx.insert("version", "1.0.0");
// 渲染範本
let rendered = tmpl
.render("index.html", &ctx)
.map_err(|e| {
eprintln!("範本渲染錯誤:{}", e);
actix_web::error::ErrorInternalServerError("範本渲染失敗")
})?;
Ok(HttpResponse::Ok()
.content_type("text/html; charset=utf-8")
.body(rendered))
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
println!("🚀 伺服器已啟動!");
println!("📡 正在監聽位址:http://127.0.0.1:8080");
// 初始化 Tera 範本引擎
let tera = match Tera::new("static/**/*") {
Ok(t) => Arc::new(t),
Err(e) => {
eprintln!("Tera 初始化錯誤:{}", e);
std::process::exit(1);
}
};
HttpServer::new(move || {
App::new()
.app_data(web::Data::new(tera.clone()))
.route("/", web::get().to(index))
})
.bind(("127.0.0.1", 8080))?
.run()
.await
}
注意:此程式碼使用 chrono 套件來取得當前時間,需要在 Cargo.toml 加入:
chrono = "0.4"
程式碼重點說明:
Arc<Tera>:使用Arc(原子參考計數)來安全地在多個執行緒間共享 Tera 實例。tera::Context:用於儲存要傳遞給範本的所有變數資料。錯誤處理:妥善處理範本渲染可能發生的錯誤,避免伺服器崩潰。
內容類型設定:明確指定
charset=utf-8確保中文正確顯示。
2.4 測試動態渲染
重新執行專案:
cargo run
重新整理瀏覽器頁面,您會看到動態生成的個人化內容,包含即時的伺服器時間。
步驟三:實作完整的使用者表單互動功能
現在我們要實現一個完整的互動流程:顯示表單 → 接收使用者輸入 → 處理資料 → 顯示結果頁面。
3.1 建立表單範本
在 static/ 資料夾中新增 form.html:
<!DOCTYPE html>
<html lang="zh-TW">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>講師資料登記表單</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: "Microsoft JhengHei", "Noto Sans TC", sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
display: flex;
justify-content: center;
align-items: center;
padding: 20px;
}
.form-container {
background: white;
padding: 40px;
border-radius: 15px;
box-shadow: 0 15px 35px rgba(0,0,0,0.3);
max-width: 500px;
width: 100%;
}
h2 {
color: #333;
margin-bottom: 30px;
text-align: center;
border-bottom: 3px solid #667eea;
padding-bottom: 15px;
}
.form-group {
margin-bottom: 25px;
}
label {
display: block;
margin-bottom: 8px;
color: #555;
font-weight: bold;
}
input, select, textarea {
width: 100%;
padding: 12px 15px;
border: 2px solid #e0e0e0;
border-radius: 8px;
font-size: 16px;
transition: border-color 0.3s;
font-family: inherit;
}
input:focus, select:focus, textarea:focus {
outline: none;
border-color: #667eea;
}
textarea {
resize: vertical;
min-height: 100px;
}
button {
width: 100%;
padding: 15px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
border: none;
border-radius: 8px;
font-size: 18px;
font-weight: bold;
cursor: pointer;
transition: transform 0.2s, box-shadow 0.2s;
}
button:hover {
transform: translateY(-2px);
box-shadow: 0 5px 15px rgba(102, 126, 234, 0.4);
}
button:active {
transform: translateY(0);
}
.required {
color: #e74c3c;
}
</style>
</head>
<body>
<div class="form-container">
<h2>📚 講師資料登記表</h2>
<form action="/tutor" method="POST">
<div class="form-group">
<label for="name">姓名 <span class="required">*</span></label>
<input type="text" id="name" name="name" required
placeholder="請輸入您的姓名" maxlength="50">
</div>
<div class="form-group">
<label for="email">電子郵件 <span class="required">*</span></label>
<input type="email" id="email" name="email" required
placeholder="example@email.com">
</div>
<div class="form-group">
<label for="subject">專長科目 <span class="required">*</span></label>
<select id="subject" name="subject" required>
<option value="">請選擇專長科目</option>
<option value="rust">Rust 程式設計</option>
<option value="web">網頁開發</option>
<option value="data">資料科學</option>
<option value="ai">人工智慧</option>
<option value="other">其他</option>
</select>
</div>
<div class="form-group">
<label for="experience">教學經驗(年)</label>
<input type="number" id="experience" name="experience"
min="0" max="50" placeholder="0">
</div>
<div class="form-group">
<label for="bio">個人簡介</label>
<textarea id="bio" name="bio"
placeholder="請簡單介紹您的教學背景與專長領域..."></textarea>
</div>
<button type="submit">🚀 提交資料</button>
</form>
</div>
</body>
</html>
3.2 建立結果顯示範本
建立 static/welcome.html:
<!DOCTYPE html>
<html lang="zh-TW">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>註冊成功</title>
<style>
body {
font-family: "Microsoft JhengHei", "Noto Sans TC", sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
display: flex;
justify-content: center;
align-items: center;
padding: 20px;
}
.welcome-box {
background: white;
padding: 50px;
border-radius: 15px;
box-shadow: 0 15px 35px rgba(0,0,0,0.3);
max-width: 600px;
text-align: center;
}
.success-icon {
font-size: 80px;
margin-bottom: 20px;
}
h1 {
color: #333;
margin-bottom: 20px;
}
.info-section {
background: #f8f9fa;
padding: 25px;
border-radius: 10px;
margin: 30px 0;
text-align: left;
}
.info-item {
margin: 12px 0;
padding: 10px;
border-left: 4px solid #667eea;
padding-left: 15px;
}
.info-label {
font-weight: bold;
color: #555;
display: inline-block;
width: 120px;
}
.info-value {
color: #333;
}
.back-link {
display: inline-block;
margin-top: 20px;
padding: 12px 30px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
text-decoration: none;
border-radius: 8px;
transition: transform 0.2s;
}
.back-link:hover {
transform: translateY(-2px);
}
</style>
</head>
<body>
<div class="welcome-box">
<div class="success-icon">✅</div>
<h1>註冊成功!</h1>
<p>歡迎加入我們的講師團隊,<strong>{{ name }}</strong>!</p>
<div class="info-section">
<h3>📋 您提交的資料:</h3>
<div class="info-item">
<span class="info-label">姓名:</span>
<span class="info-value">{{ name }}</span>
</div>
<div class="info-item">
<span class="info-label">電子郵件:</span>
<span class="info-value">{{ email }}</span>
</div>
<div class="info-item">
<span class="info-label">專長科目:</span>
<span class="info-value">{{ subject_display }}</span>
</div>
{% if experience %}
<div class="info-item">
<span class="info-label">教學經驗:</span>
<span class="info-value">{{ experience }} 年</span>
</div>
{% endif %}
{% if bio %}
<div class="info-item">
<span class="info-label">個人簡介:</span>
<span class="info-value">{{ bio }}</span>
</div>
{% endif %}
</div>
<p>我們已收到您的資料,將盡快與您聯繫!</p>
<a href="/form" class="back-link">← 返回表單</a>
</div>
</body>
</html>
3.3 實作完整的表單處理邏輯
完整更新 src/main.rs:
use actix_web::{web, App, Error, HttpResponse, HttpServer, Result};
use serde::Deserialize;
use tera::Tera;
use std::sync::Arc;
// 定義講師資料結構
#[derive(Deserialize, Debug)]
pub struct TutorForm {
name: String,
email: String,
subject: String,
experience: Option<u32>,
bio: Option<String>,
}
// 首頁處理函式
async fn index(tmpl: web::Data<Arc<Tera>>) -> Result<HttpResponse, Error> {
let mut ctx = tera::Context::new();
ctx.insert("page_title", "Actix Web + Tera 範例");
ctx.insert("user_name", "訪客");
let rendered = tmpl
.render("index.html", &ctx)
.map_err(|e| {
eprintln!("範本渲染錯誤:{}", e);
actix_web::error::ErrorInternalServerError("範本渲染失敗")
})?;
Ok(HttpResponse::Ok()
.content_type("text/html; charset=utf-8")
.body(rendered))
}
// 顯示表單頁面
async fn show_form(tmpl: web::Data<Arc<Tera>>) -> Result<HttpResponse, Error> {
let ctx = tera::Context::new();
let rendered = tmpl
.render("form.html", &ctx)
.map_err(|e| {
eprintln!("範本渲染錯誤:{}", e);
actix_web::error::ErrorInternalServerError("範本渲染失敗")
})?;
Ok(HttpResponse::Ok()
.content_type("text/html; charset=utf-8")
.body(rendered))
}
// 處理表單提交
async fn handle_post_tutor(
tmpl: web::Data<Arc<Tera>>,
form: web::Form<TutorForm>,
) -> Result<HttpResponse, Error> {
// 記錄收到的資料(實際應用中會存入資料庫)
println!("收到講師註冊資料:{:#?}", form);
// 準備範本上下文
let mut ctx = tera::Context::new();
ctx.insert("name", &form.name);
ctx.insert("email", &form.email);
// 將科目代碼轉換為中文顯示
let subject_display = match form.subject.as_str() {
"rust" => "Rust 程式設計",
"web" => "網頁開發",
"data" => "資料科學",
"ai" => "人工智慧",
"other" => "其他",
_ => "未指定",
};
ctx.insert("subject_display", subject_display);
// 處理選填欄位
if let Some(exp) = form.experience {
ctx.insert("experience", &exp);
}
if let Some(bio) = &form.bio {
if !bio.trim().is_empty() {
ctx.insert("bio", bio);
}
}
// 渲染歡迎頁面
let rendered = tmpl
.render("welcome.html", &ctx)
.map_err(|e| {
eprintln!("範本渲染錯誤:{}", e);
actix_web::error::ErrorInternalServerError("範本渲染失敗")
})?;
Ok(HttpResponse::Ok()
.content_type("text/html; charset=utf-8")
.body(rendered))
}
// 設定路由
fn config_routes(cfg: &mut web::ServiceConfig) {
cfg.service(
web::scope("")
.route("/", web::get().to(index))
.route("/form", web::get().to(show_form))
.route("/tutor", web::post().to(handle_post_tutor)),
);
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
println!("🚀 Actix Web SSR 伺服器已啟動!");
println!("📡 監聽位址:http://127.0.0.1:8080");
println!("📄 可用路由:");
println!(" - GET / → 首頁");
println!(" - GET /form → 講師註冊表單");
println!(" - POST /tutor → 提交表單資料");
println!("💡 按下 Ctrl+C 可停止伺服器\n");
// 初始化 Tera 範本引擎
let tera = match Tera::new("static/**/*") {
Ok(t) => {
println!("✅ Tera 範本引擎初始化成功");
Arc::new(t)
}
Err(e) => {
eprintln!("❌ Tera 初始化失敗:{}", e);
std::process::exit(1);
}
};
HttpServer::new(move || {
App::new()
.app_data(web::Data::new(tera.clone()))
.configure(config_routes)
})
.bind(("127.0.0.1", 8080))?
.run()
.await
}
重要程式碼說明:
TutorForm結構體:- 使用
#[derive(Deserialize)]讓 Actix Web 自動將表單資料解析為此結構 Option<T>型別表示該欄位為選填
- 使用
web::Form<TutorForm>:Actix Web 會自動從 HTTP 請求中提取表單資料並填充到結構體。資料驗證:實際應用中應加入更嚴格的資料驗證(例如使用
validatorcrate)。錯誤處理:每個可能失敗的操作都有適當的錯誤處理機制。
3.4 測試完整流程
執行伺服器:
cargo run
測試步驟:
- 訪問
http://localhost:8080/form - 填寫表單並提交
- 確認能正確跳轉到歡迎頁面並顯示提交的資料
架構圖解:SSR 完整流程
以下圖表展示了從使用者請求到伺服器回應的完整互動流程:
系統架構圖
@startuml
!define DISABLE_LINK
!define PLANTUML_FORMAT svg
!theme _none_
skinparam dpi auto
skinparam shadowing false
skinparam linetype ortho
skinparam roundcorner 5
skinparam defaultFontName "Microsoft JhengHei UI"
skinparam defaultFontSize 16
skinparam minClassWidth 100
package "客戶端" {
[瀏覽器] as Browser
}
package "Actix Web 伺服器" {
[HTTP 路由器] as Router
[請求處理器] as Handler
[Tera 範本引擎] as Tera
[資料驗證] as Validator
}
package "檔案系統" {
folder "static/" {
[index.html] as Index
[form.html] as Form
[welcome.html] as Welcome
}
}
Browser --> Router : HTTP 請求
Router --> Handler : 路由分發
Handler --> Validator : 驗證表單資料
Handler --> Tera : 請求渲染範本
Tera --> Index : 讀取範本
Tera --> Form : 讀取範本
Tera --> Welcome : 讀取範本
Tera --> Handler : 回傳 HTML
Handler --> Router : 回應資料
Router --> Browser : HTTP 回應
note right of Tera
Tera 引擎負責:
- 載入範本檔案
- 填入動態資料
- 生成最終 HTML
end note
note left of Handler
處理器負責:
- 業務邏輯處理
- 資料準備
- 範本上下文建立
end note
@enduml表單提交時序圖
@startuml
!define DISABLE_LINK
!define PLANTUML_FORMAT svg
!theme _none_
skinparam dpi auto
skinparam shadowing false
skinparam linetype ortho
skinparam roundcorner 5
skinparam defaultFontName "Microsoft JhengHei UI"
skinparam defaultFontSize 16
skinparam minClassWidth 100
skinparam sequenceMessageAlign center
title SSR 表單提交完整流程
actor "使用者" as User
participant "瀏覽器" as Browser
participant "Actix Web\n伺服器" as Server
participant "路由處理器" as Router
participant "表單處理器" as Handler
participant "Tera 引擎" as Tera
database "範本檔案" as Template
User -> Browser : 輸入網址 /form
activate Browser
Browser -> Server : GET /form
activate Server
Server -> Router : 路由解析
activate Router
Router -> Handler : show_form()
activate Handler
Handler -> Tera : 請求渲染 form.html
activate Tera
Tera -> Template : 讀取 form.html
Template --> Tera : 回傳範本內容
Tera --> Handler : 回傳渲染後 HTML
deactivate Tera
Handler --> Router : HTTP 200 + HTML
deactivate Handler
Router --> Server : 回應資料
deactivate Router
Server --> Browser : 表單頁面 HTML
deactivate Server
Browser -> User : 顯示表單
deactivate Browser
User -> Browser : 填寫並提交表單
activate Browser
Browser -> Server : POST /tutor\n(表單資料)
activate Server
Server -> Router : 路由解析
activate Router
Router -> Handler : handle_post_tutor(form)
activate Handler
Handler -> Handler : 驗證與處理資料
Handler -> Tera : 請求渲染 welcome.html
activate Tera
Tera -> Template : 讀取 welcome.html
Template --> Tera : 回傳範本內容
Tera -> Tera : 填入使用者資料
Tera --> Handler : 回傳渲染後 HTML
deactivate Tera
Handler --> Router : HTTP 200 + HTML
deactivate Handler
Router --> Server : 回應資料
deactivate Router
Server --> Browser : 歡迎頁面 HTML
deactivate Server
Browser -> User : 顯示成功訊息
deactivate Browser
@enduml資料流程圖
@startuml
!define DISABLE_LINK
!define PLANTUML_FORMAT svg
!theme _none_
skinparam dpi auto
skinparam shadowing false
skinparam linetype ortho
skinparam roundcorner 5
skinparam defaultFontName "Microsoft JhengHei UI"
skinparam defaultFontSize 16
skinparam minClassWidth 100
title 伺服器端渲染資料處理流程
start
:接收 HTTP 請求;
if (請求類型?) then (GET)
:解析路由路徑;
if (路徑判斷) then (/form)
:載入 form.html 範本;
:建立空白 Context;
else (/)
:載入 index.html 範本;
:建立預設 Context;
endif
else (POST)
:解析表單資料;
:驗證資料格式;
if (資料是否有效?) then (是)
:轉換科目代碼;
:建立包含使用者資料的 Context;
:載入 welcome.html 範本;
else (否)
:回傳錯誤訊息;
stop
endif
endif
:Tera 引擎渲染範本;
:將 Context 資料填入範本;
:生成最終 HTML 字串;
:設定 Content-Type 為 text/html;
:回傳 HTTP 200 與 HTML;
stop
@enduml進階應用建議
1. 加入資料庫支援
實際應用中,表單資料應該儲存到資料庫。推薦使用:
- SQLx:非同步 SQL 工具包,支援編譯時檢查
- Diesel:功能完整的 ORM 框架
- SeaORM:現代化的非同步 ORM
2. 表單驗證強化
使用 validator crate 進行資料驗證:
validator = { version = "0.16", features = ["derive"] }
use validator::Validate;
#[derive(Deserialize, Validate)]
pub struct TutorForm {
#[validate(length(min = 2, max = 50))]
name: String,
#[validate(email)]
email: String,
#[validate(range(min = 0, max = 50))]
experience: Option<u32>,
}
3. 錯誤頁面處理
建立統一的錯誤處理範本:
<!-- static/error.html -->
<!DOCTYPE html>
<html lang="zh-TW">
<head>
<title>錯誤 - {{ error_code }}</title>
</head>
<body>
<h1>發生錯誤</h1>
<p>錯誤代碼:{{ error_code }}</p>
<p>{{ error_message }}</p>
</body>
</html>
4. CSRF 防護
使用 actix-csrf 套件防範跨站請求偽造攻擊。
5. 環境變數設定
使用 dotenv 管理設定:
dotenv = "0.15"
use dotenv::dotenv;
#[actix_web::main]
async fn main() -> std::io::Result<()> {
dotenv().ok();
let port = std::env::var("PORT").unwrap_or_else(|_| "8080".to_string());
// ...
}
效能最佳化建議
範本快取:Tera 預設會快取編譯後的範本,生產環境無需額外設定。
啟用 Release 模式:
cargo build --release cargo run --release設定適當的 Worker 數量:
HttpServer::new(|| { /* ... */ }) .workers(4) // 設定為 CPU 核心數 .bind(("127.0.0.1", 8080))? .run() .await靜態檔案壓縮:使用
actix-web-middleware-compress啟用 gzip 壓縮。
常見問題排解
Q1: 範本無法載入
解決方法:
- 確認
static/資料夾位於正確位置 - 檢查檔案權限
- 使用絕對路徑進行測試
Q2: 中文顯示亂碼
解決方法:
- 確保所有檔案使用 UTF-8 編碼儲存
- HTTP 回應必須包含
charset=utf-8 - HTML 檔案要有
<meta charset="UTF-8">
Q3: 表單提交後頁面空白
解決方法:
- 檢查伺服器控制台的錯誤訊息
- 確認表單
action屬性與路由一致 - 驗證表單欄位的
name屬性與結構體欄位名稱相符