From 6dbfd936547c9648337ef07d32a011ffe5370f06 Mon Sep 17 00:00:00 2001 From: Manuel Date: Sun, 16 May 2021 23:16:37 +0200 Subject: [PATCH] Adds body support to get in mvc ProxyExchange. Fixes gh-2247 --- spring-cloud-gateway-mvc/pom.xml | 5 + .../cloud/gateway/mvc/ProxyExchange.java | 3 +- .../gateway/mvc/GetWithBodyRequestTest.java | 203 ++++++++++++++++++ .../mvc/ProductionConfigurationTests.java | 11 +- .../ProxyExchangeArgumentResolverTest.java | 7 +- .../AbstractBufferingClientHttpRequest.java | 48 +++++ ...thBodyRequestClientHttpRequestFactory.java | 123 +++++++++++ .../http/HttpComponentsClientHttpRequest.java | 93 ++++++++ .../HttpComponentsClientHttpResponse.java | 84 ++++++++ 9 files changed, 567 insertions(+), 10 deletions(-) create mode 100644 spring-cloud-gateway-mvc/src/test/java/org/springframework/cloud/gateway/mvc/GetWithBodyRequestTest.java create mode 100644 spring-cloud-gateway-mvc/src/test/java/org/springframework/cloud/gateway/mvc/http/AbstractBufferingClientHttpRequest.java create mode 100644 spring-cloud-gateway-mvc/src/test/java/org/springframework/cloud/gateway/mvc/http/GetWithBodyRequestClientHttpRequestFactory.java create mode 100644 spring-cloud-gateway-mvc/src/test/java/org/springframework/cloud/gateway/mvc/http/HttpComponentsClientHttpRequest.java create mode 100644 spring-cloud-gateway-mvc/src/test/java/org/springframework/cloud/gateway/mvc/http/HttpComponentsClientHttpResponse.java diff --git a/spring-cloud-gateway-mvc/pom.xml b/spring-cloud-gateway-mvc/pom.xml index faa4d43e..656bac74 100644 --- a/spring-cloud-gateway-mvc/pom.xml +++ b/spring-cloud-gateway-mvc/pom.xml @@ -40,6 +40,11 @@ spring-boot-starter-actuator test + + org.apache.httpcomponents + httpclient + test + org.springframework.boot spring-boot-configuration-processor diff --git a/spring-cloud-gateway-mvc/src/main/java/org/springframework/cloud/gateway/mvc/ProxyExchange.java b/spring-cloud-gateway-mvc/src/main/java/org/springframework/cloud/gateway/mvc/ProxyExchange.java index f38f0e57..31828ffe 100644 --- a/spring-cloud-gateway-mvc/src/main/java/org/springframework/cloud/gateway/mvc/ProxyExchange.java +++ b/spring-cloud-gateway-mvc/src/main/java/org/springframework/cloud/gateway/mvc/ProxyExchange.java @@ -277,7 +277,8 @@ public class ProxyExchange { } public ResponseEntity get() { - RequestEntity requestEntity = headers((BodyBuilder) RequestEntity.get(uri)).build(); + RequestEntity requestEntity = headers((BodyBuilder) RequestEntity.get(uri)) + .body(body()); return exchange(requestEntity); } diff --git a/spring-cloud-gateway-mvc/src/test/java/org/springframework/cloud/gateway/mvc/GetWithBodyRequestTest.java b/spring-cloud-gateway-mvc/src/test/java/org/springframework/cloud/gateway/mvc/GetWithBodyRequestTest.java new file mode 100644 index 00000000..41d951e7 --- /dev/null +++ b/spring-cloud-gateway-mvc/src/test/java/org/springframework/cloud/gateway/mvc/GetWithBodyRequestTest.java @@ -0,0 +1,203 @@ +/* + * Copyright 2016-2019 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.cloud.gateway.mvc; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.io.IOException; +import java.net.URI; +import java.util.Collections; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.web.client.TestRestTemplate; +import org.springframework.boot.web.client.RestTemplateBuilder; +import org.springframework.boot.web.server.LocalServerPort; +import org.springframework.cloud.gateway.mvc.GetWithBodyRequestTest.TestApplication.Foo; +import org.springframework.cloud.gateway.mvc.config.ProxyExchangeArgumentResolver; +import org.springframework.cloud.gateway.mvc.config.ProxyProperties; +import org.springframework.cloud.gateway.mvc.http.GetWithBodyRequestClientHttpRequestFactory; +import org.springframework.context.annotation.Bean; +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.ResponseEntity; +import org.springframework.http.client.ClientHttpResponse; +import org.springframework.http.converter.ByteArrayHttpMessageConverter; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringRunner; +import org.springframework.web.bind.annotation.GetMapping; +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.RestController; +import org.springframework.web.client.DefaultResponseErrorHandler; +import org.springframework.web.client.RestTemplate; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; + +@RunWith(SpringRunner.class) +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +@ContextConfiguration(classes = GetWithBodyRequestTest.TestApplication.class) +public class GetWithBodyRequestTest { + + @Autowired + private TestRestTemplate rest; + + @Autowired + private TestApplication testApplication; + + @LocalServerPort + private int port; + + @Before + public void init() throws Exception { + testApplication.setHome(new URI("http://localhost:" + port)); + rest.getRestTemplate() + .setRequestFactory(new GetWithBodyRequestClientHttpRequestFactory()); + } + + @Test + public void get() { + assertThat(rest.getForObject("/proxy/0", Foo.class).getName()).isEqualTo("bye"); + } + + @Test + public void getWithBodyRequest() { + final HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.APPLICATION_JSON); + headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON)); + + final Foo bodyRequest = new Foo("hello"); + final HttpEntity entity = new HttpEntity<>(bodyRequest, headers); + + final ResponseEntity response = rest.exchange("/proxy/get-with-body-request", + HttpMethod.GET, entity, Foo.class); + + assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); + assertThat(response.getBody()).isInstanceOfSatisfying(Foo.class, + foo -> assertThat(foo.getName()).isEqualTo("hello world")); + } + + @SpringBootApplication + static class TestApplication { + + @Autowired + private ProxyController proxyController; + + public void setHome(URI home) { + proxyController.setHome(home); + } + + @Bean + public ProxyExchangeArgumentResolver proxyExchangeArgumentResolver( + final ProxyProperties proxy) { + ProxyExchangeArgumentResolver resolver = new ProxyExchangeArgumentResolver( + generateConfiguredRestTemplate()); + resolver.setHeaders(proxy.convertHeaders()); + resolver.setAutoForwardedHeaders(proxy.getAutoForward()); + resolver.setSensitive(proxy.getSensitive()); + return resolver; + } + + private RestTemplate generateConfiguredRestTemplate() { + final RestTemplateBuilder builder = new RestTemplateBuilder(); + final RestTemplate template = builder.build(); + + template.setRequestFactory(new GetWithBodyRequestClientHttpRequestFactory()); + template.setErrorHandler(new NoOpResponseErrorHandler()); + template.getMessageConverters().add(new ByteArrayHttpMessageConverter() { + @Override + public boolean supports(Class clazz) { + return true; + } + }); + + return template; + } + + @RestController + static class ProxyController { + + private URI home; + + public void setHome(URI home) { + this.home = home; + } + + @GetMapping("/proxy/{id}") + public ResponseEntity proxyFoos(@PathVariable Integer id, + ProxyExchange proxy) throws Exception { + return proxy.uri(home.toString() + "/foos/" + id).get(); + } + + @GetMapping("/proxy/get-with-body-request") + public ResponseEntity proxyFooWithBody(@RequestBody Foo foo, + ProxyExchange proxy) throws Exception { + return proxy.uri(home.toString() + "/foo/get-with-body-request").get(); + } + } + + @RestController + static class TestController { + + @GetMapping("/foos/{id}") + public Foo foo(@PathVariable Integer id, @RequestHeader HttpHeaders headers) { + String custom = headers.getFirst("X-Custom"); + return new Foo(id == 1 ? "foo" : custom != null ? custom : "bye"); + } + + @GetMapping("/foo/get-with-body-request") + public Foo getWithBody(@RequestBody Foo foo) { + return new Foo(foo.getName() + " world"); + } + } + + @JsonIgnoreProperties(ignoreUnknown = true) + static class Foo { + private String name; + + Foo() { + } + + Foo(String name) { + this.name = name; + } + + public String getName() { + return name; + } + + public void setName(final String name) { + this.name = name; + } + } + + private static class NoOpResponseErrorHandler + extends DefaultResponseErrorHandler { + @Override + public void handleError(ClientHttpResponse response) throws IOException { + } + } + } +} diff --git a/spring-cloud-gateway-mvc/src/test/java/org/springframework/cloud/gateway/mvc/ProductionConfigurationTests.java b/spring-cloud-gateway-mvc/src/test/java/org/springframework/cloud/gateway/mvc/ProductionConfigurationTests.java index c2430e9c..c62dc000 100644 --- a/spring-cloud-gateway-mvc/src/test/java/org/springframework/cloud/gateway/mvc/ProductionConfigurationTests.java +++ b/spring-cloud-gateway-mvc/src/test/java/org/springframework/cloud/gateway/mvc/ProductionConfigurationTests.java @@ -16,17 +16,17 @@ package org.springframework.cloud.gateway.mvc; +import static org.assertj.core.api.Assertions.assertThat; + import java.net.URI; import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Map; -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; - import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.test.context.SpringBootTest; @@ -43,6 +43,7 @@ import org.springframework.http.HttpMethod; import org.springframework.http.HttpStatus; import org.springframework.http.RequestEntity; import org.springframework.http.ResponseEntity; +import org.springframework.http.client.SimpleClientHttpRequestFactory; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringRunner; import org.springframework.util.LinkedMultiValueMap; @@ -55,7 +56,7 @@ import org.springframework.web.bind.annotation.RequestHeader; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.util.UriComponentsBuilder; -import static org.assertj.core.api.Assertions.assertThat; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; @RunWith(SpringRunner.class) @SpringBootTest(properties = { "spring.cloud.gateway.proxy.auto-forward=Baz" }, @@ -75,6 +76,7 @@ public class ProductionConfigurationTests { @Before public void init() throws Exception { application.setHome(new URI("http://localhost:" + port)); + rest.getRestTemplate().setRequestFactory(new SimpleClientHttpRequestFactory()); } @Test @@ -536,9 +538,6 @@ public class ProductionConfigurationTests { public void setName(String name) { this.name = name; } - } - } - } diff --git a/spring-cloud-gateway-mvc/src/test/java/org/springframework/cloud/gateway/mvc/config/ProxyExchangeArgumentResolverTest.java b/spring-cloud-gateway-mvc/src/test/java/org/springframework/cloud/gateway/mvc/config/ProxyExchangeArgumentResolverTest.java index 9cf70aab..3e874751 100644 --- a/spring-cloud-gateway-mvc/src/test/java/org/springframework/cloud/gateway/mvc/config/ProxyExchangeArgumentResolverTest.java +++ b/spring-cloud-gateway-mvc/src/test/java/org/springframework/cloud/gateway/mvc/config/ProxyExchangeArgumentResolverTest.java @@ -16,6 +16,8 @@ package org.springframework.cloud.gateway.mvc.config; +import static org.assertj.core.api.Assertions.assertThat; + import java.net.URI; import java.util.Collections; import java.util.List; @@ -23,7 +25,6 @@ import java.util.List; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; - import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.test.context.SpringBootTest; @@ -35,6 +36,7 @@ import org.springframework.cloud.gateway.mvc.ProxyExchange; import org.springframework.context.annotation.Bean; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; +import org.springframework.http.client.SimpleClientHttpRequestFactory; import org.springframework.http.converter.ByteArrayHttpMessageConverter; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringRunner; @@ -42,8 +44,6 @@ import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.client.DefaultResponseErrorHandler; -import static org.assertj.core.api.Assertions.assertThat; - @RunWith(SpringRunner.class) @SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT) @ContextConfiguration(classes = ProxyExchangeArgumentResolverTest.ProxyExchangeArgumentResolverTestApplication.class) @@ -61,6 +61,7 @@ public class ProxyExchangeArgumentResolverTest { @Before public void setUp() throws Exception { application.setHome(new URI("http://localhost:" + port)); + rest.getRestTemplate().setRequestFactory(new SimpleClientHttpRequestFactory()); } @Test diff --git a/spring-cloud-gateway-mvc/src/test/java/org/springframework/cloud/gateway/mvc/http/AbstractBufferingClientHttpRequest.java b/spring-cloud-gateway-mvc/src/test/java/org/springframework/cloud/gateway/mvc/http/AbstractBufferingClientHttpRequest.java new file mode 100644 index 00000000..2cbe44a4 --- /dev/null +++ b/spring-cloud-gateway-mvc/src/test/java/org/springframework/cloud/gateway/mvc/http/AbstractBufferingClientHttpRequest.java @@ -0,0 +1,48 @@ +/* + * Copyright 2016-2019 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.cloud.gateway.mvc.http; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.OutputStream; + +import org.springframework.http.HttpHeaders; +import org.springframework.http.client.AbstractClientHttpRequest; +import org.springframework.http.client.ClientHttpResponse; + +abstract class AbstractBufferingClientHttpRequest extends AbstractClientHttpRequest { + private ByteArrayOutputStream bufferedOutput = new ByteArrayOutputStream(1024); + + protected OutputStream getBodyInternal(final HttpHeaders headers) { + return bufferedOutput; + } + + protected abstract ClientHttpResponse executeInternal(final HttpHeaders headers, + final byte[] body) throws IOException; + + protected ClientHttpResponse executeInternal(final HttpHeaders headers) + throws IOException { + final byte[] bytes = bufferedOutput.toByteArray(); + if (headers.getContentLength() < 0L) { + headers.setContentLength(bytes.length); + } + + final ClientHttpResponse response = executeInternal(headers, bytes); + bufferedOutput = new ByteArrayOutputStream(0); + return response; + } +} diff --git a/spring-cloud-gateway-mvc/src/test/java/org/springframework/cloud/gateway/mvc/http/GetWithBodyRequestClientHttpRequestFactory.java b/spring-cloud-gateway-mvc/src/test/java/org/springframework/cloud/gateway/mvc/http/GetWithBodyRequestClientHttpRequestFactory.java new file mode 100644 index 00000000..2aaed08b --- /dev/null +++ b/spring-cloud-gateway-mvc/src/test/java/org/springframework/cloud/gateway/mvc/http/GetWithBodyRequestClientHttpRequestFactory.java @@ -0,0 +1,123 @@ +/* + * Copyright 2016-2019 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.cloud.gateway.mvc.http; + +import java.io.Closeable; +import java.io.IOException; +import java.net.URI; + +import org.apache.http.client.HttpClient; +import org.apache.http.client.config.RequestConfig; +import org.apache.http.client.methods.Configurable; +import org.apache.http.client.methods.HttpDelete; +import org.apache.http.client.methods.HttpEntityEnclosingRequestBase; +import org.apache.http.client.methods.HttpHead; +import org.apache.http.client.methods.HttpOptions; +import org.apache.http.client.methods.HttpPatch; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.client.methods.HttpPut; +import org.apache.http.client.methods.HttpTrace; +import org.apache.http.client.methods.HttpUriRequest; +import org.apache.http.client.protocol.HttpClientContext; +import org.apache.http.impl.client.HttpClients; +import org.apache.http.protocol.HttpContext; +import org.springframework.beans.factory.DisposableBean; +import org.springframework.http.HttpMethod; +import org.springframework.http.client.ClientHttpRequest; +import org.springframework.http.client.ClientHttpRequestFactory; + +public class GetWithBodyRequestClientHttpRequestFactory + implements ClientHttpRequestFactory, DisposableBean { + private final HttpClient httpClient; + + public GetWithBodyRequestClientHttpRequestFactory() { + this.httpClient = HttpClients.createSystem(); + } + + @Override + public ClientHttpRequest createRequest(final URI uri, final HttpMethod httpMethod) + throws IOException { + final HttpUriRequest httpRequest = createHttpUriRequest(httpMethod, uri); + final HttpContext context = HttpClientContext.create(); + + if (context.getAttribute("http.request-config") == null) { + RequestConfig config = null; + if (httpRequest instanceof Configurable) { + config = ((Configurable) httpRequest).getConfig(); + } + if (config == null) { + config = createRequestConfig(httpClient); + } + if (config != null) { + context.setAttribute("http.request-config", config); + } + } + + return new HttpComponentsClientHttpRequest(httpClient, httpRequest, context); + } + + private HttpUriRequest createHttpUriRequest(final HttpMethod httpMethod, + final URI uri) { + switch (httpMethod) { + case GET: + return new GetWithEntity(uri); + case HEAD: + return new HttpHead(uri); + case POST: + return new HttpPost(uri); + case PUT: + return new HttpPut(uri); + case PATCH: + return new HttpPatch(uri); + case DELETE: + return new HttpDelete(uri); + case OPTIONS: + return new HttpOptions(uri); + case TRACE: + return new HttpTrace(uri); + default: + throw new IllegalArgumentException("Invalid HTTP method: " + httpMethod); + } + } + + private RequestConfig createRequestConfig(final HttpClient client) { + if (client instanceof Configurable) { + return ((Configurable) client).getConfig(); + } + return null; + } + + @Override + public void destroy() throws Exception { + if (httpClient instanceof Closeable) { + ((Closeable) httpClient).close(); + } + } + + public static class GetWithEntity extends HttpEntityEnclosingRequestBase { + public static final String METHOD_NAME = "GET"; + + public GetWithEntity(final URI uri) { + setURI(uri); + } + + @Override + public String getMethod() { + return METHOD_NAME; + } + } +} diff --git a/spring-cloud-gateway-mvc/src/test/java/org/springframework/cloud/gateway/mvc/http/HttpComponentsClientHttpRequest.java b/spring-cloud-gateway-mvc/src/test/java/org/springframework/cloud/gateway/mvc/http/HttpComponentsClientHttpRequest.java new file mode 100644 index 00000000..231796e1 --- /dev/null +++ b/spring-cloud-gateway-mvc/src/test/java/org/springframework/cloud/gateway/mvc/http/HttpComponentsClientHttpRequest.java @@ -0,0 +1,93 @@ +/* + * Copyright 2016-2019 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.cloud.gateway.mvc.http; + +import java.io.IOException; +import java.net.URI; +import java.util.Iterator; + +import org.apache.http.HttpEntityEnclosingRequest; +import org.apache.http.HttpResponse; +import org.apache.http.client.HttpClient; +import org.apache.http.client.methods.HttpUriRequest; +import org.apache.http.entity.ByteArrayEntity; +import org.apache.http.protocol.HttpContext; +import org.springframework.http.HttpHeaders; +import org.springframework.http.client.ClientHttpResponse; +import org.springframework.util.StringUtils; + +final class HttpComponentsClientHttpRequest extends AbstractBufferingClientHttpRequest { + private static final String COOKIE_HEADER_NAME = "Cookie"; + private static final String CONTENT_LENGTH_HEADER_NAME = "Content-Length"; + private static final String TRANSFER_ENCODING_HEADER_NAME = "Transfer-Encoding"; + + private final HttpClient httpClient; + private final HttpUriRequest httpRequest; + private final HttpContext httpContext; + + HttpComponentsClientHttpRequest(final HttpClient httpClient, + final HttpUriRequest httpRequest, final HttpContext httpContext) { + this.httpClient = httpClient; + this.httpRequest = httpRequest; + this.httpContext = httpContext; + } + + @Override + public String getMethodValue() { + return httpRequest.getMethod(); + } + + @Override + public URI getURI() { + return httpRequest.getURI(); + } + + @Override + protected ClientHttpResponse executeInternal(final HttpHeaders headers, + final byte[] bufferedOutput) throws IOException { + addHeaders(headers); + attachBodyRequest(bufferedOutput); + + final HttpResponse response = httpClient.execute(httpRequest, httpContext); + return new HttpComponentsClientHttpResponse(response); + } + + private void addHeaders(final HttpHeaders headers) { + headers.forEach((headerName, headerValues) -> { + if (COOKIE_HEADER_NAME.equalsIgnoreCase(headerName)) { + String headerValue = StringUtils.collectionToDelimitedString(headerValues, + ": "); + httpRequest.addHeader(headerName, headerValue); + } + else if (!CONTENT_LENGTH_HEADER_NAME.equalsIgnoreCase(headerName) + && !TRANSFER_ENCODING_HEADER_NAME.equalsIgnoreCase(headerName)) { + final Iterator it = headerValues.iterator(); + while (it.hasNext()) { + String value = it.next(); + httpRequest.addHeader(headerName, value); + } + } + }); + } + + private void attachBodyRequest(final byte[] bufferedOutput) { + if (httpRequest instanceof HttpEntityEnclosingRequest) { + ((HttpEntityEnclosingRequest) httpRequest) + .setEntity(new ByteArrayEntity(bufferedOutput)); + } + } +} diff --git a/spring-cloud-gateway-mvc/src/test/java/org/springframework/cloud/gateway/mvc/http/HttpComponentsClientHttpResponse.java b/spring-cloud-gateway-mvc/src/test/java/org/springframework/cloud/gateway/mvc/http/HttpComponentsClientHttpResponse.java new file mode 100644 index 00000000..40323ae8 --- /dev/null +++ b/spring-cloud-gateway-mvc/src/test/java/org/springframework/cloud/gateway/mvc/http/HttpComponentsClientHttpResponse.java @@ -0,0 +1,84 @@ +/* + * Copyright 2016-2019 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.cloud.gateway.mvc.http; + +import java.io.Closeable; +import java.io.IOException; +import java.io.InputStream; + +import org.apache.http.Header; +import org.apache.http.HttpEntity; +import org.apache.http.HttpResponse; +import org.apache.http.util.EntityUtils; +import org.springframework.http.HttpHeaders; +import org.springframework.http.client.AbstractClientHttpResponse; +import org.springframework.lang.Nullable; +import org.springframework.util.StreamUtils; + +final class HttpComponentsClientHttpResponse extends AbstractClientHttpResponse { + private final HttpResponse response; + @Nullable + private HttpHeaders headers; + + HttpComponentsClientHttpResponse(final HttpResponse response) { + this.response = response; + } + + @Override + public int getRawStatusCode() throws IOException { + return response.getStatusLine().getStatusCode(); + } + + @Override + public String getStatusText() throws IOException { + return response.getStatusLine().getReasonPhrase(); + } + + @Override + public HttpHeaders getHeaders() { + if (headers == null) { + headers = new HttpHeaders(); + final Header[] responseHeaders = response.getAllHeaders(); + for (Header header : responseHeaders) { + headers.add(header.getName(), header.getValue()); + } + } + return headers; + } + + @Override + public InputStream getBody() throws IOException { + final HttpEntity entity = response.getEntity(); + return entity != null ? entity.getContent() : StreamUtils.emptyInput(); + } + + @Override + public void close() { + try { + try { + EntityUtils.consume(response.getEntity()); + } + finally { + if (response instanceof Closeable) { + ((Closeable) response).close(); + } + } + } + catch (IOException ignored) { + } + } +}