/**
* XWeb project
* https://github.com/abdollahpour/xweb
* Hamed Abdollahpour - 2013
*/
package ir.xweb.module;
import ir.xweb.server.Constants;
import ir.xweb.util.Tools;
import org.apache.commons.fileupload.FileItem;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletResponseWrapper;
import java.io.*;
import java.net.URI;
import java.nio.file.Files;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream;
public class GzipModule extends Module {
public final static String PARAM_MODULES = "modules";
public final static String PARAM_EXTENSIONS = "extensions";
public final static String PARAM_REQUESTS = "requests";
public final static String PARAM_DIR_CACHE = "dir.cache";
public final static String PARAM_SIZE_MAX = "size.max";
private final int maxSize;
private final List<String> modules;
private final List<String> extensions;
private final String requests;
private File cacheDir;
public GzipModule(
final Manager manager,
final ModuleInfo info,
final ModuleParam properties) throws ModuleException {
super(manager, info, properties);
this.modules = Arrays.asList(properties.getStrings(PARAM_MODULES, new String[0]));
this.extensions = Arrays.asList(properties.getStrings(PARAM_EXTENSIONS, new String[0]));
requests = properties.getString(PARAM_REQUESTS);
cacheDir = properties.getFile(PARAM_DIR_CACHE);
maxSize = properties.getInt(PARAM_SIZE_MAX, 2097152); // 2MB default
}
/**
* {@inheritDoc}
*/
@Override
public void doFilter(
final ServletContext context,
final HttpServletRequest request,
final HttpServletResponse response,
final FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest chainedRequest = null;
HttpServletResponse chainedResponse = null;
if (!response.isCommitted() && !response.containsHeader("Content-Encoding")) {
// we will check for incoming request anyway
final String contentEncoding = request.getHeader("Content-Encoding");
if (contentEncoding != null && contentEncoding.toLowerCase().indexOf("gzip") > -1) {
chainedRequest = new ZipRequestWrapper(request);
}
final String acceptEncoding = request.getHeader("Accept-Encoding");
if (acceptEncoding != null && acceptEncoding.toLowerCase().indexOf("gzip") > -1) {
URI uri;
try {
uri = new URI(request.getRequestURI());
} catch (Exception ex) {
// It will never happen because it passed by http request
throw new IOException(ex);
}
// we don't care about context path, so we trunk it
final String path = uri.getPath().substring(request.getContextPath().length());
// By regex
if(requests != null) {
if(path.matches(requests)) {
chainedResponse = new ZipResponseWrapper(response);
}
}
// By API call
if(chainedResponse == null) {
if(modules.size() > 0) {
// We handle API requests with different authentication method (Role base)
boolean isApiCall = path.equals(Constants.MODULE_URI_PERFIX);
if(isApiCall) {
final String moduleName = request.getParameter(Constants.MODULE_NAME_PARAMETER);
if(modules.contains(moduleName)) {
chainedResponse = new ZipResponseWrapper(response);
}
}
}
}
// By extensions
if (chainedResponse == null) {
if (extensions.size() > 0) {
File dir = new File(context.getRealPath(File.separator));
File file = new File(dir, path);
if(file.exists() && file.length() <= maxSize) {
String extension = Tools.getFileExtension(file.getName());
// check extension
if(extensions.contains(extension.toLowerCase())) {
// check for cache dir
if(cacheDir == null || !cacheDir.exists()) {
cacheDir = getManager().getModule(ResourceModule.class).initTempDir();
}
File zipFile = new File(cacheDir.getPath() + path + ".gz");
File zipDir = zipFile.getParentFile();
// Because of some weird problem, this solution does not work in Jetty, but Java7
// API is fine anywhere
//if(zipDir == null || (!zipDir.exists() && !zipDir.mkdirs())) {
// throw new IOException("Can not create zip dir: " + zipDir);
//}
Files.createDirectories(zipDir.toPath());
if(!zipFile.exists() || zipFile.lastModified() < file.lastModified()) {
Tools.zipFile(file, zipFile);
}
// TODO: It's not the right way to redirect and cut the filter!
// It will cut the rest of filters!
// we redirect it to resource module
final String filePath = Constants.MODULE_URI_PERFIX + "?" +
Constants.MODULE_NAME_PARAMETER + "=" +
getInfo().getName() + "&file=" + path;
RequestDispatcher dispatcher = request.getRequestDispatcher(filePath);
dispatcher.forward(request, response);
return;
}
}
}
}
}
}
//if(chainedRequest != null || chainedResponse != null) {
filterChain.doFilter(
chainedRequest == null ? request : chainedRequest,
chainedResponse == null ? response : chainedResponse
);
//}
}
@Override
public void process(
final ServletContext context,
final HttpServletRequest request,
final HttpServletResponse response,
final ModuleParam param,
final Map<String, FileItem> files) throws IOException {
if(param.containsKey("file")) {
final ResourceModule module = getManager().getModuleOrThrow(ResourceModule.class);
final String path = param.getString("file", null);
final File file = new File(cacheDir, path);
module.writeFile(request, response, file);
}
}
/*private class ZipFileRequestWrapper extends HttpServletRequestWrapper {
FileServletInputStream inputStream;
final File zipFile;
BufferedReader reader;
public ZipFileRequestWrapper(
final HttpServletRequest request,
final HttpServletResponse response,
final File zipFile) {
super(request);
this.zipFile = zipFile;
response.addHeader("Content-Encoding", "gzip");
}
@Override
public ServletInputStream getInputStream() throws IOException {
if(inputStream == null) {
inputStream = new FileServletInputStream(zipFile);
}
return inputStream;
}
@Override
public int getContentLength() {
return (int) zipFile.length();
}
@Override
public BufferedReader getReader() throws IOException {
if(this.reader == null) {
this.reader = new BufferedReader(new InputStreamReader(getInputStream(), getRequest().getCharacterEncoding()));
}
return this.reader;
}
}
private class FileServletInputStream extends ServletInputStream {
final FileInputStream inputStream;
FileServletInputStream(final File file) throws IOException {
inputStream = new FileInputStream(file);
}
@Override
public int read() throws IOException {
return inputStream.read();
}
@Override
public int read(byte[] b, int off, int len) throws IOException {
return inputStream.read(b, off, len);
}
@Override
public int read(byte[] b) throws IOException {
return inputStream.read(b);
}
@Override
public void close() throws IOException {
inputStream.close();
}
}*/
private class ZipRequestStream extends ServletInputStream {
final HttpServletRequest request;
final ServletInputStream inStream;
final GZIPInputStream in;
public ZipRequestStream(final HttpServletRequest request) throws IOException {
this.request = request;
inStream = request.getInputStream();
in = new GZIPInputStream(inStream);
}
@Override
public int read() throws IOException {
return in.read();
}
@Override
public int read(byte b[]) throws IOException {
return in.read(b);
}
@Override
public int read(byte b[], int off, int len) throws IOException {
return in.read(b, off, len);
}
@Override
public void close() throws IOException {
in.close();
}
}
private class ZipRequestWrapper extends HttpServletRequestWrapper {
final HttpServletRequest origRequest;
final ServletInputStream inStream;
BufferedReader reader;
public ZipRequestWrapper(final HttpServletRequest req) throws IOException {
super(req);
origRequest = null;
inStream = new ZipRequestStream(req);
reader = new BufferedReader(new InputStreamReader(inStream));
}
@Override
public ServletInputStream getInputStream() throws IOException {
return inStream;
}
@Override
public BufferedReader getReader() throws IOException {
return reader;
}
}
private class ZipResponseStream extends ServletOutputStream {
final ServletOutputStream outStream;
final GZIPOutputStream out;
public ZipResponseStream(final HttpServletResponse response) throws IOException {
outStream = response.getOutputStream();
out = new GZIPOutputStream(outStream);
response.addHeader("Content-Encoding", "gzip");
}
@Override
public void write(final int b) throws IOException {
out.write(b);
}
@Override
public void write(final byte b[]) throws IOException {
out.write(b);
}
@Override
public void write(final byte b[], final int off, final int len) throws IOException {
out.write(b, off, len);
}
@Override
public void close() throws IOException {
out.close();
}
@Override
public void flush() throws IOException {
out.flush();
}
public void finish() throws IOException {
out.finish();
}
}
private class ZipResponseWrapper extends HttpServletResponseWrapper {
final HttpServletResponse response;
ZipResponseStream outStream = null;
PrintWriter writer;
public ZipResponseWrapper(final HttpServletResponse response) {
super(response);
this.response = response;
}
@Override
public ServletOutputStream getOutputStream() throws IOException {
if(outStream == null) {
outStream = new ZipResponseStream(response);
}
return outStream;
}
@Override
public void flushBuffer() throws IOException {
if (writer != null) {
writer.flush();
outStream.finish();
outStream.flush();
} else if (outStream != null) {
outStream.finish();
outStream.flush();
}
super.flushBuffer();
}
@Override
public void setContentLength(int len) {}
@Override
public PrintWriter getWriter() throws IOException {
if(writer == null) {
writer = new PrintWriter(new OutputStreamWriter(getOutputStream(), getResponse().getCharacterEncoding()), true);
}
return writer;
}
}
}