Polishing contribution

Closes gh-30869
This commit is contained in:
rstoyanchev 2023-07-11 19:12:29 +01:00
parent 8b77ed164d
commit 57ed5bf34b
5 changed files with 136 additions and 341 deletions

View File

@ -31,24 +31,26 @@ import org.springframework.web.service.invoker.HttpRequestValues;
import org.springframework.web.service.invoker.HttpServiceProxyFactory; import org.springframework.web.service.invoker.HttpServiceProxyFactory;
/** /**
* {@link HttpExchangeAdapter} that enables an {@link HttpServiceProxyFactory} to use * {@link HttpExchangeAdapter} that enables an {@link HttpServiceProxyFactory}
* {@link RestClient} for request execution. * to use {@link RestClient} for request execution.
* *
* <p> * <p>Use static factory methods in this class to create an
* Use static factory methods in this class to create an {@link HttpServiceProxyFactory} * {@link HttpServiceProxyFactory} configured with the given {@link RestClient}.
* configured with a given {@link RestClient}.
* *
* @author Olga Maciaszek-Sharma * @author Olga Maciaszek-Sharma
* @author Rossen Stoyanchev
* @since 6.1 * @since 6.1
*/ */
public final class RestClientAdapter implements HttpExchangeAdapter { public final class RestClientAdapter implements HttpExchangeAdapter {
private final RestClient restClient; private final RestClient restClient;
private RestClientAdapter(RestClient restClient) { private RestClientAdapter(RestClient restClient) {
this.restClient = restClient; this.restClient = restClient;
} }
@Override @Override
public boolean supportsRequestAttributes() { public boolean supportsRequestAttributes() {
return true; return true;
@ -60,66 +62,66 @@ public final class RestClientAdapter implements HttpExchangeAdapter {
} }
@Override @Override
public HttpHeaders exchangeForHeaders(HttpRequestValues requestValues) { public HttpHeaders exchangeForHeaders(HttpRequestValues values) {
return newRequest(requestValues).retrieve().toBodilessEntity().getHeaders(); return newRequest(values).retrieve().toBodilessEntity().getHeaders();
} }
@Override @Override
public <T> T exchangeForBody(HttpRequestValues requestValues, ParameterizedTypeReference<T> bodyType) { public <T> T exchangeForBody(HttpRequestValues values, ParameterizedTypeReference<T> bodyType) {
return newRequest(requestValues).retrieve().body(bodyType); return newRequest(values).retrieve().body(bodyType);
} }
@Override @Override
public ResponseEntity<Void> exchangeForBodilessEntity(HttpRequestValues requestValues) { public ResponseEntity<Void> exchangeForBodilessEntity(HttpRequestValues values) {
return newRequest(requestValues).retrieve().toBodilessEntity(); return newRequest(values).retrieve().toBodilessEntity();
} }
@Override @Override
public <T> ResponseEntity<T> exchangeForEntity(HttpRequestValues requestValues, public <T> ResponseEntity<T> exchangeForEntity(HttpRequestValues values, ParameterizedTypeReference<T> bodyType) {
ParameterizedTypeReference<T> bodyType) { return newRequest(values).retrieve().toEntity(bodyType);
return newRequest(requestValues).retrieve().toEntity(bodyType);
} }
private RestClient.RequestBodySpec newRequest(HttpRequestValues requestValues) { private RestClient.RequestBodySpec newRequest(HttpRequestValues values) {
HttpMethod httpMethod = requestValues.getHttpMethod(); HttpMethod httpMethod = values.getHttpMethod();
Assert.notNull(httpMethod, "HttpMethod is required"); Assert.notNull(httpMethod, "HttpMethod is required");
RestClient.RequestBodyUriSpec uriSpec = this.restClient.method(httpMethod); RestClient.RequestBodyUriSpec uriSpec = this.restClient.method(httpMethod);
RestClient.RequestBodySpec bodySpec; RestClient.RequestBodySpec bodySpec;
if (requestValues.getUri() != null) { if (values.getUri() != null) {
bodySpec = uriSpec.uri(requestValues.getUri()); bodySpec = uriSpec.uri(values.getUri());
} }
else if (requestValues.getUriTemplate() != null) { else if (values.getUriTemplate() != null) {
bodySpec = uriSpec.uri(requestValues.getUriTemplate(), requestValues.getUriVariables()); bodySpec = uriSpec.uri(values.getUriTemplate(), values.getUriVariables());
} }
else { else {
throw new IllegalStateException("Neither full URL nor URI template"); throw new IllegalStateException("Neither full URL nor URI template");
} }
bodySpec.headers(headers -> headers.putAll(requestValues.getHeaders())); bodySpec.headers(headers -> headers.putAll(values.getHeaders()));
if (!requestValues.getCookies().isEmpty()) { if (!values.getCookies().isEmpty()) {
List<String> cookies = new ArrayList<>(); List<String> cookies = new ArrayList<>();
requestValues.getCookies().forEach((name, values) -> values.forEach(value -> { values.getCookies().forEach((name, cookieValues) -> cookieValues.forEach(value -> {
HttpCookie cookie = new HttpCookie(name, value); HttpCookie cookie = new HttpCookie(name, value);
cookies.add(cookie.toString()); cookies.add(cookie.toString());
})); }));
bodySpec.header(HttpHeaders.COOKIE, String.join("; ", cookies)); bodySpec.header(HttpHeaders.COOKIE, String.join("; ", cookies));
} }
bodySpec.attributes(attributes -> attributes.putAll(requestValues.getAttributes())); bodySpec.attributes(attributes -> attributes.putAll(values.getAttributes()));
if (requestValues.getBodyValue() != null) { if (values.getBodyValue() != null) {
bodySpec.body(requestValues.getBodyValue()); bodySpec.body(values.getBodyValue());
} }
return bodySpec; return bodySpec;
} }
/** /**
* Create a {@link RestClientAdapter} with the given {@link RestClient}. * Create a {@link RestClientAdapter} for the given {@link RestClient}.
*/ */
public static RestClientAdapter create(RestClient restClient) { public static RestClientAdapter create(RestClient restClient) {
return new RestClientAdapter(restClient); return new RestClientAdapter(restClient);

View File

@ -34,11 +34,11 @@ import org.springframework.web.service.invoker.HttpRequestValues;
import org.springframework.web.service.invoker.HttpServiceProxyFactory; import org.springframework.web.service.invoker.HttpServiceProxyFactory;
/** /**
* {@link HttpExchangeAdapter} that enables an {@link HttpServiceProxyFactory} to use * {@link HttpExchangeAdapter} that enables an {@link HttpServiceProxyFactory}
* {@link RestTemplate} for request execution. * to use {@link RestTemplate} for request execution.
* *
* <p>Use static factory methods in this class to create an * <p>Use static factory methods in this class to create an
* {@link HttpServiceProxyFactory} configured with a given {@link RestTemplate}. * {@link HttpServiceProxyFactory} configured with the given {@link RestTemplate}.
* *
* @author Olga Maciaszek-Sharma * @author Olga Maciaszek-Sharma
* @since 6.1 * @since 6.1
@ -59,63 +59,61 @@ public final class RestTemplateAdapter implements HttpExchangeAdapter {
} }
@Override @Override
public void exchange(HttpRequestValues requestValues) { public void exchange(HttpRequestValues values) {
this.restTemplate.exchange(newRequest(requestValues), Void.class); this.restTemplate.exchange(newRequest(values), Void.class);
} }
@Override @Override
public HttpHeaders exchangeForHeaders(HttpRequestValues requestValues) { public HttpHeaders exchangeForHeaders(HttpRequestValues values) {
return this.restTemplate.exchange(newRequest(requestValues), Void.class).getHeaders(); return this.restTemplate.exchange(newRequest(values), Void.class).getHeaders();
} }
@Override @Override
public <T> T exchangeForBody(HttpRequestValues requestValues, ParameterizedTypeReference<T> bodyType) { public <T> T exchangeForBody(HttpRequestValues values, ParameterizedTypeReference<T> bodyType) {
return this.restTemplate.exchange(newRequest(requestValues), bodyType).getBody(); return this.restTemplate.exchange(newRequest(values), bodyType).getBody();
} }
@Override @Override
public ResponseEntity<Void> exchangeForBodilessEntity(HttpRequestValues requestValues) { public ResponseEntity<Void> exchangeForBodilessEntity(HttpRequestValues values) {
return this.restTemplate.exchange(newRequest(requestValues), Void.class); return this.restTemplate.exchange(newRequest(values), Void.class);
} }
@Override @Override
public <T> ResponseEntity<T> exchangeForEntity( public <T> ResponseEntity<T> exchangeForEntity(HttpRequestValues values, ParameterizedTypeReference<T> bodyType) {
HttpRequestValues requestValues, ParameterizedTypeReference<T> bodyType) { return this.restTemplate.exchange(newRequest(values), bodyType);
return this.restTemplate.exchange(newRequest(requestValues), bodyType);
} }
private RequestEntity<?> newRequest(HttpRequestValues requestValues) { private RequestEntity<?> newRequest(HttpRequestValues values) {
URI uri; URI uri;
if (requestValues.getUri() != null) { if (values.getUri() != null) {
uri = requestValues.getUri(); uri = values.getUri();
} }
else if (requestValues.getUriTemplate() != null) { else if (values.getUriTemplate() != null) {
String uriTemplate = requestValues.getUriTemplate(); String uriTemplate = values.getUriTemplate();
Map<String, String> variables = requestValues.getUriVariables(); Map<String, String> variables = values.getUriVariables();
uri = this.restTemplate.getUriTemplateHandler().expand(uriTemplate, variables); uri = this.restTemplate.getUriTemplateHandler().expand(uriTemplate, variables);
} }
else { else {
throw new IllegalStateException("Neither full URL nor URI template"); throw new IllegalStateException("Neither full URL nor URI template");
} }
HttpMethod httpMethod = requestValues.getHttpMethod(); HttpMethod httpMethod = values.getHttpMethod();
Assert.notNull(httpMethod, "HttpMethod is required"); Assert.notNull(httpMethod, "HttpMethod is required");
RequestEntity.BodyBuilder builder = RequestEntity.method(httpMethod, uri); RequestEntity.BodyBuilder builder = RequestEntity.method(httpMethod, uri);
builder.headers(requestValues.getHeaders()); builder.headers(values.getHeaders());
if (!requestValues.getCookies().isEmpty()) { if (!values.getCookies().isEmpty()) {
List<String> cookies = new ArrayList<>(); List<String> cookies = new ArrayList<>();
requestValues.getCookies().forEach((name, values) -> values.forEach(value -> { values.getCookies().forEach((name, cookieValues) -> cookieValues.forEach(value -> {
HttpCookie cookie = new HttpCookie(name, value); HttpCookie cookie = new HttpCookie(name, value);
cookies.add(cookie.toString()); cookies.add(cookie.toString());
})); }));
builder.header(HttpHeaders.COOKIE, String.join("; ", cookies)); builder.header(HttpHeaders.COOKIE, String.join("; ", cookies));
} }
if (requestValues.getBodyValue() != null) { if (values.getBodyValue() != null) {
return builder.body(requestValues.getBodyValue()); return builder.body(values.getBodyValue());
} }
return builder.build(); return builder.build();
@ -123,7 +121,7 @@ public final class RestTemplateAdapter implements HttpExchangeAdapter {
/** /**
* Create a {@link RestTemplateAdapter} with the given {@link RestTemplate}. * Create a {@link RestTemplateAdapter} for the given {@link RestTemplate}.
*/ */
public static RestTemplateAdapter create(RestTemplate restTemplate) { public static RestTemplateAdapter create(RestTemplate restTemplate) {
return new RestTemplateAdapter(restTemplate); return new RestTemplateAdapter(restTemplate);

View File

@ -53,9 +53,9 @@ import org.springframework.web.service.annotation.HttpExchange;
* *
* @author Rossen Stoyanchev * @author Rossen Stoyanchev
* @since 6.0 * @since 6.0
* @see org.springframework.web.client.support.RestTemplateAdapter
* @see org.springframework.web.reactive.function.client.support.WebClientAdapter
* @see org.springframework.web.client.support.RestClientAdapter * @see org.springframework.web.client.support.RestClientAdapter
* @see org.springframework.web.reactive.function.client.support.WebClientAdapter
* @see org.springframework.web.client.support.RestTemplateAdapter
*/ */
public final class HttpServiceProxyFactory { public final class HttpServiceProxyFactory {

View File

@ -16,17 +16,21 @@
package org.springframework.web.client.support; package org.springframework.web.client.support;
import java.io.IOException; import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.net.URI; import java.net.URI;
import java.util.Optional; import java.util.Optional;
import java.util.stream.Stream;
import okhttp3.mockwebserver.MockResponse; import okhttp3.mockwebserver.MockResponse;
import okhttp3.mockwebserver.MockWebServer; import okhttp3.mockwebserver.MockWebServer;
import okhttp3.mockwebserver.RecordedRequest; import okhttp3.mockwebserver.RecordedRequest;
import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.params.provider.MethodSource;
import org.junit.jupiter.api.Test;
import org.springframework.cglib.core.internal.Function;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
@ -40,113 +44,127 @@ import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RequestPart; import org.springframework.web.bind.annotation.RequestPart;
import org.springframework.web.client.RestClient; import org.springframework.web.client.RestClient;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.service.annotation.GetExchange; import org.springframework.web.service.annotation.GetExchange;
import org.springframework.web.service.annotation.PostExchange; import org.springframework.web.service.annotation.PostExchange;
import org.springframework.web.service.annotation.PutExchange; import org.springframework.web.service.annotation.PutExchange;
import org.springframework.web.service.invoker.HttpExchangeAdapter;
import org.springframework.web.service.invoker.HttpServiceProxyFactory; import org.springframework.web.service.invoker.HttpServiceProxyFactory;
import org.springframework.web.testfixture.servlet.MockMultipartFile; import org.springframework.web.testfixture.servlet.MockMultipartFile;
import org.springframework.web.util.DefaultUriBuilderFactory;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
/** /**
* Integration tests for {@link HttpServiceProxyFactory HTTP Service proxy} with * Integration tests for {@link HttpServiceProxyFactory} with {@link RestClient}
* {@link RestClientAdapter} connecting to {@link MockWebServer}. * and {@link RestTemplate} connecting to {@link MockWebServer}.
* *
* @author Olga Maciaszek-Sharma * @author Olga Maciaszek-Sharma
* @author Rossen Stoyanchev
*/ */
@SuppressWarnings("JUnitMalformedDeclaration")
class RestClientAdapterTests { class RestClientAdapterTests {
private MockWebServer server; @Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
private Service service; @ParameterizedTest
@MethodSource("arguments")
@BeforeEach @interface ParameterizedAdapterTest {
void setUp() {
this.server = new MockWebServer();
prepareResponse();
RestClient restClient = RestClient.builder().baseUrl(this.server.url("/").toString()).build();
RestClientAdapter adapter = RestClientAdapter.create(restClient);
this.service = HttpServiceProxyFactory.builderFor(adapter).build().createClient(Service.class);
} }
@SuppressWarnings("ConstantConditions") public static Stream<Object[]> arguments() {
@AfterEach return Stream.of(
void shutDown() throws IOException { args(url -> {
if (this.server != null) { RestClient restClient = RestClient.builder().baseUrl(url).build();
this.server.shutdown(); return RestClientAdapter.create(restClient);
} }),
args(url -> {
RestTemplate restTemplate = new RestTemplate();
restTemplate.setUriTemplateHandler(new DefaultUriBuilderFactory(url));
return RestTemplateAdapter.create(restTemplate);
}));
} }
@Test @SuppressWarnings("resource")
void greeting() throws InterruptedException { private static Object[] args(Function<String, HttpExchangeAdapter> adapterFactory) {
String response = this.service.getGreeting(); MockWebServer server = new MockWebServer();
RecordedRequest request = this.server.takeRequest(); MockResponse response = new MockResponse();
response.setHeader("Content-Type", "text/plain").setBody("Hello Spring!");
server.enqueue(response);
HttpExchangeAdapter adapter = adapterFactory.apply(server.url("/").toString());
Service service = HttpServiceProxyFactory.builderFor(adapter).build().createClient(Service.class);
return new Object[] { server, service };
}
@ParameterizedAdapterTest
void greeting(MockWebServer server, Service service) throws Exception {
String response = service.getGreeting();
RecordedRequest request = server.takeRequest();
assertThat(response).isEqualTo("Hello Spring!"); assertThat(response).isEqualTo("Hello Spring!");
assertThat(request.getMethod()).isEqualTo("GET"); assertThat(request.getMethod()).isEqualTo("GET");
assertThat(request.getPath()).isEqualTo("/greeting"); assertThat(request.getPath()).isEqualTo("/greeting");
} }
@Test @ParameterizedAdapterTest
void greetingById() throws InterruptedException { void greetingById(MockWebServer server, Service service) throws Exception {
ResponseEntity<String> response = this.service.getGreetingById("456"); ResponseEntity<String> response = service.getGreetingById("456");
RecordedRequest request = this.server.takeRequest(); RecordedRequest request = server.takeRequest();
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
assertThat(response.getBody()).isEqualTo("Hello Spring!"); assertThat(response.getBody()).isEqualTo("Hello Spring!");
assertThat(request.getMethod()).isEqualTo("GET"); assertThat(request.getMethod()).isEqualTo("GET");
assertThat(request.getPath()).isEqualTo("/greeting/456"); assertThat(request.getPath()).isEqualTo("/greeting/456");
} }
@Test @ParameterizedAdapterTest
void greetingWithDynamicUri() throws InterruptedException { void greetingWithDynamicUri(MockWebServer server, Service service) throws Exception {
URI dynamicUri = this.server.url("/greeting/123").uri(); URI dynamicUri = server.url("/greeting/123").uri();
Optional<String> response = service.getGreetingWithDynamicUri(dynamicUri, "456");
Optional<String> response = this.service.getGreetingWithDynamicUri(dynamicUri, "456"); RecordedRequest request = server.takeRequest();
RecordedRequest request = this.server.takeRequest();
assertThat(response.orElse("empty")).isEqualTo("Hello Spring!"); assertThat(response.orElse("empty")).isEqualTo("Hello Spring!");
assertThat(request.getMethod()).isEqualTo("GET"); assertThat(request.getMethod()).isEqualTo("GET");
assertThat(request.getRequestUrl().uri()).isEqualTo(dynamicUri); assertThat(request.getRequestUrl().uri()).isEqualTo(dynamicUri);
} }
@Test @ParameterizedAdapterTest
void postWithHeader() throws InterruptedException { void postWithHeader(MockWebServer server, Service service) throws Exception {
service.postWithHeader("testHeader", "testBody"); service.postWithHeader("testHeader", "testBody");
RecordedRequest request = this.server.takeRequest(); RecordedRequest request = server.takeRequest();
assertThat(request.getMethod()).isEqualTo("POST"); assertThat(request.getMethod()).isEqualTo("POST");
assertThat(request.getPath()).isEqualTo("/greeting"); assertThat(request.getPath()).isEqualTo("/greeting");
assertThat(request.getHeaders().get("testHeaderName")).isEqualTo("testHeader"); assertThat(request.getHeaders().get("testHeaderName")).isEqualTo("testHeader");
assertThat(request.getBody().readUtf8()).isEqualTo("testBody"); assertThat(request.getBody().readUtf8()).isEqualTo("testBody");
} }
@Test @ParameterizedAdapterTest
void formData() throws Exception { void formData(MockWebServer server, Service service) throws Exception {
MultiValueMap<String, String> map = new LinkedMultiValueMap<>(); MultiValueMap<String, String> map = new LinkedMultiValueMap<>();
map.add("param1", "value 1"); map.add("param1", "value 1");
map.add("param2", "value 2"); map.add("param2", "value 2");
service.postForm(map); service.postForm(map);
RecordedRequest request = this.server.takeRequest(); RecordedRequest request = server.takeRequest();
assertThat(request.getHeaders().get("Content-Type")) assertThat(request.getHeaders().get("Content-Type")).isEqualTo("application/x-www-form-urlencoded;charset=UTF-8");
.isEqualTo("application/x-www-form-urlencoded;charset=UTF-8");
assertThat(request.getBody().readUtf8()).isEqualTo("param1=value+1&param2=value+2"); assertThat(request.getBody().readUtf8()).isEqualTo("param1=value+1&param2=value+2");
} }
@Test // gh-30342 @ParameterizedAdapterTest // gh-30342
void multipart() throws InterruptedException { void multipart(MockWebServer server, Service service) throws Exception {
String fileName = "testFileName"; MultipartFile file = new MockMultipartFile(
String originalFileName = "originalTestFileName"; "testFileName", "originalTestFileName", MediaType.APPLICATION_JSON_VALUE, "test".getBytes());
MultipartFile file = new MockMultipartFile(fileName, originalFileName, MediaType.APPLICATION_JSON_VALUE,
"test".getBytes());
service.postMultipart(file, "test2"); service.postMultipart(file, "test2");
RecordedRequest request = this.server.takeRequest(); RecordedRequest request = server.takeRequest();
assertThat(request.getHeaders().get("Content-Type")).startsWith("multipart/form-data;boundary="); assertThat(request.getHeaders().get("Content-Type")).startsWith("multipart/form-data;boundary=");
assertThat(request.getBody().readUtf8()).containsSubsequence( assertThat(request.getBody().readUtf8()).containsSubsequence(
"Content-Disposition: form-data; name=\"file\"; filename=\"originalTestFileName\"", "Content-Disposition: form-data; name=\"file\"; filename=\"originalTestFileName\"",
@ -155,29 +173,24 @@ class RestClientAdapterTests {
"Content-Length: 5", "test2"); "Content-Length: 5", "test2");
} }
@Test @ParameterizedAdapterTest
void putWithCookies() throws InterruptedException { void putWithCookies(MockWebServer server, Service service) throws Exception {
service.putWithCookies("test1", "test2"); service.putWithCookies("test1", "test2");
RecordedRequest request = this.server.takeRequest(); RecordedRequest request = server.takeRequest();
assertThat(request.getMethod()).isEqualTo("PUT"); assertThat(request.getMethod()).isEqualTo("PUT");
assertThat(request.getHeader("Cookie")).isEqualTo("firstCookie=test1; secondCookie=test2"); assertThat(request.getHeader("Cookie")).isEqualTo("firstCookie=test1; secondCookie=test2");
} }
@Test @ParameterizedAdapterTest
void putWithSameNameCookies() throws InterruptedException { void putWithSameNameCookies(MockWebServer server, Service service) throws Exception {
service.putWithSameNameCookies("test1", "test2"); service.putWithSameNameCookies("test1", "test2");
RecordedRequest request = this.server.takeRequest(); RecordedRequest request = server.takeRequest();
assertThat(request.getMethod()).isEqualTo("PUT"); assertThat(request.getMethod()).isEqualTo("PUT");
assertThat(request.getHeader("Cookie")).isEqualTo("testCookie=test1; testCookie=test2"); assertThat(request.getHeader("Cookie")).isEqualTo("testCookie=test1; testCookie=test2");
} }
private void prepareResponse() {
MockResponse response = new MockResponse();
response.setHeader("Content-Type", "text/plain").setBody("Hello Spring!");
this.server.enqueue(response);
}
private interface Service { private interface Service {
@ -203,8 +216,8 @@ class RestClientAdapterTests {
void putWithCookies(@CookieValue String firstCookie, @CookieValue String secondCookie); void putWithCookies(@CookieValue String firstCookie, @CookieValue String secondCookie);
@PutExchange @PutExchange
void putWithSameNameCookies(@CookieValue("testCookie") String firstCookie, void putWithSameNameCookies(
@CookieValue("testCookie") String secondCookie); @CookieValue("testCookie") String firstCookie, @CookieValue("testCookie") String secondCookie);
} }

View File

@ -1,218 +0,0 @@
/*
* Copyright 2002-2023 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.web.client.support;
import java.io.IOException;
import java.net.URI;
import java.util.Optional;
import okhttp3.mockwebserver.MockResponse;
import okhttp3.mockwebserver.MockWebServer;
import okhttp3.mockwebserver.RecordedRequest;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.lang.Nullable;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.bind.annotation.CookieValue;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RequestPart;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.service.annotation.GetExchange;
import org.springframework.web.service.annotation.PostExchange;
import org.springframework.web.service.annotation.PutExchange;
import org.springframework.web.service.invoker.HttpServiceProxyFactory;
import org.springframework.web.testfixture.servlet.MockMultipartFile;
import org.springframework.web.util.DefaultUriBuilderFactory;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Integration tests for {@link HttpServiceProxyFactory HTTP Service proxy}
* with {@link RestTemplateAdapter} connecting to {@link MockWebServer}.
*
* @author Olga Maciaszek-Sharma
*/
class RestTemplateAdapterTests {
private MockWebServer server;
private Service service;
@BeforeEach
void setUp() {
this.server = new MockWebServer();
prepareResponse();
RestTemplate restTemplate = new RestTemplate();
restTemplate.setUriTemplateHandler(new DefaultUriBuilderFactory(this.server.url("/").toString()));
RestTemplateAdapter adapter = RestTemplateAdapter.create(restTemplate);
this.service = HttpServiceProxyFactory.builder().exchangeAdapter(adapter).build().createClient(Service.class);
}
@SuppressWarnings("ConstantConditions")
@AfterEach
void shutDown() throws IOException {
if (this.server != null) {
this.server.shutdown();
}
}
@Test
void greeting() throws InterruptedException {
String response = this.service.getGreeting();
RecordedRequest request = this.server.takeRequest();
assertThat(response).isEqualTo("Hello Spring!");
assertThat(request.getMethod()).isEqualTo("GET");
assertThat(request.getPath()).isEqualTo("/greeting");
}
@Test
void greetingById() throws InterruptedException {
ResponseEntity<String> response = this.service.getGreetingById("456");
RecordedRequest request = this.server.takeRequest();
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
assertThat(response.getBody()).isEqualTo("Hello Spring!");
assertThat(request.getMethod()).isEqualTo("GET");
assertThat(request.getPath()).isEqualTo("/greeting/456");
}
@Test
void greetingWithDynamicUri() throws InterruptedException {
URI dynamicUri = this.server.url("/greeting/123").uri();
Optional<String> response = this.service.getGreetingWithDynamicUri(dynamicUri, "456");
RecordedRequest request = this.server.takeRequest();
assertThat(response.orElse("empty")).isEqualTo("Hello Spring!");
assertThat(request.getMethod()).isEqualTo("GET");
assertThat(request.getRequestUrl().uri()).isEqualTo(dynamicUri);
}
@Test
void postWithHeader() throws InterruptedException {
service.postWithHeader("testHeader", "testBody");
RecordedRequest request = this.server.takeRequest();
assertThat(request.getMethod()).isEqualTo("POST");
assertThat(request.getPath()).isEqualTo("/greeting");
assertThat(request.getHeaders().get("testHeaderName")).isEqualTo("testHeader");
assertThat(request.getBody().readUtf8()).isEqualTo("testBody");
}
@Test
void formData() throws Exception {
MultiValueMap<String, String> map = new LinkedMultiValueMap<>();
map.add("param1", "value 1");
map.add("param2", "value 2");
service.postForm(map);
RecordedRequest request = this.server.takeRequest();
assertThat(request.getHeaders().get("Content-Type"))
.isEqualTo("application/x-www-form-urlencoded;charset=UTF-8");
assertThat(request.getBody().readUtf8()).isEqualTo("param1=value+1&param2=value+2");
}
@Test // gh-30342
void multipart() throws InterruptedException {
String fileName = "testFileName";
String originalFileName = "originalTestFileName";
MultipartFile file = new MockMultipartFile(fileName, originalFileName, MediaType.APPLICATION_JSON_VALUE,
"test".getBytes());
service.postMultipart(file, "test2");
RecordedRequest request = this.server.takeRequest();
assertThat(request.getHeaders().get("Content-Type")).startsWith("multipart/form-data;boundary=");
assertThat(request.getBody().readUtf8()).containsSubsequence(
"Content-Disposition: form-data; name=\"file\"; filename=\"originalTestFileName\"",
"Content-Type: application/json", "Content-Length: 4", "test",
"Content-Disposition: form-data; name=\"anotherPart\"", "Content-Type: text/plain;charset=UTF-8",
"Content-Length: 5", "test2");
}
@Test
void putWithCookies() throws InterruptedException {
service.putWithCookies("test1", "test2");
RecordedRequest request = this.server.takeRequest();
assertThat(request.getMethod()).isEqualTo("PUT");
assertThat(request.getHeader("Cookie")).isEqualTo("firstCookie=test1; secondCookie=test2");
}
@Test
void putWithSameNameCookies() throws InterruptedException {
service.putWithSameNameCookies("test1", "test2");
RecordedRequest request = this.server.takeRequest();
assertThat(request.getMethod()).isEqualTo("PUT");
assertThat(request.getHeader("Cookie")).isEqualTo("testCookie=test1; testCookie=test2");
}
private void prepareResponse() {
MockResponse response = new MockResponse();
response.setHeader("Content-Type", "text/plain").setBody("Hello Spring!");
this.server.enqueue(response);
}
private interface Service {
@GetExchange("/greeting")
String getGreeting();
@GetExchange("/greeting/{id}")
ResponseEntity<String> getGreetingById(@PathVariable String id);
@GetExchange("/greeting/{id}")
Optional<String> getGreetingWithDynamicUri(@Nullable URI uri, @PathVariable String id);
@PostExchange("/greeting")
void postWithHeader(
@RequestHeader("testHeaderName") String testHeader, @RequestBody String requestBody);
@PostExchange(contentType = "application/x-www-form-urlencoded")
void postForm(@RequestParam MultiValueMap<String, String> params);
@PostExchange
void postMultipart(MultipartFile file, @RequestPart String anotherPart);
@PutExchange
void putWithCookies(
@CookieValue String firstCookie, @CookieValue String secondCookie);
@PutExchange
void putWithSameNameCookies(
@CookieValue("testCookie") String firstCookie, @CookieValue("testCookie") String secondCookie);
}
}