1   /**
2    *  Copyright 2003-2007 Greg Luck
3    *
4    *  Licensed under the Apache License, Version 2.0 (the "License");
5    *  you may not use this file except in compliance with the License.
6    *  You may obtain a copy of the License at
7    *
8    *      http://www.apache.org/licenses/LICENSE-2.0
9    *
10   *  Unless required by applicable law or agreed to in writing, software
11   *  distributed under the License is distributed on an "AS IS" BASIS,
12   *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   *  See the License for the specific language governing permissions and
14   *  limitations under the License.
15   */
16  
17  package net.sf.ehcache.constructs.web;
18  
19  import net.sf.ehcache.StopWatch;
20  import org.apache.commons.logging.Log;
21  import org.apache.commons.logging.LogFactory;
22  
23  import java.io.ByteArrayInputStream;
24  import java.io.ByteArrayOutputStream;
25  import java.io.File;
26  import java.io.FileInputStream;
27  import java.io.FileOutputStream;
28  import java.io.IOException;
29  import java.util.ArrayList;
30  import java.util.Collection;
31  import java.util.zip.GZIPInputStream;
32  import java.util.zip.GZIPOutputStream;
33  
34  /**
35   * PageInfo is a {@link java.io.Serializable} representation of a {@link javax.servlet.http.HttpServletResponse}.
36   *
37   * @author <a href="mailto:gluck@thoughtworks.com">Greg Luck</a>
38   * @version $Id: PageInfoTest.java 512 2007-07-10 09:18:45Z gregluck $
39   */
40  public class PageInfoTest extends AbstractWebTest {
41      private static final Log LOG = LogFactory.getLog(PageInfoTest.class.getName());
42  
43      private File testFile;
44  
45      /**
46       * Create gzip file in tmp
47       *
48       * @throws Exception
49       */
50      protected void setUp() throws Exception {
51          String testGzipFile = System.getProperty("java.io.tmpdir") + File.separator + "test.gzip";
52          testFile = new File(testGzipFile);
53          FileOutputStream fout = new FileOutputStream(testFile);
54          GZIPOutputStream gzipOutputStream = new GZIPOutputStream(fout);
55          for (int j = 0; j < 100; j++) {
56              for (int i = 0; i < 1000; i++) {
57                  gzipOutputStream.write(i);
58              }
59          }
60          gzipOutputStream.close();
61      }
62  
63      /**
64       * Remove the testFile
65       *
66       * @throws Exception
67       */
68      protected void tearDown() throws Exception {
69          testFile.delete();
70      }
71  
72      /**
73       * Tests what happens when we trick {@link PageInfo} in thinking the content is not gzipped
74       * and it tries to gzip it again.
75       * <p/>
76       * This will happen in the wild if an upstream filter or servlet gzips the content but fails to
77       * set the gzip header.
78       * <p/>
79       * The correct result is that the PageInfo constructor should throw a {@link AlreadyGzippedException}.
80       *
81       * @throws IOException
82       */
83      public void testAttemptedDoubleGzip() throws IOException {
84          byte[] gzip = getGzipFileAsBytes();
85          try {
86              new PageInfo(200, "text/plain", new ArrayList(), new ArrayList(), gzip, true);
87              fail();
88          } catch (AlreadyGzippedException e) {
89              assertEquals("The byte[] is already gzipped. It should not be gzipped again.", e.getMessage());
90          }
91  
92      }
93  
94      /**
95       * Based on the gunzip1 implementation.
96       * <p/>
97       * Takes 9ms for the 100kb test document on the reference machine
98       * @throws IOException
99       * @throws AlreadyGzippedException
100      * @throws InterruptedException
101      */
102     public void testUsedGunzipImplementationPerformance() throws IOException, AlreadyGzippedException, InterruptedException {
103         byte[] gzip = getGzipFileAsBytes();
104         Collection headers = new ArrayList();
105         String[] header = new String[]{"Content-Encoding", "gzip"};
106         headers.add(header);
107         PageInfo pageInfo = new PageInfo(200, "text/plain", headers, new ArrayList(), gzip, true);
108         long initialMemoryUsed = memoryUsed();
109         StopWatch stopWatch = new StopWatch();
110         int size = 0;
111         long timeTaken = 0;
112         long finalMemoryUsed = 0;
113         long incrementalMemoryUsed = 0;
114         byte[] ungzipped = null;
115 
116         //warmup JVM
117         for (int i = 0; i < 5; i++) {
118             ungzipped = pageInfo.getUngzippedBody();
119             Thread.sleep(200);
120         }
121         stopWatch.getElapsedTime();
122 
123         for (int i = 0; i < 50; i++) {
124             ungzipped = pageInfo.getUngzippedBody();
125         }
126         size = ungzipped.length;
127         timeTaken = stopWatch.getElapsedTime() / 50;
128         finalMemoryUsed = memoryUsed();
129         incrementalMemoryUsed = finalMemoryUsed - initialMemoryUsed;
130         LOG.info("Average gunzip time: " + timeTaken
131                 + ". Memory used: " + incrementalMemoryUsed
132                 + ". Size: " + size);
133         assertEquals(100000, size);
134         assertTrue(timeTaken < 30);
135     }
136 
137 
138     /**
139      * Tests the performance of gunzip using a variety of implementations.
140      */
141     public void testGunzipPerformance() throws IOException, InterruptedException {
142         long initialMemoryUsed = memoryUsed();
143         byte[] gzip = getGzipFileAsBytes();
144         byte[] ungzipped = null;
145         int size = 0;
146         long timeTaken = 0;
147         long finalMemoryUsed = 0;
148         long incrementalMemoryUsed = 0;
149         StopWatch stopWatch = new StopWatch();
150 
151         //warmup JVM
152         for (int i = 0; i < 5; i++) {
153             ungzipped = ungzip1(gzip);
154             ungzipped = ungzip2(gzip);
155             ungzipped = ungzip3(gzip);
156             ungzipped = ungzip4(gzip);
157             ungzipped = ungzip5(gzip);
158             Thread.sleep(200);
159         }
160 
161         stopWatch.getElapsedTime();
162         for (int i = 0; i < 50; i++) {
163             ungzipped = ungzip1(gzip);
164         }
165         size = ungzipped.length;
166         timeTaken = stopWatch.getElapsedTime() / 50;
167         finalMemoryUsed = memoryUsed();
168         incrementalMemoryUsed = finalMemoryUsed - initialMemoryUsed;
169         LOG.info("Average gunzip time: " + timeTaken
170                 + ". Memory used: " + incrementalMemoryUsed
171                 + ". Size: " + size);
172         assertEquals(100000, size);
173 
174         stopWatch.getElapsedTime();
175         ungzipped = ungzip2(gzip);
176         for (int i = 0; i < 50; i++) {
177             size = ungzipped.length;
178         }
179         timeTaken = stopWatch.getElapsedTime() / 50;
180         finalMemoryUsed = memoryUsed();
181         incrementalMemoryUsed = finalMemoryUsed - initialMemoryUsed;
182         LOG.info("Average gunzip time: " + timeTaken
183                 + ". Memory used: " + incrementalMemoryUsed
184                 + ". Size: " + size);
185         assertEquals(100000, size);
186 
187         stopWatch.getElapsedTime();
188         ungzipped = ungzip3(gzip);
189         for (int i = 0; i < 50; i++) {
190             size = ungzipped.length;
191         }
192         timeTaken = stopWatch.getElapsedTime() / 50;
193         finalMemoryUsed = memoryUsed();
194         incrementalMemoryUsed = finalMemoryUsed - initialMemoryUsed;
195         LOG.info("Average gunzip time: " + timeTaken
196                 + ". Memory used: " + incrementalMemoryUsed
197                 + ". Size: " + size);
198         assertEquals(100000, size);
199 
200         stopWatch.getElapsedTime();
201         for (int i = 0; i < 50; i++) {
202             ungzipped = ungzip5(gzip);
203         }
204         size = ungzipped.length;
205         timeTaken = stopWatch.getElapsedTime() / 50;
206         finalMemoryUsed = memoryUsed();
207         incrementalMemoryUsed = finalMemoryUsed - initialMemoryUsed;
208         LOG.info("Average gunzip time: " + timeTaken
209                 + ". Memory used: " + incrementalMemoryUsed
210                 + ". Size: " + size);
211         assertEquals(100000, size);
212 
213         //Throws out the numbers. Go last.
214         stopWatch.getElapsedTime();
215         for (int i = 0; i < 5; i++) {
216             ungzipped = ungzip4(gzip);
217         }
218         size = ungzipped.length;
219         timeTaken = stopWatch.getElapsedTime() / 5;
220         finalMemoryUsed = memoryUsed();
221         incrementalMemoryUsed = finalMemoryUsed - initialMemoryUsed;
222         LOG.info("Average gunzip time: " + timeTaken
223                 + ". Memory used: " + incrementalMemoryUsed
224                 + ". Size: " + size);
225         assertEquals(100000, size);
226     }
227 
228     /**
229      * Tests the performance and correctness of gzip.
230      */
231     public void testGzipPerformance() throws IOException, InterruptedException {
232         long initialMemoryUsed = memoryUsed();
233         byte[] gzip = getGzipFileAsBytes();
234         byte[] ungzipped = null;
235         int size = 0;
236         long timeTaken = 0;
237         long finalMemoryUsed = 0;
238         long incrementalMemoryUsed = 0;
239         StopWatch stopWatch = new StopWatch();
240 
241         ungzipped = ungzip1(gzip);
242         stopWatch.getElapsedTime();
243         for (int i = 0; i < 50; i++) {
244             gzip = gzip(ungzipped);
245         }
246         timeTaken = stopWatch.getElapsedTime() / 50;
247         ungzipped = ungzip1(gzip);
248         size = ungzipped.length;
249         assertEquals(100000, size);
250         finalMemoryUsed = memoryUsed();
251         incrementalMemoryUsed = finalMemoryUsed - initialMemoryUsed;
252         LOG.info("Average gzip time: " + timeTaken
253                 + ". Memory used: " + incrementalMemoryUsed
254                 + ". Size: " + size);
255     }
256 
257 
258     /**
259      * Tests that we can reliably determine if a byte[] is gzipped.
260      * Note that this test demonstrates that byte[] can be gzipped multiple times
261      * and are still gzipped i.e. though gzip has been run multiple times a valid
262      * gzip file results, although ungzipping once won't be enough to get back to
263      * uncompressed source byte[]
264      */
265     public void magicNumberTest() throws IOException {
266         byte[] gzip = getGzipFileAsBytes();
267 
268         //A large generated gzip file
269         assertTrue(PageInfo.isGzipped(gzip));
270         //Short String
271         assertTrue(PageInfo.isGzipped(gzip("The rain in spain".getBytes())));
272         //Null
273         assertTrue(!PageInfo.isGzipped(null));
274         //Less than two bytes
275         assertTrue(!PageInfo.isGzipped(new byte[]{0x11}));
276         //Not Gzipped
277         assertTrue(!PageInfo.isGzipped("This is not gzipped".getBytes()));
278         //Double Gzipped
279         assertTrue(PageInfo.isGzipped(gzip(getGzipFileAsBytes())));
280         //Triple Gzipped
281         assertTrue(PageInfo.isGzipped(gzip(gzip(getGzipFileAsBytes()))));
282 
283     }
284 
285     /**
286      * @param ungzipped the bytes to be gzipped
287      * @return gzipped bytes
288      */
289     private byte[] gzip(byte[] ungzipped) throws IOException {
290         final ByteArrayOutputStream bytes = new ByteArrayOutputStream();
291         final GZIPOutputStream gzipOutputStream = new GZIPOutputStream(bytes);
292         gzipOutputStream.write(ungzipped);
293         gzipOutputStream.close();
294         return bytes.toByteArray();
295     }
296 
297 
298     /**
299      * A high performance implementation, although not as fast as gunzip3.
300      * gunzips 100000 of ungzipped content in 9ms on the reference machine.
301      * It does not use a fixed size buffer and is therefore suitable for arbitrary
302      * length arrays.
303      * @param gzipped
304      * @return
305      * @throws IOException
306      */
307     public byte[] ungzip1(final byte[] gzipped) throws IOException {
308         final GZIPInputStream inputStream = new GZIPInputStream(new ByteArrayInputStream(gzipped));
309         ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(gzipped.length);
310         final byte[] buffer = new byte[4096];
311         int bytesRead = 0;
312         while (bytesRead != -1) {
313             bytesRead = inputStream.read(buffer, 0, 4096);
314             if (bytesRead != -1) {
315                 byteArrayOutputStream.write(buffer, 0, bytesRead);
316             }
317         }
318         byte[] ungzipped = byteArrayOutputStream.toByteArray();
319         inputStream.close();
320         byteArrayOutputStream.close();
321         return ungzipped;
322     }
323 
324     private byte[] ungzip2(final byte[] gzip) throws IOException {
325         final GZIPInputStream inputStream = new GZIPInputStream(new ByteArrayInputStream(gzip));
326         final byte[] buffer = new byte[1500000];
327         int bytesRead = 0;
328         int counter = 0;
329         while (bytesRead != -1) {
330             bytesRead = inputStream.read(buffer, counter, 4096);
331             counter += bytesRead;
332         }
333         //Revert the last -1 when the end of stream was reached
334         counter++;
335         byte[] unzipped = new byte[counter];
336         System.arraycopy(buffer, 0, unzipped, 0, counter);
337         return unzipped;
338     }
339 
340     private byte[] ungzip3(final byte[] gzip) throws IOException {
341         int size = 0;
342         int counter = 0;
343         GZIPInputStream gzipInputStream = new GZIPInputStream(new ByteArrayInputStream(gzip));
344         int bytesRead = 0;
345         byte[] buffer = new byte[500000];
346         byte[] tempBuffer = new byte[4096];
347         counter = 0;
348         while (bytesRead != -1) {
349             bytesRead = gzipInputStream.read(tempBuffer);
350             if (bytesRead != -1) {
351                 System.arraycopy(tempBuffer, 0, buffer, counter, bytesRead);
352                 counter += bytesRead;
353             }
354         }
355         gzipInputStream.close();
356         size = counter;
357         byte[] unzipped = new byte[size];
358         System.arraycopy(buffer, 0, unzipped, 0, counter);
359         return unzipped;
360     }
361 
362     private byte[] ungzip4(final byte[] gzip) throws IOException {
363         byte[] buffer = new byte[500000];
364         int size = 0;
365 
366         ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(gzip);
367         GZIPInputStream gzipInputStream = new GZIPInputStream(byteArrayInputStream);
368 
369         int nextByte = 0;
370         int counter = 0;
371         while (nextByte != -1) {
372             nextByte = gzipInputStream.read();
373             if (nextByte != -1) {
374                 buffer[counter] = (byte) nextByte;
375                 counter++;
376                 size = counter;
377             }
378         }
379 
380         gzipInputStream.close();
381         byte[] unzipped = new byte[counter];
382         System.arraycopy(buffer, 0, unzipped, 0, counter);
383         return unzipped;
384     }
385 
386     private byte[] ungzip5(final byte[] gzip) throws IOException {
387         int size = 0;
388         int counter = 0;
389         GZIPInputStream gzipInputStream = new GZIPInputStream(new ByteArrayInputStream(gzip));
390         int bytesRead = 0;
391         byte[] buffer = new byte[32768];
392         byte[] tempBuffer = new byte[4096];
393         counter = 0;
394         while (bytesRead != -1) {
395             bytesRead = gzipInputStream.read(tempBuffer);
396             if (bytesRead != -1) {
397                 if (buffer.length < counter + bytesRead) {
398                     byte[] newBuffer = new byte[buffer.length + 32768];
399                     System.arraycopy(buffer, 0, newBuffer, 0, counter);
400                     buffer = newBuffer;
401                 }
402                 System.arraycopy(tempBuffer, 0, buffer, counter, bytesRead);
403                 counter += bytesRead;
404             }
405         }
406         gzipInputStream.close();
407         size = counter;
408         byte[] unzipped = new byte[size];
409         System.arraycopy(buffer, 0, unzipped, 0, counter);
410         return unzipped;
411     }
412 
413     private long memoryUsed() {
414         return Runtime.getRuntime().maxMemory() - Runtime.getRuntime().freeMemory();
415     }
416 
417     private byte[] getGzipFileAsBytes() throws IOException {
418         byte[] buffer = new byte[500000];
419         FileInputStream fileInputStream = new FileInputStream(testFile);
420         int fileSize = fileInputStream.read(buffer);
421         byte[] gzip = new byte[fileSize];
422         System.arraycopy(buffer, 0, gzip, 0, fileSize);
423         return gzip;
424     }
425 
426 }