001: // TEFilter.java
002: // $Id: TEFilter.java,v 1.6 2002/02/05 16:34:38 ylafon Exp $
003: // (c) COPYRIGHT MIT and INRIA, 1996.
004: // Please first read the full copyright statement in file COPYRIGHT.html
005:
006: package org.w3c.jigsaw.filters;
007:
008: import java.io.IOException;
009: import java.io.InputStream;
010: import java.io.OutputStream;
011: import java.io.PipedInputStream;
012: import java.io.PipedOutputStream;
013:
014: import java.util.zip.DeflaterOutputStream;
015: import java.util.zip.GZIPOutputStream;
016:
017: import org.w3c.tools.resources.Attribute;
018: import org.w3c.tools.resources.AttributeRegistry;
019: import org.w3c.tools.resources.ProtocolException;
020: import org.w3c.tools.resources.ReplyInterface;
021: import org.w3c.tools.resources.RequestInterface;
022: import org.w3c.tools.resources.Resource;
023: import org.w3c.tools.resources.ResourceFilter;
024: import org.w3c.tools.resources.ResourceFrame;
025: import org.w3c.tools.resources.StringArrayAttribute;
026:
027: import org.w3c.www.mime.MimeType;
028:
029: import org.w3c.jigsaw.http.Reply;
030: import org.w3c.jigsaw.http.Request;
031:
032: import org.w3c.www.http.HttpAcceptEncoding;
033: import org.w3c.www.http.HttpEntityMessage;
034: import org.w3c.www.http.HttpMessage;
035: import org.w3c.www.http.HttpRequestMessage;
036:
037: /**
038: * This filter will compress the content of replies using GZIP or whatever
039: * encoding scheme requested in the TE: header of the request.
040: * Compression is done <em>on the fly</em>. This assumes that you're really
041: * on a slow link, where you have lots of CPU, but not much bandwidth.
042: * <p>A nifty usage for that filter, is to plug it on top of a
043: * <code>org.w3c.jigsaw.proxy.ProxyFrame</code>, in which case it
044: * will encode the data when it flies out of the proxy.
045: */
046:
047: public class TEFilter extends ResourceFilter {
048: /**
049: * Attribute index - List of MIME type that we can compress
050: */
051: protected static int ATTR_MIME_TYPES = -1;
052:
053: static {
054: Class c = null;
055: Attribute a = null;
056: try {
057: c = Class.forName("org.w3c.jigsaw.filters.TEFilter");
058: } catch (Exception ex) {
059: ex.printStackTrace();
060: System.exit(1);
061: }
062: // Register the MIME types attribute:
063: a = new StringArrayAttribute("mime-types", null,
064: Attribute.EDITABLE);
065: ATTR_MIME_TYPES = AttributeRegistry.registerAttribute(c, a);
066: }
067:
068: /**
069: * The set of MIME types we are allowed to compress.
070: */
071: protected MimeType types[] = null;
072:
073: // grumble... DeflateInputStream with a compression/decompression
074: // flag would have been better in the core API ;)
075: private class DataMover extends Thread {
076: InputStream in = null;
077: OutputStream out = null;
078:
079: public void run() {
080: try {
081: byte buf[] = new byte[1024];
082: int got = -1;
083: while ((got = in.read(buf)) >= 0)
084: out.write(buf, 0, got);
085: } catch (IOException ex) {
086: ex.printStackTrace();
087: } finally {
088: try {
089: in.close();
090: } catch (Exception ex) {
091: }
092: ;
093: try {
094: out.close();
095: } catch (Exception ex) {
096: }
097: ;
098: }
099: }
100:
101: DataMover(InputStream in, OutputStream out) {
102: this .in = in;
103: this .out = out;
104: setName("DataMover");
105: start();
106: }
107: }
108:
109: /**
110: * Catch the setting of mime types to compress.
111: * @param idx The attribute being set.
112: * @param val The new attribute value.
113: */
114:
115: public void setValue(int idx, Object value) {
116: super .setValue(idx, value);
117: if (idx == ATTR_MIME_TYPES) {
118: synchronized (this ) {
119: types = null;
120: }
121: }
122: }
123:
124: /**
125: * Get the set of MIME types to match:
126: * @return An array of MimeType instances.
127: */
128:
129: public synchronized MimeType[] getMimeTypes() {
130: if (types == null) {
131: String strtypes[] = (String[]) getValue(ATTR_MIME_TYPES,
132: null);
133: if (strtypes == null)
134: return null;
135: types = new MimeType[strtypes.length];
136: for (int i = 0; i < types.length; i++) {
137: try {
138: types[i] = new MimeType(strtypes[i]);
139: } catch (Exception ex) {
140: types[i] = null;
141: }
142: }
143: }
144: return types;
145: }
146:
147: protected double getCompressibilityFactor(Reply reply) {
148: // Match possible mime types:
149: MimeType t[] = getMimeTypes();
150: if (t != null) {
151: for (int i = 0; i < t.length; i++) {
152: if (t[i] == null)
153: continue;
154: if (t[i].match(reply.getContentType()) > 0) {
155: String enc[] = reply.getContentEncoding();
156: if (enc != null) {
157: for (int j = 0; j < enc.length; j++) {
158: if ((enc[j].indexOf("gzip") >= 0)
159: || (enc[j].indexOf("deflate") >= 0)
160: || (enc[j].indexOf("compress") >= 0)) {
161: // no more compression
162: return 0.001;
163: }
164: }
165: }
166: return 1.0;
167: }
168: }
169: }
170: return 0.001; // minimal value
171: }
172:
173: protected void doEncoding(HttpAcceptEncoding enc, Reply reply) {
174: // Anything to compress ?
175: if (!reply.hasStream())
176: return;
177: // Match possible mime types:
178: MimeType t[] = getMimeTypes();
179: boolean matched = false;
180: if (t != null) {
181: for (int i = 0; i < t.length; i++) {
182: if (t[i] == null)
183: continue;
184: if (t[i].match(reply.getContentType()) > 0) {
185: matched = true;
186: break;
187: }
188: }
189: }
190: if (!matched)
191: return;
192: InputStream orig_is = reply.openStream();
193: // do the gzip encoding
194: if (enc.getEncoding().equals("gzip")) {
195: try {
196: PipedOutputStream gzpout = new PipedOutputStream();
197: PipedInputStream gzpin = new PipedInputStream(gzpout);
198: new DataMover(reply.openStream(), new GZIPOutputStream(
199: gzpout));
200: reply.setStream(gzpin);
201: } catch (IOException ex) {
202: ex.printStackTrace();
203: reply.setStream(orig_is);
204: return;
205: }
206: reply.addTransferEncoding("gzip");
207: reply.setContentLength(-1);
208: } else if (enc.getEncoding().equals("deflate")) {
209: // do the deflate encoding
210: try {
211: PipedOutputStream zpout = new PipedOutputStream();
212: PipedInputStream zpin = new PipedInputStream(zpout);
213: new DataMover(reply.openStream(),
214: new DeflaterOutputStream(zpout));
215: reply.setStream(zpin);
216: } catch (IOException ex) {
217: ex.printStackTrace();
218: reply.setStream(orig_is);
219: return;
220: }
221: reply.addTransferEncoding("deflate");
222: reply.setContentLength(-1);
223: }
224: return;
225: }
226:
227: /**
228: * @param request The original request.
229: * @param reply It's original reply.
230: * @return A Reply instance, or <strong>null</strong> if processing
231: * should continue normally.
232: * @exception ProtocolException If processing should be interrupted,
233: * because an abnormal situation occured.
234: */
235: public ReplyInterface outgoingFilter(RequestInterface req,
236: ReplyInterface rep) throws ProtocolException {
237: Request request = (Request) req;
238: Reply reply = (Reply) rep;
239: HttpAcceptEncoding encs[] = request.getTE();
240: String trenc[] = reply.getTransferEncoding();
241:
242: // Anything to compress ?
243: if (!reply.hasStream())
244: return null;
245:
246: if (trenc != null) {
247: // don't mess with already encoded stuff
248: // otherwise we have to dechunk/rechunk and it would be painful
249: return null;
250: }
251:
252: if (encs != null) { // identity and chucked always ok
253: double max = -1.0;
254: double identity = 1.0; // some dummy default values
255: double chunked = 1.0;
256: double comp_factor = getCompressibilityFactor(reply);
257: HttpAcceptEncoding best = null;
258: for (int i = 0; i < encs.length; i++) {
259: if (encs[i].getEncoding().equals("identity")) {
260: identity = encs[i].getQuality();
261: continue;
262: } else if (encs[i].getEncoding().equals("chunked")) {
263: chunked = encs[i].getQuality();
264: continue;
265: } else if (encs[i].getEncoding().equals("trailers")) {
266: // means that the client understand trailers.. check
267: // that with pending trailers impl
268: // req.setTrailerOk();
269: continue;
270: }
271: if (encs[i].getQuality() * comp_factor > max) {
272: best = encs[i];
273: max = encs[i].getQuality() * comp_factor;
274: if (max == 1.0) // can't be better
275: break;
276: }
277: }
278: if (best != null && (max >= identity)) {
279: doEncoding(best, reply);
280: } else {
281: if (identity > 0) {
282: if (chunked > identity)
283: reply.setContentLength(-1);
284: } else {
285: // spec says: chunked always acceptable
286: reply.setContentLength(-1);
287: }
288: }
289: }
290: return null;
291: }
292: }
|