Avoid redundant URI object creation in WebClientUtils

Prior to this commit, WebClientUtils.getRequestDescription()
created a new URI object on every invocation. Since the URI
constructor includes validation and parsing, which is already
performed by the parameter URI object, this was unnecessarily
expensive for a logging utility.

This commit reuses the original URI's string representation
to avoid redundant parsing.
This commit also strips userInfo, query, and fragment from the
log description even if URIs contain only userInfo or
fragment (without a query).

Closes gh-36641

Signed-off-by: 박동윤 (Park Dong-Yun) <ehddbs7458@gmail.com>
This commit is contained in:
박동윤 (Park Dong-Yun)
2026-04-08 03:58:53 +09:00
committed by Brian Clozel
parent be70e1c5a7
commit 865cf3c117
2 changed files with 158 additions and 8 deletions
@@ -17,7 +17,6 @@
package org.springframework.web.reactive.function.client;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.List;
import java.util.function.Predicate;
@@ -28,7 +27,6 @@ import reactor.core.publisher.Mono;
import org.springframework.core.codec.CodecException;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.util.StringUtils;
/**
* Internal methods shared between {@link DefaultWebClient} and
@@ -69,18 +67,43 @@ abstract class WebClientUtils {
}
/**
* Return a String representation of the request details for logging purposes.
* Return a String representation of the request details for logging purposes
* in "METHOD URI" format.
* For the Security purpose, URI is returned in encoded format,
* while userInfo, query, and fragment is stripped out.
* @since 6.0.16
*/
public static String getRequestDescription(HttpMethod httpMethod, URI uri) {
if (StringUtils.hasText(uri.getQuery())) {
try {
uri = new URI(uri.getScheme(), null, uri.getHost(), uri.getPort(), uri.getPath(), null, null);
StringBuilder sb = new StringBuilder()
.append(httpMethod.name()).append(" ");
// also handles Opaque URI, which has only schemeSpecificPart
if (uri.getRawUserInfo() == null && uri.getRawQuery() == null && uri.getRawFragment() == null) {
return sb.append(uri).toString();
}
if (uri.getScheme() != null) {
sb.append(uri.getScheme()).append(':');
}
if (uri.getHost() != null) {
sb.append("//");
String host = uri.getHost();
// IPv6 handling
if (host.indexOf(':') >= 0 && !host.startsWith("[") && !host.endsWith("]")) {
sb.append('[').append(host).append(']');
}
catch (URISyntaxException ignored) {
else {
sb.append(host);
}
if (uri.getPort() != -1) {
sb.append(':').append(uri.getPort());
}
}
return httpMethod.name() + " " + uri;
if (uri.getRawPath() != null) {
sb.append(uri.getRawPath());
}
return sb.toString();
}
}
@@ -0,0 +1,127 @@
/*
* Copyright 2002-present 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.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.reactive.function.client;
import java.net.URI;
import org.junit.jupiter.api.Test;
import org.springframework.http.HttpMethod;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link WebClientUtils#getRequestDescription}.
*
* @author Park Dong-Yun
*/
class WebClientUtilsTests {
@Test
void stripsQueryParams() {
URI uri = URI.create("https://api.example.com/search?q=test&page=1");
assertThat(WebClientUtils.getRequestDescription(HttpMethod.GET, uri))
.isEqualTo("GET https://api.example.com/search");
}
@Test
void noQueryReturnsAsIs() {
URI uri = URI.create("https://api.example.com/health");
assertThat(WebClientUtils.getRequestDescription(HttpMethod.GET, uri))
.isEqualTo("GET https://api.example.com/health");
}
@Test
void preservesPort() {
URI uri = URI.create("https://api.example.com:8443/path?q=1");
assertThat(WebClientUtils.getRequestDescription(HttpMethod.GET, uri))
.isEqualTo("GET https://api.example.com:8443/path");
}
@Test
void remainsPathEncodedWhenStrippingQuery() {
URI uri = URI.create("https://host/hello%20world?q=1");
assertThat(WebClientUtils.getRequestDescription(HttpMethod.GET, uri))
.isEqualTo("GET https://host/hello%20world");
}
@Test
void stripsUserInfoWithQuery() {
URI uri = URI.create("https://admin:secret@host/api?token=abc");
assertThat(WebClientUtils.getRequestDescription(HttpMethod.GET, uri))
.isEqualTo("GET https://host/api");
}
@Test
void stripsUserInfoWithoutQuery() {
URI uri = URI.create("https://admin:secret@host/api");
assertThat(WebClientUtils.getRequestDescription(HttpMethod.GET, uri))
.isEqualTo("GET https://host/api");
}
@Test
void stripsFragmentWithQuery() {
URI uri = URI.create("https://host/page?q=1#section");
assertThat(WebClientUtils.getRequestDescription(HttpMethod.GET, uri))
.isEqualTo("GET https://host/page");
}
@Test
void stripsFragmentWithoutQuery() {
URI uri = URI.create("https://host/page#section");
assertThat(WebClientUtils.getRequestDescription(HttpMethod.GET, uri))
.isEqualTo("GET https://host/page");
}
@Test
void questionMarkInFragmentNotTreatedAsQuery() {
URI uri = URI.create("https://host/page#frag?param=value");
assertThat(WebClientUtils.getRequestDescription(HttpMethod.GET, uri))
.isEqualTo("GET https://host/page");
}
@Test
void ipv6Host() {
URI uri = URI.create("http://[::1]:8080/path?q=1");
assertThat(WebClientUtils.getRequestDescription(HttpMethod.GET, uri))
.isEqualTo("GET http://[::1]:8080/path");
}
@Test
void opaqueUriUnchanged() {
URI uri = URI.create("mailto:user@example.com?subject=hello");
assertThat(WebClientUtils.getRequestDescription(HttpMethod.GET, uri))
.isEqualTo("GET mailto:user@example.com?subject=hello");
}
@Test
void relativeUri() {
URI uri = URI.create("/api/search?q=test");
assertThat(WebClientUtils.getRequestDescription(HttpMethod.GET, uri))
.isEqualTo("GET /api/search");
}
@Test
void prefixesHttpMethod() {
URI uri = URI.create("https://host/resource?v=1");
assertThat(WebClientUtils.getRequestDescription(HttpMethod.POST, uri))
.startsWith("POST ");
assertThat(WebClientUtils.getRequestDescription(HttpMethod.DELETE, uri))
.startsWith("DELETE ");
}
}