Rust 的型別系統和所有權機制在處理非同步操作時,能有效避免資料競爭和記憶體安全問題。本文示範如何使用 Actix Web 客戶端傳送 HTTP PUT 請求到後端服務,更新課程資源。程式碼中,UpdateCourse 結構體定義了可更新的欄位,Option<T> 允許部分更新。handle_update_course 函式則處理請求,使用 awc::Client 傳送 PUT 請求,並將結果解析為 UpdateCourseResponse 傳回。此外,文章也說明瞭如何使用 curl 測試更新功能,並提供 Plantuml 語法繪製的流程圖,清楚展示客戶端、伺服器和資料函式庫的互動。非同步程式設計部分則解釋了平行性與平行性的概念,並以 Rust 程式碼示例說明如何使用 async/awaitFuture 處理非同步操作,以及如何自定義 Future

更新課程資源的實作細節

在前一章節中,我們實作了新增課程的功能。本章節將著重於更新課程資源的實作,使用HTTP PUT方法來達成此功能。

更新課程的資料結構定義

首先,我們需要在$PROJECT_ROOT/src/iter6/model.rs檔案中定義更新課程所需的資料結構。

// 更新課程
#[derive(Deserialize, Serialize, Debug, Clone)]
pub struct UpdateCourse {
    pub course_name: Option<String>,
    pub course_description: Option<String>,
    pub course_format: Option<String>,
    pub course_duration: Option<String>,
    pub course_structure: Option<String>,
    pub course_price: Option<i32>,
    pub course_language: Option<String>,
    pub course_level: Option<String>,
}

#[derive(Deserialize, Serialize, Debug, Clone)]
pub struct UpdateCourseResponse {
    pub course_id: i32,
    pub tutor_id: i32,
    pub course_name: String,
    pub course_description: String,
    pub course_format: String,
    pub course_structure: String,
    pub course_duration: String,
    pub course_price: i32,
    pub course_language: String,
    pub course_level: String,
    pub posted_time: String,
}

impl From<web::Json<UpdateCourseResponse>> for UpdateCourseResponse {
    fn from(new_course: web::Json<UpdateCourseResponse>) -> Self {
        UpdateCourseResponse {
            tutor_id: new_course.tutor_id,
            course_id: new_course.course_id,
            course_name: new_course.course_name.clone(),
            course_description: new_course.course_description.clone(),
            course_format: new_course.course_format.clone(),
            course_structure: new_course.course_structure.clone(),
            course_duration: new_course.course_duration.clone(),
            course_price: new_course.course_price,
            course_language: new_course.course_language.clone(),
            course_level: new_course.course_level.clone(),
            posted_time: new_course.posted_time.clone(),
        }
    }
}

內容解密:

  • UpdateCourse結構體用於捕捉使用者修改的課程資訊。Option<T>型別表示並非所有課程資訊都必須在更新請求中傳送。
  • UpdateCourseResponse結構體用於儲存從導師Web服務接收到的課程更新請求的資料。
  • 實作From特徵以將從導師Web服務接收到的JSON資料轉換為Rust的UpdateCourseResponse結構體。對於字串型別(堆積分配)的欄位進行克隆,而整數型別(堆積疊分配)的欄位則不需要克隆。

更新課程的處理函式

接下來,我們需要修改處理函式來處理更新課程的請求。

pub async fn handle_update_course(
    _tmpl: web::Data<tera::Tera>,
    _app_state: web::Data<AppState>,
    path: web::Path<(i32, i32)>,
    params: web::Json<UpdateCourse>,
) -> Result<HttpResponse, Error> {
    let (tutor_id, course_id) = path.into_inner();
    let update_course = json!({
        "course_name": &params.course_name,
        "course_description": &params.course_description,
        "course_format": &params.course_format,
        "course_duration": &params.course_duration,
        "course_structure": &params.course_structure,
        "course_price": &params.course_price,
        "course_language": &params.course_language,
        "course_level": &params.course_level,
    });
    let awc_client = awc::Client::default();
    let res = awc_client
        .put(format!("http://localhost:3000/courses/{}/{}", tutor_id, course_id))
        .send_json(&update_course)
        .await
        .unwrap()
        .body()
        .await?;
    let course_response: UpdateCourseResponse = serde_json::from_str(&std::str::from_utf8(&res)?)?;
    Ok(HttpResponse::Ok().json(course_response))
}

內容解密:

  • 從路徑引數中提取tutor_idcourse_id
  • 從請求的JSON資料中構建一個新的JSON物件,用於更新課程。
  • 使用Actix Web客戶端傳送HTTP PUT請求到導師Web服務,以更新指定的課程。
  • 將從導師Web服務接收到的JSON資料轉換為Rust的UpdateCourseResponse結構體。
  • 將更新後的課程資料傳回給發起請求的HTTP客戶端。

圖表翻譯:

此圖示顯示了更新課程資源的流程,包括客戶端傳送更新請求、伺服器處理請求並更新資料函式庫中的課程資訊,以及伺服器傳回更新後的課程資料給客戶端。

@startuml
note
  無法自動轉換的 Plantuml 圖表
  請手動檢查和調整
@enduml

圖表翻譯: 此圖示呈現了客戶端傳送HTTP PUT請求以更新課程資料,伺服器處理請求並更新資料函式庫中的課程資訊,然後伺服器傳回更新後的課程資料給客戶端的整個過程。

測試更新課程功能

要測試更新課程功能,首先確保導師Web服務正在執行。然後,從另一個終端機執行以下命令:

curl -X PUT localhost:8080/courses/1/1 -d '{"course_name":"Updated Rust web development"}' -H "Content-Type: application/json"

驗證課程是否已更新,可以對導師Web服務執行GET請求:

curl localhost:3000/courses/1

您應該能夠在傳回的課程列表中看到更新後的課程資訊。

Rust 網頁應用程式開發:與後端服務互動的完整

在前面的章節中,我們已經學習瞭如何使用 Rust 開發網頁應用程式。在本章中,我們將探討如何建立一個與後端網頁服務互動的網頁客戶端前端。我們將使用 Actix-web 框架和 Tera 範本引擎來建立一個功能齊全的網頁應用程式。

建立課程資源的處理函式

首先,我們來看看如何建立一個處理函式來更新課程詳情。在 $PROJECT_ROOT/src/iter6/handler/course.rs 中,我們定義了一個名為 handle_update_course 的函式:

pub async fn handle_update_course(
    _tmpl: web::Data<tera::Tera>,
    _app_state: web::Data<AppState>,
    web::Path((tutor_id, course_id)): web::Path<(i32, i32)>,
    params: web::Json<UpdateCourse>,
) -> Result<HttpResponse, Error> {
    // ...
}

內容解密:

此函式使用 Actix-web 的 extractor 功能來提取路徑引數 tutor_idcourse_id,以及 JSON 請求體中的 UpdateCourse 資料結構。它使用 Actix HTTP 客戶端向 tutor 網頁服務傳送 HTTP PUT 請求,以更新課程詳情。

更新課程詳情的 HTTP 請求

handle_update_course 函式中,我們使用 Actix HTTP 客戶端向 tutor 網頁服務傳送 HTTP PUT 請求:

let awc_client = awc::Client::default();
let update_url = format!("http://localhost:3000/courses/{}/{}", tutor_id, course_id);
let res = awc_client.put(update_url).send_json(&update_course).await.unwrap().body().await?;

內容解密:

此程式碼片段建立了一個 Actix HTTP 客戶端例項,並使用它向 tutor 網頁服務傳送 HTTP PUT 請求,以更新課程詳情。請求體中包含了更新後的課程資料。

刪除課程資源的處理函式

接下來,我們來看看如何建立一個處理函式來刪除課程。在 $PROJECT_ROOT/src/iter6/handler/course.rs 中,我們定義了一個名為 handle_delete_course 的函式:

pub async fn handle_delete_course(
    _tmpl: web::Data<tera::Tera>,
    _app_state: web::Data<AppState>,
    path: web::Path<(i32, i32)>,
) -> Result<HttpResponse, Error> {
    // ...
}

內容解密:

此函式使用 Actix-web 的 extractor 功能來提取路徑引數 tutor_idcourse_id。它使用 Actix HTTP 客戶端向 tutor 網頁服務傳送 HTTP DELETE 請求,以刪除指定的課程。

傳送 DELETE 請求

handle_delete_course 函式中,我們使用 Actix HTTP 客戶端向 tutor 網頁服務傳送 HTTP DELETE 請求:

let awc_client = awc::Client::default();
let delete_url = format!("http://localhost:3000/courses/{}/{}", tutor_id, course_id);
let _res = awc_client.delete(delete_url).send().await.unwrap();

內容解密:

此程式碼片段建立了一個 Actix HTTP 客戶端例項,並使用它向 tutor 網頁服務傳送 HTTP DELETE 請求,以刪除指定的課程。

在下一章中,我們將探討 Rust 中非同步伺服器的進階主題。敬請期待!

@startuml
skinparam backgroundColor #FEFEFE
skinparam defaultTextAlignment center
skinparam rectangleBackgroundColor #F5F5F5
skinparam rectangleBorderColor #333333
skinparam arrowColor #333333

title 內容解密:

rectangle "HTTP 請求" as node1
rectangle "HTTP 回應" as node2

node1 --> node2

@enduml

圖表翻譯: 此圖表展示了客戶端、Actix-web 伺服器和 tutor 網頁服務之間的互動流程。客戶端向 Actix-web 伺服器傳送 HTTP 請求,伺服器再將請求轉發給 tutor 網頁服務。tutor 網頁服務處理請求後傳回 HTTP 回應給 Actix-web 伺服器,伺服器再將回應傳回給客戶端。

深入理解非同步Rust程式設計

本章節將探討非同步程式設計的概念、撰寫平行程式、探討非同步Rust、理解Futures以及實作自定義的Future。在前面的章節中,我們使用Rust建立了一個網頁服務和網頁應用程式,並利用Actix Web框架處理網路通訊。然而,當數十或數百名使用者同時傳送請求註冊導師或課程時,伺服器會如何處理?或者,更廣泛地說,現代網頁伺服器如何處理數以萬計的平行請求?請繼續閱讀以瞭解詳情。

介紹非同步程式設計概念

在電腦科學中,平行性(Concurrency)是指程式的不同部分可以無序執行或同時執行,而不影響最終結果。嚴格來說,無序執行程式的一部分是平行性,而同時執行多個任務是平行性(Parallelism)。圖10.1說明瞭這兩者的區別,但在本章中,我們將使用平行性一詞來廣泛指代這兩個方面。在實踐中,兩者通常結合使用,以實作同時處理多個請求的高效和安全結果。

圖10.1:平行性 vs. 平行性

此圖示說明瞭平行性和平行性的區別。

圖表翻譯: 圖10.1展示了平行性和平行性的概念。左側代表平行性,表示任務可以交錯執行但不一定同時執行。右側代表平行性,表示多個任務可以同時在不同的處理器或核心上執行。

你可能會想,為什麼要無序執行程式的一部分?畢竟,程式應該從上到下逐條執行,對吧?

使用平行程式設計有兩個主要驅動力:需求側和供應側。

  • 需求側:在使用者需求方面,對程式執行速度的期望促使軟體開發人員考慮使用平行程式設計技術。
  • 供應側:在硬體供應方面,多核處理器和多核心CPU的可用性為軟體開發人員提供了機會,讓他們能夠編寫能夠利用多核心和處理器來加快整體執行速度和提高效率的程式。

為什麼需要非同步程式設計?

非同步程式設計使開發人員能夠充分利用運算資源,特別是在資料處理活動的時間變化很大或系統存在延遲的情況下。對於分散式系統而言,非同步程式設計尤為重要。

平行程式設計技術

在Rust中,可以使用多執行緒或非同步程式設計來實作平行性。多執行緒涉及建立多個執行緒,每個執行緒執行程式的一部分。非同步程式設計則涉及使用非同步函式和Futures來實作平行性。

Rust中的非同步原語

Rust提供了豐富的非同步原語,包括Futures、async/await等,使開發人員能夠編寫高效的非同步程式。

Futures

Futures是Rust中的一種非同步原語,代表了一個可能尚未完成的計算結果。可以使用async函式建立Futures,並使用await等待其完成。

async fn my_async_function() -> i32 {
    // 非同步運算
    42
}

#[tokio::main]
async fn main() {
    let result = my_async_function().await;
    println!("Result: {}", result);
}

內容解密:

這段程式碼展示瞭如何在Rust中使用非同步函式和Futures。my_async_function是一個非同步函式,傳回一個i32值。在main函式中,我們使用await等待my_async_function的結果,並列印出來。

實作自定義Future

除了使用Rust提供的非同步原語外,還可以實作自定義的Future。這需要實作Future trait,並提供poll方法的實作。

use std::pin::Pin;
use std::task::{Context, Poll};
use std::future::Future;

struct MyFuture {
    // 自定義Future的狀態
}

impl Future for MyFuture {
    type Output = i32;

    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
        // 實作poll方法
        Poll::Ready(42)
    }
}

#[tokio::main]
async fn main() {
    let my_future = MyFuture { /* 初始化 */ };
    let result = my_future.await;
    println!("Result: {}", result);
}

內容解密:

這段程式碼展示瞭如何實作自定義的Future。我們定義了一個MyFuture結構體,並為其實作了Future trait。在poll方法中,我們傳回了一個Poll::Ready值,表示計算已經完成。在main函式中,我們使用await等待自定義Future的結果,並列印出來。