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 }