Fork of daniellemaywood.uk/gleam — Wasm codegen work
2

Configure Feed

Select the types of activity you want to include in your feed.

gleam / compiler-cli / src / http.rs
4.0 kB 126 lines
1// SPDX-License-Identifier: Apache-2.0 2// SPDX-FileCopyrightText: 2021 The Gleam contributors 3 4use std::convert::TryInto; 5use std::sync::OnceLock; 6 7use async_trait::async_trait; 8use camino::Utf8PathBuf; 9use gleam_core::{ 10 Error, Result, 11 error::{FileIoAction, FileKind}, 12}; 13use http::{Request, Response}; 14use reqwest::{Certificate, Client}; 15 16use crate::fs; 17 18static REQWEST_CLIENT: OnceLock<Client> = OnceLock::new(); 19 20#[derive(Debug)] 21pub struct HttpClient; 22 23impl HttpClient { 24 pub fn new() -> Self { 25 Self 26 } 27 28 pub fn boxed() -> Box<Self> { 29 Box::new(Self::new()) 30 } 31} 32 33#[async_trait] 34impl gleam_core::io::HttpClient for HttpClient { 35 async fn send(&self, request: Request<Vec<u8>>) -> Result<Response<Vec<u8>>> { 36 tracing::debug!( 37 method = request.method().as_str(), 38 url = request.uri().to_string(), 39 "http-send", 40 ); 41 let request = request 42 .try_into() 43 .expect("Unable to convert HTTP request for use by reqwest library"); 44 let client = init_client().map_err(Error::http)?; 45 let mut response = client.execute(request).await.map_err(Error::http)?; 46 let mut builder = Response::builder() 47 .status(response.status()) 48 .version(response.version()); 49 if let Some(headers) = builder.headers_mut() { 50 std::mem::swap(headers, response.headers_mut()); 51 } 52 builder 53 .body(response.bytes().await.map_err(Error::http)?.to_vec()) 54 .map_err(Error::http) 55 } 56} 57 58fn init_client() -> Result<&'static Client, Error> { 59 if let Some(client) = REQWEST_CLIENT.get() { 60 return Ok(client); 61 } 62 63 let client = build_client()?; 64 Ok(REQWEST_CLIENT.get_or_init(|| client)) 65} 66 67/// Build the HTTP client with an appropriate certificate trust store. 68/// 69/// On most platforms reqwest's default `rustls` configuration uses 70/// `rustls-platform-verifier`, which delegates to the operating system's trust 71/// manager. This respects OS-installed and enterprise root certificates along 72/// with the system's own trust decisions. 73/// 74/// On Android that verifier reaches the trust manager by calling into the JVM, 75/// and without it panics on the first request (see issue #5823). There we read 76/// the same system trust store from the filesystem and configure those roots 77/// explicitly instead. 78fn build_client() -> Result<Client, Error> { 79 if cfg!(target_os = "android") { 80 let mut certificates = system_certificates(); 81 if let Ok(certificate_path) = std::env::var("GLEAM_CACERTS_PATH") { 82 let certificate = read_certificate(&certificate_path)?; 83 certificates.push(certificate); 84 } 85 86 Client::builder() 87 .tls_certs_only(certificates) 88 .build() 89 .map_err(Error::http) 90 } else { 91 let Ok(certificate_path) = std::env::var("GLEAM_CACERTS_PATH") else { 92 return Client::builder().build().map_err(Error::http); 93 }; 94 95 tracing::trace!("Using GLEAM_CACERTS_PATH environment variable"); 96 let certificate = read_certificate(&certificate_path)?; 97 Client::builder() 98 .add_root_certificate(certificate) 99 .build() 100 .map_err(Error::http) 101 } 102} 103 104fn read_certificate(path: &str) -> Result<Certificate, Error> { 105 let bytes = fs::read_bytes(path)?; 106 Certificate::from_pem(&bytes).map_err(|error| Error::FileIo { 107 kind: FileKind::File, 108 action: FileIoAction::Parse, 109 path: Utf8PathBuf::from(path), 110 err: Some(error.to_string()), 111 }) 112} 113 114/// Load the system trust store (only used on Android) 115fn system_certificates() -> Vec<Certificate> { 116 let loaded = rustls_native_certs::load_native_certs(); 117 for error in &loaded.errors { 118 tracing::warn!("Failed to load a system certificate: {error}"); 119 } 120 121 loaded 122 .certs 123 .iter() 124 .filter_map(|certificate| Certificate::from_der(certificate.as_ref()).ok()) 125 .collect() 126}