librespot/core/src/http_client.rs

147 lines
4.8 KiB
Rust
Raw Normal View History

2021-11-27 07:30:51 +00:00
use bytes::Bytes;
2021-12-16 21:42:37 +00:00
use futures_util::future::IntoStream;
use futures_util::FutureExt;
2021-11-27 07:30:51 +00:00
use http::header::HeaderValue;
use http::uri::InvalidUri;
2021-12-16 21:42:37 +00:00
use hyper::client::{HttpConnector, ResponseFuture};
2021-12-18 11:39:16 +00:00
use hyper::header::USER_AGENT;
use hyper::{Body, Client, Request, Response, StatusCode};
2021-06-20 21:09:27 +00:00
use hyper_proxy::{Intercept, Proxy, ProxyConnector};
use hyper_rustls::HttpsConnector;
2021-12-18 11:39:16 +00:00
use rustls::{ClientConfig, RootCertStore};
use thiserror::Error;
2021-06-20 21:09:27 +00:00
use url::Url;
2021-12-18 11:39:16 +00:00
use std::env::consts::OS;
2021-12-11 15:43:34 +00:00
use crate::version::{SPOTIFY_MOBILE_VERSION, SPOTIFY_VERSION, VERSION_STRING};
2021-06-20 21:09:27 +00:00
pub struct HttpClient {
2021-12-18 11:39:16 +00:00
user_agent: HeaderValue,
2021-06-20 21:09:27 +00:00
proxy: Option<Url>,
2021-12-16 21:42:37 +00:00
tls_config: ClientConfig,
2021-06-20 21:09:27 +00:00
}
#[derive(Error, Debug)]
pub enum HttpClientError {
#[error("could not parse request: {0}")]
2021-11-27 07:30:51 +00:00
Parsing(#[from] http::Error),
#[error("could not send request: {0}")]
Request(hyper::Error),
#[error("could not read response: {0}")]
Response(hyper::Error),
#[error("status code: {0}")]
NotOK(u16),
#[error("could not build proxy connector: {0}")]
ProxyBuilder(#[from] std::io::Error),
}
2021-11-27 07:30:51 +00:00
impl From<InvalidUri> for HttpClientError {
fn from(err: InvalidUri) -> Self {
Self::Parsing(err.into())
}
}
2021-06-20 21:09:27 +00:00
impl HttpClient {
pub fn new(proxy: Option<&Url>) -> Self {
2021-12-18 11:39:16 +00:00
let spotify_version = match OS {
"android" | "ios" => SPOTIFY_MOBILE_VERSION.to_owned(),
_ => SPOTIFY_VERSION.to_string(),
};
let spotify_platform = match OS {
"android" => "Android/31",
"ios" => "iOS/15.1.1",
"macos" => "OSX/0",
"windows" => "Win32/0",
_ => "Linux/0",
};
let user_agent_str = &format!(
"Spotify/{} {} ({})",
spotify_version, spotify_platform, VERSION_STRING
);
let user_agent = HeaderValue::from_str(user_agent_str).unwrap_or_else(|err| {
error!("Invalid user agent <{}>: {}", user_agent_str, err);
error!("Parts of the API will probably not work. Please report this as a bug.");
HeaderValue::from_static("")
});
2021-12-16 21:42:37 +00:00
// 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
}
2021-12-18 11:39:16 +00:00
Err((None, err)) => {
error!("Cannot access native certificate store: {}", err);
error!("Continuing, but most requests will probably fail until you fix your system certificate store.");
RootCertStore::empty()
}
2021-12-16 21:42:37 +00:00
};
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()];
2021-06-20 21:09:27 +00:00
Self {
2021-12-18 11:39:16 +00:00
user_agent,
2021-06-20 21:09:27 +00:00
proxy: proxy.cloned(),
2021-12-16 21:42:37 +00:00
tls_config,
}
}
pub async fn request(&self, req: Request<Body>) -> Result<Response<Body>, HttpClientError> {
2021-12-18 11:39:16 +00:00
debug!("Requesting {:?}", req.uri().to_string());
2021-12-16 21:42:37 +00:00
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)
2021-06-20 21:09:27 +00:00
}
}
2021-12-16 21:42:37 +00:00
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> {
let mut http = HttpConnector::new();
http.enforce_http(false);
let connector = HttpsConnector::from((http, self.tls_config.clone()));
2021-11-27 07:30:51 +00:00
let headers_mut = req.headers_mut();
2021-12-18 11:39:16 +00:00
headers_mut.insert(USER_AGENT, self.user_agent.clone());
2021-11-27 07:30:51 +00:00
2021-12-16 21:42:37 +00:00
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)?;
2021-12-16 21:42:37 +00:00
Client::builder().build(proxy_connector).request(req)
2021-06-20 21:09:27 +00:00
} else {
2021-12-16 21:42:37 +00:00
Client::builder().build(connector).request(req)
};
2021-12-16 21:42:37 +00:00
Ok(request)
2021-06-20 21:09:27 +00:00
}
}