--- /dev/null
+// TEFilter.java\r
+// $Id: TEFilter.java,v 1.2 2010/06/15 17:52:54 smhuang Exp $\r
+// (c) COPYRIGHT MIT and INRIA, 1996.\r
+// Please first read the full copyright statement in file COPYRIGHT.html\r
+\r
+package org.w3c.jigsaw.filters;\r
+\r
+import java.io.IOException;\r
+import java.io.InputStream;\r
+import java.io.OutputStream;\r
+import java.io.PipedInputStream;\r
+import java.io.PipedOutputStream;\r
+\r
+import java.util.zip.DeflaterOutputStream;\r
+import java.util.zip.GZIPOutputStream;\r
+\r
+import org.w3c.tools.resources.Attribute;\r
+import org.w3c.tools.resources.AttributeRegistry;\r
+import org.w3c.tools.resources.ProtocolException;\r
+import org.w3c.tools.resources.ReplyInterface;\r
+import org.w3c.tools.resources.RequestInterface;\r
+import org.w3c.tools.resources.Resource;\r
+import org.w3c.tools.resources.ResourceFilter;\r
+import org.w3c.tools.resources.ResourceFrame;\r
+import org.w3c.tools.resources.StringArrayAttribute;\r
+\r
+import org.w3c.www.mime.MimeType;\r
+\r
+import org.w3c.jigsaw.http.Reply;\r
+import org.w3c.jigsaw.http.Request;\r
+\r
+import org.w3c.www.http.HttpAcceptEncoding;\r
+import org.w3c.www.http.HttpEntityMessage;\r
+import org.w3c.www.http.HttpMessage;\r
+import org.w3c.www.http.HttpRequestMessage;\r
+\r
+/**\r
+ * This filter will compress the content of replies using GZIP or whatever\r
+ * encoding scheme requested in the TE: header of the request.\r
+ * Compression is done <em>on the fly</em>. This assumes that you're really\r
+ * on a slow link, where you have lots of CPU, but not much bandwidth.\r
+ * <p>A nifty usage for that filter, is to plug it on top of a\r
+ * <code>org.w3c.jigsaw.proxy.ProxyFrame</code>, in which case it\r
+ * will encode the data when it flies out of the proxy.\r
+ */\r
+\r
+public class TEFilter extends ResourceFilter {\r
+ /**\r
+ * Attribute index - List of MIME type that we can compress\r
+ */\r
+ protected static int ATTR_MIME_TYPES = -1;\r
+\r
+ static {\r
+ Class c = null;\r
+ Attribute a = null;\r
+ try {\r
+ c = Class.forName("org.w3c.jigsaw.filters.TEFilter");\r
+ //Added by Jeff Huang\r
+ //TODO: FIXIT\r
+ } catch (Exception ex) {\r
+ ex.printStackTrace();\r
+ System.exit(1);\r
+ }\r
+ // Register the MIME types attribute:\r
+ a = new StringArrayAttribute("mime-types"\r
+ , null\r
+ , Attribute.EDITABLE);\r
+ ATTR_MIME_TYPES = AttributeRegistry.registerAttribute(c, a);\r
+ }\r
+\r
+ /**\r
+ * The set of MIME types we are allowed to compress.\r
+ */\r
+ protected MimeType types[] = null;\r
+\r
+ // grumble... DeflateInputStream with a compression/decompression\r
+ // flag would have been better in the core API ;)\r
+ private class DataMover extends Thread {\r
+ InputStream in = null;\r
+ OutputStream out = null;\r
+\r
+ public void run() {\r
+ try {\r
+ byte buf[] = new byte[1024];\r
+ int got = -1;\r
+ while ((got = in.read(buf)) >= 0) \r
+ out.write(buf, 0, got);\r
+ } catch (IOException ex) {\r
+ ex.printStackTrace();\r
+ } finally {\r
+ try { in.close(); } catch (Exception ex) {};\r
+ try { out.close(); } catch (Exception ex) {} ;\r
+ }\r
+ }\r
+\r
+ DataMover(InputStream in, OutputStream out) {\r
+ this.in = in;\r
+ this.out = out;\r
+ setName("DataMover");\r
+ start();\r
+ }\r
+ }\r
+\r
+ /**\r
+ * Catch the setting of mime types to compress.\r
+ * @param idx The attribute being set.\r
+ * @param val The new attribute value.\r
+ */\r
+\r
+ public void setValue(int idx, Object value) {\r
+ super.setValue(idx, value);\r
+ if ( idx == ATTR_MIME_TYPES ) {\r
+ synchronized (this) {\r
+ types = null;\r
+ }\r
+ }\r
+ }\r
+\r
+ \r
+ /**\r
+ * Get the set of MIME types to match:\r
+ * @return An array of MimeType instances.\r
+ */\r
+\r
+ public synchronized MimeType[] getMimeTypes() {\r
+ if ( types == null ) {\r
+ String strtypes[] = (String[]) getValue(ATTR_MIME_TYPES, null);\r
+ if ( strtypes == null )\r
+ return null;\r
+ types = new MimeType[strtypes.length];\r
+ for (int i = 0 ; i < types.length ; i++) {\r
+ try {\r
+ types[i] = new MimeType(strtypes[i]);\r
+ } catch (Exception ex) {\r
+ types[i] = null;\r
+ }\r
+ }\r
+ }\r
+ return types;\r
+ }\r
+\r
+ protected double getCompressibilityFactor(Reply reply) {\r
+ // Match possible mime types:\r
+ MimeType t[] = getMimeTypes();\r
+ if ( t != null ) {\r
+ for (int i = 0 ; i < t.length ; i++) {\r
+ if ( t[i] == null )\r
+ continue;\r
+ if ( t[i].match(reply.getContentType()) > 0 ) {\r
+ String enc[] = reply.getContentEncoding();\r
+ if (enc != null ) {\r
+ for (int j=0; j< enc.length; j++) {\r
+ if ((enc[j].indexOf("gzip") >= 0) ||\r
+ (enc[j].indexOf("deflate") >= 0) ||\r
+ (enc[j].indexOf("compress") >= 0)) {\r
+ // no more compression\r
+ return 0.001;\r
+ }\r
+ }\r
+ }\r
+ return 1.0;\r
+ }\r
+ }\r
+ }\r
+ return 0.001; // minimal value\r
+ }\r
+\r
+ protected void doEncoding(HttpAcceptEncoding enc, Reply reply) {\r
+ // Anything to compress ?\r
+ if ( ! reply.hasStream() )\r
+ return;\r
+ // Match possible mime types:\r
+ MimeType t[] = getMimeTypes();\r
+ boolean matched = false;\r
+ if ( t != null ) {\r
+ for (int i = 0 ; i < t.length ; i++) {\r
+ if ( t[i] == null )\r
+ continue;\r
+ if ( t[i].match(reply.getContentType()) > 0 ) {\r
+ matched = true;\r
+ break;\r
+ }\r
+ }\r
+ }\r
+ if ( ! matched ) \r
+ return; \r
+ InputStream orig_is = reply.openStream();\r
+ // do the gzip encoding\r
+ if (enc.getEncoding().equals("gzip")) {\r
+ try {\r
+ PipedOutputStream gzpout = new PipedOutputStream();\r
+ PipedInputStream gzpin = new PipedInputStream(gzpout);\r
+ new DataMover(reply.openStream()\r
+ , new GZIPOutputStream(gzpout));\r
+ reply.setStream(gzpin);\r
+ } catch (IOException ex) {\r
+ ex.printStackTrace();\r
+ reply.setStream(orig_is);\r
+ return;\r
+ }\r
+ reply.addTransferEncoding("gzip");\r
+ reply.setContentLength(-1);\r
+ } else if (enc.getEncoding().equals("deflate")) {\r
+ // do the deflate encoding\r
+ try {\r
+ PipedOutputStream zpout = new PipedOutputStream();\r
+ PipedInputStream zpin = new PipedInputStream(zpout);\r
+ new DataMover(reply.openStream()\r
+ , new DeflaterOutputStream(zpout));\r
+ reply.setStream(zpin);\r
+ } catch (IOException ex) {\r
+ ex.printStackTrace();\r
+ reply.setStream(orig_is);\r
+ return;\r
+ }\r
+ reply.addTransferEncoding("deflate");\r
+ reply.setContentLength(-1);\r
+ }\r
+ return;\r
+ }\r
+\r
+ /**\r
+ * @param request The original request.\r
+ * @param reply It's original reply. \r
+ * @return A Reply instance, or <strong>null</strong> if processing \r
+ * should continue normally. \r
+ * @exception ProtocolException If processing should be interrupted, \r
+ * because an abnormal situation occured. \r
+ */\r
+ public ReplyInterface outgoingFilter(RequestInterface req,\r
+ ReplyInterface rep) \r
+ throws ProtocolException\r
+ {\r
+ Request request = (Request) req;\r
+ Reply reply = (Reply) rep;\r
+ HttpAcceptEncoding encs[] = request.getTE();\r
+ String trenc[] = reply.getTransferEncoding();\r
+ \r
+ // Anything to compress ?\r
+ if ( ! reply.hasStream() )\r
+ return null;\r
+ \r
+ if (trenc != null) {\r
+ // don't mess with already encoded stuff\r
+ // otherwise we have to dechunk/rechunk and it would be painful\r
+ return null;\r
+ }\r
+\r
+ if (encs != null) { // identity and chucked always ok\r
+ double max = -1.0;\r
+ double identity = 1.0; // some dummy default values\r
+ double chunked = 1.0;\r
+ double comp_factor = getCompressibilityFactor(reply);\r
+ HttpAcceptEncoding best = null;\r
+ for (int i = 0 ; i < encs.length ; i++) {\r
+ if (encs[i].getEncoding().equals("identity")) {\r
+ identity = encs[i].getQuality();\r
+ continue;\r
+ } else if (encs[i].getEncoding().equals("chunked")) {\r
+ chunked = encs[i].getQuality();\r
+ continue;\r
+ } else if (encs[i].getEncoding().equals("trailers")) {\r
+ // means that the client understand trailers.. check \r
+ // that with pending trailers impl\r
+ // req.setTrailerOk();\r
+ continue;\r
+ }\r
+ if (encs[i].getQuality() * comp_factor > max) {\r
+ best = encs[i];\r
+ max = encs[i].getQuality() * comp_factor;\r
+ if (max == 1.0) // can't be better\r
+ break;\r
+ }\r
+ }\r
+ if (best != null && (max >= identity)) {\r
+ doEncoding(best, reply);\r
+ } else {\r
+ if (identity > 0) {\r
+ if (chunked > identity)\r
+ reply.setContentLength(-1);\r
+ } else {\r
+ // spec says: chunked always acceptable\r
+ reply.setContentLength(-1);\r
+ }\r
+ }\r
+ }\r
+ return null;\r
+ }\r
+}\r