1use std::{
4 fmt,
5 sync::{
6 Arc, OnceLock,
7 atomic::{AtomicU64, Ordering},
8 },
9 time::{Duration, Instant},
10};
11
12use arc_swap::ArcSwapOption;
13use rama_core::{
14 Layer, Service,
15 error::{BoxError, BoxErrorExt as _, ErrorContext},
16 error_sink::{ErrorSink, TracingErrorSink},
17 extensions::{Extensions, ExtensionsRef},
18 service::{BoxService, service_fn},
19};
20use rama_utils::macros::generate_set_and_with;
21
22#[cfg(any(
23 test,
24 target_vendor = "apple",
25 target_os = "android",
26 target_os = "linux",
27 target_os = "freebsd",
28 target_os = "netbsd",
29 target_os = "openbsd",
30 target_os = "dragonfly"
31))]
32use crate::address::{Host, HostWithPort};
33use crate::{
34 Protocol,
35 address::{Authority, HostRef, HostWithOptPort, ProxyAddress},
36 input_ext::{AuthorityInputExt, ProtocolInputExt, UriInputExt},
37 uri::Uri,
38};
39
40use super::{
41 ProxyRoute, ProxyRoutes,
42 bypass::{BypassRule, BypassRuleDialect, is_simple_hostname, matches_any_rule},
43 load::LoadErrorPolicy,
44};
45
46mod platform;
47
48pub const DEFAULT_SYSTEM_PROXY_CONFIG_TTL: Duration = Duration::from_secs(10);
58
59#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
66#[non_exhaustive]
67pub enum SystemProxyInvalidBypassRulePolicy {
68 #[default]
70 Ignore,
71 Reject,
73}
74
75#[derive(Debug, Clone)]
83pub struct SystemProxyPacRequest {
84 pub extensions: Extensions,
86 pub uri: Uri,
88}
89
90impl SystemProxyPacRequest {
91 pub fn new(extensions: Extensions, uri: Uri) -> Result<Self, BoxError> {
93 if !uri.is_absolute() || uri.host().is_none() {
94 return Err(BoxError::from_static_str(
95 "system proxy PAC request URI must be absolute and have a host",
96 ));
97 }
98 Ok(Self { extensions, uri })
99 }
100}
101
102impl ExtensionsRef for SystemProxyPacRequest {
103 fn extensions(&self) -> &Extensions {
104 &self.extensions
105 }
106}
107
108impl UriInputExt for SystemProxyPacRequest {
109 fn uri(&self) -> &Uri {
110 &self.uri
111 }
112}
113
114pub trait SystemProxyPacResolver:
122 Service<SystemProxyPacRequest, Output = Option<ProxyRoutes>, Error: Into<BoxError>>
123{
124}
125
126impl<T> SystemProxyPacResolver for T where
127 T: Service<SystemProxyPacRequest, Output = Option<ProxyRoutes>, Error: Into<BoxError>>
128{
129}
130
131pub trait SystemProxyPacService:
137 Service<Uri, Error: Into<BoxError>, Output: SystemProxyPacResolver>
138{
139}
140
141impl<T> SystemProxyPacService for T where
142 T: Service<Uri, Error: Into<BoxError>, Output: SystemProxyPacResolver>
143{
144}
145
146#[derive(Debug, Clone, Default)]
156pub struct SystemProxyConfig {
157 http: Option<ProxyAddress>,
158 https: Option<ProxyAddress>,
159 socks5: Option<ProxyAddress>,
160 pac_uri: Option<Uri>,
161 auto_detect: bool,
162 bypass: Arc<[BypassRule]>,
163 exclude_simple_hostnames: bool,
164 reversed_bypass: bool,
165 bypass_before_pac: bool,
166}
167
168impl SystemProxyConfig {
169 fn replace_bypass_ignoring_invalid(
170 &mut self,
171 bypass: impl IntoIterator<Item = impl Into<Box<str>>>,
172 dialect: BypassRuleDialect,
173 ) {
174 self.bypass = bypass
175 .into_iter()
176 .filter_map(
177 |value| match BypassRule::compile_with_dialect(value, dialect) {
178 Ok(rule) => Some(rule),
179 Err(error) => {
180 rama_core::telemetry::tracing::debug!(
181 error = %error,
182 "ignoring invalid system proxy bypass pattern"
183 );
184 None
185 }
186 },
187 )
188 .collect();
189 }
190
191 fn try_replace_bypass(
192 &mut self,
193 bypass: impl IntoIterator<Item = impl Into<Box<str>>>,
194 policy: SystemProxyInvalidBypassRulePolicy,
195 dialect: BypassRuleDialect,
196 ) -> Result<(), BoxError> {
197 match policy {
198 SystemProxyInvalidBypassRulePolicy::Ignore => {
199 self.replace_bypass_ignoring_invalid(bypass, dialect);
200 }
201 SystemProxyInvalidBypassRulePolicy::Reject => {
202 self.bypass = bypass
203 .into_iter()
204 .map(|value| BypassRule::compile_with_dialect(value, dialect))
205 .collect::<Result<Vec<_>, _>>()?
206 .into();
207 }
208 }
209 Ok(())
210 }
211
212 #[cfg(any(
213 test,
214 target_vendor = "apple",
215 target_os = "android",
216 target_os = "windows",
217 target_os = "linux",
218 target_os = "freebsd",
219 target_os = "netbsd",
220 target_os = "openbsd",
221 target_os = "dragonfly"
222 ))]
223 fn try_set_bypass_with_dialect(
224 &mut self,
225 bypass: impl IntoIterator<Item = impl Into<Box<str>>>,
226 policy: SystemProxyInvalidBypassRulePolicy,
227 dialect: BypassRuleDialect,
228 ) -> Result<(), BoxError> {
229 self.try_replace_bypass(bypass, policy, dialect)
230 }
231
232 pub async fn try_from_system() -> Result<Self, BoxError> {
255 Self::try_from_system_with_invalid_bypass_rule_policy(
256 SystemProxyInvalidBypassRulePolicy::Ignore,
257 )
258 .await
259 }
260
261 pub async fn try_from_system_with_invalid_bypass_rule_policy(
269 policy: SystemProxyInvalidBypassRulePolicy,
270 ) -> Result<Self, BoxError> {
271 platform::read(policy)
272 .await
273 .context("read system proxy configuration")
274 }
275
276 #[must_use]
279 pub fn is_empty(&self) -> bool {
280 self.http.is_none()
281 && self.https.is_none()
282 && self.socks5.is_none()
283 && self.pac_uri.is_none()
284 && !self.auto_detect
285 }
286
287 #[must_use]
289 pub const fn http_proxy(&self) -> Option<&ProxyAddress> {
290 self.http.as_ref()
291 }
292
293 #[must_use]
295 pub const fn https_proxy(&self) -> Option<&ProxyAddress> {
296 self.https.as_ref()
297 }
298
299 #[must_use]
301 pub const fn socks5_proxy(&self) -> Option<&ProxyAddress> {
302 self.socks5.as_ref()
303 }
304
305 #[must_use]
307 pub const fn pac_uri(&self) -> Option<&Uri> {
308 self.pac_uri.as_ref()
309 }
310
311 #[must_use]
314 pub const fn auto_detect(&self) -> bool {
315 self.auto_detect
316 }
317
318 pub fn bypass(&self) -> impl Iterator<Item = &str> {
320 self.bypass.iter().map(BypassRule::raw)
321 }
322
323 #[must_use]
325 pub const fn exclude_simple_hostnames(&self) -> bool {
326 self.exclude_simple_hostnames
327 }
328
329 #[must_use]
334 pub const fn reversed_bypass(&self) -> bool {
335 self.reversed_bypass
336 }
337
338 generate_set_and_with! {
339 pub fn http_proxy(mut self, proxy: Option<ProxyAddress>) -> Self {
341 self.http = proxy;
342 self
343 }
344 }
345
346 generate_set_and_with! {
347 pub fn https_proxy(mut self, proxy: Option<ProxyAddress>) -> Self {
349 self.https = proxy;
350 self
351 }
352 }
353
354 generate_set_and_with! {
355 pub fn socks5_proxy(mut self, proxy: Option<ProxyAddress>) -> Self {
357 self.socks5 = proxy;
358 self
359 }
360 }
361
362 generate_set_and_with! {
363 pub fn pac_uri(mut self, pac_uri: Option<Uri>) -> Self {
365 self.pac_uri = pac_uri;
366 self
367 }
368 }
369
370 generate_set_and_with! {
371 pub fn auto_detect(mut self, auto_detect: bool) -> Self {
375 self.auto_detect = auto_detect;
376 self
377 }
378 }
379
380 generate_set_and_with! {
381 pub fn bypass(
389 mut self,
390 bypass: impl IntoIterator<Item = impl Into<Box<str>>>,
391 ) -> Self {
392 self.replace_bypass_ignoring_invalid(bypass, BypassRuleDialect::Rama);
393 self
394 }
395 }
396
397 generate_set_and_with! {
398 pub fn bypass(
407 mut self,
408 bypass: impl IntoIterator<Item = impl Into<Box<str>>>,
409 policy: SystemProxyInvalidBypassRulePolicy,
410 ) -> Result<Self, BoxError> {
411 self.try_replace_bypass(bypass, policy, BypassRuleDialect::Rama)?;
412 Ok(self)
413 }
414 }
415
416 generate_set_and_with! {
417 pub fn exclude_simple_hostnames(mut self, exclude: bool) -> Self {
419 self.exclude_simple_hostnames = exclude;
420 self
421 }
422 }
423
424 generate_set_and_with! {
425 pub fn reversed_bypass(mut self, reversed: bool) -> Self {
427 self.reversed_bypass = reversed;
428 self
429 }
430 }
431
432 fn decision(&self, uri: &Uri) -> SystemProxyDecision {
433 if let Some(pac_uri) = &self.pac_uri {
434 if uri.host().is_some_and(|host| {
435 host.is_loopback()
436 || (self.bypass_before_pac
437 && self.bypasses(
438 uri.scheme(),
439 host,
440 uri.port_u16()
441 .or_else(|| uri.scheme().and_then(Protocol::default_port)),
442 ))
443 }) {
444 return SystemProxyDecision::Route(ProxyRoute::Direct);
445 }
446 return SystemProxyDecision::Pac(pac_uri.clone());
447 }
448
449 self.fixed_route(uri)
450 .map(SystemProxyDecision::Route)
451 .unwrap_or(SystemProxyDecision::None)
452 }
453
454 fn fixed_route(&self, uri: &Uri) -> Option<ProxyRoute> {
455 let host = uri.host()?;
456 self.fixed_route_for(
457 uri.scheme(),
458 host,
459 uri.port_u16()
460 .or_else(|| uri.scheme().and_then(Protocol::default_port)),
461 )
462 }
463
464 fn fixed_route_for(
465 &self,
466 scheme: Option<&Protocol>,
467 host: HostRef<'_>,
468 port: Option<u16>,
469 ) -> Option<ProxyRoute> {
470 let proxy = match scheme {
471 Some(protocol) if *protocol == Protocol::HTTPS || *protocol == Protocol::WSS => {
472 self.https.as_ref()
473 }
474 Some(protocol) if *protocol == Protocol::HTTP || *protocol == Protocol::WS => {
475 self.http.as_ref()
476 }
477 _ => None,
478 }
479 .or(self.socks5.as_ref());
480 let proxy = proxy?;
481
482 if host.is_loopback() || self.bypasses(scheme, host, port) {
485 return Some(ProxyRoute::Direct);
486 }
487 Some(ProxyRoute::Proxy(proxy.clone()))
488 }
489
490 fn bypasses(&self, scheme: Option<&Protocol>, host: HostRef<'_>, port: Option<u16>) -> bool {
491 let matches = (self.exclude_simple_hostnames && is_simple_hostname(host))
492 || matches_any_rule(&self.bypass, scheme, host, port);
493 if self.reversed_bypass {
494 !matches
495 } else {
496 matches
497 }
498 }
499}
500
501enum SystemProxyDecision {
502 None,
503 Route(ProxyRoute),
504 Pac(Uri),
505}
506
507type SystemProxyConfigReader = BoxService<(), SystemProxyConfig, BoxError>;
508type BoxSystemProxyConfigChangeTrigger = BoxService<(), bool, BoxError>;
509
510#[derive(Clone, Default)]
511struct LazyPlatformConfigChangeTrigger {
512 trigger: Arc<OnceLock<Arc<platform::PlatformConfigChangeTrigger>>>,
513}
514
515impl LazyPlatformConfigChangeTrigger {
516 fn poll(&self) -> Result<bool, BoxError> {
517 self.trigger
518 .get_or_init(platform::config_change_trigger)
519 .poll()
520 }
521
522 #[cfg(test)]
523 fn is_initialized(&self) -> bool {
524 self.trigger.get().is_some()
525 }
526}
527
528#[derive(Clone)]
529enum SystemProxyConfigChangeTrigger {
530 Platform(LazyPlatformConfigChangeTrigger),
531 Custom(BoxSystemProxyConfigChangeTrigger),
532}
533
534impl fmt::Debug for SystemProxyConfigChangeTrigger {
535 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
536 match self {
537 Self::Platform(_) => f.write_str("Platform(_)"),
538 Self::Custom(trigger) => f.debug_tuple("Custom").field(trigger).finish(),
539 }
540 }
541}
542
543#[derive(Debug, Default)]
544struct RefreshRequestState {
545 requested_generation: AtomicU64,
546 completed_generation: AtomicU64,
547}
548
549impl RefreshRequestState {
550 fn request(&self) -> u64 {
551 self.requested_generation
552 .fetch_update(Ordering::AcqRel, Ordering::Acquire, |generation| {
553 Some(generation.saturating_add(1))
554 })
555 .unwrap_or_else(|generation| generation)
556 .saturating_add(1)
557 }
558}
559
560#[derive(Clone)]
561struct RefreshRequest {
562 state: Arc<RefreshRequestState>,
563 generation: u64,
564}
565
566impl RefreshRequest {
567 fn is_pending(&self) -> bool {
568 self.generation > self.state.completed_generation.load(Ordering::Acquire)
569 }
570
571 fn complete(&self) {
572 self.state
573 .completed_generation
574 .fetch_max(self.generation, Ordering::AcqRel);
575 }
576}
577
578#[derive(Clone)]
579struct SystemProxyConfigRefresh {
580 enabled: bool,
581 trigger: Option<SystemProxyConfigChangeTrigger>,
582 trigger_error_sink: Arc<dyn ErrorSink>,
583 requests: Arc<RefreshRequestState>,
584}
585
586impl fmt::Debug for SystemProxyConfigRefresh {
587 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
588 f.debug_struct("SystemProxyConfigRefresh")
589 .field("enabled", &self.enabled)
590 .field("trigger", &self.trigger)
591 .finish_non_exhaustive()
592 }
593}
594
595impl Default for SystemProxyConfigRefresh {
596 fn default() -> Self {
597 Self {
598 enabled: true,
599 trigger: Some(SystemProxyConfigChangeTrigger::Platform(
600 LazyPlatformConfigChangeTrigger::default(),
601 )),
602 trigger_error_sink: Arc::new(TracingErrorSink::default()),
603 requests: Arc::new(RefreshRequestState::default()),
604 }
605 }
606}
607
608impl SystemProxyConfigRefresh {
609 async fn requested(&self) -> RefreshRequest {
610 let changed = if !self.enabled {
611 false
612 } else if let Some(trigger) = &self.trigger {
613 match trigger {
614 SystemProxyConfigChangeTrigger::Platform(trigger) => trigger.poll(),
615 SystemProxyConfigChangeTrigger::Custom(trigger) => trigger.serve(()).await,
616 }
617 .unwrap_or_else(|error| {
618 self.trigger_error_sink.sink_error(error);
619 false
620 })
621 } else {
622 false
623 };
624 let generation = if changed {
625 self.requests.request()
626 } else {
627 self.requests.requested_generation.load(Ordering::Acquire)
628 };
629 RefreshRequest {
630 state: self.requests.clone(),
631 generation,
632 }
633 }
634}
635
636#[derive(Debug)]
637struct IntoBoxErrorService<T>(T);
638
639impl<T> Service<()> for IntoBoxErrorService<T>
640where
641 T: Service<(), Output = bool>,
642 T::Error: Into<BoxError>,
643{
644 type Output = bool;
645 type Error = BoxError;
646
647 async fn serve(&self, (): ()) -> Result<Self::Output, Self::Error> {
648 self.0.serve(()).await.map_err(Into::into)
649 }
650}
651
652fn system_proxy_config_reader(
653 policy: SystemProxyInvalidBypassRulePolicy,
654) -> SystemProxyConfigReader {
655 BoxService::new(service_fn(move |()| async move {
656 SystemProxyConfig::try_from_system_with_invalid_bypass_rule_policy(policy).await
657 }))
658}
659
660struct SystemProxyConfigCache {
661 current: ArcSwapOption<SystemProxyConfig>,
662 ttl: Duration,
663 epoch: Instant,
664 refresh_after_nanos: AtomicU64,
665 cold_failure_generation: AtomicU64,
666 refresh_lock: tokio::sync::Mutex<()>,
667 reader: SystemProxyConfigReader,
668}
669
670impl fmt::Debug for SystemProxyConfigCache {
671 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
672 f.debug_struct("SystemProxyConfigCache")
673 .field("current", &self.current.load_full())
674 .field("ttl", &self.ttl)
675 .field(
676 "refresh_after_nanos",
677 &self.refresh_after_nanos.load(Ordering::Relaxed),
678 )
679 .field(
680 "cold_failure_generation",
681 &self.cold_failure_generation.load(Ordering::Relaxed),
682 )
683 .finish_non_exhaustive()
684 }
685}
686
687impl SystemProxyConfigCache {
688 fn new(
689 current: Option<SystemProxyConfig>,
690 ttl: Duration,
691 reader: SystemProxyConfigReader,
692 ) -> Self {
693 let epoch = Instant::now();
694 let refresh_after_nanos = if current.is_some() {
695 duration_nanos(ttl)
696 } else {
697 0
698 };
699 Self {
700 current: ArcSwapOption::from(current.map(Arc::new)),
701 ttl,
702 epoch,
703 refresh_after_nanos: AtomicU64::new(refresh_after_nanos),
704 cold_failure_generation: AtomicU64::new(0),
705 refresh_lock: tokio::sync::Mutex::new(()),
706 reader,
707 }
708 }
709
710 fn cached(&self) -> Option<Arc<SystemProxyConfig>> {
711 self.current.load_full()
712 }
713
714 fn is_fresh(&self, now: u64) -> bool {
715 now < self.refresh_after_nanos.load(Ordering::Acquire)
716 }
717
718 fn schedule_next_refresh(&self) {
719 let now = duration_nanos(self.epoch.elapsed());
720 self.refresh_after_nanos.store(
721 now.saturating_add(duration_nanos(self.ttl)),
722 Ordering::Release,
723 );
724 }
725
726 async fn refresh(
727 &self,
728 stale: Option<Arc<SystemProxyConfig>>,
729 load_error_policy: &LoadErrorPolicy,
730 ) -> Result<Arc<SystemProxyConfig>, BoxError> {
731 match self.reader.serve(()).await {
732 Ok(config) => {
733 let config = Arc::new(config);
734 self.current.store(Some(config.clone()));
735 self.schedule_next_refresh();
736 Ok(config)
737 }
738 Err(error) => {
739 let Some(stale) = stale else {
740 load_error_policy.handle(error)?;
741 let config = Arc::new(SystemProxyConfig::default());
742 self.current.store(Some(config.clone()));
743 self.schedule_next_refresh();
744 return Ok(config);
745 };
746 self.schedule_next_refresh();
747 if let Err(error) = load_error_policy.handle(error) {
748 rama_core::telemetry::tracing::warn!(
749 error = %error,
750 "failed to refresh system proxy configuration; retaining prior snapshot"
751 );
752 }
753 Ok(stale)
754 }
755 }
756 }
757
758 async fn snapshot(
759 &self,
760 refresh_enabled: bool,
761 refresh_request: &RefreshRequest,
762 load_error_policy: &LoadErrorPolicy,
763 ) -> Result<Arc<SystemProxyConfig>, BoxError> {
764 let refresh_requested = refresh_request.is_pending();
765 let current = self.current.load_full();
766 let now = duration_nanos(self.epoch.elapsed());
767 if let Some(current) = current
768 .as_ref()
769 .filter(|_| !refresh_enabled || (!refresh_requested && self.is_fresh(now)))
770 {
771 return Ok(current.clone());
772 }
773
774 if let Some(stale) = current {
775 let Ok(_guard) = self.refresh_lock.try_lock() else {
776 return Ok(stale);
777 };
778 let latest = self.current.load_full().unwrap_or(stale);
779 let now = duration_nanos(self.epoch.elapsed());
780 if !refresh_request.is_pending() && self.is_fresh(now) {
781 return Ok(latest);
782 }
783 let result = self.refresh(Some(latest), load_error_policy).await;
784 refresh_request.complete();
785 return result;
786 }
787
788 let observed_failure = self.cold_failure_generation.load(Ordering::Acquire);
792 let _guard = self.refresh_lock.lock().await;
793 if let Some(current) = self.current.load_full() {
794 if !refresh_request.is_pending() {
795 return Ok(current);
796 }
797 let result = self.refresh(Some(current), load_error_policy).await;
798 refresh_request.complete();
799 return result;
800 }
801 if observed_failure != self.cold_failure_generation.load(Ordering::Acquire) {
802 return Err(BoxError::from_static_str(
803 "system proxy configuration load failed while this request was waiting",
804 ));
805 }
806 let result = self.refresh(None, load_error_policy).await;
807 refresh_request.complete();
808 if result.is_err() {
809 self.cold_failure_generation.fetch_add(1, Ordering::Release);
810 }
811 result
812 }
813}
814
815fn duration_nanos(duration: Duration) -> u64 {
816 duration.as_nanos().try_into().unwrap_or(u64::MAX)
817}
818
819#[doc(hidden)]
820#[derive(Debug, Clone, Copy, Default)]
821pub struct SystemProxyPacDisabled;
822
823#[doc(hidden)]
824#[derive(Debug, Clone, Copy)]
825pub struct SystemProxyPacDisabledResolver;
826
827impl Service<Uri> for SystemProxyPacDisabled {
828 type Output = SystemProxyPacDisabledResolver;
829 type Error = std::convert::Infallible;
830
831 async fn serve(&self, _uri: Uri) -> Result<Self::Output, Self::Error> {
832 Ok(SystemProxyPacDisabledResolver)
833 }
834}
835
836impl Service<SystemProxyPacRequest> for SystemProxyPacDisabledResolver {
837 type Output = Option<ProxyRoutes>;
838 type Error = std::convert::Infallible;
839
840 async fn serve(&self, _request: SystemProxyPacRequest) -> Result<Self::Output, Self::Error> {
841 Ok(None)
842 }
843}
844
845#[derive(Clone)]
879pub struct SystemProxyLayer<P = SystemProxyPacDisabled> {
880 config: Arc<SystemProxyConfigCache>,
881 refresh: SystemProxyConfigRefresh,
882 load_error_policy: LoadErrorPolicy,
883 pac: P,
884 pac_enabled: bool,
885 overwrite: bool,
886}
887
888impl<P: fmt::Debug> fmt::Debug for SystemProxyLayer<P> {
889 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
890 f.debug_struct("SystemProxyLayer")
891 .field("config", &self.config)
892 .field("refresh", &self.refresh)
893 .field("load_error_policy", &self.load_error_policy)
894 .field("pac", &self.pac)
895 .field("pac_enabled", &self.pac_enabled)
896 .field("overwrite", &self.overwrite)
897 .finish()
898 }
899}
900
901impl SystemProxyLayer {
902 #[must_use]
907 pub fn new() -> Self {
908 Self::new_with_ttl_and_invalid_bypass_rule_policy(
909 DEFAULT_SYSTEM_PROXY_CONFIG_TTL,
910 SystemProxyInvalidBypassRulePolicy::Ignore,
911 )
912 }
913
914 #[must_use]
916 pub fn new_with_ttl(ttl: Duration) -> Self {
917 Self::new_with_ttl_and_invalid_bypass_rule_policy(
918 ttl,
919 SystemProxyInvalidBypassRulePolicy::Ignore,
920 )
921 }
922
923 #[must_use]
925 pub fn new_with_ttl_and_invalid_bypass_rule_policy(
926 ttl: Duration,
927 policy: SystemProxyInvalidBypassRulePolicy,
928 ) -> Self {
929 Self::new_with_reader(ttl, system_proxy_config_reader(policy))
930 }
931
932 fn new_with_reader(ttl: Duration, reader: SystemProxyConfigReader) -> Self {
933 Self {
934 config: Arc::new(SystemProxyConfigCache::new(None, ttl, reader)),
935 refresh: SystemProxyConfigRefresh::default(),
936 load_error_policy: LoadErrorPolicy::Reject,
937 pac: SystemProxyPacDisabled,
938 pac_enabled: false,
939 overwrite: false,
940 }
941 }
942
943 #[must_use]
949 pub fn from_cached(config: SystemProxyConfig) -> Self {
950 Self::from_cached_with_invalid_bypass_rule_policy(
951 config,
952 SystemProxyInvalidBypassRulePolicy::Ignore,
953 )
954 }
955
956 #[must_use]
959 pub fn from_cached_with_invalid_bypass_rule_policy(
960 config: SystemProxyConfig,
961 policy: SystemProxyInvalidBypassRulePolicy,
962 ) -> Self {
963 Self::from_cached_with_reader(
964 config,
965 DEFAULT_SYSTEM_PROXY_CONFIG_TTL,
966 system_proxy_config_reader(policy),
967 )
968 }
969
970 fn from_cached_with_reader(
971 config: SystemProxyConfig,
972 ttl: Duration,
973 reader: SystemProxyConfigReader,
974 ) -> Self {
975 Self {
976 config: Arc::new(SystemProxyConfigCache::new(Some(config), ttl, reader)),
977 refresh: SystemProxyConfigRefresh::default(),
978 load_error_policy: LoadErrorPolicy::Reject,
979 pac: SystemProxyPacDisabled,
980 pac_enabled: false,
981 overwrite: false,
982 }
983 }
984
985 pub async fn try_from_system() -> Result<Self, BoxError> {
987 Self::try_from_system_with_invalid_bypass_rule_policy(
988 SystemProxyInvalidBypassRulePolicy::Ignore,
989 )
990 .await
991 }
992
993 pub async fn try_from_system_with_invalid_bypass_rule_policy(
996 policy: SystemProxyInvalidBypassRulePolicy,
997 ) -> Result<Self, BoxError> {
998 Self::try_from_system_with_ttl_and_invalid_bypass_rule_policy(
999 DEFAULT_SYSTEM_PROXY_CONFIG_TTL,
1000 policy,
1001 )
1002 .await
1003 }
1004
1005 pub async fn try_from_system_with_ttl(ttl: Duration) -> Result<Self, BoxError> {
1012 Self::try_from_system_with_ttl_and_invalid_bypass_rule_policy(
1013 ttl,
1014 SystemProxyInvalidBypassRulePolicy::Ignore,
1015 )
1016 .await
1017 }
1018
1019 pub async fn try_from_system_with_ttl_and_invalid_bypass_rule_policy(
1022 ttl: Duration,
1023 policy: SystemProxyInvalidBypassRulePolicy,
1024 ) -> Result<Self, BoxError> {
1025 Self::try_from_system_with_reader(ttl, system_proxy_config_reader(policy)).await
1026 }
1027
1028 async fn try_from_system_with_reader(
1029 ttl: Duration,
1030 reader: SystemProxyConfigReader,
1031 ) -> Result<Self, BoxError> {
1032 let layer = Self::new_with_reader(ttl, reader);
1033 layer.warm_up().await?;
1034 Ok(layer)
1035 }
1036}
1037
1038impl Default for SystemProxyLayer {
1039 fn default() -> Self {
1040 Self::new()
1041 }
1042}
1043
1044impl<P> SystemProxyLayer<P> {
1045 pub async fn config(&self) -> Result<Arc<SystemProxyConfig>, BoxError> {
1052 let refresh_request = self.refresh.requested().await;
1053 self.config
1054 .snapshot(
1055 self.refresh.enabled,
1056 &refresh_request,
1057 &self.load_error_policy,
1058 )
1059 .await
1060 }
1061
1062 #[must_use]
1064 pub fn cached_config(&self) -> Option<Arc<SystemProxyConfig>> {
1065 self.config.cached()
1066 }
1067
1068 pub async fn warm_up(&self) -> Result<(), BoxError> {
1070 self.config().await.map(drop)
1071 }
1072
1073 #[must_use]
1079 pub fn with_pac_service<Q>(self, pac: Q) -> SystemProxyLayer<Q> {
1080 SystemProxyLayer {
1081 config: self.config,
1082 refresh: self.refresh,
1083 load_error_policy: self.load_error_policy,
1084 pac,
1085 pac_enabled: true,
1086 overwrite: self.overwrite,
1087 }
1088 }
1089
1090 generate_set_and_with! {
1091 pub fn load_error_sink(mut self, error_sink: impl ErrorSink) -> Self {
1098 self.load_error_policy = LoadErrorPolicy::Handle(Arc::new(error_sink));
1099 self
1100 }
1101 }
1102
1103 generate_set_and_with! {
1104 pub fn config_refresh(mut self, config_refresh: bool) -> Self {
1110 self.refresh.enabled = config_refresh;
1111 self
1112 }
1113 }
1114
1115 generate_set_and_with! {
1116 pub fn config_change_trigger(
1123 mut self,
1124 trigger: impl Service<(), Output = bool, Error: Into<BoxError>>,
1125 ) -> Self {
1126 self.refresh.trigger = Some(SystemProxyConfigChangeTrigger::Custom(BoxService::new(
1127 IntoBoxErrorService(trigger),
1128 )));
1129 self
1130 }
1131 }
1132
1133 #[must_use]
1135 pub fn without_config_change_trigger(mut self) -> Self {
1136 self.refresh.trigger = None;
1137 self
1138 }
1139
1140 pub fn unset_config_change_trigger(&mut self) -> &mut Self {
1142 self.refresh.trigger = None;
1143 self
1144 }
1145
1146 generate_set_and_with! {
1147 pub fn config_change_trigger_error_sink(
1149 mut self,
1150 error_sink: impl ErrorSink,
1151 ) -> Self {
1152 self.refresh.trigger_error_sink = Arc::new(error_sink);
1153 self
1154 }
1155 }
1156
1157 generate_set_and_with! {
1158 pub fn overwrite(mut self, overwrite: bool) -> Self {
1160 self.overwrite = overwrite;
1161 self
1162 }
1163 }
1164}
1165
1166impl<S, P> Layer<S> for SystemProxyLayer<P>
1167where
1168 P: Clone,
1169{
1170 type Service = SystemProxyService<S, P>;
1171
1172 fn layer(&self, inner: S) -> Self::Service {
1173 SystemProxyService {
1174 inner,
1175 layer: self.clone(),
1176 }
1177 }
1178
1179 fn into_layer(self, inner: S) -> Self::Service {
1180 SystemProxyService { inner, layer: self }
1181 }
1182}
1183
1184#[derive(Debug, Clone)]
1186pub struct SystemProxyService<S, P = SystemProxyPacDisabled> {
1187 inner: S,
1188 layer: SystemProxyLayer<P>,
1189}
1190
1191impl<S, P> SystemProxyService<S, P> {
1192 #[must_use]
1194 pub const fn inner(&self) -> &S {
1195 &self.inner
1196 }
1197
1198 #[must_use]
1200 pub fn inner_mut(&mut self) -> &mut S {
1201 &mut self.inner
1202 }
1203
1204 #[must_use]
1206 pub fn into_inner(self) -> S {
1207 self.inner
1208 }
1209}
1210
1211impl<S, P, Input> Service<Input> for SystemProxyService<S, P>
1212where
1213 S: Service<Input, Error: Into<BoxError>>,
1214 P: SystemProxyPacService,
1215 Input: UriInputExt + AuthorityInputExt + ProtocolInputExt + ExtensionsRef + Send + 'static,
1216{
1217 type Output = S::Output;
1218 type Error = BoxError;
1219
1220 async fn serve(&self, input: Input) -> Result<Self::Output, Self::Error> {
1221 if !self.layer.overwrite && is_already_routed(&input) {
1222 return self.inner.serve(input).await.map_err(Into::into);
1223 }
1224 let config = self.layer.config().await?;
1225 if config.is_empty() {
1226 return self.inner.serve(input).await.map_err(Into::into);
1227 }
1228
1229 let mut normalized_uri = None;
1230 let decision = if self.layer.pac_enabled && config.pac_uri().is_some() {
1231 let uri = absolute_uri(&input)?;
1232 let decision = config.decision(&uri);
1233 normalized_uri = Some(uri);
1234 decision
1235 } else {
1236 let protocol = request_protocol(&input);
1237 let authority = input
1238 .uri()
1239 .authority()
1240 .map(|authority| authority.into_owned().address)
1241 .or_else(|| input.authority());
1242 if let Some(authority) = authority {
1243 config
1244 .fixed_route_for(
1245 Some(&protocol),
1246 authority.host.view(),
1247 authority.port_u16().or_else(|| protocol.default_port()),
1248 )
1249 .map(SystemProxyDecision::Route)
1250 .unwrap_or(SystemProxyDecision::None)
1251 } else {
1252 rama_core::telemetry::tracing::debug!(
1253 "fixed system proxy cannot route an input without an authority"
1254 );
1255 SystemProxyDecision::None
1256 }
1257 };
1258 match decision {
1259 SystemProxyDecision::Pac(pac_uri) => {
1260 let Some(uri) = normalized_uri else {
1261 return Err(BoxError::from_static_str(
1262 "system PAC decision is missing its normalized request URI",
1263 ));
1264 };
1265 let resolver = self
1266 .layer
1267 .pac
1268 .serve(pac_uri)
1269 .await
1270 .context("create system PAC resolver")?;
1271 match resolver
1272 .serve(SystemProxyPacRequest::new(
1273 input.extensions().clone(),
1274 uri.clone(),
1275 )?)
1276 .await
1277 .context("resolve system PAC routes")?
1278 {
1279 Some(routes) => {
1280 input.extensions().insert(routes);
1281 }
1282 None => {
1283 if let Some(route) = config.fixed_route(&uri) {
1284 input.extensions().insert(route);
1285 }
1286 }
1287 }
1288 }
1289 SystemProxyDecision::Route(route) => {
1290 input.extensions().insert(route);
1291 }
1292 SystemProxyDecision::None => {}
1293 }
1294 self.inner.serve(input).await.map_err(Into::into)
1295 }
1296}
1297
1298pub(super) fn absolute_uri<I>(input: &I) -> Result<Uri, BoxError>
1299where
1300 I: UriInputExt + AuthorityInputExt + ProtocolInputExt,
1301{
1302 let uri = input.uri();
1303 let protocol = request_protocol(input);
1304 proxy_request_uri(uri, input.authority(), protocol)
1305}
1306
1307pub(super) fn request_protocol<I>(input: &I) -> Protocol
1308where
1309 I: UriInputExt + ProtocolInputExt,
1310{
1311 input
1312 .uri()
1313 .scheme()
1314 .cloned()
1315 .or_else(|| input.uri().authority().map(|_| Protocol::HTTPS))
1319 .or_else(|| input.protocol().cloned())
1320 .unwrap_or(Protocol::HTTP)
1321}
1322
1323pub fn proxy_request_uri(
1329 uri: &Uri,
1330 fallback_authority: Option<HostWithOptPort>,
1331 protocol: Protocol,
1332) -> Result<Uri, BoxError> {
1333 let authority = uri
1334 .authority()
1335 .map(|authority| authority.into_owned().address)
1336 .or(fallback_authority)
1337 .ok_or_else(|| BoxError::from_static_str("request has no resolvable authority"))?
1338 .without_default_port_for(Some(&protocol));
1339
1340 let mut uri = if uri.is_asterisk() {
1341 Uri::from_authority(protocol, authority)
1342 } else {
1343 uri.clone()
1344 .with_authority(Authority::from(authority))
1345 .with_scheme(protocol)
1346 };
1347 uri.ensure_path_or_root();
1348 Ok(uri)
1349}
1350
1351pub(super) fn is_already_routed(input: &impl ExtensionsRef) -> bool {
1352 input.extensions().contains::<ProxyRoute>() || input.extensions().contains::<ProxyRoutes>()
1353}
1354
1355#[cfg(any(
1356 test,
1357 target_vendor = "apple",
1358 target_os = "android",
1359 target_os = "linux",
1360 target_os = "freebsd",
1361 target_os = "netbsd",
1362 target_os = "openbsd",
1363 target_os = "dragonfly"
1364))]
1365pub(super) fn proxy_address(
1366 protocol: Protocol,
1367 host: impl AsRef<str>,
1368 port: u16,
1369) -> Result<ProxyAddress, BoxError> {
1370 let value = host.as_ref().trim();
1371 let host = match Host::try_from(value) {
1372 Ok(host) => host,
1373 Err(error) if value.contains("://") => value
1374 .parse::<Uri>()
1375 .context("parse system proxy host URI")?
1376 .host()
1377 .map(|host| host.into_owned())
1378 .ok_or(error)
1379 .context("parse system proxy host")?,
1380 Err(error) => return Err(error).context("parse system proxy host"),
1381 };
1382 Ok(ProxyAddress {
1383 protocol: Some(protocol),
1384 address: HostWithPort::new(host, port),
1385 credential: None,
1386 })
1387}
1388
1389#[cfg(test)]
1390mod tests {
1391 use std::convert::Infallible;
1392
1393 use parking_lot::Mutex;
1394 use rama_core::{
1395 extensions::{Extension, FromExtensions},
1396 service::service_fn,
1397 };
1398
1399 use super::*;
1400
1401 #[derive(Debug, Clone, Extension)]
1402 struct Marker(&'static str);
1403
1404 #[derive(FromExtensions)]
1405 enum RecordedProxyDecision {
1406 Route(Arc<ProxyRoute>),
1407 Routes(Arc<ProxyRoutes>),
1408 }
1409
1410 #[derive(Debug, Clone)]
1411 struct TestInput {
1412 uri: Uri,
1413 protocol: Option<Protocol>,
1414 authority: Option<crate::address::HostWithOptPort>,
1415 extensions: Extensions,
1416 }
1417
1418 impl TestInput {
1419 fn new(uri: &str) -> Self {
1420 Self {
1421 uri: uri.parse().unwrap(),
1422 protocol: None,
1423 authority: None,
1424 extensions: Extensions::new(),
1425 }
1426 }
1427
1428 fn origin_form(uri: &str, protocol: Protocol, authority: &str) -> Self {
1429 Self {
1430 uri: uri.parse().unwrap(),
1431 protocol: Some(protocol),
1432 authority: Some(authority.parse().unwrap()),
1433 extensions: Extensions::new(),
1434 }
1435 }
1436
1437 fn authority_form(authority: &str) -> Self {
1438 Self {
1439 uri: Uri::parse_authority_form(authority).unwrap(),
1440 protocol: None,
1441 authority: None,
1442 extensions: Extensions::new(),
1443 }
1444 }
1445 }
1446
1447 impl UriInputExt for TestInput {
1448 fn uri(&self) -> &Uri {
1449 &self.uri
1450 }
1451 }
1452
1453 impl AuthorityInputExt for TestInput {
1454 fn authority(&self) -> Option<crate::address::HostWithOptPort> {
1455 self.authority.clone().or_else(|| {
1456 self.uri
1457 .authority()
1458 .map(|authority| authority.into_owned().address)
1459 })
1460 }
1461 }
1462
1463 impl ProtocolInputExt for TestInput {
1464 fn protocol(&self) -> Option<&Protocol> {
1465 self.protocol.as_ref().or_else(|| self.uri.scheme())
1466 }
1467 }
1468
1469 impl ExtensionsRef for TestInput {
1470 fn extensions(&self) -> &Extensions {
1471 &self.extensions
1472 }
1473 }
1474
1475 fn proxy(protocol: Protocol, host: &'static str, port: u16) -> ProxyAddress {
1476 proxy_address(protocol, host, port).unwrap()
1477 }
1478
1479 fn recorder() -> (
1480 impl Service<TestInput, Output = (), Error = Infallible> + Clone,
1481 Arc<Mutex<Vec<Option<ProxyRoutes>>>>,
1482 ) {
1483 let seen = Arc::new(Mutex::new(Vec::new()));
1484 let service = service_fn({
1485 let seen = seen.clone();
1486 move |input: TestInput| {
1487 let routes = match RecordedProxyDecision::from_extensions(&input.extensions) {
1488 Some(RecordedProxyDecision::Route(route)) => {
1489 Some(ProxyRoutes::from(route.as_ref().clone()))
1490 }
1491 Some(RecordedProxyDecision::Routes(routes)) => Some(routes.as_ref().clone()),
1492 None => None,
1493 };
1494 seen.lock().push(routes);
1495 async { Ok::<_, Infallible>(()) }
1496 }
1497 });
1498 (service, seen)
1499 }
1500
1501 #[tokio::test]
1502 async fn fixed_proxies_are_selected_by_destination_scheme() {
1503 let config = SystemProxyConfig::default()
1504 .with_http_proxy(proxy(Protocol::HTTP, "http.proxy", 8080))
1505 .with_https_proxy(proxy(Protocol::HTTP, "https.proxy", 8443));
1506 let (inner, seen) = recorder();
1507 let service = SystemProxyLayer::from_cached(config).into_layer(inner);
1508
1509 service
1510 .serve(TestInput::new("http://example.com/"))
1511 .await
1512 .unwrap();
1513 service
1514 .serve(TestInput::new("https://example.com/"))
1515 .await
1516 .unwrap();
1517 service
1518 .serve(TestInput::new("ws://example.com/"))
1519 .await
1520 .unwrap();
1521 service
1522 .serve(TestInput::new("wss://example.com/"))
1523 .await
1524 .unwrap();
1525
1526 let seen = seen.lock();
1527 assert_eq!(
1528 seen[0].as_ref().unwrap().as_slice()[0]
1529 .proxy_address()
1530 .unwrap()
1531 .address
1532 .host
1533 .to_str(),
1534 "http.proxy"
1535 );
1536 assert_eq!(
1537 seen[1].as_ref().unwrap().as_slice()[0]
1538 .proxy_address()
1539 .unwrap()
1540 .address
1541 .host
1542 .to_str(),
1543 "https.proxy"
1544 );
1545 assert_eq!(
1546 seen[2].as_ref().unwrap().as_slice()[0]
1547 .proxy_address()
1548 .unwrap()
1549 .address
1550 .host
1551 .to_str(),
1552 "http.proxy"
1553 );
1554 assert_eq!(
1555 seen[3].as_ref().unwrap().as_slice()[0]
1556 .proxy_address()
1557 .unwrap()
1558 .address
1559 .host
1560 .to_str(),
1561 "https.proxy"
1562 );
1563 }
1564
1565 #[tokio::test]
1566 async fn fixed_and_bypass_decisions_publish_singular_routes() {
1567 let config = SystemProxyConfig::default().with_http_proxy(proxy(
1568 Protocol::HTTP,
1569 "system.proxy",
1570 8080,
1571 ));
1572 let service = SystemProxyLayer::from_cached(config).into_layer(service_fn(
1573 async |input: TestInput| Ok::<_, Infallible>(input),
1574 ));
1575
1576 let proxied = service
1577 .serve(TestInput::new("http://example.com/"))
1578 .await
1579 .unwrap();
1580 assert_eq!(
1581 proxied
1582 .extensions
1583 .get_ref::<ProxyRoute>()
1584 .and_then(ProxyRoute::proxy_address)
1585 .map(|address| address.address.host.to_string()),
1586 Some("system.proxy".to_owned())
1587 );
1588 assert!(!proxied.extensions.contains::<ProxyRoutes>());
1589
1590 let bypassed = service
1591 .serve(TestInput::new("http://localhost/"))
1592 .await
1593 .unwrap();
1594 assert_eq!(
1595 bypassed.extensions.get_ref::<ProxyRoute>(),
1596 Some(&ProxyRoute::Direct)
1597 );
1598 assert!(!bypassed.extensions.contains::<ProxyRoutes>());
1599 }
1600
1601 #[tokio::test]
1602 async fn system_decisions_override_configured_route_defaults() {
1603 let config = SystemProxyConfig::default().with_http_proxy(proxy(
1604 Protocol::HTTP,
1605 "system.proxy",
1606 8080,
1607 ));
1608 let service = SystemProxyLayer::from_cached(config).into_layer(
1609 crate::client::ProxyRoutesLayer::with_routes(ProxyRoute::Proxy(proxy(
1610 Protocol::HTTP,
1611 "default.proxy",
1612 8080,
1613 )))
1614 .into_layer(service_fn(async |input: TestInput| {
1615 Ok::<_, Infallible>(input)
1616 })),
1617 );
1618
1619 let proxied = service
1620 .serve(TestInput::new("http://example.com/"))
1621 .await
1622 .unwrap();
1623 assert_eq!(
1624 proxied
1625 .extensions
1626 .get_ref::<ProxyRoute>()
1627 .and_then(ProxyRoute::proxy_address)
1628 .map(|address| address.address.host.to_string()),
1629 Some("system.proxy".to_owned())
1630 );
1631
1632 let bypassed = service
1633 .serve(TestInput::new("http://localhost/"))
1634 .await
1635 .unwrap();
1636 assert_eq!(
1637 bypassed.extensions.get_ref::<ProxyRoute>(),
1638 Some(&ProxyRoute::Direct)
1639 );
1640 }
1641
1642 #[tokio::test]
1643 async fn socks_is_the_scheme_independent_fallback() {
1644 let config = SystemProxyConfig::default().with_socks5_proxy(proxy(
1645 Protocol::SOCKS5,
1646 "socks.proxy",
1647 1080,
1648 ));
1649 let (inner, seen) = recorder();
1650 let service = SystemProxyLayer::from_cached(config).into_layer(inner);
1651
1652 service
1653 .serve(TestInput::new("https://example.com/"))
1654 .await
1655 .unwrap();
1656 service
1657 .serve(TestInput::new("ftp://example.com/file"))
1658 .await
1659 .unwrap();
1660
1661 let seen = seen.lock();
1662 for routes in seen.iter() {
1663 let address = routes.as_ref().unwrap().as_slice()[0]
1664 .proxy_address()
1665 .unwrap();
1666 assert_eq!(address.protocol, Some(Protocol::SOCKS5));
1667 }
1668 }
1669
1670 #[tokio::test]
1671 async fn scheme_specific_proxy_does_not_capture_other_protocols() {
1672 let config = SystemProxyConfig::default()
1673 .with_http_proxy(proxy(Protocol::HTTP, "http.proxy", 8080))
1674 .with_bypass(["example.com"]);
1675 let (inner, seen) = recorder();
1676
1677 SystemProxyLayer::from_cached(config)
1678 .into_layer(inner)
1679 .serve(TestInput::new("ftp://example.com/file"))
1680 .await
1681 .unwrap();
1682
1683 assert!(seen.lock()[0].is_none());
1684 }
1685
1686 #[tokio::test]
1687 async fn empty_config_does_not_require_routing_metadata() {
1688 let (inner, seen) = recorder();
1689
1690 SystemProxyLayer::from_cached(SystemProxyConfig::default())
1691 .into_layer(inner)
1692 .serve(TestInput::new("/relative"))
1693 .await
1694 .unwrap();
1695
1696 assert!(seen.lock()[0].is_none());
1697 }
1698
1699 #[tokio::test]
1700 async fn active_fixed_config_passes_input_without_an_authority() {
1701 let config = SystemProxyConfig::default().with_http_proxy(proxy(
1702 Protocol::HTTP,
1703 "system.proxy",
1704 8080,
1705 ));
1706 let (inner, seen) = recorder();
1707
1708 SystemProxyLayer::from_cached(config)
1709 .into_layer(inner)
1710 .serve(TestInput::new("/relative"))
1711 .await
1712 .unwrap();
1713
1714 assert!(seen.lock()[0].is_none());
1715 }
1716
1717 #[tokio::test]
1718 async fn input_without_a_protocol_defaults_to_http() {
1719 let config = SystemProxyConfig::default().with_http_proxy(proxy(
1720 Protocol::HTTP,
1721 "system.proxy",
1722 8080,
1723 ));
1724 let (inner, seen) = recorder();
1725 let mut input = TestInput::new("/relative");
1726 input.authority = Some("example.com".parse().unwrap());
1727
1728 SystemProxyLayer::from_cached(config)
1729 .into_layer(inner)
1730 .serve(input)
1731 .await
1732 .unwrap();
1733
1734 assert!(matches!(
1735 seen.lock()[0].as_ref().unwrap().as_slice(),
1736 [ProxyRoute::Proxy(_)]
1737 ));
1738 }
1739
1740 #[tokio::test]
1741 async fn existing_route_wins_unless_overwrite_is_enabled() {
1742 let config = SystemProxyConfig::default().with_http_proxy(proxy(
1743 Protocol::HTTP,
1744 "system.proxy",
1745 8080,
1746 ));
1747 let (inner, seen) = recorder();
1748 let request = TestInput::new("http://example.com/");
1749 request.extensions.insert(ProxyRoutes::from(proxy(
1750 Protocol::HTTP,
1751 "explicit.proxy",
1752 9000,
1753 )));
1754
1755 SystemProxyLayer::from_cached(config.clone())
1756 .into_layer(inner.clone())
1757 .serve(request.clone())
1758 .await
1759 .unwrap();
1760 SystemProxyLayer::from_cached(config)
1761 .with_overwrite(true)
1762 .into_layer(inner)
1763 .serve(request)
1764 .await
1765 .unwrap();
1766
1767 let seen = seen.lock();
1768 let hosts: Vec<_> = seen
1769 .iter()
1770 .map(|routes| {
1771 routes.as_ref().unwrap().as_slice()[0]
1772 .proxy_address()
1773 .unwrap()
1774 .address
1775 .host
1776 .to_str()
1777 .into_owned()
1778 })
1779 .collect();
1780 assert_eq!(hosts, ["explicit.proxy", "system.proxy"]);
1781 }
1782
1783 #[tokio::test]
1784 async fn overwrite_route_takes_priority_over_an_existing_route() {
1785 let config = SystemProxyConfig::default().with_http_proxy(proxy(
1786 Protocol::HTTP,
1787 "system.proxy",
1788 8080,
1789 ));
1790 let request = TestInput::new("http://example.com/");
1791 request.extensions.insert(ProxyRoute::Direct);
1792 let (inner, seen) = recorder();
1793
1794 SystemProxyLayer::from_cached(config)
1795 .with_overwrite(true)
1796 .into_layer(inner)
1797 .serve(request)
1798 .await
1799 .unwrap();
1800
1801 let seen = seen.lock();
1802 let routes = seen[0].as_ref().unwrap();
1803 assert_eq!(
1804 routes.as_slice()[0]
1805 .proxy_address()
1806 .unwrap()
1807 .address
1808 .host
1809 .to_str(),
1810 "system.proxy"
1811 );
1812 }
1813
1814 #[tokio::test]
1815 async fn pac_receives_full_uri_and_cloned_extensions() {
1816 let pac_uri: Uri = "http://config.example/proxy.pac".parse().unwrap();
1817 let factory_seen = Arc::new(Mutex::new(Vec::new()));
1818 let resolver_seen = Arc::new(Mutex::new(Vec::new()));
1819 let factory = service_fn({
1820 let factory_seen = factory_seen.clone();
1821 let resolver_seen = resolver_seen.clone();
1822 move |uri: Uri| {
1823 factory_seen.lock().push(uri);
1824 let resolver_seen = resolver_seen.clone();
1825 async move {
1826 Ok::<_, Infallible>(service_fn(move |request: SystemProxyPacRequest| {
1827 resolver_seen.lock().push((
1828 request.uri.clone(),
1829 request.extensions().get_ref::<Marker>().cloned(),
1830 ));
1831 async move {
1832 Ok::<_, Infallible>(Some(ProxyRoutes::from(proxy(
1833 Protocol::HTTP,
1834 "pac.proxy",
1835 8080,
1836 ))))
1837 }
1838 }))
1839 }
1840 }
1841 });
1842 let config = SystemProxyConfig::default().with_pac_uri(pac_uri.clone());
1843 let request = TestInput::new("https://example.com/private?q=1");
1844 request.extensions.insert(Marker("kept"));
1845 let inner = service_fn(async |input: TestInput| Ok::<_, Infallible>(input));
1846
1847 let output = SystemProxyLayer::from_cached(config)
1848 .with_pac_service(factory)
1849 .into_layer(inner)
1850 .serve(request)
1851 .await
1852 .unwrap();
1853
1854 assert_eq!(factory_seen.lock().as_slice(), [pac_uri]);
1855 let resolved = resolver_seen.lock();
1856 assert_eq!(resolved[0].0.to_string(), "https://example.com/private?q=1");
1857 assert_eq!(resolved[0].1.as_ref().unwrap().0, "kept");
1858 assert_eq!(
1859 output
1860 .extensions
1861 .get_ref::<ProxyRoutes>()
1862 .unwrap()
1863 .as_slice()[0]
1864 .proxy_address()
1865 .unwrap()
1866 .address
1867 .host
1868 .to_str(),
1869 "pac.proxy"
1870 );
1871 assert!(output.extensions.get_ref::<ProxyRoute>().is_none());
1872 }
1873
1874 #[tokio::test]
1875 async fn pac_factory_errors_fail_the_request_with_context() {
1876 let factory = service_fn(|_uri: Uri| async {
1877 Err::<SystemProxyPacDisabledResolver, _>(std::io::Error::other("PAC fetch failed"))
1878 });
1879 let config = SystemProxyConfig::default()
1880 .with_pac_uri("https://config.example/proxy.pac".parse().unwrap());
1881 let (inner, seen) = recorder();
1882
1883 let error = SystemProxyLayer::from_cached(config)
1884 .with_pac_service(factory)
1885 .into_layer(inner)
1886 .serve(TestInput::new("https://example.com/"))
1887 .await
1888 .unwrap_err();
1889
1890 assert!(error.to_string().contains("create system PAC resolver"));
1891 assert!(seen.lock().is_empty());
1892 }
1893
1894 #[tokio::test]
1895 async fn pac_resolver_errors_fail_the_request_with_context() {
1896 let factory = service_fn(|_uri: Uri| async {
1897 Ok::<_, Infallible>(service_fn(|_request: SystemProxyPacRequest| async {
1898 Err::<Option<ProxyRoutes>, _>(std::io::Error::other("PAC evaluation failed"))
1899 }))
1900 });
1901 let config = SystemProxyConfig::default()
1902 .with_pac_uri("https://config.example/proxy.pac".parse().unwrap());
1903 let (inner, seen) = recorder();
1904
1905 let error = SystemProxyLayer::from_cached(config)
1906 .with_pac_service(factory)
1907 .into_layer(inner)
1908 .serve(TestInput::new("https://example.com/"))
1909 .await
1910 .unwrap_err();
1911
1912 assert!(error.to_string().contains("resolve system PAC routes"));
1913 assert!(seen.lock().is_empty());
1914 }
1915
1916 #[tokio::test]
1917 async fn pac_receives_an_absolute_uri_for_origin_form_input() {
1918 let received = Arc::new(Mutex::new(None));
1919 let factory = service_fn({
1920 let received = received.clone();
1921 move |_uri: Uri| {
1922 let received = received.clone();
1923 async move {
1924 Ok::<_, Infallible>(service_fn(move |request: SystemProxyPacRequest| {
1925 *received.lock() = Some(request.uri);
1926 async { Ok::<_, Infallible>(None) }
1927 }))
1928 }
1929 }
1930 });
1931 let config = SystemProxyConfig::default()
1932 .with_pac_uri("https://config.example/proxy.pac".parse().unwrap());
1933 let (inner, _) = recorder();
1934
1935 SystemProxyLayer::from_cached(config)
1936 .with_pac_service(factory)
1937 .into_layer(inner)
1938 .serve(TestInput::origin_form(
1939 "/private?q=1",
1940 Protocol::HTTPS,
1941 "example.com:8443",
1942 ))
1943 .await
1944 .unwrap();
1945
1946 assert_eq!(
1947 received.lock().as_ref().unwrap().to_string(),
1948 "https://example.com:8443/private?q=1"
1949 );
1950 }
1951
1952 #[tokio::test]
1953 async fn pac_normalizes_default_ports_with_an_unboxed_resolver() {
1954 let factory_calls = Arc::new(std::sync::atomic::AtomicUsize::new(0));
1955 let received = Arc::new(Mutex::new(Vec::new()));
1956 let factory = service_fn({
1957 let factory_calls = factory_calls.clone();
1958 let received = received.clone();
1959 move |_uri: Uri| {
1960 factory_calls.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
1961 let received = received.clone();
1962 async move {
1963 Ok::<_, Infallible>(service_fn(move |request: SystemProxyPacRequest| {
1964 received.lock().push(request.uri);
1965 async { Ok::<_, Infallible>(None) }
1966 }))
1967 }
1968 }
1969 });
1970 let config = SystemProxyConfig::default()
1971 .with_pac_uri("https://config.example/proxy.pac".parse().unwrap());
1972 let (inner, _) = recorder();
1973 let service = SystemProxyLayer::from_cached(config)
1974 .with_pac_service(factory)
1975 .into_layer(inner);
1976
1977 for input in [
1978 TestInput::new("http://example.com:80/path"),
1979 TestInput::new("https://example.com:443/"),
1980 TestInput::new("http://example.com:8080/"),
1981 TestInput::authority_form("example.com:443"),
1982 ] {
1983 service.serve(input).await.unwrap();
1984 }
1985
1986 assert_eq!(factory_calls.load(std::sync::atomic::Ordering::Relaxed), 4);
1987 assert_eq!(
1988 received
1989 .lock()
1990 .iter()
1991 .map(ToString::to_string)
1992 .collect::<Vec<_>>(),
1993 [
1994 "http://example.com/path",
1995 "https://example.com/",
1996 "http://example.com:8080/",
1997 "https://example.com/",
1998 ]
1999 );
2000 }
2001
2002 #[tokio::test]
2003 async fn authority_form_selects_the_https_proxy() {
2004 let config = SystemProxyConfig::default().with_https_proxy(proxy(
2005 Protocol::HTTP,
2006 "https.proxy",
2007 8443,
2008 ));
2009 let (inner, seen) = recorder();
2010
2011 SystemProxyLayer::from_cached(config)
2012 .into_layer(inner)
2013 .serve(TestInput::authority_form("example.com:443"))
2014 .await
2015 .unwrap();
2016
2017 let routes = seen.lock();
2018 assert_eq!(
2019 routes[0].as_ref().unwrap().as_slice()[0]
2020 .proxy_address()
2021 .unwrap()
2022 .address
2023 .host
2024 .to_str(),
2025 "https.proxy"
2026 );
2027 }
2028
2029 #[tokio::test]
2030 async fn pac_without_a_service_leaves_the_request_undecided() {
2031 let config = SystemProxyConfig::default()
2032 .with_pac_uri("http://config.example/proxy.pac".parse().unwrap());
2033 let (inner, seen) = recorder();
2034
2035 SystemProxyLayer::from_cached(config)
2036 .into_layer(inner)
2037 .serve(TestInput::new("/relative"))
2038 .await
2039 .unwrap();
2040
2041 assert!(seen.lock()[0].is_none());
2042 }
2043
2044 #[tokio::test]
2045 async fn pac_without_a_service_uses_a_fixed_proxy_fallback() {
2046 let config = SystemProxyConfig::default()
2047 .with_http_proxy(proxy(Protocol::HTTP, "fixed.proxy", 8080))
2048 .with_pac_uri("http://config.example/proxy.pac".parse().unwrap());
2049 let (inner, seen) = recorder();
2050
2051 SystemProxyLayer::from_cached(config)
2052 .into_layer(inner)
2053 .serve(TestInput::new("http://example.com/"))
2054 .await
2055 .unwrap();
2056
2057 assert_eq!(
2058 seen.lock()[0].as_ref().unwrap().as_slice()[0]
2059 .proxy_address()
2060 .unwrap()
2061 .address
2062 .host
2063 .to_str(),
2064 "fixed.proxy"
2065 );
2066 }
2067
2068 #[tokio::test]
2069 async fn active_pac_requires_a_resolvable_authority() {
2070 let factory = service_fn(|_uri: Uri| async {
2071 Ok::<_, Infallible>(service_fn(|_request| async { Ok::<_, Infallible>(None) }))
2072 });
2073 let config = SystemProxyConfig::default()
2074 .with_pac_uri("http://config.example/proxy.pac".parse().unwrap());
2075 let (inner, _) = recorder();
2076
2077 SystemProxyLayer::from_cached(config)
2078 .with_pac_service(factory)
2079 .into_layer(inner)
2080 .serve(TestInput::new("/relative"))
2081 .await
2082 .unwrap_err();
2083 }
2084
2085 #[tokio::test]
2086 async fn singular_route_also_prevents_pac_lookup() {
2087 let factory_calls = Arc::new(std::sync::atomic::AtomicUsize::new(0));
2088 let factory = service_fn({
2089 let factory_calls = factory_calls.clone();
2090 move |_uri: Uri| {
2091 factory_calls.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
2092 async move {
2093 Ok::<_, Infallible>(service_fn(|_request| async { Ok::<_, Infallible>(None) }))
2094 }
2095 }
2096 });
2097 let config = SystemProxyConfig::default()
2098 .with_pac_uri("https://config.example/proxy.pac".parse().unwrap());
2099 let request = TestInput::new("https://example.com/");
2100 request.extensions.insert(ProxyRoute::Direct);
2101 let (inner, _) = recorder();
2102
2103 SystemProxyLayer::from_cached(config)
2104 .with_pac_service(factory)
2105 .into_layer(inner)
2106 .serve(request)
2107 .await
2108 .unwrap();
2109
2110 assert_eq!(factory_calls.load(std::sync::atomic::Ordering::Relaxed), 0);
2111 }
2112
2113 #[tokio::test]
2114 async fn an_existing_route_prevents_system_config_refresh() {
2115 let calls = Arc::new(std::sync::atomic::AtomicUsize::new(0));
2116 let reader = BoxService::new(service_fn({
2117 let calls = calls.clone();
2118 move |()| {
2119 calls.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
2120 async {
2121 Ok::<_, BoxError>(SystemProxyConfig::default().with_http_proxy(proxy(
2122 Protocol::HTTP,
2123 "system.proxy",
2124 8080,
2125 )))
2126 }
2127 }
2128 }));
2129 let layer = SystemProxyLayer::try_from_system_with_reader(Duration::ZERO, reader)
2130 .await
2131 .unwrap();
2132 let request = TestInput::new("http://example.com/");
2133 request.extensions.insert(ProxyRoute::Direct);
2134 let (inner, _) = recorder();
2135
2136 layer.into_layer(inner).serve(request).await.unwrap();
2137 tokio::time::sleep(Duration::from_millis(50)).await;
2138
2139 assert_eq!(calls.load(std::sync::atomic::Ordering::Relaxed), 1);
2140 }
2141
2142 #[tokio::test]
2143 async fn bypass_and_inverted_bypass_select_direct_routes() {
2144 let base = SystemProxyConfig::default()
2145 .with_http_proxy(proxy(Protocol::HTTP, "system.proxy", 8080))
2146 .with_bypass([".example.com", "default-port.test:80"]);
2147 let (inner, seen) = recorder();
2148 let service = SystemProxyLayer::from_cached(base.clone()).into_layer(inner.clone());
2149 service
2150 .serve(TestInput::new("http://api.example.com/"))
2151 .await
2152 .unwrap();
2153 service
2154 .serve(TestInput::new("http://elsewhere.test/"))
2155 .await
2156 .unwrap();
2157 service
2158 .serve(TestInput::new("http://default-port.test/"))
2159 .await
2160 .unwrap();
2161
2162 let inverted =
2163 SystemProxyLayer::from_cached(base.with_reversed_bypass(true)).into_layer(inner);
2164 inverted
2165 .serve(TestInput::new("http://api.example.com/"))
2166 .await
2167 .unwrap();
2168 inverted
2169 .serve(TestInput::new("http://elsewhere.test/"))
2170 .await
2171 .unwrap();
2172
2173 let seen = seen.lock();
2174 assert!(matches!(
2175 seen[0].as_ref().unwrap().as_slice(),
2176 [ProxyRoute::Direct]
2177 ));
2178 assert!(matches!(
2179 seen[1].as_ref().unwrap().as_slice(),
2180 [ProxyRoute::Proxy(_)]
2181 ));
2182 assert!(matches!(
2183 seen[2].as_ref().unwrap().as_slice(),
2184 [ProxyRoute::Direct]
2185 ));
2186 assert!(matches!(
2187 seen[3].as_ref().unwrap().as_slice(),
2188 [ProxyRoute::Proxy(_)]
2189 ));
2190 assert!(matches!(
2191 seen[4].as_ref().unwrap().as_slice(),
2192 [ProxyRoute::Direct]
2193 ));
2194 }
2195
2196 #[tokio::test]
2197 async fn simple_hostname_bypass_is_opt_in() {
2198 let config = SystemProxyConfig::default()
2199 .with_http_proxy(proxy(Protocol::HTTP, "system.proxy", 8080))
2200 .with_exclude_simple_hostnames(true);
2201 let (inner, seen) = recorder();
2202 let service = SystemProxyLayer::from_cached(config).into_layer(inner);
2203
2204 service
2205 .serve(TestInput::new("http://printer/"))
2206 .await
2207 .unwrap();
2208 service
2209 .serve(TestInput::new("http://printer.example/"))
2210 .await
2211 .unwrap();
2212 service
2213 .serve(TestInput::new("http://[2001:db8::1]/"))
2214 .await
2215 .unwrap();
2216
2217 let seen = seen.lock();
2218 assert!(matches!(
2219 seen[0].as_ref().unwrap().as_slice(),
2220 [ProxyRoute::Direct]
2221 ));
2222 assert!(matches!(
2223 seen[1].as_ref().unwrap().as_slice(),
2224 [ProxyRoute::Proxy(_)]
2225 ));
2226 assert!(matches!(
2227 seen[2].as_ref().unwrap().as_slice(),
2228 [ProxyRoute::Proxy(_)]
2229 ));
2230 }
2231
2232 #[tokio::test]
2233 async fn inverted_simple_hostname_bypass_uses_only_simple_names() {
2234 let config = SystemProxyConfig::default()
2235 .with_http_proxy(proxy(Protocol::HTTP, "system.proxy", 8080))
2236 .with_exclude_simple_hostnames(true)
2237 .with_reversed_bypass(true);
2238 let (inner, seen) = recorder();
2239 let service = SystemProxyLayer::from_cached(config).into_layer(inner);
2240
2241 service
2242 .serve(TestInput::new("http://printer/"))
2243 .await
2244 .unwrap();
2245 service
2246 .serve(TestInput::new("http://printer.example/"))
2247 .await
2248 .unwrap();
2249
2250 let seen = seen.lock();
2251 assert!(matches!(
2252 seen[0].as_ref().unwrap().as_slice(),
2253 [ProxyRoute::Proxy(_)]
2254 ));
2255 assert!(matches!(
2256 seen[1].as_ref().unwrap().as_slice(),
2257 [ProxyRoute::Direct]
2258 ));
2259 }
2260
2261 #[tokio::test]
2262 async fn fixed_system_proxies_implicitly_bypass_loopback() {
2263 let config = SystemProxyConfig::default()
2264 .with_http_proxy(proxy(Protocol::HTTP, "system.proxy", 8080))
2265 .with_bypass(["localhost", "127.0.0.0/8", "::1", "remote.example"])
2266 .with_reversed_bypass(true);
2267 let (inner, seen) = recorder();
2268 let service = SystemProxyLayer::from_cached(config).into_layer(inner);
2269
2270 for uri in [
2271 "http://localhost/",
2272 "http://service.localhost/",
2273 "http://127.42.0.1/",
2274 "http://[::1]/",
2275 "http://remote.example/",
2276 ] {
2277 service.serve(TestInput::new(uri)).await.unwrap();
2278 }
2279
2280 let seen = seen.lock();
2281 for routes in &seen[..4] {
2282 assert!(matches!(
2283 routes.as_ref().unwrap().as_slice(),
2284 [ProxyRoute::Direct]
2285 ));
2286 }
2287 assert!(matches!(
2288 seen[4].as_ref().unwrap().as_slice(),
2289 [ProxyRoute::Proxy(_)]
2290 ));
2291 }
2292
2293 #[tokio::test]
2294 async fn pac_is_not_consulted_for_loopback() {
2295 let calls = Arc::new(std::sync::atomic::AtomicUsize::new(0));
2296 let factory = service_fn({
2297 let calls = calls.clone();
2298 move |_uri: Uri| {
2299 calls.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
2300 async {
2301 Ok::<_, Infallible>(service_fn(|_request| async {
2302 Ok::<_, Infallible>(Some(ProxyRoutes::from(ProxyRoute::Direct)))
2303 }))
2304 }
2305 }
2306 });
2307 let config = SystemProxyConfig::default()
2308 .with_pac_uri("https://config.example/proxy.pac".parse().unwrap());
2309 let (inner, seen) = recorder();
2310 let service = SystemProxyLayer::from_cached(config)
2311 .with_pac_service(factory)
2312 .into_layer(inner);
2313
2314 for uri in [
2315 "http://localhost/",
2316 "http://service.localhost/",
2317 "http://127.42.0.1/",
2318 "http://[::1]/",
2319 ] {
2320 service.serve(TestInput::new(uri)).await.unwrap();
2321 }
2322
2323 assert_eq!(calls.load(std::sync::atomic::Ordering::Relaxed), 0);
2324 assert!(
2325 seen.lock()
2326 .iter()
2327 .all(|routes| matches!(routes.as_ref().unwrap().as_slice(), [ProxyRoute::Direct]))
2328 );
2329 }
2330
2331 #[test]
2332 fn platform_bypass_precedence_controls_pac_decision() {
2333 let pac_uri: Uri = "https://config.example/proxy.pac".parse().unwrap();
2334 let uri: Uri = "https://bypass.example/".parse().unwrap();
2335 let mut config = SystemProxyConfig::default()
2336 .with_pac_uri(pac_uri.clone())
2337 .with_bypass(["bypass.example"]);
2338
2339 assert!(matches!(
2340 config.decision(&uri),
2341 SystemProxyDecision::Pac(uri) if uri == pac_uri
2342 ));
2343
2344 config.bypass_before_pac = true;
2345 assert!(matches!(
2346 config.decision(&uri),
2347 SystemProxyDecision::Route(ProxyRoute::Direct)
2348 ));
2349 }
2350
2351 #[test]
2352 fn config_accessors_and_public_pac_request_fields_round_trip() {
2353 let http = proxy(Protocol::HTTP, "http.proxy", 8080);
2354 let https = proxy(Protocol::HTTP, "https.proxy", 8443);
2355 let socks = proxy(Protocol::SOCKS5, "socks.proxy", 1080);
2356 let pac: Uri = "https://config.example/proxy.pac".parse().unwrap();
2357 let config = SystemProxyConfig::default()
2358 .with_http_proxy(http.clone())
2359 .with_https_proxy(https.clone())
2360 .with_socks5_proxy(socks.clone())
2361 .with_pac_uri(pac.clone())
2362 .with_bypass(["localhost"])
2363 .with_exclude_simple_hostnames(true)
2364 .with_reversed_bypass(true)
2365 .with_auto_detect(true);
2366
2367 assert!(!config.is_empty());
2368 assert_eq!(config.http_proxy(), Some(&http));
2369 assert_eq!(config.https_proxy(), Some(&https));
2370 assert_eq!(config.socks5_proxy(), Some(&socks));
2371 assert_eq!(config.pac_uri(), Some(&pac));
2372 assert_eq!(config.bypass().collect::<Vec<_>>(), ["localhost"]);
2373 assert!(config.exclude_simple_hostnames());
2374 assert!(config.reversed_bypass());
2375 assert!(config.auto_detect());
2376
2377 let extensions = Extensions::new();
2378 extensions.insert(Marker("parts"));
2379 let request =
2380 SystemProxyPacRequest::new(extensions, "http://example.com/path".parse().unwrap())
2381 .unwrap();
2382 assert_eq!(
2383 UriInputExt::uri(&request).to_string(),
2384 "http://example.com/path"
2385 );
2386 assert_eq!(
2387 ExtensionsRef::extensions(&request)
2388 .get_ref::<Marker>()
2389 .unwrap()
2390 .0,
2391 "parts"
2392 );
2393 assert_eq!(request.extensions.get_ref::<Marker>().unwrap().0, "parts");
2394 assert_eq!(request.uri.to_string(), "http://example.com/path");
2395 }
2396
2397 #[test]
2398 fn platform_proxy_host_accepts_a_scheme_prefix() {
2399 let proxy = proxy_address(Protocol::HTTP, "http://proxy.corp", 8080).unwrap();
2400 assert_eq!(proxy.to_string(), "http://proxy.corp:8080");
2401 }
2402
2403 #[cfg(any(target_os = "macos", target_os = "windows", target_os = "linux"))]
2404 #[tokio::test]
2405 async fn native_system_proxy_snapshot_can_be_read() {
2406 SystemProxyConfig::try_from_system().await.unwrap();
2407 }
2408
2409 #[test]
2410 fn every_proxy_source_independently_makes_config_non_empty() {
2411 assert!(SystemProxyConfig::default().is_empty());
2412 for config in [
2413 SystemProxyConfig::default().with_http_proxy(proxy(Protocol::HTTP, "http.proxy", 8080)),
2414 SystemProxyConfig::default().with_https_proxy(proxy(
2415 Protocol::HTTP,
2416 "https.proxy",
2417 8443,
2418 )),
2419 SystemProxyConfig::default().with_socks5_proxy(proxy(
2420 Protocol::SOCKS5,
2421 "socks.proxy",
2422 1080,
2423 )),
2424 SystemProxyConfig::default()
2425 .with_pac_uri("https://config.example/proxy.pac".parse().unwrap()),
2426 SystemProxyConfig::default().with_auto_detect(true),
2427 ] {
2428 assert!(!config.is_empty());
2429 }
2430 }
2431
2432 #[test]
2433 fn pac_request_rejects_non_absolute_or_hostless_uri() {
2434 SystemProxyPacRequest::new(Extensions::new(), "/path".parse().unwrap()).unwrap_err();
2435 SystemProxyPacRequest::new(Extensions::new(), "data:text/plain,x".parse().unwrap())
2436 .unwrap_err();
2437 }
2438
2439 #[test]
2440 fn bypass_patterns_cover_domains_ports_ip_ranges_and_local_names() {
2441 for (pattern, scheme, host, port, expected) in [
2442 ("*", None, "anything.example", None, true),
2443 ("<local>", None, "printer", None, true),
2444 ("<local>", None, "printer.example", None, false),
2445 ("<local>", None, "2001:db8::1", None, false),
2446 ("192.168.*", None, "192.168.10.20", None, true),
2447 ("*corp*", None, "api.corp.example", None, true),
2448 ("*.example.com", None, "api.example.com", None, true),
2449 ("*.example.com", None, "example.com", None, true),
2450 (".example.com", None, "api.example.com.", None, true),
2451 (".example.com", None, "notexample.com", None, false),
2452 (
2453 "api.example.com:8443",
2454 None,
2455 "api.example.com",
2456 Some(8443),
2457 true,
2458 ),
2459 (
2460 "api.example.com:8443",
2461 None,
2462 "api.example.com",
2463 Some(443),
2464 false,
2465 ),
2466 ("10.0.0.0/8", None, "10.2.3.4", None, true),
2467 ("10.0.0.0/8", None, "11.2.3.4", None, false),
2468 ("[::1]", None, "::1", None, true),
2469 ("::1", None, "::1", None, true),
2470 ("[::1]:8443", None, "::1", Some(8443), true),
2471 ("[::1]:8443", None, "::1", Some(443), false),
2472 ("2001:db8::/32", None, "2001:db8::1", None, true),
2473 (
2474 "https://secure.example:443",
2475 Some(Protocol::HTTPS),
2476 "secure.example",
2477 Some(443),
2478 true,
2479 ),
2480 (
2481 "https://secure.example:443",
2482 Some(Protocol::HTTP),
2483 "secure.example",
2484 Some(443),
2485 false,
2486 ),
2487 ] {
2488 let host = Host::try_from(host).unwrap();
2489 let host_text = host.to_string();
2490 assert_eq!(
2491 BypassRule::compile(pattern).unwrap().matches(
2492 scheme.as_ref(),
2493 (&host).into(),
2494 port,
2495 ),
2496 expected,
2497 "{pattern} {host_text:?} {port:?}"
2498 );
2499 }
2500 }
2501
2502 #[test]
2503 fn invalid_bypass_patterns_are_discarded() {
2504 let config =
2505 SystemProxyConfig::default().with_bypass(["example.com", ".not a valid domain", ""]);
2506
2507 assert_eq!(config.bypass().collect::<Vec<_>>(), ["example.com"]);
2508 }
2509
2510 #[test]
2511 fn invalid_bypass_policy_can_reject_an_update_atomically() {
2512 let mut config = SystemProxyConfig::default().with_bypass(["existing.example"]);
2513
2514 let error = config
2515 .try_set_bypass(
2516 ["replacement.example", ".not a valid domain"],
2517 SystemProxyInvalidBypassRulePolicy::Reject,
2518 )
2519 .unwrap_err();
2520
2521 assert!(
2522 error
2523 .to_string()
2524 .contains("parse system proxy bypass pattern")
2525 );
2526 assert_eq!(config.bypass().collect::<Vec<_>>(), ["existing.example"]);
2527
2528 config
2529 .try_set_bypass(
2530 ["replacement.example", ".not a valid domain"],
2531 SystemProxyInvalidBypassRulePolicy::Ignore,
2532 )
2533 .unwrap();
2534 assert_eq!(config.bypass().collect::<Vec<_>>(), ["replacement.example"]);
2535 }
2536
2537 #[tokio::test]
2538 async fn lazy_layer_construction_does_not_read_until_warmed() {
2539 let calls = Arc::new(std::sync::atomic::AtomicUsize::new(0));
2540 let reader = BoxService::new(service_fn({
2541 let calls = calls.clone();
2542 move |()| {
2543 calls.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
2544 async {
2545 Ok::<_, BoxError>(SystemProxyConfig::default().with_http_proxy(proxy(
2546 Protocol::HTTP,
2547 "lazy.proxy",
2548 8080,
2549 )))
2550 }
2551 }
2552 }));
2553 let layer = SystemProxyLayer::new_with_reader(Duration::from_mins(1), reader);
2554
2555 assert!(layer.cached_config().is_none());
2556 assert_eq!(calls.load(std::sync::atomic::Ordering::Relaxed), 0);
2557
2558 layer.warm_up().await.unwrap();
2559 layer.warm_up().await.unwrap();
2560
2561 let config = layer.cached_config().unwrap();
2562 assert_eq!(
2563 config.http_proxy().unwrap().address.host.to_str(),
2564 "lazy.proxy"
2565 );
2566 assert_eq!(calls.load(std::sync::atomic::Ordering::Relaxed), 1);
2567 }
2568
2569 #[tokio::test]
2570 async fn change_trigger_refreshes_a_fresh_snapshot() {
2571 let calls = Arc::new(std::sync::atomic::AtomicUsize::new(0));
2572 let changed = Arc::new(std::sync::atomic::AtomicBool::new(false));
2573 let reader = BoxService::new(service_fn({
2574 let calls = calls.clone();
2575 move |()| {
2576 let call = calls.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
2577 async move {
2578 let host = if call == 0 { "old.proxy" } else { "new.proxy" };
2579 Ok::<_, BoxError>(SystemProxyConfig::default().with_http_proxy(proxy(
2580 Protocol::HTTP,
2581 host,
2582 8080,
2583 )))
2584 }
2585 }
2586 }));
2587 let trigger = service_fn({
2588 let changed = changed.clone();
2589 move |()| {
2590 let changed = changed.swap(false, std::sync::atomic::Ordering::AcqRel);
2591 async move { Ok::<_, std::convert::Infallible>(changed) }
2592 }
2593 });
2594 let layer = SystemProxyLayer::new_with_reader(Duration::from_mins(1), reader)
2595 .with_config_change_trigger(trigger);
2596
2597 let old = layer.config().await.unwrap();
2598 assert_eq!(old.http_proxy().unwrap().address.host.to_str(), "old.proxy");
2599 changed.store(true, std::sync::atomic::Ordering::Release);
2600 let new = layer.config().await.unwrap();
2601 let still_new = layer.config().await.unwrap();
2602
2603 assert_eq!(new.http_proxy().unwrap().address.host.to_str(), "new.proxy");
2604 assert!(Arc::ptr_eq(&new, &still_new));
2605 assert_eq!(calls.load(std::sync::atomic::Ordering::Relaxed), 2);
2606 }
2607
2608 #[tokio::test]
2609 async fn change_during_refresh_remains_pending_for_the_next_request() {
2610 let reads = Arc::new(std::sync::atomic::AtomicUsize::new(0));
2611 let changed = Arc::new(std::sync::atomic::AtomicBool::new(false));
2612 let refresh_started = Arc::new(tokio::sync::Notify::new());
2613 let release_refresh = Arc::new(tokio::sync::Notify::new());
2614 let reader = BoxService::new(service_fn({
2615 let reads = reads.clone();
2616 let refresh_started = refresh_started.clone();
2617 let release_refresh = release_refresh.clone();
2618 move |()| {
2619 let call = reads.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
2620 let refresh_started = refresh_started.clone();
2621 let release_refresh = release_refresh.clone();
2622 async move {
2623 if call == 0 {
2624 refresh_started.notify_one();
2625 release_refresh.notified().await;
2626 }
2627 let host = if call == 0 {
2628 "first-refresh.proxy"
2629 } else {
2630 "second-refresh.proxy"
2631 };
2632 Ok::<_, BoxError>(SystemProxyConfig::default().with_http_proxy(proxy(
2633 Protocol::HTTP,
2634 host,
2635 8080,
2636 )))
2637 }
2638 }
2639 }));
2640 let trigger = service_fn({
2641 let changed = changed.clone();
2642 move |()| {
2643 let changed = changed.swap(false, std::sync::atomic::Ordering::AcqRel);
2644 async move { Ok::<_, Infallible>(changed) }
2645 }
2646 });
2647 let cached = SystemProxyConfig::default().with_http_proxy(proxy(
2648 Protocol::HTTP,
2649 "cached.proxy",
2650 8080,
2651 ));
2652 let layer =
2653 SystemProxyLayer::from_cached_with_reader(cached, Duration::from_mins(1), reader)
2654 .with_config_change_trigger(trigger);
2655
2656 changed.store(true, std::sync::atomic::Ordering::Release);
2657 let first_layer = layer.clone();
2658 let first = tokio::spawn(async move { first_layer.config().await });
2659 refresh_started.notified().await;
2660
2661 changed.store(true, std::sync::atomic::Ordering::Release);
2662 let stale = layer.config().await.unwrap();
2663 assert_eq!(stale.http_proxy().unwrap().address.host, "cached.proxy");
2664
2665 release_refresh.notify_one();
2666 let first = first.await.unwrap().unwrap();
2667 assert_eq!(
2668 first.http_proxy().unwrap().address.host,
2669 "first-refresh.proxy"
2670 );
2671 let second = layer.config().await.unwrap();
2672 assert_eq!(
2673 second.http_proxy().unwrap().address.host,
2674 "second-refresh.proxy"
2675 );
2676 assert_eq!(reads.load(std::sync::atomic::Ordering::Relaxed), 2);
2677 }
2678
2679 #[tokio::test]
2680 async fn cancelled_triggered_refresh_does_not_consume_the_request() {
2681 let reads = Arc::new(std::sync::atomic::AtomicUsize::new(0));
2682 let changed = Arc::new(std::sync::atomic::AtomicBool::new(true));
2683 let refresh_started = Arc::new(tokio::sync::Notify::new());
2684 let reader = BoxService::new(service_fn({
2685 let reads = reads.clone();
2686 let refresh_started = refresh_started.clone();
2687 move |()| {
2688 let call = reads.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
2689 let refresh_started = refresh_started.clone();
2690 async move {
2691 if call == 0 {
2692 refresh_started.notify_one();
2693 std::future::pending::<()>().await;
2694 }
2695 Ok::<_, BoxError>(SystemProxyConfig::default().with_http_proxy(proxy(
2696 Protocol::HTTP,
2697 "refreshed.proxy",
2698 8080,
2699 )))
2700 }
2701 }
2702 }));
2703 let trigger = service_fn({
2704 let changed = changed.clone();
2705 move |()| {
2706 let changed = changed.swap(false, std::sync::atomic::Ordering::AcqRel);
2707 async move { Ok::<_, Infallible>(changed) }
2708 }
2709 });
2710 let cached = SystemProxyConfig::default().with_http_proxy(proxy(
2711 Protocol::HTTP,
2712 "cached.proxy",
2713 8080,
2714 ));
2715 let layer =
2716 SystemProxyLayer::from_cached_with_reader(cached, Duration::from_mins(1), reader)
2717 .with_config_change_trigger(trigger);
2718
2719 let refresh_layer = layer.clone();
2720 let refresh = tokio::spawn(async move { refresh_layer.config().await });
2721 refresh_started.notified().await;
2722 assert_eq!(
2723 layer
2724 .config()
2725 .await
2726 .unwrap()
2727 .http_proxy()
2728 .unwrap()
2729 .address
2730 .host,
2731 "cached.proxy"
2732 );
2733 refresh.abort();
2734 refresh.await.unwrap_err();
2735
2736 let refreshed = layer.config().await.unwrap();
2737 assert_eq!(
2738 refreshed.http_proxy().unwrap().address.host,
2739 "refreshed.proxy"
2740 );
2741 assert_eq!(reads.load(std::sync::atomic::Ordering::Relaxed), 2);
2742 }
2743
2744 #[tokio::test]
2745 async fn failed_triggered_refresh_is_acknowledged_until_the_ttl() {
2746 let reads = Arc::new(std::sync::atomic::AtomicUsize::new(0));
2747 let changed = Arc::new(std::sync::atomic::AtomicBool::new(true));
2748 let reader = BoxService::new(service_fn({
2749 let reads = reads.clone();
2750 move |()| {
2751 reads.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
2752 async {
2753 Err::<SystemProxyConfig, BoxError>(std::io::Error::other("offline").into())
2754 }
2755 }
2756 }));
2757 let trigger = service_fn({
2758 let changed = changed.clone();
2759 move |()| {
2760 let changed = changed.swap(false, std::sync::atomic::Ordering::AcqRel);
2761 async move { Ok::<_, Infallible>(changed) }
2762 }
2763 });
2764 let layer = SystemProxyLayer::from_cached_with_reader(
2765 SystemProxyConfig::default(),
2766 Duration::from_mins(1),
2767 reader,
2768 )
2769 .with_config_change_trigger(trigger);
2770
2771 layer.config().await.unwrap();
2772 layer.config().await.unwrap();
2773 assert_eq!(reads.load(std::sync::atomic::Ordering::Relaxed), 1);
2774 }
2775
2776 #[tokio::test]
2777 async fn disabled_change_trigger_retains_ttl_refresh() {
2778 let reads = Arc::new(std::sync::atomic::AtomicUsize::new(0));
2779 let triggers = Arc::new(std::sync::atomic::AtomicUsize::new(0));
2780 let reader = BoxService::new(service_fn({
2781 let reads = reads.clone();
2782 move |()| {
2783 reads.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
2784 async { Ok::<_, BoxError>(SystemProxyConfig::default()) }
2785 }
2786 }));
2787 let trigger = service_fn({
2788 let triggers = triggers.clone();
2789 move |()| {
2790 triggers.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
2791 async { Ok::<_, std::convert::Infallible>(true) }
2792 }
2793 });
2794 let layer = SystemProxyLayer::new_with_reader(Duration::ZERO, reader)
2795 .with_config_change_trigger(trigger)
2796 .without_config_change_trigger();
2797
2798 layer.config().await.unwrap();
2799 layer.config().await.unwrap();
2800
2801 assert_eq!(reads.load(std::sync::atomic::Ordering::Relaxed), 2);
2802 assert_eq!(triggers.load(std::sync::atomic::Ordering::Relaxed), 0);
2803 }
2804
2805 #[tokio::test]
2806 async fn disabled_refresh_makes_a_cached_snapshot_immutable() {
2807 let reads = Arc::new(std::sync::atomic::AtomicUsize::new(0));
2808 let reader = BoxService::new(service_fn({
2809 let reads = reads.clone();
2810 move |()| {
2811 reads.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
2812 async { Ok::<_, BoxError>(SystemProxyConfig::default()) }
2813 }
2814 }));
2815 let cached = SystemProxyConfig::default().with_http_proxy(proxy(
2816 Protocol::HTTP,
2817 "cached.proxy",
2818 8080,
2819 ));
2820 let layer = SystemProxyLayer::from_cached_with_reader(cached, Duration::ZERO, reader)
2821 .with_config_refresh(false);
2822
2823 for _ in 0..2 {
2824 assert_eq!(
2825 layer
2826 .config()
2827 .await
2828 .unwrap()
2829 .http_proxy()
2830 .unwrap()
2831 .address
2832 .host
2833 .to_str(),
2834 "cached.proxy"
2835 );
2836 }
2837 assert_eq!(reads.load(std::sync::atomic::Ordering::Relaxed), 0);
2838 }
2839
2840 #[tokio::test]
2841 async fn change_trigger_errors_fall_back_to_the_ttl() {
2842 let reads = Arc::new(std::sync::atomic::AtomicUsize::new(0));
2843 let errors = Arc::new(std::sync::atomic::AtomicUsize::new(0));
2844 let reader = BoxService::new(service_fn({
2845 let reads = reads.clone();
2846 move |()| {
2847 reads.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
2848 async { Ok::<_, BoxError>(SystemProxyConfig::default()) }
2849 }
2850 }));
2851 let trigger = service_fn(|()| async {
2852 Err::<bool, _>(std::io::Error::other("configuration watcher failed"))
2853 });
2854 let layer = SystemProxyLayer::new_with_reader(Duration::from_mins(1), reader)
2855 .with_config_change_trigger(trigger)
2856 .with_config_change_trigger_error_sink({
2857 let errors = errors.clone();
2858 move |_error: BoxError| {
2859 errors.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
2860 }
2861 });
2862
2863 layer.config().await.unwrap();
2864 layer.config().await.unwrap();
2865
2866 assert_eq!(reads.load(std::sync::atomic::Ordering::Relaxed), 1);
2867 assert_eq!(errors.load(std::sync::atomic::Ordering::Relaxed), 2);
2868 }
2869
2870 #[tokio::test]
2871 async fn first_request_lazily_loads_and_applies_system_proxy() {
2872 let calls = Arc::new(std::sync::atomic::AtomicUsize::new(0));
2873 let reader = BoxService::new(service_fn({
2874 let calls = calls.clone();
2875 move |()| {
2876 calls.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
2877 async {
2878 Ok::<_, BoxError>(SystemProxyConfig::default().with_http_proxy(proxy(
2879 Protocol::HTTP,
2880 "lazy.proxy",
2881 8080,
2882 )))
2883 }
2884 }
2885 }));
2886 let layer = SystemProxyLayer::new_with_reader(Duration::from_mins(1), reader);
2887 let (inner, seen) = recorder();
2888 let service = layer.into_layer(inner);
2889
2890 assert_eq!(calls.load(std::sync::atomic::Ordering::Relaxed), 0);
2891 service
2892 .serve(TestInput::new("http://example.com/"))
2893 .await
2894 .unwrap();
2895
2896 assert_eq!(calls.load(std::sync::atomic::Ordering::Relaxed), 1);
2897 assert_eq!(
2898 seen.lock()[0].as_ref().unwrap().as_slice()[0]
2899 .proxy_address()
2900 .unwrap()
2901 .address
2902 .host
2903 .to_str(),
2904 "lazy.proxy"
2905 );
2906 }
2907
2908 #[tokio::test]
2909 async fn concurrent_cold_loads_share_one_async_read() {
2910 let calls = Arc::new(std::sync::atomic::AtomicUsize::new(0));
2911 let reader = BoxService::new(service_fn({
2912 let calls = calls.clone();
2913 move |()| {
2914 calls.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
2915 async {
2916 tokio::time::sleep(Duration::from_millis(20)).await;
2917 Ok::<_, BoxError>(SystemProxyConfig::default())
2918 }
2919 }
2920 }));
2921 let layer = SystemProxyLayer::new_with_reader(Duration::from_mins(1), reader);
2922
2923 let (first, second, third) = tokio::join!(layer.config(), layer.config(), layer.config());
2924
2925 first.unwrap();
2926 second.unwrap();
2927 third.unwrap();
2928 assert_eq!(calls.load(std::sync::atomic::Ordering::Relaxed), 1);
2929 }
2930
2931 #[tokio::test]
2932 async fn failed_cold_load_remains_retryable() {
2933 let calls = Arc::new(std::sync::atomic::AtomicUsize::new(0));
2934 let reader = BoxService::new(service_fn({
2935 let calls = calls.clone();
2936 move |()| {
2937 let call = calls.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
2938 async move {
2939 if call == 0 {
2940 Err(std::io::Error::other("temporary platform read failure").into())
2941 } else {
2942 Ok(SystemProxyConfig::default())
2943 }
2944 }
2945 }
2946 }));
2947 let layer = SystemProxyLayer::new_with_reader(Duration::from_mins(1), reader);
2948
2949 layer.config().await.unwrap_err();
2950 assert!(layer.cached_config().is_none());
2951 layer.config().await.unwrap();
2952 assert_eq!(calls.load(std::sync::atomic::Ordering::Relaxed), 2);
2953 }
2954
2955 #[tokio::test]
2956 async fn handled_cold_load_error_is_sunk_and_cached_for_the_ttl() {
2957 let reads = Arc::new(std::sync::atomic::AtomicUsize::new(0));
2958 let errors = Arc::new(std::sync::atomic::AtomicUsize::new(0));
2959 let reader = BoxService::new(service_fn({
2960 let reads = reads.clone();
2961 move |()| {
2962 reads.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
2963 async {
2964 Err::<SystemProxyConfig, BoxError>(
2965 std::io::Error::other("platform read failed").into(),
2966 )
2967 }
2968 }
2969 }));
2970 let layer = SystemProxyLayer::new_with_reader(Duration::from_mins(1), reader)
2971 .with_load_error_sink({
2972 let errors = errors.clone();
2973 move |error: BoxError| {
2974 assert_eq!(error.to_string(), "platform read failed");
2975 errors.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
2976 }
2977 });
2978
2979 assert!(layer.config().await.unwrap().is_empty());
2980 assert!(layer.config().await.unwrap().is_empty());
2981 assert_eq!(reads.load(std::sync::atomic::Ordering::Relaxed), 1);
2982 assert_eq!(errors.load(std::sync::atomic::Ordering::Relaxed), 1);
2983 }
2984
2985 #[tokio::test]
2986 async fn native_change_trigger_is_initialized_only_when_polled() {
2987 let reader = || {
2988 BoxService::new(service_fn(|()| async {
2989 Ok::<_, BoxError>(SystemProxyConfig::default())
2990 }))
2991 };
2992 let trigger_initialized = |layer: &SystemProxyLayer| match &layer.refresh.trigger {
2993 Some(SystemProxyConfigChangeTrigger::Platform(trigger)) => trigger.is_initialized(),
2994 _ => panic!("expected the default platform change trigger"),
2995 };
2996
2997 let disabled = SystemProxyLayer::from_cached_with_reader(
2998 SystemProxyConfig::default(),
2999 Duration::ZERO,
3000 reader(),
3001 )
3002 .with_config_refresh(false);
3003 assert!(!trigger_initialized(&disabled));
3004 disabled.config().await.unwrap();
3005 assert!(!trigger_initialized(&disabled));
3006
3007 let enabled = SystemProxyLayer::new_with_reader(Duration::from_mins(1), reader());
3008 assert!(!trigger_initialized(&enabled));
3009 enabled.config().await.unwrap();
3010 assert!(trigger_initialized(&enabled));
3011 }
3012
3013 #[tokio::test]
3014 async fn concurrent_cold_failure_is_shared_without_a_retry_convoy() {
3015 let calls = Arc::new(std::sync::atomic::AtomicUsize::new(0));
3016 let read_started = Arc::new(tokio::sync::Notify::new());
3017 let release_read = Arc::new(tokio::sync::Notify::new());
3018 let reader = BoxService::new(service_fn({
3019 let calls = calls.clone();
3020 let read_started = read_started.clone();
3021 let release_read = release_read.clone();
3022 move |()| {
3023 let call = calls.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
3024 let read_started = read_started.clone();
3025 let release_read = release_read.clone();
3026 async move {
3027 if call == 0 {
3028 read_started.notify_one();
3029 release_read.notified().await;
3030 Err(std::io::Error::other("temporary platform read failure").into())
3031 } else {
3032 Ok(SystemProxyConfig::default())
3033 }
3034 }
3035 }
3036 }));
3037 let layer = SystemProxyLayer::new_with_reader(Duration::from_mins(1), reader);
3038
3039 let release = async {
3040 read_started.notified().await;
3041 release_read.notify_one();
3042 };
3043 let (first, second, third, ()) = tokio::time::timeout(Duration::from_secs(5), async {
3044 tokio::join!(layer.config(), layer.config(), layer.config(), release,)
3045 })
3046 .await
3047 .expect("concurrent cold configuration load should complete");
3048
3049 first.unwrap_err();
3050 second.unwrap_err();
3051 third.unwrap_err();
3052 assert_eq!(calls.load(std::sync::atomic::Ordering::Relaxed), 1);
3053
3054 layer.config().await.unwrap();
3055 assert_eq!(calls.load(std::sync::atomic::Ordering::Relaxed), 2);
3056 }
3057
3058 #[tokio::test]
3059 async fn cancelled_cold_load_releases_single_flight_lock() {
3060 let calls = Arc::new(std::sync::atomic::AtomicUsize::new(0));
3061 let reader = BoxService::new(service_fn({
3062 let calls = calls.clone();
3063 move |()| {
3064 let call = calls.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
3065 async move {
3066 if call == 0 {
3067 std::future::pending::<()>().await;
3068 }
3069 Ok::<_, BoxError>(SystemProxyConfig::default())
3070 }
3071 }
3072 }));
3073 let layer = SystemProxyLayer::new_with_reader(Duration::from_mins(1), reader);
3074
3075 tokio::time::timeout(Duration::from_millis(10), layer.config())
3076 .await
3077 .unwrap_err();
3078 assert!(layer.cached_config().is_none());
3079 layer.config().await.unwrap();
3080 assert_eq!(calls.load(std::sync::atomic::Ordering::Relaxed), 2);
3081 }
3082
3083 #[tokio::test]
3084 async fn cancelled_stale_refresh_releases_single_flight_lock() {
3085 let calls = Arc::new(std::sync::atomic::AtomicUsize::new(0));
3086 let reader = BoxService::new(service_fn({
3087 let calls = calls.clone();
3088 move |()| {
3089 let call = calls.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
3090 async move {
3091 if call == 1 {
3092 std::future::pending::<()>().await;
3093 }
3094 let host = if call == 0 { "old.proxy" } else { "new.proxy" };
3095 Ok::<_, BoxError>(SystemProxyConfig::default().with_http_proxy(proxy(
3096 Protocol::HTTP,
3097 host,
3098 8080,
3099 )))
3100 }
3101 }
3102 }));
3103 let layer = SystemProxyLayer::try_from_system_with_reader(Duration::ZERO, reader)
3104 .await
3105 .unwrap();
3106
3107 tokio::time::timeout(Duration::from_millis(10), layer.config())
3108 .await
3109 .unwrap_err();
3110 let fresh = layer.config().await.unwrap();
3111
3112 assert_eq!(
3113 fresh.http_proxy().unwrap().address.host.to_str(),
3114 "new.proxy"
3115 );
3116 assert_eq!(calls.load(std::sync::atomic::Ordering::Relaxed), 3);
3117 }
3118
3119 #[tokio::test]
3120 async fn stale_refresh_is_single_flight_and_concurrent_calls_use_stale_config() {
3121 let calls = Arc::new(std::sync::atomic::AtomicUsize::new(0));
3122 let refresh_started = Arc::new(tokio::sync::Notify::new());
3123 let allow_refresh = Arc::new(tokio::sync::Notify::new());
3124 let reader = BoxService::new(service_fn({
3125 let calls = calls.clone();
3126 let refresh_started = refresh_started.clone();
3127 let allow_refresh = allow_refresh.clone();
3128 move |()| {
3129 let call = calls.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
3130 let refresh_started = refresh_started.clone();
3131 let allow_refresh = allow_refresh.clone();
3132 async move {
3133 if call > 0 {
3134 refresh_started.notify_one();
3135 allow_refresh.notified().await;
3136 }
3137 let host = if call == 0 { "old.proxy" } else { "new.proxy" };
3138 Ok::<_, BoxError>(SystemProxyConfig::default().with_http_proxy(proxy(
3139 Protocol::HTTP,
3140 host,
3141 8080,
3142 )))
3143 }
3144 }
3145 }));
3146 let layer = SystemProxyLayer::try_from_system_with_reader(Duration::ZERO, reader)
3147 .await
3148 .unwrap();
3149
3150 let refresh_layer = layer.clone();
3151 let refresh = tokio::spawn(async move { refresh_layer.config().await });
3152 tokio::time::timeout(Duration::from_secs(5), refresh_started.notified())
3153 .await
3154 .expect("stale configuration refresh should start");
3155
3156 let stale = tokio::time::timeout(Duration::from_millis(100), layer.config())
3157 .await
3158 .unwrap()
3159 .unwrap();
3160 assert_eq!(
3161 stale.http_proxy().unwrap().address.host.to_str(),
3162 "old.proxy"
3163 );
3164 assert_eq!(calls.load(std::sync::atomic::Ordering::Relaxed), 2);
3165
3166 allow_refresh.notify_one();
3167 let fresh = tokio::time::timeout(Duration::from_secs(5), refresh)
3168 .await
3169 .expect("stale configuration refresh should complete")
3170 .unwrap()
3171 .unwrap();
3172 assert_eq!(
3173 fresh.http_proxy().unwrap().address.host.to_str(),
3174 "new.proxy"
3175 );
3176 }
3177
3178 #[tokio::test]
3179 async fn failed_system_config_refresh_retains_snapshot_and_remains_retryable() {
3180 let calls = Arc::new(std::sync::atomic::AtomicUsize::new(0));
3181 let reader = BoxService::new(service_fn({
3182 let calls = calls.clone();
3183 move |()| {
3184 let call = calls.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
3185 async move {
3186 match call {
3187 0 => Ok(SystemProxyConfig::default().with_http_proxy(proxy(
3188 Protocol::HTTP,
3189 "old.proxy",
3190 8080,
3191 ))),
3192 1 => Err(std::io::Error::other("temporary platform read failure").into()),
3193 _ => Ok(SystemProxyConfig::default().with_http_proxy(proxy(
3194 Protocol::HTTP,
3195 "new.proxy",
3196 8080,
3197 ))),
3198 }
3199 }
3200 }
3201 }));
3202 let layer = SystemProxyLayer::try_from_system_with_reader(Duration::ZERO, reader)
3203 .await
3204 .unwrap();
3205
3206 let stale = layer.config().await.unwrap();
3207 assert_eq!(
3208 stale.http_proxy().unwrap().address.host.to_str(),
3209 "old.proxy"
3210 );
3211 let fresh = layer.config().await.unwrap();
3212 assert_eq!(
3213 fresh.http_proxy().unwrap().address.host.to_str(),
3214 "new.proxy"
3215 );
3216 assert_eq!(calls.load(std::sync::atomic::Ordering::Relaxed), 3);
3217 }
3218
3219 #[tokio::test]
3220 async fn a_pac_uri_discovered_by_refresh_is_used_by_the_existing_service() {
3221 let calls = Arc::new(std::sync::atomic::AtomicUsize::new(0));
3222 let reader = BoxService::new(service_fn({
3223 let calls = calls.clone();
3224 move |()| {
3225 let call = calls.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
3226 async move {
3227 if call == 0 {
3228 Ok::<_, BoxError>(SystemProxyConfig::default().with_http_proxy(proxy(
3229 Protocol::HTTP,
3230 "fixed.proxy",
3231 8080,
3232 )))
3233 } else {
3234 Ok(SystemProxyConfig::default()
3235 .with_pac_uri("https://config.example/proxy.pac".parse().unwrap()))
3236 }
3237 }
3238 }
3239 }));
3240 let layer = SystemProxyLayer::try_from_system_with_reader(Duration::from_mins(1), reader)
3241 .await
3242 .unwrap();
3243 let factory_calls = Arc::new(std::sync::atomic::AtomicUsize::new(0));
3244 let factory = service_fn({
3245 let factory_calls = factory_calls.clone();
3246 move |_uri: Uri| {
3247 factory_calls.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
3248 async move {
3249 Ok::<_, Infallible>(service_fn(|_request| async move {
3250 Ok::<_, Infallible>(Some(ProxyRoutes::from(proxy(
3251 Protocol::HTTP,
3252 "pac.proxy",
3253 8080,
3254 ))))
3255 }))
3256 }
3257 }
3258 });
3259 let (inner, seen) = recorder();
3260 let service = layer.clone().with_pac_service(factory).into_layer(inner);
3261
3262 service
3263 .serve(TestInput::new("http://example.com/first"))
3264 .await
3265 .unwrap();
3266 layer.config.refresh_after_nanos.store(0, Ordering::Release);
3267 service
3268 .serve(TestInput::new("http://example.com/second"))
3269 .await
3270 .unwrap();
3271
3272 let seen = seen.lock();
3273 let hosts = seen
3274 .iter()
3275 .map(|routes| {
3276 routes.as_ref().unwrap().as_slice()[0]
3277 .proxy_address()
3278 .unwrap()
3279 .address
3280 .host
3281 .to_str()
3282 .into_owned()
3283 })
3284 .collect::<Vec<_>>();
3285 assert_eq!(hosts, ["fixed.proxy", "pac.proxy"]);
3286 assert_eq!(factory_calls.load(std::sync::atomic::Ordering::Relaxed), 1);
3287 }
3288
3289 #[tokio::test]
3290 async fn system_config_cache_honors_a_custom_ttl() {
3291 let calls = Arc::new(std::sync::atomic::AtomicUsize::new(0));
3292 let reader = BoxService::new(service_fn({
3293 let calls = calls.clone();
3294 move |()| {
3295 calls.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
3296 async { Ok::<_, BoxError>(SystemProxyConfig::default()) }
3297 }
3298 }));
3299 let layer = SystemProxyLayer::try_from_system_with_reader(Duration::from_mins(1), reader)
3300 .await
3301 .unwrap();
3302
3303 for _ in 0..10 {
3304 drop(layer.config().await.unwrap());
3305 }
3306 assert_eq!(calls.load(std::sync::atomic::Ordering::Relaxed), 1);
3307 }
3308}