Feign - URL encode path params - spring-boot

This is my contract,
#RequestLine("GET /products/{id}")
#Headers({"Content-Type: application/json"})
ApiResponse getProduct(#Param("id") String productId) throws Exception;
I want to fetch the product with id = "a/b",
If I send this as a param to getProduct("a/b")
then the URL that is formed is http://api/products/a/b and I am getting a 404 instead the url should be http://api/products/a%2Fb
Is there a way around this?

A simple config did it,
#RequestLine(value = "GET /products/{id}", decodeSlash = false)
#Headers({"Content-Type: application/json"})
ApiResponse getProduct(#Param("id") String productId) throws Exception;
The path param was correctly getting encoded but the RequestTemplate was decoding the URL again (decodeSlash=true by default) before sending out the request which was causing the issue.

In my case, when code looks like this:
#GetMapping(path = "/document/{documentId}/files/{fileId}")
ResponseEntity<byte[]> getDocument(#PathVariable("documentId") String documentId, #PathVariable(value = "fileId") String fileId);
Also problem was that #PathVariable fileId could be 123/SGINED.
Setting application.property feign.client.decodeSlash=false helped.

Related

Pass a string in payload of POST

Usually we pass key-value pair in JSON payload for POST/PUT request. Is it possible to pass a sting only ex:
If so what do we set the object for #RequestBody? Would it be String type or JSONObject?
I did this:
#PostMapping(value = "businessdate", consumes = MediaType.TEXT_PLAIN_VALUE)
public void postBusinessDate(#RequestBody String businessDate) throws IOException, InterruptedException, SQLException {
businessDateService.updateBusinessDate(LocalDate.parse(businessDate));
}
and passed this:
It would be a String. Even though you choose content type as application/json
What worked for me is setting
#PutMapping(value = "/test/{id}", consumes = MediaType.APPLICATION_JSON_VALUE)
String updateInfo(#RequestHeader(#PathVariable("id") String id, #RequestBody String payload){...}
and passing the string payload by escacping the double quotes
updateInfo(token, id, "\"TEST\"");

java.lang.AssertionError: Status : 404

Please Could someone help me , I cant figure out what is the problem, I'am trying to implement a test to this method but it always gives me
java.lang.AssertionError: Status
Expected :200
Actual :400
#PutMapping("/infoUtile/update/{id}")
public Map<String,Object> editActualite(#PathVariable Long id, #Valid #RequestParam ArrayList<Long> idDeleted,
#Valid #RequestParam String content, #Valid #RequestParam String description){
InformationUtile info = this.infoUtileService.getInfoUtileById(id);
info.setContent(content);
info.setDescription(description);
info.setDate(new Date());
if(idDeleted.size() != 0) {
for (int i = 0; i < idDeleted.size(); i++) {
this.mediaService.deleteMedia(idDeleted.get(i));
}
}
InformationUtile i = this.infoUtileService.addOrEditInfoUtile(info);
return getInfoUtileWeb(i);
}
and here is my test that Im trying to implement
#Test
public void update() throws Exception {
InformationUtile informationUtile = new InformationUtile();
informationUtile.setId(1);
informationUtile.setContent("oumaima");
informationUtile.setDescription("test");
Media medias = new Media();
medias.setId(1);
medias.setType("image/png");
medias.setUrl("C:\\files\\actualite\\32769\\adobexd.png");
List<Media> allMedias = new ArrayList<Media>();
allMedias.add(medias);
informationUtile.setMedias(allMedias);
User user = new User();
user.setId(1);
user.setNom("oumaima");
informationUtile.setUser(user);
ArrayList<Long> idDeleted = new ArrayList<>();
idDeleted.add(0L);
Mockito.when(informationUtileService.getInfoUtileById(Mockito.<Long>any())).thenReturn(new InformationUtile());
Mockito.when(informationUtileService.addOrEditInfoUtile(Mockito.any(InformationUtile .class))).thenReturn(informationUtile);
mockMvc.perform(put("/infoUtile/update/{id}",informationUtile.getId()).requestAttr("idDeleted",idDeleted)
.param("content",informationUtile.getContent())
.param("description",informationUtile.getDescription())
)
.andExpect(status().isOk());
verify(informationUtileService, times(1)).getInfoUtileById(informationUtile.getId());
verify(informationUtileService, times(1)).addOrEditInfoUtile(informationUtile);
verifyNoMoreInteractions(informationUtileService);
}
You are defining three request parameters at your endpoint #Valid #RequestParam ArrayList<Long> idDeleted, #Valid #RequestParam String content, #Valid #RequestParam String description which means they are query parameters after the url, e.g. http://localhost:8080/?idDeleted=1&idDeleted=2&content=Hello&description=Duke.
The HTTP 404 indicates that Spring could not find a handler for your request, meaning the client (in your case MockMvc) has a malformed URL.
In your current MockMvc request setup you are using .requestAttr() for the idDeleted request parameter.
All of them should be .param():
mockMvc
.perform(put("/infoUtile/update/{id}",informationUtile.getId())
.param("idDeleted", idDeletedOne , idDeletedTwo)
.param("content",informationUtile.getContent())
.param("description",informationUtile.getDescription())
)
PS: I guess the #Valid annotations are redundant/not needed here as you are not checking e.g. payload which has Bean Validation annotations to verify the content.
UPDATE: .param() is overloaded with .parm(String name, String... values), so you can pass your list of idDeleted with either .param("idDeleted", idDeletedOne, idDeletedTwo) or you can pass a String[] with all your Long values represented as a String

Spring get all queries params as a string

I have an API gateway that handles all GET requests and forwards them to the correct url like so
#RequestMapping(value = "**", method = RequestMethod.GET)
public #ResponseBody ResponseEntity<String> doGet(HttpServletRequest req) {
String uriString = (String) req.getAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE);
String targetHost = uriString.split("/")[0];
URI uri = UriComponentsBuilder.fromUriString(targetHost)
.path(uriString)
.build().normalize().encode().toUri();
try {
ClientHttpRequest request = requestFactory.createRequest(uri, HttpMethod.GET);
request.getHeaders().add(HttpHeaders.ACCEPT, "application/json");
ClientHttpResponse response = request.execute();
HttpStatus status = response.getStatusCode();
String json = readBodyAsString(response);
return new ResponseEntity<>(json, status);
} catch (IOException ioe) {
StringBuilder sb = new StringBuilder();
sb.append("{\"message\": \"").append(ioe.getMessage()).append("\"}");
return new ResponseEntity<>(sb.toString(), HttpStatus.INTERNAL_SERVER_ERROR);
}
}
This works really well for all get requests that have any number of paths.
Problem is the
HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE
Only grabs the paths of a given URL and not the Query Params
So if this controller gets a request with /api/path/path/path it works but if it gets /api/path/path/path?query=1?search=2 for example it will only grab /api/path/path/path and then the subsequent request will fail since it required query params.
How can I get the entire path of the wild card match to include any queryParams that might be here
Thanks
Really simple just needed to use req.getQueryString() to get all the query params as a string.
Also important to note that I needed to pass the query as a .query() on the UriComponentsBuilder so that it gets encoded properly.
URI uri = UriComponentsBuilder.fromUriString(targetHost)
.path(uriString)
.query(queryParams)
.build().normalize().encode().toUri();

Debugging AJAX to Spring Controller (count of variables and spelling)

I am spending a lot of effort debugging ajax calls. The common issues are
number of parameters dont match from the ajax to the controller
spelling of the #RequestMapping parameter does not match
If the type does not match the call happens and it can be debugged. But the bigger issue is the above two. I have 88 parameters that I am passing and have a hell of a time trying to figure out what is missing or spelt incorrectly.
example
#RequestMapping("/saveClient")
public #ResponseBody String saveClientAJAXMethodView(#RequestParam(value = "clientName") String clientName,
.... 88 parameters more
$
.ajax({
type : "Post",
url : "saveClient",
data : {
clientName : clientName,
... 88 parameters more
I got this error
The request sent by the client was syntactically incorrect.
So I changed the signature of my controller to add , method = RequestMethod.POST).
Now I am getting
message Request method 'GET' not supported
description The specified HTTP method is not allowed for the requested resource.
Its clearly a "POST" and still it get a request method GET not supported.
The question is NOT how to solve this problem. The question is how to debug such issues easily. What errors map to what issues, how to debug the 88 parameter spellings and count ? There must be a easier way to do this debugging.
I use the following
Debugging Mode of the controller
Inspect on Chrome.
Since you are sending huge amount of parameter in URL for POST request. I would suggest you to send your data in body.
For example if you are sending parameters like clientId, clientName, clientEmail etc.. you have used #RequestParam annotation to get individual parameter data in your controller:
String saveClientAJAXMethodView( #RequestParam String clientId,
#RequestParam String clientName,
#RequestParam String clientEmail
.... more parameters)
Instead of using #RequestParam I would suggest you to use #RequestBody, For this you need to create a Data transfer object (DTO) like this:
class ClientInfo{
String clientId,
String clientName,
String clientEmail,
....
.... other variables
.... getters and setters of variables
}
And then use this DTO in your controller method like this:
String saveClientAJAXMethodView(#RequestBody ClientInfo clientInfo){
}
Using this approach you will not get any exception regarding spelling mistake or parameter missing .The value will be assigned to a DTO variable if you are sending value with right key as specified in DTO.
To count variables in ClientInfo object you will need to cast ClientInfo to JSONObject and use its size() method to get count of variables
String saveClientAJAXMethodView(#RequestBody ClientInfo clientInfo){
JSONObject json = new JSONObject(clientInfo);
System.out.println(json.keySet().size());
}
Your ajax call will look like this:
var clientInfo = {
'clientName': 'tom',
'clientId': '23AZ1',
'clientEmail': 'xyz#gmail.com',
...
};
$.ajax({
url: url,
type: "POST",
data: JSON.stringify(clientInfo),
contentType: "application/json",
complete: callback
});
I hope following steps would help you debug:
1- Use a filter to intercept request.
2- Create a custom annotation which would indicate that you want to debug this method.
3- Use the method defined in this post Can I get all of requestMapping URL with GET method in the Spring? and your custom annotation to store list of all methods which you want to debug in a singleton bean.
4- Now write some logic in filter which would print mismatch between the method parameters and request parameters.
CustomFilter:
public class CustomFilter extends GenericFilterBean {
#Autowired
#Qualifier("printMismatchMethods")
HashMap<String,Method> methodsToCheck;
#Override
public void doFilter(
ServletRequest request,
ServletResponse response,
FilterChain chain) throws IOException, ServletException {
SpringBeanAutowiringSupport.processInjectionBasedOnCurrentContext(this);
HttpServletRequest r = (HttpServletRequest) request;
String url = r.getRequestURI().substring(r.getContextPath().length());
//Remove extensions if present any
int index = url.indexOf('.');
if(index > 0)
url = url.substring(0,url.indexOf('.'));
/*Matching string this should be replaced by url pattern matching of spring.*/
if (methodsToCheck.containsKey(url)){
Method method = methodsToCheck.get(url);
Map<String, String[]> requestParameterMap = r.getParameterMap();
Map<String,Boolean> isParamPresent = new HashMap<String,Boolean>();
for (Parameter parameter : method.getParameters()){
RequestParam requestParam = parameter.getAnnotation(RequestParam.class);
if (requestParam != null && requestParam.required()){
if (!requestParam.name().isEmpty())
isParamPresent.put(requestParam.name(), false);
else
isParamPresent.put(requestParam.value(), false);
}
}
for (Parameter parameter : method.getParameters()){
RequestParam requestParam = parameter.getAnnotation(RequestParam.class);
if (requestParam != null && requestParam.required()){
String name = null;
if (!requestParam.name().isEmpty())
name=requestParam.name();
else
name=requestParam.value();
if (requestParameterMap.containsKey(name)){
isParamPresent.put(name, true);
}
}
}
for (Map.Entry<String, Boolean> entry : isParamPresent.entrySet()){
if (!entry.getValue()){
System.out.println(entry.getKey() + " is either missing or mis-spelled");
}
}
}
chain.doFilter(request, response);
}
}
configured as follows:
http.addFilterAfter(
new CustomFilter(), BasicAuthenticationFilter.class);
Declare following custom annotation.
#Target(ElementType.METHOD)
#Retention(RetentionPolicy.RUNTIME)
#Documented
public #interface PrintParamMismatch {
}
Define following bean which would populate on startup
#Bean(name="printMismatchMethods")
#Autowired
public HashMap<String,Method> printParamMismatchMethods(BeanFactory beanFactory){
HashMap<String,Method> methods = new HashMap<String,Method>();
Map<String, RequestMappingHandlerMapping> matchingBeans =
BeanFactoryUtils.beansOfTypeIncludingAncestors(
(ListableBeanFactory)beanFactory,
RequestMappingHandlerMapping.class, true, false);
if (!matchingBeans.isEmpty()) {
ArrayList<HandlerMapping> handlerMappings = new ArrayList<HandlerMapping>(matchingBeans.values());
AnnotationAwareOrderComparator.sort(handlerMappings);
RequestMappingHandlerMapping mappings = matchingBeans.get("requestMappingHandlerMapping");
Map<RequestMappingInfo, HandlerMethod> handlerMethods = mappings.getHandlerMethods();
for (Map.Entry<RequestMappingInfo, HandlerMethod> handlerMethod : handlerMethods.entrySet()){
RequestMappingInfo info = handlerMethod.getKey();
HandlerMethod hMethod = handlerMethod.getValue();
Method method = hMethod.getMethod();
if (method.getAnnotation(PrintParamMismatch.class) != null){
String path = info.getPatternsCondition().toString();
path = path.substring(1,path.length());
path = path.substring(0,path.length()-1);
methods.put(path, method);
}
}
}
return methods;
}
This, I think is generic enough to show debug information for now. However we need to store and match Patterns instead of url string.
So I used divide and rule and solved my issue. I commented top half of the parameters and ran, to check if my controller gets called. It did. then I added 1/4th, then 1/8th and found that I missed a parameter.
That along with #ArsianAnjum's answer is good for debugging. #Aji's answer is the long term solution. I should be using that.

Using both #RequestBody and #RequestParam together in spring mvc3

I am using spring-mvc 3.1.0.RELEASE and for some reason, mapping POST with query params and request body does not work.
Here is how my controller method looks:
#RequestMapping(method = POST, value = "/post-to-me/")
public void handlePost(
#RequestBody Content content,
#RequestParam("param1") String param1,
#RequestParam("param2") String param2
){
//do stuff
}
However, if I convert all the request params to path params, mapping works. Has anyone run into something similar?
Thanks!
EDIT:
"does not work" == 404 when I try doing, POST /post-to-me?param1=x&param2=y
First, your POST url doen't match the controller method url, your POST url must be "/post-to-me/?param1=x&param2=y" not "/post-to-me?param1=x&param2=y"
Second, where did Content class come from?? I used a String and works fine for me
#RequestMapping(method = RequestMethod.POST, value = "/post-to-me/")
public void handlePost(#RequestBody String content,
#RequestParam("param1") String param1,
#RequestParam("param2") String param2, HttpServletResponse response) {
System.out.println(content);
System.out.println(param1);
System.out.println(param2);
response.setStatus(HttpServletResponse.SC_OK);
}
Note that I used HttpServletResponse to return a HTTP 200 code, but I think there is a better solution for return Http codes, check this: Multiple response http status in Spring MVC
Trailing slash at the end of your request mapping value might be the problem.
Try:
#RequestMapping(method = RequestMethod.POST, value = "/post-to-me")
or send your POST request to POST /post-to-me/?param1=x&param2=y

Resources