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);
}
Related
I need to create a rest service in java which will in turn connect to another rest service for file download. For now, I just need to transfer the file from the other backend to client but in future some processing/transformations would be done.
For all the web services in my project, we are using spring rest (for providing as well as consuming the services).
My question is what would be the appropriate way of doing it considering that the files would be large and I don't want to run into OutOfMemory errors.
People in some other posts have suggested to use streams on both the ends but is that really possible? For this, do I need to write the file on disk first?
My current code for file download (consumer) -
public BackendResponse<byte[]> callBackendForFile(BackendRequest request) {
String body = null;
ResponseEntity<byte[]> responseEntity = null;
URI uri = createURI(request);
MultiValueMap<String, String> requestHeaders = getHeadersInfo(request.getHttpRequest());
if (HttpMethod.GET.equals(request.getMethod())) {
responseEntity = restTemplate.exchange(uri, request.getMethod(),
new HttpEntity<String>(body, requestHeaders), byte[].class);
} else {
LOG.error("Method:{} not supported yet", request.getMethod());
}
BackendResponse<byte[]> response = new BackendResponse<>();
response.setResponse(responseEntity);
return response;
}
My client code (provider):
#RequestMapping(value = "/file", method = RequestMethod.GET, produces = "application/xml")
#ResponseBody
public void downloadFileWithoutSpring(HttpMethod method, HttpServletRequest httpRequest,
HttpServletResponse httpResponse) {
BackendRequest request = new BackendRequest(method,
httpRequest.getRequestURI(), httpRequest.getQueryString(), httpRequest);
BackendResponse<byte[]> backendResponse = dutyplanService.getFile(request);
ResponseEntity<byte[]> response = backendResponse.getResponse();
httpResponse.addHeader("Content-Disposition", "attachment; filename=\"" + "attachment.zip" + "\"");
httpResponse.getOutputStream().write(response.getBody());
httpResponse.flushBuffer();
}
Note: The code above doesn't work somehow as the attachment downloaded is a corrupt file
I don't think you will need to create that file on server as long as you are having the bytearray content of it received from another server.
You can try changing value of produces annotation to the value application/zip (or application/octet-stream, depending on the target browser) instead of 'application/xml'
you can pass HttpServletResponse#getOutputStream() directly in restTemplate and write it without save file in server.
public void getFile(HttpServletResponse response) throws IOException {
restTemplate.execute(
"http://ip:port/temp.csv",
HttpMethod.GET,
null,
clientHttpResponse -> {
StreamUtils.copy(clientHttpResponse.getBody(), response.getOutputStream());
return null;
}
);
}
note that after call getFile(), you should close outputStream like this
response.getOutputStream().close()
When I tried to user Micro Message Public Platform, the weChat server will invoke one of my API and I need to return a token to validate my identity. However,when I return the token directly like this, the weChat server alerts that validation is error.
#RequestMapping(value="/userFollow", method= RequestMethod.GET)
#ResponseBody
public String weChatToken(HttpServletRequest request,String signature,String timestamp,String nonce,String echostr,HttpServletResponse response) throws IOException, DocumentException {
String result=weChatService.checkSignature(signature,timestamp,nonce,echostr);
return result;
}
Then I changed my code as below. This time, the validation is correct.
#RequestMapping(value="/userFollow", method= RequestMethod.GET)
#ResponseBody
public String weChatToken(HttpServletRequest request,String signature,String timestamp,String nonce,String echostr,HttpServletResponse response) throws IOException, DocumentException {
String result=weChatService.checkSignature(signature,timestamp,nonce,echostr);
PrintWriter pw=response.getWriter();
pw.write(result);
pw.flush();
return null;
}
I Googled and got that when using #Responsebody, Spring write messages to the body of response.
So what's the difference between them? Why the first way is Wrong?
An HTTP response consists of a status code, some headers, and a body. Using #ResponseBody means your method gives the content of the body, and nothing else. Using HttpServletResponse enables your method to set all aspects of the response, but is a little inconvenient to use.
You should use ResponseBody for returning some data structure. Since you need "only" String, you should change the return type of your method to void from String and remove ResponseBody annotation.
#RequestMapping(value="/userFollow", method= RequestMethod.GET)
public void weChatToken(HttpServletRequest request,String signature,String timestamp,String nonce,String echostr,HttpServletResponse response) throws IOException, DocumentException {
String result=weChatService.checkSignature(signature,timestamp,nonce,echostr);
PrintWriter pw=response.getWriter();
pw.write(result);
pw.flush();
}
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));
}
I use Liferay and I have code:
#ResourceMapping(value = "keaFileUpload")
public ModelAndView fileUpload(ResourceRequest request, ResourceResponse response,
ModelMap modelMap) throws PortalException, SystemException {
// code
}
How can I use Multipart as parameter request? For example MultipartHttpServletRequest?
When I use it instead of ResourceRequest request I have error.
When the form is multipart/form-data you should get extra parameter(like "name") from UploadPortletRequest instead of ResourceRequest.
correct form of getting parameter:
UploadPortletRequest uploadRequest = PortalUtil.getUploadPortletRequest(request);
String name = ParamUtil.getString(uploadRequest,"name");
Also you can check out this link
https://www.liferay.com/community/forums/-/message_boards/message/17237791
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.