View | Details | Raw Unified | Return to bug 48738
Collapse All | Expand All

(-)test/org/apache/catalina/tomcat/util/http/TestGzipOutputFilter.java (+99 lines)
Line 0 Link Here
1
package org.apache.catalina.tomcat.util.http;
2
3
import junit.framework.Test;
4
import junit.framework.TestCase;
5
import junit.framework.TestSuite;
6
import junit.textui.TestRunner;
7
import org.apache.coyote.Response;
8
import org.apache.coyote.http11.InternalOutputBuffer;
9
import org.apache.coyote.http11.filters.GzipOutputFilter;
10
import org.apache.tomcat.util.buf.ByteChunk;
11
12
import java.io.ByteArrayOutputStream;
13
import java.util.zip.GZIPOutputStream;
14
15
/**
16
 * User: Jiong Wang (jiwang@linkedin.com)
17
 * Date: Feb 13, 2010
18
 * Time: 12:04:38 AM
19
 *
20
 * Test case to demonstrate the interaction between gzip and flushing in the output filter.
21
 */
22
public class TestGzipOutputFilter extends TestCase
23
{
24
25
  public static void main( String args[] ) {
26
     TestRunner.run(suite());
27
  }
28
29
  public static Test suite() {
30
     TestSuite suite = new TestSuite();
31
     suite.addTest(new TestSuite(TestGzipOutputFilter.class));
32
     return suite;
33
  }
34
35
36
  public TestGzipOutputFilter()
37
  {
38
  }
39
40
  public TestGzipOutputFilter(String s)
41
  {
42
    super(s);
43
  }
44
45
  /**
46
   * Test the interaction betwen gzip and flushing.
47
   *
48
   * The idea is to:
49
   * 1. create a internal output buffer, response, and attach an active gzipoutputfilter to the output buffer
50
   * 2. set the output stream of the internal buffer to be a ByteArrayOutputStream so we can inspect the output bytes
51
   * 3. write a chunk out using the gzipoutputfilter and invoke a flush on the InternalOutputBuffer
52
   * 4. read from the ByteArrayOutputStream to find out what's being written out (flushed)
53
   * 5. find out what's expected by wrting to GZIPOutputStream and close it (to force flushing)
54
   * 6. Compare the size of the two arrays, they should be close (instead of one being much shorter than the other one)
55
   * @throws Exception
56
   */
57
  public void testFlushingWithGzip() throws Exception
58
  {
59
    // set up response, InternalOutputBuffer, and ByteArrayOutputStream
60
    Response res = new Response();
61
    InternalOutputBuffer iob = new InternalOutputBuffer(res);
62
    ByteArrayOutputStream bos = new ByteArrayOutputStream();
63
    iob.setOutputStream(bos);
64
    res.setOutputBuffer(iob);
65
66
    // set up GzipOutputFilter to attach to the InternalOutputBuffer
67
    GzipOutputFilter gf = new GzipOutputFilter();
68
    iob.addFilter(gf);
69
    iob.addActiveFilter(gf);
70
71
    // write a chunk out
72
    ByteChunk chunk = new ByteChunk(1024);
73
    byte[] d = "Hello there tomcat developers, there is a bug in JDK".getBytes();
74
    chunk.append(d, 0, d.length);
75
    iob.doWrite(chunk, res);
76
77
    // flush the InternalOutputBuffer
78
    iob.flush();
79
80
    // read from the ByteArrayOutputStream to find out what's being written out (flushed)
81
    byte[] dataFound = bos.toByteArray();
82
83
    // find out what's expected by wrting to GZIPOutputStream and close it (to force flushing)
84
    ByteArrayOutputStream gbos = new ByteArrayOutputStream(1024);
85
    GZIPOutputStream gos = new GZIPOutputStream(gbos);
86
    gos.write(d);
87
    gos.close();
88
89
    // read the expected data
90
    byte[] dataExpected = gbos.toByteArray();
91
92
    // most of the data should have been flushed out
93
    assertTrue(dataFound.length >= (dataExpected.length - 20));
94
95
//    System.out.println("dataFound = " + Arrays.toString(dataFound));
96
//    System.out.println("dataExpected = " + Arrays.toString(dataExpected));
97
98
  }
99
}
(-)java/org/apache/coyote/http11/InternalOutputBuffer.java (+22 lines)
Lines 32-37 Link Here
32
import org.apache.coyote.ActionCode;
32
import org.apache.coyote.ActionCode;
33
import org.apache.coyote.OutputBuffer;
33
import org.apache.coyote.OutputBuffer;
34
import org.apache.coyote.Response;
34
import org.apache.coyote.Response;
35
import org.apache.coyote.http11.filters.GzipOutputFilter;
35
36
36
/**
37
/**
37
 * Output buffer.
38
 * Output buffer.
Lines 41-46 Link Here
41
public class InternalOutputBuffer 
42
public class InternalOutputBuffer 
42
    implements OutputBuffer, ByteChunk.ByteOutputChannel {
43
    implements OutputBuffer, ByteChunk.ByteOutputChannel {
43
44
45
  /**
46
   * Logger.
47
   */
48
  protected static org.apache.juli.logging.Log log
49
      = org.apache.juli.logging.LogFactory.getLog(InternalOutputBuffer.class);
50
51
52
44
    // -------------------------------------------------------------- Constants
53
    // -------------------------------------------------------------- Constants
45
54
46
55
Lines 293-298 Link Here
293
            response.action(ActionCode.ACTION_COMMIT, null);
302
            response.action(ActionCode.ACTION_COMMIT, null);
294
303
295
        }
304
        }
305
      // go through the filters and if there is gzip filter
306
      // invoke it to flush
307
      for (int i = 0; i <= lastActiveFilter; i++) {
308
        if (activeFilters[i] instanceof GzipOutputFilter)
309
        {
310
          if (log.isDebugEnabled())
311
          {
312
            log.debug("Flushing the gzip filter at position " + i + " of the filter chain...");
313
          }
314
          ((GzipOutputFilter)activeFilters[i]).flush();
315
          break; 
316
        }
317
      }
296
318
297
        // Flush the current buffer
319
        // Flush the current buffer
298
        if (useSocketBuffer) {
320
        if (useSocketBuffer) {
(-)java/org/apache/coyote/http11/filters/GzipOutputFilter.java (-8 / +40 lines)
Lines 17-32 Link Here
17
17
18
package org.apache.coyote.http11.filters;
18
package org.apache.coyote.http11.filters;
19
19
20
import org.apache.coyote.OutputBuffer;
21
import org.apache.coyote.Response;
22
import org.apache.coyote.http11.OutputFilter;
23
import org.apache.tomcat.util.buf.ByteChunk;
24
20
import java.io.IOException;
25
import java.io.IOException;
21
import java.io.OutputStream;
26
import java.io.OutputStream;
22
import java.util.zip.GZIPOutputStream;
27
import java.util.zip.GZIPOutputStream;
23
28
24
import org.apache.tomcat.util.buf.ByteChunk;
29
import org.apache.coyote.http11.filters.FlushableGZIPOutputStream;
25
30
26
import org.apache.coyote.OutputBuffer;
27
import org.apache.coyote.Response;
28
import org.apache.coyote.http11.OutputFilter;
29
30
/**
31
/**
31
 * Gzip output filter.
32
 * Gzip output filter.
32
 * 
33
 * 
Lines 34-40 Link Here
34
 */
35
 */
35
public class GzipOutputFilter implements OutputFilter {
36
public class GzipOutputFilter implements OutputFilter {
36
37
37
38
    // -------------------------------------------------------------- Constants
38
    // -------------------------------------------------------------- Constants
39
39
40
40
Lines 42-47 Link Here
42
    protected static final ByteChunk ENCODING = new ByteChunk();
42
    protected static final ByteChunk ENCODING = new ByteChunk();
43
43
44
44
45
  /**
46
   * Logger.
47
   */
48
  protected static org.apache.juli.logging.Log log
49
      = org.apache.juli.logging.LogFactory.getLog(GzipOutputFilter.class);
50
51
45
    // ----------------------------------------------------- Static Initializer
52
    // ----------------------------------------------------- Static Initializer
46
53
47
54
Lines 82-88 Link Here
82
    public int doWrite(ByteChunk chunk, Response res)
89
    public int doWrite(ByteChunk chunk, Response res)
83
        throws IOException {
90
        throws IOException {
84
        if (compressionStream == null) {
91
        if (compressionStream == null) {
85
            compressionStream = new GZIPOutputStream(fakeOutputStream);
92
//          compressionStream = new GZIPOutputStream(fakeOutputStream);
93
          compressionStream = new FlushableGZIPOutputStream(fakeOutputStream);
86
        }
94
        }
87
        compressionStream.write(chunk.getBytes(), chunk.getStart(), 
95
        compressionStream.write(chunk.getBytes(), chunk.getStart(), 
88
                                chunk.getLength());
96
                                chunk.getLength());
Lines 92-97 Link Here
92
100
93
    // --------------------------------------------------- OutputFilter Methods
101
    // --------------------------------------------------- OutputFilter Methods
94
102
103
  /**
104
   * Added to allow flushing to happen for the gzip'ed outputstream
105
   */
106
  public void flush()
107
  {
108
    if (compressionStream != null)
109
    {
110
      try
111
      {
112
        if (log.isDebugEnabled())
113
        {
114
          log.debug("Flushing the compression stream!");
115
        }
116
        compressionStream.flush();
117
      }
118
      catch (IOException e)
119
      {
120
        if (log.isDebugEnabled())
121
        {
122
          log.debug("Ignored exception while flushing gzip filter", e);
123
        }
124
      }
125
    }
126
  }
95
127
96
    /**
128
    /**
97
     * Some filters need additional parameters from the response. All the 
129
     * Some filters need additional parameters from the response. All the 
Lines 117-123 Link Here
117
    public long end()
149
    public long end()
118
        throws IOException {
150
        throws IOException {
119
        if (compressionStream == null) {
151
        if (compressionStream == null) {
120
            compressionStream = new GZIPOutputStream(fakeOutputStream);
152
          compressionStream = new FlushableGZIPOutputStream(fakeOutputStream);
121
        }
153
        }
122
        compressionStream.finish();
154
        compressionStream.finish();
123
        compressionStream.close();
155
        compressionStream.close();
(-)java/org/apache/coyote/http11/filters/FlushableGZIPOutputStream.java (+97 lines)
Line 0 Link Here
1
package org.apache.coyote.http11.filters;
2
3
import java.util.zip.GZIPOutputStream;
4
import java.util.zip.Deflater;
5
import java.io.OutputStream;
6
import java.io.IOException;
7
8
/**
9
 * User: Jiong Wang (jiwang@linkedin.com)
10
 * Date: Jan 6, 2010
11
 * Time: 10:30:48 AM
12
 *
13
 */
14
public class FlushableGZIPOutputStream extends GZIPOutputStream
15
 {
16
   public FlushableGZIPOutputStream(OutputStream os) throws IOException
17
   {
18
     super(os);
19
   }
20
21
   private static final byte[] EMPTYBYTEARRAY = new byte[0];
22
   private boolean hasData = false;
23
24
   /**
25
    * Here we make sure we have received data, so that the header has
26
    * been for sure written to the output stream already.
27
    */
28
   @Override
29
   public synchronized void write(byte[] bytes, int i, int i1) throws IOException
30
   {
31
     super.write(bytes, i, i1);
32
     hasData = true;
33
   }
34
35
   @Override
36
   public synchronized void write(int i) throws IOException
37
   {
38
     super.write(i);
39
     hasData = true;
40
   }
41
42
   @Override
43
   public synchronized void write(byte[] bytes) throws IOException
44
   {
45
     super.write(bytes);
46
     hasData = true;
47
   }
48
49
   @Override
50
   public synchronized void flush() throws IOException
51
   {
52
     if (!hasData)
53
     {
54
       return; // do not allow the gzip header to be flushed on its own
55
     }
56
57
     // trick the deflater to flush
58
     /**
59
      * Now this is tricky: We force the Deflater to flush its data by
60
      * switching compression level. As yet, a perplexingly simple workaround
61
      * for http://developer.java.sun.com/developer/bugParade/bugs/4255743.html
62
      */
63
     if (!def.finished()) {
64
       def.setInput(EMPTYBYTEARRAY, 0, 0);
65
66
       def.setLevel(Deflater.NO_COMPRESSION);
67
       deflate();
68
69
       def.setLevel(Deflater.DEFAULT_COMPRESSION);
70
       deflate();
71
72
       out.flush();
73
     }
74
75
     hasData = false; // no more data to flush 
76
   }
77
78
   /*
79
    * Keep on calling deflate until it runs dry. The default implementation only does it once and can therefore
80
    * hold onto data when they need to be flushed out.
81
    */
82
  protected void deflate() throws IOException
83
  {
84
    int len;
85
    do
86
    {
87
      len = def.deflate(buf, 0, buf.length);
88
      if (len > 0)
89
      {
90
        out.write(buf, 0, len);
91
      }
92
    }
93
    while (len != 0);
94
  }
95
96
 }
97

Return to bug 48738