|
|
|
@ -4,7 +4,7 @@ use std::io::{Read, Write}; |
|
|
|
|
use std::net::{SocketAddr, TcpStream, ToSocketAddrs}; |
|
|
|
|
use std::result::Result as StdResult; |
|
|
|
|
|
|
|
|
|
use http::Uri; |
|
|
|
|
use http::{Uri, request::Parts}; |
|
|
|
|
use log::*; |
|
|
|
|
|
|
|
|
|
use url::Url; |
|
|
|
@ -89,25 +89,62 @@ use crate::stream::{Mode, NoDelay}; |
|
|
|
|
pub fn connect_with_config<Req: IntoClientRequest>( |
|
|
|
|
request: Req, |
|
|
|
|
config: Option<WebSocketConfig>, |
|
|
|
|
max_redirects: u8, |
|
|
|
|
) -> Result<(WebSocket<AutoStream>, Response)> { |
|
|
|
|
let request: Request = request.into_client_request()?; |
|
|
|
|
let uri = request.uri(); |
|
|
|
|
let mode = uri_mode(uri)?; |
|
|
|
|
let host = request |
|
|
|
|
.uri() |
|
|
|
|
.host() |
|
|
|
|
.ok_or_else(|| Error::Url("No host name in the URL".into()))?; |
|
|
|
|
let port = uri.port_u16().unwrap_or(match mode { |
|
|
|
|
Mode::Plain => 80, |
|
|
|
|
Mode::Tls => 443, |
|
|
|
|
}); |
|
|
|
|
let addrs = (host, port).to_socket_addrs()?; |
|
|
|
|
let mut stream = connect_to_some(addrs.as_slice(), &request.uri(), mode)?; |
|
|
|
|
NoDelay::set_nodelay(&mut stream, true)?; |
|
|
|
|
client_with_config(request, stream, config).map_err(|e| match e { |
|
|
|
|
HandshakeError::Failure(f) => f, |
|
|
|
|
HandshakeError::Interrupted(_) => panic!("Bug: blocking handshake not blocked"), |
|
|
|
|
}) |
|
|
|
|
|
|
|
|
|
fn try_client_handshake(request: Request, config: Option<WebSocketConfig>) |
|
|
|
|
-> Result<(WebSocket<AutoStream>, Response)> |
|
|
|
|
{ |
|
|
|
|
let uri = request.uri(); |
|
|
|
|
let mode = uri_mode(uri)?; |
|
|
|
|
let host = request |
|
|
|
|
.uri() |
|
|
|
|
.host() |
|
|
|
|
.ok_or_else(|| Error::Url("No host name in the URL".into()))?; |
|
|
|
|
let port = uri.port_u16().unwrap_or(match mode { |
|
|
|
|
Mode::Plain => 80, |
|
|
|
|
Mode::Tls => 443, |
|
|
|
|
}); |
|
|
|
|
let addrs = (host, port).to_socket_addrs()?; |
|
|
|
|
let mut stream = connect_to_some(addrs.as_slice(), &request.uri(), mode)?; |
|
|
|
|
NoDelay::set_nodelay(&mut stream, true)?; |
|
|
|
|
client_with_config(request, stream, config).map_err(|e| match e { |
|
|
|
|
HandshakeError::Failure(f) => f, |
|
|
|
|
HandshakeError::Interrupted(_) => panic!("Bug: blocking handshake not blocked"), |
|
|
|
|
}) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
fn create_request(parts: &Parts, uri: &Uri) -> Request { |
|
|
|
|
let mut builder = Request::builder() |
|
|
|
|
.uri(uri.clone()) |
|
|
|
|
.method(parts.method.clone()) |
|
|
|
|
.version(parts.version.clone()); |
|
|
|
|
*builder.headers_mut().expect("Failed to create `Request`") = parts.headers.clone(); |
|
|
|
|
builder.body(()).expect("Failed to create `Request`") |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
let (parts, _) = request.into_client_request()?.into_parts(); |
|
|
|
|
let mut uri = parts.uri.clone(); |
|
|
|
|
|
|
|
|
|
for attempt in 0..(max_redirects + 1) { |
|
|
|
|
let request = create_request(&parts, &uri); |
|
|
|
|
|
|
|
|
|
match try_client_handshake(request, config) { |
|
|
|
|
Err(Error::Http(res)) if res.status().is_redirection() && attempt < max_redirects => { |
|
|
|
|
if let Some(location) = res.headers().get("Location") { |
|
|
|
|
uri = location.to_str()?.parse::<Uri>()?; |
|
|
|
|
debug!("Redirecting to {:?}", uri); |
|
|
|
|
continue; |
|
|
|
|
} else { |
|
|
|
|
warn!("No `Location` found in redirect"); |
|
|
|
|
return Err(Error::Http(res)); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
other => return other, |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
unreachable!("Bug in a redirect handling logic") |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/// Connect to the given WebSocket in blocking mode.
|
|
|
|
@ -123,7 +160,7 @@ pub fn connect_with_config<Req: IntoClientRequest>( |
|
|
|
|
/// use `client` instead. There is no need to enable the "tls" feature if you don't call
|
|
|
|
|
/// `connect` since it's the only function that uses native_tls.
|
|
|
|
|
pub fn connect<Req: IntoClientRequest>(request: Req) -> Result<(WebSocket<AutoStream>, Response)> { |
|
|
|
|
connect_with_config(request, None) |
|
|
|
|
connect_with_config(request, None, 3) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
fn connect_to_some(addrs: &[SocketAddr], uri: &Uri, mode: Mode) -> Result<AutoStream> { |
|
|
|
|