iT邦幫忙

2023 iThome 鐵人賽

DAY 3
1
Software Development

Rust Web API 從零開始系列 第 3

Day03 - 淺談Axum

  • 分享至 

  • xImage
  •  

初始化專案

在開始之前先快速介紹如何初始化一個Rust專案:

//// 初始化一個binary專案,也可省略--bin
cargo init hello_world --bin

//// 這會生成以下的檔案結構
//// .
//// ├── Cargo.toml
//// └── src
////     └── main.rs

cargo init初始化了一個名為hello_world的binary專案,如果要初始化函式庫專案,則需要使用--lib選項,這樣src目錄下會生成一個lib.rs檔案。打開Cargo.toml檔案會看到以下內容:

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

[dependencies]

在Cargo專案會使用toml文件來管理專案的依賴。目前這是一個空的專案,因此[dependencies]區段是空白的。接下來需要添加Axum的依賴。請在terminal中輸入以下命令:

cargo add axum tokio --features tokio/full

這時候就會看到Cargo.toml的[dependencies]下多了幾行:

[dependencies]
axum = "0.6.20"
tokio = { version = "1.32.0", features = ["full"] }

也可以透過直接編輯toml檔的方式將依賴加到專案裡面。根據官方文件,因為依賴於tokio這個非同步crate,所以我們也要一併把tokio加進去,注意一下features,由於rust的crate下可能有多個功能,可以透過指定features來使用所需要的功能。

Hello World

在此先研究一下官方的第一個範例,打開main.rs

use axum::{
    routing::get,
    Router,
};

#[tokio::main]
async fn main() {
    // build our application with a single route
    let app = Router::new().route("/", get(|| async { "Hello, World!" }));

    // run it with hyper on localhost:3000
    axum::Server::bind(&"0.0.0.0:3000".parse().unwrap())
        .serve(app.into_make_service())
        .await
        .unwrap();
}

如果有打開看過一開始使用cargo init產生的main,會發現和範例中長的不太一樣。範例中的main除了前面多了async修飾詞外,也多了[tokio::main],這是rust的巨集功能,透過標注告知編譯器在編譯時會需要多生成一些程式碼,我們先來安裝cargo的擴充功能cargo-expand,透過它可以將巨集展開。

cargo install cargo-expand

安裝完畢後下達指令

cargo expand

接下來就會看到將[tokio::main]巨集展開的結果

#![feature(prelude_import)]
#[prelude_import]
use std::prelude::rust_2021::*;
#[macro_use]
extern crate std;
use axum::{routing::get, Router};
fn main() {
    let body = async {
        let app = Router::new().route("/", get(|| async { "Hello, World!" }));
        axum::Server::bind(&"0.0.0.0:3000".parse().unwrap())
            .serve(app.into_make_service())
            .await
            .unwrap();
    };
    #[allow(clippy::expect_used, clippy::diverging_sub_expression)]
    {
        return tokio::runtime::Builder::new_multi_thread()
            .enable_all()
            .build()
            .expect("Failed building the Runtime")
            .block_on(body);
    }
}

本質上仍然是一個普通的同步的main,先來看最後return的部份,前面的大意是準備一個tokio多執行序的非同步runtime,注意一下這邊使用了builder pattern,這是rust中的一個特色,在很多crate中都會使用這個模式。當runtime準備好後調用了block_on方法,並把原本寫在main的內容以非同步閉包的方式傳進來。在非同步的模型中,rust需要一個基底的程序來負責調度所有的非同步任務,block_on會以執行緒阻塞的方式來處理傳進來的非同步方法,而由外面看起來就像一個同步方法一樣。expect則是rust中錯誤處裡的方式,這邊表示如果runtime準備失敗的話就會中止程式並告知在準備Runtime時發生錯誤,之後有空會特別介紹rust中的錯誤處裡。

Router與Server

接下來,讓我們看一下routerserver的部分:

let app = Router::new().route("/", get(|| async { "Hello, World!" }));

在這段程式碼中Router::new()產生了一個結構體,用於定義Web應用程序的路由和路由處理程序,作用是將不同endpoint的請求分派給相應的handlerhandler是一個處理函數,在這個範例中是個非同步的closure,透過這樣的方式就可以簡潔的設定路由與處裡請求的邏輯。
這邊我想與C#的MVC比較一下,在.Net還沒有出現minimal api以前,我們通常需要建立一個Controller的類別,然後在該類別中定義多個操作方法(action methods),這點rust就和C#十分不同,rust並不是一門標榜物件導向的語言,在rust中,方法並不需要依附於某個class之下。

然後是Server的部份:

axum::Server::bind(&"0.0.0.0:3000".parse().unwrap())
    .serve(app.into_make_service())
    .await
    .unwrap();

這個部份是運行應用程式的關鍵,透過監聽3000這個port,並將請求分派到app相應的處理器上。我想強調一下into_make_service這個方法,Rust社群許多crate的設計都非常的模組化,以Axum來說只專注於解析API的Request與Response,本身只提供少量的middleware。rust的世界中有另外一個叫做tower的crate專們處裡非同步的請求與回應,servicetower crate中一個核心的抽象概念,而into_make_service這個方法的目的是要將axum的應用程式包裝成一個service以便嵌入到tower的系統中。

小結

tower生態系中提供了很多的中介軟體來協助處裡請求與響應,而axum因為支援tower,也就可以使用生態系中大多數的組件,明天預計對routertower進行更深入的介紹。

註:Crate是rust套件系統中編譯的最小單位,在這邊簡單理解成其他語言的套件或函式庫即可,可以到Crates.io找到自己需要的crate使用


上一篇
Day02 - 開發之前的那些事
下一篇
Day04 - 先做一個health check吧
系列文
Rust Web API 從零開始30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

1 則留言

0
Hell Kiki
iT邦新手 4 級 ‧ 2023-09-04 10:24:51

good,

以下補充:

  1. 巨集是簡化我們寫代碼的routine,就像常用的fn我們會抽共用,而常寫的code抽出來的共用就叫巨集(Macro)
  2. axum還是有middleware
marvinhsu iT邦新手 4 級 ‧ 2023-09-04 11:00:22 檢舉

感謝補充,說不提供的確太武斷了

我要留言

立即登入留言