001 /* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017 package compressionFilters; 018 019 import java.io.IOException; 020 import java.util.zip.GZIPOutputStream; 021 022 import javax.servlet.ServletOutputStream; 023 import javax.servlet.http.HttpServletResponse; 024 025 026 /** 027 * Implementation of <b>ServletOutputStream</b> that works with 028 * the CompressionServletResponseWrapper implementation. 029 * 030 * @author Amy Roh 031 * @author Dmitri Valdin 032 * @version $Revision: 664175 $, $Date: 2008-06-06 18:43:44 -0400 (Fri, 06 Jun 2008) $ 033 */ 034 035 public class CompressionResponseStream 036 extends ServletOutputStream { 037 038 // ----------------------------------------------------------- Constructors 039 040 041 /** 042 * Construct a servlet output stream associated with the specified Response. 043 * 044 * @param response The associated response 045 */ 046 public CompressionResponseStream(HttpServletResponse response) throws IOException { 047 048 super(); 049 closed = false; 050 this.response = response; 051 this.output = response.getOutputStream(); 052 053 } 054 055 // ----------------------------------------------------- Instance Variables 056 057 058 /** 059 * The threshold number which decides to compress or not. 060 * Users can configure in web.xml to set it to fit their needs. 061 */ 062 protected int compressionThreshold = 0; 063 064 /** 065 * Debug level 066 */ 067 private int debug = 0; 068 069 /** 070 * The buffer through which all of our output bytes are passed. 071 */ 072 protected byte[] buffer = null; 073 074 /** 075 * The number of data bytes currently in the buffer. 076 */ 077 protected int bufferCount = 0; 078 079 /** 080 * The underlying gzip output stream to which we should write data. 081 */ 082 protected GZIPOutputStream gzipstream = null; 083 084 /** 085 * Has this stream been closed? 086 */ 087 protected boolean closed = false; 088 089 /** 090 * The content length past which we will not write, or -1 if there is 091 * no defined content length. 092 */ 093 protected int length = -1; 094 095 /** 096 * The response with which this servlet output stream is associated. 097 */ 098 protected HttpServletResponse response = null; 099 100 /** 101 * The underlying servket output stream to which we should write data. 102 */ 103 protected ServletOutputStream output = null; 104 105 // --------------------------------------------------------- Public Methods 106 107 /** 108 * Set debug level 109 */ 110 public void setDebugLevel(int debug) { 111 this.debug = debug; 112 } 113 114 115 /** 116 * Set the compressionThreshold number and create buffer for this size 117 */ 118 protected void setBuffer(int threshold) { 119 compressionThreshold = threshold; 120 buffer = new byte[compressionThreshold]; 121 if (debug > 1) { 122 System.out.println("buffer is set to " + compressionThreshold); 123 } 124 } 125 126 /** 127 * Close this output stream, causing any buffered data to be flushed and 128 * any further output data to throw an IOException. 129 */ 130 public void close() throws IOException { 131 132 if (debug > 1) { 133 System.out.println("close() @ CompressionResponseStream"); 134 } 135 if (closed) 136 throw new IOException("This output stream has already been closed"); 137 138 if (gzipstream != null) { 139 flushToGZip(); 140 gzipstream.close(); 141 gzipstream = null; 142 } else { 143 if (bufferCount > 0) { 144 if (debug > 2) { 145 System.out.print("output.write("); 146 System.out.write(buffer, 0, bufferCount); 147 System.out.println(")"); 148 } 149 output.write(buffer, 0, bufferCount); 150 bufferCount = 0; 151 } 152 } 153 154 output.close(); 155 closed = true; 156 157 } 158 159 160 /** 161 * Flush any buffered data for this output stream, which also causes the 162 * response to be committed. 163 */ 164 public void flush() throws IOException { 165 166 if (debug > 1) { 167 System.out.println("flush() @ CompressionResponseStream"); 168 } 169 if (closed) { 170 throw new IOException("Cannot flush a closed output stream"); 171 } 172 173 if (gzipstream != null) { 174 gzipstream.flush(); 175 } 176 177 } 178 179 public void flushToGZip() throws IOException { 180 181 if (debug > 1) { 182 System.out.println("flushToGZip() @ CompressionResponseStream"); 183 } 184 if (bufferCount > 0) { 185 if (debug > 1) { 186 System.out.println("flushing out to GZipStream, bufferCount = " + bufferCount); 187 } 188 writeToGZip(buffer, 0, bufferCount); 189 bufferCount = 0; 190 } 191 192 } 193 194 /** 195 * Write the specified byte to our output stream. 196 * 197 * @param b The byte to be written 198 * @throws IOException if an input/output error occurs 199 */ 200 public void write(int b) throws IOException { 201 202 if (debug > 1) { 203 System.out.println("write " + b + " in CompressionResponseStream "); 204 } 205 if (closed) 206 throw new IOException("Cannot write to a closed output stream"); 207 208 if (bufferCount >= buffer.length) { 209 flushToGZip(); 210 } 211 212 buffer[bufferCount++] = (byte) b; 213 214 } 215 216 217 /** 218 * Write <code>b.length</code> bytes from the specified byte array 219 * to our output stream. 220 * 221 * @param b The byte array to be written 222 * @throws IOException if an input/output error occurs 223 */ 224 public void write(byte b[]) throws IOException { 225 226 write(b, 0, b.length); 227 228 } 229 230 231 /** 232 * Write <code>len</code> bytes from the specified byte array, starting 233 * at the specified offset, to our output stream. 234 * 235 * @param b The byte array containing the bytes to be written 236 * @param off Zero-relative starting offset of the bytes to be written 237 * @param len The number of bytes to be written 238 * @throws IOException if an input/output error occurs 239 */ 240 public void write(byte b[], int off, int len) throws IOException { 241 242 if (debug > 1) { 243 System.out.println("write, bufferCount = " + bufferCount + " len = " + len + " off = " + off); 244 } 245 if (debug > 2) { 246 System.out.print("write("); 247 System.out.write(b, off, len); 248 System.out.println(")"); 249 } 250 251 if (closed) 252 throw new IOException("Cannot write to a closed output stream"); 253 254 if (len == 0) 255 return; 256 257 // Can we write into buffer ? 258 if (len <= (buffer.length - bufferCount)) { 259 System.arraycopy(b, off, buffer, bufferCount, len); 260 bufferCount += len; 261 return; 262 } 263 264 // There is not enough space in buffer. Flush it ... 265 flushToGZip(); 266 267 // ... and try again. Note, that bufferCount = 0 here ! 268 if (len <= (buffer.length - bufferCount)) { 269 System.arraycopy(b, off, buffer, bufferCount, len); 270 bufferCount += len; 271 return; 272 } 273 274 // write direct to gzip 275 writeToGZip(b, off, len); 276 } 277 278 public void writeToGZip(byte b[], int off, int len) throws IOException { 279 280 if (debug > 1) { 281 System.out.println("writeToGZip, len = " + len); 282 } 283 if (debug > 2) { 284 System.out.print("writeToGZip("); 285 System.out.write(b, off, len); 286 System.out.println(")"); 287 } 288 if (gzipstream == null) { 289 if (debug > 1) { 290 System.out.println("new GZIPOutputStream"); 291 } 292 response.addHeader("Content-Encoding", "gzip"); 293 gzipstream = new GZIPOutputStream(output); 294 } 295 gzipstream.write(b, off, len); 296 297 } 298 299 // -------------------------------------------------------- Package Methods 300 301 302 /** 303 * Has this response stream been closed? 304 */ 305 public boolean closed() { 306 307 return (this.closed); 308 309 } 310 311 }