Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
  • Loading branch information
romac committed Jul 28, 2022
1 parent 2426a65 commit e21b17a
Show file tree
Hide file tree
Showing 4 changed files with 99 additions and 12 deletions.
1 change: 1 addition & 0 deletions rpc/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ structopt = { version = "0.3", optional = true, default-features = false }
tokio = { version = "1.0", optional = true, default-features = false, features = ["rt-multi-thread"] }
tracing = { version = "0.1", optional = true, default-features = false }
tracing-subscriber = { version = "0.2", optional = true, default-features = false, features = ["fmt"] }
tungstenite = "0.17.3"

[dev-dependencies]
lazy_static = { version = "1.4.0", default-features = false }
Expand Down
22 changes: 22 additions & 0 deletions rpc/src/client/transport/http.rs
Original file line number Diff line number Diff line change
Expand Up @@ -156,13 +156,16 @@ impl TryFrom<HttpClientUrl> for hyper::Uri {
mod sealed {
use crate::prelude::*;
use crate::{Error, Response, SimpleRequest};
use http::header::AUTHORIZATION;
use http::uri::Authority;
use hyper::body::Buf;
use hyper::client::connect::Connect;
use hyper::client::HttpConnector;
use hyper::{header, Uri};
use hyper_proxy::{Intercept, Proxy, ProxyConnector};
use hyper_rustls::HttpsConnector;
use std::io::Read;
use subtle_encoding::base64;

/// A wrapper for a `hyper`-based client, generic over the connector type.
#[derive(Debug, Clone)]
Expand Down Expand Up @@ -216,12 +219,31 @@ mod sealed {
.parse()
.unwrap(),
);

if let Some(credentials) = self.uri.authority().and_then(parse_credentials) {
headers.insert(
AUTHORIZATION,
format!("Basic {credentials}").parse().unwrap(),
);
}
}

Ok(request)
}
}

fn parse_credentials(a: &Authority) -> Option<String> {
let s = a.as_str();

if let Some((userpass, _)) = s.split_once('@') {
let bytes = base64::encode(userpass);
let credentials = String::from_utf8_lossy(bytes.as_slice());
Some(credentials.to_string())
} else {
None
}
}

/// We offer several variations of `hyper`-based client.
///
/// Here we erase the type signature of the underlying `hyper`-based
Expand Down
46 changes: 44 additions & 2 deletions rpc/src/client/transport/websocket.rs
Original file line number Diff line number Diff line change
Expand Up @@ -264,11 +264,13 @@ mod sealed {
use crate::utils::uuid_str;
use crate::{Error, Response, SimpleRequest, Subscription, Url};

use async_tungstenite::tungstenite::client::IntoClientRequest;
use async_tungstenite::{
tokio::{connect_async_with_config, connect_async_with_tls_connector_and_config},
tungstenite::protocol::WebSocketConfig,
};

use http::uri::Authority;
use tracing::debug;

/// Marker for the [`AsyncTungsteniteClient`] for clients operating over
Expand Down Expand Up @@ -306,7 +308,6 @@ mod sealed {
url: Url,
config: Option<WebSocketConfig>,
) -> Result<(Self, WebSocketClientDriver), Error> {
let url = url.to_string();
debug!("Connecting to unsecure WebSocket endpoint: {}", url);

let (stream, _response) = connect_async_with_config(url, config)
Expand Down Expand Up @@ -338,7 +339,6 @@ mod sealed {
url: Url,
config: Option<WebSocketConfig>,
) -> Result<(Self, WebSocketClientDriver), Error> {
let url = url.to_string();
debug!("Connecting to secure WebSocket endpoint: {}", url);

// Not supplying a connector means async_tungstenite will create the
Expand Down Expand Up @@ -476,6 +476,48 @@ mod sealed {
}
}
}

impl IntoClientRequest for Url {
fn into_client_request(
self,
) -> tungstenite::Result<tungstenite::handshake::client::Request> {
fn parse_credentials(a: &Authority) -> Option<String> {
let s = a.as_str();

if let Some((userpass, _)) = s.split_once('@') {
let bytes = subtle_encoding::base64::encode(userpass);
let credentials = String::from_utf8_lossy(bytes.as_slice());
Some(credentials.to_string())
} else {
None
}
}

let uri = self.to_string().parse::<http::Uri>().unwrap();

let builder = tungstenite::handshake::client::Request::builder()
.method("GET")
.header("Host", self.host())
.header("Connection", "Upgrade")
.header("Upgrade", "websocket")
.header("Sec-WebSocket-Version", "13")
.header(
"Sec-WebSocket-Key",
tungstenite::handshake::client::generate_key(),
);

let builder = if let Some(credentials) = uri.authority().and_then(parse_credentials) {
builder.header("Authorization", format!("Basic {credentials}"))
} else {
builder
};

builder
.uri(uri)
.body(())
.map_err(tungstenite::error::Error::HttpFormat)
}
}
}

// The different types of commands that can be sent from the WebSocketClient to
Expand Down
42 changes: 32 additions & 10 deletions rpc/src/rpc_url.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ impl FromStr for Scheme {
pub struct Url {
inner: url::Url,
scheme: Scheme,
username: Option<String>,
password: Option<String>,
host: String,
port: u16,
}
Expand All @@ -63,6 +65,12 @@ impl FromStr for Url {

let scheme: Scheme = inner.scheme().parse()?;

let username = Some(inner.username())
.filter(|s| !s.is_empty())
.map(|s| s.to_owned());

let password = inner.password().map(|s| s.to_owned());

let host = inner
.host_str()
.ok_or_else(|| Error::invalid_params(format!("URL is missing its host: {}", s)))?
Expand All @@ -71,9 +79,12 @@ impl FromStr for Url {
let port = inner.port_or_known_default().ok_or_else(|| {
Error::invalid_params(format!("cannot determine appropriate port for URL: {}", s))
})?;

Ok(Self {
inner,
scheme,
username,
password,
host,
port,
})
Expand All @@ -98,13 +109,20 @@ impl Url {
}

/// Get the username associated with this URL, if any.
pub fn username(&self) -> &str {
self.inner.username()
pub fn username(&self) -> Option<&str> {
self.username.as_deref()
}

/// Get the password associated with this URL, if any.
pub fn password(&self) -> Option<&str> {
self.inner.password()
self.password.as_deref()
}

/// Get the authority associated with this URL, if any.
/// The authority is the username and password separated by a colon.
pub fn authority(&self) -> Option<String> {
self.username()
.map(|user| format!("{user}:{}", self.password().unwrap_or_default()))
}

/// Get the host associated with this URL.
Expand Down Expand Up @@ -166,7 +184,7 @@ mod test {
host: String,
port: u16,
path: String,
username: String,
username: Option<String>,
password: Option<String>,
}

Expand All @@ -179,7 +197,7 @@ mod test {
host: "127.0.0.1".to_string(),
port: 26657,
path: "".to_string(),
username: "".to_string(),
username: None,
password: None,
}
),
Expand All @@ -190,7 +208,7 @@ mod test {
host: "127.0.0.1".to_string(),
port: 26657,
path: "/".to_string(),
username: "".to_string(),
username: None,
password: None,
}
),
Expand All @@ -201,7 +219,7 @@ mod test {
host: "127.0.0.1".to_string(),
port: 26657,
path: "/".to_string(),
username: "".to_string(),
username: None,
password: None,
}
),
Expand All @@ -212,7 +230,7 @@ mod test {
host: "127.0.0.1".to_string(),
port: 26657,
path: "/websocket".to_string(),
username: "".to_string(),
username: None,
password: None,
}
),
Expand All @@ -223,7 +241,7 @@ mod test {
host: "127.0.0.1".to_string(),
port: 26657,
path: "/websocket".to_string(),
username: "".to_string(),
username: None,
password: None,
}
)
Expand All @@ -238,7 +256,11 @@ mod test {
assert_eq!(expected.host, u.host(), "{}", url_str);
assert_eq!(expected.port, u.port(), "{}", url_str);
assert_eq!(expected.path, u.path(), "{}", url_str);
assert_eq!(expected.username, u.username());
if let Some(n) = u.username() {
assert_eq!(expected.username.as_ref().unwrap(), n, "{}", url_str);
} else {
assert!(expected.username.is_none(), "{}", url_str);
}
if let Some(pw) = u.password() {
assert_eq!(expected.password.as_ref().unwrap(), pw, "{}", url_str);
} else {
Expand Down

0 comments on commit e21b17a

Please sign in to comment.