Skip to content

Commit 27db8b0

Browse files
authored
feat(client): add set_scheme, set_host, and set_port for Destination
Closes #1564
1 parent e4ebf44 commit 27db8b0

File tree

2 files changed

+262
-1
lines changed

2 files changed

+262
-1
lines changed

src/client/connect.rs

+256-1
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,11 @@
66
//! establishes connections over TCP.
77
//! - The [`Connect`](Connect) trait and related types to build custom connectors.
88
use std::error::Error as StdError;
9+
use std::mem;
910

11+
use bytes::{BufMut, BytesMut};
1012
use futures::Future;
11-
use http::Uri;
13+
use http::{uri, Uri};
1214
use tokio_io::{AsyncRead, AsyncWrite};
1315

1416
#[cfg(feature = "runtime")] pub use self::http::HttpConnector;
@@ -79,6 +81,144 @@ impl Destination {
7981
self.uri.port()
8082
}
8183

84+
/// Update the scheme of this destination.
85+
///
86+
/// # Example
87+
///
88+
/// ```rust
89+
/// # use hyper::client::connect::Destination;
90+
/// # fn with_dst(mut dst: Destination) {
91+
/// // let mut dst = some_destination...
92+
/// // Change from "http://"...
93+
/// assert_eq!(dst.scheme(), "http");
94+
///
95+
/// // to "ws://"...
96+
/// dst.set_scheme("ws");
97+
/// assert_eq!(dst.scheme(), "ws");
98+
/// # }
99+
/// ```
100+
///
101+
/// # Error
102+
///
103+
/// Returns an error if the string is not a valid scheme.
104+
pub fn set_scheme(&mut self, scheme: &str) -> ::Result<()> {
105+
let scheme = scheme.parse().map_err(::error::Parse::from)?;
106+
self.update_uri(move |parts| {
107+
parts.scheme = Some(scheme);
108+
})
109+
}
110+
111+
/// Update the host of this destination.
112+
///
113+
/// # Example
114+
///
115+
/// ```rust
116+
/// # use hyper::client::connect::Destination;
117+
/// # fn with_dst(mut dst: Destination) {
118+
/// // let mut dst = some_destination...
119+
/// // Change from "hyper.rs"...
120+
/// assert_eq!(dst.host(), "hyper.rs");
121+
///
122+
/// // to "some.proxy"...
123+
/// dst.set_host("some.proxy");
124+
/// assert_eq!(dst.host(), "some.proxy");
125+
/// # }
126+
/// ```
127+
///
128+
/// # Error
129+
///
130+
/// Returns an error if the string is not a valid hostname.
131+
pub fn set_host(&mut self, host: &str) -> ::Result<()> {
132+
if host.contains(&['@',':'][..]) {
133+
return Err(::error::Parse::Uri.into());
134+
}
135+
let auth = if let Some(port) = self.port() {
136+
format!("{}:{}", host, port).parse().map_err(::error::Parse::from)?
137+
} else {
138+
host.parse().map_err(::error::Parse::from)?
139+
};
140+
self.update_uri(move |parts| {
141+
parts.authority = Some(auth);
142+
})
143+
}
144+
145+
/// Update the port of this destination.
146+
///
147+
/// # Example
148+
///
149+
/// ```rust
150+
/// # use hyper::client::connect::Destination;
151+
/// # fn with_dst(mut dst: Destination) {
152+
/// // let mut dst = some_destination...
153+
/// // Change from "None"...
154+
/// assert_eq!(dst.port(), None);
155+
///
156+
/// // to "4321"...
157+
/// dst.set_port(4321);
158+
/// assert_eq!(dst.port(), Some(4321));
159+
///
160+
/// // Or remove the port...
161+
/// dst.set_port(None);
162+
/// assert_eq!(dst.port(), None);
163+
/// # }
164+
/// ```
165+
pub fn set_port<P>(&mut self, port: P)
166+
where
167+
P: Into<Option<u16>>,
168+
{
169+
self.set_port_opt(port.into());
170+
}
171+
172+
fn set_port_opt(&mut self, port: Option<u16>) {
173+
use std::fmt::Write;
174+
175+
let auth = if let Some(port) = port {
176+
let host = self.host();
177+
// Need space to copy the hostname, plus ':',
178+
// plus max 5 port digits...
179+
let cap = host.len() + 1 + 5;
180+
let mut buf = BytesMut::with_capacity(cap);
181+
buf.put_slice(host.as_bytes());
182+
buf.put_u8(b':');
183+
write!(buf, "{}", port)
184+
.expect("should have space for 5 digits");
185+
186+
uri::Authority::from_shared(buf.freeze())
187+
.expect("valid host + :port should be valid authority")
188+
} else {
189+
self.host().parse()
190+
.expect("valid host without port should be valid authority")
191+
};
192+
193+
self.update_uri(move |parts| {
194+
parts.authority = Some(auth);
195+
})
196+
.expect("valid uri should be valid with port");
197+
}
198+
199+
fn update_uri<F>(&mut self, f: F) -> ::Result<()>
200+
where
201+
F: FnOnce(&mut uri::Parts)
202+
{
203+
// Need to store a default Uri while we modify the current one...
204+
let old_uri = mem::replace(&mut self.uri, Uri::default());
205+
// However, mutate a clone, so we can revert if there's an error...
206+
let mut parts: uri::Parts = old_uri.clone().into();
207+
208+
f(&mut parts);
209+
210+
match Uri::from_parts(parts) {
211+
Ok(uri) => {
212+
self.uri = uri;
213+
Ok(())
214+
},
215+
Err(err) => {
216+
self.uri = old_uri;
217+
Err(::error::Parse::from(err).into())
218+
},
219+
}
220+
}
221+
82222
/*
83223
/// Returns whether this connection must negotiate HTTP/2 via ALPN.
84224
pub fn must_h2(&self) -> bool {
@@ -121,6 +261,121 @@ impl Connected {
121261
*/
122262
}
123263

264+
#[cfg(test)]
265+
mod tests {
266+
use super::Destination;
267+
268+
#[test]
269+
fn test_destination_set_scheme() {
270+
let mut dst = Destination {
271+
uri: "http://hyper.rs".parse().expect("initial parse"),
272+
};
273+
274+
assert_eq!(dst.scheme(), "http");
275+
assert_eq!(dst.host(), "hyper.rs");
276+
277+
dst.set_scheme("https").expect("set https");
278+
assert_eq!(dst.scheme(), "https");
279+
assert_eq!(dst.host(), "hyper.rs");
280+
281+
dst.set_scheme("<im not a scheme//?>").unwrap_err();
282+
assert_eq!(dst.scheme(), "https", "error doesn't modify dst");
283+
assert_eq!(dst.host(), "hyper.rs", "error doesn't modify dst");
284+
}
285+
286+
#[test]
287+
fn test_destination_set_host() {
288+
let mut dst = Destination {
289+
uri: "http://hyper.rs".parse().expect("initial parse"),
290+
};
291+
292+
assert_eq!(dst.scheme(), "http");
293+
assert_eq!(dst.host(), "hyper.rs");
294+
assert_eq!(dst.port(), None);
295+
296+
dst.set_host("seanmonstar.com").expect("set https");
297+
assert_eq!(dst.scheme(), "http");
298+
assert_eq!(dst.host(), "seanmonstar.com");
299+
assert_eq!(dst.port(), None);
300+
301+
dst.set_host("/im-not a host! >:)").unwrap_err();
302+
assert_eq!(dst.scheme(), "http", "error doesn't modify dst");
303+
assert_eq!(dst.host(), "seanmonstar.com", "error doesn't modify dst");
304+
assert_eq!(dst.port(), None, "error doesn't modify dst");
305+
306+
// Also test that an exist port is set correctly.
307+
let mut dst = Destination {
308+
uri: "http://hyper.rs:8080".parse().expect("initial parse 2"),
309+
};
310+
311+
assert_eq!(dst.scheme(), "http");
312+
assert_eq!(dst.host(), "hyper.rs");
313+
assert_eq!(dst.port(), Some(8080));
314+
315+
dst.set_host("seanmonstar.com").expect("set host");
316+
assert_eq!(dst.scheme(), "http");
317+
assert_eq!(dst.host(), "seanmonstar.com");
318+
assert_eq!(dst.port(), Some(8080));
319+
320+
dst.set_host("/im-not a host! >:)").unwrap_err();
321+
assert_eq!(dst.scheme(), "http", "error doesn't modify dst");
322+
assert_eq!(dst.host(), "seanmonstar.com", "error doesn't modify dst");
323+
assert_eq!(dst.port(), Some(8080), "error doesn't modify dst");
324+
325+
// Check port isn't snuck into `set_host`.
326+
dst.set_host("seanmonstar.com:3030").expect_err("set_host sneaky port");
327+
assert_eq!(dst.scheme(), "http", "error doesn't modify dst");
328+
assert_eq!(dst.host(), "seanmonstar.com", "error doesn't modify dst");
329+
assert_eq!(dst.port(), Some(8080), "error doesn't modify dst");
330+
331+
// Check userinfo isn't snuck into `set_host`.
332+
dst.set_host("sean@nope").expect_err("set_host sneaky userinfo");
333+
assert_eq!(dst.scheme(), "http", "error doesn't modify dst");
334+
assert_eq!(dst.host(), "seanmonstar.com", "error doesn't modify dst");
335+
assert_eq!(dst.port(), Some(8080), "error doesn't modify dst");
336+
}
337+
338+
#[test]
339+
fn test_destination_set_port() {
340+
let mut dst = Destination {
341+
uri: "http://hyper.rs".parse().expect("initial parse"),
342+
};
343+
344+
assert_eq!(dst.scheme(), "http");
345+
assert_eq!(dst.host(), "hyper.rs");
346+
assert_eq!(dst.port(), None);
347+
348+
dst.set_port(None);
349+
assert_eq!(dst.scheme(), "http");
350+
assert_eq!(dst.host(), "hyper.rs");
351+
assert_eq!(dst.port(), None);
352+
353+
dst.set_port(8080);
354+
assert_eq!(dst.scheme(), "http");
355+
assert_eq!(dst.host(), "hyper.rs");
356+
assert_eq!(dst.port(), Some(8080));
357+
358+
// Also test that an exist port is set correctly.
359+
let mut dst = Destination {
360+
uri: "http://hyper.rs:8080".parse().expect("initial parse 2"),
361+
};
362+
363+
assert_eq!(dst.scheme(), "http");
364+
assert_eq!(dst.host(), "hyper.rs");
365+
assert_eq!(dst.port(), Some(8080));
366+
367+
dst.set_port(3030);
368+
assert_eq!(dst.scheme(), "http");
369+
assert_eq!(dst.host(), "hyper.rs");
370+
assert_eq!(dst.port(), Some(3030));
371+
372+
dst.set_port(None);
373+
assert_eq!(dst.scheme(), "http");
374+
assert_eq!(dst.host(), "hyper.rs");
375+
assert_eq!(dst.port(), None);
376+
}
377+
}
378+
124379
#[cfg(feature = "runtime")]
125380
mod http {
126381
use super::*;

src/error.rs

+6
Original file line numberDiff line numberDiff line change
@@ -350,6 +350,12 @@ impl From<http::uri::InvalidUri> for Parse {
350350
}
351351
}
352352

353+
impl From<http::uri::InvalidUriParts> for Parse {
354+
fn from(_: http::uri::InvalidUriParts) -> Parse {
355+
Parse::Uri
356+
}
357+
}
358+
353359
#[doc(hidden)]
354360
trait AssertSendSync: Send + Sync + 'static {}
355361
#[doc(hidden)]

0 commit comments

Comments
 (0)