diff --git a/http/http/pom.xml b/http/http/pom.xml
index e86d5d91194..9a2459f6f53 100644
--- a/http/http/pom.xml
+++ b/http/http/pom.xml
@@ -60,6 +60,16 @@
helidon-config-metadata
true
+
+ io.helidon.config
+ helidon-config
+ test
+
+
+ io.helidon.config
+ helidon-config-yaml
+ test
+
io.helidon.common.testing
helidon-common-testing-junit5
diff --git a/http/http/src/main/java/io/helidon/http/RequestedUriDiscoveryContext.java b/http/http/src/main/java/io/helidon/http/RequestedUriDiscoveryContext.java
index 5bef159e3d2..0808b9f0390 100644
--- a/http/http/src/main/java/io/helidon/http/RequestedUriDiscoveryContext.java
+++ b/http/http/src/main/java/io/helidon/http/RequestedUriDiscoveryContext.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2023 Oracle and/or its affiliates.
+ * Copyright (c) 2023, 2024 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.
@@ -93,7 +93,7 @@ final class Builder implements io.helidon.common.Builder discoveryTypes = new ArrayList<>();
private AllowList trustedProxies;
- private String socketId;
+ private String socketId = "@default";
private Builder() {
}
@@ -115,9 +115,14 @@ public Builder config(Config requestedUriDiscoveryConfig) {
requestedUriDiscoveryConfig.get("enabled")
.as(Boolean.class)
.ifPresent(this::enabled);
+ // TODO - discoveryTypes as a key was never documented but was hand-coded this way. Keep for compatibility
+ // in case existing apps happen to use it. Remove as soon as practical.
requestedUriDiscoveryConfig.get("discoveryTypes")
.asList(RequestedUriDiscoveryType.class)
.ifPresent(this::discoveryTypes);
+ requestedUriDiscoveryConfig.get("types")
+ .asList(RequestedUriDiscoveryType.class)
+ .ifPresent(this::types);
requestedUriDiscoveryConfig.get("trusted-proxies")
.map(AllowList::create)
.ifPresent(this::trustedProxies);
@@ -130,7 +135,7 @@ public Builder config(Config requestedUriDiscoveryConfig) {
* @param value new enabled state
* @return updated builder
*/
- @ConfiguredOption(value = "true if 'discoveryTypes' or 'trusted-proxies' is set; false otherwise")
+ @ConfiguredOption(value = "true if 'types' or 'trusted-proxies' is set; false otherwise")
public Builder enabled(boolean value) {
enabled = value;
return this;
@@ -154,13 +159,25 @@ public Builder trustedProxies(AllowList trustedProxies) {
* @param discoveryTypes discovery types to use
* @return updated builder
*/
- @ConfiguredOption
- public Builder discoveryTypes(List discoveryTypes) {
+ @ConfiguredOption()
+ public Builder types(List discoveryTypes) {
this.discoveryTypes.clear();
this.discoveryTypes.addAll(discoveryTypes);
return this;
}
+ /**
+ * Sets the discovery types for requested URI discovery for requests arriving on the socket.
+ *
+ * @param discoveryTypes discovery types to use
+ * @return updated builder
+ * @deprecated Use {@link #types(java.util.List)} instead
+ */
+ @Deprecated(since = "4.0.6", forRemoval = true)
+ public Builder discoveryTypes(List discoveryTypes) {
+ return types(discoveryTypes);
+ }
+
/**
* Adds a discovery type for requested URI discovery for requests arriving on the socket.
*
@@ -202,9 +219,6 @@ public Builder socketId(String socketId) {
*
*/
private void prepareAndCheckRequestedUriSettings() {
- if (socketId == null) {
- throw new IllegalArgumentException("Required socket ID not specified");
- }
boolean isDiscoveryEnabledDefaulted = (enabled == null);
if (enabled == null) {
enabled = !discoveryTypes.isEmpty() || trustedProxies != null;
diff --git a/http/http/src/test/java/io/helidon/http/RequestedUriConfigTest.java b/http/http/src/test/java/io/helidon/http/RequestedUriConfigTest.java
new file mode 100644
index 00000000000..dec93676f0d
--- /dev/null
+++ b/http/http/src/test/java/io/helidon/http/RequestedUriConfigTest.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (c) 2024 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.
+ * You may obtain a copy of the License at
+ *
+ * http://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 io.helidon.http;
+
+import io.helidon.config.Config;
+import io.helidon.config.ConfigSources;
+import io.helidon.http.RequestedUriDiscoveryContext.RequestedUriDiscoveryType;
+import io.helidon.http.RequestedUriDiscoveryContext.UnsafeRequestedUriSettingsException;
+
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+
+import static io.helidon.http.RequestedUriDiscoveryContext.Builder.REQUESTED_URI_DISCOVERY_CONFIG_KEY;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.allOf;
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.notNullValue;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+class RequestedUriConfigTest {
+
+ private static Config config;
+
+ @BeforeAll
+ static void initConfig() {
+ config = Config.just(ConfigSources.classpath("/requestUriDiscovery.yaml"));
+ }
+
+ @Test
+ void checkUnsafeDetectionOnEnable() {
+ Config c = config.get("test-enabled-no-details.server." + REQUESTED_URI_DISCOVERY_CONFIG_KEY);
+ assertThrows(UnsafeRequestedUriSettingsException.class, () ->
+ RequestedUriDiscoveryContext.builder()
+ .config(c)
+ .build(),
+ "defaulted non-HOST discovery type with no proxy settings");
+ }
+
+ @Test
+ void checkExplicitTypesNoDetails() {
+ Config c = config.get("test-explicit-types-no-details.server." + REQUESTED_URI_DISCOVERY_CONFIG_KEY);
+ assertThrows(UnsafeRequestedUriSettingsException.class, () ->
+ RequestedUriDiscoveryContext.builder()
+ .config(c)
+ .build(),
+ "explicit non-HOST discovery types with no proxy settings");
+ }
+
+ @Test
+ void checkEnum() {
+ String v = "x-forwarded";
+ Class> eClass = RequestedUriDiscoveryType.class;
+ if (eClass.isEnum()) {
+ RequestedUriDiscoveryType type = null;
+ for (Object o : eClass.getEnumConstants()) {
+ if (((Enum>) o).name().equalsIgnoreCase((v.replace('-', '_')))) {
+ type = (RequestedUriDiscoveryType) o;
+ break;
+ }
+ }
+ assertThat("Mapped string to discovery type",
+ type,
+ allOf(notNullValue(),is(RequestedUriDiscoveryType.X_FORWARDED)));
+ }
+ }
+}
diff --git a/http/http/src/test/resources/requestUriDiscovery.yaml b/http/http/src/test/resources/requestUriDiscovery.yaml
new file mode 100644
index 00000000000..b9fd9fe4be9
--- /dev/null
+++ b/http/http/src/test/resources/requestUriDiscovery.yaml
@@ -0,0 +1,104 @@
+#
+# Copyright (c) 2023, 2024 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.
+# You may obtain a copy of the License at
+#
+# http://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.
+#
+
+# Should throw unsafe exception - defaulted non-HOST type without trusted proxy settings.
+test-enabled-no-details:
+ server:
+ port: 0
+ requested-uri-discovery:
+ enabled: true
+
+# Should be OK - HOST does not require trusted proxies.
+test-enabled-choose-host:
+ server:
+ port: 0
+ requested-uri-discovery:
+ types: host,forwarded
+ trusted-proxies:
+ allow:
+ exact: trust.com
+ deny:
+ exact: otherBadProxy.com
+
+
+# Should throw unsafe exception - non-HOST type without trusted proxy settings.
+test-explicit-types-no-details:
+ server:
+ port: 0
+ requested-uri-discovery:
+ types: forwarded,x-forwarded
+
+# Should be OK - trusted-proxies is set with non-HOST discovery type.
+test-defaulted-discovery-type:
+ server:
+ port: 0
+ requested-uri-discovery:
+ trusted-proxies:
+ allow:
+ exact: trust.com
+
+# Should reflect only the Forwarded header, not the X-Forwarded family.
+test-forwarded-only:
+ server:
+ port: 0
+ requested-uri-discovery:
+ types: forwarded
+ trusted-proxies:
+ allow:
+ all: true
+ deny:
+ exact: otherUntrustedProxy.com,untrustedProxy.com
+
+# Should reflect only the X-Forwarded headers, not Forwarded.
+test-x-forwarded-only:
+ server:
+ port: 0
+ requested-uri-discovery:
+ types: x-forwarded
+ trusted-proxies:
+ allow:
+ all: true
+ deny:
+ exact: otherUntrustedProxy.com,untrustedProxy.com
+
+# Should reflect only the X-Forwarded headers, not Forwarded.
+test-both-x-forwarded-first:
+ server:
+ port: 0
+ requested-uri-discovery:
+ types: x-forwarded,forwarded
+ trusted-proxies:
+ allow:
+ all: true
+ deny:
+ exact: otherUntrustedProxy.com,untrustedProxy.com
+
+# Should reflect only the X-Forwarded headers, not Forwarded.
+test-both-forwarded-first:
+ server:
+ port: 0
+ requested-uri-discovery:
+ types: forwarded,x-forwarded
+ trusted-proxies:
+ allow:
+ all: true
+ deny:
+ exact: otherUntrustedProxy.com,untrustedProxy.com
+
+# Should reflect the Host header by default.
+test-default-discovery:
+ server:
+ port: 0
diff --git a/webserver/http2/src/main/java/io/helidon/webserver/http2/Http2ServerRequest.java b/webserver/http2/src/main/java/io/helidon/webserver/http2/Http2ServerRequest.java
index ddb84736946..42185e185ee 100644
--- a/webserver/http2/src/main/java/io/helidon/webserver/http2/Http2ServerRequest.java
+++ b/webserver/http2/src/main/java/io/helidon/webserver/http2/Http2ServerRequest.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2022, 2023 Oracle and/or its affiliates.
+ * Copyright (c) 2022, 2024 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.
@@ -31,6 +31,7 @@
import io.helidon.common.uri.UriQuery;
import io.helidon.http.Header;
import io.helidon.http.HttpPrologue;
+import io.helidon.http.RequestedUriDiscoveryContext;
import io.helidon.http.RoutedPath;
import io.helidon.http.ServerRequestHeaders;
import io.helidon.http.WritableHeaders;
@@ -47,6 +48,11 @@
* HTTP/2 server request.
*/
class Http2ServerRequest implements RoutingRequest {
+ private static final RequestedUriDiscoveryContext DEFAULT_REQUESTED_URI_DISCOVERY_CONTEXT =
+ RequestedUriDiscoveryContext.builder()
+ .build();
+
+
private static final Runnable NO_OP_RUNNABLE = () -> {
};
private final Http2Headers http2Headers;
@@ -228,11 +234,13 @@ public Optional proxyProtocolData() {
}
private UriInfo createUriInfo() {
- return ctx.listenerContext().config().requestedUriDiscoveryContext().uriInfo(remotePeer().address().toString(),
- localPeer().address().toString(),
- path.path(),
- headers,
- query(),
- isSecure());
+ return ctx.listenerContext().config().requestedUriDiscoveryContext()
+ .orElse(DEFAULT_REQUESTED_URI_DISCOVERY_CONTEXT)
+ .uriInfo(remotePeer().address().toString(),
+ localPeer().address().toString(),
+ path.path(),
+ headers,
+ query(),
+ isSecure());
}
}
diff --git a/webserver/tests/webserver/src/test/java/io/helidon/webserver/tests/RequestedUriServerTest.java b/webserver/tests/webserver/src/test/java/io/helidon/webserver/tests/RequestedUriServerTest.java
new file mode 100644
index 00000000000..c182b633ce6
--- /dev/null
+++ b/webserver/tests/webserver/src/test/java/io/helidon/webserver/tests/RequestedUriServerTest.java
@@ -0,0 +1,200 @@
+/*
+ * Copyright (c) 2024 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.
+ * You may obtain a copy of the License at
+ *
+ * http://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 io.helidon.webserver.tests;
+
+import java.io.IOException;
+import java.io.StringReader;
+import java.io.StringWriter;
+import java.time.Duration;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeoutException;
+import java.util.stream.Stream;
+
+import io.helidon.common.uri.UriInfo;
+import io.helidon.config.Config;
+import io.helidon.config.ConfigSources;
+import io.helidon.http.HeaderNames;
+import io.helidon.http.RequestedUriDiscoveryContext.UnsafeRequestedUriSettingsException;
+import io.helidon.http.Status;
+import io.helidon.webclient.http1.Http1Client;
+import io.helidon.webclient.http1.Http1ClientRequest;
+import io.helidon.webclient.http1.Http1ClientResponse;
+import io.helidon.webserver.WebServer;
+import io.helidon.webserver.WebServerConfig;
+import io.helidon.webserver.http.HttpRouting;
+import io.helidon.webserver.http.ServerRequest;
+import io.helidon.webserver.http.ServerResponse;
+import io.helidon.webserver.testing.junit5.ServerTest;
+import io.helidon.webserver.testing.junit5.SetUpServer;
+
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.MethodSource;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.equalTo;
+import static org.hamcrest.Matchers.is;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+/**
+ * Positive tests that use multiple sockets, each configured with different requested URI discovery settings, and negative
+ * tests in which a mis-configured server correctly fails to start.
+ */
+@ServerTest
+class RequestedUriServerTest {
+
+ private static final Map> TEST_HEADERS = Map.of(
+ "Host", List.of("theHost.com"),
+ "Forwarded", List.of("by=untrustedProxy.com;for=theClient.com",
+ "by=myLB.com;for=otherProxy.com;host=myHost.com;proto=https"),
+ "X-Forwarded-For", List.of("xClient.com,xUntrustedProxy.com,xLB.com"),
+ "X-Forwarded-Host", List.of("myXHost.com"),
+ "X-Forwarded-Proto", List.of("http"));
+
+ private static final Map socketToPort = new HashMap<>();
+
+ private static Config config;
+
+ @BeforeAll
+ static void initConfig() {
+ config = Config.just(ConfigSources.classpath("/requestUriDiscovery.yaml"));
+ }
+
+
+ @SetUpServer
+ static void prepareServer(WebServerConfig.Builder builder) {
+ Config config = Config.just(ConfigSources.classpath("requestUriDiscovery.yaml")).get("valid.server");
+ builder.config(config);
+ builder.sockets()
+ .forEach((socketName, socketListener) -> builder.routing(socketName,
+ HttpRouting.builder()
+ .get("/test",
+ RequestedUriServerTest::echoHandler)));
+ }
+
+ RequestedUriServerTest(WebServer webServer) {
+ webServer.prototype().sockets()
+ .forEach((socketName, listenerConfig) -> socketToPort.put(socketName, webServer.port(socketName)));
+ }
+
+ @Test
+ void checkUnsafeDetectionOnEnable() {
+ Config c = config.get("test-enabled-no-details.server");
+ assertThrows(UnsafeRequestedUriSettingsException.class, () ->
+ WebServer.builder()
+ .config(c)
+ .build(),
+ "defaulted non-HOST discovery type with no proxy settings");
+ }
+
+ @Test
+ void checkExplicitTypesNoDetails() {
+ Config c = config.get("test-explicit-types-no-details.server");
+ assertThrows(UnsafeRequestedUriSettingsException.class, () ->
+ WebServer.builder()
+ .config(c)
+ .build(),
+ "explicit non-HOST discovery types with no proxy settings");
+ }
+
+ @Test
+ void checkSpecifiedHostDiscovery() throws IOException {
+ // Run with no headers at all, especially no forwarding headers.
+ runTest("test-enabled-choose-host", "http", "localhost");
+ }
+
+ @ParameterizedTest
+ @MethodSource
+ void checkConfiguredForwarding(TestData testData)
+ throws IOException, ExecutionException, InterruptedException, TimeoutException {
+ // Scenarios:
+ // Forwarded case: client requests https://myHost.com -> untrustedProxy.com -> myLB.com (trusted) -> myHost.com
+ // X-Forwarded case: xClient requests https://xHost.com -> xUntrustedProxy.com -> xLB.com (trusted) -> myXHost.com
+ runTest(testData.socketName, TEST_HEADERS, testData.protocol, testData.host);
+ }
+
+ static Stream checkConfiguredForwarding() {
+ return Stream.of(td("test-x-forwarded-only", "http", "myXHost.com"),
+ td("test-forwarded-only", "https", "myHost.com"),
+ td("test-both-x-forwarded-first", "http", "myXHost.com"),
+ td("test-both-forwarded-first", "https", "myHost.com"),
+ td("test-enabled-choose-host", "http", "theHost.com"),
+ td("test-defaulted-discovery-type", "http", "theHost.com"));
+
+ }
+
+ private void runTest(String socketName, String expectedProtocol, String expectedHost) throws IOException {
+ runTest(socketName, null, expectedProtocol, expectedHost);
+ }
+
+ private void runTest(String socketName, Map> headers, String expectedProtocol, String expectedHost)
+ throws IOException {
+ Http1Client testClient = Http1Client.builder()
+ .readTimeout(Duration.ofSeconds(10))
+ .baseUri("http://localhost:" + socketToPort.get(socketName) + "/test")
+ .build();
+
+ try {
+ Http1ClientRequest clientRequest = testClient.get();
+ if (headers != null) {
+ headers.forEach((name, values) -> clientRequest.header(HeaderNames.create(name), values));
+ }
+
+ try (Http1ClientResponse response = clientRequest.request()) {
+ assertThat("Response status", response.status(), is(equalTo(Status.OK_200)));
+ String answer = response.entity().as(String.class);
+ Properties props = new Properties();
+ props.load(new StringReader(answer));
+
+ assertThat("For test scenario " + socketName + " UriInfo host", props.getProperty("host"), is(expectedHost));
+ assertThat("For test scenario " + socketName + " UriInfo scheme",
+ props.getProperty("scheme"),
+ is(expectedProtocol));
+ }
+ } finally {
+ testClient.closeResource();
+ }
+ }
+
+ private static void echoHandler(ServerRequest request, ServerResponse response) {
+ UriInfo uriInfo = request.requestedUri();
+ Properties props = new Properties();
+ props.setProperty("host", uriInfo.host());
+ props.setProperty("scheme", uriInfo.scheme());
+ props.setProperty("authority", uriInfo.authority());
+ props.setProperty("path", uriInfo.path().path());
+
+ StringWriter sw = new StringWriter();
+ try {
+ props.store(sw, "From server");
+ response.send(sw.toString());
+ } catch (IOException e) {
+ response.status(Status.INTERNAL_SERVER_ERROR_500)
+ .send(e.getMessage());
+ }
+ }
+
+ private record TestData(String socketName, String protocol, String host) {}
+
+ private static TestData td(String socketName, String protocol, String host) {
+ return new TestData(socketName, protocol, host);
+ }
+}
diff --git a/webserver/tests/webserver/src/test/resources/requestUriDiscovery.yaml b/webserver/tests/webserver/src/test/resources/requestUriDiscovery.yaml
new file mode 100644
index 00000000000..7c473ec3036
--- /dev/null
+++ b/webserver/tests/webserver/src/test/resources/requestUriDiscovery.yaml
@@ -0,0 +1,104 @@
+#
+# Copyright (c) 2023, 2024 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.
+# You may obtain a copy of the License at
+#
+# http://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.
+#
+
+# The "valid" config section sets up a properly-configured server with different sockets, each
+# with different requested URI discovery behavior. The other sections contain illegal configuration
+# settings which should prevent the server from starting.
+
+valid:
+ server:
+ sockets:
+ - name: "test-enabled-choose-host"
+ port: 0
+ bind-address: "localhost"
+ requested-uri-discovery:
+ types: host,forwarded
+ trusted-proxies:
+ allow:
+ exact: trust.com
+ deny:
+ exact: otherBadProxy.com
+ # Should be OK - trusted-proxies is set with non-HOST discovery type.
+ - name: "test-defaulted-discovery-type"
+ port: 0
+ bind-address: "localhost"
+ requested-uri-discovery:
+ trusted-proxies:
+ allow:
+ exact: trust.com
+ # Should reflect only the Forwarded header, not the X-Forwarded family.
+ - name: "test-forwarded-only"
+ port: 0
+ bind-address: "localhost"
+ requested-uri-discovery:
+ types: forwarded
+ trusted-proxies:
+ allow:
+ all: true
+ deny:
+ exact: otherUntrustedProxy.com,untrustedProxy.com
+ # Should reflect only the X-Forwarded headers, not Forwarded.
+ - name: "test-x-forwarded-only"
+ port: 0
+ bind-address: "localhost"
+ requested-uri-discovery:
+ types: x-forwarded
+ trusted-proxies:
+ allow:
+ all: true
+ deny:
+ exact: otherUntrustedProxy.com,untrustedProxy.com
+ # Should reflect both X-Forwarded headers and Forwarded with X-Forwarded first.
+ - name: "test-both-x-forwarded-first"
+ port: 0
+ bind-address: "localhost"
+ requested-uri-discovery:
+ types: x-forwarded,forwarded
+ trusted-proxies:
+ allow:
+ all: true
+ deny:
+ exact: otherUntrustedProxy.com,untrustedProxy.com
+ # Should reflect both X-Forwarded headers and Forwarded with Forwarded first.
+ - name: "test-both-forwarded-first"
+ port: 0
+ bind-address: "localhost"
+ requested-uri-discovery:
+ types: forwarded,x-forwarded
+ trusted-proxies:
+ allow:
+ all: true
+ deny:
+ exact: otherUntrustedProxy.com,untrustedProxy.com
+ # Should reflect the Host header by default.
+ - name: "test-default-discovery"
+ port: 0
+ bind-address: "localhost"
+
+
+# Should throw unsafe exception - defaulted non-HOST type without trusted proxy settings.
+test-enabled-no-details:
+ server:
+ port: 0
+ requested-uri-discovery:
+ enabled: true
+
+# Should throw unsafe exception - non-HOST type without trusted proxy settings.
+test-explicit-types-no-details:
+ server:
+ port: 0
+ requested-uri-discovery:
+ types: forwarded,x-forwarded
diff --git a/webserver/webserver/src/main/java/io/helidon/webserver/ListenerConfigBlueprint.java b/webserver/webserver/src/main/java/io/helidon/webserver/ListenerConfigBlueprint.java
index df40279f690..21fc25b488f 100644
--- a/webserver/webserver/src/main/java/io/helidon/webserver/ListenerConfigBlueprint.java
+++ b/webserver/webserver/src/main/java/io/helidon/webserver/ListenerConfigBlueprint.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2023 Oracle and/or its affiliates.
+ * Copyright (c) 2023, 2024 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.
@@ -345,7 +345,8 @@ interface ListenerConfigBlueprint {
*
* @return discovery context
*/
- RequestedUriDiscoveryContext requestedUriDiscoveryContext();
+ @Option.Configured("requested-uri-discovery")
+ Optional requestedUriDiscoveryContext();
/**
* Update the server socket with configured socket options.
diff --git a/webserver/webserver/src/main/java/io/helidon/webserver/http1/Http1ServerRequest.java b/webserver/webserver/src/main/java/io/helidon/webserver/http1/Http1ServerRequest.java
index f0b03e811ee..78d5e4a92ef 100644
--- a/webserver/webserver/src/main/java/io/helidon/webserver/http1/Http1ServerRequest.java
+++ b/webserver/webserver/src/main/java/io/helidon/webserver/http1/Http1ServerRequest.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2022, 2023 Oracle and/or its affiliates.
+ * Copyright (c) 2022, 2024 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.
@@ -31,6 +31,7 @@
import io.helidon.http.HeaderNames;
import io.helidon.http.Headers;
import io.helidon.http.HttpPrologue;
+import io.helidon.http.RequestedUriDiscoveryContext;
import io.helidon.http.RoutedPath;
import io.helidon.http.ServerRequestHeaders;
import io.helidon.http.WritableHeaders;
@@ -45,6 +46,11 @@
* Http 1 server request base.
*/
abstract class Http1ServerRequest implements RoutingRequest {
+
+ private static final RequestedUriDiscoveryContext DEFAULT_REQUESTED_URI_DISCOVERY_CONTEXT =
+ RequestedUriDiscoveryContext.builder()
+ .build();
+
private final ServerRequestHeaders headers;
private final ConnectionContext ctx;
private final HttpSecurity security;
@@ -215,11 +221,13 @@ public Optional proxyProtocolData() {
}
private UriInfo createUriInfo() {
- return ctx.listenerContext().config().requestedUriDiscoveryContext().uriInfo(remotePeer().address().toString(),
- localPeer().address().toString(),
- path.path(),
- headers,
- query(),
- isSecure());
+ return ctx.listenerContext().config().requestedUriDiscoveryContext()
+ .orElse(DEFAULT_REQUESTED_URI_DISCOVERY_CONTEXT)
+ .uriInfo(remotePeer().address().toString(),
+ localPeer().address().toString(),
+ path.path(),
+ headers,
+ query(),
+ isSecure());
}
}