001// Copyright 2009, 2012 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 015package org.apache.tapestry5.http.internal.gzip; 016 017import java.io.BufferedOutputStream; 018import java.io.ByteArrayOutputStream; 019import java.io.IOException; 020import java.io.OutputStream; 021import java.util.zip.GZIPOutputStream; 022 023import javax.servlet.ServletOutputStream; 024import javax.servlet.http.HttpServletResponse; 025 026import org.apache.tapestry5.http.internal.TapestryHttpInternalConstants; 027import org.apache.tapestry5.http.services.CompressionAnalyzer; 028 029/** 030 * A buffered output stream that, when a certain number of bytes is buffered (the cutover point) will open a compressed 031 * stream (via {@link org.apache.tapestry5.http.services.Response#getOutputStream(String)} 032 */ 033public class BufferedGZipOutputStream extends ServletOutputStream 034{ 035 private final String contentType; 036 037 private final HttpServletResponse response; 038 039 private final CompressionAnalyzer analyzer; 040 041 private final int cutover; 042 043 private ByteArrayOutputStream byteArrayOutputStream; 044 045 /** 046 * Initially the ByteArrayOutputStream, later the response output stream (possibly wrapped with a 047 * GZIPOutputStream). 048 */ 049 private OutputStream currentOutputStream; 050 051 public BufferedGZipOutputStream(String contentType, HttpServletResponse response, int cutover, 052 CompressionAnalyzer analyzer) 053 { 054 this.contentType = contentType; 055 this.response = response; 056 this.cutover = cutover; 057 this.analyzer = analyzer; 058 059 byteArrayOutputStream = new ByteArrayOutputStream(cutover); 060 061 currentOutputStream = byteArrayOutputStream; 062 } 063 064 private void checkForCutover() throws IOException 065 { 066 if (byteArrayOutputStream == null) return; 067 068 if (byteArrayOutputStream.size() < cutover) return; 069 070 // Time to switch over to GZIP. 071 openResponseOutputStream(true); 072 } 073 074 private void openResponseOutputStream(boolean gzip) throws IOException 075 { 076 OutputStream responseOutputStream = response.getOutputStream(); 077 078 boolean useCompression = gzip && analyzer.isCompressable(contentType); 079 080 OutputStream possiblyCompressed = useCompression 081 ? new GZIPOutputStream(responseOutputStream) 082 : responseOutputStream; 083 084 if (useCompression) 085 { 086 response.setHeader( 087 TapestryHttpInternalConstants.CONTENT_ENCODING_HEADER, 088 TapestryHttpInternalConstants.GZIP_CONTENT_ENCODING); 089 } 090 091 currentOutputStream = 092 new BufferedOutputStream(possiblyCompressed); 093 094 // Write what content we already have to the new stream. 095 096 byteArrayOutputStream.writeTo(currentOutputStream); 097 098 byteArrayOutputStream = null; 099 } 100 101 public void write(int b) throws IOException 102 { 103 currentOutputStream.write(b); 104 105 checkForCutover(); 106 } 107 108 @Override 109 public void write(byte[] b) throws IOException 110 { 111 currentOutputStream.write(b); 112 113 checkForCutover(); 114 } 115 116 @Override 117 public void write(byte[] b, int off, int len) throws IOException 118 { 119 currentOutputStream.write(b, off, len); 120 121 checkForCutover(); 122 } 123 124 @Override 125 public void flush() throws IOException 126 { 127 forceOutputStream().flush(); 128 } 129 130 @Override 131 public void close() throws IOException 132 { 133 // When closing, if we haven't accumulated enough output yet to start compressing, 134 // then send what we have, uncompressed. 135 136 forceOutputStream().close(); 137 } 138 139 private OutputStream forceOutputStream() throws IOException 140 { 141 if (byteArrayOutputStream != null) 142 openResponseOutputStream(false); 143 144 return currentOutputStream; 145 } 146}