https-dns 0.2.0

Minimal and efficient DNS-over-HTTPS (DoH) client
Documentation
use crate::bootstrap::BootstrapClient;
use crate::cache::Cache;
use crate::error::UpstreamError::{self, Build, Resolve};
use reqwest::{
    header::{HeaderMap, HeaderValue, CONTENT_TYPE},
    Client,
};
use std::{net::IpAddr, time::Duration};
use tracing::info;
use trust_dns_proto::op::message::Message;

#[derive(Clone, Debug)]
pub struct HttpsClient {
    host: String,
    port: u16,
    https_client: Client,
    cache: Cache,
}

impl HttpsClient {
    pub async fn new(host: String, port: u16) -> Result<Self, UpstreamError> {
        let mut headers = HeaderMap::new();
        headers.insert(
            CONTENT_TYPE,
            HeaderValue::from_str("application/dns-message").unwrap(),
        );

        let mut client_builder = Client::builder()
            .default_headers(headers)
            .https_only(true)
            .gzip(true)
            .brotli(true)
            .timeout(Duration::from_secs(10));

        if host.parse::<IpAddr>().is_err() {
            let bootstrap_client = match BootstrapClient::new() {
                Ok(bootstrap_client) => bootstrap_client,
                Err(error) => return Err(error),
            };
            let ip_addr = match bootstrap_client.bootstrap(&host).await {
                Ok(ip_addr) => ip_addr,
                Err(error) => return Err(error),
            };
            client_builder = client_builder.resolve(&host, ip_addr);
        }

        let https_client = match client_builder.build() {
            Ok(https_client) => https_client,
            Err(_) => return Err(Build),
        };
        info!("connected to https://{}:{}", host, port);

        Ok(HttpsClient {
            host,
            port,
            https_client,
            cache: Cache::new(),
        })
    }

    pub async fn process(&mut self, request_message: Message) -> Result<Message, UpstreamError> {
        if let Some(response_message) = self.cache.get(&request_message) {
            return Ok(response_message);
        }

        let raw_request_message = match request_message.to_vec() {
            Ok(raw_request_message) => raw_request_message,
            Err(_) => return Err(Resolve),
        };

        let url = format!("https://{}:{}/dns-query", self.host, self.port);
        let request = self.https_client.post(url).body(raw_request_message);
        let response = match request.send().await {
            Ok(response) => response,
            Err(_) => return Err(Resolve),
        };

        let raw_response_message = match response.bytes().await {
            Ok(response_bytes) => response_bytes,
            Err(_) => return Err(Resolve),
        };

        let message = match Message::from_vec(&raw_response_message) {
            Ok(message) => message,
            Err(_) => return Err(Resolve),
        };

        self.cache.put(message.clone());
        Ok(message)
    }
}