Spring 3.0 MultipartFile upload - java

I am converting Java web application to Spring framework and appreciate some advice on the issues I am facing with the file upload. Original code was written using org.apache.commons.fileupload.
Does Spring MultipartFile wraps org.apache.commons.fileupload or I can exclude this dependency from my POM file?
I have seen following example:
#RequestMapping(value = "/form", method = RequestMethod.POST)
public String handleFormUpload(#RequestParam("file") MultipartFile file) {
if (!file.isEmpty()) {
byte[] bytes = file.getBytes();
// store the bytes somewhere
return "redirect:uploadSuccess";
} else {
return "redirect:uploadFailure";
}
}
Originally I tried to follow this example but was always getting an error as it couldn't find this request param. So, in my controller I have done the following:
#RequestMapping(value = "/upload", method = RequestMethod.POST)
public #ResponseBody
ExtResponse upload(HttpServletRequest request, HttpServletResponse response)
{
// Create a JSON response object.
ExtResponse extResponse = new ExtResponse();
try {
if (request instanceof MultipartHttpServletRequest)
{
MultipartHttpServletRequest multipartRequest =
(MultipartHttpServletRequest) request;
MultipartFile file = multipartRequest.getFiles("file");
InputStream input = file.getInputStream();
// do the input processing
extResponse.setSuccess(true);
}
} catch (Exception e) {
extResponse.setSuccess(false);
extResponse.setMessage(e.getMessage());
}
return extResponse;
}
and it is working. If someone can tell me why #RequestParam did not work for me, I will appreciate. BTW I do have
<bean id="multipartResolver"
class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<property name="maxUploadSize" value="2097152"/>
</bean>
in my servlet context file.

I had to
add commons-fileupload dependency to my pom,
add multipartResolver bean (mentioned in the question),
use #RequestParam("file") MultipartFile file in the handleFormUpload method and
add enctype in my jsp : <form:form method="POST" action="/form" enctype="multipart/form-data" >
to get it to work.

spring does not have a dependency on commons-fileupload, so you'll need it. If it's not there spring will use its internal mechanism
You should pass a MultipartFile as a method parameter, rather than #RequestParam(..)

This works for me.
#RequestMapping(value = "upload.spr", method = RequestMethod.POST)
public ModelAndView upload(#RequestParam("file") MultipartFile file, HttpServletResponse response)
{
// handle file here
}

The General sysntax for request param is this #RequestParam(value="Your value", required=true),
mode over request param is used to get a value frm the Url.

In a POST you will only send the params in the request body, not in the URL (for which you use #RequestParams)
Thats why your second method worked.

In Spring MVC 3.2 support for Servet 3.0 was introduced. So you need to include commons-file upload if you use earlier versions of Spring.

Related

How to modify the parameter name of MultipartFile in uploading?

I need to upload a MultipartFile to a third-party service via my own backend service. The parameter in the multipart form is 'nameA' but the third-party service need its param name is 'nameB'.
Normally I can solve it in two ways:
Change the param name of frontend to 'nameB'
Change the param name of MultipartFile to 'nameB' in backend service.
But I cannot change the frontend now, so I want to finger out how to modify the param name of MultipartFile in backend service.
The controller of backend service is:
#PostMapping("/url")
public Response method(#RequestParam("nameA") MultipartFile file) {
return Service.method(file);
}
In Feign Client for uploading file to third-party service:
#PostMapping(value = "/url1/url2", consumes = MULTIPART_FORM_DATA_VALUE)
Response method(#RequestParam("nameB") MultipartFile file);
However the use of specify the param with đŸ‘† #RequestParam doesn't work.
So does anyone know how to modify the param name of MultipartFile? Thanks a lot!
That is completely unrelated to your controllers spring annotations and instead depends on how you would upload that file to the 3rd party service. Since you mentioned uploading it, I assume you need to create a new HTTP multipart request in your backend service that would upload the file to the 3rd party service. When creating that request, you will be able to specify the name of the multipart part.
You can set a name of the MultipartFile in the FeignClient as you need, this is a sample from my project:
Đ¡ontroller API (receiving side):
#RestController
#RequestMapping("/files")
public class FilesController {
#PostMapping(path = "/upload")
#ResponseStatus(HttpStatus.CREATED)
public FileDescriptor upload(#RequestPart(value = "data") MultipartFile multipartFile) {
...
}
}
Feign client (sending side):
#FeignClient(value = "file-service", configuration = FeignConfig.class)
public interface ContentStorageFeign {
#ResponseBody
#PostMapping(value = "/files/upload", produces = MediaType.APPLICATION_JSON_VALUE)
FileDescriptor create(#RequestPart(value = "data") MultipartFile multipartFile);
}
And this is my FeignConfig:
#Configuration
public class FeignConfig {
#Bean
public Decoder decoder(ObjectFactory<HttpMessageConverters> messageConverters) {
return new ResponseEntityDecoder(new SpringDecoder(messageConverters));
}
#Bean
public Encoder encoder(ObjectFactory<HttpMessageConverters> messageConverters) {
return new SpringFormEncoder(new SpringEncoder(messageConverters));
}
}
But if you need to create a new request(from a file received from somewhere) and rename this file before sending, this is another problem.

in spring 4 controllers produce plain text instead of html

I'm updating my application from
spring 3.2.5 to 4.2.0
spring security 3.1.4 to 4.0.2.
A lot of my controllers which used to display HTML now display plain text.
I created a few test methods, the following displayed HTML in version 3 and displays plain text in version 4:
#RequestMapping(value = "/htmltest", method = RequestMethod.GET)
public void test(Writer writer, HttpServletRequest request, HttpServletResponse response) throws IOException {
writer.append("<html><head></head><body>"); //$NON-NLS-1$
writer.append("this is a HTML test");
writer.append("</body></html>"); //$NON-NLS-1$
writer.flush();
}
I also added produces = "text/html" to the #RequestMapping, did not have an effect.
I think this only has an effect using #ResponseBody? Did the following test:
#ResponseBody
#RequestMapping(value = "/htmltest2", method = RequestMethod.GET, produces = "text/html")
public String test2(HttpServletRequest request, HttpServletResponse response) {
StringBuilder str = new StringBuilder();
str.append("<html><head></head><body>"); //$NON-NLS-1$
str.append("this is a HTML test");
str.append("</body></html>"); //$NON-NLS-1$
return str.toString();
}
This displays HTML and if I switch to produces = "text/plain" returns text.
So the question is how do I get controllers to produce HTML where the writer is being used?
I found out that I can set the content type on the response response.setContentType("text/html"), but would prefer not having to do that for all my controllers.
Especially since it worked in the previous spring version, could it be a configuration problem?
#RequestMapping(value = "/htmltest3", method = RequestMethod.GET)
public void test3(Writer writer, HttpServletRequest request, HttpServletResponse response) throws IOException {
response.setContentType("text/html");
writer.append("<html><head></head><body>"); //$NON-NLS-1$
writer.append("this is a HTML test");
writer.append("</body></html>"); //$NON-NLS-1$
writer.flush();
}
Edit: I just found out that only Chrome (44.0.2403.155 m) does not display HTML, Firefox (40.0.2) and InternetExplorer (11.0.9600.17959) work as expected.
Still it may also not work in older browser versions, but maybe this gives a hint?
So the problem was the headers which have been introduced in Spring Security 3.2.0:
http://spring.io/blog/2013/08/23/spring-security-3-2-0-rc1-highlights-security-headers/
If I add the following lines to my application context, the controllers display HTML again.
<http>
...
<headers disabled="true"/>
</http>
Well but this is a bad idea since it will disable all the additional seurity features (X-XSS-Protection, X-Frame-Options, HSTS).
So one option could be to disable the defaults and configure own headers:
<http>
...
<headers defaults-disabled="true">
<frame-options policy="SAMEORIGIN" />
</headers>
</http>
After narrowing it down this solves my issue:
<http>
...
<headers>
<content-type-options disabled="true" />
</headers>
</http>
There are some security aspects that need to be considered, but for me this works for now.
http://docs.spring.io/autorepo/docs/spring-security/4.0.x/reference/html/headers.html#headers-content-type-options
Try:
#ResponseBody
#RequestMapping(value = "/htmltest3", method = RequestMethod.GET)
public void test3(Writer writer, HttpServletRequest request, HttpServletResponse response) throws IOException {
String html = "<html><head></head><body>this is a HTML test</body></html>";
return html;
}

How to set HttpServletResponse on Jboss EAP 6.2?

I have a simple method where I need to change the contentType to "text/plain" but when I am using Jboss EAP 6.2 it is ignored and the returned value is "application/json.
#RestController
#RequestMapping("/upload")
public class UploadController {
...
#RequestMapping(method = RequestMethod.POST)
public Result foo(MultipartFile arquivo, HttpServletResponse response) {
response.setContentType("text/plain");
return uploadService.saveFile(arquivo);
}
}
I am using SpringMVC (rest controller) and Jackson to parse to JSON. The exactly same method works fine when I use Jetty instead of Jboss.
I can't use #RequestMapping(produces="text/plain") because the actual method has a bit more logic into it and sometimes it will return application/json others text/plain, depending if the request came from IE9 browser or not.
Can someone please tell me how can I set the contentType on Jboss?
Unfortunately the only way I could get this working for Jboss was creating a different method for IE9:
#RequestMapping(value = "/ie9", method = RequestMethod.POST, produces = MediaType.TEXT_PLAIN_VALUE)
public String fooIE9(#RequestParam("arquivo") MultipartFile arquivo) throws JsonProcessingException {
ObjectWriter ow = new ObjectMapper().writer();
return ow.writeValueAsString(uploadService.saveFile(arquivo));
}

download uploaded file using spring mvc abstractions (avoid using raw HttpServletResponse)

I am trying to add file uploading and downloading in my web application.
I am used to don't use raw HttpServletRequest and HttpServletResponse when I use spring mvc. But now I have following controller to download files.
public ModelAndView download(HttpServletRequest request, HttpServletResponse response) throws Exception {
int id = ServletRequestUtils.getRequiredIntParameter(request, "id");
Files file = this.filesService.find(id);
response.setContentType(file.getType());
response.setContentLength(file.getFile().length);
response.setHeader("Content-Disposition","attachment; filename=\"" + file.getFilename() +"\"");
FileCopyUtils.copy(file.getFile(), response.getOutputStream());
return null;
}
As you can see I use HttpServletRequest and HttpServletResponse here.
I want to find way to avoid using of these classes. Is it possible?
The id parameter that you are getting from request can be substituted with the use of #RequestParam or #PathVariable. See bellow for an example of #RequestParam
public ModelAndView download(#RequestParam("id") int id) {
// Now you can use the variable id as Spring MVC has extracted it from the HttpServletRequest
Files file = this.filesService.find(id); // Continue from here...
}
And now the response part
#RequestMapping(value = "/download")
public ResponseEntity<byte[]> download(#RequestParam("id") int id) throws IOException
{
// Use of http headers....
final HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
InputStream is // Get your file contents read into this input stream
return new ResponseEntity<byte[]>(IOUtils.toByteArray(is), headers, HttpStatus.CREATED);
}

Can you set the src attribute of an HTML image tag to a controller method?

I know that you can do this
<img src="http://some_svg_on_the_web" />
But what if I have a controller method annotated with #ResponseBody
#RequestMapping(value="getSVG")
public #ResponseBody String getSVG(HttpServletRequest request, HttpServerletResponse response) {
String SVG = // build the SVG XML as a string
return SVG;
}
Can I then say
<img src="/getSVG" />
I have tested and the controller is definitely being hit but no image is being shown on the page.
I believe the problem is Spring is setting the default content type to application/octet-stream and the browser can't read your XML as that. Instead, you need to actually set the Content-Type header, either through the HttpServerletResponse or with Spring's ResponseEntity.
#RequestMapping(value="getSVG")
public #ResponseBody ResponseEntity<String> getSVG(HttpServletRequest request, HttpServerletResponse response) {
String SVG = // build the SVG XML as a string
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.valueOf("image/svg+xml"));
ResponseEntity<String> svgEntity = new ResponseEntity<String>(svg, headers, HttpStatus.OK);
return svgEntity;
}
The fact that the you have the XML as a String doesn't really matter, you could've used getBytes() to make the content byte[]. You can also use the Resource class to have Spring get the bytes directly from a classpath or file system resource. You would parameterize ResponseEntity accordingly (there are a number of supported types).

Categories