1 /*
2  *  Licensed to the Apache Software Foundation (ASF) under one or more
3  *  contributor license agreements.  See the NOTICE file distributed with
4  *  this work for additional information regarding copyright ownership.
5  *  The ASF licenses this file to You under the Apache License, Version 2.0
6  *  (the "License"); you may not use this file except in compliance with
7  *  the License.  You may obtain a copy of the License at
8  *
9  *      http://www.apache.org/licenses/LICENSE-2.0
10  *
11  *  Unless required by applicable law or agreed to in writing, software
12  *  distributed under the License is distributed on an "AS IS" BASIS,
13  *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  *  See the License for the specific language governing permissions and
15  *  limitations under the License.
16  */

17 package org.apache.coyote.http11.filters;
18
19 import java.io.IOException;
20 import java.io.OutputStream;
21 import java.nio.ByteBuffer;
22 import java.util.zip.GZIPOutputStream;
23
24 import org.apache.coyote.Response;
25 import org.apache.coyote.http11.HttpOutputBuffer;
26 import org.apache.coyote.http11.OutputFilter;
27 import org.apache.juli.logging.Log;
28 import org.apache.juli.logging.LogFactory;
29
30 /**
31  * Gzip output filter.
32  *
33  * @author Remy Maucherat
34  */

35 public class GzipOutputFilter implements OutputFilter {
36
37     protected static final Log log = LogFactory.getLog(GzipOutputFilter.class);
38
39
40     // ----------------------------------------------------- Instance Variables
41
42     /**
43      * Next buffer in the pipeline.
44      */

45     protected HttpOutputBuffer buffer;
46
47
48     /**
49      * Compression output stream.
50      */

51     protected GZIPOutputStream compressionStream = null;
52
53
54     /**
55      * Fake internal output stream.
56      */

57     protected final OutputStream fakeOutputStream = new FakeOutputStream();
58
59
60     // --------------------------------------------------- OutputBuffer Methods
61
62     @Override
63     public int doWrite(ByteBuffer chunk) throws IOException {
64         if (compressionStream == null) {
65             compressionStream = new GZIPOutputStream(fakeOutputStream, true);
66         }
67         int len = chunk.remaining();
68         if (chunk.hasArray()) {
69             compressionStream.write(chunk.array(), chunk.arrayOffset() + chunk.position(), len);
70         } else {
71             byte[] bytes = new byte[len];
72             chunk.put(bytes);
73             compressionStream.write(bytes, 0, len);
74         }
75         return len;
76     }
77
78
79     @Override
80     public long getBytesWritten() {
81         return buffer.getBytesWritten();
82     }
83
84
85     // --------------------------------------------------- OutputFilter Methods
86
87     /**
88      * Added to allow flushing to happen for the gzip'ed outputstream
89      */

90     @Override
91     public void flush() throws IOException {
92         if (compressionStream != null) {
93             try {
94                 if (log.isDebugEnabled()) {
95                     log.debug("Flushing the compression stream!");
96                 }
97                 compressionStream.flush();
98             } catch (IOException e) {
99                 if (log.isDebugEnabled()) {
100                     log.debug("Ignored exception while flushing gzip filter", e);
101                 }
102             }
103         }
104         buffer.flush();
105     }
106
107
108     @Override
109     public void setResponse(Response response) {
110         // NOOP: No need for parameters from response in this filter
111     }
112
113
114     @Override
115     public void setBuffer(HttpOutputBuffer buffer) {
116         this.buffer = buffer;
117     }
118
119
120     @Override
121     public void end() throws IOException {
122         if (compressionStream == null) {
123             compressionStream = new GZIPOutputStream(fakeOutputStream, true);
124         }
125         compressionStream.finish();
126         compressionStream.close();
127         buffer.end();
128     }
129
130
131     /**
132      * Make the filter ready to process the next request.
133      */

134     @Override
135     public void recycle() {
136         // Set compression stream to null
137         compressionStream = null;
138     }
139
140
141     // ------------------------------------------- FakeOutputStream Inner Class
142
143
144     protected class FakeOutputStream
145         extends OutputStream {
146         protected final ByteBuffer outputChunk = ByteBuffer.allocate(1);
147         @Override
148         public void write(int b)
149             throws IOException {
150             // Shouldn't get used for good performance, but is needed for
151             // compatibility with Sun JDK 1.4.0
152             outputChunk.put(0, (byte) (b & 0xff));
153             buffer.doWrite(outputChunk);
154         }
155         @Override
156         public void write(byte[] b, int off, int len)
157             throws IOException {
158             buffer.doWrite(ByteBuffer.wrap(b, off, len));
159         }
160         @Override
161         public void flush() throws IOException {/*NOOP*/}
162         @Override
163         public void close() throws IOException {/*NOOP*/}
164     }
165
166
167 }
168