Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -451,8 +451,8 @@ tokio-test = { default-features = false, version = "0.4" }

# Local packages used as dependencies.
google-cloud-auth = { default-features = false, version = "1.5", path = "src/auth" }
gax = { default-features = false, version = "1.6", path = "src/gax", package = "google-cloud-gax" }
google-cloud-gax = { default-features = false, version = "1.6", path = "src/gax" }
gax = { default-features = false, version = "1.7", path = "src/gax", package = "google-cloud-gax" }
google-cloud-gax = { default-features = false, version = "1.7", path = "src/gax" }
Comment on lines +454 to +455
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
gax = { default-features = false, version = "1.7", path = "src/gax", package = "google-cloud-gax" }
google-cloud-gax = { default-features = false, version = "1.7", path = "src/gax" }
google-cloud-gax = { default-features = false, version = "1.7", path = "src/gax" }

gaxi = { default-features = false, version = "0.7.9", path = "src/gax-internal", package = "google-cloud-gax-internal" }
wkt = { default-features = false, version = "1", path = "src/wkt", package = "google-cloud-wkt" }
google-cloud-wkt = { default-features = false, version = "1", path = "src/wkt", package = "google-cloud-wkt" }
Expand Down
54 changes: 51 additions & 3 deletions src/gax-internal/src/grpc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ pub struct Client {
retry_throttler: SharedRetryThrottler,
polling_error_policy: Arc<dyn PollingErrorPolicy>,
polling_backoff_policy: Arc<dyn PollingBackoffPolicy>,
user_agent: Option<String>,
}

impl Client {
Expand Down Expand Up @@ -136,6 +137,7 @@ impl Client {
polling_backoff_policy: config
.polling_backoff_policy
.unwrap_or_else(|| Arc::new(ExponentialBackoff::default())),
user_agent: config.user_agent,
})
}

Expand All @@ -153,7 +155,9 @@ impl Client {
Request: prost::Message + Clone + 'static,
Response: prost::Message + Default + 'static,
{
let headers = Self::make_headers(api_client_header, request_params, &options).await?;
let headers = self
.make_headers(api_client_header, request_params, &options)
.await?;
self.retry_loop::<Request, Response>(extensions, path, request, options, headers)
.await
}
Expand Down Expand Up @@ -203,7 +207,9 @@ impl Client {
Response: prost::Message + Default + 'static,
{
use ::tonic::IntoStreamingRequest;
let headers = Self::make_headers(api_client_header, request_params, &options).await?;
let headers = self
.make_headers(api_client_header, request_params, &options)
.await?;
let headers = self.add_auth_headers(headers).await?;
let metadata = tonic::MetadataMap::from_headers(headers);
let request = ::tonic::Request::from_parts(metadata, extensions, request);
Expand Down Expand Up @@ -415,12 +421,18 @@ impl Client {
}

async fn make_headers(
&self,
api_client_header: &'static str,
request_params: &str,
options: &RequestOptions,
) -> Result<http::header::HeaderMap> {
let mut headers = HeaderMap::new();
if let Some(user_agent) = options.user_agent() {
let user_agent = options
.user_agent()
.as_deref()
.or(self.user_agent.as_deref());

if let Some(user_agent) = user_agent {
headers.append(
http::header::USER_AGENT,
http::header::HeaderValue::from_str(user_agent).map_err(Error::ser)?,
Expand Down Expand Up @@ -522,3 +534,39 @@ mod tests {
// but this verifies the method exists and runs.
}
}

#[cfg(test)]
mod headers_tests {
use super::*;

#[tokio::test]
async fn test_user_agent_headers() {
let api_client = "api-client";
let client_agent = "client-agent/1.0.0";
let request_agent = "request-agent/1.0.0";
let mut config = crate::options::ClientConfig::default();
config.user_agent = Some(client_agent.to_string());
let client = Client::new(config, "http://example.com").await.unwrap();

// Test with client agent only
let options = RequestOptions::default();
let headers = client.make_headers(api_client, "", &options).await.unwrap();
let agents: Vec<_> = headers
.get_all(http::header::USER_AGENT)
.iter()
.map(|v| v.to_str().unwrap())
.collect();
assert_eq!(agents, vec![client_agent]);

// Test that request agent overrides client agent
let mut options = RequestOptions::default();
options.set_user_agent(request_agent);
let headers = client.make_headers(api_client, "", &options).await.unwrap();
let agents: Vec<_> = headers
.get_all(http::header::USER_AGENT)
.iter()
.map(|v| v.to_str().unwrap())
.collect();
assert_eq!(agents, vec![request_agent]);
}
}
63 changes: 62 additions & 1 deletion src/gax-internal/src/http.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ pub struct ReqwestClient {
retry_throttler: SharedRetryThrottler,
polling_error_policy: Arc<dyn PollingErrorPolicy>,
polling_backoff_policy: Arc<dyn PollingBackoffPolicy>,
user_agent: Option<String>,
instrumentation: Option<&'static crate::options::InstrumentationClientInfo>,
_tracing_enabled: bool,
}
Expand All @@ -86,6 +87,7 @@ impl ReqwestClient {
if config.disable_follow_redirects {
builder = builder.redirect(::reqwest::redirect::Policy::none());
}

let inner = builder.build().map_err(BuilderError::transport)?;
let host = crate::host::from_endpoint(
config.endpoint.as_deref(),
Expand Down Expand Up @@ -118,6 +120,7 @@ impl ReqwestClient {
polling_backoff_policy: config
.polling_backoff_policy
.unwrap_or_else(|| Arc::new(ExponentialBackoff::default())),
user_agent: config.user_agent,
instrumentation: None,
_tracing_enabled: tracing_enabled,
})
Expand Down Expand Up @@ -197,7 +200,12 @@ impl ReqwestClient {
mut builder: reqwest::RequestBuilder,
options: &RequestOptions,
) -> Result<reqwest::RequestBuilder> {
if let Some(user_agent) = options.user_agent() {
let user_agent = options
.user_agent()
.as_deref()
.or(self.user_agent.as_deref());

if let Some(user_agent) = user_agent {
builder = builder.header(
::reqwest::header::USER_AGENT,
reqwest::HeaderValue::from_str(user_agent).map_err(Error::ser)?,
Expand Down Expand Up @@ -786,4 +794,57 @@ mod tests {
assert_eq!(result.err().unwrap().http_status_code(), Some(308));
Ok(())
}

#[tokio::test]
Comment thread
joshuatants marked this conversation as resolved.
async fn test_user_agent_header() -> TestResult {
let client_agent = "client-agent/1.0.0";
let request_agent = "request-agent/1.0.0";

let server = httptest::Server::run();
server.expect(
httptest::Expectation::matching(httptest::matchers::all_of![
httptest::matchers::request::method_path("GET", "/foo"),
httptest::matchers::request::headers(httptest::matchers::contains((
"user-agent",
client_agent
))),
])
.times(1)
.respond_with(httptest::responders::status_code(200)),
);
server.expect(
httptest::Expectation::matching(httptest::matchers::all_of![
httptest::matchers::request::method_path("GET", "/foo"),
httptest::matchers::request::headers(httptest::matchers::contains((
"user-agent",
request_agent
))),
])
.times(1)
.respond_with(httptest::responders::status_code(200)),
);

let mut config = ClientConfig::default();
config.cred = Some(Anonymous::new().build());
config.user_agent = Some(client_agent.to_string());

let client = ReqwestClient::new(config, &server.url_str("/")).await?;

// Test with client agent only
let builder = client.builder(Method::GET, "foo".to_string());
let options = RequestOptions::default();
let _ = client
.execute_streaming_once(builder, options, None, 0)
.await?;

// Test that request agent overrides client agent
let builder = client.builder(Method::GET, "foo".to_string());
let mut options = RequestOptions::default();
options.set_user_agent(request_agent);
let _ = client
.execute_streaming_once(builder, options, None, 0)
.await?;

Ok(())
}
}
33 changes: 33 additions & 0 deletions src/gax/src/client_builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,25 @@ impl<F, Cr> ClientBuilder<F, Cr> {
self
}

/// Sets the user-agent.
///
/// The user-agent header is set in all requests made by the client.
///
/// ```
/// # use google_cloud_gax::client_builder::examples;
/// # use google_cloud_gax::client_builder::Result;
/// # tokio_test::block_on(async {
/// use examples::Client; // Placeholder for examples
/// let client = Client::builder()
/// .with_user_agent("my-app/1.0.0")
/// .build().await?;
/// # Result::<()>::Ok(()) });
/// ```
pub fn with_user_agent<V: Into<String>>(mut self, v: V) -> Self {
Comment thread
joshuatants marked this conversation as resolved.
self.config.user_agent = Some(v.into());
self
}

/// Enables tracing.
///
/// The client libraries can be dynamically instrumented with the Tokio
Expand Down Expand Up @@ -427,6 +446,7 @@ pub mod internal {
pub disable_follow_redirects: bool,
pub grpc_subchannel_count: Option<usize>,
pub grpc_request_buffer_capacity: Option<usize>,
pub user_agent: Option<String>,
}

impl<Cr> std::default::Default for ClientConfig<Cr> {
Expand All @@ -446,6 +466,7 @@ pub mod internal {
disable_follow_redirects: false,
grpc_subchannel_count: None,
grpc_request_buffer_capacity: None,
user_agent: None,
}
}
}
Expand Down Expand Up @@ -705,6 +726,18 @@ pub mod examples {
let config = client.0;
assert!(config.polling_backoff_policy.is_some(), "{config:?}");
}

#[tokio::test]
async fn user_agent() {
let agent = "test-agent/1.0.0";
let client = Client::builder()
.with_user_agent(agent)
.build()
.await
.unwrap();
let config = client.0;
assert_eq!(config.user_agent, Some(agent.to_string()));
}
}
}

Expand Down
29 changes: 29 additions & 0 deletions src/storage/src/storage/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -421,6 +421,25 @@ impl ClientBuilder {
self
}

/// Sets the user-agent.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To achieve parity with the other client libraries you also need to add with_user_agent to the request builders, including:

impl<S> ReadObject<S>
where
S: crate::storage::stub::Storage + 'static,
{

impl<T, S> WriteObject<T, S>
where
S: crate::storage::stub::Storage + 'static,

and

impl<S> OpenObject<S>
where
S: crate::storage::stub::Storage + 'static,

///
/// The user-agent header is set in all requests made by the client.
///
/// # Example
/// ```
/// # use google_cloud_storage::client::Storage;
/// # async fn sample() -> anyhow::Result<()> {
/// let client = Storage::builder()
/// .with_user_agent("my-app/1.0.0")
/// .build()
/// .await?;
/// # Ok(()) }
/// ```
pub fn with_user_agent<V: Into<String>>(mut self, v: V) -> Self {
self.config.user_agent = Some(v.into());
self
}

/// Configures the authentication credentials.
///
/// Google Cloud Storage requires authentication for most buckets. Use this
Expand Down Expand Up @@ -783,6 +802,16 @@ pub(crate) mod tests {
);
}

#[test]
fn user_agent() {
let agent = "test-agent/1.0.0";
let builder = ClientBuilder::new()
.with_credentials(Anonymous::new().build())
.with_user_agent(agent);
let config = builder.config;
assert_eq!(config.user_agent, Some(agent.to_string()));
}

pub(crate) fn test_builder() -> ClientBuilder {
ClientBuilder::new()
.with_credentials(Anonymous::new().build())
Expand Down
Loading