Skip to content

Commit e76e9b0

Browse files
authored
impl(gax-internal): fallback on URL for server address (#5155)
1 parent a6e0359 commit e76e9b0

2 files changed

Lines changed: 87 additions & 12 deletions

File tree

src/gax-internal/src/observability/client_signals/recorder.rs

Lines changed: 85 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,15 @@ use crate::observability::http_tracing::sanitize_url;
1818
use crate::options::InstrumentationClientInfo;
1919
#[cfg(feature = "_internal-http-client")]
2020
use google_cloud_gax::error::Error;
21+
use http::Uri;
2122
use reqwest::Method;
2223
use std::future::Future;
2324
use std::net::SocketAddr;
2425
use std::sync::{Arc, Mutex};
2526
use tokio::time::Instant;
2627

28+
const HTTPS_PORT: u16 = 443;
29+
2730
tokio::task_local! {
2831
static RECORDER: RequestRecorder;
2932
}
@@ -300,22 +303,40 @@ impl ClientSnapshot {
300303
///
301304
/// If no address is known, use the target address from `info.default_host`.
302305
pub fn server_address(&self) -> String {
303-
self.transport_snapshot
306+
if let Some(address) = self
307+
.transport_snapshot
304308
.as_ref()
305309
.and_then(|s| s.server_address)
306310
.map(|a| a.ip().to_string())
307-
.unwrap_or_else(|| self.info.default_host.to_string())
311+
{
312+
return address;
313+
}
314+
if let Some(uri) = self.sanitized_url().and_then(|u| u.parse::<Uri>().ok()) {
315+
if let Some(host) = uri.authority().map(|a| a.host().to_string()) {
316+
return host;
317+
}
318+
}
319+
self.info.default_host.to_string()
308320
}
309321

310322
/// Returns the server port used in the last low-level request.
311323
///
312324
/// If no port is known, use the port implied by `info.default_host`.
313325
pub fn server_port(&self) -> u16 {
314-
self.transport_snapshot
326+
if let Some(port) = self
327+
.transport_snapshot
315328
.as_ref()
316329
.and_then(|s| s.server_address)
317330
.map(|a| a.port())
318-
.unwrap_or(443)
331+
{
332+
return port;
333+
}
334+
if let Some(uri) = self.sanitized_url().and_then(|u| u.parse::<Uri>().ok()) {
335+
if let Some(host) = uri.authority().and_then(|a| a.port_u16()) {
336+
return host;
337+
}
338+
}
339+
HTTPS_PORT
319340
}
320341

321342
/// Returns the URL template (e.g. "/v1/storage/b/{bucket}") used in the last low-level request.
@@ -397,6 +418,8 @@ mod tests {
397418
use httptest::matchers::request::method_path;
398419
use httptest::responders::status_code;
399420
use httptest::{Expectation, Server};
421+
use pretty_assertions::assert_eq;
422+
use std::net::{Ipv4Addr, SocketAddrV4};
400423

401424
const TEST_METHOD_NAME: &str = "google.test.v1.Service/SomeMethod";
402425
const TEST_PATH_TEMPLATE: &str = "/v42/{parent}";
@@ -490,11 +513,33 @@ mod tests {
490513

491514
assert_eq!(snap.attempt_count, 1, "{snap:?}");
492515
assert!(snap.http_status_code().is_none(), "{snap:?}");
493-
assert_eq!(
494-
snap.server_address().as_str(),
495-
TEST_INFO.default_host,
496-
"{snap:?}"
497-
);
516+
assert_eq!(snap.server_address().as_str(), "127.0.0.1", "{snap:?}");
517+
assert_eq!(snap.server_port(), 1, "{snap:?}");
518+
}
519+
520+
#[tokio::test(start_paused = true)]
521+
async fn http_bad_url() {
522+
const BAD_URL: &str = "bad-url";
523+
524+
let recorder = RequestRecorder::new(TEST_INFO);
525+
let scoped = recorder.clone();
526+
// Normally this code would be in the `tracing.rs` layer. Inline it here so we can examine the
527+
// effects on the `RequestRecorder`.
528+
let got = scoped
529+
.scope(simulate_http_client_transport_layer(BAD_URL))
530+
.await;
531+
assert!(matches!(got, Err(ref e) if e.is_io()), "{got:?}");
532+
let snap = recorder.client_snapshot();
533+
534+
assert_eq!(snap.start, Instant::now(), "{snap:?}");
535+
assert_eq!(snap.rpc_method(), Some(TEST_METHOD_NAME), "{snap:?}");
536+
assert_eq!(snap.url_template(), Some(TEST_PATH_TEMPLATE), "{snap:?}");
537+
assert!(snap.rpc_system().is_none(), "{snap:?}");
538+
assert!(snap.sanitized_url().is_none(), "{snap:?}");
539+
540+
assert_eq!(snap.attempt_count, 1, "{snap:?}");
541+
assert!(snap.http_status_code().is_none(), "{snap:?}");
542+
assert_eq!(snap.server_address().as_str(), "example.com", "{snap:?}");
498543
assert_eq!(snap.server_port(), 443, "{snap:?}");
499544
}
500545

@@ -561,4 +606,35 @@ mod tests {
561606
assert_eq!(snap.sanitized_url(), Some(WANT_URL), "{snap:?}");
562607
Ok(())
563608
}
609+
610+
#[test]
611+
fn address_sources() -> anyhow::Result<()> {
612+
const RAW_URL: &str = "https://127.0.0.1:1/v42/unused";
613+
614+
let recorder = RequestRecorder::new(TEST_INFO);
615+
let snap = recorder.client_snapshot();
616+
assert_eq!(snap.server_address(), TEST_INFO.default_host, "{snap:?}");
617+
assert_eq!(snap.server_port(), HTTPS_PORT, "{snap:?}");
618+
619+
let url = reqwest::Url::parse(RAW_URL)?;
620+
let request = reqwest::Request::new(reqwest::Method::GET, url);
621+
recorder.on_http_request(&request);
622+
let snap = recorder.client_snapshot();
623+
assert_eq!(snap.server_address(), "127.0.0.1", "{snap:?}");
624+
assert_eq!(snap.server_port(), 1, "{snap:?}");
625+
626+
{
627+
let mut guard = recorder.inner.lock().expect("never poisoned");
628+
let s = guard.transport_snapshot.as_mut().expect("already set");
629+
s.server_address = Some(SocketAddr::V4(SocketAddrV4::new(
630+
Ipv4Addr::new(127, 0, 0, 234),
631+
234,
632+
)));
633+
}
634+
let snap = recorder.client_snapshot();
635+
assert_eq!(snap.server_address(), "127.0.0.234", "{snap:?}");
636+
assert_eq!(snap.server_port(), 234, "{snap:?}");
637+
638+
Ok(())
639+
}
564640
}

src/gax-internal/src/observability/client_signals/with_client_logging.rs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -238,9 +238,8 @@ mod tests {
238238
"gcp.client.repo": "googleapis/google-cloud-rust",
239239
"gcp.client.version": "1.2.3",
240240
"url.full": format!("{}/", BAD_URL),
241-
// TODO(#5144) - use the port attempted
242-
"server.address": "example.com",
243-
"server.port": 443,
241+
"server.address": "127.0.0.1",
242+
"server.port": 1,
244243
"http.request.method": "GET",
245244
});
246245
assert_eq!(Some(&fields), want.as_object(), "{parsed:?}");

0 commit comments

Comments
 (0)