mirror of
https://github.com/librespot-org/librespot.git
synced 2024-12-18 17:11:53 +00:00
Put apresolve behind feature flag
This commit is contained in:
parent
9253be7bc9
commit
8cff10e983
6 changed files with 124 additions and 101 deletions
2
Cargo.lock
generated
2
Cargo.lock
generated
|
@ -1450,6 +1450,7 @@ dependencies = [
|
||||||
"base64",
|
"base64",
|
||||||
"byteorder",
|
"byteorder",
|
||||||
"bytes",
|
"bytes",
|
||||||
|
"cfg-if 1.0.0",
|
||||||
"env_logger",
|
"env_logger",
|
||||||
"error-chain",
|
"error-chain",
|
||||||
"futures",
|
"futures",
|
||||||
|
@ -1473,7 +1474,6 @@ dependencies = [
|
||||||
"shannon",
|
"shannon",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tokio-util",
|
"tokio-util",
|
||||||
"tower-service",
|
|
||||||
"url 1.7.2",
|
"url 1.7.2",
|
||||||
"vergen",
|
"vergen",
|
||||||
]
|
]
|
||||||
|
|
|
@ -61,6 +61,9 @@ sha-1 = "0.8"
|
||||||
hex = "0.4"
|
hex = "0.4"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
|
apresolve = ["librespot-core/apresolve"]
|
||||||
|
apresolve-http2 = ["librespot-core/apresolve-http2"]
|
||||||
|
|
||||||
alsa-backend = ["librespot-playback/alsa-backend"]
|
alsa-backend = ["librespot-playback/alsa-backend"]
|
||||||
portaudio-backend = ["librespot-playback/portaudio-backend"]
|
portaudio-backend = ["librespot-playback/portaudio-backend"]
|
||||||
pulseaudio-backend = ["librespot-playback/pulseaudio-backend"]
|
pulseaudio-backend = ["librespot-playback/pulseaudio-backend"]
|
||||||
|
@ -75,7 +78,7 @@ with-vorbis = ["librespot-audio/with-vorbis"]
|
||||||
|
|
||||||
# with-dns-sd = ["librespot-connect/with-dns-sd"]
|
# with-dns-sd = ["librespot-connect/with-dns-sd"]
|
||||||
|
|
||||||
default = ["librespot-playback/rodio-backend"]
|
default = ["rodio-backend", "apresolve"]
|
||||||
|
|
||||||
[package.metadata.deb]
|
[package.metadata.deb]
|
||||||
maintainer = "librespot-org"
|
maintainer = "librespot-org"
|
||||||
|
|
|
@ -17,11 +17,12 @@ aes = "0.6"
|
||||||
base64 = "0.13"
|
base64 = "0.13"
|
||||||
byteorder = "1.4"
|
byteorder = "1.4"
|
||||||
bytes = "1.0"
|
bytes = "1.0"
|
||||||
|
cfg-if = "1"
|
||||||
error-chain = { version = "0.12", default-features = false }
|
error-chain = { version = "0.12", default-features = false }
|
||||||
futures = { version = "0.3", features = ["bilock", "unstable"] }
|
futures = { version = "0.3", features = ["bilock", "unstable"] }
|
||||||
hmac = "0.10"
|
hmac = "0.10"
|
||||||
httparse = "1.3"
|
httparse = "1.3"
|
||||||
hyper = { version = "0.14", features = ["client", "tcp", "http1", "http2"] }
|
hyper = { version = "0.14", optional = true, features = ["client", "tcp", "http1"] }
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
num-bigint = "0.3"
|
num-bigint = "0.3"
|
||||||
num-integer = "0.1"
|
num-integer = "0.1"
|
||||||
|
@ -38,7 +39,6 @@ sha-1 = "0.9"
|
||||||
shannon = "0.2.0"
|
shannon = "0.2.0"
|
||||||
tokio = { version = "1.0", features = ["io-util", "rt-multi-thread"] }
|
tokio = { version = "1.0", features = ["io-util", "rt-multi-thread"] }
|
||||||
tokio-util = { version = "0.6", features = ["codec"] }
|
tokio-util = { version = "0.6", features = ["codec"] }
|
||||||
tower-service = "0.3"
|
|
||||||
url = "1.7"
|
url = "1.7"
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
|
@ -48,3 +48,7 @@ vergen = "3.0.4"
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
env_logger = "*"
|
env_logger = "*"
|
||||||
tokio = {version = "1.0", features = ["macros"] }
|
tokio = {version = "1.0", features = ["macros"] }
|
||||||
|
|
||||||
|
[features]
|
||||||
|
apresolve = ["hyper"]
|
||||||
|
apresolve-http2 = ["apresolve", "hyper/http2"]
|
||||||
|
|
|
@ -1,61 +1,72 @@
|
||||||
const AP_FALLBACK: &'static str = "ap.spotify.com:443";
|
const AP_FALLBACK: &'static str = "ap.spotify.com:443";
|
||||||
const APRESOLVE_ENDPOINT: &'static str = "http://apresolve.spotify.com:80";
|
|
||||||
|
|
||||||
use hyper::{Body, Client, Method, Request, Uri};
|
|
||||||
use std::error::Error;
|
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
use crate::proxytunnel::ProxyTunnel;
|
cfg_if! {
|
||||||
|
if #[cfg(feature = "apresolve")] {
|
||||||
|
const APRESOLVE_ENDPOINT: &'static str = "http://apresolve.spotify.com:80";
|
||||||
|
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
use std::error::Error;
|
||||||
pub struct APResolveData {
|
|
||||||
ap_list: Vec<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn apresolve(proxy: &Option<Url>, ap_port: &Option<u16>) -> Result<String, Box<dyn Error>> {
|
use hyper::{Body, Client, Method, Request, Uri};
|
||||||
let port = ap_port.unwrap_or(443);
|
|
||||||
|
|
||||||
let req = Request::builder()
|
use crate::proxytunnel::ProxyTunnel;
|
||||||
.method(Method::GET)
|
|
||||||
.uri(
|
|
||||||
APRESOLVE_ENDPOINT
|
|
||||||
.parse::<Uri>()
|
|
||||||
.expect("invalid AP resolve URL"),
|
|
||||||
)
|
|
||||||
.body(Body::empty())?;
|
|
||||||
|
|
||||||
let response = if let Some(url) = proxy {
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||||
Client::builder()
|
pub struct APResolveData {
|
||||||
.build(ProxyTunnel::new(url)?)
|
ap_list: Vec<String>,
|
||||||
.request(req)
|
}
|
||||||
.await?
|
|
||||||
} else {
|
|
||||||
Client::new().request(req).await?
|
|
||||||
};
|
|
||||||
|
|
||||||
let body = hyper::body::to_bytes(response.into_body()).await?;
|
async fn apresolve(proxy: &Option<Url>, ap_port: &Option<u16>) -> Result<String, Box<dyn Error>> {
|
||||||
let data: APResolveData = serde_json::from_slice(body.as_ref())?;
|
let port = ap_port.unwrap_or(443);
|
||||||
|
|
||||||
let ap = if ap_port.is_some() || proxy.is_some() {
|
let req = Request::builder()
|
||||||
data.ap_list.into_iter().find_map(|ap| {
|
.method(Method::GET)
|
||||||
if ap.parse::<Uri>().ok()?.port()? == port {
|
.uri(
|
||||||
Some(ap)
|
APRESOLVE_ENDPOINT
|
||||||
|
.parse::<Uri>()
|
||||||
|
.expect("invalid AP resolve URL"),
|
||||||
|
)
|
||||||
|
.body(Body::empty())?;
|
||||||
|
|
||||||
|
let response = if let Some(url) = proxy {
|
||||||
|
Client::builder()
|
||||||
|
.build(ProxyTunnel::new(url)?)
|
||||||
|
.request(req)
|
||||||
|
.await?
|
||||||
} else {
|
} else {
|
||||||
None
|
Client::new().request(req).await?
|
||||||
|
};
|
||||||
|
|
||||||
|
let body = hyper::body::to_bytes(response.into_body()).await?;
|
||||||
|
let data: APResolveData = serde_json::from_slice(body.as_ref())?;
|
||||||
|
|
||||||
|
let ap = if ap_port.is_some() || proxy.is_some() {
|
||||||
|
data.ap_list.into_iter().find_map(|ap| {
|
||||||
|
if ap.parse::<Uri>().ok()?.port()? == port {
|
||||||
|
Some(ap)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
data.ap_list.into_iter().next()
|
||||||
}
|
}
|
||||||
})
|
.ok_or("empty AP List")?;
|
||||||
|
|
||||||
|
Ok(ap)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn apresolve_or_fallback(proxy: &Option<Url>, ap_port: &Option<u16>) -> String {
|
||||||
|
apresolve(proxy, ap_port).await.unwrap_or_else(|e| {
|
||||||
|
warn!("Failed to resolve Access Point: {}", e);
|
||||||
|
warn!("Using fallback \"{}\"", AP_FALLBACK);
|
||||||
|
AP_FALLBACK.into()
|
||||||
|
})
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
data.ap_list.into_iter().next()
|
pub async fn apresolve_or_fallback(_: &Option<Url>, _: &Option<u16>) -> String {
|
||||||
|
AP_FALLBACK.to_string()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.ok_or("empty AP List")?;
|
|
||||||
|
|
||||||
Ok(ap)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn apresolve_or_fallback(proxy: &Option<Url>, ap_port: &Option<u16>) -> String {
|
|
||||||
apresolve(proxy, ap_port).await.unwrap_or_else(|e| {
|
|
||||||
warn!("Failed to resolve Access Point: {}", e);
|
|
||||||
warn!("Using fallback \"{}\"", AP_FALLBACK);
|
|
||||||
AP_FALLBACK.into()
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,8 @@
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate log;
|
extern crate log;
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
|
extern crate cfg_if;
|
||||||
|
#[macro_use]
|
||||||
extern crate serde_derive;
|
extern crate serde_derive;
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate pin_project_lite;
|
extern crate pin_project_lite;
|
||||||
|
|
|
@ -1,16 +1,6 @@
|
||||||
use futures::Future;
|
use std::io;
|
||||||
use hyper::Uri;
|
|
||||||
use std::{
|
use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt};
|
||||||
io,
|
|
||||||
net::{SocketAddr, ToSocketAddrs},
|
|
||||||
pin::Pin,
|
|
||||||
task::Poll,
|
|
||||||
};
|
|
||||||
use tokio::{
|
|
||||||
io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt},
|
|
||||||
net::TcpStream,
|
|
||||||
};
|
|
||||||
use tower_service::Service;
|
|
||||||
|
|
||||||
pub async fn connect<T: AsyncRead + AsyncWrite + Unpin>(
|
pub async fn connect<T: AsyncRead + AsyncWrite + Unpin>(
|
||||||
mut proxy_connection: T,
|
mut proxy_connection: T,
|
||||||
|
@ -64,43 +54,56 @@ pub async fn connect<T: AsyncRead + AsyncWrite + Unpin>(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
cfg_if! {
|
||||||
pub struct ProxyTunnel {
|
if #[cfg(feature = "apresolve")] {
|
||||||
proxy_addr: SocketAddr,
|
use std::future::Future;
|
||||||
}
|
use std::net::{SocketAddr, ToSocketAddrs};
|
||||||
|
use std::pin::Pin;
|
||||||
|
use std::task::Poll;
|
||||||
|
|
||||||
impl ProxyTunnel {
|
use hyper::service::Service;
|
||||||
pub fn new<T: ToSocketAddrs>(addr: T) -> io::Result<Self> {
|
use hyper::Uri;
|
||||||
let addr = addr.to_socket_addrs()?.next().ok_or_else(|| {
|
use tokio::net::TcpStream;
|
||||||
io::Error::new(io::ErrorKind::InvalidInput, "No socket address given")
|
|
||||||
})?;
|
#[derive(Clone)]
|
||||||
Ok(Self { proxy_addr: addr })
|
pub struct ProxyTunnel {
|
||||||
}
|
proxy_addr: SocketAddr,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Service<Uri> for ProxyTunnel {
|
impl ProxyTunnel {
|
||||||
type Response = TcpStream;
|
pub fn new<T: ToSocketAddrs>(addr: T) -> io::Result<Self> {
|
||||||
type Error = io::Error;
|
let addr = addr.to_socket_addrs()?.next().ok_or_else(|| {
|
||||||
type Future = Pin<Box<dyn Future<Output = io::Result<TcpStream>> + Send>>;
|
io::Error::new(io::ErrorKind::InvalidInput, "No socket address given")
|
||||||
|
})?;
|
||||||
fn poll_ready(&mut self, _: &mut std::task::Context<'_>) -> Poll<io::Result<()>> {
|
Ok(Self { proxy_addr: addr })
|
||||||
Poll::Ready(Ok(()))
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn call(&mut self, url: Uri) -> Self::Future {
|
impl Service<Uri> for ProxyTunnel {
|
||||||
let proxy_addr = self.proxy_addr;
|
type Response = TcpStream;
|
||||||
let fut = async move {
|
type Error = io::Error;
|
||||||
let host = url
|
type Future = Pin<Box<dyn Future<Output = io::Result<TcpStream>> + Send>>;
|
||||||
.host()
|
|
||||||
.ok_or_else(|| io::Error::new(io::ErrorKind::InvalidInput, "Host is missing"))?;
|
fn poll_ready(&mut self, _: &mut std::task::Context<'_>) -> Poll<io::Result<()>> {
|
||||||
let port = url
|
Poll::Ready(Ok(()))
|
||||||
.port()
|
}
|
||||||
.ok_or_else(|| io::Error::new(io::ErrorKind::InvalidInput, "Port is missing"))?;
|
|
||||||
|
fn call(&mut self, url: Uri) -> Self::Future {
|
||||||
let conn = TcpStream::connect(proxy_addr).await?;
|
let proxy_addr = self.proxy_addr;
|
||||||
connect(conn, host, port.as_u16()).await
|
let fut = async move {
|
||||||
};
|
let host = url
|
||||||
|
.host()
|
||||||
Box::pin(fut)
|
.ok_or_else(|| io::Error::new(io::ErrorKind::InvalidInput, "Host is missing"))?;
|
||||||
|
let port = url
|
||||||
|
.port()
|
||||||
|
.ok_or_else(|| io::Error::new(io::ErrorKind::InvalidInput, "Port is missing"))?;
|
||||||
|
|
||||||
|
let conn = TcpStream::connect(proxy_addr).await?;
|
||||||
|
connect(conn, host, port.as_u16()).await
|
||||||
|
};
|
||||||
|
|
||||||
|
Box::pin(fut)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue