Saturday, September 1, 2012

Spring MVC 3 : file uploads and POST requests

HTTP POST is the method to use when the aim of the HTTP request is to change the state of the server (thus HTTP POST is an omnipotent type of a request). We use POSTs to send bytes to the server in the form of an in-lined within the request simple form data but also to transmit larger chunks of bytes, normally stored as files on our local device.
Spring MVC (3.1+) makes it easy for us to handle POSTs within our controllers and in particular to organize our controller logic into separate sections depending on whether our server-side code handles simple POST or actual file uploads. 

  • Simple POST vs File Upload
Pre-HTML5/ES5 file upload was possible only when initiated via a FORM element. Those, now older browsers, featured a less-sophisticated version of the famous XMLHttpRequest object, which allowed simple POST requests but no file upload-like functionality. File upload in such older browsers was possible only through the FORM tag. It was that FORM tag that allowed for a functionality like the ability to fire-up a file chooser dialog and transmit the file to the server as part of a POST request.

ES5, the new hotness, came with an updated (and now a W3C standard) spec for the XMLHttpRequest object that gives us the option to carry out file uploads in new ways.

Whatever the way to initiate file uploads and simple POSTs, the server will differentiate between the two based on an HTTP header, the Content-Type header:

Simple POST:
...
Accept: */*
Accept-Encoding:gzip, deflate
Accept-Language: en-us,en;q=0.5
Connection:keep-alive
Content-Length: 21
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
Cookie: JSESSIONID=4A9CEA0165FB443EABBD45BBC36E4482
Hostlocalhost:8080
...

POST file upload:
...
Accept:text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Encoding: gzip, deflate
Accept-Language: en-us,en;q=0.5
Connection: keep-alive
Cookie: JSESSIONID=4A9CEA0165FB443EABBD45BBC36E4482
Host: localhost:8080
Content-Length: 48
Content-Type: multipart/form-data; boundary=----------------------265001916915724
...

  • Spring MVC 3 (tested 3.1.2) and POSTs on a controller level
Annotations and content negotiation will help us have different methods with almost idential RequestMapping definitions (except the consumes parameter) in order to handle the two scenarios:

@RequestMapping(method = RequestMethod.POST, value = "/url", consumes = "!multipart/form-data")
public void post(HttpServletRequest httpRequest) {
...
}

and

@RequestMapping(method = RequestMethod.POST, value = "/url", consumes = "multipart/form-data")
@ResponseStatus(HttpStatus.CREATED)
public void upload(@RequestParam("file") MultipartFile file) {
 if (!file.isEmpty()) {
 ...
 }
}

  • Spring MVC 3 (tested 3.1.2) configuration
Spring has some good documentation on the subject:
http://static.springsource.org/spring/docs/3.1.x/spring-framework-reference/html/mvc.html#mvc-multipart

What is important is the following bit of Spring config to be present:
<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping">
 ...
</bean>

<bean id="messageAdapter" class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter">
 ...
</bean>

So, if you used DefaultAnnotationHandlerMapping,  now we need to change that to RequestMappingHandlerMapping and AnnotationMethodHandlerAdapter now becomes RequestMappingHandlerAdapter. You need to define both the HandlerMapping and the HandlerAdapter to the RequestMapping* versions.


No comments:

Post a Comment