package com.aggrepoint.servlet; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.OutputStreamWriter; import java.io.PrintWriter; import java.util.ArrayList; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.servlet.Filter; import javax.servlet.FilterChain; import javax.servlet.FilterConfig; import javax.servlet.ServletException; import javax.servlet.ServletOutputStream; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.WriteListener; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponseWrapper; import com.googlecode.htmlcompressor.compressor.HtmlCompressor; public class CompressFilter implements Filter { static final Pattern JSON_LD = Pattern .compile( "<script\\s+type\\s*=\\s*([\"'])\\s*application/ld\\+json\\s*\\1\\s*>(.*?)</script>", Pattern.DOTALL); private static class CompressResponseStream extends ServletOutputStream { private ByteArrayOutputStream baos = null; private boolean closed = false; private HttpServletResponse response = null; private ServletOutputStream output = null; public CompressResponseStream(HttpServletResponse response) throws IOException { super(); closed = false; this.response = response; this.output = response.getOutputStream(); baos = new ByteArrayOutputStream(); } public void close() throws IOException { if (closed) { throw new IOException( "This output stream has already been closed"); } String contentType = response.getContentType(); if (contentType != null && contentType.startsWith("text/html")) { String html = baos.toString(); // { extract JSON-LD before compression, HtmlCompressor cannot // handle them ArrayList<String> jsonLd = new ArrayList<String>(); while (true) { Matcher m = JSON_LD.matcher(html); if (!m.find()) break; jsonLd.add(m.group(2).replace("\n", "").replace("\r", "")); html = m.replaceFirst(""); } // } HtmlCompressor compressor = new HtmlCompressor(); compressor.setCompressJavaScript(true); compressor.setCompressCss(true); html = compressor.compress(html); // insert JSON-LD back to before </head> or </body> if (jsonLd.size() > 0) { StringBuffer sb = new StringBuffer(); for (String str : jsonLd) sb.append("<script type=\"application/ld+json\">") .append(str).append("</script>"); String lower = html.toLowerCase(); int idx = lower.indexOf("</head>"); if (idx <= 0) idx = lower.indexOf("</body>"); if (idx > 0) html = html.substring(0, idx) + sb.toString() + html.substring(idx); } output.write(html.getBytes()); } else output.write(baos.toByteArray()); output.flush(); output.close(); closed = true; } public void flush() throws IOException { if (closed) { throw new IOException("Cannot flush a closed output stream"); } baos.flush(); } public void write(int b) throws IOException { if (closed) { throw new IOException("Cannot write to a closed output stream"); } baos.write((byte) b); } public void write(byte b[]) throws IOException { write(b, 0, b.length); } public void write(byte b[], int off, int len) throws IOException { if (closed) { throw new IOException("Cannot write to a closed output stream"); } baos.write(b, off, len); } @Override public boolean isReady() { return output.isReady(); } @Override public void setWriteListener(WriteListener writeListener) { output.setWriteListener(writeListener); } } private class CompressResponseWrapper extends HttpServletResponseWrapper { protected HttpServletResponse origResponse = null; protected ServletOutputStream stream = null; protected PrintWriter writer = null; public CompressResponseWrapper(HttpServletResponse response) { super(response); origResponse = response; } public ServletOutputStream createOutputStream() throws IOException { return new CompressResponseStream(origResponse); } public void finishResponse() { try { if (writer != null) { writer.close(); } else { if (stream != null) { stream.close(); } } } catch (IOException e) { } } @Override public void flushBuffer() throws IOException { stream.flush(); } @Override public ServletOutputStream getOutputStream() throws IOException { if (stream != null) return stream; if (writer != null) { throw new IllegalStateException( "getWriter() has already been called!"); } stream = createOutputStream(); return (stream); } @Override public PrintWriter getWriter() throws IOException { if (writer != null) { return (writer); } if (stream != null) { throw new IllegalStateException( "getOutputStream() has already been called!"); } stream = createOutputStream(); writer = new PrintWriter(new OutputStreamWriter(stream, "UTF-8")); return (writer); } @Override public void setContentLength(int length) { } } @Override public void init(FilterConfig filterConfig) throws ServletException { } @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { CompressResponseWrapper wrappedResponse = new CompressResponseWrapper( (HttpServletResponse) response); chain.doFilter((HttpServletRequest) request, wrappedResponse); wrappedResponse.finishResponse(); } @Override public void destroy() { } }