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()); } }