diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/WebClientUtils.java b/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/WebClientUtils.java index 09765c3f72fb..886ad173bc01 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/WebClientUtils.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/WebClientUtils.java @@ -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 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 @@ public static Mono>> mapToEntityList(ClientResponse r } /** - * 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()); + } + } + if (uri.getRawPath() != null) { + sb.append(uri.getRawPath()); } - return httpMethod.name() + " " + uri; + return sb.toString(); } } diff --git a/spring-webflux/src/test/java/org/springframework/web/reactive/function/client/WebClientUtilsTests.java b/spring-webflux/src/test/java/org/springframework/web/reactive/function/client/WebClientUtilsTests.java new file mode 100644 index 000000000000..5d4819355745 --- /dev/null +++ b/spring-webflux/src/test/java/org/springframework/web/reactive/function/client/WebClientUtilsTests.java @@ -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 "); + } + +}