Spring REST file download unable to set header content-type attachment - spring-boot

I have a Springboot REST application that downloads files from a given directory.
The downloads can be any file file and have any format, and I want to use the original filename as the filename of the downloaded file.
I used the code below to set the filename in the header, and add the header to the response:
#RestController
#RequestMapping("/downloads")
public class DownloadCsontroller {
...
#GetMapping
public void downloadSingleFile(#RequestParam("file") String filename, HttpServletResponse response) throws IOException {
String filepath = m_attachmentPathLocation + File.separator + filename;
File file = new File(filepath);
String contentType = getContentType(file);
response.setStatus(HttpServletResponse.SC_OK);
response.setContentType(contentType);
response.setHeader("Content-Disposition:", "attachment;filename=\"" + file.getName() + "\"");
...
}
...
}
Tested using both "Content-Disposition" and "Content-Disposition:" in setHeader().
Almost everything works (file types), except for PDF, ZIP, RAR, EXE, etc.
Any files (types) not on the list can be downloaded with the desired filenames.
But when any of the file download (PDF, ZIP, RAR, EXE, etc)... it seems it continuously loads like forever... and I cannot even see any request sent in POSTMAN, inspector, firebug, etc.
If I comment out:
//response.setHeader("Content-Disposition:", "attachment;filename=\"" + file.getName() + "\"");
It would work, but the filename would be set to the name of the request mapping. which in this case is "downloads".
I have seen lots of samples that uses "Content-Disposition" header to change the attachment filename... but it seems it fails on these file types.
I have no configurations yet, and it is kinda weird since in most samples I searched... this should be running or working.
TIA

Please add #GetMapping(produces = MediaType.APPLICATION_OCTET_STREAM_VALUE)
and instead of returning direct file try to return stream.
Also make a note that "Content-Disposition:" header will not work if the requesting app IP & Port number is different from server app IP & Port number.

Things would work If you can alter the code a bit, by setting all header values using org.springframework.http.HttpHeaders class.
Now looking at your code, i suspect you trying to expose an API that allows to download a multipart File.
I would suggest you not to use HttpServletResponse Class to set the Content- Dispositionheader but use HttpHeaders class. Below is the reformatted code
#RestController
public class DownloadCsontroller {
#GetMapping(value="/downloads")
public ResponseEntity<Object> downloadSingleFile(#RequestParam("file")
String filename) throws IOException {
String filepath = m_attachmentPathLocation + File.separator + filename;
File file = new File(filepath);
String contentType = getContentType(file);
/* response.setStatus(HttpServletResponse.SC_OK);
response.setContentType(contentType);
response.setHeader("Content-Disposition:", "attachment;filename=\""
+ file.getName() + "\"");
*/
// Here is the below Code you need to reform for Content-
//Disposition and the remaining header values too.
HttpHeaders headers= new HttpHeaders();
headers.add("Content-Disposition", "attachment; filename
=whatever.pdf");
headers.add("Content-Type",contentType);
// you shall add the body too in the ResponseEntity Return object
return new ResponseEntity<Object>(headers, HttpStatus.OK);
}
}

Related

Spring boot Content-Disposition filename in Russian

the problem with downloading a file from the server, the file name may contain Russian text, when downloading it substitutes ??????? instead of Russian letters, and the download breaks, a file without an extension named 1 is downloaded. How do I fix it?
#GetMapping("/download/{id}")
public ResponseEntity<byte[]> getFile(#PathVariable Long id) {
File file = fileStorageService.getFile(id);
return ResponseEntity.ok()
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + file.getName() + "\"")
.contentType(MediaType.valueOf("application/docx"))
.body(file.getData());
}
Question number two, is there a dynamic way to specify the content-type depending on the file extension?
ContentDisposition contentDisposition = ContentDisposition.builder("attachment")
.filename(file.getName(), StandardCharsets.UTF_8)
.build();
return ResponseEntity.ok()
.header(HttpHeaders.CONTENT_DISPOSITION, contentDisposition.toString())
.contentType(MediaType.valueOf(file.getType()))
.body(file.getData());

Spring boot embedded tomcat - 413 Request Entity Too Large

I'm running on the IBM public cloud. I have apu connect to access the cloud foundry microservice. I've gone through many of the posts and tried various things and I can't seem to get this to work. Here are my property file config settings for spring boot:
# The name of the application
spring.application.name=xxxxx
# web base path
management.endpoints.web.base-path=/
# Embedded tomcat config
server.tomcat.max-swallow-size=256MB
server.tomcat.max-http-post-size=256MB
# File size values
spring.servlet.multipart.max-file-size=256MB
spring.servlet.multipart.max-request-size=256MB
spring.servlet.multipart.enabled=true
# Server specific values
input.server=xxx
input.rtm.bucket=xxx
storage.server.base=xxx
# Cloudant database info
input.events.db.name=xxxx
input.ait.info.db.name=xxxx
letter.number.db.name=xxxx
letter.gen.data.db.name=xxxx
# Query index design documents
query.pad.ait.info.index.name=xxxx
query.pad.ait.info.deisgn.doc=_xxxx
query.rfa.ltr.index.name=xxxx
query.rfa.ltr.design.doc=xxxx
# The logging levels of the application
logging.level.application=DEBUG
#logging.level.root=DEBUG
#logging.level.org.springframework.web=INFO
# Testing
unit.testing=false
integration.testing=true
# Jackson json config
spring.jackson.mapper.accept-case-insensitive-properties=true
Here is the REST api function for POSTing the file
#PostMapping(value = "/send/rtm/document/{errata}")
public #ResponseBody ResponseEntity<Object> receiveRtmDocument(#PathVariable("errata") String errata, #RequestParam("file") MultipartFile file)
I'm using spring boot 2.1.6 and have not updated anything in the POM file. I'm attempting to send a 5.8 MB file to the api and it gives me this error:
com.ibm.tools.cloud.exceptions.DataNotJsonException: <html>
<head><title>413 Request Entity Too Large</title></head>
<body bgcolor="white">
<center><h1>413 Request Entity Too Large</h1></center>
<hr><center>openresty</center>
</body>
</html>
at com.ibm.msc.gasm.sapt.input.AitInputManagement.sendRtmDocument(AitInputManagement.java:182)
at com.ibm.msc.gasm.sapt.test.InputServiceTester.performTest(InputServiceTester.java:142)
at com.ibm.msc.gasm.sapt.test.InputServiceTester.main(InputServiceTester.java:96)
Here is the send code I am using in java for the multipart. The only other headers I use that are not listed here are my authorization headers.
// Create the URL connection
HttpURLConnection conn = (HttpURLConnection) (new URL(requestUri)).openConnection();
if (content != null || multipartFile) conn.setDoOutput(true);
conn.setRequestMethod(method.toString());
// Set the headers
Enumeration<String> keys = headers.keys();
while (keys.hasMoreElements())
{
// Pull out the key
String key = keys.nextElement();
// Set the header
conn.setRequestProperty(key, headers.get(key));
}
// Set the accept header
if (acceptHeader != null) conn.setRequestProperty("Accept", acceptHeader);
// Set the content header
if (contentTypeHeader != null) conn.setRequestProperty("Content-Type", contentTypeHeader);
if (content != null)
{
// Set the content
DataOutputStream dos = new DataOutputStream(conn.getOutputStream());
if (content.isFileContent()) dos.write(content.getFileContentAsByteArray());
else if (content.isByteArrayContent()) dos.write(content.getContentAsByteArray());
else if (content.isStringContent()) dos.write(content.getStringContentAsByteArray());
// close the stream
dos.flush();
dos.close();
}
// Set the multipart file
if (multipartFile)
{
// Set the properties
conn.setUseCaches(false);
conn.setRequestProperty("Connection", "Keep-Alive");
conn.setRequestProperty("Cache-Control", "no-cache");
conn.setRequestProperty("Content-Type", "multipart/form-data;boundry=" + MP_BOUNDRY);
// Set the content
DataOutputStream dos = new DataOutputStream(conn.getOutputStream());
dos.writeBytes(MP_HYPHENS + MP_BOUNDRY + StringUtils.crlf);
dos.writeBytes("Content-Disposition: form-data: name=\"" + this.mpName + "\";filename=\"" + this.mpFileName + "\"" + StringUtils.crlf);
dos.writeBytes(StringUtils.crlf);
dos.write(IOUtils.toByteArray(new FileInputStream(this.mpFileNamePath)));
dos.writeBytes(StringUtils.crlf);
dos.writeBytes(MP_HYPHENS + MP_BOUNDRY + MP_HYPHENS + StringUtils.crlf);
// close the stream
dos.flush();
dos.close();
}
// Get the response
HttpResponseMessage response = null;
try
{
// Extract the stream
InputStream is = (conn.getResponseCode() >= HttpURLConnection.HTTP_BAD_REQUEST) ? conn.getErrorStream() : conn.getInputStream();
// Pull out the information
byte[] data = IOUtils.toByteArray(is);
// Set the response
response = new HttpResponseMessage(requestUri, HttpStatusCode.getType(conn.getResponseCode()), acceptHeader, data, conn.getResponseMessage());
}
catch (Throwable e)
{
throw new IOException(String.format("Error reading results from %s", requestUri), e);
}
// Close the request
conn.disconnect();
// Send request
return response;
I've tried several things, but I am not sure what I am missing. Anyone have any ideas how to fix this?
You need to change NGINX settings;
Add to config file next line
client_max_body_size 20M;
Use the form form to submit the file and accept it with MultipartFile. In this case (the other situation is not clear), the default file size is limited to 2M. If you want to upload a large file, you need to configure the file size.
https://www.cyberciti.biz/faq/linux-unix-bsd-nginx-413-request-entity-too-large/
Try these two in your application.properties
server.tomcat.max-swallow-size=XMB //maximum size of the request body/payload
server.tomcat.max-http-post-size=XMB //maximum size of entire POST request
X is your desired integer representing megabyte.

Identify image request in servlet by filename in URI instead of as request parameter

I use a servlet to get images that saved out of my project. Code in my servlet is:
String file = request.getParameter("file");
BufferedInputStream in = new BufferedInputStream(new FileInputStream(directory + file));
// Get image contents.
byte[] bytes = new byte[in.available()];
in.read(bytes);
in.close();
// Write image contents to response.
response.getOutputStream().write(bytes);
The HTML image tags are like this:
<img src="/images/?file=example.jpg" />
Everything is good. But now I would like to have image filename in URI instead of as request parameter like so:
<img src="/images/example.jpg" />
How can I achieve it?
Map the servlet on a prefix URL pattern /images/* instead of an apparently exact URL pattern /images. Then, you can specify URLs exactly like that and obtain the filename as URI path information by HttpServletRequest#getPathInfo().
Kickoff example:
#WebServlet("/images/*")
public class ImageServlet extends HttpServlet {
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String filename = request.getPathInfo().substring(1);
File file = new File(directory, filename);
response.setHeader("Content-Type", getServletContext().getMimeType(file.getName()));
response.setHeader("Content-Length", String.valueOf(file.length()));
Files.copy(file.toPath(), response.getOutputStream());
}
}
Unrelated to the concrete problem: InputStream#available() as in your initial code doesn't do what you apparently thought it does. It doesn't return the entire length of the content. It just returns the length of the first block which the code can read without blocking the disk file system I/O. I.e. it returns the length of content currently in I/O buffer. This does not necessarily represent the entire content length! For sure not on larger images. If you're on Java 7, just use Files#copy() if the sole purpose is to achieve the job with least possible amount of code as shown above.

Uploading more than one image

Dear All,
Working on Spring MVC. I want to upload more than one images from the client. How to achieve it. I know how to handle the multipart form data for single image. But now I am expecting some data with some images from the client.
Any help or url that will help me.
Thanks,
Op
Image is also a file. Whether you would be storing it in database / in file system but it is still a file.
In spring MVC, you could do as shown in the below link:
http://viralpatel.net/blogs/spring-mvc-multiple-file-upload-example/
Here are the code i tried and it is working fine at my end.
//Handle multiple images
#RequestMapping(method = RequestMethod.POST, value="upload", consumes=MediaType.MULTIPART_FORM_DATA_VALUE,
produces=MediaType.APPLICATION_JSON_VALUE)
public #ResponseBody JSONResponse uploadImages(HttpServletRequest req)
throws Exception {
try{
MultipartHttpServletRequest multipartRequest = (MultipartHttpServletRequest) req;
Set set = multipartRequest.getFileMap().entrySet();
Iterator i = set.iterator();
while(i.hasNext()) {
Map.Entry me = (Map.Entry)i.next();
String fileName = (String)me.getKey()+"_"+System.currentTimeMillis();
MultipartFile multipartFile = (MultipartFile)me.getValue();
System.out.println("Original fileName - " + multipartFile.getOriginalFilename());
System.out.println("fileName - " + fileName);
saveImage(fileName, multipartFile);
}
}
catch(Exception e){
e.printStackTrace();
}
return new JSONResponse();
}

Spring MVC outputStream

my problem is that I have a program which does make a file & stream it out to the user & I have two problem:
1) first how do change the file name & type (x.sql)
2) what if there's an exception while making the file & I want to redirect the user to an error page because my return type of my method is void when we want to write into outputstream in servlet !
my program is like this:
#RequestMapping(value = "/test/{jobid}")
public void dumpData(#PathVariable("jobid") long jobid, HttpServletResponse response) {
try {
response.setContentType("application/octet-stream");
ServletOutputStream out = response.getOutputStream();
String downloadOutput = "";
// AM I ABLE TO SET FILENAME SO THE USER DOWNLOAD THE FILE WITH THAT NAME ?
// DOES SETHEADER HELP ME IN THIS CASE ?
... (making downloadOutput String by values coming from somewhere)
out.write(downloadOutput.getBytes("UTF-8"));
out.flush();
out.close();
} catch(SomeException e){
//WHEN THE RETURN TYPE IS VOID HOW TO REDIRECT USER TO ERROR PAGE IN CASE OF SOME PROBLEM ?
//(INFO:)IF WE DEFINE STRING AS RETURN TYPE THE PROGRAM WILL GET EXCEPTION
}
Answer to your first question:
response.setHeader( "Content-Disposition", "attachment;filename=" + filename );
//for zip file
response.setContentType("application/zip");
Here is the list of mime type for various content types.
I found a solution here. You can change return type to string and return error view name in case of error otherwise return null. What I understood from the link is that returning null and return type void is similar.
What stops you from using response.sendRedirect(errorPage)?

Resources