use crate::{Request, Response, Result, DnsError};
use super::{Transport, HttpsConfig, HttpMethod};
use super::udp::UdpTransport;
use async_trait::async_trait;
use std::time::Duration;
use tokio::time::timeout;
use reqwest::{Client, Method};
#[derive(Debug)]
pub struct HttpsTransport {
config: HttpsConfig,
client: Client,
}
impl HttpsTransport {
pub fn new(config: HttpsConfig) -> Result<Self> {
let connect_timeout = std::cmp::min(
std::cmp::max(
config.base.timeout / 3,
Duration::from_secs(2)
),
Duration::from_secs(5)
);
let client = Client::builder()
.timeout(config.base.timeout) .connect_timeout(connect_timeout) .tcp_keepalive(Duration::from_secs(30)) .tcp_nodelay(config.base.tcp_nodelay) .user_agent(&config.user_agent)
.build()
.map_err(|e| DnsError::Http(format!("Failed to create HTTP client: {}", e)))?;
Ok(Self {
config,
client,
})
}
fn encode_dns_query_base64url(request: &Request) -> Result<String> {
use base64::{Engine as _, engine::general_purpose};
let dns_data = UdpTransport::serialize_request(request)?;
Ok(general_purpose::URL_SAFE_NO_PAD.encode(&dns_data))
}
async fn send_get_request(&self, request: &Request) -> Result<Response> {
use crate::{dns_debug, dns_info};
dns_info!("🌐 DoH GET请求开始: {} -> {}", request.query.name, self.config.url);
let dns_query = Self::encode_dns_query_base64url(request)?;
let response = timeout(
self.config.base.timeout,
self.client
.get(&self.config.url)
.query(&[("dns", dns_query)])
.header("Accept", "application/dns-message")
.send()
).await;
let http_response = match response {
Ok(Ok(resp)) => resp,
Ok(Err(e)) => return Err(DnsError::Http(format!("HTTP request failed: {}", e))),
Err(_) => return Err(DnsError::Timeout),
};
if !http_response.status().is_success() {
return Err(DnsError::Http(format!(
"HTTP error: {} {}",
http_response.status().as_u16(),
http_response.status().canonical_reason().unwrap_or("Unknown")
)));
}
let content_type = http_response
.headers()
.get("content-type")
.and_then(|v| v.to_str().ok())
.unwrap_or("");
if !content_type.contains("application/dns-message") {
return Err(DnsError::Http(format!(
"Invalid content type: expected 'application/dns-message', got '{}'",
content_type
)));
}
let body_result = timeout(
self.config.base.timeout,
http_response.bytes()
).await;
let body = match body_result {
Ok(Ok(bytes)) => bytes.to_vec(),
Ok(Err(e)) => return Err(DnsError::Http(format!("Failed to read response body: {}", e))),
Err(_) => return Err(DnsError::Timeout),
};
UdpTransport::deserialize_response(&body)
}
async fn send_post_request(&self, request: &Request) -> Result<Response> {
use crate::{dns_debug, dns_info};
dns_info!("🌐 DoH POST请求开始: {} -> {}", request.query.name, self.config.url);
let dns_data = UdpTransport::serialize_request(request)?;
let response = timeout(
self.config.base.timeout,
self.client
.post(&self.config.url)
.header("Content-Type", "application/dns-message")
.header("Accept", "application/dns-message")
.body(dns_data)
.send()
).await;
let http_response = match response {
Ok(Ok(resp)) => resp,
Ok(Err(e)) => return Err(DnsError::Http(format!("HTTP request failed: {}", e))),
Err(_) => return Err(DnsError::Timeout),
};
if !http_response.status().is_success() {
return Err(DnsError::Http(format!(
"HTTP error: {} {}",
http_response.status().as_u16(),
http_response.status().canonical_reason().unwrap_or("Unknown")
)));
}
let content_type = http_response
.headers()
.get("content-type")
.and_then(|v| v.to_str().ok())
.unwrap_or("");
if !content_type.contains("application/dns-message") {
return Err(DnsError::Http(format!(
"Invalid content type: expected 'application/dns-message', got '{}'",
content_type
)));
}
let body_result = timeout(
self.config.base.timeout,
http_response.bytes()
).await;
let body = match body_result {
Ok(Ok(bytes)) => bytes.to_vec(),
Ok(Err(e)) => return Err(DnsError::Http(format!("Failed to read response body: {}", e))),
Err(_) => return Err(DnsError::Timeout),
};
UdpTransport::deserialize_response(&body)
}
}
#[async_trait]
impl Transport for HttpsTransport {
async fn send(&self, request: &Request) -> Result<Response> {
match self.config.method {
HttpMethod::GET => self.send_get_request(request).await,
HttpMethod::POST => self.send_post_request(request).await,
}
}
fn transport_type(&self) -> &'static str {
"HTTPS"
}
fn set_timeout(&mut self, timeout: Duration) {
self.config.base.timeout = timeout;
}
fn timeout(&self) -> Duration {
self.config.base.timeout
}
}