Create a text file for download on the fly with Java - java

I have a Java Servlet that generates randomly thousands of Strings every time is called. I want the user to be able to get them in a file when he calls the Servlet. I don't want to write first the file on disk or memory.
Is there a way to write the file on the fly when the user calls the servlet?
Thanks

Any text that you generate in the Servlet can simply be written to the OutputStream returned by ServletResponse.getOutputStream().
If you want the output to be downloadable as a file, you can follow the approach in this answer - https://stackoverflow.com/a/11772700/1372207
The difference would be, that the Content-type would be text/plain and instead of reading from another inputstream, you would just write the String objects directly to the ServletOutputStream using the print(String) method.

If you use the idea to write content to HttpServletResponse's output stream while offering download service, rather than saving the content locally and then reading the file as FileInputStream, you can just convert the file content to InputStream by InputStream stream = new ByteArrayInputStream(exampleString.getBytes("UTF-8"));.
The following code partially references https://www.codejava.net/java-ee/servlet/java-servlet-download-file-example.
public void doDownload(HttpServletRequest request, HttpServletResponse response) throws IOException {
String fileName = "xxx.txt";
String fileContent = "";
// get absolute path of the application
ServletContext context = request.getServletContext();
// get MIME type of the file
String mimeType = context.getMimeType(fileName);
if (mimeType == null) {
// set to binary type if MIME mapping not found
mimeType = "application/octet-stream";
}
setResponseHeader(response, fileName, mimeType, (int) fileContent.length());
InputStream inputStream = new ByteArrayInputStream(fileContent.getBytes("UTF-8"));
// get output stream of the response
OutputStream outStream = response.getOutputStream();
byte[] buffer = new byte[4096];
int bytesRead = -1;
// write bytes read from the input stream into the output stream
while ((bytesRead = inputStream.read(buffer)) != -1) {
outStream.write(buffer, 0, bytesRead);
}
inputStream.close();
outStream.close();
}
private void setResponseHeader(HttpServletResponse response, String fileName, String mimeType, Integer fileLength) {
response.setContentType(mimeType);
response.setContentLength(fileLength);
response.setContentType("application/octet-stream; charset=UTF-8");
String headerKey = "Content-Disposition";
String headerValue = String.format("attachment; filename=\"%s\"", fileName);
response.setHeader(headerKey, headerValue);
response.addHeader("Pargam", "no-cache");
response.addHeader("Cache-Control", "no-cache");
}

Related

Zip file getting downloaded without .zip extension in Chrome through Java

I am trying to download a zip file from a fixed location present in server.
In my Rest method , I am just passing the file name from client (browser) .
(Please see below code ).
In my Rest method I am sending the zip file to the client.
The file gets downloaded on the browser without any issue.
My Issue is that the zip file gets downloaded on browser without .zip extension.
#RequestMapping(value = "/zip/{filePath}", method = RequestMethod.GET)
public #ResponseBody void downloadZip(#PathVariable("filePath") String filePath, HttpServletRequest request, HttpServletResponse response) throws IOException {
ServletContext context = request.getServletContext();
File downloadFile = new File(filePath);
FileInputStream inputStream = new FileInputStream(downloadFile);
// get output stream of the response
OutputStream outStream = response.getOutputStream();
byte[] buffer = new byte[(int) downloadFile.length()];
int bytesRead = -1;
// write bytes read from the input stream into the output stream
while ((bytesRead = inputStream.read(buffer)) != -1) {
outStream.write(buffer, 0, bytesRead);
}
// get MIME type of the file
String mimeType = context.getMimeType(fullPath);
if (mimeType == null) {
// set to binary type if MIME mapping not found
mimeType = "application/octet-stream";
}
System.out.println("MIME type: " + mimeType);
// set content attributes for the response
response.setContentType(mimeType);
response.setContentLength((int) downloadFile.length());
response.setHeader("Content-Disposition",
String.format("attachment; filename=\"%s\"", downloadFile.getName()));
logger.error("Filename = " + downloadFile.getName());
inputStream.close();
outStream.close();
}
PS: The file gets downloaded on some machine with ZIP and in some machine without ZIP. I have tested only on chrome (as per client requirement).
I think, there is an issue with the Chrome settings which I need to look upon (just a guess).
Can someone help upon this?
Thanks in advance....
Change the order between setting the response headers and shoving the file down the output stream - after all, the headers need to leave first.
[Edited]
"Why setting HttpServletResponse in starting effects the code."
Well, simple: the client is supposed to receive instructions of what to do with the payload by interpreting the HTTP response headers. If those are not set in the beginning, sending those headers at the end of the transmission comes too late. And this assumes the HttpServletResponse will actually send those headers when invoked with setHeader, which is a big assumption - I suspect those headers will not actually be sent after calling response.getOutputStream - it is unlikely the response will buffer the entire payload to wait for the caller to specify those headers.

Download link to CSV file in the mail using spring boot

I am generating csv in my code, It takes some time to generate. So, I am sending an email with link once the csv file is generated. When I click that, getting 404 not found error. When I have the same link in the html, I am able to download it. Any insight or sample to refer
Sample Link -http://localhost:9090/api/report/file?fileName=filename.csv
Java code to download the report
#RequestMapping(value = "api/report/file")
public void downloadCSV(HttpServletResponse response, #RequestParam("fileName") String fileName) throws IOException {
File file = new File(fileName);
InputStream is = new FileInputStream(file);
response.setContentType("application/octet-stream");
// Response header
response.setHeader("Content-Disposition", "attachment; filename=\"" + file.getName() + "\"");
// Read from the file and write into the response
OutputStream os = response.getOutputStream();
byte[] buffer = new byte[1024];
int len;
while ((len = is.read(buffer)) != -1) {
os.write(buffer, 0, len);
}
os.flush();
os.close();
is.close();
}
Add GET method to this mapping: #RequestMapping(value = "api/report/file")

File download from the browser via DB has extra bytes added causing corruption messages from microsoft applications

<%
Document downloadFile = null;
String mimeType = null;
try{
downloadFile = new DocumentsDao().loadById(Long.parseLong(request.getParameter("id")));
// gets MIME type of the file
mimeType = downloadFile.getFileType();
if (mimeType == null) {
// set to binary type if MIME mapping not found
mimeType = "application/octet-stream";
}
System.out.println("MIME type: " + mimeType);
}catch (Exception e){
return;
}
// modifies response
response.reset();
response.resetBuffer();
response.setContentType(mimeType);
response.setContentLength((int) downloadFile.getDocumentData().length);
// forces download
String headerKey = "Content-Disposition";
String headerValue = String.format("attachment; filename=\"%s\"", downloadFile.getFileName());
response.setHeader(headerKey, headerValue);
// obtains response's output stream
OutputStream outStream = response.getOutputStream();
byte[] buffer = new byte[8192];
int bytesRead = -1;
System.out.println("### Length from db = "+downloadFile.getDocumentData().length);
ByteArrayInputStream inStream = new ByteArrayInputStream(downloadFile.getDocumentData());
while ((bytesRead = inStream.read(buffer)) != -1) {
outStream.write(buffer, 0, bytesRead);
}
inStream.close();
outStream.close();
response.flushBuffer();
return;
%>
The above code in a JSP produces a file to download which has an additional sequence of 0d0a x 4 at the end which causes the microsoft applications word and excel to complain and have to repair the file which has been downloaded.
I thought it might be the upload of the file but it was not, and retrieving from the database is fine. So the input stream is fine the problem occurs after the output stream is closed.
Errors you get are 'Word found unreadable content' 'Excel found unreadable content'
Has anyone seen this?
cheers
Charlie
The conversion of the JSP to servlet introduced the 0d0a X 4 characters. I used fiddle to find that the Apache Tomcat web server was altering the content length and sending the bytes. I looked at working apps at work and they all used servlets to do the job so I converted the above code to a servlet and it worked perfectly.
So don't use JSPs for this purpose.

Return file from Spring #Controller having OutputStream

I want to return a file from a Spring controller. I already have API that can give me any implementation of OutputStream and then I need to send it to a user.
So the flow is something like that:
getting outputstream -> service passes this outputstream to controller -> controller has to send it to a user
I think I need inputstream to do it and I have also found Apache Commons api feature that looks like this:
IOUtils.copy(InputStream is, OutputStream os)
but the problem is, it converts it to the other side -> not from os to is, but from is to os.
Edit
to be clear, because I see the answers are not hitting right thing:
I use Dropbox api and recieve file in OutputStream and I want this output stream to be sent to user while entering some URL
FileOutputStream outputStream = new FileOutputStream(); //can be any instance of OutputStream
DbxEntry.File downloadedFile = client.getFile("/fileName.mp3", null, outputStream);
Thats why i was talking about converting outputstream to inputstream, but have no idea how to do it. Furthermore, I suppose that there is better way to solve this (maybe return byte array somehow from outputstream)
I was trying to pass servlet outputstream [response.getOutputstream()] through parameter to the method that downloads file from dropbox, but it didnt work, at all
Edit 2
The "flow" of my app is something like this: #Joeblade
User enters url: /download/{file_name}
Spring Controller captures the url and calls the #Service layer to download the file and pass it to that controller:
#RequestMapping(value = "download/{name}", method = RequestMethod.GET)
public void getFileByName(#PathVariable("name") final String name, HttpServletResponse response) throws IOException {
response.setContentType("audio/mpeg3");
response.setHeader("Content-Disposition", "attachment; filename=" + name);
service.callSomeMethodAndRecieveDownloadedFileInSomeForm(name); // <- and this file(InputStream/OutputStream/byte[] array/File object/MultipartFile I dont really know..) has to be sent to the user
}
Now the #Service calls Dropbox API and downloads the file by specified file_name, and puts it all to the OutputStream, and then passes it (in some form.. maybe OutputStream, byte[] array or any other object - I dont know which is better to use) to the controller:
public SomeObjectThatContainsFileForExamplePipedInputStream callSomeMethodAndRecieveDownloadedFileInSomeForm(final String name) throws IOException {
//here any instance of OutputStream - it needs to be passed to client.getFile lower (for now it is PipedOutputStream)
PipedInputStream inputStream = new PipedInputStream(); // for now
PipedOutputStream outputStream = new PipedOutputStream(inputStream);
//some dropbox client object
DbxClient client = new DbxClient();
try {
//important part - Dropbox API downloads the file from Dropbox servers to the outputstream object passed as the third parameter
client.getFile("/" + name, null, outputStream);
} catch (DbxException e){
e.printStackTrace();
} finally {
outputStream.close();
}
return inputStream;
}
Controler recieves the file (I dont know, at all, in which form as I said upper) and passes it then to the user
So the thing is to recieve OutputStream with the downloaded file by calling dropboxClient.getFile() method and then this OutputStream that contains the downloaded file, has to be sent to the user, how to do this?
Get the OutputStream from the HttpServletResponse and write the file to it (in this example using IOUtils from Apache Commons)
#RequestMapping(value = "/download", method = RequestMethod.GET)
public void download(HttpServletResponse response) {
...
InputStream inputStream = new FileInputStream(new File(PATH_TO_FILE)); //load the file
IOUtils.copy(inputStream, response.getOutputStream());
response.flushBuffer();
...
}
Make sure you use a try/catch to close the streams in case of an exception.
The most preferable solution is to use InputStreamResource with ResponseEntity. All you need is set Content-Length manually:
#RequestMapping(value = "/download", method = RequestMethod.GET)
public ResponseEntity download() throws IOException {
String filePath = "PATH_HERE";
InputStream inputStream = new FileInputStream(new File(filePath));
InputStreamResource inputStreamResource = new InputStreamResource(inputStream);
HttpHeaders headers = new HttpHeaders();
headers.setContentLength(Files.size(Paths.get(filePath)));
return new ResponseEntity(inputStreamResource, headers, HttpStatus.OK);
}
You could use the ByteArrayOutputStream and ByteArrayInputStream. Example:
// A ByteArrayOutputStream holds the content in memory
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
// Do stuff with your OutputStream
// To convert it to a byte[] - simply use
final byte[] bytes = outputStream.toByteArray();
// To convert bytes to an InputStream, use a ByteArrayInputStream
ByteArrayInputStream inputStream = new ByteArrayInputStream(bytes);
You can do the same with other stream pairs. E.g. the file streams:
// Create a FileOutputStream
FileOutputStream fos = new FileOutputStream("filename.txt");
// Write contents to file
// Always close the stream, preferably in a try-with-resources block
fos.close();
// The, convert the file contents to an input stream
final InputStream fileInputStream = new FileInputStream("filename.txt");
And, when using Spring MVC you can definitely return a byte[] that contains your file. Just make sure that you annotate your response with #ResponseBody. Something like this:
#ResponseBody
#RequestMapping("/myurl/{filename:.*}")
public byte[] serveFile(#PathVariable("file"} String file) throws IOException {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
DbxEntry.File downloadedFile = client.getFile("/" + filename, null, outputStream);
return outputStream.toByteArray();
}
I recommend reading this answer
#ResponseBody
#RequestMapping("/photo2", method = RequestMethod.GET, produces = MediaType.IMAGE_JPEG_VALUE)
public byte[] testphoto() throws IOException {
InputStream in = servletContext.getResourceAsStream("/images/no_image.jpg");
return IOUtils.toByteArray(in);
}
answered by michal.kreuzman
I was going to write something similar myself but ofcourse it's already been answered.
If you want to just pass the stream instead of first getting everything in memory you could use this answer
I haven't tested this (not at work) but it looks legit :)
#RequestMapping(value = "report1", method = RequestMethod.GET, produces = "application/pdf")
#ResponseBody
public void getReport1(OutputStream out) {
InputStream in; // retrieve this from wherever you are receiving your stream
byte[] buffer = new byte[1024];
int len;
while ((len = in.read(buffer)) != -1) {
out.write(buffer, 0, len);
}
in.close();
out.flush(); // out.close?
}
The thing is, this is pretty much the same as IOUtils.copy / IOUtils.copyLarge does. line: 2128
Which you say copies the wrong direction.
However first make sure you understand what you ask. If you want to read from an outputstream(object for writing) and write to an input stream (object to read from) then I think what you really want is to write to an object that also supplies a read option.
for that you could use a PipedInputStream and PipedOutputStream. These are connected together so that bytes written to the outputstream are available to be read from the corresponding input stream.
so in the location where you are receiving the bytes I assume you are writing bytes to an outputstream.
there do this:
// set up the input/output stream so that bytes written to writeToHere are available to be read from readFromhere
PipedInputStream readFromHere = new PipedInputStream();
PipedOutputStream writeToHere = new PipedOutputStream(readFromHere);
// write to the outputstream as you like
writeToHere.write(...)
// or pass it as an outputstream to an external method
someMather(writeToHere);
// when you're done close this end.
writeToHere.close();
// then whenever you like, read from the inputstream
IOUtils.copy(readFromHere, out, new byte[1024]);
If you use IOUtils.copy it will continue to read until the outputstream is closed. so make sure that it is already closed before starting (if you run write/read on the same thread) or use another thread to write to the output buffer and close it at the end.
If this is still not what you're looking for then you'll have to refine your question.
The most memory-efficient solution in your case would be to pass the response OutputStream right to the Dropbox API:
#GetMapping(value = "download/{name}")
public void getFileByName(#PathVariable("name") final String name, HttpServletResponse response)
throws IOException, DbxException {
response.setContentType("audio/mpeg3");
response.setHeader(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + name + "\"");
response.setContentLength(filesize); // if you know size of the file in advance
new DbxClient().getFile("/" + name, null, response.getOutputStream());
}
Data read by the API will be sent directly to the user. No additional byte buffer of any type is required.
As for PipedInputStream/PipedOutputStream, they are intended for the blocking communication between 2 threads. PipedOutputStream blocks writing thread after 1024 bytes (by default) until some other thread start reading from the end of the pipe (PipedInputStream).
One thing to keep in mind when writing to the response outputstream is that it is a very good idea to call flush() on whatever writer that you've wrapped it with periodically. The reason for this is that a broken connection (for example caused by a user canceling a download) may not end up throwing an exception for a long time, if ever. This can effectively be a resource leak on your container.

How to set encoding to file for download

I have a method which start download of text file that is stored in oracle.
The column type is BLOB. I'm using this code below to init download, but I have no idea how set encode to this file when client downloaded it.
if (result.next()) {
String fileName0 = String.valueOf(result.getDate(columnData));
String fileName1 = String.valueOf(result.getInt(columnNumSolit));
String fileName2 = String.valueOf(result.getInt(columnNumComplto));
BLOB blob = ((OracleResultSet) result).getBLOB(columnFile);
InputStream inputStream = blob.getBinaryStream();
//int fileLength = inputStream.available();
int fileLength = blob.getChunkSize();
ServletContext context = getServlet().getServletContext();
// Set MIME to file.
String mimeType = context.getMimeType(fileName0+fileName1+fileName2+ext);
if (mimeType == null) {
mimeType = "application/octet-stream";
}
// header to response.
response.setContentType(mimeType);
response.setContentLength(fileLength);
String headerKey = "Content-Disposition";
String headerValue = String.format("attachment; filename=\"%s\"",fileName0+fileName1+fileName2+ext);
response.setHeader(headerKey, headerValue);
// write file to client.
OutputStream outStream = response.getOutputStream();
byte[] buffer = new byte[fileLength];
int bytesRead = -1;
while ((bytesRead = inputStream.read(buffer)) != -1) {
outStream.write(buffer, 0, bytesRead);
}
inputStream.close();
outStream.close();
tipoSolit = null;
}else {
//do something
}
What behavior are you trying to get the browser to do? Are you sending a video or mp3 or what? The mimetype is set by the headers you send before the file. If know of a website that has the behavior you are looking for, you can simply view what headers they send in chrome or in wireshark and then just use those in your own code.
One of the problems with file-type encoding is that you need to keep the encoding correct for each step that the bytes take. This means that if any step in the "upload->storage in db->fetching db->download" removes utf-8 encoding, you get the wrong answer. I wrote a post about my travels with UTF-8 and MySQL here: https://stackoverflow.com/a/14411280/836450 I would suggest sending a file that has a single utf-8 character, and then debugging the entire chain by hand.

Categories

Resources