mirror of
https://github.com/librespot-org/librespot.git
synced 2025-10-03 01:39:28 +02:00
Implement CDN for audio files
This commit is contained in:
parent
9a93cca562
commit
2f7b9863d9
23 changed files with 518 additions and 251 deletions
|
@ -1,10 +1,14 @@
|
|||
use bytes::Bytes;
|
||||
use futures_util::future::IntoStream;
|
||||
use futures_util::FutureExt;
|
||||
use http::header::HeaderValue;
|
||||
use http::uri::InvalidUri;
|
||||
use hyper::header::InvalidHeaderValue;
|
||||
use hyper::client::{HttpConnector, ResponseFuture};
|
||||
use hyper::header::{InvalidHeaderValue, USER_AGENT};
|
||||
use hyper::{Body, Client, Request, Response, StatusCode};
|
||||
use hyper_proxy::{Intercept, Proxy, ProxyConnector};
|
||||
use hyper_rustls::HttpsConnector;
|
||||
use rustls::ClientConfig;
|
||||
use std::env::consts::OS;
|
||||
use thiserror::Error;
|
||||
use url::Url;
|
||||
|
@ -13,6 +17,7 @@ use crate::version::{SPOTIFY_MOBILE_VERSION, SPOTIFY_VERSION, VERSION_STRING};
|
|||
|
||||
pub struct HttpClient {
|
||||
proxy: Option<Url>,
|
||||
tls_config: ClientConfig,
|
||||
}
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
|
@ -43,15 +48,60 @@ impl From<InvalidUri> for HttpClientError {
|
|||
|
||||
impl HttpClient {
|
||||
pub fn new(proxy: Option<&Url>) -> Self {
|
||||
// configuring TLS is expensive and should be done once per process
|
||||
let root_store = match rustls_native_certs::load_native_certs() {
|
||||
Ok(store) => store,
|
||||
Err((Some(store), err)) => {
|
||||
warn!("Could not load all certificates: {:?}", err);
|
||||
store
|
||||
}
|
||||
Err((None, err)) => Err(err).expect("cannot access native cert store"),
|
||||
};
|
||||
|
||||
let mut tls_config = ClientConfig::new();
|
||||
tls_config.root_store = root_store;
|
||||
tls_config.alpn_protocols = vec![b"h2".to_vec(), b"http/1.1".to_vec()];
|
||||
|
||||
Self {
|
||||
proxy: proxy.cloned(),
|
||||
tls_config,
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn request(&self, mut req: Request<Body>) -> Result<Response<Body>, HttpClientError> {
|
||||
pub async fn request(&self, req: Request<Body>) -> Result<Response<Body>, HttpClientError> {
|
||||
let request = self.request_fut(req)?;
|
||||
{
|
||||
let response = request.await;
|
||||
if let Ok(response) = &response {
|
||||
let status = response.status();
|
||||
if status != StatusCode::OK {
|
||||
return Err(HttpClientError::NotOK(status.into()));
|
||||
}
|
||||
}
|
||||
response.map_err(HttpClientError::Response)
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn request_body(&self, req: Request<Body>) -> Result<Bytes, HttpClientError> {
|
||||
let response = self.request(req).await?;
|
||||
hyper::body::to_bytes(response.into_body())
|
||||
.await
|
||||
.map_err(HttpClientError::Response)
|
||||
}
|
||||
|
||||
pub fn request_stream(
|
||||
&self,
|
||||
req: Request<Body>,
|
||||
) -> Result<IntoStream<ResponseFuture>, HttpClientError> {
|
||||
Ok(self.request_fut(req)?.into_stream())
|
||||
}
|
||||
|
||||
pub fn request_fut(&self, mut req: Request<Body>) -> Result<ResponseFuture, HttpClientError> {
|
||||
trace!("Requesting {:?}", req.uri().to_string());
|
||||
|
||||
let connector = HttpsConnector::with_native_roots();
|
||||
let mut http = HttpConnector::new();
|
||||
http.enforce_http(false);
|
||||
let connector = HttpsConnector::from((http, self.tls_config.clone()));
|
||||
|
||||
let spotify_version = match OS {
|
||||
"android" | "ios" => SPOTIFY_MOBILE_VERSION.to_owned(),
|
||||
|
@ -68,7 +118,7 @@ impl HttpClient {
|
|||
|
||||
let headers_mut = req.headers_mut();
|
||||
headers_mut.insert(
|
||||
"User-Agent",
|
||||
USER_AGENT,
|
||||
// Some features like lyrics are version-gated and require an official version string.
|
||||
HeaderValue::from_str(&format!(
|
||||
"Spotify/{} {} ({})",
|
||||
|
@ -76,38 +126,16 @@ impl HttpClient {
|
|||
))?,
|
||||
);
|
||||
|
||||
let response = if let Some(url) = &self.proxy {
|
||||
let request = if let Some(url) = &self.proxy {
|
||||
let proxy_uri = url.to_string().parse()?;
|
||||
let proxy = Proxy::new(Intercept::All, proxy_uri);
|
||||
let proxy_connector = ProxyConnector::from_proxy(connector, proxy)?;
|
||||
|
||||
Client::builder()
|
||||
.build(proxy_connector)
|
||||
.request(req)
|
||||
.await
|
||||
.map_err(HttpClientError::Request)
|
||||
Client::builder().build(proxy_connector).request(req)
|
||||
} else {
|
||||
Client::builder()
|
||||
.build(connector)
|
||||
.request(req)
|
||||
.await
|
||||
.map_err(HttpClientError::Request)
|
||||
Client::builder().build(connector).request(req)
|
||||
};
|
||||
|
||||
if let Ok(response) = &response {
|
||||
let status = response.status();
|
||||
if status != StatusCode::OK {
|
||||
return Err(HttpClientError::NotOK(status.into()));
|
||||
}
|
||||
}
|
||||
|
||||
response
|
||||
}
|
||||
|
||||
pub async fn request_body(&self, req: Request<Body>) -> Result<Bytes, HttpClientError> {
|
||||
let response = self.request(req).await?;
|
||||
hyper::body::to_bytes(response.into_body())
|
||||
.await
|
||||
.map_err(HttpClientError::Response)
|
||||
Ok(request)
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue