|
6 | 6 | //! establishes connections over TCP.
|
7 | 7 | //! - The [`Connect`](Connect) trait and related types to build custom connectors.
|
8 | 8 | use std::error::Error as StdError;
|
| 9 | +use std::mem; |
9 | 10 |
|
| 11 | +use bytes::{BufMut, BytesMut}; |
10 | 12 | use futures::Future;
|
11 |
| -use http::Uri; |
| 13 | +use http::{uri, Uri}; |
12 | 14 | use tokio_io::{AsyncRead, AsyncWrite};
|
13 | 15 |
|
14 | 16 | #[cfg(feature = "runtime")] pub use self::http::HttpConnector;
|
@@ -79,6 +81,144 @@ impl Destination {
|
79 | 81 | self.uri.port()
|
80 | 82 | }
|
81 | 83 |
|
| 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 | + |
82 | 222 | /*
|
83 | 223 | /// Returns whether this connection must negotiate HTTP/2 via ALPN.
|
84 | 224 | pub fn must_h2(&self) -> bool {
|
@@ -121,6 +261,121 @@ impl Connected {
|
121 | 261 | */
|
122 | 262 | }
|
123 | 263 |
|
| 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 | + |
124 | 379 | #[cfg(feature = "runtime")]
|
125 | 380 | mod http {
|
126 | 381 | use super::*;
|
|
0 commit comments