use std::convert::TryInto; use std::sync::OnceLock; use async_trait::async_trait; use camino::Utf8PathBuf; use gleam_core::{ Error, Result, error::{FileIoAction, FileKind}, }; use http::{Request, Response}; use reqwest::{Certificate, Client}; use crate::fs; static REQWEST_CLIENT: OnceLock = OnceLock::new(); #[derive(Debug)] pub struct HttpClient; impl HttpClient { pub fn new() -> Self { Self } pub fn boxed() -> Box { Box::new(Self::new()) } } #[async_trait] impl gleam_core::io::HttpClient for HttpClient { async fn send(&self, request: Request>) -> Result>> { let request = request .try_into() .expect("Unable to convert HTTP request for use by reqwest library"); let client = init_client().map_err(Error::http)?; let mut response = client.execute(request).await.map_err(Error::http)?; let mut builder = Response::builder() .status(response.status()) .version(response.version()); if let Some(headers) = builder.headers_mut() { std::mem::swap(headers, response.headers_mut()); } builder .body(response.bytes().await.map_err(Error::http)?.to_vec()) .map_err(Error::http) } } fn init_client() -> Result<&'static Client, Error> { if let Some(client) = REQWEST_CLIENT.get() { return Ok(client); } let certificate_path = match std::env::var("GLEAM_CACERTS_PATH") { Ok(path) => path, Err(_) => { return Ok(REQWEST_CLIENT.get_or_init(|| { Client::builder() .build() .expect("Failed to create reqwest client") })); } }; let certificate_bytes = fs::read_bytes(&certificate_path)?; let certificate = Certificate::from_pem(&certificate_bytes).map_err(|error| Error::FileIo { kind: FileKind::File, action: FileIoAction::Parse, path: Utf8PathBuf::from(&certificate_path), err: Some(error.to_string()), })?; Ok(REQWEST_CLIENT.get_or_init(|| { Client::builder() .add_root_certificate(certificate) .build() .expect("Failed to create reqwest client") })) }