Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ws client redirections #397

Merged
merged 43 commits into from
Oct 5, 2021
Merged
Changes from 11 commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
31ae533
feat(ws client): support redirections
niklasad1 Jun 30, 2021
753699c
reuse socket
niklasad1 Jun 30, 2021
b6f09da
reuse socket
niklasad1 Jun 30, 2021
ce77bf5
add hacks
niklasad1 Jun 30, 2021
67b550e
fix build
niklasad1 Jun 30, 2021
e9d863f
remove hacks
niklasad1 Jun 30, 2021
4c1dc3c
Merge branch 'master' into na-ws-client-redirections
dvdplm Jul 12, 2021
8a31148
Merge remote-tracking branch 'origin/master' into na-ws-client-redire…
niklasad1 Aug 30, 2021
4685447
Merge branch 'na-ws-client-redirections' of github.com:paritytech/jso…
niklasad1 Aug 30, 2021
5a1e118
fix bad merge
niklasad1 Aug 30, 2021
9e506c1
address grumbles
niklasad1 Aug 30, 2021
ffa8150
Merge remote-tracking branch 'origin/master' into na-ws-client-redire…
niklasad1 Sep 29, 2021
d27a708
fix grumbles
niklasad1 Sep 29, 2021
1fce702
fix grumbles
niklasad1 Sep 29, 2021
250c896
fix nit
niklasad1 Sep 29, 2021
d200744
add redirection test
niklasad1 Sep 30, 2021
1f7cfe4
Merge branch 'master' into na-ws-client-redirections
dvdplm Oct 1, 2021
33f364c
Update test-utils/src/types.rs
dvdplm Oct 1, 2021
9d9aaba
Resolved todo
dvdplm Oct 1, 2021
720f709
Check that redirected client actually works
dvdplm Oct 1, 2021
ce87119
Merge branch 'master' into na-ws-client-redirections
dvdplm Oct 1, 2021
21583d1
Rename test-utils "types" to "mocks"
dvdplm Oct 1, 2021
f25a22d
Fix windows test (?)
dvdplm Oct 1, 2021
0d54d2e
fmt
dvdplm Oct 1, 2021
b93287c
What is wrong with you windows?
dvdplm Oct 1, 2021
4df383f
Ignore redirect test on windows
dvdplm Oct 1, 2021
ae2791c
fix bad transport errors
niklasad1 Oct 2, 2021
0d4b18b
debug windows tests
niklasad1 Oct 2, 2021
adbd2c6
update soketto
niklasad1 Oct 2, 2021
bf0d71d
maybe fix windows test
niklasad1 Oct 2, 2021
186bf84
add config flag for max redirections
niklasad1 Oct 2, 2021
efe1c28
revert faulty change.
niklasad1 Oct 2, 2021
e70842b
revert windows path
niklasad1 Oct 2, 2021
549e563
use manual join paths
niklasad1 Oct 3, 2021
df28dbd
remove url dep
niklasad1 Oct 3, 2021
4b5065c
Update ws-client/src/tests.rs
niklasad1 Oct 3, 2021
93d0c3b
default max redirects 5
niklasad1 Oct 4, 2021
c257fea
Merge branch 'na-ws-client-redirections' of github.com:paritytech/jso…
niklasad1 Oct 4, 2021
0488a2a
remove needless clone vec
niklasad1 Oct 5, 2021
56db40a
Merge remote-tracking branch 'origin/master' into na-ws-client-redire…
niklasad1 Oct 5, 2021
3aede64
Merge remote-tracking branch 'origin/master' into na-ws-client-redire…
niklasad1 Oct 5, 2021
223c37c
fix bad merge
niklasad1 Oct 5, 2021
ae1e405
cmon CI run
niklasad1 Oct 5, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
172 changes: 110 additions & 62 deletions ws-client/src/transport.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,11 @@
// IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.

use crate::stream::EitherStream;
use futures::io::{BufReader, BufWriter};
use futures::prelude::*;
use soketto::connection;
use soketto::handshake::client::{Client as WsRawClient, ServerResponse};
use soketto::handshake::client::{Client as WsHandshakeClient, ServerResponse};
use std::path::{Path, PathBuf};
use std::{borrow::Cow, io, net::SocketAddr, sync::Arc, time::Duration};
use thiserror::Error;
use tokio::net::TcpStream;
Expand All @@ -37,7 +38,10 @@ use tokio_rustls::{
TlsConnector,
};

type TlsOrPlain = crate::stream::EitherStream<TcpStream, TlsStream<TcpStream>>;
type TlsOrPlain = EitherStream<TcpStream, TlsStream<TcpStream>>;

// TODO(niklasad1): make this configurable.
dvdplm marked this conversation as resolved.
Show resolved Hide resolved
const MAX_REDIRECTIONS_ALLOWED: usize = 5;

/// Sending end of WebSocket transport.
#[derive(Debug)]
Expand Down Expand Up @@ -112,7 +116,7 @@ pub enum WsHandshakeError {
#[error("Invalid DNS name: {}", 0)]
InvalidDnsName(#[source] InvalidDNSNameError),

/// RawServer rejected our handshake.
/// Server rejected the handshake.
#[error("Connection rejected with status code: {}", status_code)]
Rejected {
/// HTTP status code that the server returned.
Expand Down Expand Up @@ -179,74 +183,118 @@ impl<'a> WsTransportClientBuilder<'a> {
Mode::Plain => None,
};

let mut err = None;
for sockaddr in &self.target.sockaddrs {
match self.try_connect(*sockaddr, &connector).await {
Ok(res) => return Ok(res),
Err(e) => {
log::debug!("Failed to connect to sockaddr: {:?} with err: {:?}", sockaddr, e);
err = Some(Err(e));
}
}
}
// NOTE(niklasad1): this is most likely unreachable because [`Url::socket_addrs`] doesn't
// return an empty `Vec` if no socket address was found for the host name.
err.unwrap_or(Err(WsHandshakeError::NoAddressFound(self.target.host)))
self.try_connect(connector).await
}

async fn try_connect(
&self,
sockaddr: SocketAddr,
tls_connector: &Option<TlsConnector>,
self,
mut tls_connector: Option<TlsConnector>,
) -> Result<(Sender, Receiver), WsHandshakeError> {
// Try establish the TCP connection.
let tcp_stream = {
let socket = TcpStream::connect(sockaddr);
let timeout = tokio::time::sleep(self.timeout);
futures::pin_mut!(socket, timeout);
match future::select(socket, timeout).await {
future::Either::Left((socket, _)) => {
let socket = socket?;
if let Err(err) = socket.set_nodelay(true) {
log::warn!("set nodelay failed: {:?}", err);
}
match tls_connector {
None => TlsOrPlain::Plain(socket),
Some(connector) => {
let dns_name = DNSNameRef::try_from_ascii_str(&self.target.host)?;
let tls_stream = connector.connect(dns_name, socket).await?;
TlsOrPlain::Tls(tls_stream)
}
}
}
future::Either::Right((_, _)) => return Err(WsHandshakeError::Timeout(self.timeout)),
let mut sockaddrs = self.target.sockaddrs;
let mut path_and_query = PathBuf::from(self.target.path_and_query);
let mut host = self.target.host;
let mut host_header = self.target.host_header;
dvdplm marked this conversation as resolved.
Show resolved Hide resolved

let mut err = None;

for _ in 0..MAX_REDIRECTIONS_ALLOWED {
let sockaddr = match sockaddrs.pop() {
niklasad1 marked this conversation as resolved.
Show resolved Hide resolved
Some(addr) => addr,
None => return err.unwrap_or(Err(WsHandshakeError::NoAddressFound(host))),
};

let tcp_stream = connect(sockaddr, self.timeout, &host, &tls_connector).await?;
let mut client = WsHandshakeClient::new(
BufReader::new(BufWriter::new(tcp_stream)),
&host_header,
path_and_query.to_str().expect("valid UTF-8 checked by Url::parse; qed"),
);
if let Some(origin) = self.origin_header.as_ref() {
client.set_origin(origin);
niklasad1 marked this conversation as resolved.
Show resolved Hide resolved
}
};
// Perform the initial handshake.
match client.handshake().await? {
ServerResponse::Accepted { .. } => {
let mut builder = client.into_builder();
builder.set_max_message_size(self.max_request_body_size as usize);
let (sender, receiver) = builder.finish();
return Ok((Sender { inner: sender }, Receiver { inner: receiver }));
}

log::debug!("Connecting to target: {:?}", self.target);
let mut client = WsRawClient::new(
BufReader::new(BufWriter::new(tcp_stream)),
&self.target.host_header,
&self.target.path_and_query,
);
if let Some(origin) = self.origin_header.as_ref() {
client.set_origin(origin);
ServerResponse::Rejected { status_code } => {
err = Some(Err(WsHandshakeError::Rejected { status_code }));
}
ServerResponse::Redirect { status_code, location } => {
log::trace!("recv redirection: status_code: {}, location: {}", status_code, location);
match url::Url::parse(&location) {
// redirection with absolute path => need to lookup.
Ok(url) => {
let target = Target::parse(url)?;
sockaddrs = target.sockaddrs;
path_and_query = PathBuf::from(target.path_and_query);
host = target.host;
host_header = target.host_header;
dvdplm marked this conversation as resolved.
Show resolved Hide resolved
tls_connector = match target.mode {
Mode::Tls => {
let mut client_config = rustls::ClientConfig::default();
if let CertificateStore::Native = self.certificate_store {
client_config.root_store = rustls_native_certs::load_native_certs()
.map_err(|(_, e)| WsHandshakeError::CertificateStore(e))?;
}
Some(Arc::new(client_config).into())
}
Mode::Plain => None,
};
}
// redirection is relative, either `/baz` or `bar`.
Err(
url::ParseError::RelativeUrlWithoutBase | url::ParseError::RelativeUrlWithCannotBeABaseBase,
) => {
// replace the entire path if `location` is `/`.
if location.starts_with('/') {
path_and_query = location.into();
} else {
dvdplm marked this conversation as resolved.
Show resolved Hide resolved
// join paths such that the leaf is replaced with `location`.
let strip_last_child =
Path::new(&path_and_query).ancestors().nth(1).unwrap_or_else(|| Path::new("/"));
path_and_query = strip_last_child.join(location);
}
}
Err(e) => {
err = Some(Err(WsHandshakeError::Url(e.to_string().into())));
}
};
}
};
}
err.unwrap_or(Err(WsHandshakeError::NoAddressFound(host)))
}
}

// Perform the initial handshake.
match client.handshake().await? {
ServerResponse::Accepted { .. } => {}
ServerResponse::Rejected { status_code } | ServerResponse::Redirect { status_code, .. } => {
// TODO: HTTP redirects also lead here #339.
return Err(WsHandshakeError::Rejected { status_code });
async fn connect(
sockaddr: SocketAddr,
timeout_dur: Duration,
host: &str,
tls_connector: &Option<TlsConnector>,
) -> Result<EitherStream<TcpStream, TlsStream<TcpStream>>, WsHandshakeError> {
let socket = TcpStream::connect(sockaddr);
let timeout = tokio::time::sleep(timeout_dur);
tokio::select! {
socket = socket => {
let socket = socket?;
if let Err(err) = socket.set_nodelay(true) {
log::warn!("set nodelay failed: {:?}", err);
}
match tls_connector {
None => Ok(TlsOrPlain::Plain(socket)),
Some(connector) => {
let dns_name = DNSNameRef::try_from_ascii_str(host)?;
let tls_stream = connector.connect(dns_name, socket).await?;
Ok(TlsOrPlain::Tls(tls_stream))
}
}
}

// If the handshake succeeded, return.
let mut builder = client.into_builder();
builder.set_max_message_size(self.max_request_body_size as usize);
let (sender, receiver) = builder.finish();
Ok((Sender { inner: sender }, Receiver { inner: receiver }))
_ = timeout => Err(WsHandshakeError::Timeout(timeout_dur))
}
}

Expand Down