spring-mvc download action - java

Is there some trick to getting a SpringMVC custom view to cause a file download in the broswer? I've implemented the render method from org.springframework.web.servlet.View but the code results in my data being written to the page as a blob of data rather having a download action start.
try {
Document oDoc = (Document) model.get("oDoc");
out = new PrintWriter(response.getOutputStream());
response.setContentType("application/vnd.ms-excel");
response.setHeader("content-disposition", "attachment; filename=file.xls");
GenerateXLSFile gof = new GenerateXLSFile();
gof.outputTSVFromDom(out, oDoc);
} catch block here {
//writes to log here
} finally {
if (out != null) {
out.flush();
out.close();
}
}
I know the render method is being called from the server logs. I know the GenerateXLSFile is being created from the server logs. I know the outputTSVFromDom can take a document and transform it work from my JUnit test. It also writes to the server log and completes. The data ends up in the browser. The HTTP Headers looks normal according to firebug. No errors from the catch block are in the server log.
What am I missing here?

First of all, which API are you using? Excel documents are binary, so you should use OutputStream, not Writer.
Second, Spring has a built in support for serving Excel documents:
AbstractExcelView, based on the Apache POI API
AbstractJExcelView, based on the JExcel API

Related

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);

how to write a file object on server response and without saving file on server?

I am using Spring with DWR . I want to return a file object as response , however I save the file (to be sent) at server temporary location and then send its location as href for anchor tag on client side , however I wonder if there could be a way to throw the file directly to browser on response object without saving it temporarily on server.
I expected if there could be a way to send file as a response via DWR.
public ModelAndView writeFileContentInResponse(HttpServletRequest request, HttpServletResponse response) throws IOException {
FileInputStream inputStream = new FileInputStream("FileInputStreamDemo.java"); //read the file
response.setHeader("Content-Disposition","attachment; filename=test.txt");
try {
int c;
while ((c = inputStream.read()) != -1) {
response.getWriter().write(c);
}
} finally {
if (inputStream != null)
inputStream.close();
response.getWriter().close();
}
}
It has been years since I've used Spring, and I'm unfamiliar with DWR, but the essence of your question is basic to the web.
The answer is yes, you can. In effect, you need to set the HTTP header Content-Disposition: attachment, then stream down the contents. All of this will be in the response to the original request (as opposed to sending back a link).
The actual code to achieve this will depend on your circumstances, but this should get you started.
you call the method from Java Script, right? I didn't really understand how Spring is related in this flow, but as far as I know DWR allows you to produce Java Script Stubs and call the Java methods of the exposed bean directly on server right from your java script client code.
You can read the file byte-by-byte and return it from your java method as long as it really returns a byte array.
However what would you do with this byte array on client?
I just think in this specific flow you shouldn't use the DWR but rather issue an ordinar AJAX request (if DWR can wrap it somehow for convenience - great). This request shouldn't come to DWRServlet, but rather be proceeded by a regular servlet/some web-based framework, like Spring MVC :)
Once the request comes to the servlet, use
response.setHeader("Content-Disposition","attachment; filename=test.txt");
as was already stated.
Hope this helps,
Good luck!
Mark
An example which return a excel to download from client:
//Java side:
public FileTransfer getExcel(Parametros param){
byte[] result = <here get data>;
InputStream myInputStream = new ByteArrayInputStream(result);
String excelFormat = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
FileTransfer dwrExcelFile = new FileTransfer("excel.xlsx", excelFormat, myInputStream);
return dwrExcelFile;
}
//Javascript side:
function downloadExcelFile() {
dwr.engine.setTimeout(59000);
var params = <params_to_send>;
<Java_class>.getExcel(params, {callback:function(dataFromServer) {
downloadExcelCallback(dataFromServer);
}});
}
function downloadExcelCallback(data) {
dwr.engine.openInDownload(data);
}

After form-submit, download file

Im using struts2 and also servlets. (Due to a 3rd party ajax thing that ships with servlets).
One of my forms is posting to a servlet. (Name "/exclude/new.srl")
I've set struts2 up to ignore all requests to the "exclude" namespace.
So the request is reaching the servlet just fine.
The servlet does its job, and then goes on to do the following before ending:
response.getOutputStream().print("'OK'");
response.getOutputStream().close();
Now i dont know the 3rd party software in detail, so im not exactly sure, but i think the OK statement tells my 3rd party ajax-solution to close the form and refresh some contents of the page.
This all works fine.
Now however i am trying to add a new bit to this. I would like send a file to the user. In other words, when the form is submitted, everything that used to happen should still happen, but also the user should be asked to save or open a file.
So I have created a struts2 action that will return the file, no problem.
But how can i program the servlet to push this file to the user AND ALSO return response OK?
I do not need to use the struts2 action to push the file, if it can be done from within the servlet that is perfectly acceptable.
Anyone?
You can send the file
// Set the headers.
res.setContentType("application/x-download");
res.setHeader("Content-Disposition", "attachment; filename=" + filename);
// Send the file.
OutputStream out = res.getOutputStream();
File file = new File(yourPath);
response.setContentLength((int) file.length());
BufferedInputStream buf = new BufferedInputStream(new FileInputStream(file));
int readBytes = 0;
while ((readBytes = buf.read()) != -1)
stream.write(readBytes);
buf.close();
stream.close();
But how can i program the servlet to push this file to the user AND ALSO return response OK?
This I don't think is possible. The response from a servlet has a tyle, denoted by the content-type. If you set it to a download, then you cannot send a normal response, meaning you cannot send the "OK".
You will probably have to setup your unmentioned 3rd party ajax-solution to forward to the file action/servlet URL on form submission success (ie redirect to file download after form was submitted ok).

how read a remote file with GWT

I need to read a file located on a server but I see that in GWT is not possible use some java library.
what I have to do?
try requestBuilder!! this code can help?
RequestBuilder requestBuilder = new RequestBuilder( RequestBuilder.GET, "yourfile.txt" );
try {
requestBuilder.sendRequest( null, new RequestCallback(){
public void onError(Request request, Throwable exception) {
GWT.log( "failed file reading", exception );
}
public void onResponseReceived(Request request, Response response) {
String result=response.getText();
}} );
} catch (RequestException e) {
GWT.log( "failed file reading", e );
}
The Rule: JavaScript cannot read data from a URL that doesn’t have a host name and port that matches those of the page the JavaScript is running in.
In other words: If it is on a different site — you can’t read it directly with JS and therefore GWT, which is nothing more than Javascript once compiled.
It applies to data from XMLHttpRequest, frames, and anything else you care to name.
This may change in the future, but for now the rule stands.
With this in mind there are a couple of workarounds.
1) Call your server with RPC or whatever mechanism and have your server do the request and then send it back to the client. Here is a sample.
2) There are several hacks on allowing JavaScript to access cross-domain sites just do a google search on how to get this. Some browsers will flag this as being dangerous.
3) If you are using Firefox and Firefox only it looks like Firefox has the ability to do this, but you will need to enable this manually.
Simply write first a servlet that sends the file located on the server to the user.
Then when the user clicks on a button for instance you call the servlet with the proper parameter.
Here is an excerpt from our servlet implementation
response.reset();
response.setContentType("application/octet-stream");
response.setContentLength(contentLength);
response.setHeader("Content-disposition", "attachment;
filename=\"" + filename + "\"");
output = new
BufferedOutputStream(response.getOutputStream());
int data = input.read();
while (data != -1)
{
output.write(data);
data = input.read();
}
output.flush();

Categories

Resources