A proxy endpoint that handles both JSON & x-www-form-urlencoded

Spac Valentin
3 min readNov 5, 2019

A few weeks back, I got a request to expose a ‘proxy’ endpoint. It had to accept an arbitrary JSON payload with only 2 mandatory fields: email and
fromEmail. This endpoint had to run some logic based on those 2 emails, remove one field (fromEmail) and pass forward the request to a third-party.
The rest of the JSON fields are not of interest and are only to be forwarded.

Therefore, I decided to write this article specifically because of the “tricks” I had to pull that are presented below. Full code can be found on GitHub.

Usual suspects

Let’s start with the usual suspects, an endpoint, a DTO with constraint validation annotations on the expected fields and the rest of the request body in a map (which is later forwarded). Logic is encapsulated in ReferralService and we use RestTemplate to forward the request. Tests are there to validate the behavior so we are good to go.

Now let’s add the same support for x-www-form-urlencoded requests.

Add x-www-form-urlencoded support

1. Adding tests

We start by adding a new test class. Spring doesn’t have a RequestMatcher for x-www-form-urlencoded so I had to build one (opened a PR).

2. Update controller

Time to update the controller. We can change the existing endpoint to consumex-www-form-urlencoded along with JSON but this won’t work because @RequestBody uses HttpMessageConverter to convert request payload to POJO (Plain Old Java Object) and does not know how to handle form-encoded parameters (1).

We could change@RequestBody to @ModelAttribute to accommodate the form-encoded request conversion but it will break the JSON conversion. In addition, only the explicitly declared fields (email & fromEmail ) from ReferralRegister will be populated because@ModelAttribute needs a 1 to 1 mapping between form data and POJO fields. The easy way out is just to create another handler method for form data.

2.1 Form data

Using @ModelAttribute on the new endpoint still does not fit our needs because we don’t know all the form fields and, as mentioned earlier, only the explicitly declared fields will be populated (2).

2.2 Request parameters as function arguments

To handle the above issue, we list the expected fields in the parameters list of the handler function. The request still gets validated because we are using @Email and @NotNull annotations and we added @Validated on controller

This approach breaks encapsulation as we need to change in two places (ReferralRegister#sanitized & ReferralController#formUrlEncodedHandler) if, for example, we need to translate the fromEmail to its associated user id and pass it along to the third-party.

3. Custom HttpMessageConverter

Let’s fix this by creating a custom HttpMessageConverter that will convert form parameters to our POJO.

ReferralConverter must be declared as a bean for Spring to pick it up, so we add it in Config inner class from Main . We also add the converter to the RestTemplate used for forwarding the request so it uses the same converter for transforming the POJO back to form encoded parameters (see ReferralConverter#writeInternal ).

Spring configuration

Now we can get rid of the ReferralController#formUrlEncodedHandler method and use JSON ReferralController#handler for both JSON
& x-www-form-urlencoded requests! Constraint annotations will continue to validate the payload and we now have all the logic in one place. If anything related to read/write the payload needs to change, we only need to update the converter.

Conclusion

I am not sure why but I was living under the impression that Spring had an out of the box mechanism for such use cases. Seems like it is only giving you the tools to do so and you have to handle it yourself.

If you encountered a similar issue or you have a better approach to this one, drop me a message. I’m keen to see how others tackled this.

(1) You can test it by checking out git tag MediaTypeNotSupported and run the tests

(2) You can test it by checking out git tag ExplicitFields and run the tests

--

--