diff --git a/src/client.rs b/src/client.rs index 526f0338..dfee5e7e 100644 --- a/src/client.rs +++ b/src/client.rs @@ -314,6 +314,28 @@ impl HttpClientBuilder { self } + /// Set a mapping of host rule + /// + /// Entries in the given map will be used first before using the default host + /// rules for host+port pairs. + /// + /// # Examples + /// + /// ``` + /// use isahc::{config::HostRuleMap, prelude::*, HttpClient}; + /// + /// let client = HttpClient::builder() + /// .host_rule(HostRuleMap::new() + /// // Send requests for example.org on port 80 to github.com. + /// .add("example.org", 80, "github.com")) + /// .build()?; + /// # Ok::<(), Box>(()) + /// ``` + pub fn host_rule(mut self, map: HostRuleMap) -> Self { + self.client_config.host_rule = Some(map); + self + } + /// Add a default header to be passed with every request. /// /// If a default header value is already defined for the given key, then a diff --git a/src/config/client.rs b/src/config/client.rs index 19fc2b94..fbff4bb5 100644 --- a/src/config/client.rs +++ b/src/config/client.rs @@ -1,5 +1,6 @@ use super::{ dns::{DnsCache, ResolveMap}, + host_rule::HostRuleMap, request::SetOpt, }; use std::time::Duration; @@ -10,6 +11,7 @@ pub(crate) struct ClientConfig { pub(crate) close_connections: bool, pub(crate) dns_cache: Option, pub(crate) dns_resolve: Option, + pub(crate) host_rule: Option, } impl SetOpt for ClientConfig { @@ -26,6 +28,10 @@ impl SetOpt for ClientConfig { map.set_opt(easy)?; } + if let Some(map) = self.host_rule.as_ref() { + map.set_opt(easy)?; + } + easy.forbid_reuse(self.close_connections) } } diff --git a/src/config/host_rule.rs b/src/config/host_rule.rs new file mode 100644 index 00000000..68b666ff --- /dev/null +++ b/src/config/host_rule.rs @@ -0,0 +1,39 @@ +//! Configuration of host rule. +use super::SetOpt; + +/// A mapping of host and port pairs to specified host +/// +/// Entries added to this map can be used to override how host is resolved for a +/// request and use specific host instead of using the default name +/// resolver. +#[derive(Clone, Debug, Default)] +pub struct HostRuleMap(Vec); + +impl HostRuleMap { + /// Create a new empty rule map. + pub const fn new() -> Self { + HostRuleMap(Vec::new()) + } + + /// Add a host mapping for a given host and port pair. + pub fn add(mut self, host: H, port: u16, connect_to_host: H) -> Self + where + H: AsRef, + { + self.0 + .push(format!("{}:{}:{}", host.as_ref(), port, connect_to_host.as_ref())); + self + } +} + +impl SetOpt for HostRuleMap { + fn set_opt(&self, easy: &mut curl::easy::Easy2) -> Result<(), curl::Error> { + let mut list = curl::easy::List::new(); + + for entry in self.0.iter() { + list.append(entry)?; + } + + easy.connect_to(list) + } +} diff --git a/src/config/mod.rs b/src/config/mod.rs index 50d7a03d..48819ad9 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -22,12 +22,14 @@ pub(crate) mod client; pub(crate) mod dial; pub(crate) mod dns; pub(crate) mod proxy; +pub(crate) mod host_rule; pub(crate) mod redirect; pub(crate) mod request; pub(crate) mod ssl; pub use dial::{Dialer, DialerParseError}; pub use dns::{DnsCache, ResolveMap}; +pub use host_rule::HostRuleMap; pub use redirect::RedirectPolicy; pub use ssl::{CaCertificate, ClientCertificate, PrivateKey, SslOption};