diff --git a/common/client-libs/validator-client/src/client.rs b/common/client-libs/validator-client/src/client.rs index 2e6d19d9ea..74c15f2d64 100644 --- a/common/client-libs/validator-client/src/client.rs +++ b/common/client-libs/validator-client/src/client.rs @@ -385,6 +385,11 @@ impl Client { } } +/// DEPRECATED: Use nym_http_api_client::Client with from_network() or with_bincode() instead +#[deprecated( + since = "1.2.0", + note = "Use nym_http_api_client::Client::from_network() or ClientBuilder::with_bincode() instead" +)] #[derive(Clone)] pub struct NymApiClient { pub use_bincode: bool, diff --git a/common/client-libs/validator-client/src/nym_api/mod.rs b/common/client-libs/validator-client/src/nym_api/mod.rs index aa13634503..4e1ce0b286 100644 --- a/common/client-libs/validator-client/src/nym_api/mod.rs +++ b/common/client-libs/validator-client/src/nym_api/mod.rs @@ -50,6 +50,8 @@ use time::Date; use tracing::instrument; pub use nym_coconut_dkg_common::types::EpochId; +// DEPRECATED: Use nym_http_api_client::Client directly +#[deprecated(since = "1.2.0", note = "Use nym_http_api_client::Client directly")] pub use nym_http_api_client::Client; pub mod error; diff --git a/common/http-api-client/Cargo.toml b/common/http-api-client/Cargo.toml index 853b79b9a5..10f93c92e1 100644 --- a/common/http-api-client/Cargo.toml +++ b/common/http-api-client/Cargo.toml @@ -13,6 +13,7 @@ license.workspace = true [features] default=["tunneling"] tunneling=[] +network-defaults = ["dep:nym-network-defaults"] [dependencies] async-trait = { workspace = true } @@ -34,6 +35,7 @@ mime = { workspace = true } nym-http-api-common = { path = "../http-api-common", default-features = false } nym-bin-common = { path = "../bin-common" } +nym-network-defaults = { path = "../network-defaults", optional = true } [target."cfg(not(target_arch = \"wasm32\"))".dependencies] hickory-resolver = { workspace = true, features = ["https-ring", "tls-ring", "webpki-roots"] } diff --git a/common/http-api-client/src/lib.rs b/common/http-api-client/src/lib.rs index 60122efaf1..730a4fad29 100644 --- a/common/http-api-client/src/lib.rs +++ b/common/http-api-client/src/lib.rs @@ -162,6 +162,8 @@ use std::sync::Arc; #[cfg(feature = "tunneling")] mod fronted; +#[cfg(feature = "tunneling")] +pub use fronted::FrontPolicy; mod url; pub use url::{IntoUrl, Url}; mod user_agent; @@ -192,6 +194,15 @@ pub type Params<'a, K, V> = &'a [(K, V)]; /// Empty collection of HTTP Request Parameters. pub const NO_PARAMS: Params<'_, &'_ str, &'_ str> = &[]; +/// Serialization format for API requests and responses +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum SerializationFormat { + /// Use JSON serialization (default, always works) + Json, + /// Use bincode serialization (must be explicitly opted into) + Bincode, +} + /// The Errors that may occur when creating or using an HTTP client. #[derive(Debug, Error)] #[allow(missing_docs)] @@ -371,6 +382,7 @@ pub struct ClientBuilder { front: Option, retry_limit: usize, + serialization: SerializationFormat, } impl ClientBuilder { @@ -396,6 +408,43 @@ impl ClientBuilder { } } + /// Create a client builder from network details with sensible defaults + #[cfg(feature = "network-defaults")] + pub fn from_network(network: &nym_network_defaults::NymNetworkDetails) -> Result { + let urls = network.nym_api_urls + .as_ref() + .ok_or_else(|| HttpClientError::GenericRequestFailure("No API URLs configured in network details".to_string()))? + .iter() + .map(|api_url| { + // Convert ApiUrl to our Url type with fronting support + let mut url = Url::parse(&api_url.url)?; + + // Add fronting domains if available + #[cfg(feature = "tunneling")] + if let Some(ref front_hosts) = api_url.front_hosts { + let fronts: Vec = front_hosts + .iter() + .map(|host| format!("https://{}", host)) + .collect(); + url = Url::new(api_url.url.clone(), Some(fronts)) + .map_err(|e| HttpClientError::GenericRequestFailure(e.to_string()))?; + } + + Ok(url) + }) + .collect::, HttpClientError>>()?; + + let mut builder = Self::new_with_urls(urls); + + // Enable domain fronting by default (on retry) + #[cfg(feature = "tunneling")] + { + builder = builder.with_fronting(FrontPolicy::OnRetry); + } + + Ok(builder) + } + /// Constructs a new http `ClientBuilder` from a valid url. pub fn new_with_urls(urls: Vec) -> Self { let urls = Self::check_urls(urls); @@ -429,6 +478,7 @@ impl ClientBuilder { front: None, retry_limit: 0, + serialization: SerializationFormat::Json, } } @@ -501,6 +551,17 @@ impl ClientBuilder { self } + /// Set the serialization format for API requests and responses + pub fn with_serialization(mut self, format: SerializationFormat) -> Self { + self.serialization = format; + self + } + + /// Configure the client to use bincode serialization + pub fn with_bincode(self) -> Self { + self.with_serialization(SerializationFormat::Bincode) + } + /// Returns a Client that uses this ClientBuilder configuration. pub fn build(self) -> Result> where @@ -542,6 +603,7 @@ impl ClientBuilder { #[cfg(target_arch = "wasm32")] request_timeout: self.timeout.unwrap_or(DEFAULT_TIMEOUT), retry_limit: self.retry_limit, + serialization: self.serialization, }; Ok(client) @@ -562,6 +624,7 @@ pub struct Client { request_timeout: Duration, retry_limit: usize, + serialization: SerializationFormat, } impl Client { @@ -619,6 +682,7 @@ impl Client { #[cfg(target_arch = "wasm32")] request_timeout: self.request_timeout, + serialization: self.serialization, } } @@ -743,6 +807,13 @@ impl ApiClientCore for Client { let mut rb = RequestBuilder::from_parts(self.reqwest_client.clone(), req); + // Set Accept header based on serialization preference + let accept_header = match self.serialization { + SerializationFormat::Json => "application/json", + SerializationFormat::Bincode => "application/bincode", + }; + rb = rb.header(reqwest::header::ACCEPT, accept_header); + if let Some(body) = json_body { rb = rb.json(body); }