I have encouterd an odd situation while we are doing press testing in our test env. When the app load is high, the Query String will missing occasionally and the Spring will throw MissingServletRequestParameterException.
In order to find the root cause , I add some logs at the foremost Filter(code is shown below), and something weired happened.
public static void printRequestParameter(HttpServletRequest request) {
Map<String, String[]> parameterMap = request.getParameterMap();
log.info("Request URI : {}, method = {} , query string = {}", request.getRequestURI(), request.getMethod(), request.getQueryString());
if (MapUtils.isNotEmpty(parameterMap)) {
parameterMap.forEach((k, v) -> {
log.info("Request parameter name = {}, value = {}", k, ArrayUtils.isEmpty(v) ? Strings.EMPTY : Arrays.stream(v).collect(Collectors.joining(COMMA)));
});
}
}
The request.getParameterMap() is empty, but , the query string is not empty , and I got a log like :
Request URI : /a/b/c, method = POST , query string = foo=bar.
But no logs like:
Request parameter name = foo , value = bar
And our Controller use #RequestParam() String foo to receivce the parameter , finally , the Spring throws
MissingServletRequestParameterException Handler org.springframework.web.bind.MissingServletRequestParameterException: Required String parameter 'foo' is not present
at org.springframework.web.method.annotation.RequestParamMethodArgumentResolver.handleMissingValue(RequestParamMethodArgumentResolver.java:204)
at org.springframework.web.method.annotation.AbstractNamedValueMethodArgumentResolver.resolveArgument(AbstractNamedValueMethodArgumentResolver.java:114)
at org.springframework.web.method.support.HandlerMethodArgumentResolverComposite.resolveArgument(HandlerMethodArgumentResolverComposite.java:121)
at org.springframework.web.method.support.InvocableHandlerMethod.getMethodArgumentValues(InvocableHandlerMethod.java:167)
at org.springframework.web.method.support.InvocableH
I just wonder, why the parameter in query string is not contained in parameterMap?
Note:
The odd behavior is only happened occasionally, at most time it's just works.
My spring boot version is 2.3.9.RELEASE and the embedded tomcat version is 9.0.43.
Any help is appreciated!
Since the specification does not allow ServletRequest.getParameterMap to throw any exception, any failure in parameter parsing will cause the parameter list to be empty.
To detect this situation you can log the "org.apache.catalina.parameter_parse_failed_reason" attribute of your request.
Examples of query strings that should fail:
?=noname,
?urlEncoding=%ue
Related
I am working with Redmine API, and want to update an issue.
The issue has 30 variables, Subject, Description, Author, and Assignee ...
I have no problem updating the issue Subject and Description like this:
#RequestMapping(value = "/issues/update/{id}", method = RequestMethod.PUT)
public ResponseEntity<Object> issueUpdate(#RequestBody ObjectNode json, #PathVariable int id) throws RedmineException, RuntimeException, IllegalArgumentException {
String apiKey = json.get("API_KEY").asText();
RedmineManager mgr = RedmineManagerFactory.createWithApiKey("http://localhost:3001/", apiKey);
IssueManager issueManager = mgr.getIssueManager();
Issue issue = issueManager.getIssueById(id);
issue.setSubject(json.get("SUBJECT").asText())
.setDescription(json.get("DESCRIPTION").asText())
.update();
return new ResponseEntity<>(HttpStatus.OK);
}
The problem with this way is I am only allowed to change these 2 values and I have to include, SUBJECT and DESCRIPTION in my JSON request body.
If SUBJECT is not in JSON, then it will be considered as null and I get NullPointerException.
I want to make something more flexible and elegant, to allow the change of each value, and if not exist, don't set to null but keep the old values.
something logical to this but a bit smarter
if json.get("SUBJECT").asText() != null {
issue.setSubject(json.get("SUBJECT").asText()) //set if mentioned
} else {
issue.setSubject(issue.getSubject()) //set the old value
}
The idea is, now am available to have setSubject and all the other available setters in case it's mentioned in my JSON request or not.
#RequestMapping(value = "/**", consumes = MediaType.ALL_VALUE)
#ResponseBody
public Object mirror(HttpServletRequest req, #Nullable #RequestBody Map<String, String> form) {
...
}
I just want the plain key and value for all form data entries here but it also includes query parameters in the map.
I need to be able to tell the difference between what came from the form and what came from the query.
Getting the query parameters separately is easily done using URI parsing but it's not so easy to remove the query parameters from the form map. Especially in the case they have the same keys.
Changing the parameter to MultiValueMap adds values with the same key into an array. Using just a Map causes the query parameters to overwrite the form data with equal keys.
I found where this is happening, for the MockHttpServletRequest at least: buildRequest method:
String query = this.url.getRawQuery();
if (!this.queryParams.isEmpty()) {
String s = UriComponentsBuilder.newInstance().queryParams(this.queryParams).build().encode().getQuery();
query = StringUtils.isEmpty(query) ? s : query + "&" + s;
}
if (query != null) {
request.setQueryString(query);
}
addRequestParams(request, UriComponentsBuilder.fromUri(this.url).build().getQueryParams());
this.parameters.forEach((name, values) -> {
for (String value : values) {
request.addParameter(name, value);
}
});
It's combining the form data and query data into one map. So is there an alternative way to parse the form data ONLY or exclude query params from the map!?
From the javadoc for #RequestParam:
In Spring MVC, "request parameters" map to query parameters, form data, and parts in multipart requests. This is because the Servlet API combines query parameters and form data into a single map called "parameters", and that includes automatic parsing of the request body.
Not sure if there's a more elegant way, but you could possibly use Spring's UriComponentsBuilder class to parse the URI string and get back the query parameters.
I'm trying to pass in a bunch of id's to create a filter.
The incoming request looks like (ignoring the pagination stuff, which works fine)
http://localhost:8080/news-items?filter%5B%5D=09c731de-7ed8-385d-849c-f4d6535137ab&filter%5B%5D=dd1ba187-2df9-3985-ad1c-a4cde2dfe669&modelPath=controller.newsItems&page=0&per_page=25
Where the filter param equals an ID, but there is a bunch of them, for example:
filter: [
"09c731de-7ed8-385d-849c-f4d6535137ab",
"dd1ba187-2df9-3985-ad1c-a4cde2dfe669"
],
I can't seem to collect the filters in the controller. At the moment I have
public String getFeeds(#RequestParam(value = "filter", required = false) MultiValueMap<String, String> filter, #RequestParam(value = "page", required = false) int page, #RequestParam(value = "per_page", required = false) int perPage) {
log.info("Filter: {}", filter);
}
However filter is always null. I've tried using a String rather than a map but that is also always null.
How do I go about accepting an unknown number of params in this manner? I get the feeling this is really simple but I'm just missing the obvious...
Turns out it was simple like I thought. When using a Map in the #RequestParam it takes all the incoming params, regardless of what they are.
So from what I can tell the correct solution is to do something like
#GetMapping(produces = APPLICATION_JSON)
public String getFeeds(#RequestParam MultiValueMap<String, String> params) {
params.forEach(//something);
}
I think what you are looking for is just an Array or a List, something like below :
public String getFeeds(#RequestParam(value = "filter", required = false) List<String> filters) { ... }
public String searchUserById(#RequestParam(value = "fileType", required = true) String fileType) {
System.out.println(fileType);
return "downloadexcel";
}
public String searchUserByName(#RequestParam(value = "fileType", required = true) String fileType, #RequestParam(value = "batchNo", required = true) String batchNo) {
System.out.println(fileType + " ----------- " + batchNo);
return "downloadexcel";
}
This kind of URL Patterns i need I found some relevant Questions but i am getting 400 bad request when hitting the url
1.http://localhost:8080/apna-shopee/download.htm?fileType=mani
2.http://localhost:8080/apna-shopee/download.htm?fileType=mani&batchNo=1001
Didn't find the correct solution.
visit following link this may help you to solve your issue
create two method for same url pattern with different arguments
You can also do this based on condition. just make simple method with two parameters and based on second parameter value you can perform appropriate action. i.e if second parameter value is null that means request done using only single parameter
I am using Spring MVC in my project and while mapping user request to a URI, I get the 404 error. Here is my function skeleton which I want to be invoked:
#RequestMapping(value="/inventory/discovery", method = RequestMethod.GET, params = {"discoveryType"}, produces = {"application/json"})
public String getDiscoveryByType(#RequestParam("discoveryType") String discoveryType)
{
return discoveryType;
}
I am expecting this method to be called when I give the URL
http://<some-ip>/inventory/discovery/discoveryType?=DMVPN
However, when I test the code using Chrome's Advanced Rest Client I see that the URI it is trying to access is "/inventory/DMVPN" and not the "/inventory/discovery?discoveryType=DMVPN".
Am I missing something here? I don't see anything wrong with my URL syntax
I have another function in my code (but I don't think it is causing the problem) which has same request-mapping value, but has no params attribute.
#RequestMapping(value = "/inventory/discovery", method = RequestMethod.GET, produces = { "application/json" })
public ResponseEntity<DiscoveryNIOListResult> getAllDiscovery() {
logger.trace("getAllDiscovery");
List<DiscoveryNIO> listDiscoveryNIO = discoveryDasClient.getDiscoveryList();
DiscoveryNIOListResult result = new DiscoveryNIOListResult();
result.setResponse(listDiscoveryNIO);
return new ResponseEntity<DiscoveryNIOListResult>(result, HttpStatus.OK);
}
Ok, so a second look at your URL:
http://<some-ip>/inventory/discovery/discoveryType?=DMVPN
This is NOT passing discoveryType as a request GET parameter. For that you would need to do:
http://<some-ip>/inventory/discovery&discoveryType?=DMVPN
If you want to use path variables instead, you could do something like:
http://<some-ip>/inventory/discovery/DMVPN
And change your handler to something like:
#RequestMapping(value="/inventory/discovery/{discoveryType}", method = RequestMethod.GET, produces = {"application/json"})
public String getDiscoveryByType(#PathVariable("discoveryType") String discoveryType)
{
return discoveryType;
}
It looks like you were trying to mix these two methods, and that wont work.
Try changing your URL to:
http://<some-ip>/inventory/discovery?discoveryType=DMVPN