The PUT Method with AJAX in Spring 3.2 doesn't work
Unless one is using only path parameters, processing a regular HTTP PUT needs some more work.
Since Spring 3.1, HttpPutFormContentFilter
can be used to make @RequestParam
work for application/x-www-form-urlencoded
data:
Filter that makes form encoded data available through the
ServletRequest.getParameter*()
family of methods during HTTP PUT requests.The Servlet spec requires form data to be available for HTTP POST but not for HTTP PUT requests. This filter intercepts HTTP PUT requests where content type is '
application/x-www-form-urlencoded
', reads form encoded content from the body of the request, and wraps the ServletRequest in order to make the form data available as request parameters just like it is for HTTP POST requests.
However: this filter consumes the request's input stream, making it unavailable for converters such as FormHttpMessageConverter
, like used for @RequestBody MultiValueMap<String, String>
or HttpEntity<MultiValueMap<String, String>>
. As a result, once you have configured the above filter in your application, you will get "IOException: stream closed" when invoking methods that use other converters that also expect raw application/x-www-form-urlencoded
PUT data.
Alternatively one can do everything manually, using @RequestBody
or HttpEntity<?>
:
@RequestMapping(value="ajax/UpdateUserRole", method=RequestMethod.PUT, produces = MediaType.TEXT_PLAIN_VALUE)public @ResponseBody String updateUserRole( @RequestBody final MultiValueMap<String, String> data, final HttpServletResponse response) { Map<String, String> params = data.toSingleValueMap(); String id = params.get("id"); String a = params.get("a"); String b = params.get("b"); if(id == null || a == null || b == null) { response.setStatus(HttpServletResponse.SC_BAD_REQUEST); return null; } return "id = " + id;}
See also an example using WebDataBinder
, or use:
public ResponseEntity<String> updateUserRole( final HttpEntity<MultiValueMap<String, String>> entity) { Map<String, String> params = entity.getBody().toSingleValueMap(); String id = params.get("id"); ...
Note that for testing, using MockMvc's mockMvc.perform(put(url).param(name, value))
would actually also work with the code form the question, even though it would fail in a servlet container. But MockMvc is not running in such servlet container, hence is fooling you a bit.
MockMvc's .param(name, value)
also works nicely with HttpPutFormContentFilter
. But when using MockMvc to test @RequestBody
or HttpEntity<?>
, one also needs to create any application/x-www-form-urlencoded
PUT content manually. Like:
mockMvc.perform(put(url).content("id=" + URLEncoder.encode(id, "UTF-8") + "&a=" + URLEncoder.encode(a, "UTF-8") + "&b=" + ...)
To be able to simply use .param(name, value)
, just like for GET and POST, one could define:
public static RequestPostProcessor convertParameters() { return new RequestPostProcessor() { @Override public MockHttpServletRequest postProcessRequest( final MockHttpServletRequest request) { if ("PUT".equalsIgnoreCase(request.getMethod()) { Map<String, String[]> params = request.getParameterMap(); if (params != null) { StringBuilder content = new StringBuilder(); for (Entry<String, String[]> es : params.entrySet()) { for (String value : es.getValue()) { try { content.append(URLEncoder.encode(es.getKey(), "UTF-8")) .append("=") .append(URLEncoder.encode(value, "UTF-8")) .append("&"); } catch (UnsupportedEncodingException e) { throw new IllegalArgumentException("UTF-8 not supported"); } } } request.setParameters(new HashMap<String, String[]>()); request.setContent(content.toString().getBytes()); request.setContentType(MediaType.APPLICATION_FORM_URLENCODED_VALUE); } } return request; } };}
...and then use .with(convertParameters())
next to .param(name, value)
:
mockMvc.perform(put(url) .with(convertParameters()) .param("id", id).param("a", a).param("b", b) ...)
Given all the above, simply using HttpPutFormContentFilter
for application/x-www-form-urlencoded
data really makes life easier.
When the browser is not sending application/x-www-form-urlencoded
data, but things such as JSON, then trying to map to MultiValueMap
will yield 415 Unsupported Media Type. Instead, use something like @RequestBody MyDTO data
or HttpEntity<MyDTO> entity
as explained in Parsing JSON in Spring MVC using Jackson JSON.