Created
March 4, 2026 15:06
-
-
Save jiayun/aff36dfeba415d7feaae797f225eced6 to your computer and use it in GitHub Desktop.
Ch24 Web API Server — 完整範例
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // 執行:cargo run -p ch24_web_api_server,然後用 curl 測試 | |
| // | |
| // 範例 curl 指令: | |
| // curl http://localhost:3000/todos | |
| // curl -X POST http://localhost:3000/todos -H "Content-Type: application/json" -d '{"title":"學 Rust"}' | |
| // curl http://localhost:3000/todos/1 | |
| // curl -X DELETE http://localhost:3000/todos/1 | |
| use axum::{ | |
| Json, Router, | |
| extract::{Path, State}, | |
| http::StatusCode, | |
| response::IntoResponse, | |
| routing::get, | |
| }; | |
| use serde::{Deserialize, Serialize}; | |
| use std::sync::{Arc, Mutex}; | |
| // === 資料模型 === | |
| /// Todo 項目 | |
| #[derive(Debug, Clone, Serialize, Deserialize)] | |
| struct Todo { | |
| id: u64, | |
| title: String, | |
| completed: bool, | |
| } | |
| /// 建立 Todo 的請求 | |
| #[derive(Debug, Deserialize)] | |
| struct CreateTodo { | |
| title: String, | |
| } | |
| // === 共享狀態 === | |
| /// 應用程式狀態,使用 Arc<Mutex<>> 實現執行緒安全的共享 | |
| #[derive(Clone)] | |
| struct AppState { | |
| todos: Arc<Mutex<Vec<Todo>>>, | |
| next_id: Arc<Mutex<u64>>, | |
| } | |
| impl AppState { | |
| fn new() -> Self { | |
| Self { | |
| todos: Arc::new(Mutex::new(Vec::new())), | |
| next_id: Arc::new(Mutex::new(1)), | |
| } | |
| } | |
| } | |
| // === 處理函式(Handlers)=== | |
| /// GET /todos — 列出所有待辦事項 | |
| async fn list_todos(State(state): State<AppState>) -> Json<Vec<Todo>> { | |
| let todos = state.todos.lock().expect("mutex poisoned"); | |
| Json(todos.clone()) | |
| } | |
| /// POST /todos — 建立新的待辦事項 | |
| async fn create_todo( | |
| State(state): State<AppState>, | |
| Json(input): Json<CreateTodo>, | |
| ) -> impl IntoResponse { | |
| let mut next_id = state.next_id.lock().expect("mutex poisoned"); | |
| let mut todos = state.todos.lock().expect("mutex poisoned"); | |
| let todo = Todo { | |
| id: *next_id, | |
| title: input.title, | |
| completed: false, | |
| }; | |
| *next_id += 1; | |
| todos.push(todo.clone()); | |
| (StatusCode::CREATED, Json(todo)) | |
| } | |
| /// GET /todos/:id — 取得單一待辦事項 | |
| async fn get_todo( | |
| State(state): State<AppState>, | |
| Path(id): Path<u64>, | |
| ) -> Result<Json<Todo>, StatusCode> { | |
| let todos = state.todos.lock().expect("mutex poisoned"); | |
| todos | |
| .iter() | |
| .find(|t| t.id == id) | |
| .cloned() | |
| .map(Json) | |
| .ok_or(StatusCode::NOT_FOUND) | |
| } | |
| /// DELETE /todos/:id — 刪除待辦事項 | |
| async fn delete_todo( | |
| State(state): State<AppState>, | |
| Path(id): Path<u64>, | |
| ) -> Result<impl IntoResponse, StatusCode> { | |
| let mut todos = state.todos.lock().expect("mutex poisoned"); | |
| let len_before = todos.len(); | |
| todos.retain(|t| t.id != id); | |
| if todos.len() == len_before { | |
| Err(StatusCode::NOT_FOUND) | |
| } else { | |
| Ok(StatusCode::NO_CONTENT) | |
| } | |
| } | |
| // === 路由設定 === | |
| fn create_router() -> Router { | |
| let state = AppState::new(); | |
| Router::new() | |
| .route("/todos", get(list_todos).post(create_todo)) | |
| .route("/todos/{id}", get(get_todo).delete(delete_todo)) | |
| .with_state(state) | |
| } | |
| // === 主程式 === | |
| #[tokio::main] | |
| async fn main() { | |
| let app = create_router(); | |
| println!("🚀 伺服器啟動於 http://localhost:3000"); | |
| println!(" GET /todos — 列出所有待辦事項"); | |
| println!(" POST /todos — 建立新待辦事項"); | |
| println!(" GET /todos/{{id}} — 取得單一待辦事項"); | |
| println!(" DELETE /todos/{{id}} — 刪除待辦事項"); | |
| let listener = tokio::net::TcpListener::bind("0.0.0.0:3000") | |
| .await | |
| .expect("無法綁定連接埠 3000"); | |
| axum::serve(listener, app).await.expect("伺服器啟動失敗"); | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment