diff --git a/nima/webclient/webclient/pom.xml b/nima/webclient/webclient/pom.xml index e43a7570add..efce71e3f61 100644 --- a/nima/webclient/webclient/pom.xml +++ b/nima/webclient/webclient/pom.xml @@ -89,6 +89,11 @@ junit-jupiter-api test + + junit-jupiter-params + org.junit.jupiter + test + org.hamcrest hamcrest-all diff --git a/nima/webclient/webclient/src/main/java/io/helidon/nima/webclient/http1/Http1Client.java b/nima/webclient/webclient/src/main/java/io/helidon/nima/webclient/http1/Http1Client.java index 8ce2f2bdacb..28e6e2aef6d 100644 --- a/nima/webclient/webclient/src/main/java/io/helidon/nima/webclient/http1/Http1Client.java +++ b/nima/webclient/webclient/src/main/java/io/helidon/nima/webclient/http1/Http1Client.java @@ -235,6 +235,18 @@ public Http1ClientBuilder useSystemServiceLoader(boolean useServiceLoader) { configBuilder.servicesUseServiceLoader(useServiceLoader); return this; } + + /** + * Can be set to {@code true} to force the use of relative URIs in all requests, + * regardless of the presence or absence of proxies or no-proxy lists. + * + * @param relativeUris relative URIs flag + * @return updated builder instance + */ + public Http1ClientBuilder relativeUris(boolean relativeUris) { + configBuilder.relativeUris(relativeUris); + return this; + } } } diff --git a/nima/webclient/webclient/src/main/java/io/helidon/nima/webclient/http1/Http1ClientConfig.java b/nima/webclient/webclient/src/main/java/io/helidon/nima/webclient/http1/Http1ClientConfig.java index 26fea9fc098..7413bf5ab86 100644 --- a/nima/webclient/webclient/src/main/java/io/helidon/nima/webclient/http1/Http1ClientConfig.java +++ b/nima/webclient/webclient/src/main/java/io/helidon/nima/webclient/http1/Http1ClientConfig.java @@ -65,6 +65,10 @@ interface Http1ClientConfig extends ClientConfig { @ConfiguredOption("true") boolean servicesUseServiceLoader(); + // TODO Set the default value to false when proxy is implemented and see Http1CallChainBase.prologue for other changes + @ConfiguredOption("true") + boolean relativeUris(); + @Singular List services(); } diff --git a/nima/webclient/webclient/src/main/java/io/helidon/nima/webclient/http1/HttpCallChainBase.java b/nima/webclient/webclient/src/main/java/io/helidon/nima/webclient/http1/HttpCallChainBase.java index f9c688374d5..c400669379c 100644 --- a/nima/webclient/webclient/src/main/java/io/helidon/nima/webclient/http1/HttpCallChainBase.java +++ b/nima/webclient/webclient/src/main/java/io/helidon/nima/webclient/http1/HttpCallChainBase.java @@ -79,8 +79,13 @@ abstract WebClientServiceResponse doProceed(ClientConnection connection, BufferData writeBuffer); void prologue(BufferData nonEntityData, WebClientServiceRequest request, UriHelper uri) { + // TODO When proxy is implemented, change default value of Http1ClientConfig.relativeUris to false + // and below conditional statement to: + // proxy == Proxy.noProxy() || proxy.noProxyPredicate().apply(finalUri) || clientConfig.relativeUris + String schemeHostPort = clientConfig.relativeUris() ? "" : uri.scheme() + "://" + uri.host() + ":" + uri.port(); nonEntityData.writeAscii(request.method().text() + " " + + schemeHostPort + uri.pathWithQueryAndFragment(request.query(), request.fragment()) + " HTTP/1.1\r\n"); } diff --git a/nima/webclient/webclient/src/test/java/io/helidon/nima/webclient/http1/ClientRequestImplTest.java b/nima/webclient/webclient/src/test/java/io/helidon/nima/webclient/http1/ClientRequestImplTest.java index 7e6afcfa0e8..3233bc8870d 100644 --- a/nima/webclient/webclient/src/test/java/io/helidon/nima/webclient/http1/ClientRequestImplTest.java +++ b/nima/webclient/webclient/src/test/java/io/helidon/nima/webclient/http1/ClientRequestImplTest.java @@ -19,9 +19,11 @@ import java.io.OutputStream; import java.net.URI; import java.nio.charset.StandardCharsets; +import java.util.StringTokenizer; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; +import java.util.stream.Stream; import io.helidon.common.GenericType; import io.helidon.common.buffers.BufferData; @@ -39,13 +41,18 @@ import io.helidon.nima.webclient.WebClient; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; import static io.helidon.common.testing.http.junit5.HttpHeaderMatcher.hasHeader; import static io.helidon.common.testing.http.junit5.HttpHeaderMatcher.noHeader; import static org.hamcrest.CoreMatchers.containsString; import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.startsWith; import static org.hamcrest.MatcherAssert.assertThat; import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.params.provider.Arguments.arguments; class ClientRequestImplTest { private static final Http.HeaderValue REQ_CHUNKED_HEADER = Http.Header.createCached( @@ -193,6 +200,28 @@ void testSkipUrlEncoding() { assertThat(uri.getRawFragment(), is("someFragment,")); } + @ParameterizedTest + @MethodSource("relativeUris") + void testRelativeUris(boolean relativeUris, boolean outputStream, String requestUri, String expectedUriStart) { + Http1Client client = WebClient.builder().relativeUris(relativeUris).build(); + FakeHttp1ClientConnection connection = new FakeHttp1ClientConnection(); + Http1ClientRequest request = client.put(requestUri); + request.connection(connection); + Http1ClientResponse response; + if (outputStream) { + response = getHttp1ClientResponseFromOutputStream(request, new String[] {"Sending Something"}); + } else { + response = request.submit("Sending Something"); + } + + assertThat(response.status(), is(Http.Status.OK_200)); + StringTokenizer st = new StringTokenizer(connection.getPrologue(), " "); + // skip method part + st.nextToken(); + // Validate URI part + assertThat(st.nextToken(), startsWith(expectedUriStart)); + } + private static void validateSuccessfulResponse(Http1Client client, ClientConnection connection) { String requestEntity = "Sending Something"; Http1ClientRequest request = client.put("http://localhost:" + dummyPort + "/test"); @@ -248,6 +277,28 @@ private static Http1ClientResponse getHttp1ClientResponseFromOutputStream(Http1C return response; } + private static Stream relativeUris() { + return Stream.of( + // OutputStream (chunk request) + arguments(false, true, "http://www.dummy.com/test", "http://www.dummy.com:80/"), + arguments(false, true, "http://www.dummy.com:1111/test", "http://www.dummy.com:1111/"), + arguments(false, true, "https://www.dummy.com/test", "https://www.dummy.com:443/"), + arguments(false, true, "https://www.dummy.com:1111/test", "https://www.dummy.com:1111/"), + arguments(true, true, "http://www.dummy.com/test", "/test"), + arguments(true, true, "http://www.dummy.com:1111/test", "/test"), + arguments(true, true, "https://www.dummy.com/test", "/test"), + arguments(true, true, "https://www.dummy.com:1111/test", "/test"), + // non-OutputStream (single entity request) + arguments(false, false, "http://www.dummy.com/test", "http://www.dummy.com:80/"), + arguments(false, false, "http://www.dummy.com:1111/test", "http://www.dummy.com:1111/"), + arguments(false, false, "https://www.dummy.com/test", "https://www.dummy.com:443/"), + arguments(false, false, "https://www.dummy.com:1111/test", "https://www.dummy.com:1111/"), + arguments(true, false, "http://www.dummy.com/test", "/test"), + arguments(true, false, "http://www.dummy.com:1111/test", "/test"), + arguments(true, false, "https://www.dummy.com/test", "/test"), + arguments(true, false, "https://www.dummy.com:1111/test", "/test")); + } + private static class FakeHttp1ClientConnection implements ClientConnection { private final DataReader clientReader; private final DataWriter clientWriter; @@ -255,6 +306,7 @@ private static class FakeHttp1ClientConnection implements ClientConnection { private final DataWriter serverWriter; private Throwable serverException; private ExecutorService webServerEmulator; + private String prologue; FakeHttp1ClientConnection() { ArrayBlockingQueue serverToClient = new ArrayBlockingQueue<>(1024); @@ -290,6 +342,11 @@ public String channelId() { return null; } + // This will be used for testing the element of Prologue + String getPrologue() { + return prologue; + } + private DataWriter writer(ArrayBlockingQueue queue) { return new DataWriter() { @Override @@ -361,8 +418,8 @@ private void webServerHandle() { // Read prologue int lineLength = serverReader.findNewLine(16384); if (lineLength > 0) { - //String prologue = serverReader.readAsciiString(lineLength); - serverReader.skip(lineLength + 2); // skip Prologue + CRLF + prologue = serverReader.readAsciiString(lineLength); + serverReader.skip(2); // skip CRLF } // Read Headers