nym_http_api_client/
fronted.rs1use std::sync::atomic::{AtomicBool, Ordering};
7use tracing::warn;
8
9use crate::ClientBuilder;
10
11#[derive(Debug)]
13pub(crate) struct Front {
14 pub(crate) policy: FrontPolicy,
15 enabled: AtomicBool,
16}
17
18impl Clone for Front {
19 fn clone(&self) -> Self {
20 Self {
21 policy: self.policy.clone(),
22 enabled: AtomicBool::new(self.enabled.load(Ordering::Relaxed)),
23 }
24 }
25}
26
27impl Front {
28 pub(crate) fn new(policy: FrontPolicy) -> Self {
29 Self {
30 enabled: AtomicBool::new(policy == FrontPolicy::Always),
31 policy,
32 }
33 }
34
35 pub(crate) fn is_enabled(&self) -> bool {
36 match self.policy {
37 FrontPolicy::Off => false,
38 FrontPolicy::OnRetry => self.enabled.load(Ordering::Relaxed),
39 FrontPolicy::Always => true,
40 }
41 }
42
43 pub(crate) fn retry_enable(&self) {
46 if self.is_enabled() {
47 return;
48 }
49 if matches!(self.policy, FrontPolicy::OnRetry) {
50 self.enabled.store(true, Ordering::Relaxed);
51 }
52 }
53}
54
55#[derive(Debug, Default, PartialEq, Clone)]
56#[cfg(feature = "tunneling")]
57pub enum FrontPolicy {
59 Always,
61 OnRetry,
63 #[default]
64 Off,
66}
67
68impl ClientBuilder {
69 #[cfg(feature = "tunneling")]
71 pub fn with_fronting(mut self, policy: FrontPolicy) -> Self {
72 let front = Front::new(policy);
73
74 if !self.urls.iter().any(|url| url.has_front()) {
76 warn!(
77 "fronting is enabled, but none of the supplied urls have configured fronting domains"
78 );
79 }
80
81 self.front = Some(front);
82
83 self
84 }
85}
86
87#[cfg(test)]
88mod tests {
89 use super::*;
90 use crate::{ApiClientCore, NO_PARAMS, Url};
91
92 #[tokio::test]
93 async fn nym_api_works() {
94 let url1 = Url::new(
95 "https://validator.global.ssl.fastly.net",
96 Some(vec!["https://yelp.global.ssl.fastly.net"]),
97 )
98 .unwrap(); let client = ClientBuilder::new(url1)
106 .expect("bad url")
107 .with_fronting(FrontPolicy::Always)
108 .build()
109 .expect("failed to build client");
110
111 let response = client
112 .send_request::<_, (), &str, &str>(
113 reqwest::Method::GET,
114 &["api", "v1", "network", "details"],
115 NO_PARAMS,
116 None,
117 )
118 .await
119 .expect("failed get request");
120
121 assert_eq!(response.status(), 200);
123 }
124
125 #[tokio::test]
126 async fn fallback_on_failure() {
127 let url1 = Url::new(
128 "https://fake-domain.nymtech.net",
129 Some(vec![
130 "https://fake-front-1.nymtech.net",
131 "https://fake-front-2.nymtech.net",
132 ]),
133 )
134 .unwrap();
135 let url2 = Url::new(
136 "https://validator.global.ssl.fastly.net",
137 Some(vec!["https://yelp.global.ssl.fastly.net"]),
138 )
139 .unwrap(); let client = ClientBuilder::new_with_urls(vec![url1, url2])
142 .expect("bad url")
143 .with_fronting(FrontPolicy::Always)
144 .build()
145 .expect("failed to build client");
146
147 assert_eq!(
149 client.current_url().as_str(),
150 "https://fake-domain.nymtech.net/",
151 );
152 assert_eq!(
153 client.current_url().front_str(),
154 Some("fake-front-1.nymtech.net"),
155 );
156
157 let result = client
158 .send_request::<_, (), &str, &str>(
159 reqwest::Method::GET,
160 &["api", "v1", "network", "details"],
161 NO_PARAMS,
162 None,
163 )
164 .await;
165 assert!(result.is_err());
166
167 assert_eq!(
169 client.current_url().as_str(),
170 "https://fake-domain.nymtech.net/",
171 );
172 assert_eq!(
173 client.current_url().front_str(),
174 Some("fake-front-2.nymtech.net"),
175 );
176
177 let result = client
178 .send_request::<_, (), &str, &str>(
179 reqwest::Method::GET,
180 &["api", "v1", "network", "details"],
181 NO_PARAMS,
182 None,
183 )
184 .await;
185 assert!(result.is_err());
186
187 assert_eq!(
189 client.current_url().as_str(),
190 "https://validator.global.ssl.fastly.net/",
191 );
192 assert_eq!(
193 client.current_url().front_str(),
194 Some("yelp.global.ssl.fastly.net"),
195 );
196
197 let response = client
198 .send_request::<_, (), &str, &str>(
199 reqwest::Method::GET,
200 &["api", "v1", "network", "details"],
201 NO_PARAMS,
202 None,
203 )
204 .await
205 .expect("failed get request");
206
207 assert_eq!(response.status(), 200);
208 }
209}