MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

从零开始搭建Rust Web服务器

2023-10-121.7k 阅读

环境搭建

在开始搭建 Rust Web 服务器之前,我们需要确保开发环境准备就绪。

安装 Rust

Rust 的官方安装程序是 rustup,它可以方便地安装 Rust 编译器及其相关工具。

  1. Linux 和 macOS: 在终端中运行以下命令:
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh

这个脚本会下载并安装 rustup,同时会安装最新稳定版本的 Rust 编译器。安装完成后,按照提示将 Rust 的 bin 目录添加到系统的 PATH 环境变量中。通常需要重新启动终端使更改生效。

  1. Windows: 从 Rust 官方网站(https://www.rust-lang.org/tools/install)下载并运行安装程序。在安装过程中,按照提示完成安装,安装程序会自动将 Rust 添加到系统的 PATH 中。

安装完成后,可以通过以下命令验证 Rust 是否安装成功:

rustc --version

如果安装成功,会输出当前安装的 Rust 版本号。

安装 Cargo

Cargo 是 Rust 的包管理器和构建工具,它会随着 Rust 一起安装。可以通过以下命令验证 Cargo 是否安装成功:

cargo --version

如果成功安装,会输出 Cargo 的版本号。

选择 Web 框架

Rust 生态中有多个优秀的 Web 框架可供选择,比如 Actix Web、Rocket、Axum 等。每个框架都有其特点和适用场景。这里我们以 Actix Web 为例,它是一个基于异步 I/O 的高性能 Web 框架,适合构建各种类型的 Web 应用。

首先,在项目的 Cargo.toml 文件中添加 Actix Web 的依赖:

[dependencies]
actix-web = "4.3.1"

然后运行 cargo build 来下载并编译依赖。

创建基本的 Web 服务器

创建项目

使用 Cargo 创建一个新的 Rust 项目:

cargo new rust_web_server
cd rust_web_server

这会创建一个新的 Rust 项目目录 rust_web_server,并初始化一个基本的项目结构。

编写服务器代码

src/main.rs 文件中编写以下代码:

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

#[get("/")]
async fn index() -> impl Responder {
    HttpResponse::Ok().body("Hello, World!")
}

#[actix_web::main]
async fn main() -> std::io::Result<()> {
    HttpServer::new(|| {
        App::new()
           .service(index)
    })
   .bind(("127.0.0.1", 8080))?
   .run()
   .await
}

在这段代码中:

  1. 我们首先导入了 Actix Web 所需的模块,包括 get 宏用于定义 GET 请求处理函数,web 模块用于处理请求和响应,App 用于构建应用,HttpResponse 用于构建 HTTP 响应,HttpServer 用于启动服务器,以及 Responder 用于定义响应类型。
  2. index 函数是一个处理根路径(/)GET 请求的异步函数。它返回一个实现了 Responder trait 的 HttpResponse,这里返回一个简单的 "Hello, World!" 响应。
  3. main 函数是程序的入口。actix_web::main 宏用于将这个函数标记为 Actix Web 应用的入口点。在 main 函数中,我们创建了一个 HttpServer,并在其上挂载了我们定义的 AppApp 中包含了 index 服务。服务器绑定到本地地址 127.0.0.1 的 8080 端口,并通过 run 方法启动。

运行服务器

在项目根目录下运行以下命令启动服务器:

cargo run

如果一切顺利,你会看到服务器启动的日志输出,表明服务器正在监听 127.0.0.1:8080。打开浏览器,访问 http://127.0.0.1:8080,应该能看到 "Hello, World!" 的页面。

处理路由和请求

定义更多路由

可以在 App 中定义多个路由来处理不同的请求。例如,添加一个处理 /about 路径的路由:

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

#[get("/")]
async fn index() -> impl Responder {
    HttpResponse::Ok().body("Hello, World!")
}

#[get("/about")]
async fn about() -> impl Responder {
    HttpResponse::Ok().body("This is an about page.")
}

#[actix_web::main]
async fn main() -> std::io::Result<()> {
    HttpServer::new(|| {
        App::new()
           .service(index)
           .service(about)
    })
   .bind(("127.0.0.1", 8080))?
   .run()
   .await
}

这里我们定义了一个新的 about 函数来处理 /about 路径的 GET 请求,并在 App 中添加了这个服务。现在访问 http://127.0.0.1:8080/about 会看到 "This is an about page." 的响应。

处理动态路由

有时候我们需要处理动态路由,例如处理用户个人页面,路径可能是 /user/{username}。在 Actix Web 中可以这样实现:

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

#[get("/")]
async fn index() -> impl Responder {
    HttpResponse::Ok().body("Hello, World!")
}

#[get("/about")]
async fn about() -> impl Responder {
    HttpResponse::Ok().body("This is an about page.")
}

#[get("/user/{username}")]
async fn user(username: web::Path<String>) -> impl Responder {
    HttpResponse::Ok().body(format!("Hello, {}!", username))
}

#[actix_web::main]
async fn main() -> std::io::Result<()> {
    HttpServer::new(|| {
        App::new()
           .service(index)
           .service(about)
           .service(user)
    })
   .bind(("127.0.0.1", 8080))?
   .run()
   .await
}

user 函数中,我们通过 web::Path<String> 从路径中提取出 username 参数,并在响应中使用它。访问 http://127.0.0.1:8080/user/john 会看到 "Hello, john!" 的响应。

处理不同的 HTTP 方法

除了 GET 方法,Actix Web 也很容易处理其他 HTTP 方法,如 POST、PUT、DELETE 等。以处理 POST 请求为例:

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

#[post("/submit")]
async fn submit(form: web::Form<(String, i32)>) -> impl Responder {
    let (name, age) = form.into_inner();
    HttpResponse::Ok().body(format!("Name: {}, Age: {}", name, age))
}

#[actix_web::main]
async fn main() -> std::io::Result<()> {
    HttpServer::new(|| {
        App::new()
           .service(submit)
    })
   .bind(("127.0.0.1", 8080))?
   .run()
   .await
}

在这段代码中,我们定义了一个处理 /submit 路径 POST 请求的 submit 函数。web::Form<(String, i32)> 用于解析表单数据,这里假设表单包含一个字符串类型的 name 和一个 32 位整数类型的 age。我们可以使用工具如 curl 来测试这个 POST 接口:

curl -X POST -d "name=Alice&age=25" http://127.0.0.1:8080/submit

会得到 "Name: Alice, Age: 25" 的响应。

处理请求体和响应

解析 JSON 请求体

在现代 Web 开发中,JSON 是一种常见的数据格式。Actix Web 可以方便地解析 JSON 请求体。首先,在 Cargo.toml 中添加 serdeserde_json 依赖,用于序列化和反序列化 JSON 数据:

[dependencies]
actix-web = "4.3.1"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"

然后编写处理 JSON 请求的代码:

use actix_web::{post, web, App, HttpResponse, HttpServer, Responder};
use serde::{Deserialize, Serialize};

#[derive(Deserialize)]
struct User {
    name: String,
    age: i32,
}

#[post("/user")]
async fn create_user(user: web::Json<User>) -> impl Responder {
    HttpResponse::Ok().json(user)
}

#[actix_web::main]
async fn main() -> std::io::Result<()> {
    HttpServer::new(|| {
        App::new()
           .service(create_user)
    })
   .bind(("127.0.0.1", 8080))?
   .run()
   .await
}

在这段代码中:

  1. 我们定义了一个 User 结构体,并使用 serdeDeserialize trait 来自动生成反序列化 JSON 数据的代码。
  2. create_user 函数使用 web::Json<User> 来解析 JSON 请求体,并将解析后的 User 对象作为响应返回,这里通过 HttpResponse::Ok().json(user)User 对象序列化为 JSON 格式。 可以使用 curl 来测试这个接口:
curl -X POST -H "Content-Type: application/json" -d '{"name":"Bob","age":30}' http://127.0.0.1:8080/user

会得到 {"name":"Bob","age":30} 的响应。

自定义响应

除了返回简单的文本或 JSON 响应,我们还可以自定义响应格式。例如,返回一个 HTML 页面:

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

#[get("/")]
async fn index() -> impl Responder {
    let html = r#"
        <html>
            <head>
                <title>My Rust Web Server</title>
            </head>
            <body>
                <h1>Welcome to my Rust web server!</h1>
            </body>
        </html>
    "#;
    HttpResponse::Ok()
       .content_type("text/html")
       .body(html)
}

#[actix_web::main]
async fn main() -> std::io::Result<()> {
    HttpServer::new(|| {
        App::new()
           .service(index)
    })
   .bind(("127.0.0.1", 8080))?
   .run()
   .await
}

在这个例子中,我们定义了一个 HTML 字符串,并在响应中设置了 Content-Typetext/html,然后将 HTML 内容作为响应体返回。访问 http://127.0.0.1:8080 会看到一个简单的 HTML 页面。

中间件的使用

中间件是在请求处理前后执行的代码块,可以用于日志记录、身份验证、错误处理等功能。

日志中间件

Actix Web 提供了 actix_web::middleware::Logger 中间件用于记录请求日志。在 Cargo.toml 中添加 log 依赖:

[dependencies]
actix-web = "4.3.1"
log = "0.4"

然后在服务器代码中添加日志中间件:

use actix_web::{get, web, App, HttpResponse, HttpServer, Responder};
use actix_web::middleware::Logger;

#[get("/")]
async fn index() -> impl Responder {
    HttpResponse::Ok().body("Hello, World!")
}

#[actix_web::main]
async fn main() -> std::io::Result<()> {
    std::env::set_var("RUST_LOG", "actix_web=info");
    env_logger::init();

    HttpServer::new(|| {
        App::new()
           .wrap(Logger::default())
           .service(index)
    })
   .bind(("127.0.0.1", 8080))?
   .run()
   .await
}

在这段代码中:

  1. 我们首先设置了环境变量 RUST_LOG 来指定日志级别为 info,并初始化了 env_logger
  2. 然后在 App 中使用 wrap 方法添加了 Logger::default() 中间件。现在每次有请求到达服务器,都会在控制台输出请求的相关日志信息,如请求方法、路径、响应状态码等。

错误处理中间件

我们可以自定义错误处理中间件来统一处理应用中的错误。例如,定义一个简单的错误处理中间件,将所有错误转换为 HTTP 500 响应:

use actix_web::{error, get, web, App, HttpResponse, HttpServer, Responder};
use std::fmt::Debug;

struct ErrorHandler;

impl<S, B> actix_web::middleware::ErrorHandler<S, B> for ErrorHandler
where
    S: 'static,
    B: actix_web::body::MessageBody,
{
    fn handle<E>(&self, _req: &actix_web::HttpRequest, err: E) -> Result<HttpResponse<B>, E>
    where
        E: Debug + error::ResponseError + 'static,
    {
        let response = HttpResponse::InternalServerError().body("Internal Server Error");
        Ok(response.map_into())
    }
}

#[get("/error")]
async fn error_route() -> Result<impl Responder, std::io::Error> {
    Err(std::io::Error::new(std::io::ErrorKind::Other, "simulated error"))
}

#[actix_web::main]
async fn main() -> std::io::Result<()> {
    HttpServer::new(|| {
        App::new()
           .wrap(ErrorHandler)
           .service(error_route)
    })
   .bind(("127.0.0.1", 8080))?
   .run()
   .await
}

在这个例子中:

  1. 我们定义了一个 ErrorHandler 结构体,并为其实现了 actix_web::middleware::ErrorHandler trait。在 handle 方法中,我们将所有错误转换为 HTTP 500 响应。
  2. error_route 函数故意返回一个错误,用于测试错误处理中间件。当访问 http://127.0.0.1:8080/error 时,会看到 "Internal Server Error" 的响应,而不是默认的错误页面。

数据库连接

在实际的 Web 应用中,通常需要与数据库进行交互。Rust 生态中有多种数据库驱动可供选择,这里以 SQLite 为例,使用 rusqlite 库。

添加依赖

Cargo.toml 中添加 rusqlite 依赖:

[dependencies]
actix-web = "4.3.1"
rusqlite = "0.28"

连接数据库并执行查询

编写代码来连接 SQLite 数据库并执行简单的查询:

use actix_web::{get, web, App, HttpResponse, HttpServer, Responder};
use rusqlite::{Connection, Result};

#[get("/users")]
async fn get_users() -> impl Responder {
    let conn = Connection::open("test.db").expect("Failed to open database");
    let mut stmt = conn
       .prepare("SELECT name, age FROM users")
       .expect("Failed to prepare statement");
    let users: Result<Vec<(String, i32)>> = stmt.query_map([], |row| {
        Ok((
            row.get(0)?,
            row.get(1)?,
        ))
    })
   .map(|mut iter| iter.collect());

    match users {
        Ok(users) => {
            let mut response = String::new();
            for (name, age) in users {
                response.push_str(&format!("Name: {}, Age: {}\n", name, age));
            }
            HttpResponse::Ok().body(response)
        }
        Err(e) => HttpResponse::InternalServerError().body(format!("Database error: {}", e)),
    }
}

#[actix_web::main]
async fn main() -> std::io::Result<()> {
    HttpServer::new(|| {
        App::new()
           .service(get_users)
    })
   .bind(("127.0.0.1", 8080))?
   .run()
   .await
}

在这段代码中:

  1. get_users 函数尝试连接名为 test.db 的 SQLite 数据库。如果数据库不存在,Connection::open 会创建一个新的数据库。
  2. 准备一个 SQL 查询语句 SELECT name, age FROM users,并通过 query_map 方法执行查询,将结果映射为 (String, i32) 类型的向量。
  3. 根据查询结果,构建一个包含用户信息的字符串作为响应体返回。如果查询过程中出现错误,返回一个 HTTP 500 错误响应。

部署 Rust Web 服务器

构建可执行文件

在本地开发完成后,需要将项目构建为可执行文件以便部署。在项目根目录下运行以下命令:

cargo build --release

这会在 target/release 目录下生成一个优化后的可执行文件。

选择部署环境

  1. 云服务器:可以选择如 AWS、Google Cloud、阿里云等云服务提供商提供的云服务器。在云服务器上安装必要的运行时环境(如 Rust 运行时,如果服务器上没有预装),然后将构建好的可执行文件上传到服务器,并运行。
  2. 容器化部署:可以使用 Docker 将 Rust Web 服务器容器化,然后部署到 Kubernetes 集群等容器编排平台上。首先,创建一个 Dockerfile
FROM rust:1.60.0-slim as builder

WORKDIR /app
COPY Cargo.toml Cargo.lock./
RUN cargo fetch
COPY. /app
RUN cargo build --release

FROM debian:buster-slim

RUN apt-get update && apt-get install -y libsqlite3-0
COPY --from=builder /app/target/release/rust_web_server /usr/local/bin/
EXPOSE 8080
CMD ["/usr/local/bin/rust_web_server"]

在这个 Dockerfile 中,我们首先使用 Rust 官方镜像构建项目,然后基于 debian:buster-slim 镜像创建一个更小的运行时镜像,将构建好的可执行文件复制到运行时镜像中,并暴露 8080 端口。

构建 Docker 镜像:

docker build -t rust_web_server.

运行 Docker 容器:

docker run -p 8080:8080 rust_web_server

这样就可以在本地通过 http://127.0.0.1:8080 访问容器中的 Rust Web 服务器。如果要部署到 Kubernetes 集群,可以将 Docker 镜像推送到镜像仓库,然后编写 Kubernetes 部署文件来创建和管理容器实例。

通过以上步骤,我们从环境搭建开始,逐步搭建了一个功能较为完善的 Rust Web 服务器,包括处理路由、请求响应、中间件使用、数据库连接以及部署等方面。希望这些内容能帮助你在 Rust Web 开发领域迈出坚实的步伐。