Skip to content

Commit 7fdcc93

Browse files
committed
feat: add windows_named_pipe() option to client builder
1 parent acd1b05 commit 7fdcc93

File tree

2 files changed

+125
-17
lines changed

2 files changed

+125
-17
lines changed

src/async_impl/client.rs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ use crate::async_impl::h3_client::H3Client;
2020
use crate::config::{RequestConfig, RequestTimeout};
2121
#[cfg(unix)]
2222
use crate::connect::uds::UnixSocketProvider;
23+
#[cfg(target_os = "windows")]
24+
use crate::connect::windows_named_pipe::WindowsNamedPipeProvider;
2325
use crate::connect::{
2426
sealed::{Conn, Unnameable},
2527
BoxedConnectorLayer, BoxedConnectorService, Connector, ConnectorBuilder,
@@ -259,6 +261,8 @@ struct Config {
259261

260262
#[cfg(unix)]
261263
unix_socket: Option<Arc<std::path::Path>>,
264+
#[cfg(target_os = "windows")]
265+
windows_named_pipe: Option<Arc<str>>,
262266
}
263267

264268
impl Default for ClientBuilder {
@@ -384,6 +388,8 @@ impl ClientBuilder {
384388
dns_resolver: None,
385389
#[cfg(unix)]
386390
unix_socket: None,
391+
#[cfg(target_os = "windows")]
392+
windows_named_pipe: None,
387393
},
388394
}
389395
}
@@ -918,6 +924,8 @@ impl ClientBuilder {
918924
// ways TLS can be configured...
919925
#[cfg(unix)]
920926
connector_builder.set_unix_socket(config.unix_socket);
927+
#[cfg(target_os = "windows")]
928+
connector_builder.set_windows_named_pipe(config.windows_named_pipe.clone());
921929

922930
let mut builder =
923931
hyper_util::client::legacy::Client::builder(hyper_util::rt::TokioExecutor::new());
@@ -1757,6 +1765,26 @@ impl ClientBuilder {
17571765
self
17581766
}
17591767

1768+
/// Set that all connections will use this Windows named pipe.
1769+
///
1770+
/// If a request URI uses the `https` scheme, TLS will still be used over
1771+
/// the Windows named pipe.
1772+
///
1773+
/// # Note
1774+
///
1775+
/// This option is not compatible with any of the TCP or Proxy options.
1776+
/// Setting this will ignore all those options previously set.
1777+
///
1778+
/// Likewise, DNS resolution will not be done on the domain name.
1779+
#[cfg(target_os = "windows")]
1780+
pub fn windows_named_pipe(mut self, pipe: impl WindowsNamedPipeProvider) -> ClientBuilder {
1781+
self.config.windows_named_pipe = Some(
1782+
pipe.reqwest_windows_named_pipe_path(crate::connect::windows_named_pipe::Internal)
1783+
.into(),
1784+
);
1785+
self
1786+
}
1787+
17601788
// TLS options
17611789

17621790
/// Add a custom root certificate.

src/connect.rs

Lines changed: 97 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,8 @@ pub(crate) struct ConnectorBuilder {
8282
resolver: Option<DynResolver>,
8383
#[cfg(unix)]
8484
unix_socket: Option<Arc<std::path::Path>>,
85+
#[cfg(target_os = "windows")]
86+
windows_named_pipe: Option<Arc<std::ffi::OsStr>>,
8587
}
8688

8789
impl ConnectorBuilder {
@@ -103,13 +105,20 @@ where {
103105
resolver: self.resolver.unwrap_or_else(DynResolver::gai),
104106
#[cfg(unix)]
105107
unix_socket: self.unix_socket,
108+
#[cfg(target_os = "windows")]
109+
windows_named_pipe: self.windows_named_pipe,
106110
};
107111

108112
#[cfg(unix)]
109113
if base_service.unix_socket.is_some() && !base_service.proxies.is_empty() {
110114
base_service.proxies = Default::default();
111115
log::trace!("unix_socket() set, proxies are ignored");
112116
}
117+
#[cfg(target_os = "windows")]
118+
if base_service.windows_named_pipe.is_some() && !base_service.proxies.is_empty() {
119+
base_service.proxies = Default::default();
120+
log::trace!("windows_named_pipe() set, proxies are ignored");
121+
}
113122

114123
if layers.is_empty() {
115124
// we have no user-provided layers, only use concrete types
@@ -208,6 +217,8 @@ where {
208217
resolver: None,
209218
#[cfg(unix)]
210219
unix_socket: None,
220+
#[cfg(target_os = "windows")]
221+
windows_named_pipe: None,
211222
}
212223
}
213224

@@ -319,6 +330,8 @@ where {
319330
resolver: None,
320331
#[cfg(unix)]
321332
unix_socket: None,
333+
#[cfg(target_os = "windows")]
334+
windows_named_pipe: None,
322335
}
323336
}
324337

@@ -392,6 +405,8 @@ where {
392405
resolver: None,
393406
#[cfg(unix)]
394407
unix_socket: None,
408+
#[cfg(target_os = "windows")]
409+
windows_named_pipe: None,
395410
}
396411
}
397412

@@ -457,6 +472,11 @@ where {
457472
pub(crate) fn set_unix_socket(&mut self, path: Option<Arc<std::path::Path>>) {
458473
self.unix_socket = path;
459474
}
475+
476+
#[cfg(target_os = "windows")]
477+
pub(crate) fn set_windows_named_pipe(&mut self, pipe: Option<Arc<std::ffi::OsStr>>) {
478+
self.windows_named_pipe = pipe;
479+
}
460480
}
461481

462482
#[allow(missing_debug_implementations)]
@@ -481,6 +501,8 @@ pub(crate) struct ConnectorService {
481501
/// If set, this always takes priority over TCP.
482502
#[cfg(unix)]
483503
unix_socket: Option<Arc<std::path::Path>>,
504+
#[cfg(target_os = "windows")]
505+
windows_named_pipe: Option<Arc<std::ffi::OsStr>>,
484506
}
485507

486508
#[derive(Clone)]
@@ -673,21 +695,37 @@ impl ConnectorService {
673695
}
674696
}
675697

676-
/// Connect over Unix Domain Socket (or Windows?).
677-
#[cfg(unix)]
698+
/// Connect over a local transport: Unix Domain Socket (on Unix) or Windows Named Pipe (on Windows).
699+
#[cfg(any(unix, target_os = "windows"))]
678700
async fn connect_local_transport(self, dst: Uri) -> Result<Conn, BoxError> {
679-
let path = self
680-
.unix_socket
681-
.as_ref()
682-
.expect("connect local must have socket path")
683-
.clone();
684-
let svc = tower::service_fn(move |_| {
685-
let fut = tokio::net::UnixStream::connect(path.clone());
686-
async move {
687-
let io = fut.await?;
688-
Ok::<_, std::io::Error>(TokioIo::new(io))
689-
}
690-
});
701+
#[cfg(unix)]
702+
let svc = {
703+
let path = self
704+
.unix_socket
705+
.as_ref()
706+
.expect("connect local must have socket path")
707+
.clone();
708+
tower::service_fn(move |_| {
709+
let fut = tokio::net::UnixStream::connect(path.clone());
710+
async move {
711+
let io = fut.await?;
712+
Ok::<_, std::io::Error>(TokioIo::new(io))
713+
}
714+
})
715+
};
716+
#[cfg(target_os = "windows")]
717+
let svc = {
718+
use tokio::net::windows::named_pipe::ClientOptions;
719+
let pipe = self
720+
.windows_named_pipe
721+
.as_ref()
722+
.expect("connect local must have pipe path")
723+
.clone();
724+
tower::service_fn(move |_| {
725+
let pipe = pipe.clone();
726+
async move { ClientOptions::new().open(pipe).map(TokioIo::new) }
727+
})
728+
};
691729
let is_proxy = false;
692730
match self.inner {
693731
#[cfg(not(feature = "__tls"))]
@@ -852,6 +890,15 @@ impl ConnectorService {
852890

853891
self.connect_with_maybe_proxy(proxy_dst, true).await
854892
}
893+
894+
#[cfg(any(unix, target_os = "windows"))]
895+
fn should_use_local_transport(&self) -> bool {
896+
#[cfg(unix)]
897+
return self.unix_socket.is_some();
898+
899+
#[cfg(target_os = "windows")]
900+
return self.windows_named_pipe.is_some();
901+
}
855902
}
856903

857904
async fn with_timeout<T, F>(f: F, timeout: Option<Duration>) -> Result<T, BoxError>
@@ -882,9 +929,9 @@ impl Service<Uri> for ConnectorService {
882929
log::debug!("starting new connection: {dst:?}");
883930
let timeout = self.simple_timeout;
884931

885-
// Local transports (UDS) skip proxies
886-
#[cfg(unix)]
887-
if self.unix_socket.is_some() {
932+
// Local transports (UDS, Windows Named Pipes) skip proxies
933+
#[cfg(any(unix, target_os = "windows"))]
934+
if self.should_use_local_transport() {
888935
return Box::pin(with_timeout(
889936
self.clone().connect_local_transport(dst),
890937
timeout,
@@ -1249,6 +1296,39 @@ pub(crate) mod uds {
12491296
];
12501297
}
12511298

1299+
// Sealed trait for Windows Named Pipe support
1300+
#[cfg(target_os = "windows")]
1301+
pub(crate) mod windows_named_pipe {
1302+
use std::ffi::OsStr;
1303+
/// A provider for Windows Named Pipe paths.
1304+
///
1305+
/// This trait is sealed. This allows us to expand support in the future
1306+
/// by controlling who can implement the trait.
1307+
#[cfg(target_os = "windows")]
1308+
pub trait WindowsNamedPipeProvider {
1309+
#[doc(hidden)]
1310+
fn reqwest_windows_named_pipe_path(&self, _: Internal) -> &OsStr;
1311+
}
1312+
1313+
#[allow(missing_debug_implementations)]
1314+
pub struct Internal;
1315+
1316+
macro_rules! as_os_str {
1317+
($($t:ty,)+) => {
1318+
$(
1319+
impl WindowsNamedPipeProvider for $t {
1320+
#[doc(hidden)]
1321+
fn reqwest_windows_named_pipe_path(&self, _: Internal) -> &OsStr {
1322+
self.as_ref()
1323+
}
1324+
}
1325+
)+
1326+
}
1327+
}
1328+
1329+
as_os_str![String, &'_ str,];
1330+
}
1331+
12521332
pub(crate) type Connecting = Pin<Box<dyn Future<Output = Result<Conn, BoxError>> + Send>>;
12531333

12541334
#[cfg(feature = "default-tls")]

0 commit comments

Comments
 (0)