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