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 018 package compressionFilters; 019 020 import java.io.IOException; 021 import java.util.zip.GZIPOutputStream; 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: 514091 $, $Date: 2007-03-03 01:26:39 -0500 (Sat, 03 Mar 2007) $ 033 */ 034 035 public class CompressionResponseStream 036 extends ServletOutputStream { 037 038 039 // ----------------------------------------------------------- Constructors 040 041 042 /** 043 * Construct a servlet output stream associated with the specified Response. 044 * 045 * @param response The associated response 046 */ 047 public CompressionResponseStream(HttpServletResponse response) throws IOException{ 048 049 super(); 050 closed = false; 051 this.response = response; 052 this.output = response.getOutputStream(); 053 054 } 055 056 057 // ----------------------------------------------------- Instance Variables 058 059 060 /** 061 * The threshold number which decides to compress or not. 062 * Users can configure in web.xml to set it to fit their needs. 063 */ 064 protected int compressionThreshold = 0; 065 066 /** 067 * Debug level 068 */ 069 private int debug = 0; 070 071 /** 072 * The buffer through which all of our output bytes are passed. 073 */ 074 protected byte[] buffer = null; 075 076 /** 077 * The number of data bytes currently in the buffer. 078 */ 079 protected int bufferCount = 0; 080 081 /** 082 * The underlying gzip output stream to which we should write data. 083 */ 084 protected GZIPOutputStream gzipstream = null; 085 086 /** 087 * Has this stream been closed? 088 */ 089 protected boolean closed = false; 090 091 /** 092 * The content length past which we will not write, or -1 if there is 093 * no defined content length. 094 */ 095 protected int length = -1; 096 097 /** 098 * The response with which this servlet output stream is associated. 099 */ 100 protected HttpServletResponse response = null; 101 102 /** 103 * The underlying servket output stream to which we should write data. 104 */ 105 protected ServletOutputStream output = null; 106 107 108 // --------------------------------------------------------- Public Methods 109 110 /** 111 * Set debug level 112 */ 113 public void setDebugLevel(int debug) { 114 this.debug = debug; 115 } 116 117 118 /** 119 * Set the compressionThreshold number and create buffer for this size 120 */ 121 protected void setBuffer(int threshold) { 122 compressionThreshold = threshold; 123 buffer = new byte[compressionThreshold]; 124 if (debug > 1) { 125 System.out.println("buffer is set to "+compressionThreshold); 126 } 127 } 128 129 /** 130 * Close this output stream, causing any buffered data to be flushed and 131 * any further output data to throw an IOException. 132 */ 133 public void close() throws IOException { 134 135 if (debug > 1) { 136 System.out.println("close() @ CompressionResponseStream"); 137 } 138 if (closed) 139 throw new IOException("This output stream has already been closed"); 140 141 if (gzipstream != null) { 142 flushToGZip(); 143 gzipstream.close(); 144 gzipstream = null; 145 } else { 146 if (bufferCount > 0) { 147 if (debug > 2) { 148 System.out.print("output.write("); 149 System.out.write(buffer, 0, bufferCount); 150 System.out.println(")"); 151 } 152 output.write(buffer, 0, bufferCount); 153 bufferCount = 0; 154 } 155 } 156 157 output.close(); 158 closed = true; 159 160 } 161 162 163 /** 164 * Flush any buffered data for this output stream, which also causes the 165 * response to be committed. 166 */ 167 public void flush() throws IOException { 168 169 if (debug > 1) { 170 System.out.println("flush() @ CompressionResponseStream"); 171 } 172 if (closed) { 173 throw new IOException("Cannot flush a closed output stream"); 174 } 175 176 if (gzipstream != null) { 177 gzipstream.flush(); 178 } 179 180 } 181 182 public void flushToGZip() throws IOException { 183 184 if (debug > 1) { 185 System.out.println("flushToGZip() @ CompressionResponseStream"); 186 } 187 if (bufferCount > 0) { 188 if (debug > 1) { 189 System.out.println("flushing out to GZipStream, bufferCount = " + bufferCount); 190 } 191 writeToGZip(buffer, 0, bufferCount); 192 bufferCount = 0; 193 } 194 195 } 196 197 /** 198 * Write the specified byte to our output stream. 199 * 200 * @param b The byte to be written 201 * 202 * @exception IOException if an input/output error occurs 203 */ 204 public void write(int b) throws IOException { 205 206 if (debug > 1) { 207 System.out.println("write "+b+" in CompressionResponseStream "); 208 } 209 if (closed) 210 throw new IOException("Cannot write to a closed output stream"); 211 212 if (bufferCount >= buffer.length) { 213 flushToGZip(); 214 } 215 216 buffer[bufferCount++] = (byte) b; 217 218 } 219 220 221 /** 222 * Write <code>b.length</code> bytes from the specified byte array 223 * to our output stream. 224 * 225 * @param b The byte array to be written 226 * 227 * @exception IOException if an input/output error occurs 228 */ 229 public void write(byte b[]) throws IOException { 230 231 write(b, 0, b.length); 232 233 } 234 235 236 /** 237 * Write <code>len</code> bytes from the specified byte array, starting 238 * at the specified offset, to our output stream. 239 * 240 * @param b The byte array containing the bytes to be written 241 * @param off Zero-relative starting offset of the bytes to be written 242 * @param len The number of bytes to be written 243 * 244 * @exception IOException if an input/output error occurs 245 */ 246 public void write(byte b[], int off, int len) throws IOException { 247 248 if (debug > 1) { 249 System.out.println("write, bufferCount = " + bufferCount + " len = " + len + " off = " + off); 250 } 251 if (debug > 2) { 252 System.out.print("write("); 253 System.out.write(b, off, len); 254 System.out.println(")"); 255 } 256 257 if (closed) 258 throw new IOException("Cannot write to a closed output stream"); 259 260 if (len == 0) 261 return; 262 263 // Can we write into buffer ? 264 if (len <= (buffer.length - bufferCount)) { 265 System.arraycopy(b, off, buffer, bufferCount, len); 266 bufferCount += len; 267 return; 268 } 269 270 // There is not enough space in buffer. Flush it ... 271 flushToGZip(); 272 273 // ... and try again. Note, that bufferCount = 0 here ! 274 if (len <= (buffer.length - bufferCount)) { 275 System.arraycopy(b, off, buffer, bufferCount, len); 276 bufferCount += len; 277 return; 278 } 279 280 // write direct to gzip 281 writeToGZip(b, off, len); 282 } 283 284 public void writeToGZip(byte b[], int off, int len) throws IOException { 285 286 if (debug > 1) { 287 System.out.println("writeToGZip, len = " + len); 288 } 289 if (debug > 2) { 290 System.out.print("writeToGZip("); 291 System.out.write(b, off, len); 292 System.out.println(")"); 293 } 294 if (gzipstream == null) { 295 if (debug > 1) { 296 System.out.println("new GZIPOutputStream"); 297 } 298 response.addHeader("Content-Encoding", "gzip"); 299 gzipstream = new GZIPOutputStream(output); 300 } 301 gzipstream.write(b, off, len); 302 303 } 304 305 306 // -------------------------------------------------------- Package Methods 307 308 309 /** 310 * Has this response stream been closed? 311 */ 312 public boolean closed() { 313 314 return (this.closed); 315 316 } 317 318 }