关于本Rust实战教程系列:
- 定位:一份从Rust基础迈向项目实战的练习记录。
- 内容:聚焦实战中遇到的具体问题、解决思路与心得总结。
- 读者 :适合有Rust基础,但急需项目经验巩固知识的开发者。资深玩家可忽略。
- 计划:本期为系列第二篇,将根据我的学习进度持续更新。
- 导航 :相关内容都已经放到专栏中:Rust实战课程
简要说明
本篇是之前的网络资源监控器(初版)的续集,在这篇文章中我将会继续丰富网络资源监控器的功能,当然本篇也没有结束,只是二版,因为完整的功能确实太多太多了,本身这系列也是自己的学习的记录,如果一次完成的话太复杂了,对我这种新手来说不是很友好,所以我这边会像堆积木一样一点点的来完成每个模块的内容,争取我们能完成一个可用的企业级网络资源监控器(牛逼吹起来了~);
需求内容
1. 搭建监控引擎的框架
- 支持多种监控类型(HTTP/HTTPS、Memory、TCP、PING等)
- 可配置的检查频率
- 并发执行监控检查
- 重试机制
- 超时控制
- 结果记录(包括响应时间、状态、错误信息等)
2.实现HTTP/HTTPS监控引擎
- 基础可用监控(BasicAvailability)
- 性能监控(PerformanceTimings)
- 安全监控(SslSecurity, SecurityHeaders)
- 内容验证监控(ContentVerification)
功能点详解
首先,我们将基于上一篇内容中的监控配置列表,搭建一个支持多监控类型的框架。在本次课程中,我们会一步步完成整体框架的基础结构,并重点实现 HTTP/HTTPS 类型的监控逻辑。其他监控类型(如 ICMP、TCP、DNS 等)暂时不在此次实现范围之内,将作为课后扩展练习留给大家自行完成与完善(其实主要是因为监控类型实在太多,写不动啦~)。
好的,我们来梳理一下核心任务。简单来说,这个框架需要支持多种监控类型(如 HTTP、HTTPS、DNS、TCP 等),并通过一个监控引擎来统一调度这些检查任务,最终汇总并输出所有类型的监控结果。
为了实现这个目标,我们可以将整体逻辑划分为以下两个核心部分来构建:
- 监控引擎:这是框架的大脑,负责调度和执行任务。它的主要工作是读取监控列表,依次调用不同监控类型的检查逻辑,并收集结果。
- 监控类型实现:这是框架的肌肉,包含各种监控类型(如 HTTP/HTTPS)的具体检查逻辑。每种类型都是一个独立的模块,负责执行专业检查并返回标准化的结果。
下面,我们就按照这两个模块来具体实现:
多类型监控引擎实现
在本次框架设计中,我们核心采用了 策略模式与工厂模式相结合 的架构方案:
- 策略模式 :我们将每种监控类型(如 HTTP、HTTPS)都定义为实现了统一
MonitorTrait 的独立策略。这确保了每种监控检查逻辑都高度内聚,可以独立开发和演变。 - 工厂模式:我们通过一个统一的工厂,根据配置来批量创建对应的监控器实例。这消除了代码中复杂的对象创建逻辑,实现了客户端与具体实现的解耦。
这样未来扩展新的监控类型(如 DNS、TCP)将变得异常简单:只需定义一个新的策略并实现 Monitor Trait 即可,无需修改引擎的核心调度逻辑。高内聚、低耦合,完美~~~
1. Monitor公共Trait的实现
首先我们来看下我们统一的Trait的定义,其实这个特征对象很简单,核心只需要实现一个检查函数即可
rust
// src/monitor/monitor_trait.rs
use super::types::{MonitorConfig, CheckResult};
use crate::tools_types::MonitorType;
// 定义监控类型的 trait 后续的不同的监控类型都实现这个 trait
// 在这里添加 Send 和 Sync 作为父 trait
// Send: 允许在线程间移动
// Sync: 允许在线程间共享引用 (&T)
// 对于 tokio::spawn 和多线程异步,这两个通常都需要。
#[async_trait::async_trait]
pub trait Monitor: Send + Sync {
async fn check(&self, config: &MonitorConfig) -> CheckResult;
fn get_type(&self) -> MonitorType;
}
对应的check函数只需要传入一个监控配置参数,然后返回一个检测结果就可以了,同时我们还多定义了一个get_type方法:用于获取当前的检测类型,这样的话其实我们就已经实现了我们统一的特征对象的定义任务了。 下面的话我们来具体的看下,我们抽象出来的监控配置参数MonitorConfig、检测结果CheckResult这两个对象的定义逻辑:
这里的话,我声明了一个types.rs模块,专门用来声明对应的结构体对象、枚举值对象等过程中使用到一下基础的类型定义,
1.1 监控配置参数MonitorConfig
rust
// src/monitor/types.rs 类型声明模块
// 定义核心的监控配置参数结构体
#[derive(Debug, Clone)]
pub struct MonitorConfig {
pub target: Option<String>, // 监控的目标URL或IP地址
pub interval: Option<u64>, // 监控间隔,单位秒
pub monitor_type: MonitorType,// 监控类型 HTTP TCP FTP等等
pub details: MonitorConfigDetail, // 这里的话是不同监控类型的具体参数,跟公共的一些参数区分开 详见14行代码声明
}
// 定义一个监控数据的详情监控参数 结构体 ,用于每个监控类型中的不同的参数类型
#[derive(Debug, Clone)]
pub enum MonitorConfigDetail {
Http(HttpMonitorConfig),
Https(HttpsMonitorConfig),
Ftp(FtpMonitorConfig),
Traceroute(TracerouteMonitorConfig),
Cpu(CpuMonitorConfig),
Memory(MemoryMonitorConfig),
Disk(DiskMonitorConfig),
Process(ProcessMonitorConfig),
Unknown(UnknownQueryConfig),
}
// 其中这些不同监控类型的机构体我们就不一一声明了 ,都是类似的,
// 异常监控的兜底类型
#[derive(Debug, Clone)]
pub struct UnknownQueryConfig{
pub description: String, // 异常描述信息
}
// HTTP监控的配置参数
#[derive(Debug, Clone)]
pub struct HttpMonitorConfig {
pub url: String, // 定义HTTP监控的URL
pub method: HttpMethodTypes, // GET, POST, etc.
pub timeout: u64, // 配置超时时间
pub headers: Option<HeaderMap<HeaderValue>>, // 可选的请求URL需要的HTTP头
pub body: Option<HttpBody>, // 可选的请求URL需要的body体
pub rules: Option<Vec<ContentVerificationRulesSingle>>, // 可配置的监控规则
}
// 其中监控的规则的话我们来简单实现了一下
//定义内容监控规则结构体明细字段
#[derive(Debug, Clone, Default, serde::Deserialize)]
pub struct ContentVerificationRulesSingle {
pub rule_type: ContentVerificationRules, // 只支持Contains、NotContains、Regex三种不同的规则
pub rule_content: String, // 每种规则的关键字
pub rule_description: String, // 规则的描述字段
}
1.2 检测结果CheckResult
rust
// 定义检查结果结构体
#[derive(Debug, Clone)]
pub struct CheckResult {
pub id: u128, // 监控记录的ID 唯一标识
pub monitor_type: MonitorType, // 监控类型
pub target: Option<String>, // 监控的目标URL或IP地址
pub status: bool, // 监控状态,true表示正常,false表示异常
pub details: CheckResultDetail, // 监控结果的详细信息,依然是每种不同的监控类型都是不同的监控结果
}
// 定义不同类型的监控返回结果详情结构体
#[derive(Debug, Clone)]
pub enum CheckResultDetail {
Http(HttpMonitorResult),
Https(HttpsMonitorResult),
Ftp(FtpMonitorResult),
Traceroute(TracerouteMonitorResult),
Cpu(CpuMonitorResult),
Memory(MemoryMonitorResult),
Disk(DiskMonitorResult),
Process(ProcessMonitorResult),
Icmp(IcmpMonitorResult),
Tcp(TcpMonitorResult),
Udp(UdpMonitorResult),
Dns(DnsMonitorResult),
Unknown(UnknownQueryResult),
}
// 定义一个保存http监控的最终结果参数结构体
#[derive(Debug, Clone, Default)]
pub struct HttpMonitorResult{
pub basic_avaliable: BasicAvailability, // 包含基础的检测内容
pub response_headers: SecurityHeaders, // 安全检测相关的头信息
pub performance_timings: PerformanceTimings, // 性能指标相关信息
pub certificate_info: CertificateInfo, // SSL证书信息
pub content_verification: ContentVerificationResult, // 内容检测相关信息
pub advanced_avaliable: AdvancedAvailability, // 高级监控相关信息
}
// 这里的话我们就不过多展示HTTP中检测的各种类型的具体的实现Struct了,在后面我们会详细的讲解每一种类型的检测内容都包含哪些具体的字段值
通过前面的讲解,我们已经完成了公共 Trait 的定义与相关参数的声明,奠定了框架的核心基础。接下来,我们将进入监控引擎工厂的实现部分。
首先,我们来明确一下工厂的核心职责:监控引擎工厂用于根据不同的监控类型,动态创建对应的监控引擎实例。举个例子,当我需要监控一个 HTTP 服务时,我只需告知工厂"我需要一个 HTTP 监控引擎",工厂便会返回一个专门用于执行 HTTP 检查的引擎实例。
理解了这个核心概念后,具体的代码实现就会显得清晰而直接:
rust
src/monitor/mod.rs
pub mod types;
use crate::tools_types::MonitorType; // 全局通用的类型定义模块
// 引进特征对象
pub mod monitor_trait;
use monitor_trait::Monitor;
// 声明具体的监控引擎
mod icmp_monitor;
mod tcp_monitor;
mod udp_monitor;
mod dns_monitor;
mod http_monitor;
mod ftp_monitor;
mod traceroute_monitor;
mod cpu_monitor;
mod memory_monitor;
mod disk_monitor;
mod process_monitor;
// 不同类型的监控系统
use icmp_monitor::IcmpMonitor;
use tcp_monitor::TcpMonitor;
use udp_monitor::UdpMonitor;
use dns_monitor::DnsMonitor;
use http_monitor::HttpMonitor;
use ftp_monitor::FtpMonitor;
use traceroute_monitor::TracerouteMonitor;
use cpu_monitor::CpuMonitor;
use memory_monitor::MemoryMonitor;
use disk_monitor::DiskMonitor;
use process_monitor::ProcessMonitor;
// 定义监控工厂
pub struct MonitorFactory;
impl MonitorFactory {
// 基于策略模式 工厂函数返回一个实现特征Monitor的监控引擎
// 入参为监控类型,出参为具体的监控引擎
// 通过Box<>统一返回类型,同时因为尺寸未知必须要放在Box后面,同时通过vTable调用实现多态,调用端无需关注具体类型
pub fn create_monitor(monitor_type: MonitorType) -> Box<dyn Monitor> {
match monitor_type {
MonitorType::Icmp => Box::new(IcmpMonitor::new()),
MonitorType::Tcp => Box::new(TcpMonitor::new()),
MonitorType::Udp => Box::new(UdpMonitor::new()),
MonitorType::Dns => Box::new(DnsMonitor::new()),
MonitorType::Http => Box::new(HttpMonitor::new()),
MonitorType::Ftp => Box::new(FtpMonitor::new()),
MonitorType::Traceroute => Box::new(TracerouteMonitor::new()),
MonitorType::Cpu => Box::new(CpuMonitor::new()),
MonitorType::Memory => Box::new(MemoryMonitor::new()),
MonitorType::Disk => Box::new(DiskMonitor::new()),
MonitorType::Process => Box::new(ProcessMonitor::new()),
}
}
}
接下来,我们将定义具体的监控引擎模块。所有监控引擎均遵循相同的结构:实现统一的 Monitor trait,并通过完善 check 方法,来承载各类监控任务的具体逻辑,为便于理解,这里我们仅以其中一个监控类型为例进行展示,其他类型的实现方式与此类似。
rust
// src/monitor/cpu_monitor.rs
use super::monitor_trait::Monitor;
use super::types::{ MonitorConfig, CheckResult, CheckResultDetail, CpuMonitorResult};
use crate::tools_types::MonitorType;
pub struct CpuMonitor {
}
impl CpuMonitor {
pub fn new() -> Self {
CpuMonitor {}
}
}
// 用来让trait中的异步方法可用,详细的请查看Q&A
#[async_trait::async_trait]
impl Monitor for CpuMonitor {
async fn check(&self, config: &MonitorConfig) -> CheckResult {
CheckResult {
id: uuid::Uuid::new_v4().as_u128(),
monitor_type: MonitorType::Cpu,
target: config.target.clone(),
status: true,
details: CheckResultDetail::Cpu(CpuMonitorResult::default())
}
}
fn get_type(&self) -> MonitorType {
MonitorType::Cpu
}
}
在完成了多类型监控引擎的声明与模块定义之后,我们的各个引擎已经具备了独立运行的能力。然而,引擎本身需要被协调和驱动,这正是调度器所承担的核心角色。
调度器作为整个系统的中枢,负责串联起完整的监控流程:它首先获取监控任务列表,随后根据每个任务的具体配置参数,动态调用对应的监控引擎来执行监控检查。简而言之,调度器不关心具体如何监控,而是专注于"何时"以及"如何调度"监控任务。
接下来,我们就具体实现这个关键的监控调度器:
2.监控引擎调度器的具体实现
调度器虽承担着系统"中枢"的名号,但在各监控引擎已封装完备的前提下,其核心职责确实清晰而聚焦------它无需关心具体的检测逻辑,只需专注于调度本身。 具体来说,它需要完成三个核心任务:
- 异步调度:并发地调用各个监控引擎的检测方法;
- 循环执行:按预设间隔启动轮询任务,实现持续监控;
- 结果收集:汇总并输出每次任务的执行结果。
下面,我们就来具体实现这一调度逻辑:
rust
use std::{ time::Duration}; // 引入时间模块
// 定义公用的枚举值模块 这样在其他的地方 可以通过 use crate::tools_types..来使用
pub mod tools_types;
use tools_types::{ convert_to_monitor_config };
// 异步监控模块,用于实现异步调度
mod async_monitor;
use async_monitor::AsyncMonitor;
// 定义文件读取模块,封装通用的文件处理操作
mod tools;
use tools::file_tool::{read_json_file};
// 定义监控引擎模块,引入监控引擎工厂对象
pub mod monitor;
use monitor::{ MonitorFactory } ;
#[tokio::main]
async fn main() {
println!("网络监控器 启动...");
// 获取需要监控的网站列表,这里的话我们改成了一个JSON文件,每个监控引擎参数都是一个对象,方便后续通过页面来配置,类似下面的数据结构:
// [{
// "target": "https://www.google.com",
// "monitor_type": "HTTP",
// "method": "GET",
// "params": {},
// "headers": {},
// "rules": [
// {
// "rule_type": "contains",
// "rule_content": "Google",
// "rule_description": "检查页面是否包含 'Google' 字符串"
// }
// ],
// "timeout": 5000
// }]
let monitor_website_list = read_json_file("monitor_list.json");
match monitor_website_list {
Ok(monitor_list) => {
// 存储线程的运行结果
let mut result_monitors = Vec::new();
for monitor in monitor_list {
// 创建一个具体的监控器实例
let target = MonitorFactory::create_monitor(monitor.monitor_type);
// 把监控配置文件中的对象转换成监控器检测时需要的对象
let monitor_config = convert_to_monitor_config(&monitor);
// 如果是一个 轮询监控 创建一个轮询监控器
match monitor.interval {
Some(interval) => {
// 创建一个轮询监控调度器
let async_monitor = AsyncMonitor::create_interval_monitoring(target, monitor_config).await;
result_monitors.push(async_monitor);
},
None => {
// 创建一个单次监控调度器
let once_monitor = AsyncMonitor::create_once_monitoring(target, monitor_config).await;
result_monitors.push(once_monitor);
}
}
}
// 处理所有的监控结果
for monitor_receiver in result_monitors {
// 使用tokio::spawn异步任务来监控不同类型的调度器,避免阻塞主进程
tokio::spawn(async move {
// 这里使用一个循环来接收所有的监控结果
let mut result_receiver = monitor_receiver;
while let Some(result) = result_receiver.recv().await {
// 直接打印相关的检测结果,后续会接入H5页面来实现
println!("Received result: {:?}", result);
}
});
}
println!("所有监控已启动,程序将继续运行");
// 你可以根据需要调整这个时间或者使用其他方式保持程序运行
tokio::time::sleep(Duration::from_secs(60)).await; // 让程序运行60s然后程序就会退出
},
Err(err) => {
eprintln!("Error reading monitor list: {}", err.to_string());
}
}
println!("所有 URL 检查完毕");
}
其中上面使用到了轮询调度器和单次调度器,下面我们来具体的看下相关的实现逻辑:
rust
src/async_monitor.rs
use tokio::time::{interval, Duration};
use crate::monitor::monitor_trait::Monitor;
use crate::monitor::types::{MonitorConfig, CheckResult};
// 1. 引入mpsc 通道概念 用于创建通道 并将接收端返回给调用者
use tokio::sync::mpsc;
use std::pin::Pin;
// 这里统一返回异步监控的类型,方便后续进行统一的监控结果检测
type MonitorResultReceiver = mpsc::Receiver<CheckResult>;
type PinnedReceiverFuture = Pin<Box<dyn Future<Output = MonitorResultReceiver> + Send>>;
pub struct AsyncMonitor {
}
impl AsyncMonitor {
// 创建一个轮询的监控器
pub fn create_interval_monitoring(target: Box<dyn Monitor>, config: MonitorConfig) -> PinnedReceiverFuture {
Box::pin(async move {
let (tx, rx) = mpsc::channel(100);
tokio::spawn(async move {
let mut interval: tokio::time::Interval = interval(Duration::from_secs(config.interval.unwrap()));
loop {
interval.tick().await;
let check_result = target.check(&config).await;
if tx.send(check_result).await.is_err() {
println!("Error sending result to channel");
}
}
});
// 返回接收端 对象
rx
})
}
// 创建一个单次监控
pub fn create_once_monitoring( target: Box<dyn Monitor>, config: MonitorConfig) -> PinnedReceiverFuture {
Box::pin(async move {
let (tx, rx) = mpsc::channel(1);
tokio::spawn(async move {
let check_result = target.check(&config).await;
if tx.send(check_result).await.is_err() {
println!("Error sending result to channel");
}
});
// 返回接收端 对象
rx
})
}
}
🎉 热烈祝贺! 🎉
如果您已经跟随到这里,那么恭喜------您已经成功完成了整个监控系统核心框架的搭建!这是值得👏掌声的重要里程碑!
根据我们最初的规划,接下来将进入具体监控类型的实现阶段。在本次实现中,我们将聚焦于 HTTP 监控 的详细逻辑开发,这也是最常用和基础的服务监控类型。
至于其他监控类型(如 TCP、DNS、ICMP 等),我们将它们留作大家的课后实践作业------当然,也不排除我后续会悄悄更新补充的可能性 🙄
实现HTTP/HTTPS监控引擎
基于之前的需求分析,我们已经明确了 HTTP 监控所需实现的功能。现在,我们将根据既定的设计,开始定义与之对应的监控器结构体。
1.1 定义HTTP监控类型结构体
rust
// src/tools_types.rs
// 定义一个struct 来保存http相关监控的最终结果参数结构体
#[derive(Debug, Clone, Default)]
pub struct HttpMonitorResult{
pub basic_avaliable: BasicAvailability,
pub response_headers: SecurityHeaders,
pub performance_timings: PerformanceTimings,
pub certificate_info: CertificateInfo, // SSL证书信息
pub content_verification: ContentVerificationResult,
pub advanced_avaliable: AdvancedAvailability,
}
在上面我们已经看到了HTTP监控引擎最后的监控结果了,包含6个模块:
BasicAvailability:基础监控结果,包括是否可访问、状态码、协议类型等等
rust
// 定义一个struct 来保存基本的HTTP可用性信息
#[derive(Debug, Clone, Default)]
pub struct BasicAvailability{
pub is_reachable: bool, // 是否可达
pub dns_resolvable: bool, // DNS解析是否成功
pub tcp_connect_success: bool, // TCP链接是否成功
// HTTP相关
pub res_received: bool, // 是否收到响应
pub res_status_code: Option<u16>, // 响应状态码
pub res_status_category: StatusCategory, // 响应状态码类别
pub protocol_version: String, // HTTP协议版本(如HTTP/1.1, HTTP/2)
pub res_content_type: Option<String>, // 响应内容类型
pub res_content_length: u64, // 响应内容长度
pub res_charset: Option<String>, // 响应字符集
}
SecurityHeaders:安全头相关监控结果,是否包含一些安全头信息:strict_transport_security、content_security_policy等等
rust
// 定义一个struct 来保存安全头监控信息
#[derive(Debug, Clone, Default)]
pub struct SecurityHeaders{
pub strict_transport_security: Option<String>, // 是否存在Strict-Transport-Security头
pub content_security_policy: Option<String>, // 是否存在Content-Security-Policy头
pub x_content_type_options: Option<String>, // 是否存在X-Content-Type-Options头
pub x_frame_options: Option<String>, // 是否存在X-Frame-Options头
pub x_xss_protection: Option<String>, // 是否存在X-XSS-Protection头
pub referrer_policy: Option<String>, // 是否存在Referrer-Policy头
pub feature_policy: Option<String>, // 是否存在Feature-Policy头
pub permissions_policy: Option<String>, // 是否存在Permissions-Policy头
pub security_headers_ok: bool, // 是否所有安全头都存在
}
PerformanceTimings:性能指标,包括TCP连接时间、TLS链接时间、连接耗时等等
rust
// 创建一个枚举类型,表示性能指标的类别
#[derive(Debug, Clone, Default)]
pub struct PerformanceTimings {
// 性能指标相关
pub dns_lookup_time: u128, // DNS查询时间,单位毫秒
pub tcp_connect_time: u128, // TCP连接时间,单位毫秒
pub tls_handshake_time: u128, // TLS握手时间,单位毫秒
pub first_byte_time: u128, // 首字节时间,单位毫秒
pub content_download_time: u128, // 内容下载时间,单位毫秒
pub ssl_negotiation_time: u128, // SSL协商时间,单位毫秒
pub ssl_cert_valid: bool, // SSL证书是否有效
pub request_sending_time: u128, // 请求发送时间,单位毫秒
pub server_processing_time: u128, // 服务器处理时间,单位毫秒
pub response_receiving_time: u128, // 响应接收时间,单位毫秒
pub total_time: u128, // 总时间,单位毫秒
}
CertificateInfo:SSL证书相关内容,包括证书编号、是否有效等等
rust
#[derive(Debug, Clone, Default)]
pub struct CertificateInfo {
pub issuer: Option<String>, // 证书颁发者
pub subject: Option<String>, // 证书主题
pub valid_from: Option<String>, // 证书有效期开始时间
pub valid_until: Option<String>, // 证书有效期结束时间
pub serial_number: Option<String>, // 证书序列号
pub signature_algorithm: Option<String>, // 签名算法
pub public_key_algorithm: Option<String>, // 公钥算法
pub public_key_size: Option<usize>, // 公钥大小
pub is_valid: bool, // 证书是否有效
}
ContentVerificationResult:响应内容监控结果,包括是否包含某字段、正则匹配内容等
rust
// 创建一个内容验证结果
#[derive(Debug, Clone, Default)]
pub struct ContentVerificationResult {
pub match_rules: Vec<ContentVerificationRulesResult>, // 匹配的规则
pub failed_rules: Vec<ContentVerificationRulesResult>, // 未匹配的规则 及其原因
}
// 定义核心内容验证返回数据结构体
#[derive(Debug, Clone, Default)]
pub struct ContentVerificationRulesResult {
pub rule_id: u64,
pub status:StatusInfo,
pub message: String,
pub rules: ContentVerificationRulesSingle
}
//定义内容监控规则结构体明细字段
#[derive(Debug, Clone, Default, serde::Deserialize)]
pub struct ContentVerificationRulesSingle {
pub rule_type: ContentVerificationRules,
pub rule_content: String,
pub rule_description: String,
}
// 内容验证类别
#[derive(Debug, Clone, serde::Deserialize)]
pub enum ContentVerificationRules {
#[serde(rename = "contains")]
Contains, // 响应内容中包含指定字符串
#[serde(rename = "not_contains")]
NotContains, // 响应内容中不包含指定字符串
#[serde(rename = "regex")]
Regex, // 响应内容匹配正则表达式
// 定义一个默认值 用于匹配
#[serde(rename = "default")] // 定义默认值
Default,
}
// 设置默认值
impl Default for ContentVerificationRules {
fn default() -> Self {
ContentVerificationRules::Default
}
}
#[derive(Debug, Clone, serde::Deserialize)]
pub enum StatusInfo {
Success,
Failed,
Unknown,
}
impl Default for StatusInfo {
fn default() -> Self {
StatusInfo::Unknown
}
}
- AdvancedAvailability:高级监控内容,包含事物监控等,本次先不涉及...
rust
// 高级可用性类别 业务指标监控 事务监控 等
#[derive(Debug, Clone, Default)]
pub struct AdvancedAvailability {
pub bussiness_metrics: HashMap<String, String>,
}
现在,我们已经完成了监控结果结构体的定义,接下来要做的就是逐步填充每个字段的值------这就像完成拼图的最后一块,当所有字段都被正确赋值时,整个HTTP监控模块的实现也就圆满完成了。
让我们立即开始这最后的实现环节,为每个字段注入实际的数据内容。
1.2 HTTP监控引擎的实现
现在,让我们将视线转回最初设计的 HTTP 监控引擎。在之前搭建框架时,我们已经实现了一个基础的结果返回逻辑。接下来,我们将在此基础上逐步深化,填充更多实际功能。
正如前文所提到的,我们将继续使用 reqwest 库来处理 HTTP 请求。其基本用法在此不再赘述,现在让我们集中精力,进一步完善 HttpMonitor 引擎的完整逻辑。
rust
src/monitor/http_monitor.rs
pub struct HttpMonitor {
client: Client,
}
impl HttpMonitor {
pub fn new() -> Self {
// 创建一个Client,方便后续进行URL调用
HttpMonitor {
client: Client::builder()
.redirect(reqwest::redirect::Policy::limited(5)) // 重定向次数限制为5次
.build().expect("Failed to create HTTP client"),
}
}
// 获取一个http 客户端链接 根据 method 方法来判断 创建一个http 链接
pub fn get_client(&self, config: &HttpMonitorConfig) -> reqwest::RequestBuilder {
// 创建一个http 请求
let mut request_client = match config.method {
HttpMethodTypes::Get => self.client.get(config.url.as_str()),
HttpMethodTypes::Post => self.client.post(config.url.as_str()),
HttpMethodTypes::Put => self.client.put(config.url.as_str()),
HttpMethodTypes::Delete => self.client.delete(config.url.as_str()),
HttpMethodTypes::Head => self.client.head(config.url.as_str()),
HttpMethodTypes::Patch => self.client.patch(config.url.as_str()),
_ => self.client.get(config.url.as_str()), // 默认使用 GET 方法
};
// 根据配置信息 设置超时时间
request_client = request_client.timeout(std::time::Duration::from_millis(config.timeout));
// 如果用户自定义了Header头信息 在这里要添加进去
if let Some(headers) = &config.headers {
request_client = request_client.headers(headers.clone());
}
// 兼容几种不同的body类型,分别进行设置
if let Some(body) = &config.body {
match body {
HttpBody::Text(text) => {
request_client = request_client.body(text.clone());
},
HttpBody::Binary(bin) => {
request_client = request_client.body(bin.clone());
},
HttpBody::Json(json_value) => {
request_client = request_client.json(json_value);
},
HttpBody::Empty => {request_client = request_client.body("");},
}
}
// 最终返回一个HTTP连接 Client
request_client
}
// 创建一个生成基础监控结果的函数
fn create_basic_result(&self, ...) -> BasicAvailability {🚗🚗🚗🚗🚗🚗🚗}
// 创建一个生成安全头监控结果的函数
fn create_headers_result(&self,...) -> SecurityHeaders{🚗🚗🚗🚗🚗🚗🚗}
// 创建一个生成性能指标的监控结果的函数
fn create_performance_timings(&self,...) -> PerformanceTimings {🚗🚗🚗🚗🚗🚗🚗}
// 创建一个校验响应内容监控结果的函数
fn create_content_verification_result(&self,...) -> ContentVerificationResult{🚗🚗🚗🚗🚗🚗🚗}
// 创建高级可用性结果 直接返回一个默认结果
fn create_advanced_availability_result(&self) -> AdvancedAvailability {
AdvancedAvailability {
bussiness_metrics: HashMap::new(),
}
}
}
// 下面的话 就是要实现具体的check方法了
#[async_trait::async_trait]
impl Monitor for HttpMonitor{
async fn check(&self, config: &MonitorConfig) -> CheckResult {
// 这里就是我们的主战场了 首先我们先把异常情况写完,然后在后面进行一点点的填空
// 判断一下是否存在 target 此时的target 应该是一个 URL 地址
if config.target.is_none() {
return CheckResult {
id: uuid::Uuid::new_v4().as_u128(), // V4 uuid 一种随机数 基于时间戳和随机数 V4版本的生成功能
monitor_type: MonitorType::Http,
target: None,
status: false, // 监控状态失败 根本就没有进入监控当中去
details: CheckResultDetail::Http(HttpMonitorResult::default()), // default 表示调用Default trait方法,默认值为每个字段的默认值
};
}
// 来处理具体的HTTP监控的类型数据
match config.details {
MonitorConfigDetail::Http(ref detail) => {
// 在这里的话 我们在前一章里面也看到了 首先我们要发送HTTP请求到Target地址
let request_client = self.get_client(detail);
// 发送请求,来匹配返回的结果
match request_client.send().await{
Ok(response)=> {
// 实际上我们要做的就是把这个地方的逻辑来一点点的完善 好 如果细心的你看到这里的话,别急 我们这个里面的内容请往下翻哦~~~
🛺🛺🛺🛺🛺🛺🛺🛺上车准备出发喽!!!!🛺🛺🛺🛺🛺🛺🛺🛺
},
Err(e) => {
// 检查各种错误类型
return CheckResult {
id: uuid::Uuid::new_v4().as_u128(), // V4 uuid 一种随机数 基于时间戳和随机数 V4版本的生成功能
monitor_type: MonitorType::Http,
target: config.target.clone(),
status: true, // 代表监控状态为成功,但是target访问失败了
details: CheckResultDetail::Http(HttpMonitorResult::default()),
};
},
}
},
_ => {
// 如果不是 HTTP 监控类型,返回错误结果
return CheckResult {
id: uuid::Uuid::new_v4().as_u128(), // V4 uuid 一种随机数 基于时间戳和随机数 V4版本的生成功能
monitor_type: MonitorType::Http,
target: None,
status: false, // 监控状态失败 根本就没有进入监控当中去
details: CheckResultDetail::Unknown(UnknownQueryResult {
description: "调用监控类型: HTTP, 请查实后再继续操作".to_string(),
query_type: MonitorType::Http,
}),
};
},
}
}
// 定义返回具体类型的函数
fn get_type(&self) -> MonitorType {
MonitorType::Http
}
}
好,细心的你应该已经发现上面的代码需要填空的地方我都已经做了标注,现在我们来一个一个的实现具体的逻辑即可:
拼接基础监控信息函数create_basic_result:这个地方没有什么复杂的内容,直接根据response进行处理相关的数据即可
rust
// 解析成基础的响应结果结构体
fn create_basic_result(&self, status_code: u16, version: reqwest::Version, headers: &reqwest::header::HeaderMap, content_length: Option<u64> ) -> BasicAvailability {
BasicAvailability {
is_reachable: true, // 代表这个target 是可以访问的
dns_resolvable: true, // 代表这个target 的域名是可以解析的
tcp_connect_success: true, // 代表这个target 的 TCP 连接是成功的
res_received: true,
res_status_code: Some(status_code),
res_status_category: StatusCategory::from_status_code(status_code),//获取对应的状态码描述信息
protocol_version: format!("{:?}", version), // 获取HTTP协议版本
res_content_type: headers.get(reqwest::header::CONTENT_TYPE).and_then(|v| v.to_str().ok()).map(|s| s.to_string()),
res_content_length: content_length.unwrap_or(0),
res_charset: headers.get(reqwest::header::ACCEPT_CHARSET).and_then(|v| v.to_str().ok()).map(|s| s.to_string()),
}
}
// 定义状态码对应的状态枚举类型
#[derive(Debug, Clone)]
pub enum StatusCategory {
Informational,
Success,
Redirection,
ClientError,
ServerError,
Unknown,
}
// 给对应的枚举值 添加方法 来获取不同的状态码的实际状态
impl StatusCategory {
pub fn from_status_code(status_code: u16) -> Self {
match status_code {
100..=199 => StatusCategory::Informational,
200..=299 => StatusCategory::Success,
300..=399 => StatusCategory::Redirection,
400..=499 => StatusCategory::ClientError,
500..=599 => StatusCategory::ServerError,
_ => StatusCategory::Unknown,
}
}
}
解析安全头信息函数create_headers_result:也是相对比较简单的,就是获取返回请求头里面有么有一些安全字段值,最后判断如果大于4个字段的话就设置security_headers_ok为true
rust
// 解析相关头信息
fn create_headers_result(&self, headers: &reqwest::header::HeaderMap) -> SecurityHeaders {
SecurityHeaders {
strict_transport_security: headers.get("Strict-Transport-Security").as_ref().map(|v| v.to_str().unwrap_or("").to_string()),
content_security_policy: headers.get("Content-Security-Policy").as_ref().map(|v| v.to_str().unwrap_or("").to_string()),
x_content_type_options: headers.get("X-Content-Type-Options").as_ref().map(|v| v.to_str().unwrap_or("").to_string()),
x_frame_options: headers.get("X-Frame-Options").as_ref().map(|v| v.to_str().unwrap_or("").to_string()),
x_xss_protection: headers.get("X-XSS-Protection").as_ref().map(|v| v.to_str().unwrap_or("").to_string()),
referrer_policy: headers.get("Referrer-Policy").as_ref().map(|v| v.to_str().unwrap_or("").to_string()),
feature_policy: headers.get("Feature-Policy").as_ref().map(|v| v.to_str().unwrap_or("").to_string()),
permissions_policy: headers.get("Permissions-Policy").as_ref().map(|v| v.to_str().unwrap_or("").to_string()),
security_headers_ok: self.check_security_headers_ok(&headers),
}
}
// 检测是否安全头有效
fn check_security_headers_ok(&self, headers: &reqwest::header::HeaderMap) -> bool {
let mut score = 0;
if headers.get("Strict-Transport-Security").is_some() {
score += 1;
}
if headers.get("Content-Security-Policy").is_some() {
score += 1;
}
if headers.get("X-Content-Type-Options").is_some() {
score += 1;
}
if headers.get("X-Frame-Options").is_some() {
score += 1;
}
if headers.get("X-XSS-Protection").is_some() {
score += 1;
}
if headers.get("Referrer-Policy").is_some() {
score += 1;
}
if headers.get("Permissions-Policy").is_some() {
score += 1;
}
score >= 4 // 例如,至少有4个安全头部存在则认为安全头部检查通过
}
获取性能指标参数函数create_performance_timings:这里的话reqwest并没有直接提供获取DNS、TCP、TLS相关的数据指标,所以这里的话我们还需要定义另外的方法来实现
rust
// 获取性能参数
fn create_performance_timings(&self, start_time: std::time::Instant, start_time_unix: u128) -> PerformanceTimings {
let total_time = start_time.elapsed().as_millis();
PerformanceTimings {
dns_lookup_time: 0,
tcp_connect_time: 0,
tls_handshake_time: 0,
first_byte_time: 0, // mor
content_download_time: 0,
ssl_negotiation_time: 0,
ssl_cert_valid: true,
request_sending_time: start_time_unix,
server_processing_time: 0,
response_receiving_time: total_time,
total_time: total_time,
}
}
// 单独定义一个获取TCP 、DNS、TLS的函数
//定义一个函数返回类型 一个元组即可
type DnsTcpTlsPerformance = (u128, u128, u128, Option<CertificateInfo>); // DNS时间,TCP时间,TLS时间,证书信息
// 计算 性能监控相关数据 DNS TCP TLS 三个数据耗时 以及在连接过程中SSL证书相关信息
pub async fn get_dns_tcp_tls_performance(url: &str) -> Result<DnsTcpTlsPerformance, Box<dyn std::error::Error>> {
// 使用Url进行解析相关的地址
let url = reqwest::Url::parse(url)?;
let host = url.host_str().ok_or("Invalid URL")?;
let port = url.port_or_known_default().unwrap_or(80);
let resolver = TokioAsyncResolver::tokio_from_system_conf()?;
let dns_lookup_time =Instant::now();
let ips = resolver.lookup_ip(host).await?;
// 开始设置DNS的缓存时间
let dns_lookup_ms = dns_lookup_time.elapsed().as_millis();
// 开始设置TCP的缓存时间 以及 TLS的时间
// 遍历相关的IP 地址 取其中最小的一个时间即可
let mut tls_min_time: Option<u128> = None;
let mut tcp_min_time: Option<u128> = None;
let per_addr_timeout = Duration::from_secs(3);
let is_https = url.scheme() == "https";
// 复用 TLS 连接器
let tls_connector = if is_https {
Some(TokioTlsConnector::from(TlsConnector::new()?))
} else {
None
};
let mut ssl_certificate_info: Option<CertificateInfo> = None;
for ip in ips.iter(){
let addr = std::net::SocketAddr::new(ip, port);
let start_tcp = Instant::now();
// 设置一个超时时间 避免个别IP无法连接阻塞后续的连接
let tcp_res = timeout(per_addr_timeout, TcpStream::connect(addr)).await;
let tcp_stream = match tcp_res {
Ok(Ok(stream)) => {
let ms = start_tcp.elapsed().as_millis();
// 设置TCP链接时间
tcp_min_time = tcp_min_time.map_or(Some(ms), |min| Some(min.min(ms)));
stream
},
_ => continue, // 超时或连接失败,尝试下一个地址
};
if let Some(tls) = &tls_connector {
let hs_start = Instant::now();
let hs_res = timeout(per_addr_timeout, tls.connect(host, tcp_stream)).await;
if let Ok(Ok(_tls_stream)) = hs_res {
let ms = hs_start.elapsed().as_millis();
// 设置TLS的连接时间
tls_min_time = tls_min_time.map_or(Some(ms), |min| Some(min.min(ms)));
// 检测证书有效性可以在这里进行
if ssl_certificate_info.is_none() {
if let Some(cert) = _tls_stream.get_ref().peer_certificate()? {
let cert_der = cert.to_der()?;
// from_der 返回 nom::IResult,需要手动 map_err
let (_, x509_cert) = X509Certificate::from_der(&cert_der)
.map_err(|e| io::Error::new(io::ErrorKind::InvalidData, format!("x509 parse error: {e}")))?;
let validity = x509_cert.validity();
let now = std::time::SystemTime::now();
let not_before = validity.not_before.to_datetime();
let not_after = validity.not_after.to_datetime();
let days_until_expiry = (not_after - now).whole_days();
// 这里的话需要查看相关的API文档来获取相关的库的信息
let info = CertificateInfo {
issuer: Some(x509_cert.issuer().to_string()),
subject: Some(x509_cert.subject().to_string()),
valid_from: Some(not_before.to_string()),
valid_until: Some(not_after.to_string()),
serial_number: Some(x509_cert.tbs_certificate.serial.to_string()),
signature_algorithm: Some(x509_cert.signature_algorithm.algorithm.to_string()),
public_key_algorithm: Some(format!("{:?}", x509_cert.public_key().algorithm)),
public_key_size: Some(x509_cert.public_key().subject_public_key.data.len() * 8), // 转换为位数
is_valid: days_until_expiry > 0,
};
ssl_certificate_info = Some(info);
}
}
}
}
}
let tcp = tcp_min_time.ok_or("all TCP connect attempts failed")?;
let tls = if is_https { tls_min_time.unwrap_or(0) } else { 0 };
// 返回相关结果
Ok((dns_lookup_ms, tcp, tls, ssl_certificate_info))
}
创建响应内容校验函数create_content_verification_result:我们获取到body内容,然后执行相关的校验逻辑即可,
rust
// 创建内容验证结果
fn create_content_verification_result(&self,body: String, rules: &Option<Vec<ContentVerificationRulesSingle>>) -> ContentVerificationResult {
// 判空处理
if(rules.is_none() || body.is_empty()){
return ContentVerificationResult {
match_rules: vec![],
failed_rules: vec![],
};
}
let rules = rules.as_ref().unwrap();
let mut match_rules = vec![];
let mut failed_rules = vec![];
for ContentVerificationRulesSingle{rule_type, rule_content,rule_description } in rules.iter() {
let status = match rule_type {
// 是否包含哪些内容?
ContentVerificationRules::Contains => {
body.contains(rule_content)
},
// 是否不包含哪些内容?
ContentVerificationRules::NotContains => {
!body.contains(rule_content)
},
// 匹配正则表达式
ContentVerificationRules::Regex => {
let regex = Regex::new(&rule_content).unwrap();
regex.is_match(&body)
},
_ => {
false
},
};
self.get_content_verify_result(status, rule_type.clone(), rule_content.clone(), rule_description.clone(), &mut match_rules, &mut failed_rules);
}
ContentVerificationResult {
match_rules: match_rules,
failed_rules: failed_rules,
}
}
// 拼接内容校验的内容 因为不管是什么类型 返回的数据结构都是一样的
fn get_content_verify_result(&self, status: bool, rule_type: ContentVerificationRules, rule_content: String, rule_description: String, true_Rules: &mut Vec<ContentVerificationRulesResult>, failed_Rules: &mut Vec<ContentVerificationRulesResult>) {
let result = ContentVerificationRulesResult {
rule_id: Uuid::new_v4().as_u128() as u64,
status: if status { StatusInfo::Success } else { StatusInfo::Failed },
message: if status { "Rule verification passed".to_string() } else { "Rule verification failed".to_string() },
rules: ContentVerificationRulesSingle{rule_type: rule_type.clone(),rule_content: rule_content.clone(),rule_description: rule_description.clone()}
};
if status {
true_Rules.push(result);
} else {
failed_Rules.push(result);
}
}
上面的这几个函数定义完来的话,其实我们的HTTP监控引擎的内容就已经全部完成了,现在我们最后来看下HTTP监控中我们待完成的那一段代码就可以了:
rust
impl Monitor for HttpMonitor {
async fn check(&self, config: &MonitorConfig)->CheckResult{
// 判空逻辑 上面写过了 这里就不赘述了
...
// 为了记录请求发送时间
let start_time_instant = std::time::Instant::now();
match config.details {
MonitorConfigDetail::Http(ref detail) => {
// 获取一个http 链接
let request_client = self.get_client(detail);
match request_client.send().await{
Ok(response)=>{
// 🀄️点,🀄️点,🀄️点
// 获取状态码
let (dns_lookup_time, tcp_connect_time, tls_handshake_time, ssl_certificate_info) = match get_dns_tcp_tls_performance(config.target.as_ref().unwrap()).await {
Ok(performance) => performance,
Err(_) => (0, 0, 0, None),
};
// 提取需要的数据
let status_code = response.status().as_u16();
let version = response.version();
let headers = response.headers().clone();
let content_length = response.content_length();
// 因为这里的代码会获取所有权 所以后面就没办法直接用response了 所以上面就单独获取response中的字段值用在后面的方法中
let body = response.text().await.unwrap_or_default();
// 创建基本结果和头部结果
let basic_avaliable = self.create_basic_result(status_code, version, &headers, content_length);
let response_headers = self.create_headers_result(&headers);
let performance_timings = PerformanceTimings { dns_lookup_time, tcp_connect_time, tls_handshake_time, ..self.create_performance_timings(start_time_instant, unix_now_ms()) };
return CheckResult {
id: uuid::Uuid::new_v4().as_u128(), // V4 uuid 一种随机数 基于时间戳和随机数 V4版本的生成功能
monitor_type: MonitorType::Http,
target: config.target.clone(),
status: true, // 代表监控状态为成功,但是target访问失败了
details: CheckResultDetail::Http(HttpMonitorResult {
basic_avaliable,
response_headers,
performance_timings,
certificate_info: ssl_certificate_info.unwrap_or_default(),
content_verification: self.create_content_verification_result(body, &detail.rules),
advanced_avaliable: self.create_advanced_availability_result(),
})
}
},
Err(_)=>{
....
}
}
},
_=>{
// 其他类型 上面也写过了
...
}
}
}
}
如果您能坚持学习到这里,那么恭喜------我们的代码实现部分已经圆满完成!
回顾整个开发过程,其实我们一直在进行一场精妙的"填空游戏":先搭建起整体的框架结构,再逐步填充每个模块的具体实现。这种由宏观到微观的开发方式,能够让我们始终保持清晰的思路,即使面对复杂系统也不会迷失方向。
在这个过程中,我自己的开发经历也是如此------从最初对各个模块的陌生,到逐步拆解需求、研究技术细节,最终独立完成整个功能的实现。这段经历让我深刻体会到,对于初学者而言,勤于动手实践至关重要。
至此,我们本阶段的实现就告一段落了。建议大家可以好好消化吸收这些内容,尝试自己动手扩展更多监控类型。期待在接下来的学习中与大家再次相遇!
过程问题&&解决方案
#[async_trait::async_trait]这种宏的作用是什么?
答:它是 async-trait 宏,用来让 trait 里的异步方法可用。核心点:- Rust 原生的
trait里写async fn(或返回 impl Future)在用作 trait object(如 Box<dyn Monitor>)时不满足对象安全,编译不过。 #[async_trait::async_trait]会把你的async fn 自动展开成返回 Pin<Box<dyn Future<Output = *> + Send + '* >>的形式,从而让 trait 可以作为dyn Trait使用。
- Rust 原生的
rust
// 原始写法
#[async_trait::async_trait]
trait Monitor {
async fn check(&self, cfg: &MonitorConfig) -> CheckResult;
}
// 展开后大致等价
trait Monitor {
fn check<'a>(&'a self, cfg: &'a MonitorConfig)
-> core::pin::Pin<Box<dyn core::future::Future<Output = CheckResult> + Send + 'a>>;
}
- 在
src/async_monitor.rs中使用mpsc、Box::pin、tokio::spawn都是用来干嘛的? 答:把一个异步块变成"可返回/可存放"的 Future,用来统一返回类型并在堆上固定(Pin)它,便于在外部 await 与存入集合。要点解释:async move {...}: 生成一个Future,并把target、config的所有权移动进去,确保在tokio::spawn中满足'static要求。Box::pin(...):把这个Future放到堆上并固定位置(Pin),再通过dyn Future做类型擦除,得到统一的返回类型PinnedReceiverFuture = Pin<Box<dyn Future<Output = mpsc::Receiver<_>> + Send>>。mpsc通道:函数里创建tx/rx,后台循环每次check后用tx发送结果;函数本身返回rx(通过await得到)。
写在最后
诚然,本篇内容最初定位为企业级监控系统,但在实际开发过程中,我才深刻体会到什么叫做"无知者无畏"。若要真正打造一个称得上"企业级"的监控系统,需要考虑的维度远超想象。
在我看来,"可用"与"能用"之间那道看似无形的鸿沟,本质上是由用户规模 与资金投入共同决定的。当用户量达到一定规模时,那些在 demo 阶段看似无足轻重的细节------比如性能瓶颈、数据一致性、高可用架构------都会骤然成为系统稳定性的致命缺口。而一旦涉及资金流转或商业损失,对系统的可靠性要求更是会跃升至全新的量级。
因此,很多时候我们认为"简单"的系统或功能,其实并非真的简单,只是尚未经历大规模用户与真实资金的考验。本次我们实现的,仅仅是其中一个基础的 HTTP 监控模块,距离完整的企业级方案仍有大量功能有待完善。
但即便如此,对于一名初学者而言,能够一步步走完这个从零到一的过程,理解监控系统的核心架构与实现逻辑,已经是极具价值的成长。愿这份实践经验,能成为你走向更复杂系统设计的坚实第一步。
在AI技术如此普及的今天,我想特别分享一点心得:请不要过度依赖AI的代码生成能力。优秀的开发者应该将AI视为提升效率的工具 ,而非替代思考的大脑 。重要的是保持自己的思考主导权------由你来驾驭技术,而不是被技术所驾驭。