主頁 > 後端開發 > Rust Web 全堆疊開發之 Actix 嘗鮮并構建REST API

Rust Web 全堆疊開發之 Actix 嘗鮮并構建REST API

2023-05-29 07:47:16 後端開發

Rust Web 全堆疊開發之 Actix 嘗鮮并構建REST API

一、Actix 嘗鮮

需要使用的crate

  • actix-web v4.3.1
  • actix-rt v2.8.0
~ via ?? base
? cd rust

~/rust via ?? base
? cargo new ws  # workspace
     Created binary (application) `ws` package

~/rust via ?? base
? cd ws

ws on  master [?] is ?? 0.1.0 via ?? 1.67.1 via ?? base
? c

ws on  master [?] is ?? 0.1.0 via ?? 1.67.1 via ?? base
?

ws on  master [?] is ?? 0.1.0 via ?? 1.67.1 via ?? base
? cargo new webservice
     Created binary (application) `webservice` package

ws on  master [?] via ?? 1.67.1 via ?? base
?

目錄

ws on  master [?] via ?? 1.67.1 via ?? base
? tree -I target
.
├── Cargo.lock
├── Cargo.toml
├── src
│   └── main.rs
└── webservice
    ├── Cargo.toml
    └── src
        ├── bin
        │   └── server1.rs
        └── main.rs

5 directories, 6 files

ws on  master [?] via ?? 1.67.1 via ?? base
?

Cargo.toml

[workspace]
members = ["webservice"]

webservice/Cargo.toml

[package]
name = "webservice"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
actix-web = "4.3.1"
actix-rt = "2.8.0"

[[bin]]
name = "server1"

webservice/src/bin/server1.rs

use actix_web::{web, App, HttpResponse, HttpServer, Responder};
use std::io;

// 配置 route
pub fn general_routes(cfg: &mut web::ServiceConfig) {
    cfg.route("/health", web::get().to(health_check_handler));
}

// 配置 handler
pub async fn health_check_handler() -> impl Responder {
    HttpResponse::Ok().json("Actix Web Service is running!")
}

// 實體化 HTTP Server 并運行
#[actix_rt::main]
async fn main() -> io::Result<()> {
    // 構建 App 配置 route
    let app = move || App::new().configure(general_routes);

    // 運行 HTTP Server
    HttpServer::new(app).bind("127.0.0.1:3000")?.run().await
}

運行

ws on  master [?] via ?? 1.67.1 via ?? base 
? cargo run -p webservice --bin server1
   Compiling actix-rt v2.8.0
   Compiling actix-http v3.3.1
   Compiling actix-server v2.2.0
   Compiling actix-web v4.3.1
   Compiling webservice v0.1.0 (/Users/qiaopengjun/rust/ws/webservice)
    Finished dev [unoptimized + debuginfo] target(s) in 4.26s
     Running `target/debug/server1`


ws on  master [?] via ?? 1.67.1 via ?? base 
? cd webservice                        

ws/webservice on  master [?] is ?? 0.1.0 via ?? 1.67.1 via ?? base 
? cargo run --bin server1              
    Finished dev [unoptimized + debuginfo] target(s) in 0.15s
     Running `/Users/qiaopengjun/rust/ws/target/debug/server1`

Actix的基本組件

客戶端瀏覽器 互聯網 Actix HTTP Server

Actix的并發(concurrency)

  • Actix支持兩類并發:
    • 異步I/O:給定的OS原生執行緒在等待I/O時執行其他任務(例如偵聽網路連接)
    • 多執行緒并行:默認情況下啟動OS原生執行緒的數量與系統邏輯CPU數量相同

二、構建REST API

需要使用的crate

  • serde, v1.0.163
  • chrono, v0.4.24

構建的內容

  • POST: /courses
  • GET:/courses/teacher_id
  • GET:/courses/teacher_id/course_id

相關檔案

  • bin/teacher-service.rs
  • models.rs
  • state.rs
  • routers.rs
  • handlers.rs

目錄

ws on  master [?] via ?? 1.67.1 via ?? base 
? tree -I target
.
├── Cargo.lock
├── Cargo.toml
├── src
│   └── main.rs
└── webservice
    ├── Cargo.toml
    └── src
        ├── bin
        │   ├── server1.rs
        │   └── teacher-service.rs
        ├── handlers.rs
        ├── main.rs
        ├── models.rs
        ├── routers.rs
        └── state.rs

5 directories, 11 files

ws on  master [?] via ?? 1.67.1 via ?? base 
? 

webservice/Cargo.toml

[package]
name = "webservice"
version = "0.1.0"
edition = "2021"
default-run = "teacher-service"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
actix-web = "4.3.1"
actix-rt = "2.8.0"

[[bin]]
name = "server1"

[[bin]]
name = "teacher-service"

webservice/src/bin/teacher-service.rs

use actix_web::{web, App, HttpServer};
use std::io;
use std::sync::Mutex;

#[path = "../handlers.rs"]
mod handlers;
#[path = "../routers.rs"]
mod routers;
#[path = "../state.rs"]
mod state;

use routers::*;
use state::AppState;

#[actix_rt::main]
async fn main() -> io::Result<()> {
    let shared_data = web::Data::new(AppState {
        health_check_response: "I'm Ok.".to_string(),
        visit_count: Mutex::new(0),
    });
    let app = move || {
        App::new()
            .app_data(shared_data.clone())
            .configure(general_routes)
    };

    HttpServer::new(app).bind("127.0.0.1:3000")?.run().await
}

webservice/src/handlers.rs

use super::state::AppState;
use actix_web::{web, HttpResponse};

pub async fn health_check_handler(app_state: web::Data<AppState>) -> HttpResponse {
    let health_check_response = &app_state.health_check_response;
    let mut visit_count = app_state.visit_count.lock().unwrap();
    let response = format!("{} {} times", health_check_response, visit_count);
    *visit_count += 1;
    HttpResponse::Ok().json(&response)
}

webservice/src/routers.rs

use super::handlers::*;
use actix_web::web;

pub fn general_routes(cfg: &mut web::ServiceConfig) {
    cfg.route("/health", web::get().to(health_check_handler));
}

webservice/src/state.rs

use std::sync::Mutex;

pub struct AppState {
    pub health_check_response: String,
    pub visit_count: Mutex<u32>,
}

運行

ws on  master [?] via ?? 1.67.1 via ?? base 
? cd webservice 

ws/webservice on  master [?] is ?? 0.1.0 via ?? 1.67.1 via ?? base 
? cargo run              
   Compiling webservice v0.1.0 (/Users/qiaopengjun/rust/ws/webservice)
    Finished dev [unoptimized + debuginfo] target(s) in 1.49s
     Running `/Users/qiaopengjun/rust/ws/target/debug/teacher-service`

請求訪問

ws on  master [?] via ?? 1.67.1 via ?? base 
? curl localhost:3000/health 
"I'm Ok. 0 times"%                                                                                           

ws on  master [?] via ?? 1.67.1 via ?? base 
? curl localhost:3000/health
"I'm Ok. 1 times"%                                                                                           

ws on  master [?] via ?? 1.67.1 via ?? base 
? curl localhost:3000/health
"I'm Ok. 2 times"%                                                                                           

ws on  master [?] via ?? 1.67.1 via ?? base 
? 

webservice/Cargo.toml

[package]
name = "webservice"
version = "0.1.0"
edition = "2021"
default-run = "teacher-service"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
actix-web = "4.3.1"
actix-rt = "2.8.0"
serde = { version = "1.0.163", features = ["derive"] }
chrono = { version = "0.4.24", features = ["serde"] }

[[bin]]
name = "server1"

[[bin]]
name = "teacher-service"

webservice/src/models.rs

use actix_web::web;
use chrono::NaiveDateTime;
use serde::{Deserialize, Serialize};

#[derive(Deserialize, Serialize, Debug, Clone)]
pub struct Course {
    pub teacher_id: usize,
    pub id: Option<usize>,
    pub name: String,
    pub time: Option<NaiveDateTime>,
}
impl From<web::Json<Course>> for Course {
    fn from(course: web::Json<Course>) -> Self {
        Course {
            teacher_id: course.teacher_id,
            id: course.id,
            name: course.name.clone(),
            time: course.time,
        }
    }
}

webservice/src/state.rs

use super::models::Course;
use std::sync::Mutex;

pub struct AppState {
    pub health_check_response: String,
    pub visit_count: Mutex<u32>,
    pub courses: Mutex<Vec<Course>>,
}

webservice/src/bin/teacher-service.rs

use actix_web::{web, App, HttpServer};
use std::io;
use std::sync::Mutex;

#[path = "../handlers.rs"]
mod handlers;
#[path = "../models.rs"]
mod models;
#[path = "../routers.rs"]
mod routers;
#[path = "../state.rs"]
mod state;

use routers::*;
use state::AppState;

#[actix_rt::main]
async fn main() -> io::Result<()> {
    let shared_data = web::Data::new(AppState {
        health_check_response: "I'm Ok.".to_string(),
        visit_count: Mutex::new(0),
        courses: Mutex::new(vec![]),
    });
    let app = move || {
        App::new()
            .app_data(shared_data.clone())
            .configure(general_routes)
    };

    HttpServer::new(app).bind("127.0.0.1:3000")?.run().await
}

運行并訪問

ws/webservice on  master [?] is ?? 0.1.0 via ?? 1.67.1 via ?? base took 2h 37m 54.5s 
? cargo run
   Compiling webservice v0.1.0 (/Users/qiaopengjun/rust/ws/webservice)
    Finished dev [unoptimized + debuginfo] target(s) in 1.54s
     Running `/Users/qiaopengjun/rust/ws/target/debug/teacher-service`


ws on  master [?] via ?? 1.67.1 via ?? base 
? curl localhost:3000/health
"I'm Ok. 0 times"%                                                                                           

ws on  master [?] via ?? 1.67.1 via ?? base 
? 

添加課程信息

webservice/src/routers.rs

use super::handlers::*;
use actix_web::web;

pub fn general_routes(cfg: &mut web::ServiceConfig) {
    cfg.route("/health", web::get().to(health_check_handler));
}

pub fn course_routes(cfg: &mut web::ServiceConfig) {
    // courses 是一套資源的根路徑
    cfg.service(web::scope("/courses").route("/", web::post().to(new_course)));
}

webservice/src/bin/teacher-service.rs

use actix_web::{web, App, HttpServer};
use std::io;
use std::sync::Mutex;

#[path = "../handlers.rs"]
mod handlers;
#[path = "../models.rs"]
mod models;
#[path = "../routers.rs"]
mod routers;
#[path = "../state.rs"]
mod state;

use routers::*;
use state::AppState;

#[actix_rt::main]
async fn main() -> io::Result<()> {
    let shared_data = web::Data::new(AppState {
        health_check_response: "I'm Ok.".to_string(),
        visit_count: Mutex::new(0),
        courses: Mutex::new(vec![]),
    });
    let app = move || {
        App::new()
            .app_data(shared_data.clone())
            .configure(general_routes)
            .configure(course_routes) // 路由注冊
    };

    HttpServer::new(app).bind("127.0.0.1:3000")?.run().await
}

webservice/src/handlers.rs

use super::state::AppState;
use actix_web::{web, HttpResponse};

pub async fn health_check_handler(app_state: web::Data<AppState>) -> HttpResponse {
    let health_check_response = &app_state.health_check_response;
    let mut visit_count = app_state.visit_count.lock().unwrap();
    let response = format!("{} {} times", health_check_response, visit_count);
    *visit_count += 1;
    HttpResponse::Ok().json(&response)
}

use super::models::Course;
use chrono::Utc;

pub async fn new_course(
    new_course: web::Json<Course>,
    app_state: web::Data<AppState>,
) -> HttpResponse {
    println!("Received new course");
    let course_count = app_state
        .courses
        .lock()
        .unwrap()
        .clone()
        .into_iter()
        .filter(|course| course.teacher_id == new_course.teacher_id)
        .collect::<Vec<Course>>()
        .len();
    let new_course = Course {
        // 創建一個新的課程
        teacher_id: new_course.teacher_id,
        id: Some(course_count + 1),
        name: new_course.name.clone(),
        time: Some(Utc::now().naive_utc()), // 當前時間
    };
    app_state.courses.lock().unwrap().push(new_course);
    HttpResponse::Ok().json("Course added")
}

#[cfg(test)]
mod tests {
    use super::*;
    use actix_web::http::StatusCode;
    use std::sync::Mutex;

    #[actix_rt::test] // 異步測驗
    async fn post_course_test() {
        let course = web::Json(Course {
            teacher_id: 1,
            name: "Test course".into(),
            id: None,
            time: None,
        });
        let app_state: web::Data<AppState> = web::Data::new(AppState {
            health_check_response: "".to_string(),
            visit_count: Mutex::new(0),
            courses: Mutex::new(vec![]),
        });
        let resp = new_course(course, app_state).await;
        assert_eq!(resp.status(), StatusCode::OK);
    }
}


運行并測驗

ws/webservice on  master [?] is ?? 0.1.0 via ?? 1.67.1 via ?? base took 41m 26.8s 
? cargo test        
   Compiling webservice v0.1.0 (/Users/qiaopengjun/rust/ws/webservice)
    Finished test [unoptimized + debuginfo] target(s) in 0.68s
     Running unittests src/bin/server1.rs (/Users/qiaopengjun/rust/ws/target/debug/deps/server1-db3d08c1708d3a7c)

running 0 tests

test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

     Running unittests src/bin/teacher-service.rs (/Users/qiaopengjun/rust/ws/target/debug/deps/teacher_service-41d6f77eb4f5c36e)

running 1 test
test handlers::tests::post_course_test ... ok

test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

     Running unittests src/main.rs (/Users/qiaopengjun/rust/ws/target/debug/deps/webservice-fd79900ffad88ae5)

running 0 tests

test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s


ws/webservice on  master [?] is ?? 0.1.0 via ?? 1.67.1 via ?? base took 2.7s 
? cargo run 
   Compiling webservice v0.1.0 (/Users/qiaopengjun/rust/ws/webservice)
    Finished dev [unoptimized + debuginfo] target(s) in 1.04s
     Running `/Users/qiaopengjun/rust/ws/target/debug/teacher-service`
Received new course


ws on  master [?] via ?? 1.67.1 via ?? base 
? curl -X POST localhost:3000/courses/ -H "Content-Type: application/json" -d '{"teacher_id":1, "name":"First course"}' 
"Course added"%                                                                                                                   

ws on  master [?] via ?? 1.67.1 via ?? base 
? 

查詢所有課程資訊

webservice/src/routers.rs

use super::handlers::*;
use actix_web::web;

pub fn general_routes(cfg: &mut web::ServiceConfig) {
    cfg.route("/health", web::get().to(health_check_handler));
}

pub fn course_routes(cfg: &mut web::ServiceConfig) {
    // courses 是一套資源的根路徑
    cfg.service(
        web::scope("/courses")
            .route("/", web::post().to(new_course))
            .route("/{user_id}", web::get().to(get_courses_for_tescher)),
    );
}

webservice/src/handlers.rs

use super::state::AppState;
use actix_web::{web, HttpResponse};

pub async fn health_check_handler(app_state: web::Data<AppState>) -> HttpResponse {
    let health_check_response = &app_state.health_check_response;
    let mut visit_count = app_state.visit_count.lock().unwrap();
    let response = format!("{} {} times", health_check_response, visit_count);
    *visit_count += 1;
    HttpResponse::Ok().json(&response)
}

use super::models::Course;
use chrono::Utc;

pub async fn new_course(
    new_course: web::Json<Course>,
    app_state: web::Data<AppState>,
) -> HttpResponse {
    println!("Received new course");
    let course_count = app_state
        .courses
        .lock()
        .unwrap()
        .clone()
        .into_iter()
        .filter(|course| course.teacher_id == new_course.teacher_id)
        .collect::<Vec<Course>>()
        .len();
    let new_course = Course {
        // 創建一個新的課程
        teacher_id: new_course.teacher_id,
        id: Some(course_count + 1),
        name: new_course.name.clone(),
        time: Some(Utc::now().naive_utc()), // 當前時間
    };
    app_state.courses.lock().unwrap().push(new_course);
    HttpResponse::Ok().json("Course added")
}

pub async fn get_courses_for_tescher(
    app_state: web::Data<AppState>,
    params: web::Path<(usize)>,
) -> HttpResponse {
    let teacher_id: usize = params.into_inner();

    let filtered_courses = app_state
        .courses
        .lock()
        .unwrap()
        .clone()
        .into_iter()
        .filter(|course| course.teacher_id == teacher_id)
        .collect::<Vec<Course>>();

    if filtered_courses.len() > 0 {
        HttpResponse::Ok().json(filtered_courses)
    } else {
        HttpResponse::Ok().json("No courses found for teacher".to_string())
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use actix_web::http::StatusCode;
    use std::sync::Mutex;

    #[actix_rt::test] // 異步測驗
    async fn post_course_test() {
        let course = web::Json(Course {
            teacher_id: 1,
            name: "Test course".into(),
            id: None,
            time: None,
        });
        let app_state: web::Data<AppState> = web::Data::new(AppState {
            health_check_response: "".to_string(),
            visit_count: Mutex::new(0),
            courses: Mutex::new(vec![]),
        });
        let resp = new_course(course, app_state).await;
        assert_eq!(resp.status(), StatusCode::OK);
    }

    #[actix_rt::test]
    async fn get_all_course_success() {
        let app_state: web::Data<AppState> = web::Data::new(AppState {
            health_check_response: "".to_string(),
            visit_count: Mutex::new(0),
            courses: Mutex::new(vec![]),
        });
        let teacher_id: web::Path<(usize)> = web::Path::from((1));
        let resp = get_courses_for_tescher(app_state, teacher_id).await;
        assert_eq!(resp.status(), StatusCode::OK);
    }
}

測驗

ws/webservice on  master [?] is ?? 0.1.0 via ?? 1.67.1 via ?? base took 1m 1.8s 
? cargo test
   Compiling webservice v0.1.0 (/Users/qiaopengjun/rust/ws/webservice)
warning: unnecessary parentheses around type
  --> webservice/src/bin/../handlers.rs:42:23
   |
42 |     params: web::Path<(usize)>,
   |                       ^     ^
   |
   = note: `#[warn(unused_parens)]` on by default
help: remove these parentheses
   |
42 -     params: web::Path<(usize)>,
42 +     params: web::Path<usize>,
   |

warning: unnecessary parentheses around type
  --> webservice/src/bin/../handlers.rs:92:35
   |
92 |         let teacher_id: web::Path<(usize)> = web::Path::from((1));
   |                                   ^     ^
   |
help: remove these parentheses
   |
92 -         let teacher_id: web::Path<(usize)> = web::Path::from((1));
92 +         let teacher_id: web::Path<usize> = web::Path::from((1));
   |

warning: unnecessary parentheses around function argument
  --> webservice/src/bin/../handlers.rs:92:62
   |
92 |         let teacher_id: web::Path<(usize)> = web::Path::from((1));
   |                                                              ^ ^
   |
help: remove these parentheses
   |
92 -         let teacher_id: web::Path<(usize)> = web::Path::from((1));
92 +         let teacher_id: web::Path<(usize)> = web::Path::from(1);
   |

warning: `webservice` (bin "teacher-service" test) generated 3 warnings (run `cargo fix --bin "teacher-service" --tests` to apply 3 suggestions)
    Finished test [unoptimized + debuginfo] target(s) in 0.60s
     Running unittests src/bin/server1.rs (/Users/qiaopengjun/rust/ws/target/debug/deps/server1-db3d08c1708d3a7c)

running 0 tests

test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

     Running unittests src/bin/teacher-service.rs (/Users/qiaopengjun/rust/ws/target/debug/deps/teacher_service-41d6f77eb4f5c36e)

running 2 tests
test handlers::tests::post_course_test ... ok
test handlers::tests::get_all_course_success ... ok

test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

     Running unittests src/main.rs (/Users/qiaopengjun/rust/ws/target/debug/deps/webservice-fd79900ffad88ae5)

running 0 tests

test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s


ws/webservice on  master [?] is ?? 0.1.0 via ?? 1.67.1 via ?? base 
? 


ws/webservice on  master [?] is ?? 0.1.0 via ?? 1.67.1 via ?? base 
? cargo run 
   Compiling webservice v0.1.0 (/Users/qiaopengjun/rust/ws/webservice)
warning: unnecessary parentheses around type
  --> webservice/src/bin/../handlers.rs:42:23
   |
42 |     params: web::Path<(usize)>,
   |                       ^     ^
   |
   = note: `#[warn(unused_parens)]` on by default
help: remove these parentheses
   |
42 -     params: web::Path<(usize)>,
42 +     params: web::Path<usize>,
   |

warning: `webservice` (bin "teacher-service") generated 1 warning (run `cargo fix --bin "teacher-service"` to apply 1 suggestion)
    Finished dev [unoptimized + debuginfo] target(s) in 1.54s
     Running `/Users/qiaopengjun/rust/ws/target/debug/teacher-service`
Received new course
Received new course
Received new course



ws on  master [?] via ?? 1.67.1 via ?? base 
? curl -X POST localhost:3000/courses/ -H "Content-Type: application/json" -d '{"teacher_id":1, "name":"First course"}'
"Course added"%                                                                                                                   

ws on  master [?] via ?? 1.67.1 via ?? base 
? curl -X POST localhost:3000/courses/ -H "Content-Type: application/json" -d '{"teacher_id":1, "name":"Second course"}'
"Course added"%                                                                                                                   

ws on  master [?] via ?? 1.67.1 via ?? base 
? curl -X POST localhost:3000/courses/ -H "Content-Type: application/json" -d '{"teacher_id":1, "name":"Third course"}' 
"Course added"%                                                                                                                   

ws on  master [?] via ?? 1.67.1 via ?? base 
? curl localhost:3000/courses/1                                                                                        
[{"teacher_id":1,"id":1,"name":"First course","time":"2023-05-28T11:16:50.312820"},{"teacher_id":1,"id":2,"name":"Second course","time":"2023-05-28T11:17:08.358168"},{"teacher_id":1,"id":3,"name":"Third course","time":"2023-05-28T11:17:23.295881"}]%           

ws on  master [?] via ?? 1.67.1 via ?? base 
? 

查詢單個課程資訊

webservice/src/routers.rs

use super::handlers::*;
use actix_web::web;

pub fn general_routes(cfg: &mut web::ServiceConfig) {
    cfg.route("/health", web::get().to(health_check_handler));
}

pub fn course_routes(cfg: &mut web::ServiceConfig) {
    // courses 是一套資源的根路徑
    cfg.service(
        web::scope("/courses")
            .route("/", web::post().to(new_course))
            .route("/{user_id}", web::get().to(get_courses_for_tescher))
            .route("/{user_id}/{course_id}", web::get().to(get_courses_detail)),
    );
}

webservice/src/handlers.rs

use super::state::AppState;
use actix_web::{web, HttpResponse};

pub async fn health_check_handler(app_state: web::Data<AppState>) -> HttpResponse {
    let health_check_response = &app_state.health_check_response;
    let mut visit_count = app_state.visit_count.lock().unwrap();
    let response = format!("{} {} times", health_check_response, visit_count);
    *visit_count += 1;
    HttpResponse::Ok().json(&response)
}

use super::models::Course;
use chrono::Utc;

pub async fn new_course(
    new_course: web::Json<Course>,
    app_state: web::Data<AppState>,
) -> HttpResponse {
    println!("Received new course");
    let course_count = app_state
        .courses
        .lock()
        .unwrap()
        .clone()
        .into_iter()
        .filter(|course| course.teacher_id == new_course.teacher_id)
        .collect::<Vec<Course>>()
        .len();
    let new_course = Course {
        // 創建一個新的課程
        teacher_id: new_course.teacher_id,
        id: Some(course_count + 1),
        name: new_course.name.clone(),
        time: Some(Utc::now().naive_utc()), // 當前時間
    };
    app_state.courses.lock().unwrap().push(new_course);
    HttpResponse::Ok().json("Course added")
}

pub async fn get_courses_for_tescher(
    app_state: web::Data<AppState>,
    params: web::Path<usize>,
) -> HttpResponse {
    let teacher_id: usize = params.into_inner();

    let filtered_courses = app_state
        .courses
        .lock()
        .unwrap()
        .clone()
        .into_iter()
        .filter(|course| course.teacher_id == teacher_id)
        .collect::<Vec<Course>>();

    if filtered_courses.len() > 0 {
        HttpResponse::Ok().json(filtered_courses)
    } else {
        HttpResponse::Ok().json("No courses found for teacher".to_string())
    }
}

pub async fn get_courses_detail(
    app_state: web::Data<AppState>,
    params: web::Path<(usize, usize)>,
) -> HttpResponse {
    let (teacher_id, course_id) = params.into_inner();
    let selected_course = app_state
        .courses
        .lock()
        .unwrap()
        .clone()
        .into_iter()
        .find(|x| x.teacher_id == teacher_id && x.id == Some(course_id))
        .ok_or("Course not found"); // Option 型別 轉化成 Result<T, E> 型別

    if let Ok(course) = selected_course {
        HttpResponse::Ok().json(course)
    } else {
        HttpResponse::Ok().json("Course not found".to_string())
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use actix_web::http::StatusCode;
    use std::sync::Mutex;

    #[actix_rt::test] // 異步測驗
    async fn post_course_test() {
        let course = web::Json(Course {
            teacher_id: 1,
            name: "Test course".into(),
            id: None,
            time: None,
        });
        let app_state: web::Data<AppState> = web::Data::new(AppState {
            health_check_response: "".to_string(),
            visit_count: Mutex::new(0),
            courses: Mutex::new(vec![]),
        });
        let resp = new_course(course, app_state).await;
        assert_eq!(resp.status(), StatusCode::OK);
    }

    #[actix_rt::test]
    async fn get_all_course_success() {
        let app_state: web::Data<AppState> = web::Data::new(AppState {
            health_check_response: "".to_string(),
            visit_count: Mutex::new(0),
            courses: Mutex::new(vec![]),
        });
        let teacher_id: web::Path<usize> = web::Path::from(1);
        let resp = get_courses_for_tescher(app_state, teacher_id).await;
        assert_eq!(resp.status(), StatusCode::OK);
    }

    #[actix_rt::test]
    async fn get_one_course_success() {
        let app_state: web::Data<AppState> = web::Data::new(AppState {
            health_check_response: "".to_string(),
            visit_count: Mutex::new(0),
            courses: Mutex::new(vec![]),
        });
        let params: web::Path<(usize, usize)> = web::Path::from((1, 1));
        let resp = get_courses_detail(app_state, params).await;
        assert_eq!(resp.status(), StatusCode::OK);
    }
}

運行測驗

ws/webservice on  master [?] is ?? 0.1.0 via ?? 1.67.1 via ?? base took 18m 27.8s 
? cargo test
   Compiling webservice v0.1.0 (/Users/qiaopengjun/rust/ws/webservice)
    Finished test [unoptimized + debuginfo] target(s) in 1.02s
     Running unittests src/bin/server1.rs (/Users/qiaopengjun/rust/ws/target/debug/deps/server1-db3d08c1708d3a7c)

running 0 tests

test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

     Running unittests src/bin/teacher-service.rs (/Users/qiaopengjun/rust/ws/target/debug/deps/teacher_service-41d6f77eb4f5c36e)

running 3 tests
test handlers::tests::get_one_course_success ... ok
test handlers::tests::get_all_course_success ... ok
test handlers::tests::post_course_test ... ok

test result: ok. 3 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

     Running unittests src/main.rs (/Users/qiaopengjun/rust/ws/target/debug/deps/webservice-fd79900ffad88ae5)

running 0 tests

test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s


ws/webservice on  master [?] is ?? 0.1.0 via ?? 1.67.1 via ?? base 
? cargo run                                                                                                             
   Compiling webservice v0.1.0 (/Users/qiaopengjun/rust/ws/webservice)
    Finished dev [unoptimized + debuginfo] target(s) in 0.86s
     Running `/Users/qiaopengjun/rust/ws/target/debug/teacher-service`
Received new course
Received new course


ws on  master [?] via ?? 1.67.1 via ?? base 
? curl -X POST localhost:3000/courses/ -H "Content-Type: application/json" -d '{"teacher_id":1, "name":"First course"}' 
"Course added"%                                                                                                                   

ws on  master [?] via ?? 1.67.1 via ?? base 
? curl -X POST localhost:3000/courses/ -H "Content-Type: application/json" -d '{"teacher_id":1, "name":"Second course"}'
"Course added"%                                                                                                                   

ws on  master [?] via ?? 1.67.1 via ?? base 
? curl localhost:3000/courses/1/1                                                                                       
{"teacher_id":1,"id":1,"name":"First course","time":"2023-05-28T11:35:49.260822"}%                                                

ws on  master [?] via ?? 1.67.1 via ?? base 
? 

本文來自博客園,作者:QIAOPENGJUN,轉載請注明原文鏈接:https://www.cnblogs.com/QiaoPengjun/p/17438730.html

轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/553641.html

標籤:其他

上一篇:QT 繪制波形圖、頻譜圖、瀑布圖、星座圖、眼圖、語圖

下一篇:返回列表

標籤雲
其他(159869) Python(38178) JavaScript(25460) Java(18141) C(15232) 區塊鏈(8268) C#(7972) AI(7469) 爪哇(7425) MySQL(7214) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5873) 数组(5741) R(5409) Linux(5343) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4577) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2434) ASP.NET(2403) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) .NET技术(1977) 功能(1967) Web開發(1951) HtmlCss(1948) C++(1924) python-3.x(1918) 弹簧靴(1913) xml(1889) PostgreSQL(1878) .NETCore(1862) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【C++】Microsoft C++、C 和匯編程式檔案

    ......

    uj5u.com 2020-09-10 00:57:23 more
  • 例外宣告

    相比于斷言適用于排除邏輯上不可能存在的狀態,例外通常是用于邏輯上可能發生的錯誤。 例外宣告 Item 1:當函式不可能拋出例外或不能接受拋出例外時,使用noexcept 理由 如果不打算拋出例外的話,程式就會認為無法處理這種錯誤,并且應當盡早終止,如此可以有效地阻止例外的傳播與擴散。 示例 //不可 ......

    uj5u.com 2020-09-10 00:57:27 more
  • Codeforces 1400E Clear the Multiset(貪心 + 分治)

    鏈接:https://codeforces.com/problemset/problem/1400/E 來源:Codeforces 思路:給你一個陣列,現在你可以進行兩種操作,操作1:將一段沒有 0 的區間進行減一的操作,操作2:將 i 位置上的元素歸零。最終問:將這個陣列的全部元素歸零后操作的最少 ......

    uj5u.com 2020-09-10 00:57:30 more
  • UVA11610 【Reverse Prime】

    本人看到此題沒有翻譯,就附帶了一個自己的翻譯版本 思考 這一題,它的第一個要求是找出所有 $7$ 位反向質數及其質因數的個數。 我們應該需要質數篩篩選1~$10^{7}$的所有數,這里就不慢慢介紹了。但是,重讀題,我們突然發現反向質數都是 $7$ 位,而將它反過來后的數字卻是 $6$ 位數,這就說明 ......

    uj5u.com 2020-09-10 00:57:36 more
  • 統計區間素數數量

    1 #pragma GCC optimize(2) 2 #include <bits/stdc++.h> 3 using namespace std; 4 bool isprime[1000000010]; 5 vector<int> prime; 6 inline int getlist(int ......

    uj5u.com 2020-09-10 00:57:47 more
  • C/C++編程筆記:C++中的 const 變數詳解,教你正確認識const用法

    1、C中的const 1、區域const變數存放在堆疊區中,會分配記憶體(也就是說可以通過地址間接修改變數的值)。測驗代碼如下: 運行結果: 2、全域const變數存放在只讀資料段(不能通過地址修改,會發生寫入錯誤), 默認為外部聯編,可以給其他源檔案使用(需要用extern關鍵字修飾) 運行結果: ......

    uj5u.com 2020-09-10 00:58:04 more
  • 【C++犯錯記錄】VS2019 MFC添加資源不懂如何修改資源宏ID

    1. 首先在資源視圖中,添加資源 2. 點擊新添加的資源,復制自動生成的ID 3. 在解決方案資源管理器中找到Resource.h檔案,編輯,使用整個專案搜索和替換的方式快速替換 宏宣告 4. Ctrl+Shift+F 全域搜索,點擊查找全部,然后逐個替換 5. 為什么使用搜索替換而不使用屬性視窗直 ......

    uj5u.com 2020-09-10 00:59:11 more
  • 【C++犯錯記錄】VS2019 MFC不懂的批量添加資源

    1. 打開資源頭檔案Resource.h,在其中預先定義好宏 ID(不清楚其實ID值應該設定多少,可以先新建一個相同的資源項,再在這個資源的ID值的基礎上遞增即可) 2. 在資源視圖中選中專案資源,按F7編輯資源檔案,按 ID 型別 相對路徑的形式添加 資源。(別忘了先把檔案拷貝到專案中的res檔案 ......

    uj5u.com 2020-09-10 01:00:19 more
  • C/C++編程筆記:關于C++的參考型別,專供新手入門使用

    今天要講的是C++中我最喜歡的一個用法——參考,也叫別名。 參考就是給一個變數名取一個變數名,方便我們間接地使用這個變數。我們可以給一個變數創建N個參考,這N + 1個變數共享了同一塊記憶體區域。(參考型別的變數會占用記憶體空間,占用的記憶體空間的大小和指標型別的大小是相同的。雖然參考是一個物件的別名,但 ......

    uj5u.com 2020-09-10 01:00:22 more
  • 【C/C++編程筆記】從頭開始學習C ++:初學者完整指南

    眾所周知,C ++的學習曲線陡峭,但是花時間學習這種語言將為您的職業帶來奇跡,并使您與其他開發人員區分開。您會更輕松地學習新語言,形成真正的解決問題的技能,并在編程的基礎上打下堅實的基礎。 C ++將幫助您養成良好的編程習慣(即清晰一致的編碼風格,在撰寫代碼時注釋代碼,并限制類內部的可見性),并且由 ......

    uj5u.com 2020-09-10 01:00:41 more
最新发布
  • Rust Web 全堆疊開發之 Actix 嘗鮮并構建REST API

    # Rust Web 全堆疊開發之 Actix 嘗鮮并構建REST API ## 一、Actix 嘗鮮 ### 需要使用的crate - actix-web v4.3.1 - actix-rt v2.8.0 ```bash ~ via 🅒 base ? cd rust ~/rust via 🅒 b ......

    uj5u.com 2023-05-29 07:47:16 more
  • QT 繪制波形圖、頻譜圖、瀑布圖、星座圖、眼圖、語圖

    最近在學中頻信號處理的一些東西,順便用 QT 寫了一個小工具,可以顯示信號的時域波形圖、幅度譜、功率譜、二次方譜、四次方譜、八次方譜、瞬時包絡、瞬時頻率、瞬時相位、非線性瞬時相位、瞬時幅度直方圖、瞬時頻率直方圖、瞬時相位直方圖、眼圖、星座圖、語譜圖、瀑布圖。 ......

    uj5u.com 2023-05-29 07:46:15 more
  • 【VS Code+Qt6】拖放操作

    由于老周的示例代碼都是用 VS Code + CMake + Qt 寫的,為了不誤匯入,在標題中還是加上“VS Code”好一些。 上次咱們研究了剪貼板的基本用法,也了解了叫 QMimeData 的重要類。為啥要強調這個類?因為接下來扯到的拖放操作也是和它有關系。哦,對了,咱們先避開一下主題,關于剪 ......

    uj5u.com 2023-05-29 07:45:36 more
  • Python asyncio之協程學習總結

    ## 實踐環境 Python 3.6.2 ## 什么是協程 **協程**(Coroutine)一種電腦程式組件,該程式組件通過允許暫停和恢復任務,為非搶占式多任務生成子程式。**協程**也可以簡單理解為協作的程式,通過協同多任務處理實作并發的函式的變種(一種可以支持中斷的函式)。 下面,我們通過日常 ......

    uj5u.com 2023-05-29 07:45:19 more
  • Java 網路編程 —— 創建非阻塞的 HTTP 服務器

    ## HTTP 概述 HTTP 客戶程式必須先發出一個 HTTP 請求,然后才能接收到來自 HTTP 服器的回應,瀏覽器就是最常見的 HTTP 客戶程式。HTTP 客戶程式和 HTTP 服務器分別由不同的軟體開發商提供,它們都可以用任意的編程語言撰寫。HTTP 嚴格規定了 HTTP 請求和 HTTP ......

    uj5u.com 2023-05-29 07:45:07 more
  • 527訓練總結

    訓練內容:2023江西省賽VP 賽后總結: 比賽程序: 做了簽到以后純純開始坐牢...... 策略失誤: I題被定位成簽到題也過了十四個人,但是后續沒有花更多的時間去看,一直在鉆“如何存盤圖上路徑”的牛角尖,沒有往“存在巧妙解法”這個角度思考。另外寫dfs的假解法的程序中發現對vector的基本洗掉 ......

    uj5u.com 2023-05-29 07:45:03 more
  • FreeSWITCH添加自定義endpoint

    作業系統 :CentOS 7.6_x64 FreeSWITCH版本 :1.10.9 日常開發程序中會遇到需要擴展FreeSWITCH對接其它系統的情況,這里記錄下撰寫FreeSWITCH自定義endpoint的程序。 一、模塊定義函式 使用FreeSWITCH自帶的框架來定義模塊函式,函式指標及引數 ......

    uj5u.com 2023-05-29 07:44:41 more
  • 【python基礎】基本資料型別-字串型別

    # 1.初識字串 字串就是一系列字符。在python中,用引號括起來文本內容的都是字串。 其語法格式為:‘文本內容’或者“文本內容” 我們發現其中的引號可以是單引號,也可以是雙引號。這樣的靈活性可以使我們進行引號之間的嵌套。 撰寫程式如下所示: ![image](https://img2023 ......

    uj5u.com 2023-05-29 07:44:15 more
  • Rust Web 全堆疊開發之自建TCP、HTTP Server

    # Rust Web 全堆疊開發之自建TCP、HTTP Server ## 課程簡介 ### 預備知識 - Rust 編程語言入門 - https://www.bilibili.com/video/BV1hp4y1k7SV ### 課程主要內容 - WebService - 服務器端Web App - ......

    uj5u.com 2023-05-29 07:43:58 more
  • Python 標準類別庫-因特網資料處理之Base64資料編碼

    該模塊提供將二進制資料編碼為可列印ASCII字符并將這種編碼解碼回二進制資料的功能。它為[RFC 3548](https://tools.ietf.org/html/rfc3548.html)中指定的編碼提供編碼和解碼功能。定義了Base16、Base32和Base64演算法,以及事實上的標準Asci ......

    uj5u.com 2023-05-29 07:43:47 more