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.internal.gzip; 016 017import org.apache.tapestry5.internal.InternalConstants; 018import org.apache.tapestry5.services.assets.CompressionAnalyzer; 019 020import javax.servlet.ServletOutputStream; 021import javax.servlet.http.HttpServletResponse; 022import java.io.BufferedOutputStream; 023import java.io.ByteArrayOutputStream; 024import java.io.IOException; 025import java.io.OutputStream; 026import 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 */ 032public class BufferedGZipOutputStream extends ServletOutputStream 033{ 034 private final String contentType; 035 036 private final HttpServletResponse response; 037 038 private final CompressionAnalyzer 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 CompressionAnalyzer 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 { 085 response.setHeader(InternalConstants.CONTENT_ENCODING_HEADER, InternalConstants.GZIP_CONTENT_ENCODING); 086 } 087 088 currentOutputStream = 089 new BufferedOutputStream(possiblyCompressed); 090 091 // Write what content we already have to the new stream. 092 093 byteArrayOutputStream.writeTo(currentOutputStream); 094 095 byteArrayOutputStream = null; 096 } 097 098 public void write(int b) throws IOException 099 { 100 currentOutputStream.write(b); 101 102 checkForCutover(); 103 } 104 105 @Override 106 public void write(byte[] b) throws IOException 107 { 108 currentOutputStream.write(b); 109 110 checkForCutover(); 111 } 112 113 @Override 114 public void write(byte[] b, int off, int len) throws IOException 115 { 116 currentOutputStream.write(b, off, len); 117 118 checkForCutover(); 119 } 120 121 @Override 122 public void flush() throws IOException 123 { 124 forceOutputStream().flush(); 125 } 126 127 @Override 128 public void close() throws IOException 129 { 130 // When closing, if we haven't accumulated enough output yet to start compressing, 131 // then send what we have, uncompressed. 132 133 forceOutputStream().close(); 134 } 135 136 private OutputStream forceOutputStream() throws IOException 137 { 138 if (byteArrayOutputStream != null) 139 openResponseOutputStream(false); 140 141 return currentOutputStream; 142 } 143}