/* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.riotfamily.cachius.http.content;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Enumeration;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.zip.GZIPOutputStream;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.riotfamily.cachius.http.support.IOUtils;
public class GzipContent extends BinaryContent {
private static Pattern IE_MAJOR_VERSION_PATTERN =
Pattern.compile("^Mozilla/\\d\\.\\d+ \\(compatible[-;] MSIE (\\d)");
private static Pattern BUGGY_NETSCAPE_PATTERN =
Pattern.compile("^Mozilla/4\\.0[678]");
private File zipFile;
public GzipContent(File file, File zipFile) throws IOException {
super(file);
this.zipFile = zipFile;
InputStream in = new BufferedInputStream(new FileInputStream(file));
OutputStream out = new GZIPOutputStream(new FileOutputStream(zipFile));
IOUtils.copy(in, out);
IOUtils.closeStream(out);
}
@Override
public int getLength(HttpServletRequest request,
HttpServletResponse response) {
if (responseCanBeZipped(request)) {
return (int) zipFile.length();
}
return super.getLength(request, response);
}
@Override
public void serve(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.setHeader("Vary", "Accept-Encoding, User-Agent");
if (responseCanBeZipped(request)) {
serveZipped(request, response);
}
else {
super.serve(request, response);
}
}
protected void serveZipped(HttpServletRequest request,
HttpServletResponse response) throws IOException {
response.setHeader("Content-Encoding", "gzip");
IOUtils.serve(zipFile, response.getOutputStream());
}
@Override
public void delete() {
super.delete();
zipFile.delete();
}
/**
* Checks whether the response can be compressed. This is the case when
* {@link #clientAcceptsGzip(HttpServletRequest) the client accepts gzip
* encoded content}, the {@link #userAgentHasGzipBugs(HttpServletRequest)
* user-agent has no known gzip-related bugs} and the request is not an
* include request.
*/
protected boolean responseCanBeZipped(HttpServletRequest request) {
return clientAcceptsGzip(request)
&& !userAgentHasGzipBugs(request)
&& request.getAttribute("javax.servlet.include.request_uri") == null;
}
/**
* Returns whether the Accept-Encoding header contains "gzip".
*/
@SuppressWarnings("unchecked")
protected boolean clientAcceptsGzip(HttpServletRequest request) {
Enumeration values = request.getHeaders("Accept-Encoding");
if (values != null) {
while (values.hasMoreElements()) {
String value = (String) values.nextElement();
if (value.indexOf("gzip") != -1) {
return true;
}
}
}
return false;
}
/**
* Returns whether the User-Agent has known gzip-related bugs. This is true
* for Internet Explorer < 6.0 SP2 and Mozilla 4.06, 4.07 and 4.08. The
* method will also return true if the User-Agent header is not present or
* empty.
*/
protected boolean userAgentHasGzipBugs(HttpServletRequest request) {
String ua = request.getHeader("User-Agent");
if (ua == null || ua.length() == 0) {
return true;
}
Matcher m = IE_MAJOR_VERSION_PATTERN.matcher(ua);
if (m.find()) {
int major = Integer.parseInt(m.group(1));
if (major > 6) {
// Bugs are fixed in IE 7
return false;
}
if (ua.indexOf("Opera") != -1) {
// Opera has no known gzip bugs
return false;
}
if (major == 6) {
// Bugs are fixed in Service Pack 2
return ua.indexOf("SV1") == -1;
}
// All other version are buggy.
return true;
}
return BUGGY_NETSCAPE_PATTERN.matcher(ua).find();
}
}