View Javadoc

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 org.apache.commons.logging.Log;
20  import org.apache.commons.logging.LogFactory;
21  
22  import javax.servlet.http.Cookie;
23  import javax.servlet.http.HttpServletResponse;
24  import java.io.ByteArrayInputStream;
25  import java.io.ByteArrayOutputStream;
26  import java.io.IOException;
27  import java.io.Serializable;
28  import java.util.ArrayList;
29  import java.util.Collection;
30  import java.util.Iterator;
31  import java.util.List;
32  import java.util.zip.GZIPInputStream;
33  import java.util.zip.GZIPOutputStream;
34  
35  /**
36   * A Serializable representation of a {@link HttpServletResponse}.
37   *
38   * @author <a href="mailto:amurdoch@thoughtworks.com">Adam Murdoch</a>
39   * @author <a href="mailto:gluck@thoughtworks.com">Greg Luck</a>
40   * @version $Id: PageInfo.java 512 2007-07-10 09:18:45Z gregluck $
41   */
42  public class PageInfo implements Serializable {
43      private static final Log LOG = LogFactory.getLog(PageInfo.class.getName());
44      private static final int FOUR_KB = 4196;
45      private static final int GZIP_MAGIC_NUMBER_BYTE_1 = 31;
46      private static final int GZIP_MAGIC_NUMBER_BYTE_2 = -117;
47      private final ArrayList headers = new ArrayList();
48      private final ArrayList serializableCookies = new ArrayList();
49      private String contentType;
50      private byte[] gzippedBody;
51      private byte[] ungzippedBody;
52      private int statusCode;
53      private boolean storeGzipped;
54  
55      /**
56       * Creates a PageInfo.
57       * <p/>
58       *
59       * @param statusCode
60       * @param contentType
61       * @param headers
62       * @param cookies
63       * @param body
64       * @param storeGzipped set this to false for images and page fragments which should never
65       *                     be gzipped.
66       */
67      public PageInfo(final int statusCode, final String contentType, final Collection headers, final Collection cookies,
68                      final byte[] body, boolean storeGzipped) throws AlreadyGzippedException {
69          this.headers.addAll(headers);
70          this.headers.remove("Content-Encoding");
71          this.contentType = contentType;
72          this.storeGzipped = storeGzipped;
73          this.statusCode = statusCode;
74  
75          extractCookies(cookies);
76  
77          try {
78              if (storeGzipped) {
79                  //gunzip on demand
80                  ungzippedBody = null;
81                  if (isBodyParameterGzipped()) {
82                      gzippedBody = body;
83                  } else {
84                      gzippedBody = gzip(body);
85                  }
86              } else {
87                  if (isBodyParameterGzipped()) {
88                      throw new IllegalArgumentException("Non gzip content has been gzipped.");
89                  } else {
90                      ungzippedBody = body;
91                  }
92              }
93          } catch (IOException e) {
94              LOG.error("Error ungzipping gzipped body", e);
95          }
96  
97  
98      }
99  
100     private void extractCookies(Collection cookies) {
101         for (Iterator iterator = cookies.iterator(); iterator.hasNext();) {
102             final Cookie cookie = (Cookie) iterator.next();
103             serializableCookies.add(new SerializableCookie(cookie));
104         }
105     }
106 
107     /**
108      * @param ungzipped the bytes to be gzipped
109      * @return gzipped bytes
110      */
111     private byte[] gzip(byte[] ungzipped) throws IOException, AlreadyGzippedException {
112         if (isGzipped(ungzipped)) {
113             throw new AlreadyGzippedException("The byte[] is already gzipped. It should not be gzipped again.");
114         }
115         final ByteArrayOutputStream bytes = new ByteArrayOutputStream();
116         final GZIPOutputStream gzipOutputStream = new GZIPOutputStream(bytes);
117         gzipOutputStream.write(ungzipped);
118         gzipOutputStream.close();
119         return bytes.toByteArray();
120     }
121 
122     /**
123      * The response body will be assumed to be gzipped if the GZIP header has been set.
124      *
125      * @return true if the body is gzipped
126      */
127     private boolean isBodyParameterGzipped() {
128         for (int i = 0; i < headers.size(); i++) {
129             String[] keyValuePair = (String[]) headers.get(i);
130             if (keyValuePair[1].equals("gzip")) {
131                 return true;
132             }
133         }
134         return false;
135     }
136 
137     /**
138      * Checks the first two bytes of the candidate byte array for the magic number 0x677a.
139      * This magic number was obtained from /usr/share/file/magic. The line for gzip is:
140      * <p/>
141      * <code>
142      * >>14    beshort 0x677a          (gzipped)
143      * </code>
144      *
145      * @param candidate the byte array to check
146      * @return true if gzipped, false if null, less than two bytes or not gzipped
147      */
148     public static boolean isGzipped(byte[] candidate) {
149         if (candidate == null || candidate.length < 2) {
150             return false;
151         } else {
152             return (candidate[0] == GZIP_MAGIC_NUMBER_BYTE_1 && candidate[1] == GZIP_MAGIC_NUMBER_BYTE_2);
153         }
154     }
155 
156     /**
157      * @return the content type of the response.
158      */
159     public String getContentType() {
160         return contentType;
161     }
162 
163     /**
164      * @return the gzipped version of the body if the content is storeGzipped, otherwise null
165      */
166     public byte[] getGzippedBody() {
167         if (storeGzipped) {
168             return gzippedBody;
169         } else {
170             return null;
171         }
172     }
173 
174     /**
175      * Returns the headers of the response.
176      */
177     public List getHeaders() {
178         return headers;
179     }
180 
181     /**
182      * Returns the cookies of the response.
183      */
184     public List getSerializableCookies() {
185         return serializableCookies;
186     }
187 
188     /**
189      * Returns the status code of the response.
190      */
191     public int getStatusCode() {
192         return statusCode;
193     }
194 
195     /**
196      * @return the ungzipped version of the body. This gunzipped on demand when storedGzipped, otherwise
197      *         the ungzipped body is returned.
198      */
199     public byte[] getUngzippedBody() throws IOException {
200         if (storeGzipped) {
201             return ungzip(gzippedBody);
202         } else {
203             return ungzippedBody;
204         }
205     }
206 
207     /**
208      * A highly performant ungzip implementation. Do not refactor this without taking new timings.
209      * See ElementTest in ehcache for timings
210      *
211      * @param gzipped the gzipped content
212      * @return an ungzipped byte[]
213      * @throws java.io.IOException
214      */
215     private byte[] ungzip(final byte[] gzipped) throws IOException {
216         final GZIPInputStream inputStream = new GZIPInputStream(new ByteArrayInputStream(gzipped));
217         ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(gzipped.length);
218         final byte[] buffer = new byte[FOUR_KB];
219         int bytesRead = 0;
220         while (bytesRead != -1) {
221             bytesRead = inputStream.read(buffer, 0, FOUR_KB);
222             if (bytesRead != -1) {
223                 byteArrayOutputStream.write(buffer, 0, bytesRead);
224             }
225         }
226         byte[] ungzipped = byteArrayOutputStream.toByteArray();
227         inputStream.close();
228         byteArrayOutputStream.close();
229         return ungzipped;
230     }
231 
232     /**
233      * @return true if there is a non null gzipped body
234      */
235     public boolean hasGzippedBody() {
236         return (gzippedBody != null);
237     }
238 
239     /**
240      * @return true if there is a non null ungzipped body
241      */
242     public boolean hasUngzippedBody() {
243         return (ungzippedBody != null);
244     }
245 
246     /**
247      * Returns true if the response is ok.
248      */
249     public boolean isOk() {
250         return (statusCode == HttpServletResponse.SC_OK);
251     }
252 }
253