Spring Cloud Gateway — Custom Filter to convert POST request with query parameters in body to GET request

Sumant Rana
The Startup
Published in
5 min readJun 4, 2020

This post provides a working example of using Spring Cloud Gateway to convert a POST request with query parameters in body to a GET request having those parameters as a part of the URL.

To get hold of the code referred to in this article please visit the repository @ https://github.com/sumantrana/SpringGatewayCustomFilter.git

Introduction

While modernizing legacy systems we come across certain requests which violate the REST principles.

An example of such a request is fetching data from the server in a POST request with query parameters present as a part of the body. This might be acceptable in cases where the query parameters are present in bulk and it is not feasible to put them in the URL but not otherwise.

In cases where we are modernizing a part of legacy portfolio which is still going to interact with existing legacy systems it is not possible to enhance those legacy systems because they are out of current scope. At the same time we would not want to design modern APIs violating REST Architectural Principles. So how do we bridge this gap?

Anti Corruption Layer is a well known pattern in the micro services world which comes in handy in such situations. The ACL layer can act as the transformation layer converting to and from the legacy requests to modern ones. There are multiple ways in which these ACLs can be implemented and deployed.

If we take our example of fetching data in POST request (with query parameters in the body), we can write a spring boot application with a controller to handle POST request and convert it into a GET request with appropriate query parameters or we can use the new spring cloud gateway, write a custom filter and let it automatically do the conversion for us.

This article will get you started with the later, a custom filter in spring cloud gateway.

Custom Filter

To create a custom gateway filter in Spring Cloud Gateway we can either implement the AbstractGatewayFilterFactory or we can extend the functionality of a existing GatewayFilter. Here we are going to implement a new filter by implementing AbstractGatewayFilterFactory.

The Config class provides a mechanism to pass in required parameters to the custom filter. In our example, it is an empty class but it can be used for customizing body parser as explained later in the article.

Extract Body of Request

In order to convert the parameters in body of POST request, we need to be able to extract the body first.

Convert Body to Query Params

Next, we parse the json body and extract query parameters out of it in the form of a MultivalueMap

This method is present is a separate class called BodyHelper, which acts as a utility class to convert the body to a map of query parameters.

If we intend to generalize the filter for POST requests with different body semantics, we can use the Config class in the custom filter to accept the BodyHelper class as a parameter to be used for parsing the body and creating target query parameters.

Modify Http Request

In order to modify the incoming request, make use of ServerHttpRequestDecorator This class has helper methods to selectively override the incoming request attributes.

Here we override the incoming request and clear out the body (as it is a GET request now), update the URI with the query param map (created in previous step) and finally override the MethodValue to return GET instead of POST.

We need to override getMethodValue() instead of the getMethod() in order to successfully change HttpMethod. Overriding only the getMethod() is not going to change the actual request.

Mutate and Return ServerWebExchange to chain

Finally modify the incoming exchange by mutating it and sending it down the filter chain

Configuration

The custom filter needs to be configured using the application.properties (or application.yml). The following snippet shows the required configuration

spring:
cloud:
gateway:
routes:
- id: path_route
uri: http://localhost:8080/
predicates:
- Path=/myDefaultBook
filters:
- ConvertPostToGetGatewayFilter
- RewritePath=/myDefaultBook, /defaultBook

The above configuration creates a new route path_routeto the target uri http://localhost:8080 where the target service is hosted. It is expecting a POST request at the local uri at /myDefaultBook . Once the request is received, it will be handed over to out custom filter ConvertPostToGetGatewayFilter where it will be converted to a GET request. This request will then be handed over to the RewritePath filter, which will change the uri from /myDefaultBook to /myBook and forward the request. Finally a POST URI on gateway http://<host>:<port>/myDefaultGateway will convert to a GET URI on the target http://localhost:8080/defaultBook.

Custom Filter Test

The filter can be tested by mocking a REST endpoint using wiremock or equivalent library to spin up a mock endpoint.

The following code snippet shows a working test case:

@ExtendWith({SpringExtension.class})
@SpringBootTest( webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
public class GatewayPathForwardingTests {
@Autowired
private TestRestTemplate testRestTemplate;
@LocalServerPort
private int port;
private WireMockServer wireMockServer;
@BeforeAll
public void init(){
wireMockServer = new WireMockServer(options().port(8080));
wireMockServer.start();
}
@AfterAll
public void destroy(){
wireMockServer.stop();
}
@Test
public void pathForwardToDefaultBook_ReturnsDefaultBook(){
String returnValue = "{\n"
+ " "
+ "\"id\": 0,\n"
+ " \"name\":\"DefaultBook\",\n"
+ " \"value\":25,\n"
+ " \"authorList\":null,\n"
+ "}";
wireMockServer.stubFor( get("/defaultBook?vin=abcde12345")
.withHeader("NadId", equalTo("test"))
.withHeader("Vin", equalTo("test"))
.willReturn( status(200).withBody(returnValue)));
String gatewayUrl = "http://localhost:"
+ port +
"/myDefaultBook";
MultiValueMap<String, String> headers = new LinkedMultiValueMap<>();
headers.add("NadId", "test");
headers.add("Vin", "test");
//language=JSON
String json = "{\n"
+ " \"vin\": \"abcde12345\"\n"
+
"}";
ResponseEntity<String> outputEntity = testRestTemplate
.exchange(gatewayUrl,
HttpMethod.POST,
new HttpEntity<String>(json, headers),
String.class);
assertThat( outputEntity.getStatusCode() )
.isEqualTo( HttpStatus.OK );
assertThat( outputEntity.getBody() )
.contains( "DefaultBook" );
}

To get hold of the code referred to in this article please visit the repository @ https://github.com/sumantrana/SpringGatewayCustomFilter.git

--

--