From 90fab096d8ec259fc1da8eccc6a7a8d7ea5a7d22 Mon Sep 17 00:00:00 2001 From: David Kral Date: Wed, 25 Jun 2025 13:17:42 +0200 Subject: [PATCH 1/4] Minor Client URI handling updates Signed-off-by: David Kral --- .../helidon/webclient/api/ClientRequest.java | 7 +++-- .../webclient/api/ClientRequestBase.java | 2 +- .../io/helidon/webclient/api/ClientUri.java | 2 ++ .../io/helidon/webclient/api/HttpClient.java | 14 ++++----- .../webclient/http1/Http1ClientTest.java | 29 +++++++++++++++++++ 5 files changed, 43 insertions(+), 11 deletions(-) diff --git a/webclient/api/src/main/java/io/helidon/webclient/api/ClientRequest.java b/webclient/api/src/main/java/io/helidon/webclient/api/ClientRequest.java index 36dcbf76406..4ef78b7129b 100644 --- a/webclient/api/src/main/java/io/helidon/webclient/api/ClientRequest.java +++ b/webclient/api/src/main/java/io/helidon/webclient/api/ClientRequest.java @@ -46,13 +46,13 @@ */ public interface ClientRequest> { /** - * Configure URI. + * Configure URI. A properly encoded URI is expected to be provided. * * @param uri uri to resolve against base URI, or to use if absolute * @return updated request */ default T uri(String uri) { - return uri(URI.create(UriEncoding.encodeUri(uri))); + return uri(URI.create(uri)); } /** @@ -82,7 +82,7 @@ default T path(String uri) { T proxy(Proxy proxy); /** - * Configure URI. + * Configure URI. A properly encoded URI is expected to be provided. * * @param uri uri to resolve against base URI, or to use if absolute * @return updated request @@ -91,6 +91,7 @@ default T path(String uri) { /** * Configure request URI. This always replaces the existing URI (even if base URI is configured). + * A properly encoded URI is expected to be provided. * * @param uri uri to resolve against base URI, or to use if absolute * @return updated request diff --git a/webclient/api/src/main/java/io/helidon/webclient/api/ClientRequestBase.java b/webclient/api/src/main/java/io/helidon/webclient/api/ClientRequestBase.java index ee467cdd90a..6aaf0e76ac2 100644 --- a/webclient/api/src/main/java/io/helidon/webclient/api/ClientRequestBase.java +++ b/webclient/api/src/main/java/io/helidon/webclient/api/ClientRequestBase.java @@ -157,7 +157,7 @@ public T uri(String uri) { if (uri.indexOf('{') > -1) { this.uriTemplate = uri; } else { - uri(URI.create(UriEncoding.encodeUri(uri))); + uri(URI.create(uri)); } return identity(); diff --git a/webclient/api/src/main/java/io/helidon/webclient/api/ClientUri.java b/webclient/api/src/main/java/io/helidon/webclient/api/ClientUri.java index c0f5403f52d..3a321a52be0 100644 --- a/webclient/api/src/main/java/io/helidon/webclient/api/ClientUri.java +++ b/webclient/api/src/main/java/io/helidon/webclient/api/ClientUri.java @@ -186,6 +186,8 @@ public ClientUri resolve(URI uri) { } if (uri.getHost() != null) { uriBuilder.host(uri.getHost()); + } else if (uri.getAuthority() != null) { + throw new IllegalArgumentException("Invalid authority: " + uri.getAuthority()); } if (uri.getPort() != -1) { uriBuilder.port(uri.getPort()); diff --git a/webclient/api/src/main/java/io/helidon/webclient/api/HttpClient.java b/webclient/api/src/main/java/io/helidon/webclient/api/HttpClient.java index 6024757a18a..b1fcf6bcd63 100644 --- a/webclient/api/src/main/java/io/helidon/webclient/api/HttpClient.java +++ b/webclient/api/src/main/java/io/helidon/webclient/api/HttpClient.java @@ -59,7 +59,7 @@ default REQ get() { } /** - * Shortcut for post method with a path. + * Shortcut for post method with a path. A properly encoded URI is expected to be provided. * * @param uri path to resolve against base URI, or full URI * @return a new request (not thread safe) @@ -78,7 +78,7 @@ default REQ post() { } /** - * Shortcut for put method with a path. + * Shortcut for put method with a path. A properly encoded URI is expected to be provided. * * @param uri path to resolve against base URI, or full URI * @return a new request (not thread safe) @@ -97,7 +97,7 @@ default REQ put() { } /** - * Shortcut for delete method with a path. + * Shortcut for delete method with a path. A properly encoded URI is expected to be provided. * * @param uri path to resolve against base URI, or full URI * @return a new request (not thread safe) @@ -116,7 +116,7 @@ default REQ delete() { } /** - * Shortcut for head method with a path. + * Shortcut for head method with a path. A properly encoded URI is expected to be provided. * * @param uri path to resolve against base URI, or full URI * @return a new request (not thread safe) @@ -135,7 +135,7 @@ default REQ head() { } /** - * Shortcut for options method with a path. + * Shortcut for options method with a path. A properly encoded URI is expected to be provided. * * @param uri path to resolve against base URI, or full URI * @return a new request (not thread safe) @@ -154,7 +154,7 @@ default REQ options() { } /** - * Shortcut for trace method with a path. + * Shortcut for trace method with a path. A properly encoded URI is expected to be provided. * * @param uri path to resolve against base URI, or full URI * @return a new request (not thread safe) @@ -173,7 +173,7 @@ default REQ trace() { } /** - * Shortcut for patch method with a path. + * Shortcut for patch method with a path. A properly encoded URI is expected to be provided. * * @param uri path to resolve against base URI, or full URI * @return a new request (not thread safe) diff --git a/webclient/tests/http1/src/test/java/io/helidon/webclient/http1/Http1ClientTest.java b/webclient/tests/http1/src/test/java/io/helidon/webclient/http1/Http1ClientTest.java index 3b6a07cffa9..6e9c4ffe289 100644 --- a/webclient/tests/http1/src/test/java/io/helidon/webclient/http1/Http1ClientTest.java +++ b/webclient/tests/http1/src/test/java/io/helidon/webclient/http1/Http1ClientTest.java @@ -44,6 +44,7 @@ import io.helidon.http.media.MediaContextConfig; import io.helidon.webclient.api.ClientConnection; import io.helidon.webclient.api.ClientResponseTyped; +import io.helidon.webclient.api.ClientUri; import io.helidon.webclient.api.HttpClientRequest; import io.helidon.webclient.api.HttpClientResponse; import io.helidon.webclient.api.Proxy; @@ -119,6 +120,34 @@ void testRequestHeadersUpdated() { client.closeResource(); } + @Test + void testInvalidHost() { + var client = WebClient.builder() + .baseUri(baseURI) + .build(); + IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, + () -> client.get("http://invalid_host:80/foo")); + assertThat(exception.getMessage(), startsWith("Invalid authority")); + } + + @Test + void testUriMethodEncoding() { + var client = WebClient.builder() + .baseUri(baseURI) + .build(); + + //This query is intentionally invalid. We are testing if uri is not encoded by the client. + //It is expected to receive already properly encoded uri. + ClientUri uri = client.get("http://test.com/foo?test=value?").uri(); + assertThat(uri.toString(), is("http://test.com:80/foo?test=value?")); + + //Here query should be properly encoded, since query specific method was used. + uri = client.get("http://test.com/foo") + .queryParam("test", "value?") + .uri(); + assertThat(uri.toString(), is("http://test.com:80/foo?test=value%3F")); + } + @Test void testMaxHeaderSizeFail() { Http1Client client = Http1Client.create(clientConfig -> clientConfig.baseUri(baseURI) From 2be906c7b1977ad34e51947da74ff39358060dc1 Mon Sep 17 00:00:00 2001 From: David Kral Date: Wed, 2 Jul 2025 12:07:10 +0200 Subject: [PATCH 2/4] Test adjusted Signed-off-by: David Kral --- .../test/java/io/helidon/webserver/tests/RoutingTestBase.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webserver/tests/webserver/src/test/java/io/helidon/webserver/tests/RoutingTestBase.java b/webserver/tests/webserver/src/test/java/io/helidon/webserver/tests/RoutingTestBase.java index def6c9daa93..20d0de55e1f 100644 --- a/webserver/tests/webserver/src/test/java/io/helidon/webserver/tests/RoutingTestBase.java +++ b/webserver/tests/webserver/src/test/java/io/helidon/webserver/tests/RoutingTestBase.java @@ -63,7 +63,7 @@ static void multiHandler(ServerRequest req, ServerResponse res) { @Test void testRouteWithSpace() { - try (Http1ClientResponse response = client.get("/my path").request()) { + try (Http1ClientResponse response = client.get().path("/my path").request()) { assertThat(response.status(), is(Status.OK_200)); From aa617b1dea52a9ef72e83b910a00bfab3558e001 Mon Sep 17 00:00:00 2001 From: David Kral Date: Wed, 9 Jul 2025 15:59:35 +0200 Subject: [PATCH 3/4] copyright Signed-off-by: David Kral --- .../api/src/main/java/io/helidon/webclient/api/ClientUri.java | 2 +- .../api/src/main/java/io/helidon/webclient/api/HttpClient.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/webclient/api/src/main/java/io/helidon/webclient/api/ClientUri.java b/webclient/api/src/main/java/io/helidon/webclient/api/ClientUri.java index 3a321a52be0..9bef15a9503 100644 --- a/webclient/api/src/main/java/io/helidon/webclient/api/ClientUri.java +++ b/webclient/api/src/main/java/io/helidon/webclient/api/ClientUri.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, 2024 Oracle and/or its affiliates. + * Copyright (c) 2022, 2025 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/webclient/api/src/main/java/io/helidon/webclient/api/HttpClient.java b/webclient/api/src/main/java/io/helidon/webclient/api/HttpClient.java index b1fcf6bcd63..a060fb4bc35 100644 --- a/webclient/api/src/main/java/io/helidon/webclient/api/HttpClient.java +++ b/webclient/api/src/main/java/io/helidon/webclient/api/HttpClient.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, 2023 Oracle and/or its affiliates. + * Copyright (c) 2022, 2025 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. From 6590051e46db4e916c07af538e68dbb4d69f53e3 Mon Sep 17 00:00:00 2001 From: David Kral Date: Wed, 9 Jul 2025 16:26:40 +0200 Subject: [PATCH 4/4] test fix Signed-off-by: David Kral --- .../integrations/eureka/EurekaRegistrationHttpFeature.java | 6 ++++-- .../eureka/EurekaRegistrationServerFeatureIT.java | 3 ++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/integrations/eureka/eureka/src/main/java/io/helidon/integrations/eureka/EurekaRegistrationHttpFeature.java b/integrations/eureka/eureka/src/main/java/io/helidon/integrations/eureka/EurekaRegistrationHttpFeature.java index a3ee664feb1..e6ab87ca951 100644 --- a/integrations/eureka/eureka/src/main/java/io/helidon/integrations/eureka/EurekaRegistrationHttpFeature.java +++ b/integrations/eureka/eureka/src/main/java/io/helidon/integrations/eureka/EurekaRegistrationHttpFeature.java @@ -277,7 +277,8 @@ private boolean cancel(Http1Client client) { // DELETE {baseUri}/v2/apps/{appName}/{id} private boolean cancel(Http1Client client, String appName, String id) { try (var response = client - .delete("/v2/apps/" + Objects.requireNonNull(appName, "appName") + "/" + Objects.requireNonNull(id, "id")) + .delete() + .path("/v2/apps/" + Objects.requireNonNull(appName, "appName") + "/" + Objects.requireNonNull(id, "id")) .request()) { if (response.status().family() == SUCCESSFUL) { if (LOGGER.isLoggable(DEBUG)) { @@ -358,7 +359,8 @@ private Http1ClientResponse heartbeat(String appName, String status, Long lastDirtyTimestamp) { var request = this.client // volatile read - .put("/v2/apps/" + Objects.requireNonNull(appName, "appName") + "/" + Objects.requireNonNull(id, "id")) + .put() + .path("/v2/apps/" + Objects.requireNonNull(appName, "appName") + "/" + Objects.requireNonNull(id, "id")) .accept(APPLICATION_JSON); if (status != null) { request.queryParam("status", status); diff --git a/tests/integration/eureka/src/test/java/io/helidon/tests/integration/eureka/EurekaRegistrationServerFeatureIT.java b/tests/integration/eureka/src/test/java/io/helidon/tests/integration/eureka/EurekaRegistrationServerFeatureIT.java index 28aef039bd5..c6b359bef90 100644 --- a/tests/integration/eureka/src/test/java/io/helidon/tests/integration/eureka/EurekaRegistrationServerFeatureIT.java +++ b/tests/integration/eureka/src/test/java/io/helidon/tests/integration/eureka/EurekaRegistrationServerFeatureIT.java @@ -103,7 +103,8 @@ void test() throws InterruptedException { assertThat(this.ws.isRunning(), is(true)); Thread.sleep(500L); // wait for the registration/renewal attempt to happen in the background try (var response = this.wc - .get("/v2/apps/" + ((EurekaRegistrationServerFeature)this.ws.prototype() + .get() + .path("/v2/apps/" + ((EurekaRegistrationServerFeature)this.ws.prototype() .features() .get(0)) .prototype()