blob: 29651c29b26c72b4f302817e7b568dc281596c83 [file] [log] [blame]
//
// ========================================================================
// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
//
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
//
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
//
package org.eclipse.jetty.http.gzip;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.UnsupportedEncodingException;
import java.util.zip.DeflaterOutputStream;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.eclipse.jetty.util.ByteArrayOutputStream2;
/* ------------------------------------------------------------ */
/**
* Skeletal implementation of a CompressedStream. This class adds compression features to a ServletOutputStream and takes care of setting response headers, etc.
* Major work and configuration is done here. Subclasses using different kinds of compression only have to implement the abstract methods doCompress() and
* setContentEncoding() using the desired compression and setting the appropriate Content-Encoding header string.
*/
public abstract class AbstractCompressedStream extends ServletOutputStream
{
private final String _encoding;
protected final String _vary;
protected final CompressedResponseWrapper _wrapper;
protected final HttpServletResponse _response;
protected OutputStream _out;
protected ByteArrayOutputStream2 _bOut;
protected DeflaterOutputStream _compressedOutputStream;
protected boolean _closed;
protected boolean _doNotCompress;
/**
* Instantiates a new compressed stream.
*
*/
public AbstractCompressedStream(String encoding,HttpServletRequest request, CompressedResponseWrapper wrapper,String vary)
throws IOException
{
_encoding=encoding;
_wrapper = wrapper;
_response = (HttpServletResponse)wrapper.getResponse();
_vary=vary;
if (_wrapper.getMinCompressSize()==0)
doCompress();
}
/* ------------------------------------------------------------ */
/**
* Reset buffer.
*/
public void resetBuffer()
{
if (_response.isCommitted() || _compressedOutputStream!=null )
throw new IllegalStateException("Committed");
_closed = false;
_out = null;
_bOut = null;
_doNotCompress = false;
}
/* ------------------------------------------------------------ */
public void setBufferSize(int bufferSize)
{
if (_bOut!=null && _bOut.getBuf().length<bufferSize)
{
ByteArrayOutputStream2 b = new ByteArrayOutputStream2(bufferSize);
b.write(_bOut.getBuf(),0,_bOut.size());
_bOut=b;
}
}
/* ------------------------------------------------------------ */
public void setContentLength()
{
if (_doNotCompress)
{
long length=_wrapper.getContentLength();
if (length>=0)
{
if (length < Integer.MAX_VALUE)
_response.setContentLength((int)length);
else
_response.setHeader("Content-Length",Long.toString(length));
}
}
}
/* ------------------------------------------------------------ */
/**
* @see java.io.OutputStream#flush()
*/
@Override
public void flush() throws IOException
{
if (_out == null || _bOut != null)
{
long length=_wrapper.getContentLength();
if (length > 0 && length < _wrapper.getMinCompressSize())
doNotCompress(false);
else
doCompress();
}
_out.flush();
}
/* ------------------------------------------------------------ */
/**
* @see java.io.OutputStream#close()
*/
@Override
public void close() throws IOException
{
if (_closed)
return;
if (_wrapper.getRequest().getAttribute("javax.servlet.include.request_uri") != null)
flush();
else
{
if (_bOut != null)
{
long length=_wrapper.getContentLength();
if (length < 0)
{
length = _bOut.getCount();
_wrapper.setContentLength(length);
}
if (length < _wrapper.getMinCompressSize())
doNotCompress(false);
else
doCompress();
}
else if (_out == null)
{
// No output
doNotCompress(false);
}
if (_compressedOutputStream != null)
_compressedOutputStream.close();
else
_out.close();
_closed = true;
}
}
/**
* Finish.
*
* @throws IOException
* Signals that an I/O exception has occurred.
*/
public void finish() throws IOException
{
if (!_closed)
{
if (_out == null || _bOut != null)
{
long length=_wrapper.getContentLength();
if (length >= 0 && length < _wrapper.getMinCompressSize())
doNotCompress(false);
else
doCompress();
}
if (_compressedOutputStream != null && !_closed)
{
_closed = true;
_compressedOutputStream.close();
}
}
}
/* ------------------------------------------------------------ */
/**
* @see java.io.OutputStream#write(int)
*/
@Override
public void write(int b) throws IOException
{
checkOut(1);
_out.write(b);
}
/* ------------------------------------------------------------ */
/**
* @see java.io.OutputStream#write(byte[])
*/
@Override
public void write(byte b[]) throws IOException
{
checkOut(b.length);
_out.write(b);
}
/* ------------------------------------------------------------ */
/**
* @see java.io.OutputStream#write(byte[], int, int)
*/
@Override
public void write(byte b[], int off, int len) throws IOException
{
checkOut(len);
_out.write(b,off,len);
}
/**
* Do compress.
*
* @throws IOException Signals that an I/O exception has occurred.
*/
public void doCompress() throws IOException
{
if (_compressedOutputStream==null)
{
if (_response.isCommitted())
throw new IllegalStateException();
if (_encoding!=null)
{
setHeader("Content-Encoding", _encoding);
if (_response.containsHeader("Content-Encoding"))
{
addHeader("Vary",_vary);
_out=_compressedOutputStream=createStream();
if (_out!=null)
{
if (_bOut!=null)
{
_out.write(_bOut.getBuf(),0,_bOut.getCount());
_bOut=null;
}
String etag=_wrapper.getETag();
if (etag!=null)
setHeader("ETag",etag.substring(0,etag.length()-1)+'-'+_encoding+'"');
return;
}
}
}
doNotCompress(true); // Send vary as it could have been compressed if encoding was present
}
}
/**
* Do not compress.
*
* @throws IOException
* Signals that an I/O exception has occurred.
*/
public void doNotCompress(boolean sendVary) throws IOException
{
if (_compressedOutputStream != null)
throw new IllegalStateException("Compressed output stream is already assigned.");
if (_out == null || _bOut != null)
{
if (sendVary)
addHeader("Vary",_vary);
if (_wrapper.getETag()!=null)
setHeader("ETag",_wrapper.getETag());
_doNotCompress = true;
_out = _response.getOutputStream();
setContentLength();
if (_bOut != null)
_out.write(_bOut.getBuf(),0,_bOut.getCount());
_bOut = null;
}
}
/**
* Check out.
*
* @param lengthToWrite
* the length
* @throws IOException
* Signals that an I/O exception has occurred.
*/
private void checkOut(int lengthToWrite) throws IOException
{
if (_closed)
throw new IOException("CLOSED");
if (_out == null)
{
// If this first write is larger than buffer size, then we are committing now
if (lengthToWrite>_wrapper.getBufferSize())
{
// if we know this is all the content and it is less than minimum, then do not compress, otherwise do compress
long length=_wrapper.getContentLength();
if (length>=0 && length<_wrapper.getMinCompressSize())
doNotCompress(false); // Not compressing by size, so no vary on request headers
else
doCompress();
}
else
{
// start aggregating writes into a buffered output stream
_out = _bOut = new ByteArrayOutputStream2(_wrapper.getBufferSize());
}
}
// else are we aggregating writes?
else if (_bOut !=null)
{
// We are aggregating into the buffered output stream.
// If this write fills the buffer, then we are committing
if (lengthToWrite>=(_bOut.getBuf().length - _bOut.getCount()))
{
// if we know this is all the content and it is less than minimum, then do not compress, otherwise do compress
long length=_wrapper.getContentLength();
if (length>=0 && length<_wrapper.getMinCompressSize())
doNotCompress(false); // Not compressing by size, so no vary on request headers
else
doCompress();
}
}
}
/**
* @see org.eclipse.jetty.http.gzip.CompressedStream#getOutputStream()
*/
public OutputStream getOutputStream()
{
return _out;
}
/**
* @see org.eclipse.jetty.http.gzip.CompressedStream#isClosed()
*/
public boolean isClosed()
{
return _closed;
}
/**
* Allows derived implementations to replace PrintWriter implementation.
*/
protected PrintWriter newWriter(OutputStream out, String encoding) throws UnsupportedEncodingException
{
return encoding == null?new PrintWriter(out):new PrintWriter(new OutputStreamWriter(out,encoding));
}
protected void addHeader(String name,String value)
{
_response.addHeader(name, value);
}
protected void setHeader(String name,String value)
{
_response.setHeader(name, value);
}
/**
* Create the stream fitting to the underlying compression type.
*
* @throws IOException
* Signals that an I/O exception has occurred.
*/
protected abstract DeflaterOutputStream createStream() throws IOException;
}