/* * Copyright (c) 1998-2011 Caucho Technology -- all rights reserved * * This file is part of Resin(R) Open Source * * Each copy or derived work must preserve the copyright notice and this * notice unmodified. * * Resin Open Source is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * Resin Open Source is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE, or any warranty * of NON-INFRINGEMENT. See the GNU General Public License for more * details. * * You should have received a copy of the GNU General Public License * along with Resin Open Source; if not, write to the * * Free Software Foundation, Inc. * 59 Temple Place, Suite 330 * Boston, MA 02111-1307 USA * * @author Scott Ferguson */ package com.caucho.server.http; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.util.Locale; import java.util.logging.Level; import java.util.logging.Logger; import com.caucho.util.L10N; import com.caucho.vfs.Encoding; import com.caucho.vfs.TempBuffer; import com.caucho.vfs.i18n.EncodingWriter; /** * Handles the dual char/byte buffering for the response stream. */ public abstract class ToByteResponseStream extends AbstractResponseStream { private static final L10N L = new L10N(ToByteResponseStream.class); private static final Logger log = Logger.getLogger(ToByteResponseStream.class.getName()); protected static final int SIZE = TempBuffer.SIZE; private final char []_charBuffer = new char[SIZE]; private int _charLength; // head of the expandable buffer private final TempBuffer _head = TempBuffer.allocate(); private TempBuffer _tail; private byte []_tailByteBuffer; private int _tailByteLength; // total buffer length, from servlet response setter private int _bufferCapacity; // extended buffer length private int _bufferSize; // true if character data should be ignored private boolean _isOutputStreamOnly; // true while char buffer is flushing for length/chunked private boolean _isCharFlushing; private EncodingWriter _toByte = Encoding.getLatin1Writer(); protected ToByteResponseStream() { } /** * Initializes the Buffered Response stream at the beginning of a request. */ @Override public void start() { super.start(); _bufferCapacity = SIZE; clearBuffer(); _isOutputStreamOnly = false; _toByte = Encoding.getLatin1Writer(); } /** * Returns true for a caucho response stream. */ @Override public boolean isCauchoResponseStream() { return true; } @Override public void setOutputStreamOnly(boolean isOutputStreamOnly) { _isOutputStreamOnly = isOutputStreamOnly; } protected boolean setFlush(boolean isAllowFlush) { return true; } /** * Sets the encoding. */ @Override public void setEncoding(String encoding) throws UnsupportedEncodingException { EncodingWriter toByte; if (encoding == null) toByte = Encoding.getLatin1Writer(); else toByte = Encoding.getWriteEncoding(encoding); if (toByte != null) _toByte = toByte; else { _toByte = Encoding.getLatin1Writer(); throw new UnsupportedEncodingException(encoding); } } /** * Sets the locale. */ @Override public void setLocale(Locale locale) throws UnsupportedEncodingException { } /** * Returns the char buffer. */ @Override public final char []getCharBuffer() { return _charBuffer; } /** * Returns the char offset. */ @Override public int getCharOffset() throws IOException { return _charLength; } /** * Sets the char offset. */ @Override public void setCharOffset(int offset) throws IOException { _charLength = offset; if (_charLength == SIZE) flushCharBuffer(); } /** * Returns the byte buffer. */ @Override public byte []getBuffer() throws IOException { if (! _isOutputStreamOnly) flushCharBuffer(); return _tailByteBuffer; } /** * Returns the byte offset. */ @Override public int getBufferOffset() throws IOException { if (! _isOutputStreamOnly) flushCharBuffer(); return _tailByteLength; } /** * Returns the byte offset. */ public int getByteBufferOffset() throws IOException { return _tailByteLength; } /** * Sets the byte offset. */ @Override public void setBufferOffset(int offset) throws IOException { _tailByteLength = offset; } /** * Returns the buffer capacity. */ @Override public int getBufferSize() { return _bufferCapacity; } /** * Sets the buffer capacity. */ @Override public void setBufferSize(int size) { _bufferCapacity = SIZE * ((size + SIZE - 1) / SIZE); if (_bufferCapacity <= 0) _bufferCapacity = 0; } /** * Returns the remaining value left. */ @Override public int getRemaining() { return _bufferCapacity - getBufferLength(); } /** * Returns the data in the buffer */ protected int getBufferLength() { return _bufferSize + _tailByteLength + _charLength; } protected boolean isDisableAutoFlush() { return false; } /** * Clears the response buffer. */ @Override public void clearBuffer() { TempBuffer next = _head.getNext(); if (next != null) { _head.setNext(null); TempBuffer.freeAll(next); } _head.clear(); _tail = _head; _tailByteBuffer = _tail.getBuffer(); _tailByteLength = 0; _charLength = 0; _bufferSize = 0; } /** * Writes a byte to the output. */ @Override public void write(int ch) throws IOException { if (isClosed() || isHead()) return; if (_charLength > 0) flushCharBuffer(); if (_bufferCapacity <= _bufferSize + _tailByteLength + 1) { flushByteBuffer(); } else if (_tailByteLength == SIZE) { _tail.setLength(_tailByteLength); _bufferSize += _tailByteLength; TempBuffer tempBuf = TempBuffer.allocate(); _tail.setNext(tempBuf); _tail = tempBuf; _tailByteBuffer = _tail.getBuffer(); _tailByteLength = 0; } _tailByteBuffer[_tailByteLength++] = (byte) ch; } /** * Writes a chunk of bytes to the stream. */ @Override public void write(byte []buffer, int offset, int length) throws IOException { if (isClosed() || isHead()) return; if (_charLength > 0) flushCharBuffer(); if (_bufferCapacity <= _bufferSize + _tailByteLength + length) { if (_bufferSize + _tailByteLength > 0) flushByteBuffer(); if (_bufferCapacity <= length) { writeHeaders(-1); // server/05bj // _bufferSize = length; boolean isFinished = false; writeNext(buffer, offset, length, isFinished); _bufferSize = 0; return; } } int byteLength = _tailByteLength; while (length > 0) { if (SIZE <= byteLength) { _tail.setLength(byteLength); _bufferSize += byteLength; TempBuffer tempBuf = TempBuffer.allocate(); _tail.setNext(tempBuf); _tail = tempBuf; _tailByteBuffer = _tail.getBuffer(); byteLength = 0; } int sublen = length; if (SIZE - byteLength < sublen) sublen = SIZE - byteLength; System.arraycopy(buffer, offset, _tailByteBuffer, byteLength, sublen); offset += sublen; length -= sublen; byteLength += sublen; } _tailByteLength = byteLength; } /** * Writes a character to the output. */ @Override public void print(int ch) throws IOException { if (isClosed() || isHead()) return; // server/13ww if (SIZE <= _charLength) flushCharBuffer(); _charBuffer[_charLength++] = (char) ch; } /** * Writes a char array to the output. */ @Override public void print(char []buffer, int offset, int length) throws IOException { if (isClosed() || isHead()) return; int charLength = _charLength; while (length > 0) { int sublen = SIZE - charLength; if (length < sublen) sublen = length; System.arraycopy(buffer, offset, _charBuffer, charLength, sublen); offset += sublen; length -= sublen; charLength += sublen; if (charLength == SIZE && length > 0) { _charLength = charLength; charLength = 0; flushCharBuffer(); } } _charLength = charLength; } /** * Converts the char buffer. */ @Override public char []nextCharBuffer(int offset) throws IOException { _charLength = offset; flushCharBuffer(); return _charBuffer; } /** * True while the char buffer is being flushed, needed * for content-length vs chunked headers. */ protected boolean isCharFlushing() { return _isCharFlushing; } /** * Converts the char buffer. */ protected void flushCharBuffer() throws IOException { int charLength = _charLength; _charLength = 0; if (charLength > 0 && ! _isOutputStreamOnly) { // server/05ef _isCharFlushing = true; try { boolean isFlush = setFlush(false); _toByte.write(this, _charBuffer, 0, charLength); _charLength = 0; setFlush(isFlush); } finally { _isCharFlushing = false; } if (_bufferCapacity <= _tailByteLength + _bufferSize) { flushByteBuffer(); } // server/05e8, jsp/0182, jsp/0502, jsp/0503 // _isCommitted = true; } } @Override public int getContentLength() { try { // server/05e8 flushCharBuffer(); } catch (IOException e) { log.log(Level.FINE, e.toString(), e); } return _bufferSize + _tailByteLength; } /** * Returns the next byte buffer. */ @Override public byte []nextBuffer(int offset) throws IOException { if (offset < 0 || SIZE < offset) throw new IllegalStateException(L.l("Invalid offset: " + offset)); if (_bufferCapacity <= SIZE || _bufferCapacity <= offset + _bufferSize) { _tailByteLength = offset; flushByteBuffer(); return getBuffer(); } else { _tail.setLength(offset); _bufferSize += offset; TempBuffer tempBuf = TempBuffer.allocate(); _tail.setNext(tempBuf); _tail = tempBuf; _tailByteBuffer = _tail.getBuffer(); _tailByteLength = 0; return _tailByteBuffer; } } /** * Flushes the buffered response to the output stream. */ protected void flushByteBuffer() throws IOException { // jsp/0182 if (isDisableAutoFlush()) throw new IOException("auto-flush is disabled"); // jsp/0182 jsp/0502 jsp/0503 // _isCommitted = true; boolean isFinished = isClosing(); if (_tailByteLength == 0 && _bufferSize == 0) { if (! isCommitted()) { // server/0101 writeHeaders(0); } return; } _tail.setLength(_tailByteLength); _bufferSize += _tailByteLength; _tailByteLength = 0; writeHeaders(_bufferSize); TempBuffer ptr = _head; do { TempBuffer next = ptr.getNext(); ptr.setNext(null); writeNext(ptr.getBuffer(), 0, ptr.getLength(), isFinished); if (ptr != _head) { TempBuffer.free(ptr); ptr = null; } ptr = next; } while (ptr != null); _tail = _head; _tail.setLength(0); _tailByteBuffer = _tail.getBuffer(); _bufferSize = 0; } /** * Writes any http headers. Because this may be called * multiple times, the implementation needs to ensure * the header is written once * * @param length the current buffer length * @throws IOException */ protected void writeHeaders(int length) throws IOException { } /** * Writes the chunk to the downward stream. */ abstract protected void writeNext(byte []buffer, int offset, int length, boolean isEnd) throws IOException; /** * Flushes the buffer. */ @Override public void flushBuffer() throws IOException { if (isDisableAutoFlush()) throw new IOException("auto-flush is disabled"); if (_charLength > 0) flushCharBuffer(); flushByteBuffer(); } /** * Flushes the buffered response to the output stream. */ @Override public void flush() throws IOException { flushBuffer(); } /** * Closes the response stream. */ @Override protected void closeImpl() throws IOException { flushBuffer(); } }