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() 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..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. @@ -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..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. @@ -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) 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));