Java tutorial
// ======================================================================== // $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: <UL> * <LI>Filters for content and transfer encodings. * <LI>Allows output to be reset if not committed (buffer never flushed). * <LI>Notification of significant output events for filter triggering, * header flushing, etc. * </UL> * * 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 < len; i++) { char c = ca[offset + i]; if (c >= 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 < len; i++) { char c = str.charAt(offset + i); if (c >= 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. */ }