Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add uv version to user agent #2136

Merged
merged 11 commits into from
Mar 4, 2024
108 changes: 95 additions & 13 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions crates/uv-client/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ pep508_rs = { path = "../pep508-rs" }
platform-tags = { path = "../platform-tags" }
uv-auth = { path = "../uv-auth" }
uv-cache = { path = "../uv-cache" }
uv-config = { path = "../uv-config" }
uv-fs = { path = "../uv-fs", features = ["tokio"] }
uv-normalize = { path = "../uv-normalize" }
uv-warnings = { path = "../uv-warnings" }
Expand Down Expand Up @@ -47,5 +48,8 @@ urlencoding = { workspace = true }

[dev-dependencies]
anyhow = { workspace = true }
http-body-util = { version = "0.1.0" }
hyper = { version = "1.2.0", features = ["server", "http1"] }
hyper-util = { version = "0.1.3", features = ["tokio"] }
insta = { version = "1.35.1" }
tokio = { workspace = true, features = ["fs", "macros"] }
21 changes: 13 additions & 8 deletions crates/uv-client/src/registry_client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,23 +17,23 @@ use tokio_util::compat::FuturesAsyncReadCompatExt;
use tracing::{debug, info_span, instrument, trace, warn, Instrument};
use url::Url;

use crate::cached_client::CacheControl;
use crate::html::SimpleHtml;
use crate::middleware::OfflineMiddleware;
use crate::remote_metadata::wheel_metadata_from_remote_zip;
use crate::rkyvutil::OwnedArchive;
use crate::{CachedClient, CachedClientError, Error, ErrorKind};
use distribution_filename::{DistFilename, SourceDistFilename, WheelFilename};
use distribution_types::{BuiltDist, File, FileLocation, IndexUrl, IndexUrls, Name};
use install_wheel_rs::{find_dist_info, is_metadata_entry};
use pep440_rs::Version;
use pypi_types::{Metadata21, SimpleJson};
use uv_auth::safe_copy_url_auth;
use uv_cache::{Cache, CacheBucket, WheelCache};
use uv_config::GlobalConfig;
use uv_normalize::PackageName;
use uv_warnings::warn_user_once;

use crate::cached_client::CacheControl;
use crate::html::SimpleHtml;
use crate::middleware::OfflineMiddleware;
use crate::remote_metadata::wheel_metadata_from_remote_zip;
use crate::rkyvutil::OwnedArchive;
use crate::{CachedClient, CachedClientError, Error, ErrorKind};

/// A builder for an [`RegistryClient`].
#[derive(Debug, Clone)]
pub struct RegistryClientBuilder {
Expand Down Expand Up @@ -88,6 +88,11 @@ impl RegistryClientBuilder {
}

pub fn build(self) -> RegistryClient {
// Retrieve Settings
let user_agent_string = GlobalConfig::settings()
.map(|cfg| format!("uv/{}", cfg.version))
.unwrap_or_else(|_| "uv".to_string());

// Timeout options, matching https://doc.rust-lang.org/nightly/cargo/reference/config.html#httptimeout
// `UV_REQUEST_TIMEOUT` is provided for backwards compatibility with v0.1.6
let default_timeout = 5 * 60;
Expand All @@ -108,7 +113,7 @@ impl RegistryClientBuilder {
let client_raw = self.client.unwrap_or_else(|| {
// Disallow any connections.
let client_core = ClientBuilder::new()
.user_agent("uv")
.user_agent(user_agent_string)
.pool_max_idle_per_host(20)
.timeout(std::time::Duration::from_secs(timeout));

Expand Down
73 changes: 73 additions & 0 deletions crates/uv-client/tests/user_agent_version.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
use anyhow::Result;
use futures::future;
use http_body_util::Full;
use hyper::body::Bytes;
use hyper::header::USER_AGENT;
use hyper::server::conn::http1;
use hyper::service::service_fn;
use hyper::{Request, Response};
use hyper_util::rt::TokioIo;
use tokio::net::TcpListener;

use uv_cache::Cache;
use uv_client::RegistryClientBuilder;
use uv_config::GlobalConfig;

#[tokio::test]
async fn test_user_agent_has_version() -> Result<()> {
// Set up the TCP listener on a random available port
let listener = TcpListener::bind("127.0.0.1:0").await?;
let addr = listener.local_addr()?;

// Spawn the server loop in a background task
let server_task = tokio::spawn(async move {
let svc = service_fn(move |req: Request<hyper::body::Incoming>| {
// Get User Agent Header and send it back in the response
let user_agent = req
.headers()
.get(USER_AGENT)
.and_then(|v| v.to_str().ok())
.map(|s| s.to_string())
.unwrap_or_default(); // Empty Default
future::ok::<_, hyper::Error>(Response::new(Full::new(Bytes::from(user_agent))))
});
// Start Server (not wrapped in loop {} since we want a single response server)
// If you want server to accept multiple connections, wrap it in loop {}
let (socket, _) = listener.accept().await.unwrap();
let socket = TokioIo::new(socket);
tokio::task::spawn(async move {
http1::Builder::new()
.serve_connection(socket, svc)
.with_upgrades()
.await
.expect("Server Started");
});
});

// Initialize uv-client
let cache = Cache::temp()?;
let client = RegistryClientBuilder::new(cache).build();

// Send request to our dummy server
let res = client
.cached_client()
.uncached()
.get(format!("http://{addr}"))
.send()
.await?;

// Check the HTTP status
assert!(res.status().is_success());

// Check User Agent
let version = GlobalConfig::settings().unwrap().version;
let body = res.text().await?;

// Verify body matches regex
assert_eq!(body, format!("uv/{version}"));

// Wait for the server task to complete, to be a good citizen.
server_task.await?;

Ok(())
}
17 changes: 17 additions & 0 deletions crates/uv-config/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
[package]
name = "uv-config"
version = "0.0.1"
edition = { workspace = true }
rust-version = { workspace = true }
homepage = { workspace = true }
documentation = { workspace = true }
repository = { workspace = true }
authors = { workspace = true }
license = { workspace = true }

[lints]
workspace = true

[dependencies]
anyhow = { workspace = true }
once_cell = { workspace = true }
Loading