org.browsermob.proxy.jetty.http.HttpOutputStream Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of browsermob-proxy Show documentation
Show all versions of browsermob-proxy Show documentation
A programmatic HTTP/S designed for performance and functional testing
// ========================================================================
// $Id: HttpOutputStream.java,v 1.28 2006/10/08 14:13:05 gregwilkins Exp $
// Copyright 199-2004 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// ========================================================================
package org.browsermob.proxy.jetty.http;
import org.apache.commons.logging.Log;
import org.browsermob.proxy.jetty.log.LogFactory;
import org.browsermob.proxy.jetty.util.*;
import java.io.*;
import java.util.ArrayList;
/* ---------------------------------------------------------------- */
/** HTTP Http OutputStream.
* Acts as a BufferedOutputStream until setChunking() is called.
* Once chunking is enabled, the raw stream is chunk encoded as per RFC2616.
*
* Implements the following HTTP and Servlet features:
* - Filters for content and transfer encodings.
*
- Allows output to be reset if not committed (buffer never flushed).
*
- Notification of significant output events for filter triggering,
* header flushing, etc.
*
*
* This class is not synchronized and should be synchronized
* explicitly if an instance is used by multiple threads.
*
* @version $Id: HttpOutputStream.java,v 1.28 2006/10/08 14:13:05 gregwilkins Exp $
* @author Greg Wilkins
*/
public class HttpOutputStream extends OutputStream
implements OutputObserver, HttpMessage.HeaderWriter
{
private static Log log = LogFactory.getLog(HttpOutputStream.class);
/* ------------------------------------------------------------ */
final static int __BUFFER_SIZE=4096;
final static int __FIRST_RESERVE=512;
public final static Class[] __filterArg = {java.io.OutputStream.class};
/* ------------------------------------------------------------ */
private OutputStream _out;
private OutputStream _realOut;
private BufferedOutputStream _bufferedOut;
private boolean _written;
private ArrayList _observers;
private int _bufferSize;
private int _headerReserve;
private HttpWriter _iso8859writer;
private HttpWriter _utf8writer;
private HttpWriter _asciiwriter;
private boolean _nulled;
private boolean _closing=false;
private int _contentLength=-1;
private int _bytes;
private boolean _disableFlush;
/* ------------------------------------------------------------ */
/** Constructor.
* @param outputStream The outputStream to buffer or chunk to.
*/
public HttpOutputStream(OutputStream outputStream)
{
this (outputStream,__BUFFER_SIZE,__FIRST_RESERVE);
}
/* ------------------------------------------------------------ */
/** Constructor.
* @param outputStream The outputStream to buffer or chunk to.
*/
public HttpOutputStream(OutputStream outputStream, int bufferSize)
{
this (outputStream,bufferSize,__FIRST_RESERVE);
}
/* ------------------------------------------------------------ */
/** Constructor.
* @param outputStream The outputStream to buffer or chunk to.
*/
public HttpOutputStream(OutputStream outputStream,
int bufferSize,
int headerReserve)
{
_written=false;
_bufferSize=bufferSize;
_headerReserve=headerReserve;
_realOut=outputStream;
_out=_realOut;
}
/* ------------------------------------------------------------ */
public void setContentLength(int length)
{
if (length>=0 && length<_bytes)
throw new IllegalStateException();
_contentLength=length;
}
/* ------------------------------------------------------------ */
public void setBufferedOutputStream(BufferedOutputStream bos)
{
_bufferedOut=bos;
_bufferedOut.setCommitObserver(this);
if (_out!=null && _out!=_realOut)
_out=_bufferedOut;
}
/* ------------------------------------------------------------ */
/** Get the backing output stream.
* A stream without filters or chunking is returned.
* @return Raw OutputStream.
*/
public OutputStream getOutputStream()
{
return _realOut;
}
/* ------------------------------------------------------------ */
/** Get the buffered output stream.
*/
public OutputStream getBufferedOutputStream()
{
return _out;
}
/* ------------------------------------------------------------ */
/** Has any data been written to the stream.
* @return True if write has been called.
*/
public boolean isWritten()
{
return _written;
}
/* ------------------------------------------------------------ */
/** Get the output buffer capacity.
* @return Buffer capacity in bytes.
*/
public int getBufferSize()
{
return _bufferSize;
}
/* ------------------------------------------------------------ */
/** Set the output buffer size.
* Note that this is the minimal buffer size and that installed
* filters may perform their own buffering and are likely to change
* the size of the output. Also the pre and post reserve buffers may be
* allocated within the buffer for headers and chunking.
* @param size Minimum buffer size in bytes
* @exception IllegalStateException If output has been written.
*/
public void setBufferSize(int size)
throws IllegalStateException
{
if (size<=_bufferSize)
return;
if (_bufferedOut!=null && _bufferedOut.size()>0)
throw new IllegalStateException("Not Reset");
try
{
_bufferSize=size;
if (_bufferedOut!=null)
{
boolean fixed=_bufferedOut.isFixed();
_bufferedOut.setFixed(false);
_bufferedOut.ensureSize(size);
_bufferedOut.setFixed(fixed);
}
}
catch (IOException e){log.warn(LogSupport.EXCEPTION,e);}
}
/* ------------------------------------------------------------ */
public int getBytesWritten()
{
return _bytes;
}
/* ------------------------------------------------------------ */
/** Reset Buffered output.
* If no data has been committed, the buffer output is discarded and
* the filters may be reinitialized.
* @exception IllegalStateException
*/
public void resetBuffer()
throws IllegalStateException
{
// Shutdown filters without observation
if (_out!=null && _out!=_realOut)
{
ArrayList save_observers=_observers;
_observers=null;
_nulled=true;
try
{
// discard current buffer and set it to output
if (_bufferedOut!=null)
{
_bufferedOut.resetStream();
if (_bufferedOut instanceof ChunkingOutputStream)
((ChunkingOutputStream)_bufferedOut).setChunking(false);
}
}
catch(Exception e)
{
LogSupport.ignore(log,e);
}
finally
{
_observers=save_observers;
}
}
_contentLength=-1;
_nulled=false;
_bytes=0;
_written=false;
_out=_realOut;
try
{
notify(OutputObserver.__RESET_BUFFER);
}
catch(IOException e)
{
LogSupport.ignore(log,e);
}
}
/* ------------------------------------------------------------ */
/** Add an Output Observer.
* Output Observers get notified of significant events on the
* output stream. Observers are called in the reverse order they
* were added.
* They are removed when the stream is closed.
* @param observer The observer.
*/
public void addObserver(OutputObserver observer)
{
if (_observers==null)
_observers=new ArrayList(4);
_observers.add(observer);
_observers.add(null);
}
/* ------------------------------------------------------------ */
/** Add an Output Observer.
* Output Observers get notified of significant events on the
* output stream. Observers are called in the reverse order they
* were added.
* They are removed when the stream is closed.
* @param observer The observer.
* @param data Data to be passed wit notify calls.
*/
public void addObserver(OutputObserver observer, Object data)
{
if (_observers==null)
_observers=new ArrayList(4);
_observers.add(observer);
_observers.add(data);
}
/* ------------------------------------------------------------ */
/** Reset the observers.
*/
public void resetObservers()
{
_observers=null;
}
/* ------------------------------------------------------------ */
/** Null the output.
* All output written is discarded until the stream is reset. Used
* for HEAD requests.
*/
public void nullOutput()
throws IOException
{
_nulled=true;
}
/* ------------------------------------------------------------ */
/** is the output Nulled?
*/
public boolean isNullOutput()
throws IOException
{
return _nulled;
}
/* ------------------------------------------------------------ */
/** Set chunking mode.
*/
public void setChunking()
{
checkOutput();
if (_bufferedOut instanceof ChunkingOutputStream)
((ChunkingOutputStream)_bufferedOut).setChunking(true);
else
throw new IllegalStateException(_bufferedOut.getClass().toString());
}
/* ------------------------------------------------------------ */
/** Get chunking mode
*/
public boolean isChunking()
{
return (_bufferedOut instanceof ChunkingOutputStream) &&
((ChunkingOutputStream)_bufferedOut).isChunking();
}
/* ------------------------------------------------------------ */
/** Reset the stream.
* Turn disable all filters.
* @exception IllegalStateException The stream cannot be
* reset if chunking is enabled.
*/
public void resetStream()
throws IOException, IllegalStateException
{
if (isChunking())
close();
_out=null;
_nulled=true;
if (_bufferedOut!=null)
{
_bufferedOut.resetStream();
if (_bufferedOut instanceof ChunkingOutputStream)
((ChunkingOutputStream)_bufferedOut).setChunking(false);
}
if (_iso8859writer!=null)
_iso8859writer.flush();
if (_utf8writer!=null)
_utf8writer.flush();
if (_asciiwriter!=null)
_asciiwriter.flush();
_bytes=0;
_written=false;
_out=_realOut;
_closing=false;
_contentLength=-1;
_nulled=false;
if (_observers!=null)
_observers.clear();
}
/* ------------------------------------------------------------ */
public void destroy()
{
if (_bufferedOut!=null)
_bufferedOut.destroy();
_bufferedOut=null;
if (_iso8859writer!=null)
_iso8859writer.destroy();
_iso8859writer=null;
if (_utf8writer!=null)
_utf8writer.destroy();
_utf8writer=null;
if (_asciiwriter!=null)
_asciiwriter.destroy();
_asciiwriter=null;
}
/* ------------------------------------------------------------ */
public void writeHeader(HttpMessage httpMessage)
throws IOException
{
checkOutput();
_bufferedOut.writeHeader(httpMessage);
}
/* ------------------------------------------------------------ */
public void write(int b) throws IOException
{
prepareOutput(1);
if (!_nulled)
_out.write(b);
if (_bytes==_contentLength)
flush();
}
/* ------------------------------------------------------------ */
public void write(byte b[]) throws IOException
{
write(b,0,b.length);
}
/* ------------------------------------------------------------ */
public void write(byte b[], int off, int len)
throws IOException
{
len=prepareOutput(len);
if (!_nulled)
_out.write(b,off,len);
if (_bytes==_contentLength)
flush();
}
/* ------------------------------------------------------------ */
protected void checkOutput()
{
if (_out==_realOut)
{
if (_bufferedOut==null)
{
_bufferedOut=new ChunkingOutputStream(_realOut,
_bufferSize,
_headerReserve,
false);
_bufferedOut.setCommitObserver(this);
_bufferedOut.setBypassBuffer(true);
_bufferedOut.setFixed(true);
}
_out=_bufferedOut;
}
}
/* ------------------------------------------------------------ */
protected int prepareOutput(int length)
throws IOException
{
if (_out==null)
throw new IOException("closed");
checkOutput();
if (!_written)
{
_written=true;
notify(OutputObserver.__FIRST_WRITE);
}
if (_contentLength>=0)
{
if (_bytes+length>=_contentLength)
{
length=_contentLength-_bytes;
if (length==0)
_nulled=true;
}
}
_bytes+=length;
return length;
}
/* ------------------------------------------------------------ */
public void flush()
throws IOException
{
if (!_disableFlush && _out!=null && !_closing)
_out.flush();
}
/* ------------------------------------------------------------ */
/** Close the stream.
* @exception IOException
*/
public boolean isClosed()
throws IOException
{
return _out==null;
}
/* ------------------------------------------------------------ */
/** Close the stream.
* @exception IOException
*/
public void close()
throws IOException
{
// Are we already closed?
if (_out==null)
return;
_closing=true;
// Close
try {
notify(OutputObserver.__CLOSING);
OutputStream out =_out;
_out=null;
if (out!=_bufferedOut)
out.close();
else
_bufferedOut.close();
notify(OutputObserver.__CLOSED);
}
catch (IOException e)
{
LogSupport.ignore(log,e);
}
}
/* ------------------------------------------------------------ */
/** Output Notification.
* Called by the internal Buffered Output and the event is passed on to
* this streams observers.
*/
public void outputNotify(OutputStream out, int action, Object ignoredData)
throws IOException
{
notify(action);
}
/* ------------------------------------------------------------ */
/* Notify observers of action.
* @see OutputObserver
* @param action the action.
*/
private void notify(int action)
throws IOException
{
if (_observers!=null)
{
for (int i=_observers.size();i-->0;)
{
Object data=_observers.get(i--);
((OutputObserver)_observers.get(i)).outputNotify(this,action,data);
}
}
}
/* ------------------------------------------------------------ */
public void write(InputStream in, int len)
throws IOException
{
IO.copy(in,this,len);
}
/* ------------------------------------------------------------ */
private Writer getISO8859Writer()
throws IOException
{
if (_iso8859writer==null)
_iso8859writer=new HttpWriter(StringUtil.__ISO_8859_1,
getBufferSize());
return _iso8859writer;
}
/* ------------------------------------------------------------ */
private Writer getUTF8Writer()
throws IOException
{
if (_utf8writer==null)
_utf8writer=new HttpWriter("UTF-8",getBufferSize());
return _utf8writer;
}
/* ------------------------------------------------------------ */
private Writer getASCIIWriter()
throws IOException
{
if (_asciiwriter==null)
_asciiwriter=new HttpWriter("US-ASCII",getBufferSize());
return _asciiwriter;
}
/* ------------------------------------------------------------ */
public Writer getWriter(String encoding)
throws IOException
{
if (encoding==null ||
StringUtil.__ISO_8859_1.equalsIgnoreCase(encoding) ||
"ISO8859_1".equalsIgnoreCase(encoding))
return getISO8859Writer();
if ("UTF-8".equalsIgnoreCase(encoding) ||
"UTF8".equalsIgnoreCase(encoding))
return getUTF8Writer();
if ("US-ASCII".equalsIgnoreCase(encoding))
return getASCIIWriter();
return new OutputStreamWriter(this,encoding);
}
/* ------------------------------------------------------------ */
public String toString()
{
return super.toString() +
"\nout="+_out+
"\nrealOut="+_realOut+
"\nbufferedOut="+_bufferedOut;
}
/* ------------------------------------------------------------ */
/* ------------------------------------------------------------ */
private class HttpWriter extends Writer
{
private OutputStreamWriter _writer=null;
private boolean _writting=false;
private byte[] _buf;
private String _encoding;
/* -------------------------------------------------------- */
HttpWriter(String encoding,int bufferSize)
{
_buf = ByteArrayPool.getByteArray(bufferSize);
_encoding=encoding;
}
/* -------------------------------------------------------- */
public Object getLock()
{
return lock;
}
/* -------------------------------------------------------- */
public void write(char c)
throws IOException
{
HttpOutputStream.this.prepareOutput(1);
if (!_nulled)
{
if (_writting)
_writer.write(c);
else if (c>=0&&c<=0x7f)
HttpOutputStream.this.write((int)c);
else
{
char[] ca ={c};
writeEncoded(ca,0,1);
}
if (_bytes==_contentLength)
flush();
}
}
/* ------------------------------------------------------------ */
public void write(char[] ca)
throws IOException
{
this.write(ca,0,ca.length);
}
/* ------------------------------------------------------------ */
public void write(char[] ca,int offset, int len)
throws IOException
{
if (_writting)
_writer.write(ca,offset,len);
else
{
int s=0;
for (int i=0;i=0&&c<=0x7f)
{
_buf[s++]=(byte)c;
if (s==_buf.length)
{
s=HttpOutputStream.this.prepareOutput(s);
if (!_nulled)
HttpOutputStream.this._out.write(_buf,0,s);
s=0;
}
}
else
{
if (s>0)
{
s=HttpOutputStream.this.prepareOutput(s);
if (!_nulled)
HttpOutputStream.this._out.write(_buf,0,s);
s=0;
}
writeEncoded(ca,offset+i,len-i);
break;
}
}
if (s>0)
{
s=HttpOutputStream.this.prepareOutput(s);
if (!_nulled)
HttpOutputStream.this._out.write(_buf,0,s);
s=0;
}
}
if (!_nulled && _bytes==_contentLength)
flush();
}
/* ------------------------------------------------------------ */
public void write(String s)
throws IOException
{
this.write(s,0,s.length());
}
/* ------------------------------------------------------------ */
public void write(String str,int offset, int len)
throws IOException
{
if (_writting)
_writer.write(str,offset,len);
else
{
int s=0;
for (int i=0;i=0&&c<=0x7f)
{
_buf[s++]=(byte)c;
if (s==_buf.length)
{
s=HttpOutputStream.this.prepareOutput(s);
if (!_nulled)
HttpOutputStream.this._out.write(_buf,0,s);
s=0;
}
}
else
{
if (s>0)
{
s=HttpOutputStream.this.prepareOutput(s);
if (!_nulled)
HttpOutputStream.this._out.write(_buf,0,s);
s=0;
}
char[] chars = str.toCharArray();
writeEncoded(chars,offset+i,len-i);
break;
}
}
if (s>0)
{
s=HttpOutputStream.this.prepareOutput(s);
if (!_nulled)
HttpOutputStream.this._out.write(_buf,0,s);
s=0;
}
}
if (_bytes==_contentLength)
flush();
}
/* ------------------------------------------------------------ */
private void writeEncoded(char[] ca,int offset, int length)
throws IOException
{
_writting=true;
if (_writer==null)
_writer = new OutputStreamWriter(HttpOutputStream.this,_encoding);
try
{
HttpOutputStream.this._disableFlush=true;
_writer.write(ca,offset,length);
if (HttpOutputStream.this._contentLength>=0)
_writer.flush();
}
finally
{
HttpOutputStream.this._disableFlush=false;
}
}
/* ------------------------------------------------------------ */
public void flush()
throws IOException
{
if (_writting)
_writer.flush();
else
HttpOutputStream.this.flush();
_writting=false;
}
/* ------------------------------------------------------------ */
public void close()
throws IOException
{
_closing=true;
if (_writting)
_writer.flush();
HttpOutputStream.this.close();
_writting=false;
}
/* ------------------------------------------------------------ */
public void destroy()
{
ByteArrayPool.returnByteArray(_buf);
_buf=null;
_writer=null;
_encoding=null;
}
}
/**
* @return Returns the disableFlush.
*/
}