本篇是之前的網絡資源監控器(初版)的續集,在這篇文章中我將會繼續豐富網絡資源監控器的功能,當然本篇也沒有結束,只是二版,因為完整的功能確實太多太多了,本身這系列也是自己的學習的記錄,如果一次完成的話太複雜了,對我這種新手來説不是很友好,所以我這邊會像堆積木一樣一點點的來完成每個模塊的內容,爭取我們能完成一個可用的企業級網絡資源監控器(牛逼吹起來了~);
簡要説明
本篇是之前的網絡資源監控器(初版)的續集,在這篇文章中我將會繼續豐富網絡資源監控器的功能,當然本篇也沒有結束,只是二版,因為完整的功能確實太多太多了,本身這系列也是自己的學習的記錄,如果一次完成的話太複雜了,對我這種新手來説不是很友好,所以我這邊會像堆積木一樣一點點的來完成每個模塊的內容,爭取我們能完成一個可用的企業級網絡資源監控器(牛逼吹起來了~);
需求內容
1. 搭建監控引擎的框架
- 支持多種監控類型(HTTP/HTTPS、Memory、TCP、PING等)
- 可配置的檢查頻率
- 併發執行監控檢查
- 重試機制
- 超時控制
- 結果記錄(包括響應時間、狀態、錯誤信息等)
功能點詳解
首先,我們將基於上一篇內容中的監控配置列表,搭建一個支持多監控類型的框架。在本次課程中,我們會一步步完成整體框架的基礎結構,並重點實現 HTTP/HTTPS 類型的監控邏輯。其他監控類型(如 ICMP、TCP、DNS 等)暫時不在此次實現範圍之內,將作為課後擴展練習留給大家自行完成與完善(其實主要是因為監控類型實在太多,寫不動啦~)。
好的,我們來梳理一下核心任務。簡單來説,這個框架需要支持多種監控類型(如 HTTP、HTTPS、DNS、TCP 等),並通過一個監控引擎來統一調度這些檢查任務,最終彙總並輸出所有類型的監控結果。
為了實現這個目標,我們可以將整體邏輯劃分為以下兩個核心部分來構建:
- 監控引擎:這是框架的大腦,負責調度和執行任務。它的主要工作是讀取監控列表,依次調用不同監控類型的檢查邏輯,並收集結果。
- 監控類型實現:這是框架的肌肉,包含各種監控類型(如 HTTP/HTTPS)的具體檢查邏輯。每種類型都是一個獨立的模塊,負責執行專業檢查並返回標準化的結果。
下面,我們就按照這兩個模塊來具體實現:
多類型監控引擎實現
在本次框架設計中,我們核心採用了 **策略模式與工廠模式相結合 的架構方案:
- 策略模式:我們將每種監控類型(如 HTTP、HTTPS)都定義為實現了統一
MonitorTrait 的獨立策略。這確保了每種監控檢查邏輯都高度內聚,可以獨立開發和演變。 - 工廠模式:我們通過一個統一的工廠,根據配置來批量創建對應的監控器實例。這消除了代碼中複雜的對象創建邏輯,實現了客户端與具體實現的解耦。
這樣未來擴展新的監控類型(如 DNS、TCP)將變得異常簡單:只需定義一個新的策略並實現 Monitor Trait 即可,無需修改引擎的核心調度邏輯。高內聚、低耦合,完美~~~
1. Monitor公共Trait的實現
首先我們來看下我們統一的Trait的定義,其實這個特徵對象很簡單,核心只需要實現一個檢查函數即可
自動換行
// 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
自動換行
// 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
自動換行
// 定義檢查結果結構體
#[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 檢查的引擎實例。
理解了這個核心概念後,具體的代碼實現就會顯得清晰而直接:
自動換行
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 方法,來承載各類監控任務的具體邏輯,為便於理解,這裏我們僅以其中一個監控類型為例進行展示,其他類型的實現方式與此類似。
自動換行
// 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.監控引擎調度器的具體實現
調度器雖承擔着系統“中樞”的名號,但在各監控引擎已封裝完備的前提下,其核心職責確實清晰而聚焦——它無需關心具體的檢測邏輯,只需專注於調度本身。
具體來説,它需要完成三個核心任務:
- 異步調度:併發地調用各個監控引擎的檢測方法;
- 循環執行:按預設間隔啓動輪詢任務,實現持續監控;
- 結果收集:彙總並輸出每次任務的執行結果。
下面,我們就來具體實現這一調度邏輯:
自動換行
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 檢查完畢");
}
其中上面使用到了輪詢調度器和單次調度器,下面我們來具體的看下相關的實現邏輯:
自動換行
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監控類型結構體
自動換行
// 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:基礎監控結果,包括是否可訪問、狀態碼、協議類型等等
自動換行
// 定義一個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等等
自動換行
// 定義一個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鏈接時間、連接耗時等等
自動換行
// 創建一個枚舉類型,表示性能指標的類別
#[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證書相關內容,包括證書編號、是否有效等等
自動換行
#[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:響應內容監控結果,包括是否包含某字段、正則匹配內容等
自動換行
// 創建一個內容驗證結果
#[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:高級監控內容,包含事物監控等,本次先不涉及...
自動換行
// 高級可用性類別 業務指標監控 事務監控 等
#[derive(Debug, Clone, Default)]
pub struct AdvancedAvailability {
pub bussiness_metrics: HashMap<String, String>,
}
現在,我們已經完成了監控結果結構體的定義,接下來要做的就是逐步填充每個字段的值——這就像完成拼圖的最後一塊,當所有字段都被正確賦值時,整個HTTP監控模塊的實現也就圓滿完成了。
讓我們立即開始這最後的實現環節,為每個字段注入實際的數據內容。
1.2 HTTP監控引擎的實現
現在,讓我們將視線轉回最初設計的 HTTP 監控引擎。在之前搭建框架時,我們已經實現了一個基礎的結果返回邏輯。接下來,我們將在此基礎上逐步深化,填充更多實際功能。
正如前文所提到的,我們將繼續使用 reqwest 庫來處理 HTTP 請求。其基本用法在此不再贅述,現在讓我們集中精力,進一步完善 HttpMonitor 引擎的完整邏輯。
自動換行
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進行處理相關的數據即可
自動換行
// 解析成基礎的響應結果結構體
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
自動換行
// 解析相關頭信息
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相關的數據指標,所以這裏的話我們還需要定義另外的方法來實現
自動換行
// 獲取性能參數
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內容,然後執行相關的校驗邏輯即可,
自動換行
// 創建內容驗證結果
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監控中我們待完成的那一段代碼就可以了:
自動換行
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
自動換行
AI代碼解釋
// 原始寫法
#[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視為提升效率的工具,而非替代思考的大腦。重要的是保持自己的思考主導權——由你來駕馭技術,而不是被技術所駕馭。