From 2e07f9ab33d882876f46912fcea08030b2593d49 Mon Sep 17 00:00:00 2001 From: Rossen Stoyanchev Date: Wed, 6 Dec 2023 10:16:16 +0000 Subject: [PATCH] DefaultWebClient exposes full URI template as request attribute Closes gh-30027 --- .../web/util/DefaultUriBuilderFactory.java | 7 ++++++- .../java/org/springframework/web/util/UriBuilder.java | 11 ++++++++++- .../web/util/UriComponentsBuilder.java | 3 ++- .../web/util/DefaultUriBuilderFactoryTests.java | 10 +++++++++- .../reactive/function/client/DefaultWebClient.java | 5 +++-- .../function/client/WebClientObservationTests.java | 2 +- 6 files changed, 31 insertions(+), 7 deletions(-) diff --git a/spring-web/src/main/java/org/springframework/web/util/DefaultUriBuilderFactory.java b/spring-web/src/main/java/org/springframework/web/util/DefaultUriBuilderFactory.java index 7d06cb61a0..8b620dd41c 100644 --- a/spring-web/src/main/java/org/springframework/web/util/DefaultUriBuilderFactory.java +++ b/spring-web/src/main/java/org/springframework/web/util/DefaultUriBuilderFactory.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2023 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -410,6 +410,11 @@ public class DefaultUriBuilderFactory implements UriBuilderFactory { } return URI.create(uric.toString()); } + + @Override + public String toUriString() { + return this.uriComponentsBuilder.build().toUriString(); + } } } diff --git a/spring-web/src/main/java/org/springframework/web/util/UriBuilder.java b/spring-web/src/main/java/org/springframework/web/util/UriBuilder.java index 70b5040d1f..6b0b2987e5 100644 --- a/spring-web/src/main/java/org/springframework/web/util/UriBuilder.java +++ b/spring-web/src/main/java/org/springframework/web/util/UriBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2022 the original author or authors. + * Copyright 2002-2023 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -270,4 +270,13 @@ public interface UriBuilder { */ URI build(Map uriVariables); + /** + * Return a String representation of the URI by concatenating all URI + * component values into the fully formed URI String. Implementing classes + * should perform simple String concatenation of current URI component + * values to preserve URI template placeholders. + * @since 6.1.2 + */ + String toUriString(); + } diff --git a/spring-web/src/main/java/org/springframework/web/util/UriComponentsBuilder.java b/spring-web/src/main/java/org/springframework/web/util/UriComponentsBuilder.java index 19c0628383..c52771617f 100644 --- a/spring-web/src/main/java/org/springframework/web/util/UriComponentsBuilder.java +++ b/spring-web/src/main/java/org/springframework/web/util/UriComponentsBuilder.java @@ -501,7 +501,8 @@ public class UriComponentsBuilder implements UriBuilder, Cloneable { * @see UriComponents#toUriString() */ public String toUriString() { - return (this.uriVariables.isEmpty() ? build().encode().toUriString() : + return (this.uriVariables.isEmpty() ? + build().encode().toUriString() : buildInternal(EncodingHint.ENCODE_TEMPLATE).toUriString()); } diff --git a/spring-web/src/test/java/org/springframework/web/util/DefaultUriBuilderFactoryTests.java b/spring-web/src/test/java/org/springframework/web/util/DefaultUriBuilderFactoryTests.java index ab53012321..d93d0922fa 100644 --- a/spring-web/src/test/java/org/springframework/web/util/DefaultUriBuilderFactoryTests.java +++ b/spring-web/src/test/java/org/springframework/web/util/DefaultUriBuilderFactoryTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2023 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -186,4 +186,12 @@ public class DefaultUriBuilderFactoryTests { assertThat(uri.toString()).isEqualTo("/foo/bar"); } + @Test // gh-30027 + void uriTemplateString() { + String baseUrl = "https://github.com/spring-projects/spring-boot/releases"; + String uriTemplate = "/tag/v{version}"; + String actual = new DefaultUriBuilderFactory(baseUrl).uriString(uriTemplate).toUriString(); + assertThat(actual).isEqualTo(baseUrl + uriTemplate); + } + } diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/DefaultWebClient.java b/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/DefaultWebClient.java index 59fe45dce1..5786a21b55 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/DefaultWebClient.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/DefaultWebClient.java @@ -226,8 +226,9 @@ final class DefaultWebClient implements WebClient { @Override public RequestBodySpec uri(String uriTemplate, Object... uriVariables) { - attribute(URI_TEMPLATE_ATTRIBUTE, uriTemplate); - return uri(uriBuilderFactory.expand(uriTemplate, uriVariables)); + UriBuilder uriBuilder = uriBuilderFactory.uriString(uriTemplate); + attribute(URI_TEMPLATE_ATTRIBUTE, uriBuilder.toUriString()); + return uri(uriBuilder.build(uriVariables)); } @Override diff --git a/spring-webflux/src/test/java/org/springframework/web/reactive/function/client/WebClientObservationTests.java b/spring-webflux/src/test/java/org/springframework/web/reactive/function/client/WebClientObservationTests.java index 4cd8900ce8..5fdad3b5aa 100644 --- a/spring-webflux/src/test/java/org/springframework/web/reactive/function/client/WebClientObservationTests.java +++ b/spring-webflux/src/test/java/org/springframework/web/reactive/function/client/WebClientObservationTests.java @@ -82,7 +82,7 @@ class WebClientObservationTests { ClientRequest clientRequest = verifyAndGetRequest(); assertThatHttpObservation().hasLowCardinalityKeyValue("outcome", "SUCCESS") - .hasLowCardinalityKeyValue("uri", "/resource/{id}"); + .hasLowCardinalityKeyValue("uri", "/base/resource/{id}"); assertThat(clientRequest.headers()).containsEntry("foo", Collections.singletonList("bar")); }