Merge branch '4.1.x'

Closes gh-51122
This commit is contained in:
Stéphane Nicoll
2026-07-24 15:19:38 +02:00
24 changed files with 292 additions and 230 deletions
@@ -28,11 +28,10 @@ import org.gradle.api.DefaultTask;
import org.gradle.api.tasks.TaskAction;
import org.gradle.internal.impldep.org.apache.http.client.config.CookieSpecs;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.web.client.NoOpResponseErrorHandler;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.client.RestClient;
import org.springframework.web.client.RestClient.ResponseSpec.ErrorHandler;
/**
* Task to check that links are working.
@@ -42,6 +41,9 @@ import org.springframework.web.client.RestTemplate;
*/
public abstract class CheckLinks extends DefaultTask {
private static final ErrorHandler NOOP_ERROR_HANDLER = (request, response) -> {
};
private final BomExtension bom;
@Inject
@@ -54,14 +56,16 @@ public abstract class CheckLinks extends DefaultTask {
RequestConfig config = RequestConfig.custom().setCookieSpec(CookieSpecs.IGNORE_COOKIES).build();
CloseableHttpClient httpClient = HttpClients.custom().setDefaultRequestConfig(config).build();
HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory(httpClient);
RestTemplate restTemplate = new RestTemplate(requestFactory);
restTemplate.setErrorHandler(new NoOpResponseErrorHandler());
RestClient restClient = RestClient.builder()
.requestFactory(requestFactory)
.defaultStatusHandler((status) -> true, NOOP_ERROR_HANDLER)
.build();
for (Library library : this.bom.getLibraries()) {
library.getLinks().forEach((name, links) -> links.forEach((link) -> {
URI uri;
try {
uri = new URI(link.url(library));
ResponseEntity<String> response = restTemplate.exchange(uri, HttpMethod.HEAD, null, String.class);
ResponseEntity<String> response = restClient.head().uri(uri).retrieve().toEntity(String.class);
System.out.printf("[%3d] %s - %s (%s)%n", response.getStatusCode().value(), library.getName(), name,
uri);
}
@@ -18,7 +18,6 @@ package org.springframework.boot.build.bom.bomr;
import java.net.URI;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import java.util.SortedSet;
@@ -37,13 +36,10 @@ import org.w3c.dom.NodeList;
import org.springframework.boot.build.bom.bomr.version.DependencyVersion;
import org.springframework.boot.build.xml.XmlDocument;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.web.client.HttpClientErrorException;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.client.RestClient;
import org.springframework.web.util.UriComponentsBuilder;
/**
@@ -54,16 +50,19 @@ import org.springframework.web.util.UriComponentsBuilder;
*/
final class MavenMetadataVersionResolver implements VersionResolver {
private final RestTemplate rest;
private final RestClient rest;
private final Collection<MavenArtifactRepository> repositories;
MavenMetadataVersionResolver(Collection<MavenArtifactRepository> repositories) {
this(new RestTemplate(Collections.singletonList(new StringHttpMessageConverter())), repositories);
this(RestClient.builder()
.configureMessageConverters(
(converters) -> converters.disableDefaults().withStringConverter(new StringHttpMessageConverter()))
.build(), repositories);
}
MavenMetadataVersionResolver(RestTemplate restTemplate, Collection<MavenArtifactRepository> repositories) {
this.rest = restTemplate;
MavenMetadataVersionResolver(RestClient restClient, Collection<MavenArtifactRepository> repositories) {
this.rest = restClient;
this.repositories = repositories;
}
@@ -83,14 +82,13 @@ final class MavenMetadataVersionResolver implements VersionResolver {
.build()
.toUri();
try {
HttpHeaders headers = new HttpHeaders();
PasswordCredentials credentials = credentialsOf(repository);
String username = (credentials != null) ? credentials.getUsername() : null;
if (username != null) {
headers.setBasicAuth(username, credentials.getPassword());
}
HttpEntity<Void> request = new HttpEntity<>(headers);
String metadata = this.rest.exchange(url, HttpMethod.GET, request, String.class).getBody();
String metadata = this.rest.get().uri(url).headers((headers) -> {
PasswordCredentials credentials = credentialsOf(repository);
String username = (credentials != null) ? credentials.getUsername() : null;
if (username != null) {
headers.setBasicAuth(username, credentials.getPassword());
}
}).retrieve().body(String.class);
Document metadataDocument = XmlDocument.parseContent(metadata);
NodeList versionNodes = (NodeList) XPathFactory.newInstance()
.newXPath()
@@ -28,8 +28,7 @@ import java.util.regex.Pattern;
import org.springframework.boot.build.bom.bomr.version.DependencyVersion;
import org.springframework.http.ResponseEntity;
import org.springframework.util.LinkedCaseInsensitiveMap;
import org.springframework.web.client.RestOperations;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.client.RestClient;
/**
* Release schedule for Spring projects, retrieved from
@@ -41,20 +40,22 @@ class ReleaseSchedule {
private static final Pattern LIBRARY_AND_VERSION = Pattern.compile("([A-Za-z0-9 ]+) ([0-9A-Za-z.-]+)");
private final RestOperations rest;
private final RestClient rest;
ReleaseSchedule() {
this(new RestTemplate());
this(RestClient.create());
}
ReleaseSchedule(RestOperations rest) {
ReleaseSchedule(RestClient rest) {
this.rest = rest;
}
@SuppressWarnings({ "unchecked", "rawtypes" })
Map<String, List<Release>> releasesBetween(OffsetDateTime start, OffsetDateTime end) {
ResponseEntity<List> response = this.rest
.getForEntity("https://calendar.spring.io/releases?start=" + start + "&end=" + end, List.class);
ResponseEntity<List> response = this.rest.get()
.uri("https://calendar.spring.io/releases?start=" + start + "&end=" + end)
.retrieve()
.toEntity(List.class);
List<Map<String, String>> body = response.getBody();
Map<String, List<Release>> releasesByLibrary = new LinkedCaseInsensitiveMap<>();
body.stream()
@@ -20,7 +20,7 @@ import java.util.Collections;
import java.util.List;
import java.util.Map;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.client.RestClient;
/**
* Minimal representation of a GitHub issue.
@@ -29,7 +29,7 @@ import org.springframework.web.client.RestTemplate;
*/
public class Issue {
private final RestTemplate rest;
private final RestClient rest;
private final int number;
@@ -37,7 +37,7 @@ public class Issue {
private final State state;
Issue(RestTemplate rest, int number, String title, State state) {
Issue(RestClient rest, int number, String title, State state) {
this.rest = rest;
this.number = number;
this.title = title;
@@ -62,7 +62,7 @@ public class Issue {
*/
public void label(List<String> labels) {
Map<String, List<String>> body = Collections.singletonMap("labels", labels);
this.rest.put("issues/" + this.number + "/labels", body);
this.rest.put().uri("issues/" + this.number + "/labels").body(body).retrieve().toBodilessEntity();
}
public enum State {
@@ -17,13 +17,10 @@
package org.springframework.boot.build.bom.bomr.github;
import java.util.Base64;
import java.util.Collections;
import org.springframework.http.MediaType;
import org.springframework.http.converter.json.JacksonJsonHttpMessageConverter;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.util.DefaultUriBuilderFactory;
import org.springframework.web.util.UriTemplateHandler;
import org.springframework.web.client.RestClient;
/**
* Standard implementation of {@link GitHub}.
@@ -43,24 +40,24 @@ final class StandardGitHub implements GitHub {
@Override
public GitHubRepository getRepository(String organization, String name) {
RestTemplate restTemplate = createRestTemplate();
restTemplate.getInterceptors().add((request, body, execution) -> {
request.getHeaders().add("User-Agent", StandardGitHub.this.username);
request.getHeaders()
.add("Authorization", "Basic " + Base64.getEncoder()
.encodeToString((StandardGitHub.this.username + ":" + StandardGitHub.this.password).getBytes()));
request.getHeaders().add("Accept", MediaType.APPLICATION_JSON_VALUE);
return execution.execute(request, body);
});
UriTemplateHandler uriTemplateHandler = new DefaultUriBuilderFactory(
"https://api.github.com/repos/" + organization + "/" + name + "/");
restTemplate.setUriTemplateHandler(uriTemplateHandler);
return new StandardGitHubRepository(restTemplate);
return new StandardGitHubRepository(createRestClient(organization, name));
}
@SuppressWarnings({ "deprecation", "removal" })
private RestTemplate createRestTemplate() {
return new RestTemplate(Collections.singletonList(new JacksonJsonHttpMessageConverter()));
private RestClient createRestClient(String organization, String name) {
return RestClient.builder()
.baseUrl("https://api.github.com/repos/" + organization + "/" + name + "/")
.configureMessageConverters((converters) -> converters.disableDefaults()
.withJsonConverter(new JacksonJsonHttpMessageConverter()))
.requestInterceptor((request, body, execution) -> {
request.getHeaders().add("User-Agent", StandardGitHub.this.username);
request.getHeaders()
.add("Authorization", "Basic " + Base64.getEncoder()
.encodeToString(
(StandardGitHub.this.username + ":" + StandardGitHub.this.password).getBytes()));
request.getHeaders().add("Accept", MediaType.APPLICATION_JSON_VALUE);
return execution.execute(request, body);
})
.build();
}
}
@@ -27,8 +27,8 @@ import java.util.function.Function;
import org.springframework.http.ResponseEntity;
import org.springframework.web.client.HttpClientErrorException.Forbidden;
import org.springframework.web.client.RestClient;
import org.springframework.web.client.RestClientException;
import org.springframework.web.client.RestTemplate;
/**
* Standard implementation of {@link GitHubRepository}.
@@ -37,10 +37,10 @@ import org.springframework.web.client.RestTemplate;
*/
final class StandardGitHubRepository implements GitHubRepository {
private final RestTemplate rest;
private final RestClient rest;
StandardGitHubRepository(RestTemplate restTemplate) {
this.rest = restTemplate;
StandardGitHubRepository(RestClient restClient) {
this.rest = restClient;
}
@Override
@@ -56,7 +56,11 @@ final class StandardGitHubRepository implements GitHubRepository {
}
requestBody.put("body", body);
try {
ResponseEntity<Map> response = this.rest.postForEntity("issues", requestBody, Map.class);
ResponseEntity<Map> response = this.rest.post()
.uri("issues")
.body(requestBody)
.retrieve()
.toEntity(Map.class);
// See gh-30304
sleep(Duration.ofSeconds(3));
return (Integer) response.getBody().get("number");
@@ -92,7 +96,7 @@ final class StandardGitHubRepository implements GitHubRepository {
@SuppressWarnings({ "rawtypes", "unchecked" })
private <T> List<T> get(String name, Function<Map<String, Object>, T> mapper) {
ResponseEntity<List> response = this.rest.getForEntity(name, List.class);
ResponseEntity<List> response = this.rest.get().uri(name).retrieve().toEntity(List.class);
return ((List<Map<String, Object>>) response.getBody()).stream().map(mapper).toList();
}
@@ -26,7 +26,7 @@ import org.springframework.boot.build.bom.bomr.ReleaseSchedule.Release;
import org.springframework.core.io.ClassPathResource;
import org.springframework.http.MediaType;
import org.springframework.test.web.client.MockRestServiceServer;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.client.RestClient;
import static org.assertj.core.api.Assertions.assertThat;
import static org.springframework.test.web.client.match.MockRestRequestMatchers.requestTo;
@@ -39,11 +39,11 @@ import static org.springframework.test.web.client.response.MockRestResponseCreat
*/
class ReleaseScheduleTests {
private final RestTemplate rest = new RestTemplate();
private final RestClient.Builder restBuilder = RestClient.builder();
private final ReleaseSchedule releaseSchedule = new ReleaseSchedule(this.rest);
private final MockRestServiceServer server = MockRestServiceServer.bindTo(this.restBuilder).build();
private final MockRestServiceServer server = MockRestServiceServer.bindTo(this.rest).build();
private final ReleaseSchedule releaseSchedule = new ReleaseSchedule(this.restBuilder.build());
@Test
void releasesBetween() {
@@ -45,7 +45,7 @@ import org.springframework.test.context.support.AbstractContextLoader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.client.RestClient;
import org.springframework.web.servlet.DispatcherServlet;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
@@ -93,9 +93,11 @@ class ErrorPageFilterIntegrationTests {
private void doTest(AnnotationConfigServletWebServerApplicationContext context, String resourcePath,
HttpStatus status) throws Exception {
int port = context.getWebServer().getPort();
RestTemplate template = new RestTemplate();
ResponseEntity<String> entity = template.getForEntity(new URI("http://localhost:" + port + resourcePath),
String.class);
RestClient restClient = RestClient.create();
ResponseEntity<String> entity = restClient.get()
.uri(new URI("http://localhost:" + port + resourcePath))
.retrieve()
.toEntity(String.class);
assertThat(entity.getBody()).isEqualTo("Hello World");
assertThat(entity.getStatusCode()).isEqualTo(status);
}
@@ -19,7 +19,6 @@ package org.springframework.boot.context.embedded;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.net.URI;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Arrays;
@@ -49,9 +48,10 @@ import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.util.FileSystemUtils;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.client.NoOpResponseErrorHandler;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.util.UriTemplateHandler;
import org.springframework.web.client.RestClient;
import org.springframework.web.client.RestClient.ResponseSpec.ErrorHandler;
import org.springframework.web.util.DefaultUriBuilderFactory;
import org.springframework.web.util.DefaultUriBuilderFactory.EncodingMode;
/**
* {@link TestTemplateInvocationContextProvider} for templated
@@ -152,7 +152,7 @@ class EmbeddedServerContainerInvocationContextProvider
@Override
public List<Extension> getAdditionalExtensions() {
return Arrays.asList(this.launcher, new RestTemplateParameterResolver(this.launcher));
return Arrays.asList(this.launcher, new RestClientParameterResolver(this.launcher));
}
@Override
@@ -165,7 +165,7 @@ class EmbeddedServerContainerInvocationContextProvider
if (parameterContext.getParameter().getType().equals(AbstractApplicationLauncher.class)) {
return true;
}
return parameterContext.getParameter().getType().equals(RestTemplate.class);
return parameterContext.getParameter().getType().equals(RestClient.class);
}
@Override
@@ -178,41 +178,35 @@ class EmbeddedServerContainerInvocationContextProvider
}
private static final class RestTemplateParameterResolver implements ParameterResolver {
private static final class RestClientParameterResolver implements ParameterResolver {
private static final ErrorHandler NOOP_ERROR_HANDLER = (request, response) -> {
};
private final AbstractApplicationLauncher launcher;
private RestTemplateParameterResolver(AbstractApplicationLauncher launcher) {
private RestClientParameterResolver(AbstractApplicationLauncher launcher) {
this.launcher = launcher;
}
@Override
public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) {
return parameterContext.getParameter().getType().equals(RestTemplate.class);
return parameterContext.getParameter().getType().equals(RestClient.class);
}
@Override
public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) {
RestTemplate rest = new RestTemplate(new HttpComponentsClientHttpRequestFactory(HttpClients.custom()
.setRetryStrategy(new DefaultHttpRequestRetryStrategy(10, TimeValue.of(1, TimeUnit.SECONDS)))
.build()));
rest.setErrorHandler(new NoOpResponseErrorHandler());
rest.setUriTemplateHandler(new UriTemplateHandler() {
@Override
public URI expand(String uriTemplate, Object... uriVariables) {
return URI.create("http://localhost:" + RestTemplateParameterResolver.this.launcher.getHttpPort()
+ uriTemplate);
}
@Override
public URI expand(String uriTemplate, Map<String, ?> uriVariables) {
return URI.create("http://localhost:" + RestTemplateParameterResolver.this.launcher.getHttpPort()
+ uriTemplate);
}
});
return rest;
DefaultUriBuilderFactory uriBuilderFactory = new DefaultUriBuilderFactory(
"http://localhost:" + this.launcher.getHttpPort());
// Do not double encode test paths
uriBuilderFactory.setEncodingMode(EncodingMode.NONE);
return RestClient.builder()
.uriBuilderFactory(uriBuilderFactory)
.requestFactory(new HttpComponentsClientHttpRequestFactory(HttpClients.custom()
.setRetryStrategy(new DefaultHttpRequestRetryStrategy(10, TimeValue.of(1, TimeUnit.SECONDS)))
.build()))
.defaultStatusHandler((status) -> true, NOOP_ERROR_HANDLER)
.build();
}
}
@@ -22,7 +22,7 @@ import org.junit.jupiter.api.condition.OS;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.client.RestClient;
import static org.assertj.core.api.Assertions.assertThat;
@@ -37,25 +37,31 @@ import static org.assertj.core.api.Assertions.assertThat;
class EmbeddedServletContainerJarDevelopmentIntegrationTests {
@TestTemplate
void metaInfResourceFromDependencyIsAvailableViaHttp(RestTemplate rest) {
ResponseEntity<String> entity = rest.getForEntity("/nested-meta-inf-resource.txt", String.class);
void metaInfResourceFromDependencyIsAvailableViaHttp(RestClient rest) {
ResponseEntity<String> entity = rest.get()
.uri("/nested-meta-inf-resource.txt")
.retrieve()
.toEntity(String.class);
assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.OK);
}
@TestTemplate
@DisabledOnOs(OS.WINDOWS)
void metaInfResourceFromDependencyWithNameThatContainsReservedCharactersIsAvailableViaHttp(RestTemplate rest) {
ResponseEntity<String> entity = rest.getForEntity(
"/nested-reserved-%21%23%24%25%26%28%29%2A%2B%2C%3A%3D%3F%40%5B%5D-meta-inf-resource.txt",
String.class);
void metaInfResourceFromDependencyWithNameThatContainsReservedCharactersIsAvailableViaHttp(RestClient rest) {
ResponseEntity<String> entity = rest.get()
.uri("/nested-reserved-%21%23%24%25%26%28%29%2A%2B%2C%3A%3D%3F%40%5B%5D-meta-inf-resource.txt")
.retrieve()
.toEntity(String.class);
assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.OK);
assertThat(entity.getBody()).isEqualTo("encoded-name");
}
@TestTemplate
void metaInfResourceFromDependencyIsAvailableViaServletContext(RestTemplate rest) {
ResponseEntity<String> entity = rest.getForEntity("/servletContext?/nested-meta-inf-resource.txt",
String.class);
void metaInfResourceFromDependencyIsAvailableViaServletContext(RestClient rest) {
ResponseEntity<String> entity = rest.get()
.uri("/servletContext?/nested-meta-inf-resource.txt")
.retrieve()
.toEntity(String.class);
assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.OK);
}
@@ -22,7 +22,7 @@ import org.junit.jupiter.api.condition.OS;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.client.RestClient;
import static org.assertj.core.api.Assertions.assertThat;
@@ -37,51 +37,64 @@ import static org.assertj.core.api.Assertions.assertThat;
class EmbeddedServletContainerJarPackagingIntegrationTests {
@TestTemplate
void nestedMetaInfResourceIsAvailableViaHttp(RestTemplate rest) {
ResponseEntity<String> entity = rest.getForEntity("/nested-meta-inf-resource.txt", String.class);
void nestedMetaInfResourceIsAvailableViaHttp(RestClient rest) {
ResponseEntity<String> entity = rest.get()
.uri("/nested-meta-inf-resource.txt")
.retrieve()
.toEntity(String.class);
assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.OK);
}
@TestTemplate
@DisabledOnOs(OS.WINDOWS)
void nestedMetaInfResourceWithNameThatContainsReservedCharactersIsAvailableViaHttp(RestTemplate rest) {
ResponseEntity<String> entity = rest.getForEntity(
"/nested-reserved-%21%23%24%25%26%28%29%2A%2B%2C%3A%3D%3F%40%5B%5D-meta-inf-resource.txt",
String.class);
void nestedMetaInfResourceWithNameThatContainsReservedCharactersIsAvailableViaHttp(RestClient rest) {
ResponseEntity<String> entity = rest.get()
.uri("/nested-reserved-%21%23%24%25%26%28%29%2A%2B%2C%3A%3D%3F%40%5B%5D-meta-inf-resource.txt")
.retrieve()
.toEntity(String.class);
assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.OK);
assertThat(entity.getBody()).isEqualTo("encoded-name");
}
@TestTemplate
void nestedMetaInfResourceIsAvailableViaServletContext(RestTemplate rest) {
ResponseEntity<String> entity = rest.getForEntity("/servletContext?/nested-meta-inf-resource.txt",
String.class);
void nestedMetaInfResourceIsAvailableViaServletContext(RestClient rest) {
ResponseEntity<String> entity = rest.get()
.uri("/servletContext?/nested-meta-inf-resource.txt")
.retrieve()
.toEntity(String.class);
assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.OK);
}
@TestTemplate
void nestedJarIsNotAvailableViaHttp(RestTemplate rest) {
ResponseEntity<String> entity = rest.getForEntity("/BOOT-INF/lib/resources-1.0.jar", String.class);
void nestedJarIsNotAvailableViaHttp(RestClient rest) {
ResponseEntity<String> entity = rest.get()
.uri("/BOOT-INF/lib/resources-1.0.jar")
.retrieve()
.toEntity(String.class);
assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.NOT_FOUND);
}
@TestTemplate
void applicationClassesAreNotAvailableViaHttp(RestTemplate rest) {
ResponseEntity<String> entity = rest
.getForEntity("/BOOT-INF/classes/com/example/ResourceHandlingApplication.class", String.class);
void applicationClassesAreNotAvailableViaHttp(RestClient rest) {
ResponseEntity<String> entity = rest.get()
.uri("/BOOT-INF/classes/com/example/ResourceHandlingApplication.class")
.retrieve()
.toEntity(String.class);
assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.NOT_FOUND);
}
@TestTemplate
void launcherIsNotAvailableViaHttp(RestTemplate rest) {
ResponseEntity<String> entity = rest.getForEntity("/org/springframework/boot/loader/Launcher.class",
String.class);
void launcherIsNotAvailableViaHttp(RestClient rest) {
ResponseEntity<String> entity = rest.get()
.uri("/org/springframework/boot/loader/Launcher.class")
.retrieve()
.toEntity(String.class);
assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.NOT_FOUND);
}
@TestTemplate
void conditionalOnWarDeploymentBeanIsNotAvailableForEmbeddedServer(RestTemplate rest) {
ResponseEntity<String> entity = rest.getForEntity("/war", String.class);
void conditionalOnWarDeploymentBeanIsNotAvailableForEmbeddedServer(RestClient rest) {
ResponseEntity<String> entity = rest.get().uri("/war").retrieve().toEntity(String.class);
assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.NOT_FOUND);
}
@@ -28,7 +28,7 @@ import org.junit.jupiter.api.condition.OS;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.client.RestClient;
import static org.assertj.core.api.Assertions.assertThat;
@@ -43,37 +43,43 @@ import static org.assertj.core.api.Assertions.assertThat;
class EmbeddedServletContainerWarDevelopmentIntegrationTests {
@TestTemplate
void metaInfResourceFromDependencyIsAvailableViaHttp(RestTemplate rest) {
ResponseEntity<String> entity = rest.getForEntity("/nested-meta-inf-resource.txt", String.class);
void metaInfResourceFromDependencyIsAvailableViaHttp(RestClient rest) {
ResponseEntity<String> entity = rest.get()
.uri("/nested-meta-inf-resource.txt")
.retrieve()
.toEntity(String.class);
assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.OK);
}
@TestTemplate
@DisabledOnOs(OS.WINDOWS)
void metaInfResourceFromDependencyWithNameThatContainsReservedCharactersIsAvailableViaHttp(RestTemplate rest) {
ResponseEntity<String> entity = rest.getForEntity(
"/nested-reserved-%21%23%24%25%26%28%29%2A%2B%2C%3A%3D%3F%40%5B%5D-meta-inf-resource.txt",
String.class);
void metaInfResourceFromDependencyWithNameThatContainsReservedCharactersIsAvailableViaHttp(RestClient rest) {
ResponseEntity<String> entity = rest.get()
.uri("/nested-reserved-%21%23%24%25%26%28%29%2A%2B%2C%3A%3D%3F%40%5B%5D-meta-inf-resource.txt")
.retrieve()
.toEntity(String.class);
assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.OK);
assertThat(entity.getBody()).isEqualTo("encoded-name");
}
@TestTemplate
void metaInfResourceFromDependencyIsAvailableViaServletContext(RestTemplate rest) {
ResponseEntity<String> entity = rest.getForEntity("/servletContext?/nested-meta-inf-resource.txt",
String.class);
void metaInfResourceFromDependencyIsAvailableViaServletContext(RestClient rest) {
ResponseEntity<String> entity = rest.get()
.uri("/servletContext?/nested-meta-inf-resource.txt")
.retrieve()
.toEntity(String.class);
assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.OK);
}
@TestTemplate
void webappResourcesAreAvailableViaHttp(RestTemplate rest) {
ResponseEntity<String> entity = rest.getForEntity("/webapp-resource.txt", String.class);
void webappResourcesAreAvailableViaHttp(RestClient rest) {
ResponseEntity<String> entity = rest.get().uri("/webapp-resource.txt").retrieve().toEntity(String.class);
assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.OK);
}
@TestTemplate
void loaderClassesAreNotAvailableViaResourcePaths(RestTemplate rest) {
ResponseEntity<String> entity = rest.getForEntity("/resourcePaths", String.class);
void loaderClassesAreNotAvailableViaResourcePaths(RestClient rest) {
ResponseEntity<String> entity = rest.get().uri("/resourcePaths").retrieve().toEntity(String.class);
assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.OK);
assertThat(readLines(entity.getBody()))
.noneMatch((resourcePath) -> resourcePath.startsWith("/org/springframework/boot/loader"));
@@ -28,7 +28,7 @@ import org.junit.jupiter.api.condition.OS;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.client.RestClient;
import static org.assertj.core.api.Assertions.assertThat;
@@ -43,68 +43,85 @@ import static org.assertj.core.api.Assertions.assertThat;
class EmbeddedServletContainerWarPackagingIntegrationTests {
@TestTemplate
void nestedMetaInfResourceIsAvailableViaHttp(RestTemplate rest) {
ResponseEntity<String> entity = rest.getForEntity("/nested-meta-inf-resource.txt", String.class);
void nestedMetaInfResourceIsAvailableViaHttp(RestClient rest) {
ResponseEntity<String> entity = rest.get()
.uri("/nested-meta-inf-resource.txt")
.retrieve()
.toEntity(String.class);
assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.OK);
}
@TestTemplate
@DisabledOnOs(OS.WINDOWS)
void nestedMetaInfResourceWithNameThatContainsReservedCharactersIsAvailableViaHttp(RestTemplate rest) {
ResponseEntity<String> entity = rest.getForEntity(
"/nested-reserved-%21%23%24%25%26%28%29%2A%2B%2C%3A%3D%3F%40%5B%5D-meta-inf-resource.txt",
String.class);
void nestedMetaInfResourceWithNameThatContainsReservedCharactersIsAvailableViaHttp(RestClient rest) {
ResponseEntity<String> entity = rest.get()
.uri("/nested-reserved-%21%23%24%25%26%28%29%2A%2B%2C%3A%3D%3F%40%5B%5D-meta-inf-resource.txt")
.retrieve()
.toEntity(String.class);
assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.OK);
assertThat(entity.getBody()).isEqualTo("encoded-name");
}
@TestTemplate
void nestedMetaInfResourceIsAvailableViaServletContext(RestTemplate rest) {
ResponseEntity<String> entity = rest.getForEntity("/servletContext?/nested-meta-inf-resource.txt",
String.class);
void nestedMetaInfResourceIsAvailableViaServletContext(RestClient rest) {
ResponseEntity<String> entity = rest.get()
.uri("/servletContext?/nested-meta-inf-resource.txt")
.retrieve()
.toEntity(String.class);
assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.OK);
}
@TestTemplate
void nestedJarIsNotAvailableViaHttp(RestTemplate rest) {
ResponseEntity<String> entity = rest.getForEntity("/WEB-INF/lib/resources-1.0.jar", String.class);
void nestedJarIsNotAvailableViaHttp(RestClient rest) {
ResponseEntity<String> entity = rest.get()
.uri("/WEB-INF/lib/resources-1.0.jar")
.retrieve()
.toEntity(String.class);
assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.NOT_FOUND);
}
@TestTemplate
void applicationClassesAreNotAvailableViaHttp(RestTemplate rest) {
ResponseEntity<String> entity = rest
.getForEntity("/WEB-INF/classes/com/example/ResourceHandlingApplication.class", String.class);
void applicationClassesAreNotAvailableViaHttp(RestClient rest) {
ResponseEntity<String> entity = rest.get()
.uri("/WEB-INF/classes/com/example/ResourceHandlingApplication.class")
.retrieve()
.toEntity(String.class);
assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.NOT_FOUND);
}
@TestTemplate
void webappResourcesAreAvailableViaHttp(RestTemplate rest) {
ResponseEntity<String> entity = rest.getForEntity("/webapp-resource.txt", String.class);
void webappResourcesAreAvailableViaHttp(RestClient rest) {
ResponseEntity<String> entity = rest.get().uri("/webapp-resource.txt").retrieve().toEntity(String.class);
assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.OK);
}
@TestTemplate
void loaderClassesAreNotAvailableViaHttp(RestTemplate rest) {
ResponseEntity<String> entity = rest.getForEntity("/org/springframework/boot/loader/Launcher.class",
String.class);
void loaderClassesAreNotAvailableViaHttp(RestClient rest) {
ResponseEntity<String> entity = rest.get()
.uri("/org/springframework/boot/loader/Launcher.class")
.retrieve()
.toEntity(String.class);
assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.NOT_FOUND);
entity = rest.getForEntity("/org/springframework/../springframework/boot/loader/Launcher.class", String.class);
entity = rest.get()
.uri("/org/springframework/../springframework/boot/loader/Launcher.class")
.retrieve()
.toEntity(String.class);
assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.NOT_FOUND);
}
@TestTemplate
void loaderClassesAreNotAvailableViaResourcePaths(RestTemplate rest) {
ResponseEntity<String> entity = rest.getForEntity("/resourcePaths", String.class);
void loaderClassesAreNotAvailableViaResourcePaths(RestClient rest) {
ResponseEntity<String> entity = rest.get().uri("/resourcePaths").retrieve().toEntity(String.class);
assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.OK);
assertThat(readLines(entity.getBody()))
.noneMatch((resourcePath) -> resourcePath.startsWith("/org/springframework/boot/loader"));
}
@TestTemplate
void conditionalOnWarDeploymentBeanIsNotAvailableForEmbeddedServer(RestTemplate rest) {
assertThat(rest.getForEntity("/always", String.class).getStatusCode()).isEqualTo(HttpStatus.OK);
assertThat(rest.getForEntity("/conditionalOnWar", String.class).getStatusCode())
void conditionalOnWarDeploymentBeanIsNotAvailableForEmbeddedServer(RestClient rest) {
assertThat(rest.get().uri("/always").retrieve().toEntity(String.class).getStatusCode())
.isEqualTo(HttpStatus.OK);
assertThat(rest.get().uri("/conditionalOnWar").retrieve().toEntity(String.class).getStatusCode())
.isEqualTo(HttpStatus.NOT_FOUND);
}
@@ -34,7 +34,7 @@ import org.springframework.test.web.servlet.client.RestTestClient;
import org.springframework.test.web.servlet.client.RestTestClient.ResponseSpec;
import org.springframework.test.web.servlet.client.assertj.RestTestClientResponse;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.client.RestClient;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.WebApplicationContextUtils;
import org.springframework.web.servlet.DispatcherServlet;
@@ -81,7 +81,11 @@ abstract class AbstractSpringBootTestWebServerWebEnvironmentTests {
@Test
void runAndTestHttpEndpoint() {
assertThat(this.port).isNotEqualTo(8080).isNotZero();
String body = new RestTemplate().getForObject("http://localhost:" + this.port + "/", String.class);
String body = RestClient.create()
.get()
.uri("http://localhost:" + this.port + "/")
.retrieve()
.body(String.class);
assertThat(body).isEqualTo("Hello World");
}
@@ -31,7 +31,7 @@ import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.client.ResourceAccessException;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.client.RestClient;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
@@ -55,12 +55,12 @@ class SkipSslVerificationHttpRequestFactoryTests {
void restCallToSelfSignedServerShouldNotThrowSslException() {
String httpsUrl = getHttpsUrl();
SkipSslVerificationHttpRequestFactory requestFactory = new SkipSslVerificationHttpRequestFactory();
RestTemplate restTemplate = new RestTemplate(requestFactory);
RestTemplate otherRestTemplate = new RestTemplate();
ResponseEntity<String> responseEntity = restTemplate.getForEntity(httpsUrl, String.class);
RestClient restClient = RestClient.builder().requestFactory(requestFactory).build();
RestClient otherRestClient = RestClient.create();
ResponseEntity<String> responseEntity = restClient.get().uri(httpsUrl).retrieve().toEntity(String.class);
assertThat(responseEntity.getStatusCode()).isEqualTo(HttpStatus.OK);
assertThatExceptionOfType(ResourceAccessException.class)
.isThrownBy(() -> otherRestTemplate.getForEntity(httpsUrl, String.class))
.isThrownBy(() -> otherRestClient.get().uri(httpsUrl).retrieve().toEntity(String.class))
.withCauseInstanceOf(SSLHandshakeException.class);
}
@@ -55,7 +55,7 @@ import org.junit.jupiter.api.Test;
import org.springframework.core.task.AsyncTaskExecutor;
import org.springframework.http.HttpHeaders;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.client.RestClient;
import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.PingMessage;
import org.springframework.web.socket.PongMessage;
@@ -107,9 +107,9 @@ class LiveReloadServerTests {
@Test
@Disabled
void servesLivereloadJs() throws Exception {
RestTemplate template = new RestTemplate();
RestClient restClient = RestClient.create();
URI uri = new URI("http://localhost:" + this.port + "/livereload.js");
String script = template.getForObject(uri, String.class);
String script = restClient.get().uri(uri).retrieve().body(String.class);
assertThat(script).contains("livereload.com/protocols/official-7");
}
@@ -47,7 +47,7 @@ import org.springframework.boot.tomcat.autoconfigure.servlet.TomcatServletWebSer
import org.springframework.boot.web.server.servlet.context.AnnotationConfigServletWebServerApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.client.RestClient;
import static org.assertj.core.api.Assertions.assertThat;
@@ -112,8 +112,8 @@ class JerseyServerMetricsAutoConfigurationTests {
int port = context.getSourceApplicationContext(AnnotationConfigServletWebServerApplicationContext.class)
.getWebServer()
.getPort();
RestTemplate restTemplate = new RestTemplate();
restTemplate.getForEntity(URI.create("http://localhost:" + port + "/users/3"), String.class);
RestClient restClient = RestClient.create();
restClient.get().uri(URI.create("http://localhost:" + port + "/users/3")).retrieve().toEntity(String.class);
}
@Configuration(proxyBeanMethods = false)
@@ -48,7 +48,7 @@ import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.client.RestClient;
import org.springframework.web.multipart.MultipartResolver;
import org.springframework.web.multipart.support.StandardServletMultipartResolver;
import org.springframework.web.servlet.DispatcherServlet;
@@ -214,11 +214,11 @@ class MultipartAutoConfigurationTests {
}
private void verifyServletWorks(AnnotationConfigServletWebServerApplicationContext context) {
RestTemplate restTemplate = new RestTemplate();
RestClient restClient = RestClient.create();
WebServer webServer = context.getWebServer();
assertThat(webServer).isNotNull();
String url = "http://localhost:" + webServer.getPort() + "/";
assertThat(restTemplate.getForObject(url, String.class)).isEqualTo("Hello");
assertThat(restClient.get().uri(url).retrieve().body(String.class)).isEqualTo("Hello");
}
@Configuration(proxyBeanMethods = false)
@@ -100,8 +100,6 @@ import org.springframework.boot.web.server.servlet.ConfigurableServletWebServerF
import org.springframework.core.io.ByteArrayResource;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.support.PropertiesLoaderUtils;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
@@ -109,7 +107,7 @@ import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.util.FileSystemUtils;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.client.RestClient;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
@@ -573,14 +571,15 @@ class TomcatServletWebServerFactoryTests extends AbstractServletWebServerFactory
assertThat(servletContext).isNotNull();
File temp = (File) servletContext.getAttribute(ServletContext.TEMPDIR);
FileSystemUtils.deleteRecursively(temp);
RestTemplate restTemplate = new RestTemplate();
HttpHeaders headers = new HttpHeaders();
RestClient restClient = RestClient.create();
MultiValueMap<String, Object> body = new LinkedMultiValueMap<>();
body.add("file", new ByteArrayResource(new byte[1024 * 1024]));
headers.setContentType(MediaType.MULTIPART_FORM_DATA);
HttpEntity<MultiValueMap<String, Object>> requestEntity = new HttpEntity<>(body, headers);
ResponseEntity<String> response = restTemplate.postForEntity(getLocalUrl("/upload"), requestEntity,
String.class);
ResponseEntity<String> response = restClient.post()
.uri(getLocalUrl("/upload"))
.contentType(MediaType.MULTIPART_FORM_DATA)
.body(body)
.retrieve()
.toEntity(String.class);
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
}
@@ -50,10 +50,10 @@ import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.http.HttpStatus;
import org.springframework.http.RequestEntity;
import org.springframework.stereotype.Component;
import org.springframework.web.client.HttpClientErrorException;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.client.RestClient;
import org.springframework.web.client.RestClient.RequestHeadersSpec;
import org.springframework.web.filter.ForwardedHeaderFilter;
import static org.assertj.core.api.Assertions.assertThat;
@@ -166,15 +166,15 @@ public abstract class AbstractServletWebServerAutoConfigurationTests {
WebServer webServer = ((WebServerApplicationContext) context.getSourceApplicationContext())
.getWebServer();
int port = webServer.getPort();
RestTemplate rest = new RestTemplate();
RequestEntity<Void> request = RequestEntity.get("http://localhost:" + port)
RestClient rest = RestClient.create();
RequestHeadersSpec<?> requestSpec = rest.get()
.uri("http://localhost:" + port)
.header("Upgrade", "websocket")
.header("Connection", "upgrade")
.header("Sec-WebSocket-Version", "13")
.header("Sec-WebSocket-Key", "key")
.build();
.header("Sec-WebSocket-Key", "key");
assertThatExceptionOfType(HttpClientErrorException.Unauthorized.class)
.isThrownBy(() -> rest.exchange(request, Void.class));
.isThrownBy(() -> requestSpec.retrieve().toBodilessEntity());
});
}
@@ -69,7 +69,7 @@ import org.springframework.messaging.simp.stomp.StompSessionHandler;
import org.springframework.messaging.simp.stomp.StompSessionHandlerAdapter;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Controller;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.client.RestClient;
import org.springframework.web.socket.client.standard.StandardWebSocketClient;
import org.springframework.web.socket.config.annotation.DelegatingWebSocketMessageBrokerConfiguration;
import org.springframework.web.socket.config.annotation.EnableWebSocket;
@@ -77,7 +77,7 @@ import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBr
import org.springframework.web.socket.config.annotation.StompEndpointRegistry;
import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer;
import org.springframework.web.socket.messaging.WebSocketStompClient;
import org.springframework.web.socket.sockjs.client.RestTemplateXhrTransport;
import org.springframework.web.socket.sockjs.client.RestClientXhrTransport;
import org.springframework.web.socket.sockjs.client.SockJsClient;
import org.springframework.web.socket.sockjs.client.Transport;
import org.springframework.web.socket.sockjs.client.WebSocketTransport;
@@ -102,7 +102,7 @@ class WebSocketMessagingAutoConfigurationTests {
void setup() {
List<Transport> transports = Arrays.asList(
new WebSocketTransport(new StandardWebSocketClient(new WsWebSocketContainer())),
new RestTemplateXhrTransport(new RestTemplate()));
new RestClientXhrTransport(RestClient.create()));
this.sockJsClient = new SockJsClient(transports);
}
@@ -18,7 +18,6 @@ package smoketest.session;
import java.net.URI;
import java.util.Base64;
import java.util.Collections;
import java.util.List;
import java.util.Map;
@@ -26,24 +25,24 @@ import org.jspecify.annotations.Nullable;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.http.client.ClientHttpRequestFactoryBuilder;
import org.springframework.boot.http.client.HttpClientSettings;
import org.springframework.boot.http.client.HttpRedirects;
import org.springframework.boot.restclient.RestTemplateBuilder;
import org.springframework.boot.resttestclient.TestRestTemplate;
import org.springframework.boot.resttestclient.autoconfigure.AutoConfigureTestRestTemplate;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.web.server.LocalServerPort;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.RequestEntity;
import org.springframework.http.ResponseEntity;
import org.springframework.http.client.ClientHttpRequestFactory;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.client.RestClient;
import static org.assertj.core.api.Assertions.assertThat;
@@ -62,9 +61,6 @@ class SampleSessionJdbcApplicationTests {
private static final HttpClientSettings DONT_FOLLOW_REDIRECTS = HttpClientSettings.defaults()
.withRedirects(HttpRedirects.DONT_FOLLOW);
@Autowired
private RestTemplateBuilder restTemplateBuilder;
@Autowired
private TestRestTemplate restTemplate;
@@ -86,15 +82,18 @@ class SampleSessionJdbcApplicationTests {
}
private @Nullable String performLogin() {
RestTemplate restTemplate = this.restTemplateBuilder.clientSettings(DONT_FOLLOW_REDIRECTS).build();
HttpHeaders headers = new HttpHeaders();
headers.setAccept(Collections.singletonList(MediaType.TEXT_HTML));
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
ClientHttpRequestFactory requestFactory = ClientHttpRequestFactoryBuilder.detect().build(DONT_FOLLOW_REDIRECTS);
RestClient restClient = RestClient.builder().requestFactory(requestFactory).build();
MultiValueMap<String, String> form = new LinkedMultiValueMap<>();
form.set("username", "user");
form.set("password", "password");
ResponseEntity<String> entity = restTemplate.exchange("http://localhost:" + this.port + "/login",
HttpMethod.POST, new HttpEntity<>(form, headers), String.class);
ResponseEntity<String> entity = restClient.post()
.uri("http://localhost:" + this.port + "/login")
.accept(MediaType.TEXT_HTML)
.contentType(MediaType.APPLICATION_FORM_URLENCODED)
.body(form)
.retrieve()
.toEntity(String.class);
return entity.getHeaders().getFirst("Set-Cookie");
}
@@ -20,12 +20,11 @@ import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import smoketest.test.domain.VehicleIdentificationNumber;
import org.springframework.boot.restclient.RestTemplateBuilder;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;
import org.springframework.web.client.HttpStatusCodeException;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.client.RestClient;
/**
* {@link VehicleDetailsService} backed by a remote REST service.
@@ -37,10 +36,10 @@ public class RemoteVehicleDetailsService implements VehicleDetailsService {
private static final Log logger = LogFactory.getLog(RemoteVehicleDetailsService.class);
private final RestTemplate restTemplate;
private final RestClient restClient;
public RemoteVehicleDetailsService(ServiceProperties properties, RestTemplateBuilder restTemplateBuilder) {
this.restTemplate = restTemplateBuilder.baseUri(properties.getVehicleServiceRootUrl()).build();
public RemoteVehicleDetailsService(ServiceProperties properties, RestClient.Builder restTemplateBuilder) {
this.restClient = restTemplateBuilder.baseUrl(properties.getVehicleServiceRootUrl()).build();
}
@Override
@@ -49,8 +48,10 @@ public class RemoteVehicleDetailsService implements VehicleDetailsService {
Assert.notNull(vin, "'vin' must not be null");
logger.debug("Retrieving vehicle data for: " + vin);
try {
VehicleDetails response = this.restTemplate.getForObject("/vehicle/{vin}/details", VehicleDetails.class,
vin);
VehicleDetails response = this.restClient.get()
.uri("/vehicle/{vin}/details", vin)
.retrieve()
.body(VehicleDetails.class);
Assert.state(response != null, "'response' must not be null");
return response;
}
@@ -17,6 +17,7 @@
package smoketest.test.service;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestInstance;
import smoketest.test.domain.VehicleIdentificationNumber;
import org.springframework.beans.factory.annotation.Autowired;
@@ -25,7 +26,9 @@ import org.springframework.core.io.ClassPathResource;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.test.web.client.MockRestServiceServer;
import org.springframework.test.web.client.RequestMatcher;
import org.springframework.web.client.HttpServerErrorException;
import org.springframework.web.client.RestClient;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
@@ -39,17 +42,27 @@ import static org.springframework.test.web.client.response.MockRestResponseCreat
* Tests for {@link RemoteVehicleDetailsService}.
*
* @author Phillip Webb
* @author Stephane Nicoll
*/
@RestClientTest({ RemoteVehicleDetailsService.class, ServiceProperties.class })
@RestClientTest
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class RemoteVehicleDetailsServiceTests {
private static final String VIN = "00000000000000000";
@Autowired
private RemoteVehicleDetailsService service;
private static final String BASE_URL = "https://api.example.com";
@Autowired
private MockRestServiceServer server;
private final RemoteVehicleDetailsService service;
private final MockRestServiceServer server;
RemoteVehicleDetailsServiceTests(@Autowired RestClient.Builder restClientBuilder,
@Autowired MockRestServiceServer server) {
ServiceProperties properties = new ServiceProperties();
properties.setVehicleServiceRootUrl(BASE_URL);
this.service = new RemoteVehicleDetailsService(properties, restClientBuilder);
this.server = server;
}
@Test
@SuppressWarnings("NullAway") // Test null check
@@ -60,7 +73,7 @@ class RemoteVehicleDetailsServiceTests {
@Test
void getVehicleDetailsWhenResultIsSuccessShouldReturnDetails() {
this.server.expect(requestTo("/vehicle/" + VIN + "/details"))
this.server.expect(prepareRequest("/vehicle/" + VIN + "/details"))
.andRespond(withSuccess(getClassPathResource("vehicledetails.json"), MediaType.APPLICATION_JSON));
VehicleDetails details = this.service.getVehicleDetails(new VehicleIdentificationNumber(VIN));
assertThat(details.getMake()).isEqualTo("Honda");
@@ -69,18 +82,22 @@ class RemoteVehicleDetailsServiceTests {
@Test
void getVehicleDetailsWhenResultIsNotFoundShouldThrowException() {
this.server.expect(requestTo("/vehicle/" + VIN + "/details")).andRespond(withStatus(HttpStatus.NOT_FOUND));
this.server.expect(prepareRequest("/vehicle/" + VIN + "/details")).andRespond(withStatus(HttpStatus.NOT_FOUND));
assertThatExceptionOfType(VehicleIdentificationNumberNotFoundException.class)
.isThrownBy(() -> this.service.getVehicleDetails(new VehicleIdentificationNumber(VIN)));
}
@Test
void getVehicleDetailsWhenResultIServerErrorShouldThrowException() {
this.server.expect(requestTo("/vehicle/" + VIN + "/details")).andRespond(withServerError());
this.server.expect(prepareRequest("/vehicle/" + VIN + "/details")).andRespond(withServerError());
assertThatExceptionOfType(HttpServerErrorException.class)
.isThrownBy(() -> this.service.getVehicleDetails(new VehicleIdentificationNumber(VIN)));
}
private static RequestMatcher prepareRequest(String path) {
return requestTo(BASE_URL + path);
}
private ClassPathResource getClassPathResource(String path) {
return new ClassPathResource(path, getClass());
}