使用 Rust 构建一个简单的 Web 服务器

2025-06-07

使用 Rust 构建一个简单的 Web 服务器

使用 Rust 构建 Web 服务器并不复杂。借助 Axum 等框架,您可以轻松编写 Web 服务器。Rust 让您可以轻松处理用其他语言编写的 Web 服务。在本文中,我们将讨论如何使用 Axum 构建和部署一个简单的 Web 服务器。

我应该使用什么 Rust 框架?

虽然我们有很多选择,但我们个人选择 Axum,原因如下:

  • Axum 使用泛型和特性。这允许您以其他框架可能无法实现的方式利用 Rust 语言工具。
  • Axum 具有熟悉的语法(使用处理程序函数进行路由)。
  • 它与 crate 的兼容性极高tower。如果需要,您可以将其调到非常低的级别。

此外,我们还有一篇关于您应该使用什么框架的文章

入门

首先,您需要在系统上安装 Rust。还没有安装?您可以从此安装页面获取。

接下来,您将需要使用cargo shuttle init(需要cargo-shuttle安装)。然后选择Axum我们的框架。

你好世界!

使用 创建项目时cargo init,您需要手动创建(或复制!)初始样板。它可能看起来像这样:

use axum::{Router, routing::get};
use std::net::SocketAddr;
use tokio::net::TcpListener;

async fn hello_world() -> &'static str {
    "Hello world!"
}

#[tokio::main]
async fn main() {
    let router = Router::new().route("/", get(hello_world));

    let addr = SocketAddr::from(([127,0,0,1], 8000));
    let tcp = TcpListener::bind(&addr).await.unwrap();

    axum::serve(tcp, router).await.unwrap();
}
Enter fullscreen mode Exit fullscreen mode

从上面可以看出,我们做了几件事:

  • 我们设置了一个具有给定路由和需要调用的函数的路由器。
  • 我们为我们的 Web 服务器定义一个接收请求的地址,并将其绑定到 TCP 侦听器。
  • 然后 TCP 侦听器响应请求并做出相应的响应。

如果您曾经cargo run加载此程序然后转到[localhost:8000](http://localhost:8000)浏览器,它将返回“Hello world!”作为原始文本响应。

使用 Shuttle 初始化项目时,所有这些都已为您设置完毕。基础项目已内置原生集成,因此您无需手动设置套接字绑定。集成代码非常简短,主要围绕该shuttle_runtime::Service特征的使用。如果您需要运行非标准服务,只需在实现该Service特征的结构体中运行该服务即可,一切准备就绪!

请参阅下面的 Shuttle“Hello World”项目示例:

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

async fn hello_world() -> &'static str {
    "Hello world!"
}

#[shuttle_runtime::main]
async fn main() -> shuttle_axum::ShuttleAxum {
    let router = Router::new().route("/", get(hello_world));

    Ok(router.into())
}
Enter fullscreen mode Exit fullscreen mode

路由

Rust 框架内路由的 HTTP 响应可以通过任何实现表示 HTTP 响应的 trait 来实现。在 Axum 中,这将是IntoResponsetrait(或IntoResponseParts用于标头和其他非响应主体部分)。Web 服务器只能返回有效的 HTTP 响应。分别实现IntoResponseIntoResponseParts可以确保这一点!

可以将其用作impl IntoResponse函数的返回类型(为了方便)。但是,我们需要确保所有响应都是相同的类型。这可能会导致后续的混淆,尤其是在团队合作的情况下。

对于 JSON 响应,Axum 提供了一个方便的Json<T>结构体,我们可以通过用它包装类型来将其用作响应类型。例如,下面的代码片段展示了如何返回一些原始 JSON:

use serde_json::{json, Value};
use axum::Json;

async fn return_some_json() -> Json<Value> {
    let json = json!({"hello":"world"})

    Json(json)
}
Enter fullscreen mode Exit fullscreen mode

然而,更可能的情况是,您希望返回遵循某种模式的数据。我们可以使用crate中的Deserialize和trait 来实现这一点。我们可以通过添加特性,然后将其作为 derive 宏添加到结构体中,轻松应用这些 trait:Serializeserdederive

use serde::{Deserialize, Serialize};

#[derive(Deserialize, Serialize)]
struct MyStruct {
    my_field: String
}
Enter fullscreen mode Exit fullscreen mode

现在我们可以将其包装起来axum::Json并在 HTTP 响应中返回该结构。

async fn return_a_struct_as_json() -> Json<MyStruct> {
     let my_struct = MyStruct { my_field: "Hello world!".to_string() };

     Json(my_struct)
}
Enter fullscreen mode Exit fullscreen mode

提取器

提取器是处理函数的参数。它们提取 HTTP 请求的部分内容,并将其转换为可在应用程序中使用的简单变量。提取器可以用于很多用途:

  • 访问共享可变状态(通过向我们的应用程序添加状态,然后在函数中访问它)
  • 提取类型化的标头以用于授权目的
  • 根据您的需要,使用请求主体来提取 JSON 或表单主体。
  • 如果我们的用例没有现成的东西,我们可以自己实现它!

下面是一个如何使用提取器的示例。请注意,我们使用解构来自动获取内部变量,Json<MyStruct>这样看起来更简洁。


async fn function_with_extractors(
    Json(json): Json<MyStruct>,
) -> impl IntoResponse {
    format!("The contents of my_field is: {}", json.my_field)
}
Enter fullscreen mode Exit fullscreen mode

数据库

在 Rust 中使用数据库通常与其他语言没有太大区别。主要区别在于 Rust Web 框架通常使用共享可变状态来传递数据。这意味着您需要首先初始化数据库连接(池),然后使用状态传递它。在 Axum 中,状态是实现 的必需条件Clone。如果您的类型Clone由于一个或多个类型未实现 Clone 而无法实现,则可以将状态结构体包装在std::sync::Arc已实现 的 结构体中Clone

下面是一个使用 SQLx 和 Postgres 的示例,演示如何实现这一点。我们初始化PgPool,初始化我们的状态结构体,并将其附加到路由器。

use sqlx::PgPool;
use axum::{extract::State, Router, routing::get, http::StatusCode};

#[derive(Clone)]
struct MyState {
    db: PgPool
}

#[tokio::main]
async fn main() {
    let db: PgPool = PgPool::connect("<your-db-connection-url-here>").await.unwrap();
    let state = MyState { db };
    let router = Router::new().route("/", get(hello_world)).with_state(state);

    // the rest of your code...
}
Enter fullscreen mode Exit fullscreen mode

使用 Shuttle,我们只需添加数据库宏即可配置数据库。这不仅节省了本地部署时间,也节省了生产环境的时间!首先,我们需要添加shuttle_shared_db依赖项。

cargo add shuttle-shared-db -F postgres,sqlx
Enter fullscreen mode Exit fullscreen mode

然后我们只需将其添加到我们的主函数中并像以前一样再次初始化状态结构。

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

#[shuttle_runtime::main]
async fn main(
    #[shuttle_shared_db::Postgres] db: PgPool,
) -> shuttle_axum::ShuttleAxum {
    let state = MyState { db };
    let router = Router::new().route("/", get(hello_world)).with_state(state);

    Ok(router.into())
}
Enter fullscreen mode Exit fullscreen mode

要使用我们的状态结构,我们可以使用axum::extract::State提取器:

use axum::{extract::State, http::StatusCode};

async fn hello_world(
    State(state): State<MyState>
) -> StatusCode {
    sqlx::query("SELECT 'Hello world!")
         .execute(&state.db)
         .await
         .unwrap();

    StatusCode::OK
}
Enter fullscreen mode Exit fullscreen mode

静态文件

为了开始在 Axum 上使用静态文件,我们将在项目根目录中创建一个名为 的文件夹。然后,我们将在项目根目录中static名为的文件中定义它:Shuttle.toml

assets = ["static/*"]
Enter fullscreen mode Exit fullscreen mode

使用通配符标签允许我们使用文件路径来提供文件夹中包含的任何文件。

我们可以在静态文件夹中编写一个名为的 HTML 文件index.html

<h1>Hello world</h1>
Enter fullscreen mode Exit fullscreen mode

要在 Axum 上提供此静态文件夹,我们需要从 导入一些内容tower-http。我们将运行以下 shell 代码片段:

cargo add tower-http -F fs
Enter fullscreen mode Exit fullscreen mode

然后我们可以像下面这样添加它:

use tower_http::services::{ServeDir};

let router = Router::new()
    .route_service("/", ServeDir::new("static"));
Enter fullscreen mode Exit fullscreen mode

如果您使用像 React 或 Vue 这样的 SPA 框架来处理静态文件,那么您将需要另外设置.not_found_service()才能提供以下服务index.html

let router = Router::new()
    .route_service("/", ServeDir::new("static")
        .not_found_service(ServeFile::new("static/index.html") 
     )
);
Enter fullscreen mode Exit fullscreen mode

部署

要将 Rust 部署到 Shuttle,您可以直接使用cargo shuttle deploy并见证奇迹的发生!如果 Git 分支上有未提交的更改,请不要忘记添加--allow-dirty标记。除了 Web 服务器开发之外,Shuttle 还能帮助您轻松部署和公开 Web 服务。

感兴趣吗?您可以在这里找到我们的文档

完成

Web 开发不必太复杂。使用 Rust,我们可以快速轻松地实现构建简单 Web 服务器的目标。

阅读更多:

  • 在此更深入地了解 Axum
  • 在此处了解有关跟踪库的更多信息并改进应用程序日志记录
文章来源:https://dev.to/shuttle_dev/building-a-simple-web-server-in-rust-5c57
PREV
18+ Flutter 技巧和窍门
NEXT
设计模式:享元模式⚖️