JSP gzip output stream - java

I know I can gzip the output stream by using something like..
OutputStream outA = response.getOutputStream();
outWriter = new PrintWriter(new GZIPOutputStream(outA), false);
response.setHeader("Content-Encoding", "gzip");
outWriter.println(.....);
outWriter.close();
in a JSP, but is it possible to write it as:
OutputStream outA = response.getOutputStream();
outWriter = new PrintWriter(new GZIPOutputStream(outA), false);
response.setHeader("Content-Encoding", "gzip");
%>
...
I know this is done in PHP for example by capturing the output buffer before it is flushed, gzipping the buffer, and then finally writing it.
But is it possible in a JSP?

This Java code doesn't belong in a JSP.
If your intent is to gzip the HTML code generated by JSP, then you need to configure it at appserver level. In JBoss (and Tomcat) you need to set the compression attribute of the <Connector> element in /server.xml to on.
<Connector compression="on">
That's all. It'll be by default applied on all text/* responses (HTML/CSS/JS).
See also:
Tomcat 6.0 HTTP Connector configuration reference
Web application performance tips and tricks

Related

Java servlet method call squence lead to different result

I use SpringBoot 2.1.1 with Java 11,when i write a http interface, i need to return a excel file to client, and i fill the file with Java code, then use HttpServletResponse.getOutputStream().write() to send the file.The puzzle thing is first call HttpServletResponse.getOutputStream().write() then call HttpServletResponse.setContentType(), the client cannot accept the content-type. where is the issue?
SpringBoot 2.1.1 with Java 11.
case 1:
byte[] errorFile = new byte[0];
// fill errorFile...
ServletOutputStream out = response.getOutputStream();
out.write(errorFile);
response.setHeader("Error-File", "True");
response.setContentType("application/vnd.ms-excel");
In this call sequence, if errorFile has a short content, it will be fine, server will write content-type and the header(Error-File) to clients, but if the errorFile has a long content, clients cannot get the content-type and the header(Error-File).
case 2:
byte[] errorFile = new byte[0];
// fill errorFile...
response.setHeader("Error-File", "True");
response.setContentType("application/vnd.ms-excel");
ServletOutputStream out = response.getOutputStream();
out.write(errorFile);
If i use this call sequence, it will be fine in any length of errorFile.
The issue is that an HTTP response contains
the status,
then the headers,
then the body.
So if you first write the body, it's too late to write the headers.

is it possible to cache pdf file in web page?

I have a jsp page that is used to download pdf file from server. When I request the jsp page from browser, the jsp read file info from database, get inputstream from file and write to ServletOutputStream.
The pseudo-code like this:
response.setContentType("application/pdf");
response.setHeader("Content-disposition","inline;filename=URLEncoder.encode(filename)");
response.setHeader("Cache-Control","max-age=3600");
ServletOutputStream os = response.getOutputStream();
FileInputStream in = new FileInputStream(new File(filePath));
int size = 0;
byte[] buffer = new byte[512];
while((size=in.read(buffer))!=-1){
os.write(buffer,0,size);
}
when the request finished, the downloaded pdf was opened in browser. The same file is downloaded many times, so we want to cache them to the local file system, so that each request's return status code is 304(not modified), but the cache-control doesn't work, is there anybody can help?
This problem is typically solved by a reverse proxy like NGINX or Varnish in front of your application. Alternatively you can use a CDN.

HTTP post body lost spuriously (HttpURLConnection & Jetty)

I have a home grown protocol which uses HttpURLConnection (from Java 1.6) & Jetty (6.1.26) to POST a block of xml as a request and receive a block of xml as a response. The amounts of xml are approx. 5KB.
When running both sender and receiver on Linux EC2 instances in different parts of the world I'm finding that in about 0.04% of my requests the Jetty handler sees the xml request (the post body) as an empty string. I've checked and the client outputs that it's consistently trying to send the correct (> 0 length) xml request string.
I have also reproduced this by looping my JUnit tests on my local (Win 8) box.
I assume the error must be something like:
Misuse of buffers
An HttpURLConnection bug
A network error
A Jetty bug
A random head slapping stupid thing I've done in the code
The relevant code is below:
CLIENT
connection = (HttpURLConnection) (new URL (url)).openConnection();
connection.setReadTimeout(readTimeoutMS);
connection.setConnectTimeout(connectTimeoutMS);
connection.setRequestMethod("POST");
connection.setAllowUserInteraction(false);
connection.setDoOutput(true);
// Send request
byte[] postBytes = requestXML.getBytes("UTF-8");
connection.setRequestProperty("Content-length", "" + postBytes.length);
OutputStream os = connection.getOutputStream();
os.write(postBytes);
os.flush();
os.close();
// Read response
InputStream is = connection.getInputStream();
StringWriter writer = new StringWriter();
IOUtils.copy(is, writer, "UTF-8");
is.close();
connection.disconnect();
return writer.toString();
SERVER (Jetty handler)
public void handle(java.lang.String target, javax.servlet.http.HttpServletRequest request, javax.servlet.http.HttpServletResponse response, int dispatch) {
InputStream is = request.getInputStream();
StringWriter writer = new StringWriter();
IOUtils.copy(is, writer, "UTF-8");
is.close();
String requestXML = writer.toString();
// requestXML is 0 length string about 0.04% of time
Can anyone think of why I'd randomly get the request as an empty string?
Thanks!
EDIT
I introduced some more trace and getContentLength() returns -1 when the error occurs, but the client output still shows it's sending the right amount of bytes.
I can't think of why you are getting a empty string. Code looks correct. If you update you code to check for empty string and if found report the content-length and transfer-encoding of the request, that would be helpful to identify the culprit. A wireshark trace of the network data would also be good.
But the bad new is that jetty-6 is really end of life, and we are unlikely to be updating it. If you are writing the code today, then you really should be using jetty-7 or 8. Perhaps even jetty-9 milestone release if you are brave. If you find such and error in jetty-9, I'd be all over it like a rash trying to fix it for you!
Make sure you set connection.setRequestProperty("Content-Type", "application/xml"); It's possible POST data may be discarded without some Content-type. This was the case when I replicated your problem locally (against a Grails embedded Tomcat instance), and supplying this fixed it.

How to force browser to download file?

Everything works fine, but only if file is small, about 1MB, when I tried it with bigger files, like 20MB my browser display it, instead of force to download, I tried many headers so far, now my code looks:
PrintWriter out = response.getWriter();
String fileName = request.getParameter("filename");
File f= new File(fileName);
InputStream in = new FileInputStream(f);
BufferedInputStream bin = new BufferedInputStream(in);
DataInputStream din = new DataInputStream(bin);
while(din.available() > 0){
out.print(din.readLine());
out.print("\n");
}
response.setContentType("application/force-download");
response.setContentLength((int)f.length());
response.setHeader("Content-Transfer-Encoding", "binary");
response.setHeader("Content-Disposition","attachment; filename=\"" + "xxx\"");//fileName);
in.close();
bin.close();
din.close();
You are setting the response headers after writing the contents of the file to the output stream. This is quite late in the response lifecycle to be setting headers. The correct sequence of operations should be to set the headers first, and then write the contents of the file to the servlet's outputstream.
Therefore, your method should be written as follows (this won't compile as it is a mere representation):
response.setContentType("application/force-download");
response.setContentLength((int)f.length());
//response.setContentLength(-1);
response.setHeader("Content-Transfer-Encoding", "binary");
response.setHeader("Content-Disposition","attachment; filename=\"" + "xxx\"");//fileName);
...
...
File f= new File(fileName);
InputStream in = new FileInputStream(f);
BufferedInputStream bin = new BufferedInputStream(in);
DataInputStream din = new DataInputStream(bin);
while(din.available() > 0){
out.print(din.readLine());
out.print("\n");
}
The reason for the failure is that it is possible for the actual headers sent by the servlet would be different from what you are intending to send. After all, if the servlet container does not know what headers (which appear before the body in the HTTP response), then it may set appropriate headers to ensure that the response is valid; setting the headers after the file has been written is therefore futile and redundant as the container might have already set the headers. You could confirm this by looking at the network traffic using Wireshark or a HTTP debugging proxy like Fiddler or WebScarab.
You may also refer to the Java EE API documentation for ServletResponse.setContentType to understand this behavior:
Sets the content type of the response being sent to the client, if the response has not been committed yet. The given content type may include a character encoding specification, for example, text/html;charset=UTF-8. The response's character encoding is only set from the given content type if this method is called before getWriter is called.
This method may be called repeatedly to change content type and character encoding. This method has no effect if called after the response has been committed.
...
Set content-type and other headers before you write the file out. For small files the content is buffered, and the browser gets the headers first. For big ones the data come first.
This is from a php script which solves the problem perfectly with every browser I've tested (FF since 3.5, IE8+, Chrome)
header("Content-Disposition: attachment; filename=\"".$fname_local."\"");
header("Content-Type: application/force-download");
header("Content-Transfer-Encoding: binary");
header("Content-Length: ".filesize($fname));
So as far as I can see, you're doing everything correctly. Have you checked your browser settings?

How to gzip ajax requests with Struts 2?

How to gzip an ajax response with Struts2? I tried to create a filter but it didn't work. At client-side I'm using jQuery and the ajax response I'm expecting is in json.
This is the code I used on server:
ByteArrayOutputStream out = new ByteArrayOutputStream();
GZIPOutputStream gz = new GZIPOutputStream(out);
gz.write(json.getBytes());
gz.close();
I'm redirecting the response to dummy jsp page defined at struts.xml.
The reason why I want to gzip the data back is because there's a situation where I must send a relatively big sized json back to the client.
Any reference provided will be appreciated.
Thanks.
You shouldn't randomly gzip responses. You can only gzip the response when the client has notified the server that it accepts (understands) gzipped responses. You can do that by determining if the Accept-Encoding request header contains gzip. If it is there, then you can safely wrap the OutputStream of the response in a GZIPOutputStream. You only need to add the Content-Encoding header beforehand with a value of gzip to inform the client what encoding the content is been sent in, so that the client knows that it needs to ungzip it.
In a nutshell:
response.setContentType("application/json");
response.setCharacterEncoding("UTF-8");
OutputStream output = response.getOutputStream();
String acceptEncoding = request.getHeader("Accept-Encoding");
if (acceptEncoding != null && acceptEncoding.contains("gzip")) {
response.setHeader("Content-Encoding", "gzip");
output = new GZIPOutputStream(output);
}
output.write(json.getBytes("UTF-8"));
(note that you would like to set the content type and character encoding as well, this is taken into account in the example)
You could also configure this at appserver level. Since it's unclear which one you're using, here's just a Tomcat-targeted example: check the compression and compressableMimeType attributes of the <Connector> element in /conf/server.xml: HTTP connector reference. This way you can just write to the response without worrying about gzipping it.
If your response is JSON I would recommend using the struts2-json plugin http://struts.apache.org/2.1.8/docs/json-plugin.html and setting the
enableGZIP param to true.

Categories