I have a web service written in Spring MVC. It can be used by 3rd party developers.
Our methods have a lot of optional parameters (passed in the query string).
I want to make sure that all the query string parameters are spelled correctly and there is no typos.
Is there an easy way to do it? Method signature example:
#RequestMapping(value = {"/filter"}, method = RequestMethod.GET)
#ResponseBody
public List<MetricType> getMetricTypes(
#RequestParam(value = "subject", required = false) Long subjectId,
#RequestParam(value = "area", required = false) Long areaId,
#RequestParam(value = "onlyImmediateChildren", required = false) Boolean onlyImmediateChildren,
#RequestParam(value = "componentGroup", required = false) Long componentGroupId
) throws Exception
{
//Some code
}
If somebody calls this method with "onlyImediateChildren=true" parameter (a typo) instead of "onlyImmediateChildren=true", Spring MVC will ignore the typoed parameter and will assume "onlyImmediateChildren" is null. Developer will get slightly incorrect list of results and will not notice the error. Such issues could be widespread and difficult to diagnose. I want to check there is no typoed params in query string to prevent such issues.
UPDATE
It is possible to extract the list of actual parameters from the query string. Then it could be compared with the list of the allowed parameters. If I hardcode the allowed parameter list, it will duplicate the method signature. I wonder if it is easy to extract a list of allowed parameters from the method signature (e.g. by #RequestParam annotation)?
Many thanks
Maxim
You could implement your own HandlerInterceptor. In preHandle method you can obtain all HandlerMethod's parameters annotated with #RequestParameter. These will be all allowed parameters in request.
Here is my implementation of an HandlerInterceptor which will only accept the parameters which are explicitely defined by a parameter annotation:
import javax.servlet.http.HttpServletRequest
import javax.servlet.http.HttpServletResponse
import org.springframework.http.HttpStatus
import org.springframework.stereotype.Component
import org.springframework.web.bind.annotation.RequestParam
import org.springframework.web.method.HandlerMethod
import org.springframework.web.servlet.HandlerInterceptor
/**
* Interceptor which assures that only expected [RequestParam]s are send.
*/
#Component
class UnexpectedParameterHandler : HandlerInterceptor {
override fun preHandle(request: HttpServletRequest, response: HttpServletResponse, handler: Any): Boolean {
if (handler is HandlerMethod) {
val queryParams = request.parameterNames.toList()
val expectedParams = handler.methodParameters
.map { methodParameter ->
val requestParamName = methodParameter.getParameterAnnotation(RequestParam::class.java)?.name
val parameterName = methodParameter.parameter.name
requestParamName ?: parameterName
}
val unknownParameters = queryParams.minus(expectedParams)
if (unknownParameters.isNotEmpty()) {
response.writer.write("unexpected parameter $unknownParameters")
response.status = HttpStatus.BAD_REQUEST.value()
return false
}
}
return super.preHandle(request, response, handler)
}
}
You could use the getParameterMap method of the request to get a Map of all the submitted parameters, and validate the keys against a list of all allowed parameters. You should be able to get the request object by simply adding it to the method signature, e.g.:
public List<MetricType> getMetricTypes(
HttpServletRequest request,
#RequestParam(value = "subject", required = false) Long subjectId,
...
) throws Exception {
Spring will inject all the query parameters present in the url string through the argument of type
#RequestParam Map<String,String> in your controller method, if present.
#RequestMapping(value = "", method = RequestMethod.GET, produces = {"application/json"})
public HttpEntity<PagedResources<WebProductResource>> findAll(#RequestParam Map<String, String> allRequestParams){
...
}
You can then validate the keys of the map yourself. For an "enterprisey" way to do that generically, see my answer here: How to check spring RestController for unknown query params?
Related
What is the difference between #RequestParam and #PathVariable while handling special characters?
+ was accepted by #RequestParam as space.
In the case of #PathVariable, + was accepted as +.
#PathVariable is to obtain some placeholder from the URI (Spring call it an URI Template)
— see Spring Reference Chapter 16.3.2.2 URI Template Patterns
#RequestParam is to obtain a parameter from the URI as well — see Spring Reference Chapter 16.3.3.3 Binding request parameters to method parameters with #RequestParam
If the URL http://localhost:8080/MyApp/user/1234/invoices?date=12-05-2013 gets the invoices for user 1234 on December 5th, 2013, the controller method would look like:
#RequestMapping(value="/user/{userId}/invoices", method = RequestMethod.GET)
public List<Invoice> listUsersInvoices(
#PathVariable("userId") int user,
#RequestParam(value = "date", required = false) Date dateOrNull) {
...
}
Also, request parameters can be optional, and as of Spring 4.3.3 path variables can be optional as well. Beware though, this might change the URL path hierarchy and introduce request mapping conflicts. For example, would /user/invoices provide the invoices for user null or details about a user with ID "invoices"?
#RequestParam annotation used for accessing the query parameter values from the request. Look at the following request URL:
http://localhost:8080/springmvc/hello/101?param1=10¶m2=20
In the above URL request, the values for param1 and param2 can be accessed as below:
public String getDetails(
#RequestParam(value="param1", required=true) String param1,
#RequestParam(value="param2", required=false) String param2){
...
}
The following are the list of parameters supported by the #RequestParam annotation:
defaultValue – This is the default value as a fallback mechanism if request is not having the value or it is empty.
name – Name of the parameter to bind
required – Whether the parameter is mandatory or not. If it is true, failing to send that parameter will fail.
value – This is an alias for the name attribute
#PathVariable
#PathVariable identifies the pattern that is used in the URI for the incoming request. Let’s look at the below request URL:
http://localhost:8080/springmvc/hello/101?param1=10¶m2=20
The above URL request can be written in your Spring MVC as below:
#RequestMapping("/hello/{id}") public String getDetails(#PathVariable(value="id") String id,
#RequestParam(value="param1", required=true) String param1,
#RequestParam(value="param2", required=false) String param2){
.......
}
The #PathVariable annotation has only one attribute value for binding the request URI template. It is allowed to use the multiple #PathVariable annotation in the single method. But, ensure that no more than one method has the same pattern.
Also there is one more interesting annotation:
#MatrixVariable
http://localhost:8080/spring_3_2/matrixvars/stocks;BT.A=276.70,+10.40,+3.91;AZN=236.00,+103.00,+3.29;SBRY=375.50,+7.60,+2.07
And the Controller method for it
#RequestMapping(value = "/{stocks}", method = RequestMethod.GET)
public String showPortfolioValues(#MatrixVariable Map<String, List<String>> matrixVars, Model model) {
logger.info("Storing {} Values which are: {}", new Object[] { matrixVars.size(), matrixVars });
List<List<String>> outlist = map2List(matrixVars);
model.addAttribute("stocks", outlist);
return "stocks";
}
But you must enable:
<mvc:annotation-driven enableMatrixVariables="true" >
#RequestParam is use for query parameter(static values) like: http://localhost:8080/calculation/pow?base=2&ext=4
#PathVariable is use for dynamic values like : http://localhost:8080/calculation/sqrt/8
#RequestMapping(value="/pow", method=RequestMethod.GET)
public int pow(#RequestParam(value="base") int base1, #RequestParam(value="ext") int ext1){
int pow = (int) Math.pow(base1, ext1);
return pow;
}
#RequestMapping("/sqrt/{num}")
public double sqrt(#PathVariable(value="num") int num1){
double sqrtnum=Math.sqrt(num1);
return sqrtnum;
}
1) #RequestParam is used to extract query parameters
http://localhost:3000/api/group/test?id=4
#GetMapping("/group/test")
public ResponseEntity<?> test(#RequestParam Long id) {
System.out.println("This is test");
return ResponseEntity.ok().body(id);
}
while #PathVariable is used to extract data right from the URI:
http://localhost:3000/api/group/test/4
#GetMapping("/group/test/{id}")
public ResponseEntity<?> test(#PathVariable Long id) {
System.out.println("This is test");
return ResponseEntity.ok().body(id);
}
2) #RequestParam is more useful on a traditional web application where data is mostly passed in the query parameters while #PathVariable is more suitable for RESTful web services where URL contains values.
3) #RequestParam annotation can specify default values if a query parameter is not present or empty by using a defaultValue attribute, provided the required attribute is false:
#RestController
#RequestMapping("/home")
public class IndexController {
#RequestMapping(value = "/name")
String getName(#RequestParam(value = "person", defaultValue = "John") String personName) {
return "Required element of request param";
}
}
it may be that the application/x-www-form-urlencoded midia type convert space to +, and the reciever will decode the data by converting the + to space.check the url for more info.http://www.w3.org/TR/html401/interact/forms.html#h-17.13.4.1
#PathVariable - must be placed in the endpoint uri and access the query parameter value from the request
#RequestParam - must be passed as method parameter (optional based on the required property)
http://localhost:8080/employee/call/7865467
#RequestMapping(value=“/call/{callId}", method = RequestMethod.GET)
public List<Calls> getAgentCallById(
#PathVariable(“callId") int callId,
#RequestParam(value = “status", required = false) String callStatus) {
}
http://localhost:8080/app/call/7865467?status=Cancelled
#RequestMapping(value=“/call/{callId}", method = RequestMethod.GET)
public List<Calls> getAgentCallById(
#PathVariable(“callId") int callId,
#RequestParam(value = “status", required = true) String callStatus) {
}
Both the annotations behave exactly in same manner.
Only 2 special characters '!' and '#' are accepted by the annotations #PathVariable and #RequestParam.
To check and confirm the behavior I have created a spring boot application that contains only 1 controller.
#RestController
public class Controller
{
#GetMapping("/pvar/{pdata}")
public #ResponseBody String testPathVariable(#PathVariable(name="pdata") String pathdata)
{
return pathdata;
}
#GetMapping("/rpvar")
public #ResponseBody String testRequestParam(#RequestParam("param") String paramdata)
{
return paramdata;
}
}
Hitting following Requests I got the same response:
localhost:7000/pvar/!##$%^&*()_+-=[]{}|;':",./<>?
localhost:7000/rpvar?param=!##$%^&*()_+-=[]{}|;':",./<>?
!# was received as response in both the requests
#RequestParam:We can say it is query param like a key value pair
#PathVariable:-It is came from URI
I just found that even if I omit the #RequestParam annotation on the organization parameter, Spring is still able to bind it.
#RequestMapping(value="", method = RequestMethod.POST)
#ResponseBody
public String save(String organization){
logger.info(organization); // it works
}
Can anyone points to the documentation that clarifies this behaviour? I have always though that #RequestParam was mandatory for binding to work.
Thanks
Take a look at https://reversecoding.net/spring-mvc-requestparam-binding-request-parameters/ There is an explanation:
Examples without #RequestParam
Based on the list of
HandlerMethodArgumentResolver configured in your application,
#RequestParam can also be omitted. If you have a look at the code of
method getDefaultArgumentResolvers() of RequestMappingHandlerAdapter
there is the following piece of code at the end:
// Catch-all resolvers.add(new
RequestParamMethodArgumentResolver(getBeanFactory(), true));
resolvers.add(new ServletModelAttributeMethodProcessor(true));
// Catch-all resolvers.add(new
RequestParamMethodArgumentResolver(getBeanFactory(), true));
resolvers.add(new ServletModelAttributeMethodProcessor(true));
Basically, it’s added to the resolvers a
RequestParamMethodArgumentResolver with useDefaultResolution set to
true. Looking at the documentation we can see that this means that
method argument that is a simple type, as defined in
BeanUtils.isSimpleProperty(java.lang.Class), is treated as a
request parameter even if it isn’t annotated. The request parameter
name is derived from the method parameter name.
Your resolvers do it automatically. When you pass the HandlerMethodArgumentResolver bean to your resolver, the BeanUtil checks if the parameter is a primitive value or a simple String. If so, it does the binding itself.
#RequestMapping(value = "/rest")
#ResponseBody
public String save(String username, String password) {
return String.format("username=%s password=%s", username, password);
}
Hit the service http://localhost:8080/rest?username=mypwd&password=uname
You will be able to see the result given below.
Output: username=pwd password=uname
here I have some examples for the #RequestParam to provide you,hope they can help you:
#RequestMapping(value = "/selection/findByField", method = RequestMethod.POST)
public #ResponseBody List<selectionsDO> add(#RequestParam(value = "field", required = true) String field,#RequestParam(value = "value", required = true) String value)
{ return mongoService.findByField(field,value);
}
The words "required = true" means that this field must submit at request.
My requirement is to get the json response with customized error message when a required #RequestParam is not sent to the request handler or invalid parameter(required is int but user is passing string) is sent to the request handler.
currently I am trying to use the #Exceptionhandler mechanism to handle these exceptions. But the respective exception handler methods not getting invoked.
Please see the code snippet:
#Controller
#RequestMapping("api/v1/getDetails")
public class Abc {
#RequestMapping
#ResponseBody
public Envelope<Object> retrieveTransactions(#RequestParam(required = false) Integer a,
#RequestParam int b, #RequestParam(required = false) boolean c,
HttpServletRequest req) {`
//implementation goes here
}
#ExceptionHandler(MissingServletRequestParameterException.class)
#ResponseBody
public Envelope<Object> missingParameterExceptionHandler(Exception exception,
HttpServletRequest request) {
Envelope<Object> envelope = null;
//error implementation
return envelope;
}
#ExceptionHandler(TypeMismatchException.class)
#ResponseBody
public Envelope<Object> typeMismatchExpcetionHandler(Exception exception, HttpServletRequest request) {
Envelope<Object> envelope = null;
//error implementation
return envelope;
}
Do I need to configure anything extra for exception handler? can anyone tell me where I am doing the wrong.
Consider identifying the parameter name in the RequestParameter annotation.
For example
#RequestParam(value="blammy", required=false)
I've never bothered figuring out how to handle type mismatch,
instead I've found it easier to accept all parameters as String and perform all verification myself (including type).
Also,
If you are accepting the HttpServletRequest as a parameter to your handler,
then there is no need to use #RequestParam annotations,
just get the parameter values directly from the request.
Finally,
consider org.springframework.web.context.request.WebRequest
or org.springframework.web.context.request.NativeWebRequest
instead of HttpServletRequest.
Have you tried to use MethodArgumentNotValidException or HttpMessageNotReadableException instead on your handlers?
And put required = true on your #RequestParam declaration to catch missing params exceptions
#RequestParam(required = true)
I m actually creating a simple application and I need to have routing pattern identical in multiple case :
/*
* Returns a list of all the root directories accepting query string on name
*/
#RequestMapping(value = "/directories", method = RequestMethod.GET)
public List<DirectoryEntity> find() {
return directoryService.findAll();
}
/*
* Returns a list of all the root directories accepting query string on name
* #param name Name of the ressources to search. Query string at format : *name*
*/
#RequestMapping(value = "/directories", method = RequestMethod.GET)
public List<DirectoryEntity> findByCriteria(#RequestParam(value = "name", required = true) String name) {
return directoryService.findByName(name);
}
In fact, I dont want to manage criteria request in the same function as findAll one. Is there anyway to handle this case without be forced to manage everything inside the same function ?
Thanks for advance
Try changing the second method #RequestMapping annotation adding params:
#RequestMapping(value = "/directories", method = RequestMethod.GET, params = "name")
public List<DirectoryEntity> findByCriteria(#RequestParam(value = "name", required = true) String name) {
return directoryService.findByName(name);
}
See also the Spring Documentation for more details.
I'm not quite sure what you are asking, but assuming the decision as to which method to call is based on request parameters (it must be since they're both the same URL and HTTP method), then something like this might help:
#RequestMapping(method=RequestMethod.GET, params={"name"})
public #ResponseBody List<DirectoryEntity> findByCriteria(#RequestParam(value = "name", required = true) String name) {
//do your stuff
}
The inclusion of the params attribute in the #RequestMapping annotation removes the ambiguity in which method to call.
I've also added #ResponseBody to the return type, just in case you want Spring to return the list in the HTTP response.
I have a web service written in Spring MVC. It can be used by 3rd party developers.
Our methods have a lot of optional parameters (passed in the query string).
I want to make sure that all the query string parameters are spelled correctly and there is no typos.
Is there an easy way to do it? Method signature example:
#RequestMapping(value = {"/filter"}, method = RequestMethod.GET)
#ResponseBody
public List<MetricType> getMetricTypes(
#RequestParam(value = "subject", required = false) Long subjectId,
#RequestParam(value = "area", required = false) Long areaId,
#RequestParam(value = "onlyImmediateChildren", required = false) Boolean onlyImmediateChildren,
#RequestParam(value = "componentGroup", required = false) Long componentGroupId
) throws Exception
{
//Some code
}
If somebody calls this method with "onlyImediateChildren=true" parameter (a typo) instead of "onlyImmediateChildren=true", Spring MVC will ignore the typoed parameter and will assume "onlyImmediateChildren" is null. Developer will get slightly incorrect list of results and will not notice the error. Such issues could be widespread and difficult to diagnose. I want to check there is no typoed params in query string to prevent such issues.
UPDATE
It is possible to extract the list of actual parameters from the query string. Then it could be compared with the list of the allowed parameters. If I hardcode the allowed parameter list, it will duplicate the method signature. I wonder if it is easy to extract a list of allowed parameters from the method signature (e.g. by #RequestParam annotation)?
Many thanks
Maxim
You could implement your own HandlerInterceptor. In preHandle method you can obtain all HandlerMethod's parameters annotated with #RequestParameter. These will be all allowed parameters in request.
Here is my implementation of an HandlerInterceptor which will only accept the parameters which are explicitely defined by a parameter annotation:
import javax.servlet.http.HttpServletRequest
import javax.servlet.http.HttpServletResponse
import org.springframework.http.HttpStatus
import org.springframework.stereotype.Component
import org.springframework.web.bind.annotation.RequestParam
import org.springframework.web.method.HandlerMethod
import org.springframework.web.servlet.HandlerInterceptor
/**
* Interceptor which assures that only expected [RequestParam]s are send.
*/
#Component
class UnexpectedParameterHandler : HandlerInterceptor {
override fun preHandle(request: HttpServletRequest, response: HttpServletResponse, handler: Any): Boolean {
if (handler is HandlerMethod) {
val queryParams = request.parameterNames.toList()
val expectedParams = handler.methodParameters
.map { methodParameter ->
val requestParamName = methodParameter.getParameterAnnotation(RequestParam::class.java)?.name
val parameterName = methodParameter.parameter.name
requestParamName ?: parameterName
}
val unknownParameters = queryParams.minus(expectedParams)
if (unknownParameters.isNotEmpty()) {
response.writer.write("unexpected parameter $unknownParameters")
response.status = HttpStatus.BAD_REQUEST.value()
return false
}
}
return super.preHandle(request, response, handler)
}
}
You could use the getParameterMap method of the request to get a Map of all the submitted parameters, and validate the keys against a list of all allowed parameters. You should be able to get the request object by simply adding it to the method signature, e.g.:
public List<MetricType> getMetricTypes(
HttpServletRequest request,
#RequestParam(value = "subject", required = false) Long subjectId,
...
) throws Exception {
Spring will inject all the query parameters present in the url string through the argument of type
#RequestParam Map<String,String> in your controller method, if present.
#RequestMapping(value = "", method = RequestMethod.GET, produces = {"application/json"})
public HttpEntity<PagedResources<WebProductResource>> findAll(#RequestParam Map<String, String> allRequestParams){
...
}
You can then validate the keys of the map yourself. For an "enterprisey" way to do that generically, see my answer here: How to check spring RestController for unknown query params?