rust-integration-services 0.1.1

A modern, fast, and lightweight integration library written in Rust, designed for memory safety and stability.
Documentation
use std::sync::Arc;
use rustls::{ClientConfig, RootCertStore};
use tokio::{io::AsyncWriteExt, net::TcpStream};
use tokio_rustls::TlsConnector;
use url::Url;
use webpki_roots::TLS_SERVER_ROOTS;

use crate::utils::error::Error;

use super::{http_request::HttpRequest, http_response::HttpResponse};

#[allow(dead_code)]
pub struct HttpSender {
    url: Url,
}

impl HttpSender {
    pub fn new<T: AsRef<str>>(url: T) -> Self {
        let url = Url::parse(url.as_ref()).expect("Invalid URL!");
        HttpSender { 
            url,
        }
    }

    /// Send a request to the url.  
    /// 
    /// Host header will be set automatically on the request from the url.
    pub async fn send(&mut self, mut request: HttpRequest) -> tokio::io::Result<HttpResponse> {
        let url_host = self.url.host_str().unwrap();
        let url_port = self.url.port_or_known_default().unwrap();
        let url_path = self.url.path().to_string();
        let addr = (url_host, url_port);
        
        if request.path == "/" {
            request.path = url_path;
        }
        request.headers.insert("Host".to_string(), url_host.to_string());
        
        match self.url.scheme() {
            "http" => {
                let mut stream = TcpStream::connect(&addr).await?;
                stream.write_all(&request.to_bytes()).await?;

                let response = HttpResponse::from_stream(&mut stream).await?;
                Ok(response)
            }
            "https" => {
                let mut root_store = RootCertStore::empty();
                root_store.extend(TLS_SERVER_ROOTS.iter().cloned());

                let config = ClientConfig::builder()
                .with_root_certificates(root_store)
                .with_no_client_auth();

                let connector = TlsConnector::from(Arc::new(config));
                let stream = TcpStream::connect(&addr).await?;

                let domain = rustls::pki_types::ServerName::try_from(url_host)
                .map_err(|_| Error::tokio_io("Invalid DNS name."))?
                .to_owned();

                let mut tls_stream = connector.connect(domain, stream).await?;
                tls_stream.write_all(&request.to_bytes()).await?;

                let response = HttpResponse::from_stream(&mut tls_stream).await?;
                Ok(response)
            }
            _ => {
                Err(Error::tokio_io(format!("Scheme '{}' not supported.", self.url.scheme())))
            }
        }
    }
}