Simplify BaseUrl code and cache resolved URLs

See gh-47680
This commit is contained in:
Phillip Webb
2025-10-17 19:28:18 -07:00
parent d9fc89b013
commit 77f207a079
13 changed files with 42 additions and 79 deletions
@@ -59,7 +59,7 @@ public final class BaseUrlUriBuilderFactory implements UriBuilderFactory {
@Override
public UriBuilder uriString(String uriTemplate) {
return createDelegate().uriString(uriTemplate);
return this.baseUrl.getUriBuilderFactory().uriString(uriTemplate);
}
@Override
@@ -69,16 +69,12 @@ public final class BaseUrlUriBuilderFactory implements UriBuilderFactory {
@Override
public URI expand(String uriTemplate, Map<String, ?> uriVariables) {
return createDelegate().expand(uriTemplate, uriVariables);
return this.baseUrl.getUriBuilderFactory().expand(uriTemplate, uriVariables);
}
@Override
public URI expand(String uriTemplate, @Nullable Object... uriVariables) {
return createDelegate().expand(uriTemplate, uriVariables);
}
private UriBuilderFactory createDelegate() {
return this.baseUrl.getUriBuilderFactory();
return this.baseUrl.getUriBuilderFactory().expand(uriTemplate, uriVariables);
}
}
@@ -20,6 +20,7 @@ import java.util.function.Supplier;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import org.springframework.util.function.SingletonSupplier;
import org.springframework.web.util.DefaultUriBuilderFactory;
import org.springframework.web.util.UriBuilderFactory;
@@ -30,38 +31,53 @@ import org.springframework.web.util.UriBuilderFactory;
* @author Stephane Nicoll
* @since 4.0.0
*/
public interface BaseUrl {
public final class BaseUrl {
/**
* Default base URL suitable for mock environments.
* {@link BaseUrl} that resolves to {@code http://localhost}.
*/
BaseUrl DEFAULT = BaseUrl.of("http://localhost");
public static final BaseUrl LOCALHOST = BaseUrl.of("http://localhost");
private final boolean https;
private final Supplier<String> resolver;
private BaseUrl(boolean https, Supplier<String> resolver) {
this.https = https;
this.resolver = SingletonSupplier.of(resolver);
}
/**
* Return if the URL will ultimately resolve to an HTTPS address.
* @return if the URL is HTTPS
*/
boolean isHttps();
public boolean isHttps() {
return this.https;
}
/**
* Get a {@link UriBuilderFactory} that applies the base URL.
* @return a {@link UriBuilderFactory}
*/
UriBuilderFactory getUriBuilderFactory();
public UriBuilderFactory getUriBuilderFactory() {
return new DefaultUriBuilderFactory(this.resolver.get());
}
/**
* Return a new instance that applies the given {@code path}.
* @param path a path to append
* @return a new instance with the path added
*/
BaseUrl withPath(String path);
public BaseUrl withPath(String path) {
return new BaseUrl(this.https, () -> this.resolver.get() + path);
}
/**
* Factory method to create a new {@link BaseUrl}.
* @param url the URL to use
* @return a new {@link BaseUrl} instance
*/
static BaseUrl of(String url) {
public static BaseUrl of(String url) {
Assert.notNull(url, "'url' must not be null");
return of(StringUtils.startsWithIgnoreCase(url, "https"), () -> url);
}
@@ -72,42 +88,9 @@ public interface BaseUrl {
* @param resolver the resolver used to supply the actual URL
* @return a new {@link BaseUrl} instance
*/
static BaseUrl of(boolean https, Supplier<String> resolver) {
return new DefaultBaseUrl(https, resolver);
}
final class DefaultBaseUrl implements BaseUrl {
private final boolean https;
private final Supplier<String> resolver;
private DefaultBaseUrl(boolean https, Supplier<String> resolver) {
Assert.notNull(resolver, "'resolver' must not be null");
this.https = https;
this.resolver = resolver;
}
@Override
public boolean isHttps() {
return this.https;
}
@Override
public UriBuilderFactory getUriBuilderFactory() {
return new DefaultUriBuilderFactory(resolve());
}
String resolve() {
return this.resolver.get();
}
@Override
public BaseUrl withPath(String path) {
Supplier<String> updatedResolver = () -> this.resolver.get() + path;
return new DefaultBaseUrl(this.https, updatedResolver);
}
public static BaseUrl of(boolean https, Supplier<String> resolver) {
Assert.notNull(resolver, "'resolver' must not be null");
return new BaseUrl(https, resolver);
}
}
@@ -47,14 +47,6 @@ public class BaseUrlProviders {
this.providers = providers;
}
/**
* Return the provided {@link BaseUrl} or {@link BaseUrl#DEFAULT}.
* @return the base URL
*/
public BaseUrl getBaseUrlOrDefault() {
return getBaseUrl(BaseUrl.DEFAULT);
}
/**
* Return the provided {@link BaseUrl} or {@code null}.
* @return the base URL or {@code null}
@@ -75,7 +67,6 @@ public class BaseUrlProviders {
.filter(Objects::nonNull)
.findFirst()
.orElse(fallback);
}
}
@@ -33,11 +33,6 @@ import static org.mockito.Mockito.mock;
*/
class BaseUrlProvidersTests {
@Test
void getBaseUrlOrDefaultWhenNoProvidedBaseUrlReturnsDefault() {
assertThat(new BaseUrlProviders(Collections.emptyList()).getBaseUrlOrDefault()).isSameAs(BaseUrl.DEFAULT);
}
@Test
void getBaseUrlWhenNoProvidedBaseUrlReturnsNull() {
assertThat(new BaseUrlProviders(Collections.emptyList()).getBaseUrl()).isNull();
@@ -70,7 +70,7 @@ class BaseUrlTests {
assertThat(atomicInteger.get()).isZero();
assertThat(baseUrl.isHttps()).isTrue();
assertThat(resolve(baseUrl, "")).isEqualTo("1");
assertThat(resolve(baseUrl, "")).isEqualTo("2");
assertThat(resolve(baseUrl, "")).isEqualTo("1");
}
@Test
@@ -94,7 +94,7 @@ class BaseUrlTests {
BaseUrl baseUrl = BaseUrl.of(true, () -> "https://example.com/" + atomicInteger.incrementAndGet());
assertThat(resolve(baseUrl.withPath("/context"), "")).isEqualTo("https://example.com/1/context");
assertThat(resolve(baseUrl.withPath("/context").withPath("/test"), "/path"))
.isEqualTo("https://example.com/2/context/test/path");
.isEqualTo("https://example.com/1/context/test/path");
}
private String resolve(BaseUrl baseUrl, String path) {
@@ -60,10 +60,9 @@ public final class HttpGraphQlTesterAutoConfiguration {
private WebTestClient configureGraphQlWebTestClient(WebTestClient webTestClient, @Nullable BaseUrl baseUrl,
String graphQlPath) {
WebTestClient.Builder builder = webTestClient.mutate();
if (baseUrl != null) {
return builder.uriBuilderFactory(BaseUrlUriBuilderFactory.get(baseUrl.withPath(graphQlPath))).build();
}
return builder.baseUrl(graphQlPath).build();
return (baseUrl != null)
? builder.uriBuilderFactory(BaseUrlUriBuilderFactory.get(baseUrl.withPath(graphQlPath))).build()
: builder.baseUrl(graphQlPath).build();
}
}
@@ -46,10 +46,9 @@ public final class TestRestTemplateAutoConfiguration {
TestRestTemplate testRestTemplate(ObjectProvider<RestTemplateBuilder> builderProvider,
ApplicationContext applicationContext) {
RestTemplateBuilder builder = builderProvider.getIfAvailable(RestTemplateBuilder::new);
BaseUrl baseUrl = new BaseUrlProviders(applicationContext).getBaseUrlOrDefault();
boolean sslEnabled = baseUrl != null && baseUrl.isHttps();
BaseUrl baseUrl = new BaseUrlProviders(applicationContext).getBaseUrl(BaseUrl.LOCALHOST);
TestRestTemplate template = new TestRestTemplate(builder, null, null,
sslEnabled ? SSL_OPTIONS : DEFAULT_OPTIONS);
baseUrl.isHttps() ? SSL_OPTIONS : DEFAULT_OPTIONS);
template.setUriTemplateHandler(BaseUrlUriBuilderFactory.get(baseUrl));
return template;
}
@@ -46,7 +46,7 @@ public final class MockMvcWebClientAutoConfiguration {
@ConditionalOnMissingBean({ WebClient.class, MockMvcWebClientBuilder.class })
@ConditionalOnBean(MockMvc.class)
MockMvcWebClientBuilder mockMvcWebClientBuilder(MockMvc mockMvc, ApplicationContext applicationContext) {
BaseUrl baseUrl = new BaseUrlProviders(applicationContext).getBaseUrlOrDefault();
BaseUrl baseUrl = new BaseUrlProviders(applicationContext).getBaseUrl(BaseUrl.LOCALHOST);
return MockMvcWebClientBuilder.mockMvcSetup(mockMvc).withDelegate(new BaseUrlWebClient(baseUrl));
}
@@ -49,7 +49,7 @@ public final class MockMvcWebDriverAutoConfiguration {
@ConditionalOnMissingBean({ WebDriver.class, MockMvcHtmlUnitDriverBuilder.class })
@ConditionalOnBean(MockMvc.class)
MockMvcHtmlUnitDriverBuilder mockMvcHtmlUnitDriverBuilder(MockMvc mockMvc, ApplicationContext applicationContext) {
BaseUrl baseUrl = new BaseUrlProviders(applicationContext).getBaseUrlOrDefault();
BaseUrl baseUrl = new BaseUrlProviders(applicationContext).getBaseUrl(BaseUrl.LOCALHOST);
MockMvcHtmlUnitDriverBuilder builder = MockMvcHtmlUnitDriverBuilder.mockMvcSetup(mockMvc)
.withDelegate(new BaseUrlWebConnectionHtmlUnitDriver(baseUrl, BrowserVersion.CHROME));
return builder;
@@ -202,7 +202,7 @@ abstract class AbstractSampleActuatorCustomSecurityTests {
}
private TestRestTemplate configure(TestRestTemplate restTemplate) {
BaseUrl baseUrl = new BaseUrlProviders(getApplicationContext()).getBaseUrlOrDefault();
BaseUrl baseUrl = new BaseUrlProviders(getApplicationContext()).getBaseUrl();
restTemplate.setUriTemplateHandler(BaseUrlUriBuilderFactory.get(baseUrl));
return restTemplate;
}
@@ -56,7 +56,7 @@ class CorsSampleActuatorApplicationTests {
@BeforeEach
void setUp() {
RestTemplateBuilder builder = new RestTemplateBuilder();
BaseUrl baseUrl = new BaseUrlProviders(this.applicationContext).getBaseUrlOrDefault();
BaseUrl baseUrl = new BaseUrlProviders(this.applicationContext).getBaseUrl();
builder = builder.uriTemplateHandler(BaseUrlUriBuilderFactory.get(baseUrl));
this.testRestTemplate = new TestRestTemplate(builder);
}
@@ -67,7 +67,7 @@ class SampleActuatorExtensionApplicationTests {
void healthExtensionWithAuthHeader() {
TestRestTemplate restTemplate = new TestRestTemplate(
this.restTemplateBuilder.defaultHeader("Authorization", "Bearer secret"));
BaseUrl baseUrl = new BaseUrlProviders(this.applicationContext).getBaseUrlOrDefault();
BaseUrl baseUrl = new BaseUrlProviders(this.applicationContext).getBaseUrl();
restTemplate.setUriTemplateHandler(BaseUrlUriBuilderFactory.get(baseUrl));
ResponseEntity<Map> entity = restTemplate.getForEntity("/myextension/health", Map.class);
assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.OK);
@@ -56,7 +56,7 @@ class CorsSampleActuatorApplicationTests {
@BeforeEach
void setUp() {
RestTemplateBuilder builder = new RestTemplateBuilder();
BaseUrl baseUrl = new BaseUrlProviders(this.applicationContext).getBaseUrlOrDefault();
BaseUrl baseUrl = new BaseUrlProviders(this.applicationContext).getBaseUrl();
builder = builder.uriTemplateHandler(BaseUrlUriBuilderFactory.get(baseUrl));
this.testRestTemplate = new TestRestTemplate(builder);
}