构建一个rust生产应用读书笔记一(心跳检查)

发布于:2024-12-18 ⋅ 阅读:(50) ⋅ 点赞:(0)

撸起袖子加油干,通过阅读的方式学习rust,本文根据作者的观点以及自身学习的经验总结,把每天学习的内容记录下来。同时,希望志同道合的伙伴们共同学习、探讨及进步。

需求背景

作 为一名博客读者,我希望订阅时事通讯,以便在博客发布新内容时能够通过电子邮件收到更新通知,我们希望博客访客能够在网页上输入他们的电子邮件地址。表单将触发对后端服务器的API调用,由后端服务器处理信息并存储,然后返回响应。本章将重点介绍后端服务器的实现,我们将实现 /subscriptions POST 端点

策略

从零开始编写一个生产级的应用程序有许多的前置工作需要做

  1. 选择并熟悉一个web框架
  2. 定义测试策略
  3. 数据存储在哪里?需要一个可以交互的数据库
  4. 编写在实际代码中需要写的sql查询

在实际代码编写之前,我们做一个心跳检查的功能,如同其他语言框架(springcloud),在各个服务之间通过/health_check 检查应用状态

选择一个web框架

技术选型:actix-web

原因:actix-web框架有着广泛的社区支持,并且很多成熟的产品已经在生产环境中使用,并取得了良好的效果。

环境准备

集成开发环境: Vscode + rustanalyzer

rust 版本:1.8.1

首选创建一个zero2prod工程

cargo new zero2prod
执行完成之后代码结构如下:
zero2prod
 ├── Cargo.lock
 ├── Cargo.toml
 ├── README.md
 ├── src
    └── main.rs

Cargo.toml文件中添加项目依赖

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

[dependencies]
actix-web = "4"
tokio = { version = "1", features = ["macros", "rt-multi-thread"] }

通过使用 actix-web 库构建的简单 HTTP 服务器。它实现了两个路由,分别用于处理根路径 / 和带有名称参数的路径 /{name}

/src/main.rs

use actix_web::{web, App, HttpRequest, HttpServer, Responder};

///
///
/// greet 函数是一个异步函数,接受一个 HttpRequest 参数并返回一个实现了 Responder trait 的类型。
/// req.match_info().get("name"):从请求的匹配信息中获取名为 name 的参数。如果没有找到 name 参数,则使用默认值 "World"。
/// format!("Hello {}!", &name):生成一个包含问候消息的字符串。
///
async fn greet(req: HttpRequest) -> impl Responder {
    let name = req.match_info().get("name").unwrap_or("World");
    format!("Hello {}!", &name)
}

///
/// HttpServer::new(|| { ... }):创建一个新的 HTTP 服务器实例。传入一个闭包来配置应用程序,闭包相当于java中的lamda表达式。
///``` App::new():创建一个新的 App 实例,用于配置路由和中间件。
///.route("/", web::get().to(greet)):定义一个路由,处理根路径 / 的 GET 请求,并将其路由到 greet 函数。
///.route("/{name}", web::get().to(greet)):定义一个路由,处理路径 /{name} 的 GET 请求,
/// 并将其路由到 greet 函数。{name} 是一个动态参数,可以从 URL 中捕获。
///.bind("127.0.0.1:8000")?:将服务器绑定到本地地址 127.0.0.1 的端口 8000。
///.run().await:启动服务器并等待其完成。
#[tokio::main]
async fn main() -> std::io::Result<()> {
    HttpServer::new(|| {
        App::new()
            .route("/", web::get().to(greet))
            .route("/{name}", web::get().to(greet))
    })
    .bind("127.0.0.1:8000")?
    .run()
    .await
}

执行程序

cargo run

浏览器输入 http://127.0.0.1:8000/

可以看到返回结果 Hello World!

浏览器输入 http://127.0.0.1:8000/good

可以看到返回结果 Hello good!, 其中good表示

cargo install cargo-expand 是一个用于安装 cargo-expand 工具的命令。cargo-expand 是一个非常有用的 Rust 工具,它可以展开(expand)宏(macros),显示宏在编译时展开后的实际代码。这对于理解宏的工作原理和调试宏相关的问题非常有帮助。

用途

  1. 宏展开
  • cargo-expand 可以将宏展开后的代码输出到标准输出或文件中,帮助开发者理解宏的具体行为和生成的代码。

调试和学习

  • 通过查看宏展开后的代码,开发者可以更好地调试宏相关的问题,并学习宏是如何工作的。
  • 代码审查
  • 在代码审查过程中,使用 cargo-expand 可以确保宏生成的代码符合预期,避免潜在的错误。

安装

要安装 cargo-expand,可以使用 cargo 包管理器:

cargo install cargo-expand

使用 expand命令在展开宏,显示宏在编译器生成的实际代码,这对于调试宏相关的问题、学习宏的工作原理以及进行代码审查非常有用。通过使用 cargo-expand,你可以更好地理解宏的行为,确保代码的正确性和一致性。

当执行cargo expand后控制台输出的结果如下:

warning: unused manifest key: target.aarch64-apple-darwin.rustflags
warning: unused manifest key: target.x86_64-apple-darwin.rustflags
    Checking zero2prod v0.1.0 (/Users/kunliu/project/my/zero2prod)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 1.24s

#![feature(prelude_import)]
#[prelude_import]
use std::prelude::rust_2021::*;
#[macro_use]
extern crate std;
use actix_web::{web, App, HttpRequest, HttpResponse, HttpServer, Responder};
///
///
/// greet 函数是一个异步函数,接受一个 HttpRequest 参数并返回一个实现了 Responder trait 的类型。
/// req.match_info().get("name"):从请求的匹配信息中获取名为 name 的参数。如果没有找到 name 参数,则使用默认值 "World"。
/// format!("Hello {}!", &name):生成一个包含问候消息的字符串。
///
async fn greet(req: HttpRequest) -> impl Responder {
    let name = req.match_info().get("name").unwrap_or("World");
    HttpResponse::Ok()
        .content_type("text/plain")
        .body(
            ::alloc::__export::must_use({
                let res = ::alloc::fmt::format(format_args!("Hello {0}!", name));
                res
            }),
        )
}
///
/// HttpServer::new(|| { ... }):创建一个新的 HTTP 服务器实例。传入一个闭包来配置应用程序,闭包相当于java中的lamda表达式。
///``` App::new():创建一个新的 App 实例,用于配置路由和中间件。
///.route("/", web::get().to(greet)):定义一个路由,处理根路径 / 的 GET 请求,并将其路由到 greet 函数。
///.route("/{name}", web::get().to(greet)):定义一个路由,处理路径 /{name} 的 GET 请求,
/// 并将其路由到 greet 函数。{name} 是一个动态参数,可以从 URL 中捕获。
///.bind("127.0.0.1:8000")?:将服务器绑定到本地地址 127.0.0.1 的端口 8000。
///.run().await:启动服务器并等待其完成。
/// ```
fn main() -> std::io::Result<()> {
    let body = async {
        HttpServer::new(|| {
                App::new()
                    .route("/", web::get().to(greet))
                    .route("/{name}", web::get().to(greet))
            })
            .bind("127.0.0.1:8000")?
            .run()
            .await
    };
    #[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);
    }
}

接下来我们就正真开始一个健康检查的心跳功能函数

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

async fn health_check() -> impl Responder {
    HttpResponse::Ok()
}
///
/// HttpServer::new(|| { ... }):创建一个新的 HTTP 服务器实例。传入一个闭包来配置应用程序,闭包相当于java中的lamda表达式。
///``` App::new():创建一个新的 App 实例,用于配置路由和中间件。
///.route("/", web::get().to(greet)):定义一个路由,处理根路径 / 的 GET 请求,并将其路由到 greet 函数。
///.route("/{name}", web::get().to(greet)):定义一个路由,处理路径 /{name} 的 GET 请求,
/// 并将其路由到 greet 函数。{name} 是一个动态参数,可以从 URL 中捕获。
///.bind("127.0.0.1:8000")?:将服务器绑定到本地地址 127.0.0.1 的端口 8000。
///.run().await:启动服务器并等待其完成。
/// ```
#[tokio::main]
async fn main() -> std::io::Result<()> {
    HttpServer::new(|| App::new().route("/health_check", web::get().to(health_check)))
        .bind("127.0.0.1:8000")?
        .run()
        .await
}

返回结果

```
curl -v http://127.0.0.1:8000/health_check
*   Trying 127.0.0.1:8000...
* Connected to 127.0.0.1 (127.0.0.1) port 8000
> GET /health_check HTTP/1.1
> Host: 127.0.0.1:8000
> User-Agent: curl/8.6.0
> Accept: */*
> 
< HTTP/1.1 200 OK
< content-length: 0
< date: Tue, 12 Nov 2024 02:48:08 GMT
< 
* Connection #0 to host 127.0.0.1 left intact
``` 


网站公告

今日签到

点亮在社区的每一天
去签到