1use std::convert::TryInto;
2use std::sync::OnceLock;
3
4use async_trait::async_trait;
5use camino::Utf8PathBuf;
6use gleam_core::{
7 Error, Result,
8 error::{FileIoAction, FileKind},
9};
10use http::{Request, Response};
11use reqwest::{Certificate, Client};
12
13use crate::fs;
14
15static REQWEST_CLIENT: OnceLock<Client> = OnceLock::new();
16
17#[derive(Debug)]
18pub struct HttpClient;
19
20impl HttpClient {
21 pub fn new() -> Self {
22 Self
23 }
24
25 pub fn boxed() -> Box<Self> {
26 Box::new(Self::new())
27 }
28}
29
30#[async_trait]
31impl gleam_core::io::HttpClient for HttpClient {
32 async fn send(&self, request: Request<Vec<u8>>) -> Result<Response<Vec<u8>>> {
33 let request = request
34 .try_into()
35 .expect("Unable to convert HTTP request for use by reqwest library");
36 let client = init_client().map_err(Error::http)?;
37 let mut response = client.execute(request).await.map_err(Error::http)?;
38 let mut builder = Response::builder()
39 .status(response.status())
40 .version(response.version());
41 if let Some(headers) = builder.headers_mut() {
42 std::mem::swap(headers, response.headers_mut());
43 }
44 builder
45 .body(response.bytes().await.map_err(Error::http)?.to_vec())
46 .map_err(Error::http)
47 }
48}
49
50fn init_client() -> Result<&'static Client, Error> {
51 if let Some(client) = REQWEST_CLIENT.get() {
52 return Ok(client);
53 }
54
55 let certificate_path = match std::env::var("GLEAM_CACERTS_PATH") {
56 Ok(path) => path,
57 Err(_) => {
58 return Ok(REQWEST_CLIENT.get_or_init(|| {
59 Client::builder()
60 .build()
61 .expect("Failed to create reqwest client")
62 }));
63 }
64 };
65
66 let certificate_bytes = fs::read_bytes(&certificate_path)?;
67 let certificate = Certificate::from_pem(&certificate_bytes).map_err(|error| Error::FileIo {
68 kind: FileKind::File,
69 action: FileIoAction::Parse,
70 path: Utf8PathBuf::from(&certificate_path),
71 err: Some(error.to_string()),
72 })?;
73
74 Ok(REQWEST_CLIENT.get_or_init(|| {
75 Client::builder()
76 .add_root_certificate(certificate)
77 .build()
78 .expect("Failed to create reqwest client")
79 }))
80}