Merge branch '2.1.x'

This commit is contained in:
Spencer Gibb
2019-09-19 19:03:07 -04:00
3 changed files with 102 additions and 84 deletions

View File

@@ -116,84 +116,77 @@ public class NettyRoutingFilter implements GlobalFilter, Ordered {
boolean preserveHost = exchange
.getAttributeOrDefault(PRESERVE_HOST_HEADER_ATTRIBUTE, false);
Flux<HttpClientResponse> responseFlux = this.httpClient.request(method).uri(url)
.send((req, nettyOutbound) -> {
req.headers(httpHeaders);
Flux<HttpClientResponse> responseFlux = this.httpClient.headers(headers -> {
headers.add(httpHeaders);
if (preserveHost) {
String host = request.getHeaders().getFirst(HttpHeaders.HOST);
headers.add(HttpHeaders.HOST, host);
}
}).request(method).uri(url).send((req, nettyOutbound) -> {
if (log.isTraceEnabled()) {
nettyOutbound.withConnection(connection -> log.trace(
"outbound route: " + connection.channel().id().asShortText()
+ ", inbound: " + exchange.getLogPrefix()));
}
return nettyOutbound.send(request.getBody()
.map(dataBuffer -> ((NettyDataBuffer) dataBuffer).getNativeBuffer()));
}).responseConnection((res, connection) -> {
if (preserveHost) {
String host = request.getHeaders().getFirst(HttpHeaders.HOST);
req.header(HttpHeaders.HOST, host);
}
if (log.isTraceEnabled()) {
nettyOutbound
.withConnection(connection -> log.trace("outbound route: "
+ connection.channel().id().asShortText()
+ ", inbound: " + exchange.getLogPrefix()));
}
return nettyOutbound.send(request.getBody()
.map(dataBuffer -> ((NettyDataBuffer) dataBuffer)
.getNativeBuffer()));
}).responseConnection((res, connection) -> {
// Defer committing the response until all route filters have run
// Put client response as ServerWebExchange attribute and write
// response later NettyWriteResponseFilter
exchange.getAttributes().put(CLIENT_RESPONSE_ATTR, res);
exchange.getAttributes().put(CLIENT_RESPONSE_CONN_ATTR, connection);
// Defer committing the response until all route filters have run
// Put client response as ServerWebExchange attribute and write
// response later NettyWriteResponseFilter
exchange.getAttributes().put(CLIENT_RESPONSE_ATTR, res);
exchange.getAttributes().put(CLIENT_RESPONSE_CONN_ATTR, connection);
ServerHttpResponse response = exchange.getResponse();
// put headers and status so filters can modify the response
HttpHeaders headers = new HttpHeaders();
ServerHttpResponse response = exchange.getResponse();
// put headers and status so filters can modify the response
HttpHeaders headers = new HttpHeaders();
res.responseHeaders()
.forEach(entry -> headers.add(entry.getKey(), entry.getValue()));
res.responseHeaders().forEach(
entry -> headers.add(entry.getKey(), entry.getValue()));
String contentTypeValue = headers.getFirst(HttpHeaders.CONTENT_TYPE);
if (StringUtils.hasLength(contentTypeValue)) {
exchange.getAttributes().put(ORIGINAL_RESPONSE_CONTENT_TYPE_ATTR,
contentTypeValue);
}
String contentTypeValue = headers.getFirst(HttpHeaders.CONTENT_TYPE);
if (StringUtils.hasLength(contentTypeValue)) {
exchange.getAttributes().put(ORIGINAL_RESPONSE_CONTENT_TYPE_ATTR,
contentTypeValue);
}
HttpStatus status = HttpStatus.resolve(res.status().code());
if (status != null) {
response.setStatusCode(status);
}
else if (response instanceof AbstractServerHttpResponse) {
// https://jira.spring.io/browse/SPR-16748
((AbstractServerHttpResponse) response)
.setStatusCodeValue(res.status().code());
}
else {
// TODO: log warning here, not throw error?
throw new IllegalStateException("Unable to set status code on response: "
+ res.status().code() + ", " + response.getClass());
}
HttpStatus status = HttpStatus.resolve(res.status().code());
if (status != null) {
response.setStatusCode(status);
}
else if (response instanceof AbstractServerHttpResponse) {
// https://jira.spring.io/browse/SPR-16748
((AbstractServerHttpResponse) response)
.setStatusCodeValue(res.status().code());
}
else {
// TODO: log warning here, not throw error?
throw new IllegalStateException(
"Unable to set status code on response: "
+ res.status().code() + ", "
+ response.getClass());
}
// make sure headers filters run after setting status so it is
// available in response
HttpHeaders filteredResponseHeaders = HttpHeadersFilter
.filter(getHeadersFilters(), headers, exchange, Type.RESPONSE);
// make sure headers filters run after setting status so it is
// available in response
HttpHeaders filteredResponseHeaders = HttpHeadersFilter.filter(
getHeadersFilters(), headers, exchange, Type.RESPONSE);
if (!filteredResponseHeaders.containsKey(HttpHeaders.TRANSFER_ENCODING)
&& filteredResponseHeaders.containsKey(HttpHeaders.CONTENT_LENGTH)) {
// It is not valid to have both the transfer-encoding header and
// the content-length header.
// Remove the transfer-encoding header in the response if the
// content-length header is present.
response.getHeaders().remove(HttpHeaders.TRANSFER_ENCODING);
}
if (!filteredResponseHeaders
.containsKey(HttpHeaders.TRANSFER_ENCODING)
&& filteredResponseHeaders
.containsKey(HttpHeaders.CONTENT_LENGTH)) {
// It is not valid to have both the transfer-encoding header and
// the content-length header.
// Remove the transfer-encoding header in the response if the
// content-length header is present.
response.getHeaders().remove(HttpHeaders.TRANSFER_ENCODING);
}
exchange.getAttributes().put(CLIENT_RESPONSE_HEADER_NAMES,
filteredResponseHeaders.keySet());
exchange.getAttributes().put(CLIENT_RESPONSE_HEADER_NAMES,
filteredResponseHeaders.keySet());
response.getHeaders().putAll(filteredResponseHeaders);
response.getHeaders().putAll(filteredResponseHeaders);
return Mono.just(res);
});
return Mono.just(res);
});
if (properties.getResponseTimeout() != null) {
responseFlux = responseFlux.timeout(properties.getResponseTimeout(),

View File

@@ -99,8 +99,12 @@ public class GatewayMetricsFilterTests extends BaseWebClientTests {
}
private void assertMetricsContainsTag(String tagKey, String tagValue) {
// @formatter:off
assertThat(this.meterRegistry.get(REQUEST_METRICS_NAME).tag(tagKey, tagValue)
.timer().count()).isEqualTo(1);
.timer().count())
.as("Wrong value for metric %s: %s", tagKey, tagValue)
.isGreaterThanOrEqualTo(1);
// @formatter:on
}
@EnableAutoConfiguration

View File

@@ -29,10 +29,10 @@ import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.context.annotation.Import;
import org.springframework.core.io.ClassPathResource;
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;
import org.springframework.http.client.MultipartBodyBuilder;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.util.LinkedMultiValueMap;
@@ -43,6 +43,7 @@ import static org.assertj.core.api.Assertions.assertThat;
import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT;
import static org.springframework.cloud.gateway.test.TestUtils.getMap;
import static org.springframework.http.MediaType.APPLICATION_FORM_URLENCODED;
import static org.springframework.http.MediaType.MULTIPART_FORM_DATA;
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = RANDOM_PORT)
@@ -59,35 +60,55 @@ public class FormIntegrationTests extends BaseWebClientTests {
formData.add("foo", "bar");
formData.add("baz", "bam");
// @formatter:off
testClient.post().uri("/post").contentType(FORM_URL_ENCODED_CONTENT_TYPE)
.body(BodyInserters.fromFormData(formData)).exchange().expectStatus()
.isOk().expectBody(Map.class).consumeWith(result -> {
.body(BodyInserters.fromFormData(formData))
.exchange()
.expectStatus().isOk()
.expectBody(Map.class).consumeWith(result -> {
Map map = result.getResponseBody();
Map<String, Object> form = getMap(map, "form");
assertThat(form).containsEntry("foo", "bar");
assertThat(form).containsEntry("baz", "bam");
});
// @formatter:on
}
@Test
public void multipartFormDataWorks() {
ClassPathResource img = new ClassPathResource("1x1.png");
public void multipartFormDataWorksWebClient() {
MultiValueMap<String, HttpEntity<?>> formData = createMultipartData();
// @formatter:off
testClient.post().uri("/post").contentType(MULTIPART_FORM_DATA)
.syncBody(formData)
.exchange()
.expectStatus().isOk()
.expectBody(Map.class)
.consumeWith(result -> assertMultipartData(result.getResponseBody()));
// @formatter:on
}
@Test
public void multipartFormDataWorksRestTemplate() {
MultiValueMap<String, HttpEntity<?>> formData = createMultipartData();
TestRestTemplate rest = new TestRestTemplate();
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.IMAGE_PNG);
HttpEntity<ClassPathResource> entity = new HttpEntity<>(img, headers);
MultiValueMap<String, Object> parts = new LinkedMultiValueMap<>();
parts.add("imgpart", entity);
ResponseEntity<Map> response = rest.postForEntity(baseUri + "/post", parts,
ResponseEntity<Map> response = rest.postForEntity(baseUri + "/post", formData,
Map.class);
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
Map<String, Object> files = getMap(response.getBody(), "files");
assertMultipartData(response.getBody());
}
private MultiValueMap<String, HttpEntity<?>> createMultipartData() {
ClassPathResource part = new ClassPathResource("1x1.png");
MultipartBodyBuilder builder = new MultipartBodyBuilder();
builder.part("imgpart", part, MediaType.IMAGE_PNG);
return builder.build();
}
private void assertMultipartData(Map responseBody) {
Map<String, Object> files = getMap(responseBody, "files");
assertThat(files).containsKey("imgpart");
String file = (String) files.get("imgpart");
assertThat(file).startsWith("data:").contains(";base64,");