前言
在之前的用 Rust 搭建 React Server Components 的 Web 服務器我們利用了Axum構建了RSC的服務器。也算是用Rust在構建Web服務上的小試牛刀。
雖然說Axum在Rust Web應用中一枝獨秀。但是,市面上也有很多不同的解決方案。所以,今天我們就比較一些 Rust 框架,突出它們各自的優勢和缺點,以幫助我們為項目做出明智的決策。沒有對比就沒有選擇,我們只有在真正的了解各個框架的優缺點和適應場景,在以后的開發中才能有的放矢的放心選擇。
文本中,我們會介紹很多Rust框架。并且會按照如下的受歡迎程度的順序來講。
好了,天不早了,干點正事哇。
我們能所學到的知識點
- Axum
- Actix Web
- Rocket
- Warp
- Tide
- Poem
1. Axum
Axum[1] 是 Rust 生態系統中具有特殊地位的 Web 應用程序框架(從下載量就可見端倪)。它是 Tokio 項目[2]的一部分,Tokio 是使用 Rust 編寫「異步網絡應用程序的運行時」。Axum 不僅使用 Tokio 作為其異步運行時,還與 Tokio 生態系統的其他庫集成,利用 Hyper[3] 作為其 HTTP 服務器和 Tower[4] 作為中間件。通過這樣做,我們能夠重用 Tokio 生態系統中現有的庫和工具。
Axum 「不依賴于宏」,而是利用 Rust 的類型系統提供安全且人性化的 API。這是通過使用特性來定義框架的核心抽象實現的,例如 Handler 特性,用于「定義應用程序的核心邏輯」。這種方法允許我們輕松地「從較小的組件中組合應用程序」,這些組件可以在多個應用程序中重用。
在 Axum 中,處理程序(handler)是一個「接受請求并返回響應」的函數。這與其他后端框架類似,但使用 Axum 的 FromRequest 特性,我們可以指定從請求中提取的數據類型。返回類型需要實現 IntoResponse 特性(trAIt),已經有許多類型實現了這個特性,包括允許輕松更改響應的狀態代碼的元組類型。
Rust 的類型系統、泛型,尤其是在traits中使用異步方法(或更具體地說是返回的 Future),當不滿足trait限制時,Rust 的錯誤消息會很復雜。特別是當嘗試匹配抽象trait限制時,經常會得到一堆難以解讀的文本。為此Axum 提供了一個帶有輔助宏的庫,將錯誤放到實際發生錯誤的地方,使得更容易理解發生了什么錯誤。
雖然Axum 做了很多正確的事情,可以很容易地啟動執行許多任務的應用程序。但是,有一些事情需要特別注意。Axum版本仍然低于 1.0,也就意味著Axum 團隊保留在版本之間「根本性地更改 API 的自由」,這可能導致我們的應用程序出現嚴重問題。
Axum 示例
下面展示了一個 WebSocket 處理程序,它會回顯收到的任何消息。
// #[tokio::main] 宏標記了 `main` 函數,表明這是一個異步的`Tokio`應用程序。
#[tokio::main]
async fn main() {
// 首先創建了一個 `TcpListener` 監聽器,綁定到地址 "127.0.0.1:3000" 上
// 然后,通過 `await` 等待監聽器綁定完成
// 如果綁定失敗,會通過 `unwrap` 方法拋出錯誤。
let listener = tokio:.NET::TcpListener::bind("127.0.0.1:3000")
.await
.unwrap();
println!("listening on {}", listener.local_addr().unwrap());
// 使用 `axum::serve` 啟動 Axum 框架的服務器,
// 監聽前面創建的 `TcpListener`。
// `App()` 函數返回的是一個 `Router`
// 它定義了一個簡單的路由,將路徑 "/a" 映射到處理函數 `a_handler`。
axum::serve(listener, app()).await.unwrap();
}
// 返回一個 `Router`,它只有一個路由規則,
// 將 "/a" 路徑映射到 `a_handler` 處理函數
fn app() -> Router {
Router::new()
.route("/a", get(a_handler))
}
// 一個WebSocket處理程序,它會回顯收到的任何消息。
// 定義為一個WebSocket處理程序,
// 它接收一個 `WebSocketUpgrade` 參數,表示WebSocket升級。
async fn a_handler(ws: WebSocketUpgrade) -> Response {
// 調用將WebSocket升級后的對象傳遞給 `a_handle_socket` 處理函數。
ws.on_upgrade(a_handle_socket)
}
async fn a_handle_socket(mut socket: WebSocket) {
// 使用 while let 循環,持續從 WebSocket 連接中接收消息。
// socket.recv().await 通過異步的方式接收消息,返回一個 Result,
// 其中 Ok(msg) 表示成功接收到消息。
while let Some(Ok(msg)) = socket.recv().await {
// 使用 if let 匹配,判斷接收到的消息是否為文本消息。
// WebSocket消息可以是不同類型的,這里我們只處理文本消息。
if let Message::Text(msg) = msg {
// 構造一個回顯消息,將客戶端發送的消息包含在回顯消息中。
// 然后,使用 socket.send 方法將回顯消息發送回客戶端。
// await 等待發送操作完成。
if socket
.send(Message::Text(format!("You said: {msg}")))
.await
// 檢查 send 操作是否返回錯誤。
// 如果發送消息出現錯誤(例如,連接斷開),
// 就通過 break 跳出循環,結束處理函數。
.is_err()
{
break;
}
}
}
}
Axum 特點
- 無宏 API。
- 利用 Tokio、Tower 和 Hyper 構建強大的生態系統。
- 出色的開發體驗。
- 仍處于 0.x 版本,因此可能發生重大變更。
2. Actix Web
Actix Web[5] 是 Rust 中存在已久且非常受歡迎的 Web 框架之一。像任何良好的開源項目一樣,它經歷了許多迭代,但已經達到了主要版本(不再是 0.x),換句話說:在主要版本內,它可以確保沒有破壞性的更改。
乍一看,Actix Web 與 Rust 中的其他 Web 框架非常相似。我們使用宏來定義 HTTP 方法和路由(類似于 Rocket),并使用提取器(extractors)從請求中獲取數據(類似于 Axum)。與 Axum 相比,它們之間的相似之處顯著,甚至在它們命名概念和特性的方式上也很相似。最大的區別是 Actix Web 沒有將自己與Tokio 生態系統強關聯在一起。雖然 Tokio 仍然是 Actix Web 底層的運行時,但是該框架具有自己的抽象和特性,以及自己的一套 crates 生態系統。這既有利有弊。一方面,我們可以確保事物能夠很好地配合使用,另一方面,我們可能會錯失 Tokio 生態系統中已經可用的許多功能。
Actix Web 實現了自己的 Service 特性,它基本上與 Tower 的 Service 相同,但仍然不兼容。這意味著在 Tower 生態系統中大多數可用的中間件在 Actix 中不可用。
如果在 Actix Web 中需要實現一些特殊任務,而需要自己實現,我們可能會碰到運行框架中的 Actor 模型。這可能會增加一些意想不到的問題。
但 Actix Web 社區很給力。該框架支持 HTTP/2 和 WebSocket 升級,提供了用于 Web 框架中最常見任務的 crates 和指南,以及出色的文檔,而且速度很快。Actix Web 之所以受歡迎,是有原因的,「如果我們需要保證版本,請注意它可能是我們目前的最佳選擇」。
Actix Web 示例
在 Actix Web 中,一個簡單的 WebSocket 回顯服務器如下所示:
use actix::{Actor, StreamHandler};
use actix_web::{
web,
App,
Error,
HttpRequest,
HttpResponse,
HttpServer
};
use actix_web_actors::ws;
/// 定義HTTP Actor
// 定義了一個名為 MyWs 的結構體,這將用作WebSocket的Actix Actor。
// Actors 是Actix框架中的并發單元,用于處理異步消息
struct MyWs;
// 為 MyWs 結構體實現了 Actor trait,指定了 WebsocketContext 作為上下文類型。
impl Actor for MyWs {
type Context = ws::WebsocketContext<Self>;
}
/// 處理ws::Message消息的處理程序
// 為 MyWs 結構體實現了 StreamHandler trait,處理WebSocket連接中的消息。
impl StreamHandler<Result<ws::Message, ws::ProtocolError>> for MyWs {
// 對接收到的不同類型的消息進行處理。例如,對于 Ping 消息,發送 Pong 消息作為響應。
fn handle(&mut self, msg: Result<ws::Message, ws::ProtocolError>, ctx: &mut Self::Context) {
match msg {
Ok(ws::Message::Ping(msg)) => ctx.pong(&msg),
Ok(ws::Message::Text(text)) => ctx.text(text),
Ok(ws::Message::Binary(bin)) => ctx.binary(bin),
_ => (),
}
}
}
// 定義了一個處理HTTP請求的異步函數。
async fn index(req: HttpRequest, stream: web::Payload) -> Result<HttpResponse, Error> {
// 將WebSocket連接升級,并將請求委托給 MyWs Actor 處理。
let resp = ws::start(MyWs {}, &req, stream);
println!("{:?}", resp);
resp
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
// 創建了一個 HttpServer 實例,通過 App::new() 創建一個應用,
// 該應用只有一個路由,將路徑 "/ws/" 映射到處理函數 index 上。
HttpServer::new(|| App::new().route("/ws/", web::get().to(index)))
// 綁定服務器到地址 "127.0.0.1" 和端口 8080。
.bind(("127.0.0.1", 8080))?
// 啟動服務器并等待其完成運行。
.run()
.await
}
Actix Web 特點
- 擁有強大的生態系統。
- 基于 Actor 模型。
- 通過主要版本保證的穩定 API。
- 出色的文檔。
3. Rocket
Rocket[6] 在 Rust Web 框架生態系統中已經有一段時間了:它的主要特點是基于宏的路由、內置表單處理、對數據庫和狀態管理的支持,以及其自己版本的模板!Rocket 確實盡力做到構建 一個 Web 應用程序所需的一切。
然而,Rocket 的雄心壯志也帶來了一些代價。盡管仍在積極開發中,但發布的頻率不如以前。這意味著框架的用戶會錯過許多重要的東西。
此外,由于其一體化的方法,我們還需要了解 Rocket 的實現方式。Rocket 應用程序有一個「生命周期」,構建塊以特定的方式連接,如果出現問題,我們需要理解問題出在哪里。
Rocket 是一個很棒的框架,如果我們想開始使用 Rust 進行 Web 開發,它是一個很好的選擇。「對于我們許多人來說,Rocket 是進入 Rust 的第一步」,使用它仍然很有趣。
Rocket 示例
處理表單的 Rocket 應用程序的簡化示例:
// 定義了一個名為 Password 的結構體,該結構體派生了 Debug 和 FromForm traits。
// FromForm trait 用于從表單數據中提取數據。
// 該結構體包含兩個字段 first 和 second,分別表示密碼的第一個和第二個部分。
#[derive(Debug, FromForm)]
struct Password<'v> {
// 表示對字段的長度進行了驗證,要求長度在6個字符以上
#[field(validate = len(6..))]
// 表示第一個字段必須等于第二個字段
#[field(validate = eq(self.second))]
first: &'v str,
// 表示第二個字段必須等于第一個字段。
#[field(validate = eq(self.first))]
second: &'v str,
}
// 省略其他結構體和實現...
// 定義了一個處理GET請求的函數 index,返回一個 Template 對象。
// 這個函數用于渲染首頁。
#[get("/")]
fn index() -> Template {
Template::render("index", &Context::default())
}
// 定義了一個處理POST請求的函數 submit。
// 這個函數接受一個 Form 對象,其中包含了表單的數據
#[post("/", data = "<form>")]
fn submit(form: Form<Submit<'_>>) -> (Status, Template) {
// 通過檢查 form.value 是否包含 Some(ref submission) 來判斷表單是否提交。
let template = match form.value {
// 如果提交了表單,打印提交的內容,并渲染 "success" 頁面;
Some(ref submission) => {
println!("submission: {:#?}", submission);
Template::render("success", &form.context)
}
// 否則,渲染 "index" 頁面。
None => Template::render("index", &form.context),
};
(form.context.status(), template)
}
// 定義了啟動Rocket應用程序的函數。
#[launch]
fn rocket() -> _ {
// 使用 rocket::build() 創建一個Rocket應用程序實例
rocket::build()
// 并通過 .mount() 方法掛載路由。
// routes![index, submit] 定義了兩個路由,
// 分別映射到 index 和 submit 函數。
.mount("/", routes![index, submit])
// 添加了一個模板處理的Fairing(Rocket中的中間件)
.attach(Template::fairing())
// 將靜態文件服務掛載到根路徑。
.mount("/", FileServer::from(relative!("/static")))
}
Rocket 特點
- 一體化的方法。
- 出色的開發體驗。
- 開發活躍度不如以前。
- 初學者的絕佳選擇。
4. Warp
Warp[7] 是一個構建在 Tokio 之上的 Web 框架,而且是一個非常好的框架。它與我們之前看到的其他框架非常不同。
Warp 與 Axum 有一些共同的特點:它構建在 Tokio 和 Hyper 之上,并利用了 Tower 中間件。然而,它在方法上有很大的不同。Warp 是建立在 Filter trait 之上的。
在 Warp 中,我們構建一系列應用于傳入請求的過濾器,并將請求傳遞到管道直到達到末端。過濾器可以鏈接,它們可以組合。這使我們能夠構建非常復雜的管道,但仍然易于理解。
Warp 也比 Axum 更接近 Tokio 生態系統,這意味著我們可能會在沒有任何粘合特性的情況下處理更多 Tokio 結構和概念。
Warp 采用非常功能化的方法,如果這是我們的編程風格,我們將喜歡 Warp 的表達能力和可組合性。當我們查看 Warp 代碼片段時,它通常讀起來像正在發生的事情的故事,這在 Rust 中能夠實現是有趣且令人驚訝的。
然而,隨著這些不同的函數和過濾器被鏈接在一起,Warp 中的類型變得非常長且非常復雜,而且難以理解。錯誤消息也是如此,可能是難以理解的一大堆文本。
Warp 是一個很棒的框架。但是,它「并不是最適合初學者的框架,也不是最流行的框架」。這意味著我們可能在尋找幫助和資源方面會更加困難。但它非常「適用于快速小型應用程序」!
Warp 示例
來自其示例倉庫的 WebSocket 聊天的 Warp 應用程序的簡化示例:
// 定義了一個靜態的原子 usize 計數器,用于為每個連接的用戶分配唯一的用戶ID。
static NEXT_USER_ID: AtomicUsize = AtomicUsize::new(1);
// 當前連接用戶的狀態。
// 定義了一個類型別名 Users,它是一個原子引用計數的可讀寫鎖的 HashMap,將用戶ID映射到消息的發送器。
// Arc 是原子引用計數的智能指針,RwLock 是讀寫鎖。
// - 鍵是其id
// - 值是`warp::ws::Message`的發送器
type Users = Arc<RwLock<HashMap<usize, mpsc::UnboundedSender<Message>>>>;
#[tokio::main]
async fn main() {
// 創建了一個 users 變量,用于存儲連接的用戶信息
let users = Users::default();
// 將其包裝成 Warp 過濾器,以便在不同的路由中共享用戶狀態。
let users = warp::any().map(move || users.clone());
// chat 路由處理 WebSocket 握手
let chat = warp::path("chat")
// `ws()`過濾器將準備WebSocket握手...
.and(warp::ws())
.and(users)
// 調用 user_connected 函數處理 WebSocket 連接。
.map(|ws: warp::ws::Ws, users| {
// 如果握手成功,將調用我們的函數。
ws.on_upgrade(move |socket| user_connected(socket, users))
});
// 處理 HTTP GET 請求,返回一個包含聊天室鏈接的 html 頁面
let index = warp::path::end().map(|| warp::reply::html(INDEX_HTML));
let routes = index.or(chat);
warp::serve(routes).run(([127, 0, 0, 1], 3030)).await;
}
async fn user_connected(ws: WebSocket, users: Users) {
// 使用計數器為此用戶分配新的唯一ID。
let my_id = NEXT_USER_ID.fetch_add(1, Ordering::Relaxed);
eprintln!("new chat user: {}", my_id);
// 將套接字拆分為消息的發送器和接收器。
let (mut user_ws_tx, mut user_ws_rx) = ws.split();
// 創建一個新的消息通道 (mpsc::unbounded_channel) 用于將用戶的消息廣播給其他用戶
let (tx, rx) = mpsc::unbounded_channel();
let mut rx = UnboundedReceiverStream::new(rx);
tokio::task::spawn(async move {
// 不斷接收用戶的消息。一旦用戶斷開連接,就會退出這個循環。
while let Some(message) = rx.next().await {
user_ws_tx
.send(message)
.unwrap_or_else(|e| {
eprintln!("websocket send error: {}", e);
})
.await;
}
});
//將發送器保存在我們的已連接用戶列表中。
users.write().await.insert(my_id, tx);
// 返回一個基本上是管理此特定用戶連接的狀態機的“Future”。
// 每當用戶發送消息時,將其廣播給
// 所有其他用戶...
while let Some(result) = user_ws_rx.next().await {
let msg = match result {
Ok(msg) => msg,
Err(e) => {
eprintln!("websocket error(uid={}): {}", my_id, e);
break;
}
};
user_message(my_id, msg, &users).await;
}
// 只要用戶保持連接,user_ws_rx流就會繼續處理。一旦他們斷開連接,那么...
user_disconnected(my_id, &users).await;
}
// 處理用戶發送的消息。它跳過非文本消息,將文本消息格式化為 <User#ID>: Message,然后將其廣播給所有其他用戶。
async fn user_message(my_id: usize, msg: Message, users: &Users) {
// 跳過任何非文本消息...
let msg = if let Ok(s) = msg.to_str() {
s
} else {
return;
};
let new_msg = format!("<User#{}>: {}", my_id, msg);
// 來自此用戶的新消息,將其發送給所有其他用戶(除了相同的uid)...
for (&uid, tx) in users.read().await.iter() {
if my_id != uid {
if let Err(_disconnected) = tx.send(Message::text(new_msg.clone())) {
// 發送器已斷開連接,我們的`user_disconnected`代碼
// 應該在另一個任務中執行,這里沒有更多的事情要做。
}
}
}
}
async fn user_disconnected(my_id: usize, users: &Users) {
eprintln!("good bye user: {}", my_id);
// 流關閉,因此從用戶列表中刪除
users.write().await.remove(&my_id);
}
Warp 特點
- 函數式方法。
- 良好的表達能力。
- 通過接近 Tokio、Tower 和 Hyper 構建強大的生態系統。
- 不適合初學者的框架
5. Tide
Tide[8] 是一個建立在 async-std 運行時之上的「極簡主義 Web 框架」。極簡主義的方法意味著我們得到了一個非常小的 API 表面。Tide 中的處理函數是 async fn,接受一個 Request 并返回一個 Response 的 tide::Result。提取數據或發送正確的響應格式由我們自行完成。
雖然這可能對我們來說是更多的工作,但也更直接,意味著我們完全掌控正在發生的事情。在某些情況下,能夠離 HTTP 請求和響應如此近是一種愉悅,使事情變得更容易。
Tide 的中間件方法與我們從 Tower 中了解的類似,但 Tide 公開了 async trait crate,使實現變得更加容易。
Tide 示例
來自其示例倉庫的用戶會話示例:
// async-std crate 提供的異步 main 函數。它返回一個 Result,表示可能的錯誤。
#[async_std::main]
async fn main() -> Result<(), std::io::Error> {
// 使用 femme crate 啟用顏色日志。這是一個美觀的日志記錄庫,可以使日志輸出更易讀。
femme::start();
// 創建一個 Tide 應用程序實例
let mut app = tide::new();
// 添加一個日志中間件,用于記錄請求和響應的日志信息。
app.with(tide::log::LogMiddleware::new());
// 添加一個會話中間件,用于處理會話數據。這里使用內存存儲,并提供一個密鑰(TIDE_SECRET),用于加密和驗證會話數據。
app.with(tide::sessions::SessionMiddleware::new(
tide::sessions::MemoryStore::new(),
std::env::var("TIDE_SECRET")
.expect(
"Please provide a TIDE_SECRET value of at
least 32 bytes in order to run this example",
)
.as_bytes(),
));
// 添加一個 Before 中間件,它在處理請求之前執行。在這里,它用于增加訪問計數,存儲在會話中。
app.with(tide::utils::Before(
|mut request: tide::Request<()>| async move {
let session = request.session_mut();
let visits: usize = session.get("visits").unwrap_or_default();
session.insert("visits", visits + 1).unwrap();
request
},
));
// 定義了一個處理根路徑的GET請求的路由。這個路由通過 async move 來處理請求,獲取會話中的訪問計數,并返回一個包含訪問次數的字符串。
app.at("/").get(|req: tide::Request<()>| async move {
let visits: usize = req.session().get("visits").unwrap();
Ok(format!("you have visited this website {} times", visits))
});
// 定義了一個處理 "/reset" 路徑的GET請求的路由。這個路由通過 async move 處理請求,將會話數據清除,然后重定向到根路徑
app.at("/reset")
.get(|mut req: tide::Request<()>| async move {
req.session_mut().destroy();
Ok(tide::Redirect::new("/"))
});
// 啟動應用程序并監聽在 "127.0.0.1:8080" 地址上。使用 await? 處理可能的啟動錯誤。
app.listen("127.0.0.1:8080").await?;
Ok(())
}
Tide 簡要概述
- 極簡主義方法。
- 使用 async-std 運行時。
- 簡單的處理函數。
- 異步特性的試驗場。
6. Poem
Poem[9] 聲稱自己是一個功能齊全但易于使用的 Web 框架。乍一看,它的使用方式與 Axum 非常相似,唯一的區別是它需要使用相應的宏標記處理程序函數。它還建立在 Tokio 和 Hyper 之上,完全兼容 Tower 中間件,同時仍然暴露自己的中間件特性。
Poem 的中間件特性也非常簡單易用。我們可以直接為所有或特定的 Endpoint(Poem 表達一切都可以處理 HTTP 請求的方式)實現該特性,或者只需編寫一個接受 Endpoint 作為參數的異步函數。
Poem 不僅與更廣泛的生態系統中的許多功能兼容,而且還具有豐富的功能,包括對 OpenAPI 和 Swagger 文檔的全面支持。它不僅限于基于 HTTP 的 Web 服務,還可以用于基于 Tonic 的 gRPC 服務,甚至在 Lambda 函數中使用,而無需切換框架。添加對 OpenTelemetry、redis、Prometheus 等的支持,我們就可以勾選所有現代企業級應用程序 Web 框架的所有框。
Poem 仍然處于 0.x 版本,但如果保持勢頭并交付出色的 1.0 版本,這將是一個值得關注的框架!
Poem 示例
來自其示例倉庫的 WebSocket 聊天的縮寫版本:
// 注解表示這是一個處理器函數,用于處理 WebSocket 請求
#[handler]
fn ws(
// 提取了 WebSocket 路徑中的名字參數
Path(name): Path<String>,
// WebSocket 對象,表示與客戶端的連接
ws: WebSocket,
// 是一個數據提取器,用于獲取廣播通道的發送器。
sender: Data<&tokio::sync::broadcast::Sender<String>>,
) -> impl IntoResponse {
// 克隆了廣播通道的發送器 sender。
let sender = sender.clone();
// 它訂閱了廣播通道,創建了一個接收器 receiver
let mut receiver = sender.subscribe();
// 處理 WebSocket 連接升級
ws.on_upgrade(move |socket| async move {
// 將連接的讀寫部分拆分為 sink 和 stream
let (mut sink, mut stream) = socket.split();
// 從 WebSocket 客戶端接收消息
// 如果是文本消息,則將其格式化為 {name}: {text} 的形式,并通過廣播通道發送。
// 如果發送失敗(例如,通道關閉),則任務終止。
tokio::spawn(async move {
while let Some(Ok(msg)) = stream.next().await {
if let Message::Text(text) = msg {
if sender.send(format!("{name}: {text}")).is_err() {
break;
}
}
}
});
// 從廣播通道接收消息,并將其發送到 WebSocket 客戶端
tokio::spawn(async move {
while let Ok(msg) = receiver.recv().await {
if sink.send(Message::Text(msg)).await.is_err() {
break;
}
}
});
})
}
#[tokio::main]
async fn main() -> Result<(), std::io::Error> {
// 使用 tide::Route 創建了一個路由,其中包括兩個路徑:
// - / 路徑處理 HTTP GET 請求,調用 index 函數。
// - /ws/:name 路徑處理 WebSocket 請求,調用 ws 函數。
let app = Route::new().at("/", get(index)).at(
"/ws/:name",
// 通過 tokio::sync::broadcast::channel 創建一個廣播通道;
// 并通過 tokio::sync::broadcast::channel::<String>(32).0
// 獲取其發送器,將其作為數據傳遞給 ws 處理函數
get(ws.data(tokio::sync::broadcast::channel::<String>(32).0)),
);
// 創建了一個服務器實例
Server::new(TcpListener::bind("127.0.0.1:3000"))
// 啟動服務器,并等待其完成運行。
.run(app)
.await
}
Poem 簡要概述
- 豐富的功能集。
- 與 Tokio 生態系統兼容。
- 易于使用。
- 適用于 gRPC 和 Lambda。
后記
正如我們所見,Rust Web 框架的世界非常多樣化。沒有一種解決方案適用于所有情況,我們需要選擇最符合我們需求的框架。如果我們剛剛開始,我建議我們選擇 Actix 或 Axum,因為它們是最適合初學者的框架,而且它們有著出色的文檔。