001 // Copyright 2009 The Apache Software Foundation
002 //
003 // Licensed under the Apache License, Version 2.0 (the "License");
004 // you may not use this file except in compliance with the License.
005 // You may obtain a copy of the License at
006 //
007 // http://www.apache.org/licenses/LICENSE-2.0
008 //
009 // Unless required by applicable law or agreed to in writing, software
010 // distributed under the License is distributed on an "AS IS" BASIS,
011 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
012 // See the License for the specific language governing permissions and
013 // limitations under the License.
014
015 package org.apache.tapestry5.internal.gzip;
016
017 import org.apache.tapestry5.internal.InternalConstants;
018 import org.apache.tapestry5.services.ResponseCompressionAnalyzer;
019
020 import javax.servlet.ServletOutputStream;
021 import javax.servlet.http.HttpServletResponse;
022 import java.io.BufferedOutputStream;
023 import java.io.ByteArrayOutputStream;
024 import java.io.IOException;
025 import java.io.OutputStream;
026 import java.util.zip.GZIPOutputStream;
027
028 /**
029 * A buffered output stream that, when a certain number of bytes is buffered (the cutover point) will open a compressed
030 * stream (via {@link org.apache.tapestry5.services.Response#getOutputStream(String)}
031 */
032 public class BufferedGZipOutputStream extends ServletOutputStream
033 {
034 private final String contentType;
035
036 private final HttpServletResponse response;
037
038 private final ResponseCompressionAnalyzer analyzer;
039
040 private final int cutover;
041
042 private ByteArrayOutputStream byteArrayOutputStream;
043
044 /**
045 * Initially the ByteArrayOutputStream, later the response output stream (possibly wrapped with a
046 * GZIPOutputStream).
047 */
048 private OutputStream currentOutputStream;
049
050 public BufferedGZipOutputStream(String contentType, HttpServletResponse response, int cutover,
051 ResponseCompressionAnalyzer analyzer)
052 {
053 this.contentType = contentType;
054 this.response = response;
055 this.cutover = cutover;
056 this.analyzer = analyzer;
057
058 byteArrayOutputStream = new ByteArrayOutputStream(cutover);
059
060 currentOutputStream = byteArrayOutputStream;
061 }
062
063 private void checkForCutover() throws IOException
064 {
065 if (byteArrayOutputStream == null) return;
066
067 if (byteArrayOutputStream.size() < cutover) return;
068
069 // Time to switch over to GZIP.
070 openResponseOutputStream(true);
071 }
072
073 private void openResponseOutputStream(boolean gzip) throws IOException
074 {
075 OutputStream responseOutputStream = response.getOutputStream();
076
077 boolean useCompression = gzip && analyzer.isCompressable(contentType);
078
079 OutputStream possiblyCompressed = useCompression
080 ? new GZIPOutputStream(responseOutputStream)
081 : responseOutputStream;
082
083 if (useCompression)
084 response.setHeader(InternalConstants.CONTENT_ENCODING_HEADER, InternalConstants.GZIP_CONTENT_ENCODING);
085
086 currentOutputStream =
087 new BufferedOutputStream(possiblyCompressed);
088
089 // Write what content we already have to the new stream.
090
091 byteArrayOutputStream.writeTo(currentOutputStream);
092
093 byteArrayOutputStream = null;
094 }
095
096 public void write(int b) throws IOException
097 {
098 currentOutputStream.write(b);
099
100 checkForCutover();
101 }
102
103 @Override
104 public void write(byte[] b) throws IOException
105 {
106 currentOutputStream.write(b);
107
108 checkForCutover();
109 }
110
111 @Override
112 public void write(byte[] b, int off, int len) throws IOException
113 {
114 currentOutputStream.write(b, off, len);
115
116 checkForCutover();
117 }
118
119 @Override
120 public void flush() throws IOException
121 {
122 forceOutputStream().flush();
123 }
124
125 @Override
126 public void close() throws IOException
127 {
128 // When closing, if we haven't accumulated enough output yet to start compressing,
129 // then send what we have, uncompressed.
130
131 forceOutputStream().close();
132 }
133
134 private OutputStream forceOutputStream() throws IOException
135 {
136 if (byteArrayOutputStream != null)
137 openResponseOutputStream(false);
138
139 return currentOutputStream;
140 }
141 }