One of our external API uses query param names with special characters (don't ask me why, I don't know). My feign client's method for this API is declared like this:
@GetMapping("/v1/user/{userId}/orders")
List<Order> getOrders(
@PathVariable("userId") String userId,
@RequestParam("page[number]") Integer pageNumber,
@RequestParam("page[size]") Integer pageSize);
As I mentioned, request params contain special characters [
and ]
.
I'm using Spock for testing and I want to set up Wiremock stub like this:
wiremock.stubFor(
get(urlPathMatching('/v1/users/' + userId + '/orders'))
.withQueryParam("page[number]", new EqualToPattern("1"))
.withQueryParam("page[size]", new AnythingPattern())
.willReturn(
status(200)
.withHeader("Content-type", "application/json")
.withBody("""[]""")
))
But I get:
--------------------------------------------------------------------------------------------------
| Closest stub | Request |
--------------------------------------------------------------------------------------------------
|
GET | GET
/v1/users/123/orders | /v1/users/123/orders?page%5Bnumber%5D=%7Bpage%5Bnumber%5
| D%7D&page%5Bsize%5D=%7Bpage%5Bsize%5D%7D
|
Query: page[number] = 1 | <<<<< Query is not present
Query: page[size] [anything] (always) | <<<<< Query is not present
|
--------------------------------------------------------------------------------------------------
After lots of trials and errors, I came up with a solution to use @PathVariable
s instead of @RequestParam
s in feign client method:
@GetMapping("/v1/users/{userId}/orders?page[number]={pageNumber}&page[size]={pageSize}")
List<Order> getOrders(
@PathVariable("userId") String userId,
@PathVariable("pageNumber") Integer pageNumber,
@PathVariable("pageSize") Integer pageSize);
and encode all query params in Wiremock
wiremock.stubFor(
get(urlPathMatching('/v1/users/' + userId + '/orders'))
.withQueryParam("page%5Bnumber%5D", new EqualToPattern("1"))
.withQueryParam("page%5Bsize%5D", new AnythingPattern())
.willReturn(
status(200)
.withHeader("Content-type", "application/json")
.withBody("""[]""")
))
Then it works. But it looks like a kind of a hack. It is problematic to use an optional query params as well.
Is there a way to use @RequestParam
s with special characters?
It looks like a bug in Spring?
In the mean time I will try to debug it to understand where is the problem.
First of all, it's not a bug in Spring. The main goal of these spring-like annotations (@RequestParam
, @RequestMapping
etc. from spring-web
) is to use them in Spring MVC controllers on the server-side and not for create urls for requests.
In the case of Feign clients, there is another library, spring-cloud-openfeign
, which allows you to have them in Feign Clients for request mapping instead of original Feign annotations (like @RequestLine
, @Param
and so on). SpringMvcContract
is responsible for it, and it is used by default in Spring applications. But it's just for the convenience of this specific group of developers who're using Feign with Spring, so it's not a reason to change signatures of these annotations or add them some new settings.
About described problem - seems like there isn't any "official" way to disable url encoding even if you're using original Feign annotations for request mapping - at the moment (version 10.7.4), you can only do it for slashes (check this issue, for example).
I've performed some further investigation and come to these points:
RequestInterceptor
(by reflection, f.e.), but it's actually pointless because after applying all interceptors the request is built and, at the moment, it implicitly encodes the brackets again (check what's happened under the hood, basically it uses queryTemplate.toString()
which, in turn, encodes brackets while using methods of UriUtils class)Client
, and I actually managed to fix the url at this step. I inherited a class from the standard Client.Default
, registered it instead of the original auto-configurated one, and then I did my job before actually invoking the client (ofc it's possible to do the same for a different client, f.e., for LoadBalancerFeignClient
):import feign.Client;
import feign.Request;
import feign.Response;
import org.springframework.stereotype.Component;
import org.springframework.util.ReflectionUtils;
import java.io.IOException;
import java.lang.reflect.Field;
@Component
public class MyClient implements Client {
private Client delegate;
public MyClient() {
this.delegate = new Client.Default(null, null);
}
@Override
public Response execute(Request request, Request.Options options) throws IOException {
fixRequestUrlIfPossible(request);
return delegate.execute(request, options);
}
private void fixRequestUrlIfPossible(Request request) {
try {
Field urlField = ReflectionUtils.findField(Request.class, "url");
urlField.setAccessible(true);
String encodedUrl = (String) urlField.get(request);
String url = encodedUrl.replace("%5B", "[").replace("%5D", "]");
urlField.set(request, url);
} catch (Exception e) {
/*NOP*/
}
}
}
But it's error prone because of the reflection (can stop work in later versions of Feign).
So, it's up to you - if you're not afraid of doing this kind of things, use this approach. I actually like your version more. The main question is: is your real server able to handle requests with encoded brackets? Problems with Wiremock are less important, in my opinion.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With