diff --git a/core/src/apresolve.rs b/core/src/apresolve.rs index 975e0e18..55e9dc23 100644 --- a/core/src/apresolve.rs +++ b/core/src/apresolve.rs @@ -1,10 +1,7 @@ -use std::error::Error; - -use hyper::client::HttpConnector; -use hyper::{Body, Client, Request}; -use hyper_proxy::{Intercept, Proxy, ProxyConnector}; +use crate::http_client::HttpClient; +use hyper::{Body, Request}; use serde::Deserialize; -use url::Url; +use std::error::Error; const APRESOLVE_ENDPOINT: &str = "http://apresolve.spotify.com/?type=accesspoint&type=dealer&type=spclient"; @@ -56,35 +53,21 @@ fn select_ap(data: Vec, fallback: &str, ap_port: Option) -> SocketA ap.unwrap_or_else(|| (String::from(fallback), port)) } -async fn try_apresolve(proxy: Option<&Url>) -> Result> { +async fn try_apresolve(http_client: &HttpClient) -> Result> { let req = Request::builder() .method("GET") .uri(APRESOLVE_ENDPOINT) .body(Body::empty()) .unwrap(); - let response = if let Some(url) = proxy { - // Panic safety: all URLs are valid URIs - let uri = url.to_string().parse().unwrap(); - let proxy = Proxy::new(Intercept::All, uri); - let connector = HttpConnector::new(); - let proxy_connector = ProxyConnector::from_proxy_unsecured(connector, proxy); - Client::builder() - .build(proxy_connector) - .request(req) - .await? - } else { - Client::new().request(req).await? - }; - - let body = hyper::body::to_bytes(response.into_body()).await?; + let body = http_client.request_body(req).await?; let data: ApResolveData = serde_json::from_slice(body.as_ref())?; Ok(data) } -pub async fn apresolve(proxy: Option<&Url>, ap_port: Option) -> AccessPoints { - let data = try_apresolve(proxy).await.unwrap_or_else(|e| { +pub async fn apresolve(http_client: &HttpClient, ap_port: Option) -> AccessPoints { + let data = try_apresolve(http_client).await.unwrap_or_else(|e| { warn!("Failed to resolve access points: {}, using fallbacks.", e); ApResolveData::default() }); @@ -105,10 +88,12 @@ mod test { use std::net::ToSocketAddrs; use super::apresolve; + use crate::http_client::HttpClient; #[tokio::test] async fn test_apresolve() { - let aps = apresolve(None, None).await; + let http_client = HttpClient::new(None); + let aps = apresolve(&http_client, None).await; // Assert that the result contains a valid host and port aps.accesspoint.to_socket_addrs().unwrap().next().unwrap(); @@ -118,7 +103,8 @@ mod test { #[tokio::test] async fn test_apresolve_port_443() { - let aps = apresolve(None, Some(443)).await; + let http_client = HttpClient::new(None); + let aps = apresolve(&http_client, Some(443)).await; let port = aps .accesspoint diff --git a/core/src/http_client.rs b/core/src/http_client.rs new file mode 100644 index 00000000..5f8ef780 --- /dev/null +++ b/core/src/http_client.rs @@ -0,0 +1,34 @@ +use hyper::client::HttpConnector; +use hyper::{Body, Client, Request, Response}; +use hyper_proxy::{Intercept, Proxy, ProxyConnector}; +use url::Url; + +pub struct HttpClient { + proxy: Option, +} + +impl HttpClient { + pub fn new(proxy: Option<&Url>) -> Self { + Self { + proxy: proxy.cloned(), + } + } + + pub async fn request(&self, req: Request) -> Result, hyper::Error> { + if let Some(url) = &self.proxy { + // Panic safety: all URLs are valid URIs + let uri = url.to_string().parse().unwrap(); + let proxy = Proxy::new(Intercept::All, uri); + let connector = HttpConnector::new(); + let proxy_connector = ProxyConnector::from_proxy_unsecured(connector, proxy); + Client::builder().build(proxy_connector).request(req).await + } else { + Client::new().request(req).await + } + } + + pub async fn request_body(&self, req: Request) -> Result { + let response = self.request(req).await?; + hyper::body::to_bytes(response.into_body()).await + } +} diff --git a/core/src/lib.rs b/core/src/lib.rs index 7a07d94d..9e7b806d 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -19,11 +19,13 @@ mod connection; mod dealer; #[doc(hidden)] pub mod diffie_hellman; +mod http_client; pub mod keymaster; pub mod mercury; mod proxytunnel; pub mod session; mod socket; +mod spclient; pub mod spotify_id; mod token; #[doc(hidden)] diff --git a/core/src/session.rs b/core/src/session.rs index fe8e4d5f..8bf78f50 100644 --- a/core/src/session.rs +++ b/core/src/session.rs @@ -23,6 +23,7 @@ use crate::cache::Cache; use crate::channel::ChannelManager; use crate::config::SessionConfig; use crate::connection::{self, AuthenticationError}; +use crate::http_client::HttpClient; use crate::mercury::MercuryManager; use crate::token::TokenProvider; @@ -45,6 +46,7 @@ struct SessionInternal { config: SessionConfig, data: RwLock, + http_client: HttpClient, tx_connection: mpsc::UnboundedSender<(u8, Vec)>, audio_key: OnceCell, @@ -69,22 +71,22 @@ impl Session { credentials: Credentials, cache: Option, ) -> Result { - let ap = apresolve(config.proxy.as_ref(), config.ap_port) - .await - .accesspoint; + let http_client = HttpClient::new(config.proxy.as_ref()); + let ap = apresolve(&http_client, config.ap_port).await.accesspoint; info!("Connecting to AP \"{}:{}\"", ap.0, ap.1); - let mut conn = connection::connect(&ap.0, ap.1, config.proxy.as_ref()).await?; + let mut transport = connection::connect(&ap.0, ap.1, config.proxy.as_ref()).await?; let reusable_credentials = - connection::authenticate(&mut conn, credentials, &config.device_id).await?; + connection::authenticate(&mut transport, credentials, &config.device_id).await?; info!("Authenticated as \"{}\" !", reusable_credentials.username); if let Some(cache) = &cache { cache.save_credentials(&reusable_credentials); } let session = Session::create( - conn, + transport, + http_client, config, cache, reusable_credentials.username, @@ -96,6 +98,7 @@ impl Session { fn create( transport: connection::Transport, + http_client: HttpClient, config: SessionConfig, cache: Option, username: String, @@ -116,6 +119,7 @@ impl Session { invalid: false, time_delta: 0, }), + http_client, tx_connection: sender_tx, cache: cache.map(Arc::new), audio_key: OnceCell::new(), diff --git a/core/src/dealer/api.rs b/core/src/spclient.rs similarity index 99% rename from core/src/dealer/api.rs rename to core/src/spclient.rs index d9dd2b9b..eb7b3f0f 100644 --- a/core/src/dealer/api.rs +++ b/core/src/spclient.rs @@ -1,2 +1 @@ // https://github.com/librespot-org/librespot-java/blob/27783e06f456f95228c5ac37acf2bff8c1a8a0c4/lib/src/main/java/xyz/gianlu/librespot/dealer/ApiClient.java -