mirror of
https://github.com/librespot-org/librespot.git
synced 2024-12-18 17:11:53 +00:00
Keep using the same hyper client
- Keep using the same hyper client instead of building a new one for each request - This allows the client to reuse connections and improves the performance of multiple requests by almost 2x. - The playlist_tracks example takes 38 secs before and 20 secs after the change to enumerate a 180 track playlist - To avoid carrying the hyper Client generics through the whole project, `ProxyConnector` is always used as the Connector, but disabled when not using a proxy. - The client creation is done lazily to keep the `HttpClient::new` without a `Result` return type
This commit is contained in:
parent
35f633c93a
commit
b588d9fd07
1 changed files with 33 additions and 22 deletions
|
@ -2,7 +2,7 @@ use std::env::consts::OS;
|
||||||
|
|
||||||
use bytes::Bytes;
|
use bytes::Bytes;
|
||||||
use futures_util::{future::IntoStream, FutureExt};
|
use futures_util::{future::IntoStream, FutureExt};
|
||||||
use http::header::HeaderValue;
|
use http::{header::HeaderValue, Uri};
|
||||||
use hyper::{
|
use hyper::{
|
||||||
client::{HttpConnector, ResponseFuture},
|
client::{HttpConnector, ResponseFuture},
|
||||||
header::USER_AGENT,
|
header::USER_AGENT,
|
||||||
|
@ -10,6 +10,7 @@ use hyper::{
|
||||||
};
|
};
|
||||||
use hyper_proxy::{Intercept, Proxy, ProxyConnector};
|
use hyper_proxy::{Intercept, Proxy, ProxyConnector};
|
||||||
use hyper_rustls::{HttpsConnector, HttpsConnectorBuilder};
|
use hyper_rustls::{HttpsConnector, HttpsConnectorBuilder};
|
||||||
|
use once_cell::sync::OnceCell;
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
|
@ -70,15 +71,17 @@ impl From<HttpClientError> for Error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type HyperClient = Client<ProxyConnector<HttpsConnector<HttpConnector>>, Body>;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct HttpClient {
|
pub struct HttpClient {
|
||||||
user_agent: HeaderValue,
|
user_agent: HeaderValue,
|
||||||
proxy: Option<Url>,
|
proxy_url: Option<Url>,
|
||||||
https_connector: HttpsConnector<HttpConnector>,
|
hyper_client: OnceCell<HyperClient>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl HttpClient {
|
impl HttpClient {
|
||||||
pub fn new(proxy: Option<&Url>) -> Self {
|
pub fn new(proxy_url: Option<&Url>) -> Self {
|
||||||
let spotify_version = match OS {
|
let spotify_version = match OS {
|
||||||
"android" | "ios" => SPOTIFY_MOBILE_VERSION.to_owned(),
|
"android" | "ios" => SPOTIFY_MOBILE_VERSION.to_owned(),
|
||||||
_ => SPOTIFY_VERSION.to_string(),
|
_ => SPOTIFY_VERSION.to_string(),
|
||||||
|
@ -102,6 +105,14 @@ impl HttpClient {
|
||||||
HeaderValue::from_static(FALLBACK_USER_AGENT)
|
HeaderValue::from_static(FALLBACK_USER_AGENT)
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Self {
|
||||||
|
user_agent,
|
||||||
|
proxy_url: proxy_url.cloned(),
|
||||||
|
hyper_client: OnceCell::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn try_create_hyper_client(proxy_url: Option<&Url>) -> Result<HyperClient, Error> {
|
||||||
// configuring TLS is expensive and should be done once per process
|
// configuring TLS is expensive and should be done once per process
|
||||||
let https_connector = HttpsConnectorBuilder::new()
|
let https_connector = HttpsConnectorBuilder::new()
|
||||||
.with_native_roots()
|
.with_native_roots()
|
||||||
|
@ -110,11 +121,23 @@ impl HttpClient {
|
||||||
.enable_http2()
|
.enable_http2()
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
Self {
|
// When not using a proxy a dummy proxy is configured that will not intercept any traffic.
|
||||||
user_agent,
|
// This prevents needing to carry the Client Connector generics through the whole project
|
||||||
proxy: proxy.cloned(),
|
let proxy = match &proxy_url {
|
||||||
https_connector,
|
Some(proxy_url) => Proxy::new(Intercept::All, proxy_url.to_string().parse()?),
|
||||||
}
|
None => Proxy::new(Intercept::None, Uri::from_static("0.0.0.0")),
|
||||||
|
};
|
||||||
|
let proxy_connector = ProxyConnector::from_proxy(https_connector, proxy)?;
|
||||||
|
|
||||||
|
let client = Client::builder()
|
||||||
|
.http2_adaptive_window(true)
|
||||||
|
.build(proxy_connector);
|
||||||
|
Ok(client)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn hyper_client(&self) -> Result<&HyperClient, Error> {
|
||||||
|
self.hyper_client
|
||||||
|
.get_or_try_init(|| Self::try_create_hyper_client(self.proxy_url.as_ref()))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn request(&self, req: Request<Body>) -> Result<Response<Body>, Error> {
|
pub async fn request(&self, req: Request<Body>) -> Result<Response<Body>, Error> {
|
||||||
|
@ -146,19 +169,7 @@ impl HttpClient {
|
||||||
let headers_mut = req.headers_mut();
|
let headers_mut = req.headers_mut();
|
||||||
headers_mut.insert(USER_AGENT, self.user_agent.clone());
|
headers_mut.insert(USER_AGENT, self.user_agent.clone());
|
||||||
|
|
||||||
let request = if let Some(url) = &self.proxy {
|
let request = self.hyper_client()?.request(req);
|
||||||
let proxy_uri = url.to_string().parse()?;
|
|
||||||
let proxy = Proxy::new(Intercept::All, proxy_uri);
|
|
||||||
let proxy_connector = ProxyConnector::from_proxy(self.https_connector.clone(), proxy)?;
|
|
||||||
|
|
||||||
Client::builder().build(proxy_connector).request(req)
|
|
||||||
} else {
|
|
||||||
Client::builder()
|
|
||||||
.http2_adaptive_window(true)
|
|
||||||
.build(self.https_connector.clone())
|
|
||||||
.request(req)
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(request)
|
Ok(request)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue