Migrate to `BaseUrlUriTemplateHandler`

Remove `LocalHostUriTemplateHandler` and migrate existing code to use
`BaseUrlUriTemplateHandler`.

See gh-46356
See gh-47322
This commit is contained in:
Phillip Webb 2025-09-30 12:51:03 -07:00
parent 97c89b480f
commit 279de9e807
11 changed files with 53 additions and 260 deletions

View File

@ -1,106 +0,0 @@
/*
* Copyright 2012-present the original author or authors.
*
* 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
*
* https://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 org.springframework.boot.test.web.servlet.client;
import java.net.URI;
import java.util.HashMap;
import java.util.Map;
import org.junit.jupiter.api.Test;
import org.springframework.boot.web.server.test.client.LocalHostUriTemplateHandler;
import org.springframework.mock.env.MockEnvironment;
import org.springframework.web.util.UriTemplateHandler;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
import static org.mockito.BDDMockito.given;
import static org.mockito.BDDMockito.then;
import static org.mockito.Mockito.mock;
/**
* Tests for {@link LocalHostUriTemplateHandler}.
*
* @author Phillip Webb
* @author Andy Wilkinson
* @author Eddú Meléndez
*/
class LocalHostUriTemplateHandlerTests {
@Test
void createWhenEnvironmentIsNullShouldThrowException() {
assertThatIllegalArgumentException().isThrownBy(() -> new LocalHostUriTemplateHandler(null))
.withMessageContaining("'environment' must not be null");
}
@Test
void createWhenSchemeIsNullShouldThrowException() {
assertThatIllegalArgumentException()
.isThrownBy(() -> new LocalHostUriTemplateHandler(new MockEnvironment(), null))
.withMessageContaining("'scheme' must not be null");
}
@Test
void createWhenHandlerIsNullShouldThrowException() {
assertThatIllegalArgumentException()
.isThrownBy(() -> new LocalHostUriTemplateHandler(new MockEnvironment(), "http", null))
.withMessageContaining("'handler' must not be null");
}
@Test
void getRootUriShouldUseLocalServerPort() {
MockEnvironment environment = new MockEnvironment();
environment.setProperty("local.server.port", "1234");
LocalHostUriTemplateHandler handler = new LocalHostUriTemplateHandler(environment);
assertThat(handler.getRootUri()).isEqualTo("http://localhost:1234");
}
@Test
void getRootUriWhenLocalServerPortMissingShouldUsePort8080() {
MockEnvironment environment = new MockEnvironment();
LocalHostUriTemplateHandler handler = new LocalHostUriTemplateHandler(environment);
assertThat(handler.getRootUri()).isEqualTo("http://localhost:8080");
}
@Test
void getRootUriUsesCustomScheme() {
MockEnvironment environment = new MockEnvironment();
LocalHostUriTemplateHandler handler = new LocalHostUriTemplateHandler(environment, "https");
assertThat(handler.getRootUri()).isEqualTo("https://localhost:8080");
}
@Test
void getRootUriShouldUseContextPath() {
MockEnvironment environment = new MockEnvironment();
environment.setProperty("server.servlet.context-path", "/foo");
LocalHostUriTemplateHandler handler = new LocalHostUriTemplateHandler(environment);
assertThat(handler.getRootUri()).isEqualTo("http://localhost:8080/foo");
}
@Test
void expandShouldUseCustomHandler() {
MockEnvironment environment = new MockEnvironment();
UriTemplateHandler uriTemplateHandler = mock(UriTemplateHandler.class);
Map<String, ?> uriVariables = new HashMap<>();
URI uri = URI.create("https://www.example.com");
given(uriTemplateHandler.expand("https://localhost:8080/", uriVariables)).willReturn(uri);
LocalHostUriTemplateHandler handler = new LocalHostUriTemplateHandler(environment, "https", uriTemplateHandler);
assertThat(handler.expand("/", uriVariables)).isEqualTo(uri);
then(uriTemplateHandler).should().expand("https://localhost:8080/", uriVariables);
}
}

View File

@ -1,86 +0,0 @@
/*
* Copyright 2012-present the original author or authors.
*
* 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
*
* https://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 org.springframework.boot.web.server.test.client;
import org.springframework.boot.restclient.RootUriTemplateHandler;
import org.springframework.core.env.Environment;
import org.springframework.util.Assert;
import org.springframework.web.util.DefaultUriBuilderFactory;
import org.springframework.web.util.UriTemplateHandler;
/**
* {@link UriTemplateHandler} will automatically prefix relative URIs with
* <code>localhost:$&#123;local.server.port&#125;</code>.
*
* @author Phillip Webb
* @author Andy Wilkinson
* @author Eddú Meléndez
* @author Madhura Bhave
* @since 4.0.0
*/
public class LocalHostUriTemplateHandler extends RootUriTemplateHandler {
private static final String PREFIX = "server.servlet.";
private final Environment environment;
private final String scheme;
/**
* Create a new {@code LocalHostUriTemplateHandler} that will generate {@code http}
* URIs using the given {@code environment} to determine the context path and port.
* @param environment the environment used to determine the port
*/
public LocalHostUriTemplateHandler(Environment environment) {
this(environment, "http");
}
/**
* Create a new {@code LocalHostUriTemplateHandler} that will generate URIs with the
* given {@code scheme} and use the given {@code environment} to determine the
* context-path and port.
* @param environment the environment used to determine the port
* @param scheme the scheme of the root uri
*/
public LocalHostUriTemplateHandler(Environment environment, String scheme) {
this(environment, scheme, new DefaultUriBuilderFactory());
}
/**
* Create a new {@code LocalHostUriTemplateHandler} that will generate URIs with the
* given {@code scheme}, use the given {@code environment} to determine the
* context-path and port and delegate to the given template {@code handler}.
* @param environment the environment used to determine the port
* @param scheme the scheme of the root uri
* @param handler the delegate handler
*/
public LocalHostUriTemplateHandler(Environment environment, String scheme, UriTemplateHandler handler) {
super(handler);
Assert.notNull(environment, "'environment' must not be null");
Assert.notNull(scheme, "'scheme' must not be null");
this.environment = environment;
this.scheme = scheme;
}
@Override
public String getRootUri() {
String port = this.environment.getProperty("local.server.port", "8080");
String contextPath = this.environment.getProperty(PREFIX + "context-path", "");
return this.scheme + "://localhost:" + port + contextPath;
}
}

View File

@ -1,23 +0,0 @@
/*
* Copyright 2012-present the original author or authors.
*
* 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
*
* https://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.
*/
/**
* Client-side support for testing embedded web servers.
*/
@NullMarked
package org.springframework.boot.web.server.test.client;
import org.jspecify.annotations.NullMarked;

View File

@ -21,8 +21,10 @@ import java.util.Map;
import org.junit.jupiter.api.Test;
import org.springframework.boot.restclient.test.TestRestTemplate;
import org.springframework.boot.web.server.test.client.LocalHostUriTemplateHandler;
import org.springframework.core.env.Environment;
import org.springframework.boot.test.http.server.BaseUrl;
import org.springframework.boot.test.http.server.BaseUrlProviders;
import org.springframework.boot.test.web.servlet.client.BaseUrlUriBuilderFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
@ -39,7 +41,7 @@ abstract class AbstractSampleActuatorCustomSecurityTests {
abstract String getManagementPath();
abstract Environment getEnvironment();
abstract ApplicationContext getApplicationContext();
@Test
void homeIsSecure() {
@ -200,7 +202,8 @@ abstract class AbstractSampleActuatorCustomSecurityTests {
}
private TestRestTemplate configure(TestRestTemplate restTemplate) {
restTemplate.setUriTemplateHandler(new LocalHostUriTemplateHandler(getEnvironment()));
BaseUrl baseUrl = new BaseUrlProviders(getApplicationContext()).getBaseUrlOrDefault();
restTemplate.setUriTemplateHandler(BaseUrlUriBuilderFactory.get(baseUrl));
return restTemplate;
}

View File

@ -26,7 +26,9 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.restclient.RestTemplateBuilder;
import org.springframework.boot.restclient.test.TestRestTemplate;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.web.server.test.client.LocalHostUriTemplateHandler;
import org.springframework.boot.test.http.server.BaseUrl;
import org.springframework.boot.test.http.server.BaseUrlProviders;
import org.springframework.boot.test.web.servlet.client.BaseUrlUriBuilderFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.http.HttpStatus;
import org.springframework.http.RequestEntity;
@ -52,9 +54,8 @@ class CorsSampleActuatorApplicationTests {
@BeforeEach
void setUp() {
RestTemplateBuilder builder = new RestTemplateBuilder();
LocalHostUriTemplateHandler handler = new LocalHostUriTemplateHandler(this.applicationContext.getEnvironment(),
"http");
builder = builder.uriTemplateHandler(handler);
BaseUrl baseUrl = new BaseUrlProviders(this.applicationContext).getBaseUrlOrDefault();
builder = builder.uriTemplateHandler(BaseUrlUriBuilderFactory.get(baseUrl));
this.testRestTemplate = new TestRestTemplate(builder);
}

View File

@ -19,7 +19,7 @@ package smoketest.actuator.customsecurity;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.web.server.LocalServerPort;
import org.springframework.core.env.Environment;
import org.springframework.context.ApplicationContext;
/**
* Integration tests for actuator endpoints with custom dispatcher servlet path.
@ -34,7 +34,7 @@ class CustomServletPathSampleActuatorTests extends AbstractSampleActuatorCustomS
private int port;
@Autowired
private Environment environment;
private ApplicationContext applicationContext;
@Override
String getPath() {
@ -47,8 +47,8 @@ class CustomServletPathSampleActuatorTests extends AbstractSampleActuatorCustomS
}
@Override
Environment getEnvironment() {
return this.environment;
ApplicationContext getApplicationContext() {
return this.applicationContext;
}
}

View File

@ -24,7 +24,7 @@ import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
import org.springframework.boot.test.web.server.LocalManagementPort;
import org.springframework.boot.test.web.server.LocalServerPort;
import org.springframework.core.env.Environment;
import org.springframework.context.ApplicationContext;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
@ -48,7 +48,7 @@ class ManagementPortAndPathSampleActuatorApplicationTests extends AbstractSample
private int managementPort;
@Autowired
private Environment environment;
private ApplicationContext applicationContext;
@Test
void testMissing() {
@ -69,8 +69,8 @@ class ManagementPortAndPathSampleActuatorApplicationTests extends AbstractSample
}
@Override
Environment getEnvironment() {
return this.environment;
ApplicationContext getApplicationContext() {
return this.applicationContext;
}
}

View File

@ -23,7 +23,7 @@ import org.springframework.boot.restclient.test.TestRestTemplate;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.web.server.LocalManagementPort;
import org.springframework.boot.test.web.server.LocalServerPort;
import org.springframework.core.env.Environment;
import org.springframework.context.ApplicationContext;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
@ -46,7 +46,7 @@ class ManagementPortCustomServletPathSampleActuatorTests extends AbstractSampleA
private int managementPort;
@Autowired
private Environment environment;
private ApplicationContext applicationContext;
@Test
void actuatorPathOnMainPortShouldNotMatch() {
@ -66,8 +66,8 @@ class ManagementPortCustomServletPathSampleActuatorTests extends AbstractSampleA
}
@Override
Environment getEnvironment() {
return this.environment;
ApplicationContext getApplicationContext() {
return this.applicationContext;
}
}

View File

@ -23,7 +23,7 @@ import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.web.server.LocalServerPort;
import org.springframework.core.env.Environment;
import org.springframework.context.ApplicationContext;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
@ -44,22 +44,7 @@ class SampleActuatorCustomSecurityApplicationTests extends AbstractSampleActuato
private int port;
@Autowired
private Environment environment;
@Override
String getPath() {
return "http://localhost:" + this.port;
}
@Override
String getManagementPath() {
return "http://localhost:" + this.port;
}
@Override
Environment getEnvironment() {
return this.environment;
}
private ApplicationContext applicationContext;
@Test
@SuppressWarnings({ "rawtypes", "unchecked" })
@ -79,4 +64,19 @@ class SampleActuatorCustomSecurityApplicationTests extends AbstractSampleActuato
assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.FORBIDDEN);
}
@Override
String getPath() {
return "http://localhost:" + this.port;
}
@Override
String getManagementPath() {
return "http://localhost:" + this.port;
}
@Override
ApplicationContext getApplicationContext() {
return this.applicationContext;
}
}

View File

@ -25,8 +25,10 @@ import org.springframework.boot.restclient.RestTemplateBuilder;
import org.springframework.boot.restclient.test.TestRestTemplate;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
import org.springframework.boot.web.server.test.client.LocalHostUriTemplateHandler;
import org.springframework.core.env.Environment;
import org.springframework.boot.test.http.server.BaseUrl;
import org.springframework.boot.test.http.server.BaseUrlProviders;
import org.springframework.boot.test.web.servlet.client.BaseUrlUriBuilderFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
@ -36,7 +38,7 @@ import static org.assertj.core.api.Assertions.assertThat;
class SampleActuatorExtensionApplicationTests {
@Autowired
private Environment environment;
private ApplicationContext applicationContext;
@Autowired
private TestRestTemplate restTemplate;
@ -63,7 +65,8 @@ class SampleActuatorExtensionApplicationTests {
void healthExtensionWithAuthHeader() {
TestRestTemplate restTemplate = new TestRestTemplate(
this.restTemplateBuilder.defaultHeader("Authorization", "Bearer secret"));
restTemplate.setUriTemplateHandler(new LocalHostUriTemplateHandler(this.environment));
BaseUrl baseUrl = new BaseUrlProviders(this.applicationContext).getBaseUrlOrDefault();
restTemplate.setUriTemplateHandler(BaseUrlUriBuilderFactory.get(baseUrl));
ResponseEntity<Map> entity = restTemplate.getForEntity("/myextension/health", Map.class);
assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.OK);
}

View File

@ -26,7 +26,9 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.restclient.RestTemplateBuilder;
import org.springframework.boot.restclient.test.TestRestTemplate;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.web.server.test.client.LocalHostUriTemplateHandler;
import org.springframework.boot.test.http.server.BaseUrl;
import org.springframework.boot.test.http.server.BaseUrlProviders;
import org.springframework.boot.test.web.servlet.client.BaseUrlUriBuilderFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.http.HttpStatus;
import org.springframework.http.RequestEntity;
@ -52,9 +54,8 @@ class CorsSampleActuatorApplicationTests {
@BeforeEach
void setUp() {
RestTemplateBuilder builder = new RestTemplateBuilder();
LocalHostUriTemplateHandler handler = new LocalHostUriTemplateHandler(this.applicationContext.getEnvironment(),
"http");
builder = builder.uriTemplateHandler(handler);
BaseUrl baseUrl = new BaseUrlProviders(this.applicationContext).getBaseUrlOrDefault();
builder = builder.uriTemplateHandler(BaseUrlUriBuilderFactory.get(baseUrl));
this.testRestTemplate = new TestRestTemplate(builder);
}