From d1211012c65e6f1bffafd99b73e4052fd81303e2 Mon Sep 17 00:00:00 2001 From: Spencer Gibb Date: Tue, 7 Feb 2017 09:57:47 -0700 Subject: [PATCH] form/urlencoded support --- .../cloud/gateway/filter/RoutingFilter.java | 55 ++++++++++++------- .../gateway/test/GatewayIntegrationTests.java | 26 +++++++++ src/test/resources/application.yml | 11 +--- 3 files changed, 64 insertions(+), 28 deletions(-) diff --git a/src/main/java/org/springframework/cloud/gateway/filter/RoutingFilter.java b/src/main/java/org/springframework/cloud/gateway/filter/RoutingFilter.java index 7603b70e..5aef13d0 100644 --- a/src/main/java/org/springframework/cloud/gateway/filter/RoutingFilter.java +++ b/src/main/java/org/springframework/cloud/gateway/filter/RoutingFilter.java @@ -1,12 +1,15 @@ package org.springframework.cloud.gateway.filter; import java.net.URI; +import java.util.List; +import java.util.Map; import java.util.Optional; import org.springframework.core.Ordered; import org.springframework.core.io.buffer.DataBuffer; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; import org.springframework.http.server.reactive.ServerHttpRequest; import org.springframework.http.server.reactive.ServerHttpResponse; import org.springframework.web.server.ServerWebExchange; @@ -21,6 +24,7 @@ import io.netty.handler.codec.http.HttpMethod; import reactor.core.publisher.Mono; import reactor.ipc.netty.NettyPipeline; import reactor.ipc.netty.http.client.HttpClient; +import reactor.ipc.netty.http.client.HttpClientRequest; /** * @author Spencer Gibb @@ -53,26 +57,39 @@ public class RoutingFilter implements GlobalFilter, Ordered { final DefaultHttpHeaders httpHeaders = new DefaultHttpHeaders(); request.getHeaders().forEach(httpHeaders::set); - return this.httpClient.request(method, url, req -> - req.options(NettyPipeline.SendOptions::flushOnEach) - .headers(httpHeaders) - .sendHeaders() - .send(request.getBody() - .map(DataBuffer::asByteBuffer) - .map(Unpooled::wrappedBuffer))) - .then(res -> { - ServerHttpResponse response = exchange.getResponse(); - // put headers and status so filters can modify the response - final HttpHeaders headers = new HttpHeaders(); - res.responseHeaders().forEach(entry -> headers.add(entry.getKey(), entry.getValue())); + return this.httpClient.request(method, url, req -> { + final HttpClientRequest proxyRequest = req.options(NettyPipeline.SendOptions::flushOnEach) + .headers(httpHeaders); - response.getHeaders().putAll(headers); - response.setStatusCode(HttpStatus.valueOf(res.status().code())); + if (MediaType.APPLICATION_FORM_URLENCODED.includes(request.getHeaders().getContentType())) { + return exchange.getFormData() + .then(map -> proxyRequest.sendForm(form -> { + for (Map.Entry> entry: map.entrySet()) { + for (String value : entry.getValue()) { + form.attr(entry.getKey(), value); + } + } + }).then()) + .then(chain.filter(exchange)); + } - // Defer committing the response until all route filters have run - // Put client response as ServerWebExchange attribute and write response later WriteResponseFilter - exchange.getAttributes().put(CLIENT_RESPONSE_ATTR, res); - return Mono.empty(); - }).then(chain.filter(exchange)); + return proxyRequest.sendHeaders() + .send(request.getBody() + .map(DataBuffer::asByteBuffer) + .map(Unpooled::wrappedBuffer)); + }).then(res -> { + 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())); + + response.getHeaders().putAll(headers); + response.setStatusCode(HttpStatus.valueOf(res.status().code())); + + // Defer committing the response until all route filters have run + // Put client response as ServerWebExchange attribute and write response later WriteResponseFilter + exchange.getAttributes().put(CLIENT_RESPONSE_ATTR, res); + return Mono.empty(); + }).then(chain.filter(exchange)); } } diff --git a/src/test/java/org/springframework/cloud/gateway/test/GatewayIntegrationTests.java b/src/test/java/org/springframework/cloud/gateway/test/GatewayIntegrationTests.java index c612e360..72d2ac9c 100644 --- a/src/test/java/org/springframework/cloud/gateway/test/GatewayIntegrationTests.java +++ b/src/test/java/org/springframework/cloud/gateway/test/GatewayIntegrationTests.java @@ -21,7 +21,10 @@ import org.springframework.core.annotation.Order; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; +import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.context.junit4.SpringRunner; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.web.reactive.function.BodyInserters; import org.springframework.web.reactive.function.client.ClientResponse; import org.springframework.web.reactive.function.client.WebClient; import org.springframework.web.server.ServerWebExchange; @@ -45,6 +48,7 @@ import reactor.test.StepVerifier; @RunWith(SpringRunner.class) @SpringBootTest(webEnvironment = RANDOM_PORT) +@DirtiesContext//(methodMode = DirtiesContext.MethodMode.AFTER_METHOD) @SuppressWarnings("unchecked") public class GatewayIntegrationTests { @@ -246,6 +250,28 @@ public class GatewayIntegrationTests { .verify(DURATION); } + @Test + public void formUrlencodedWorks() { + LinkedMultiValueMap formData = new LinkedMultiValueMap<>(); + formData.add("foo", "bar"); + formData.add("baz", "bam"); + + Mono result = webClient.post() + .uri("/post") + .contentType(MediaType.APPLICATION_FORM_URLENCODED) + .exchange(BodyInserters.fromFormData(formData)) + .then(response -> response.body(toMono(Map.class))); + + StepVerifier.create(result) + .consumeNextWith(map -> { + Map form = getMap(map, "form"); + assertThat(form).containsEntry("foo", "bar"); + assertThat(form).containsEntry("baz", "bam"); + }) + .expectComplete() + .verify(DURATION); + } + @Test public void postWorks() { Mono result = webClient.post() diff --git a/src/test/resources/application.yml b/src/test/resources/application.yml index 6b317b6e..17c2d022 100644 --- a/src/test/resources/application.yml +++ b/src/test/resources/application.yml @@ -48,13 +48,6 @@ spring: filters: - AddResponseHeader=X-Request-Foo, Bar - # ===================================== - - id: complex_content_type_test - uri: http://httpbin.org:80 - predicates: - - Host=**.complexcontenttype.org - - Url=/headers - # ===================================== - id: hystrix_failure_test uri: http://httpbin.org:80 @@ -181,8 +174,8 @@ myservice: logging: level: org.springframework.cloud.gateway: TRACE -# org.springframework.http.server.reactive: DEBUG -# reactor.ipc.netty: DEBUG + org.springframework.http.server.reactive: DEBUG + reactor.ipc.netty: DEBUG management: context-path: /admin