"Bad Request" when attempting to send array to spring mvc controller - java

I saw related questions and tried those none of those helped. I'm sending POST requst with jquery like this :
var data = {};
//this works every time and it's not issue
var statusArray = $("#status").val().split(',');
var testvalue = $("#test").val();
data.test = testvalue;
data.status = statusArray ;
$.post("<c:url value="${webappRoot}/save" />", data, function() {
})
On the controller side I've tried following :
public void saveStatus(ModelMap model, Principal principal, HttpSession session, final HttpServletResponse response, #RequestParam String test, #RequestBody String [] status) {
//I never get to this point, but when I set statusArray to required false test variable is being populated correctly
}
public void saveStatus(ModelMap model, Principal principal, HttpSession session, final HttpServletResponse response, #RequestParam String test, #RequestParam String [] status) {
//I never get to this point, but when I set statusArray to required false test variable is being populated correctly
}
public void saveStatus(ModelMap model, Principal principal, HttpSession session, final HttpServletResponse response, #RequestParam String test, #RequestParam("status") String [] status) {
//I never get to this point, but when I set statusArray to required false test variable is being populated correctly
}
public void saveStatus(ModelMap model, Principal principal, HttpSession session, final HttpServletResponse response, #RequestParam String test, #RequestParam(name="status") String [] status) {
//I never get to this point, but when I set statusArray to required false test variable is being populated correctly
}
none of these worked I'm wondering what I'm doing wrong, whatever I do I get Bad request

Your status param should be #RequestParam(value = "status[]") String[] status (Spring 3.1).

I have also gone through the same problem of Bad request. I resolved it by doing the following code.
You can post an array to a controller by converting it into json string by JSON.stringify(array).
I have pushed muliple Objects into an array using push().
var a = [];
for(var i = 1; i<10; i++){
var obj = new Object();
obj.name = $("#firstName_"+i).val();
obj.surname = $("#lastName_"+i).val();
a.push(obj);
}
var myarray = JSON.stringify(a);
$.post("/ems-web/saveCust/savecustomers",{myarray : myarray},function(e) {
}, "json");
Controller :
You can use jackson for processing json string.
Jackson is a High-performance JSON processor Java library.
#RequestMapping(value = "/savecustomers", method = RequestMethod.POST)
public ServiceResponse<String> saveCustomers(ModelMap model, #RequestParam String myarray) {
try{
ObjectMapper objectMapper = new ObjectMapper().configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true);
List<DtoToMAP> parsedCustomerList = objectMapper.readValue(myarray, new TypeReference<List<DtoToMAP>>() { });
System.out.println(" parsedCustomerList :: " + parsedCustomerList);
}catch (Exception e) {
System.out.println(e);
}
}
Note : make sure that your dto should contain same variable name as you are posting with an array object.
in my case, my dto contains firstName,lastName as am posting with an array object.
Jackson Dependancy :
<dependency>
<groupId>org.codehaus.jackson</groupId>
<artifactId>jackson-core-asl</artifactId>
<version>1.9.3</version>
</dependency>
<dependency>
<groupId>org.codehaus.jackson</groupId>
<artifactId>jackson-mapper-asl</artifactId>
<version>1.9.3</version>
</dependency>

I think your problem might be that to send an array to something you have to actually send the param multiple times.
In the case of a GET operation something like: ?status=FOO&status=BAR
I'm not sure spring would convert a comma separated string to an array for you automagically. You could however, add a PropertyEditor (see PropertyEditorSupport) to split the string on commas.
#InitBinder
public void initBinder(WebDataBinder binder) {
binder.registerCustomEditor(String[].class, new PropertyEditorSupport() {
#Override
public String getAsText() {
String value[] = (String[]) getValue();
if (value == null) {
return "";
}
else {
return StringUtils.join(value, ",");
}
}
#Override
public void setAsText(String text) throws IllegalArgumentException {
if (text == null || text.trim().length() == 0) {
setValue(null);
}
else {
setValue(StrTokenizer.getCSVInstance(text).getTokenArray());
}
}
});
}
Note, i'm using commons-lang to both join and split the string, but you could easily just do it yourself using any means you want.
Once you do this, any time you want a parameter bound to a String[] from a single string, spring will automatically convert it for you.

Related

What's the problem of my Request parameter Interceptor in Spring-boot Restful service?

The original question is here:
How to resolve URI encoding problem in spring-boot?. And following one of the suggestions, I am trying to come up with a solution with an Interceptor, but still has some issue.
I need to be able to handle some special characters in URL, for instance "%" and my spring controller is below:
#Controller
#EnableAutoConfiguration
public class QueryController {
private static final Logger LOGGER = LoggerFactory.getLogger(QueryController.class);
#Autowired
QueryService jnService;
#RequestMapping(value="/extract", method = RequestMethod.GET)
#SuppressWarnings("unchecked")
#ResponseBody
public ExtractionResponse extract(#RequestParam(value = "extractionInput") String input) {
// LOGGER.info("input: " + input);
JSONObject inputObject = JSON.parseObject(input);
InputInfo inputInfo = new InputInfo();
JSONObject object = (JSONObject) inputObject.get(InputInfo.INPUT_INFO);
String inputText = object.getString(InputInfo.INPUT_TEXT);
inputInfo.setInputText(inputText);
return jnService.getExtraction(inputInfo);
}
}
Following suggestions I want to write an interceptor to encode the URL before the request is sent to the controller, and my interceptor looks like (not complete yet):
public class ParameterInterceptor extends HandlerInterceptorAdapter {
private static final Logger LOGGER = LoggerFactory.getLogger(ParameterInterceptor.class);
#Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse reponse,
Object handler) throws Exception {
Enumeration<?> e = request.getParameterNames();
LOGGER.info("Request URL::" + request.getRequestURL().toString());
StringBuffer sb = new StringBuffer();
if (e != null) {
sb.append("?");
}
while (e.hasMoreElements()) {
String curr = (String) e.nextElement();
sb.append(curr + "=");
sb.append(request.getParameter(curr));
}
LOGGER.info("Parameter: " + sb.toString());
return true;
}
}
I tested a URL in my browser:
http://localhost:8090/extract?extractionInput={"inputInfo":{"inputText":"5.00%"}}
Due to the % sign, I received the error:
[log] - Character decoding failed. Parameter [extractionInput] with value [{"inputInfo":{"inputText":"5.0022:%225.00%%22}}] has been ignored. Note that the name and value quoted here may be corrupted due to the failed decoding.
When I test the interceptor, "request.getRequestURL()" gives the expected result:
http://localhost:8090/extract
However, "request.getParameterNames()" always get an empty Elumentation object. Why doesn't it get the parameters? What I hope is to first encode the parameter value:
"inputText":"5.00%"
'inputText' is a field of the object InputInfo in the json format. So how to get the request parameters to solve the problem?

Why #RequestParam annotation bind the value from request body?

I have this request
#RequestMapping(value = "/test", method = RequestMethod.POST)
public void test(ModelMap modelMap, #RequestParam(value = "name") String name) {
modelMap.put("result",name);
}
When I call this request from Postman and pass the Name parameter in the request body and in the URL, the result is like this :
But if I remove the parameter from request body, the request is like this :
Why does #RequestParam annotation bind the value from the request body first? and if it doesn't exist in the body, it bind the value from URL parameters
Because it's how ServletRequest works. Behind the scene #RequestParam is using ServletRequest#getParameter. If you take a look at the java doc it clearly state that query parameter or form post data are used.
For HTTP servlets, parameters are contained in the query string or posted form data.
If there is a multiple value for instance same key in query and post data then it returns the first value in the array returned by getParameterValues.
Furthermore you are using multipart/form-data content type so spring handle it with DefaultMultipartHttpServletRequest where parameters found in the body are returned first:
#Override
public String[] getParameterValues(String name) {
String[] parameterValues = super.getParameterValues(name);
String[] mpValues = getMultipartParameters().get(name);
if (mpValues == null) {
return parameterValues;
}
if (parameterValues == null || getQueryString() == null) {
return mpValues;
}
else {
String[] result = new String[mpValues.length + parameterValues.length];
System.arraycopy(mpValues, 0, result, 0, mpValues.length);
System.arraycopy(parameterValues, 0, result, mpValues.length, parameterValues.length);
return result;
}
}

java - get cookie value by name in spring mvc

I'm working on a java spring mvc application. I have set a cookie in one of my controller's methods in this way:
#RequestMapping(value = {"/news"}, method = RequestMethod.GET)
public ModelAndView news(Locale locale, Model model, HttpServletResponse response, HttpServletRequest request) throws Exception {
...
response.setHeader("Set-Cookie", "test=value; Path=/");
...
modelAndView.setViewName("path/to/my/view");
return modelAndView;
}
This is working fine and I can see a cookie with name test and value "value" in my browser console. Now I want to get the cookie value by name in other method. How can I get value of test cookie?
The simplest way is using it in a controller with the #CookieValue annotation:
#RequestMapping("/hello")
public String hello(#CookieValue("foo") String fooCookie) {
// ...
}
Otherwise, you can get it from the servlet request using Spring org.springframework.web.util.WebUtils
WebUtils.getCookie(HttpServletRequest request, String cookieName)
By the way, the code pasted into the question could be refined a bit. Instead of using #setHeader(), this is much more elegant:
response.addCookie(new Cookie("test", "value"));
You can also use org.springframework.web.util.WebUtils.getCookie(HttpServletRequest, String).
private String getCookieValue(HttpServletRequest req, String cookieName) {
return Arrays.stream(req.getCookies())
.filter(c -> c.getName().equals(cookieName))
.findFirst()
.map(Cookie::getValue)
.orElse(null);
}
Spring MVC already gives you the HttpServletRequest object, it has a getCookies() method that returns Cookie[] so you can iterate on that.
private String extractCookie(HttpServletRequest req) {
for (Cookie c : req.getCookies()) {
if (c.getName().equals("myCookie"))
return c.getValue();
}
return null;
}
Cookie doesnt have method to get by value try this
Cookie cookie[]=request.getCookies();
Cookie cook;
String uname="",pass="";
if (cookie != null) {
for (int i = 0; i < cookie.length; i++) {
cook = cookie[i];
if(cook.getName().equalsIgnoreCase("loginPayrollUserName"))
uname=cook.getValue();
if(cook.getName().equalsIgnoreCase("loginPayrollPassword"))
pass=cook.getValue();
}
}

Catch GET request parameters in a Map<String,String>

I'm trying to catch key value pairs in a Map parameter at spring MVC side. This looks to me to be something simple but I can't wrap my head around it at the moment. Take following url
www.goudengids.be.localhost:8080/ms/view/sendContactForm.ajah?pageId=711408&listingId=685592&siteId=353009&url=http%3A%2F%2Fwww.goudengids.be.localhost%3A8080%2Fms%2Fms%2Fkbc-bank-versicherung-recht-4780%2Fms-353009-preview%2F&moduleId=65920100&mySiteId=353009&pageShortId=1&prefills[Naam]=janneke
You'll notice at the end my latest attempt to get this working prefills[Naam]=janneke. I like to catch this in the following controller.
public String getContactForm(#RequestParam(required = true) Long moduleId, #RequestParam(required = true) String url, #RequestParam(required=false) Map<String,String> prefills, Long mySiteId, Integer pageShortId,
DefaultPageParameters defaultPageParameters, ModelMap model, HttpServletRequest request, HttpServletResponse response, Locale locale) throws Exception {
However I'm recieving all parameters in the request in my prefills variable instead of just Naam,janneke. Is this even possible what I'm attempting or should I go with a large string with a token to tokenize ?
prefills=naam:janneke|title:maan|subject:space
I couldn't find a clean way out, so I went for the pragmatic solution
public String getContactForm(#RequestParam(required = true) Long moduleId, #RequestParam(required = true) String url, #RequestParam(required=false) List<String> prefills, Long mySiteId, Integer pageShortId,
DefaultPageParameters defaultPageParameters, ModelMap model, HttpServletRequest request, HttpServletResponse response, Locale locale) throws Exception {
private void prefillFieldsWithData(List<String> prefills, ModelMap model, BasicContactFormVo contactFormVo) {
if(prefills != null && !prefills.isEmpty()){
Map<String, String> valuesOfCustomFields = new HashMap<String, String>();
List<ContactFormElementVo> customFormElements = contactFormVo.getCustomFormElements();
for (String prefillData : prefills) {
if(prefillData.contains("|")){
String[] prefillFieldData = prefillData.split("|");
for (ContactFormElementVo contactFormElementVo : customFormElements) {
if(contactFormElementVo.getLabel().equals(prefillFieldData[0])){
valuesOfCustomFields.put("cfe"+contactFormElementVo.getId().toString(), prefillFieldData[1]);
break;
}
}
}
}
model.addAttribute("customFieldValues",valuesOfCustomFields);
}
}
It's a bit sad I have to do it this way, but it seems Spring has a generic way of detecting a Map as request parameter and filling it with all parameters in the request. There is no way around that except overloading that class which I rather do not considering it's part of the entire MVC mechanic.
I call the controller now with following URL. It works just ... meh ... not my favorite solution
http://www.goudengids.be.localhost:8080/ms/view/sendContactForm.ajah?pageId=711408&listingId=685592&siteId=353009&url=http%3A%2F%2Fwww.goudengids.be.localhost%3A8080%2Fms%2Fms%2Fkbc-bank-versicherung-recht-4780%2Fms-353009-preview%2F&moduleId=65920100&mySiteId=353009&pageShortId=1&prefills=Naam|janneke%20zag%20eens%20pruimen&prefills=E-mail|maan

How to partially mock HttpServletRequest using Mockito

i am mocking a HttpServletRequest , in servlet call there are new values getting set in request because using same request we are dispaching request to some jsp so request object is used as a input object to servlet as well as output for next page.
i mocked all input parameters , but for all request.setAttribute() , my code is doing nothing as it's a mocked class , say if i have
request.setAttribute(a,"10")
System.out.println("a = " + request.getAttribute("a"));
i get null cuz i haven't given any behavious for Request.getAttribute("a") , and i can't , it's my response for next page , so that explain i need 2 behaviour my request object thus partial mocking , and i am unable to spy or do any partial mocking on it so far. any ideas?
Code :
//Testcase
Myservlet.java
public void doPost(request,response)
{
String a = request.getAttribute("a");
String b = request.getAttribute("b");
int sum = Integer.parseInt(a) + Integer.parseInt(b);
request.setAttribute("sum",sum);
//well in this example i can use sum what i calculated but in real senario i can't , i have to use request.getAttribute("sum")
insertSumIntoDB(request.getAttribute("sum"));
}
}
//testMyservlet.java
#test
public void testServlet()
{
HttpServletRequest request = mock(HttpServletRequest.class);
HttpServletResponse response = mock(HttpServletResponse.class);
when(request.getAttribute(a)).thenReturn("10");
when(request.getAttribute(b)).thenReturn("20");
new Myservlet(request,response);
}
Spring's MockHttpServletRequest and MockHttpServletResponse are useful for that purpose. E.g.
MockHttpServletRequest request = new MockHttpServletRequest();
MockHttpServletResponse response = new MockHttpServletResponse();
request.addHeader(HttpHeaders.HOST, "myhost.com");
request.setLocalPort(PORT_VALID); // e.g. 8081
request.setRemoteAddr(REQUEST_IP); // e.g. 127.0.0.1
then I can call myclass.method(request, response, ...) and check whether some attribute has been correctly set into the request, e.g.
MyBean attr = (MyBean) request.getAttribute(ATTRIBUTE_NAME));
// do my Assert.* stuff with 'attr'
MockHttpServletRequest and MockHttpServletResponse work fine where mock(HttpServletRequest.class) fails, for instance where you need to get back the real content of a request attribute which has been previously set within your business logic.
You need to store attributes into a collection :
// Collection to store attributes keys/values
final Map<String, Object> attributes = new ConcurrentHashMap<String, Object>();
// Mock setAttribute
Mockito.doAnswer(new Answer<Void>() {
#Override
public Void answer(InvocationOnMock invocation) throws Throwable {
String key = invocation.getArgumentAt(0, String.class);
Object value = invocation.getArgumentAt(1, Object.class);
attributes.put(key, value);
System.out.println("put attribute key="+key+", value="+value);
return null;
}
}).when(request).setAttribute(Mockito.anyString(), Mockito.anyObject());
// Mock getAttribute
Mockito.doAnswer(new Answer<Object>() {
#Override
public Object answer(InvocationOnMock invocation) throws Throwable {
String key = invocation.getArgumentAt(0, String.class);
Object value = attributes.get(key);
System.out.println("get attribute value for key="+key+" : "+value);
return value;
}
}).when(request).getAttribute(Mockito.anyString());
Mockito supports real partial mocks: Real partial mocks (Since 1.8.0)
I think it fits your needs

Categories

Resources