use reqwest::Client;
use std::time::Duration;
use tokio::time::timeout;
use std::sync::Arc;
#[derive(Debug, Clone)]
pub struct VerifyResult {
pub domain: String,
pub http_status: Option<u16>,
pub https_status: Option<u16>,
pub http_alive: bool,
pub https_alive: bool,
pub redirect_url: Option<String>,
pub server_header: Option<String>,
pub title: Option<String>,
}
pub struct DomainVerifier {
client: Client,
timeout_duration: Duration,
}
impl DomainVerifier {
pub fn new(timeout_secs: u64) -> Result<Self, Box<dyn std::error::Error>> {
let client = Client::builder()
.timeout(Duration::from_secs(timeout_secs))
.danger_accept_invalid_certs(true) .build()?;
Ok(DomainVerifier {
client,
timeout_duration: Duration::from_secs(timeout_secs),
})
}
pub async fn verify_domain(&self, domain: &str) -> VerifyResult {
let mut result = VerifyResult {
domain: domain.to_string(),
http_status: None,
https_status: None,
http_alive: false,
https_alive: false,
redirect_url: None,
server_header: None,
title: None,
};
if let Ok(http_result) = self.test_http(&format!("http://{}", domain)).await {
result.http_status = Some(http_result.status);
result.http_alive = http_result.alive;
if result.redirect_url.is_none() {
result.redirect_url = http_result.redirect_url;
}
if result.server_header.is_none() {
result.server_header = http_result.server_header;
}
if result.title.is_none() {
result.title = http_result.title;
}
}
if let Ok(https_result) = self.test_http(&format!("https://{}", domain)).await {
result.https_status = Some(https_result.status);
result.https_alive = https_result.alive;
if result.redirect_url.is_none() {
result.redirect_url = https_result.redirect_url;
}
if result.server_header.is_none() {
result.server_header = https_result.server_header;
}
if result.title.is_none() {
result.title = https_result.title;
}
}
result
}
pub async fn verify_domains(&self, domains: Vec<String>) -> Vec<VerifyResult> {
let mut results = Vec::new();
let semaphore = Arc::new(tokio::sync::Semaphore::new(50));
let mut tasks = Vec::new();
for domain in domains {
let permit = Arc::clone(&semaphore);
let verifier = self.clone_client();
let task = tokio::spawn(async move {
let _permit = permit.acquire().await.unwrap();
verifier.verify_domain(&domain).await
});
tasks.push(task);
}
for task in tasks {
if let Ok(result) = task.await {
results.push(result);
}
}
results
}
async fn test_http(&self, url: &str) -> Result<HttpTestResult, Box<dyn std::error::Error>> {
let response = timeout(self.timeout_duration, self.client.get(url).send()).await??;
let status = response.status().as_u16();
let alive = status >= 200 && status < 400;
let server_header = response
.headers()
.get("server")
.and_then(|v| v.to_str().ok())
.map(|s| s.to_string());
let redirect_url = if response.status().is_redirection() {
response
.headers()
.get("location")
.and_then(|v| v.to_str().ok())
.map(|s| s.to_string())
} else {
None
};
let body = response.text().await.unwrap_or_default();
let title = self.extract_title(&body);
Ok(HttpTestResult {
status,
alive,
redirect_url,
server_header,
title,
})
}
fn extract_title(&self, html: &str) -> Option<String> {
let re = regex::Regex::new(r"<title[^>]*>([^<]*)</title>").ok()?;
re.captures(html)
.and_then(|caps| caps.get(1))
.map(|m| m.as_str().trim().to_string())
.filter(|s| !s.is_empty())
}
fn clone_client(&self) -> DomainVerifier {
DomainVerifier {
client: self.client.clone(),
timeout_duration: self.timeout_duration,
}
}
pub fn display_results(&self, results: &[VerifyResult]) {
println!("=== 域名验证结果 ===");
for result in results {
if result.http_alive || result.https_alive {
let mut status_info = Vec::new();
if result.http_alive {
status_info.push(format!("HTTP:{}", result.http_status.unwrap_or(0)));
}
if result.https_alive {
status_info.push(format!("HTTPS:{}", result.https_status.unwrap_or(0)));
}
let mut extra_info = Vec::new();
if let Some(ref title) = result.title {
extra_info.push(format!("标题: {}", title));
}
if let Some(ref server) = result.server_header {
extra_info.push(format!("服务器: {}", server));
}
if let Some(ref redirect) = result.redirect_url {
extra_info.push(format!("重定向: {}", redirect));
}
println!("{} [{}] {}",
result.domain,
status_info.join(", "),
if extra_info.is_empty() { String::new() } else { format!("- {}", extra_info.join(", ")) }
);
}
}
}
}
#[derive(Debug)]
struct HttpTestResult {
status: u16,
alive: bool,
redirect_url: Option<String>,
server_header: Option<String>,
title: Option<String>,
}