2021-11-27 07:30:51 +00:00
|
|
|
use bytes::Bytes;
|
|
|
|
use http::header::HeaderValue;
|
|
|
|
use http::uri::InvalidUri;
|
|
|
|
use hyper::header::InvalidHeaderValue;
|
2021-11-26 22:21:27 +00:00
|
|
|
use hyper::{Body, Client, Request, Response, StatusCode};
|
2021-06-20 21:09:27 +00:00
|
|
|
use hyper_proxy::{Intercept, Proxy, ProxyConnector};
|
2021-11-26 22:21:27 +00:00
|
|
|
use hyper_rustls::HttpsConnector;
|
|
|
|
use thiserror::Error;
|
2021-06-20 21:09:27 +00:00
|
|
|
use url::Url;
|
|
|
|
|
|
|
|
pub struct HttpClient {
|
|
|
|
proxy: Option<Url>,
|
|
|
|
}
|
|
|
|
|
2021-11-26 22:21: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),
|
2021-11-26 22:21:27 +00:00
|
|
|
#[error("could not send request: {0}")]
|
|
|
|
Request(hyper::Error),
|
|
|
|
#[error("could not read response: {0}")]
|
|
|
|
Response(hyper::Error),
|
2021-11-27 09:41:54 +00:00
|
|
|
#[error("status code: {0}")]
|
|
|
|
NotOK(u16),
|
2021-11-26 22:21:27 +00:00
|
|
|
#[error("could not build proxy connector: {0}")]
|
|
|
|
ProxyBuilder(#[from] std::io::Error),
|
|
|
|
}
|
|
|
|
|
2021-11-27 07:30:51 +00:00
|
|
|
impl From<InvalidHeaderValue> for HttpClientError {
|
|
|
|
fn from(err: InvalidHeaderValue) -> Self {
|
|
|
|
Self::Parsing(err.into())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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 {
|
|
|
|
Self {
|
|
|
|
proxy: proxy.cloned(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-11-27 07:30:51 +00:00
|
|
|
pub async fn request(&self, mut req: Request<Body>) -> Result<Response<Body>, HttpClientError> {
|
2021-11-27 09:41:54 +00:00
|
|
|
trace!("Requesting {:?}", req.uri().to_string());
|
|
|
|
|
2021-11-26 22:21:27 +00:00
|
|
|
let connector = HttpsConnector::with_native_roots();
|
|
|
|
|
2021-11-27 07:30:51 +00:00
|
|
|
let headers_mut = req.headers_mut();
|
|
|
|
headers_mut.insert(
|
|
|
|
"User-Agent",
|
2021-11-27 09:41:54 +00:00
|
|
|
// Some features like lyrics are version-gated and require an official version string.
|
2021-11-27 07:30:51 +00:00
|
|
|
HeaderValue::from_str("Spotify/8.6.80 iOS/13.5 (iPhone11,2)")?,
|
|
|
|
);
|
|
|
|
|
2021-11-26 22:21:27 +00:00
|
|
|
let response = if let Some(url) = &self.proxy {
|
2021-11-27 09:41:54 +00:00
|
|
|
let proxy_uri = url.to_string().parse()?;
|
|
|
|
let proxy = Proxy::new(Intercept::All, proxy_uri);
|
2021-11-26 22:21:27 +00:00
|
|
|
let proxy_connector = ProxyConnector::from_proxy(connector, proxy)?;
|
|
|
|
|
|
|
|
Client::builder()
|
|
|
|
.build(proxy_connector)
|
|
|
|
.request(req)
|
|
|
|
.await
|
|
|
|
.map_err(HttpClientError::Request)
|
2021-06-20 21:09:27 +00:00
|
|
|
} else {
|
2021-11-26 22:21:27 +00:00
|
|
|
Client::builder()
|
|
|
|
.build(connector)
|
|
|
|
.request(req)
|
|
|
|
.await
|
|
|
|
.map_err(HttpClientError::Request)
|
|
|
|
};
|
|
|
|
|
|
|
|
if let Ok(response) = &response {
|
2021-11-27 09:41:54 +00:00
|
|
|
let status = response.status();
|
|
|
|
if status != StatusCode::OK {
|
|
|
|
return Err(HttpClientError::NotOK(status.into()));
|
2021-11-26 22:21:27 +00:00
|
|
|
}
|
2021-06-20 21:09:27 +00:00
|
|
|
}
|
2021-11-26 22:21:27 +00:00
|
|
|
|
|
|
|
response
|
2021-06-20 21:09:27 +00:00
|
|
|
}
|
|
|
|
|
2021-11-27 07:30:51 +00:00
|
|
|
pub async fn request_body(&self, req: Request<Body>) -> Result<Bytes, HttpClientError> {
|
2021-06-20 21:09:27 +00:00
|
|
|
let response = self.request(req).await?;
|
2021-11-26 22:21:27 +00:00
|
|
|
hyper::body::to_bytes(response.into_body())
|
|
|
|
.await
|
|
|
|
.map_err(HttpClientError::Response)
|
2021-06-20 21:09:27 +00:00
|
|
|
}
|
|
|
|
}
|