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