send excel file to client using java servlets - java

I am using Apache POI for generating Excel file in Java Servlets.
getExcel() function returns HSSFWorkbook, which I want to send to the client.
HSSFWorkbook wb = getExcel();
This is what I have tried so far.
//block1
ByteArrayOutputStream outByteStream = new ByteArrayOutputStream();
wb.write(outByteStream);
byte [] outArray = outByteStream.toByteArray();
response.setContentType("application/ms-excel");
response.setContentLength(outArray.length);
response.setHeader("Expires:", "0");
response.setHeader("Content-Disposition", "attachment; filename=Demo1.xls");
OutputStream outStream = response.getOutputStream();
outStream.write(outArray);
outStream.flush();
//block2
request.setAttribute("Message", str1);
request.setAttribute("MessageDetails", str2);
request.getRequestDispatcher("/MyFile.jsp").forward(request, response);
Above code sends excel file to the client, but gives me exception:
java.lang.IllegalStateException: Cannot forward after response has been committed
If I remove the block1 or block2 from above code then it will not give error, but I want to send client Excel file and two attributes which I have added to request object.
So can send Excel file to client using request.getRequestDispatcher ? Or is there any better way for doing this?
Any suggestion will be appreciated.
Edit1
I know why I am getting the IllegalStateException, but then my question is how should I send ExcelFile and Request Attributes both to the client?
Edit2
The Reason why I want to send both Excel file and Attributes to the client is that MyFile.jsp has a <div> which will show message send from servlet.
<div style="background-color: aliceblue">
<h3>${Message}</h3>
</div>
Edit3
The Reason why I want to send message to client is that I am sending this Excel file as an response to Import Excel operation in which client will provide excel file for inserting data in database, and then I am highlighting the excel rows which cannot be inserted due to duplication or any other reasons. So I want to show Import statistics in the Message to client and give him copy of excel file with error rows highlighted.

You are flushing your response and then trying to forward. Container has already sent the response back to the client and now is in a dilemma as to how to forward the request to another JSP, hence it aborts the operation mid way throwing an Exception. HTTP is a request-response model . Once you request , you get back a response . But once the response is already committed the whole transaction is over.
outStream.write(outArray);
// you already committed the response here by flushing the output stream
outStream.flush();
//block2
request.setAttribute("Message", str1);
request.setAttribute("MessageDetails", str2);
// this is illegal after you have already flushed the response
request.getRequestDispatcher("/MyFile.jsp").forward(request, response);
As per the Javadoc:
IllegalStateException - if the response was already committed.
After EDIT1:
No you cannot do both . You need to decide what you want. Write the bytes to the response setting proper HEADERS and MIME-TYPE. You cannot get the browser download something as well as show a JSP page from the same response.

Related

Servlet error: Cannot call sendRedirect() after the response has been committed

I want to redirect to a page after writing the excel file. The servlet code is given below:
ByteArrayOutputStream outByteStream = new ByteArrayOutputStream();
workbook.write(outByteStream);
byte [] outArray = outByteStream.toByteArray();
response.setContentType("application/ms-excel");
response.setContentLength(outArray.length);
response.setHeader("Content-Disposition", "attachment; filename=name_"+date+".xlsx");
response.setIntHeader("Refresh", 1);
OutputStream outStream = response.getOutputStream();
outStream.write(outArray);
response.sendRedirect("url/reports.jsp");
This code downloads an Excel file which i have created.
when i call the above servlet, the excel file is being downloaded but it is throwing following exception in the last line :
Servlet Error: ::java.lang.IllegalStateException: Cannot call sendRedirect() after the response has been committed
Hence i am unable to redirect to a new page. what can i do to access the response object after i write the output in "outStream"
The basic problem is that this ...
I want to redirect to a page after writing the excel file.
... describes two separate responses. The server cannot chain them together by itself because the client will expect only one response to each request. Because two requests are required to elicit two responses, automation of this sequence will require client-side scripting.
Personally, I would probably put the script on the front end: a handler on the appropriate button or link that first downloads the file and then (on success) issues a request for the new page. It would also be possible to do as suggested in comments, however: put script in the new page that downloads the file.
You cannot have a body with a redirect because the browser, when receiving a redirect, will issue a second request to the URL it has found (in header Location), and it's the response of that second request that is displayed, unless it is also a redirect, in which case, it will issue a third request, and so on...

Java Spring - dynamically generated csv file download response is hanging

On my company's site we have some tables that we need to export to a csv file.
There are some varying parameters, so the csv file needs to be dynamically created on request.
My problem is that after clicking to download, the response hangs, and waits for the whole file to be created (which can take some time) and only then downloads the entire file in one instant.
I'm using AngularJS, so I'm using window.location = <url_for_file_download> In order to make the browser download the file.
On the server side I'm using Java Spring and I've followed all the instructions I could find on the web in order to create a file download controller.
My controller code is something like this:
#RequestMapping(value = "http://yada.yada.yada/csv/myFile.csv", method = RequestMethod.GET)
public #ResponseBody
void getCustomers(HttpServletResponse response,
#RequestParam(required = false) String someParameters)
throws NotAuthorizedException, IOException {
// set headers
setHeaders(response);
// generate writer
CSVWriter write = generateWriter(response);
// get data
List<String[]> data = getData();
// write and flush and all that
.
.
.
}
My code for setting the response headers are:
response.setContentType("text/csv;charset=utf-8");
response.setHeader("Content-Disposition","attachment; filename=\"" + fileName + ".csv\"");
I've also tried adding the following headers:
response.setHeader("Transfer-Encoding", "Chunked");
response.setHeader("Content-Description", "File Transfer");
and I've also tried setting the Content-type to "application/octet-stream".
Notice that I don't add a Content-length header, since the file doesn't exist yet, and is being written on the fly.
For writing the csv file I'm using OpenCSV and my code is as follows:
OutputStream resOs = response.getOutputStream();
OutputStream buffOs = new BufferedOutputStream(resOs);
OutputStreamWriter outputWriter = new OutputStreamWriter(buffOs,"UTF-8");
CSVWriter writer = new CSVWriter(outputWriter);
I iterate over the data and write it like so:
for (String[] row: data) {
writer.writeNext(line);
}
(It's not exactly the code - but this is more or else what happens in the code)
And at the end I flush and close:
writer.flush();
writer.close();
I also tried flushing after each line I write.
So why isn't the file being transferred before it has all been written?
Why is my browser (Google chrome) downloading the file in one instant after waiting a long time? And how can I fix this.
I hope I've added enough code, if there's something missing just please tell me and I'll try to add it here.
Thank you so much in advance.
Can you try returning a null value in your java
return null ;
Or you can try below code also
1. Jquery code upon clicking the submit button
$(document).ready( function() {
$('#buttonName').click(function(e){
$("#formName").submit();
//alert("The file ready to be downloaded");
});
});
Your controller code
#RequestMapping(value="/name",method=RequestMethod.POST)
public ModelAndView downloadCSV(ModelMap model,HttpSession session,#ModelAttribute(value="Pojo") Pojo pojo
,HttpServletRequest request,HttpServletResponse response){
----------------some code----------------
response.setContentType("application/csv");
("application/unknown");
response.setHeader("content-disposition","attachment;filename =filename.csv");
ServletOutputStream writer = response.getOutputStream();
logger.info("downloading contents to csv");
writer.print("A");
writer.print(',');
writer.println("B");
for(int i=0;i<limit;i++){
writer.print(""+pojo.get(i).getA());
writer.print(',');
writer.print(pojo.get(i).getB());
writer.println();
}
writer.flush();
writer.close();
---------------some code-----------
return null;
}
Hope this helps
The Controller will wait for the response to be written before the response is send back to the client.
Here is a nice post with multiple approaches / options outlined
Downloading a file from spring controllers
This post talks about flushing the output periodically to help fasten the download.
how to download large files without memory issues in java
If all you are trying to do is let the user know that the file download is in progress and due soon, I think an Ajax progress status indicaor might be your solution.
Trigger the ajax call to the back-end to generate the file
Show progress indicator to the user while file is being generated server side
once response is available, file is presented to the user.
I think something similar is being explored here download file with ajax() POST Request via Spring MVC
Hope this helps!
Thanks,
Paul
I faced the same issue. The code that didn't work for me was
#RequestMapping(value = "/test")
public void test(HttpServletResponse response)
throws IOException, InterruptedException {
response.getOutputStream().println("Hello");
response.getOutputStream().flush();
Thread.sleep(2000);
response.getOutputStream().println("How");
response.getOutputStream().flush();
Thread.sleep(2000);
response.getOutputStream().println("are");
response.getOutputStream().flush();
Thread.sleep(2000);
response.getOutputStream().println("you");
response.getOutputStream().flush();
}
The culprit was ShallowEtagHeaderFilter. When this filter is enabled the response is sent in one chunk. When this filter is diabled the response is send in multiple chunks.
From this thread Tomcat does not flush the response buffer it looks like another possible culprit can be GzipFilter

Sending an error response after servlet response has been written to

I am writing a data transfer application using a servlet and would like to be able to send an error response if a problem occurs after the servlet response has been written to. Is that possible?
My issue is that I will be sending large compressed csv files that are created from data read from a database. Everything is done with streams so it is possible that an error could occur in the creation of the csv file after the servlet response has been written to. I have seen it happen.
I've noticed that this is only a problem after the servlet OutputStream has been flushed. If it has not been flushed I can send an error response but not after. Since I am dealing with large amounts of data it is not feasible to send everything in one go.
I am writing a data transfer application using a servlet and would like to be able to send an error response if a problem occurs after the servlet response has been written to. Is that possible?
Not from the server side on. The server cannot take the already flushed bytes back from the client. This is a point of no return. I assume that this concerns a different exception than IOException on the response's Writer or OutputStream.
If it were HTML (even though this is a poor practice; HTML belongs in JSP), you could print some JS code which forces a location change like so:
try {
writer.write(someHtml);
} catch (SomeException e) {
writer.write("<script>window.location = 'error.jsp';</script>");
// ...
}
But this is not possible in non-HTML responses. You'd really need to buffer the entire response in memory or on (temp) disk beforehand. If buffering went flawlessly, then you can pipe it to the response again.
try {
processAndSaveInMemoryOrTempDiskFile(someData, byteArrayOrFileLocation);
} catch (SomeException e) {
throw new ServletException(e, "Processing some data failed.");
}
copyFromMemoryOrTempDiskToResponse(byteArrayOrFileLocation, writer);

Problem occured when using OutputStream & (DataOutPutStream or PrintWriter)

I wrote a simple server using java socket programming and intended to make that offered 2 files for download and display some html response when the download finished. What I did is use PrintWriter.print or DataOutPutStream.writeBytes to send the string including html tags and response string to the browser, then use OutputStream.write to send the file requested. The URL I typed in the browser was like 127.0.0.1/test1.zip, relevant code fragments as following:
pout.print("<html>");
pout.print("<head>");
pout.print("<meta http-equiv=\"Content-Type\" content=\"text/html; charset=ISO-8859-1/\">");
pout.print("<title>Response</title>");
pout.print("</head>");
pout.print("<body>");
pout.print(createResponseHeader(200, fileTypeCode));
pout.print("</body>");
pout.print("</html>");
pout.print(createResponseHeader(200, fileTypeCode));
pout.flush();
byte[] buffer = new byte[client.getSendBufferSize()];
int bytesRead = 0;
System.out.println("Sending...");
while((bytesRead = requestedFile.read(buffer))>-1)
{
out.write(buffer,0,bytesRead);
}
The pout is a PrintWriter while out is OutputStream.
The problem is when I try to use 127.0.0.1/test2.zip to download the file, it doesn't let me download, instead, print out the response string and a lot of non-sense character in the browser, e.g.
HTTP/1.0 200 OK
Connection: close
Server: COMP5116 Assignment Server v0
Content-Type: application/x-zip-compressed
PK‹â:Lmá^ЛàÍ test2.wmvì[y<”Ûÿ?3ÃØ—Ab¸eeË’5K"»±f_B*à Å*YÛ•¥M5h±¯u[(\·(-÷F)ß3ÏɽݺÝ×ýýñ{Íg^ÏûyžóYÏçœçyÎç¼P’>™îÝ+½Žö6A€;;ýmüH»êt©k]R#*€.G‰µÅRÏøÍLÔóZ; ´£åÑvP¹æª#õó”æÇ„‹&‡ëî9q‰Ú>LkÇÈyÖ2qãÌÆ(ãDŸã©ïÍš]Ð4iIJ0Àª3]B€ðÀ¸CôÁ`ä è1ü½¤Ã¬$ pBi
I believe it simply display the zip file as string with the response header all together. It seems once the PrintWriter is used before the code of sending the file, the whole output stream is used for sending string instead of bytes. However, if I put the part of code of sending the response AFTER the code of sending file, the download works properly but no any response message print out in the browser, just a blank page.
You've to remove your HTML code from here and send only the binary data. You can't mix them in a single servlet.
To achieve what you want to do is not easy.
I would start the download with some JavaScript code in the page, then the page will poll with Ajax for a server side servlet that will know if the download is completed for that particular session. In fact there is no download completed event in JavaScript.
To have this information the download servlet will update the session with a flag when download is completed.
When your Ajax call will return that the download is completed, you can change the text in the page or redirect to a new page.
Edit: Alternatively, if you can change your requirements, it will be much easier to show all messages that you have to show just before the download, and put target="_blank" in the download link so your page is not lost by clicking on the link.

Java Servlet and HTTP Response object

Question on HttpResponse object in servlets. Can the contents of a HttpResponse be only read once?
If so do I need to user a filter and some form of "javax.servlet.http.HttpServletResponseWrapper" in order to read the content of a HttpResponse object as I need to read its content to retrieve XML/JSON from the response? At the moment Im getting the below exception when I go to read the HttpResponse object.
Content has been consumed
at org.apache.http.entity.BasicHttpEntity.getContent(BasicHttpEntity.java:84)
Thanks,
John
This is not a problem in the server/servlet side. It's a problem in the client side. The servlet doesn't send HttpServletResponse object to the client or something, it just sends a byte stream only once. You just need to read it only once into a reuseable object such as a byte[] or String, depending on the actual content and and then reuse/copy exactly this object in the remnant of the code.
InputStream input = httpResponse.getEntity().getContent();
ByteArrayOutputStream output = new ByteArrayOutputStream(); // Or some file?
IOUtils.copy(input, output);
byte[] content = output.toByteArray();
// Now you can reuse content as many times as you want.
Do you want to read the content of the response or request? Usually we write the content of the response and do not read it, unless you have an special case here.

Categories

Resources