GWT Download Excel .xlsx gives me a corrupted file - java

I'm working on a GWT application which gives every team in the company an overview about what they have to do.
The program is working, but now we want that the Excel table which you can download will be a .xlsx and not a .xls.
This whole project is new for me and I consider myself as a beginner in GWT.
In the code, when the filename is given for the Excel table, there is a +".xls" at the end. When I change it to +".xlsx" and test the application, the download still works. However, when I try to open the file in Excel, it shows me an error message and tells me the file is corrupted. (.xls works)
Can you explain to me how a download works in GWT with a serverSite generated Excel?
Maybe you have some ideas what causes the file to be corrupted
(sadly the programmer of this application is on holiday, so I cannot ask him)
public class Download extends HttpServlet {
private static final long serialVersionUID = 5580666921970339383L;
#Override
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doGet(request, response);
}
#Override
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
String filename = (String)request.getSession().getAttribute(CrossReportConstants.ATTR_FILENAME);
byte[] data = (byte[])request.getSession().getAttribute(CrossReportConstants.ATTR_REPORT);
request.getSession().setAttribute(CrossReportConstants.ATTR_FILENAME, null);
request.getSession().setAttribute(CrossReportConstants.ATTR_REPORT, null);
response.setContentType("application/vnd.ms-excel");
response.setHeader("Content-Disposition", "attachment;filename=" + filename);
response.setHeader("Pragma", "no-cache");
response.setHeader("Expires", "0");
response.setHeader("Cache-Control", "no-cache, no-store, must-revalidate");
response.setContentLength(data.length);
try {
InputStream in = new ByteArrayInputStream(data);
ServletOutputStream out = response.getOutputStream();
byte[] outputByte = new byte[4096];
// copy binary contect to output stream
while (in.read(outputByte, 0, 4096) != -1) {
out.write(outputByte, 0, 4096);
}
in.close();
out.flush();
out.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}

Now as you provided code you question can be easily answered:
The shown code defines a HttpServlet. Somewhere in your project there is a file called web.xml. In this file the class you showed is mapped to an url pattern, therefore you server knows that a specific url should be handled by this servlet.
The servlet you showed first extracts the file name and the file content out of the session. Additional the http response is prepared and the file content is written out. Now you just have to replace the content type of the response with the one for xlsx.
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
The browser which handles the http response now recognizes the download as a .xlsx file. The file extension does not really matter in this step, as you noticed.
When the original programmer of the servlet comes back from his hollidays, you should/could recommend him to use response.sendError() (with an appropriate http status code) instead of e.printStackTrace(). Then the user of the servlet can better understand if something do not work and who is to blame.

Related

Why does Chrome closes connection when REST API returns a large response?

I use this REST API
#GET
#Path("/get-file")
#Consumes(MediaType.APPLICATION_JSON + ";charset=UTF-8")
public void getFile(#Context HttpServletRequest request, #Context HttpServletResponse response,
#QueryParam("filePath") String filePath, #QueryParam("fileName") String fileName) throws IOException {fileName);
File fileToDownload = new File(filePath);
if (fileToDownload.exists()) {
java.nio.file.Path path = Paths.get(fileToDownload.toURI());
response.setContentType(MediaType.APPLICATION_OCTET_STREAM);
response.setHeader(HttpHeaders.CONTENT_DISPOSITION, "attachment;filename=" + fileName);
try {
response.setHeader(HttpHeaders.CONTENT_LENGTH, Long.toString(Files.size(path)));
Files.copy(path, response.getOutputStream());
response.flushBuffer();
} catch (IOException e) {
log.error(e.getLocalizedMessage(), e);
throw e;
}
} else {
throw new FileNotFoundException();
}
}
to download files generated from the server. It all works fine if the file i'm trying to get is less than 50GB (more or less), but when I try to download a 50GB while using Chrome, I get a java.net.SocketException: Connection reset exception. This doesn't happen in Firefox, and I can get the file as usual.
This is what I see in the Chrome Network Tab:
Downloading the file is the only operation I do and it the fails immediately.
If I omit the CONTENT_LENGTH header from the response, the size of the response reaches 48GB~ and then I get the same error.
Does anyone have any clue about this? On server side I use Java 7 (I tried with Java 8 too) and on client side I use AngularJS v1.5.11. I use ApacheTomcat to deploy my application. Let me know if you need more info about the problem.

Tomcat 8 Disable Chunked Encoding Filter

We have a VPN that we sometimes use to connect to sites remotely only to find that the chunked encoding tomcat seems to use often ends up with a lot of JavaScript files being truncated. In an attempt to remedy this I wanted to build a Tomcat filter for JS files that sends them in one shot.
By reading through the articles here and here I tried to take my own shot at this and tried implementing it as follows.
I hooked the filter into my context with a custom filter map of
new CustomFilterMap(
new String[]{"*.js"},
new String[]{"REQUEST"}
)
This part has actually worked well and my filter seems to be applied to JS files. Now the part I haven't gotten working so well is the actual filter.
public class NoChunkEncodedJSFilter extends OncePerRequestFilter {
#Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
val unversionedServletPath = request.getRequestURI().substring(request.getRequestURI().indexOf("/js"));
val requestFilePath = getServletContext().getRealPath(unversionedServletPath);
if (requestFilePath != null) {
File requestedFile = new File(requestFilePath);
if (requestedFile.exists()) {
val fileSize = Math.toIntExact(requestedFile.length());
val out = response.getWriter();
val wrappedResponse = new NonEncodedResponse(response, fileSize);
filterChain.doFilter(request, wrappedResponse);
out.write(wrappedResponse.toString(), "UTF-8");
out.close();
return;
}
}
filterChain.doFilter(request, response);
}
}
With my NonEncodedResponse:
public class NonEncodedResponse extends HttpServletResponseWrapper {
private ByteArrayOutputStream baos;
public String toString() {
try {
return baos.toString("UTF-8");
} catch (UnsupportedEncodingException unsuportedEncodingException) {
return baos.toString();
}
}
/**
* Constructs a response adaptor wrapping the given response.
*
* #param response The response to be wrapped
* #throws IllegalArgumentException if the response is null
*/
public NonEncodedResponse(HttpServletResponse response, Integer fileSize) {
super(response);
this.setContentLength(fileSize);
this.setBufferSize(fileSize);
super.setContentLength(fileSize);
super.setBufferSize(fileSize);
baos = new ByteArrayOutputStream(fileSize);
}
#Override
public PrintWriter getWriter(){
return new PrintWriter(baos);
}
}
Now, originally I tried not wrapping the response at all and just calling response.setBufferSize(fileSize); and response.setContentLength(fileSize); but this seemed to have 0 actual effects on my output and when I looked at the headers I was still using a chunked transfer encoding without a fixed Content-Length. (I also tried setting a custom header like this and didn't see it appended on my response either. I'm assuming the response that goes into the filter in it's base form is some form of read only.)
I also tried using my wrapper and bypassing its output stream by reading and sending the bytes straight from the file
val fileContents = Files.readAllBytes(Paths.get(requestFilePath));
out.write(new String(fileContents));
because it seemed like even though my attempts to fix the content-length had failed I was still only seeing parts of files being sent as the whole thing in my browsers network tab while trying to debug.
All in all now I have it serving files like this (probably not ideal) and even less ideally it still says Transfer-Encoding: chunked despite all my efforts to put a fixed Content-Length on my wrapper and original response. I can now put a custom header on my content, so I know it's running through my filter. It seems like it's still just chunk encoded for some reason.
Can someone please tell me what I'm doing wrong or if filters can even be used to disable chunked encoding to begin with (I believe I saw things suggesting they could from google searches, but the truth is I just don't know). I would happily welcome any and all advice on this issue I no longer have ideas to try and I certainly don't know what I'm doing.
So I actually managed to get this working by reverting to my original approach and ditching my wrapper entirely and moving the filterChain.doFilter to the end of my code block. I'm not really sure why this works compared to what I was doing because I honestly have to confess I don't know what filterChain.doFilter actually does at all. This was my final end result that got things working.
public class NoChunkEncodedJSFilter extends OncePerRequestFilter {
#Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
val requestFilePath = this.getRealFilePath(request.getRequestURI());
File requestedFile = new File(requestFilePath);
if (requestedFile.exists()) {
val fileSize = Math.toIntExact(requestedFile.length());
val fileContents = Files.readAllBytes(Paths.get(requestFilePath));
response.reset();
response.setHeader("Content-Length", String.valueOf(fileSize));
response.setContentLength(fileSize);
ServletOutputStream sos = response.getOutputStream();
sos.write(fileContents);
sos.close();
}
filterChain.doFilter(request, response);
}
private String getRealFilePath(String requestURI) {
val unversionedServletPath = requestURI.substring(requestURI.indexOf("/js"));
return getServletContext().getRealPath(unversionedServletPath);
}
}
Now my only remaining question is, is there a smarter way to get my buffer data for my file, or do I have to re-read the file from disk every time? I'm assuming somewhere in there my file has probably already been loaded into memory ready to be served. Here I am loading it into memory a second time and I imagine this isn't very performant.

Download a file from HTML Page Using Spring

I want to download a file from from the front end. The file is generated on TestFlow.generateReport
My runs until the end but it doesn't download anything. What am I missing here??
#RequestMapping(value = "/flow/generate-report" , method = RequestMethod.GET)
public #ResponseBody void generateFlowReport(#RequestParam("flowName") String flowName, HttpServletResponse response) {
InputStream resource = TestFlow.generateReport(flowName);
response.setContentType("application/force-download");
response.setHeader("Content-Disposition","attachment; filename=report-" + flowName + ".xlsx");
try {
IOUtils.copy(resource,response.getOutputStream());
response.flushBuffer();
resource.close();
} catch (IOException e) {
e.printStackTrace();
}
}
The original AJAX request can not handle file download request.
You must create a virtual form. see this question. And there are lots of jQuery plugin help you to do this.
And since its a download request, your controller method should not use #ResponseBody, take over the response all by yourself, close it after writing the content.

How to write to a txt file in Servlet and automatically download it when requests

I want to create a txt file in my Servlet and automatically download it at the client side when client requests. I have below code to write to a txt, but it gives access denied error in Netbeans IDE using glassfishserver. How can I do it?
//File creation
String strPath = "C:\\example.txt";
File strFile = new File(strPath);
boolean fileCreated = strFile.createNewFile();
//File appending
Writer objWriter = new BufferedWriter(new FileWriter(strFile));
objWriter.write("This is a test");
objWriter.flush();
objWriter.close();
Its not a thing you do it in JSP. You better have a Servlet and just create a Outputstream and put your text in it. Then flush that stream into the HttpServletResponse.
#WebServlet(urlPatterns = "/txt")
public class TextServlet extends HttpServlet {
#Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) {
response.setContentType("text/plain");
response.setHeader("Content-Disposition", "attachment; filename=\"example.txt\"");
try {
OutputStream outputStream = response.getOutputStream();
String outputResult = "This is Test";
outputStream.write(outputResult.getBytes());
outputStream.flush();
outputStream.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
Remember you need to set the content-type text/plain and a Content-Disposition header that mentions filename and tells broswer that it should be downloaded as file attachment.
This is what Content-Disposition header is about in concise description
In a regular HTTP response, the Content-Disposition response header is
a header indicating if the content is expected to be displayed inline
in the browser, that is, as a Web page or as part of a Web page, or as
an attachment, that is downloaded and saved locally.
If you are a beginner. You may like to learn more about from this
What is HTTP, Structure of HTTP Request and Response?
How a Servlet Application works
Difference Between Servlet and JSP

write the file and download the file same as of attachments

i have a requirement where i get byte array (byte[]) data from database, i need to save this data in a file and allow the user to save where ever he want to save, same as downloading the attachments.The file name and extension also i'm retrieving from database. I'm using java,spring-mvc for this.
Below is the code:
spring controller:
#RequestMapping(value="/getFile", method = RequestMethod.GET)
public ModelAndView getFile(HttpServletRequest request, HttpServletResponse response) {
ModelAndView mav = new ModelAndView();
//logic to get the data from database
byte[] documentData = document.getDOCUMENTData();
String documentName = document.getDOCUMENTTITLE();
String documentExt = document.getDocumentExtension();
}
Please suggest, i know that using java.io.*, i can write the byte[] data in file and give file name and extension by taking the values declared above, but how can i allow users when clicked on "download file" icon to write the data and save that file where ever he wants same as downloading the attachment.Please suggest. Once user clicks on download file icon control comes to above controller.Thanks.
--EDIT--
Modified code:
#RequestMapping(value="/getFile", method = RequestMethod.GET)
public ModelAndView getFile(HttpServletRequest request, HttpServletResponse response) {
ModelAndView mav = new ModelAndView();
//logic to get the data from database
byte[] documentData = document.getDOCUMENTData();
String documentName = document.getDOCUMENTTITLE();
String documentExt = document.getDocumentExtension();
response.setHeader("Content-Disposition", "attachment;filename="+userDoc.getDOCUMENT_TITLE());
long l = userDoc.getDOCUMENT_SIZE();
int size = (int)l;
response.setContentLength(size);
response.getWriter().write("hello");//i need to write byte[] but for test i kept string.
}
I want user to see save window so that he can save where ever he want same as downloading the attachments from mail.Thanks.
This is a code I'm usign for the same request
HTML page:
<h:commandButton value="Click Here to Download" action="#{reportBean.download}" />
BEAN:
public void download(){
HttpServletResponse response = (HttpServletResponse) FacesContext.getCurrentInstance().getExternalContext().getResponse();
response.setHeader("Content-Disposition", "attachment;filename="+file.getName());
response.setContentLength((int) file.length());
ServletOutputStream out = null;
try {
FileInputStream input = new FileInputStream(file);
byte[] buffer = new byte[1024];
out = response.getOutputStream();
int i = 0;
while ((i = input.read(buffer)) != -1) {
out.write(buffer);
out.flush();
}
FacesContext.getCurrentInstance().getResponseComplete();
input.close();
} catch (IOException err) {
err.printStackTrace();
} finally {
try {
if (out != null) {
out.close();
}
} catch (IOException err) {
err.printStackTrace();
}
}
}
Just make sure you have a File object named file
You have two options to achieve this:
One is to write the file in your server's local filesystem in an internet accesible folder. You can configure which of your server's local folders are accesible from internet in your Apache/IIS serttings. Then you update your HTML so your "download file" link points to that file through an URL.
The other option is, like #an3sarmiento did, to return the file as a byte[] stream to the browser. For this option to work, you have to send, along with the file content, a response header in which you tell the browser you are returning a downloadable file as a stream. You do that with the line:
response.setHeader("Content-Disposition", "attachment;filename="+[your file name]);
response.setContentLength([your file's length or bytes count]);
response.getWriter.write([your file's content as byte array]);
In the line above I assume you are working with Java Servlets and you have an HttpServletResponse variable named reponse, which you will respond to the browser's HTTP POST or GET request.

Categories