/* * ResponseBuffer.java February 2007 * * Copyright (C) 2001, Niall Gallagher <niallg@users.sf.net> * * 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.simpleframework.http.core; import java.io.IOException; import java.io.OutputStream; import java.nio.ByteBuffer; import java.nio.channels.WritableByteChannel; import org.simpleframework.http.Response; import org.simpleframework.http.message.Entity; import org.simpleframework.transport.Channel; /** * The <code>ResponseBuffer</code> object is an output stream that can buffer * bytes written up to a given size. This is used if a buffer is requested for * the response output. Such a mechanism allows the response to be written * without committing the response. Also it enables content that has been * written to be reset, by simply clearing the response buffer. If the response * buffer overflows then the response is committed. * * @author Niall Gallagher * * @see org.simpleframework.http.core.Transfer */ class ResponseBuffer extends OutputStream implements WritableByteChannel { /** * This is the transfer object used to transfer the response. */ private Transfer transfer; /** * This is the buffer used to accumulate the response bytes. */ private byte[] buffer; /** * This is used to determine if the accumulate was flushed. */ private boolean flushed; /** * This is used to determine if the accumulator was closed. */ private boolean closed; /** * This counts the number of bytes that have been accumulated. */ private int count; /** * Constructor for the <code>ResponseBuffer</code> object. This will create * a buffering output stream which will flush data to the underlying * transport provided with the entity. All I/O events are reported to the * monitor so the server can process other requests within the pipeline when * the current one is finished. * * @param support * this is used to determine the response semantics * @param entity * this is used to acquire the underlying transport * @param monitor * this is used to report I/O events to the kernel */ public ResponseBuffer(Response response, Conversation support, Entity entity, Monitor monitor) { this(response, support, entity.getChannel(), monitor); } /** * Constructor for the <code>ResponseBuffer</code> object. This will create * a buffering output stream which will flush data to the underlying * transport provided with the channel. All I/O events are reported to the * monitor so the server can process other requests within the pipeline when * the current one is finished. * * @param support * this is used to determine the response semantics * @param channel * this is the channel used to write the data to * @param monitor * this is used to report I/O events to the kernel */ public ResponseBuffer(Response response, Conversation support, Channel channel, Monitor monitor) { this.transfer = new Transfer(response, support, channel, monitor); this.buffer = new byte[] {}; } /** * This is used to determine if the accumulator is still open. If the * accumulator is still open then data can still be written to it and this * transmitted to the client. When the accumulator is closed the data is * committed and this can not be used. * * @return this returns true if the accumulator object is open */ @Override public boolean isOpen() { return !this.closed; } /** * This is used to reset the buffer so that it can be written to again. If * the accumulator has already been flushed then the stream can not be * reset. Resetting the stream is typically done if there is an error in * writing the response and an error message is generated to replaced the * partial response. */ public void reset() throws IOException { if (this.flushed) throw new IOException("Response has been flushed"); this.count = 0; } /** * This is used to write the provided octet to the buffer. If the buffer is * full it will be flushed and the octet is appended to the start of the * buffer. If however the buffer is zero length then this will write * directly to the underlying transport. * * @param octet * this is the octet that is to be written */ @Override public void write(int octet) throws IOException { byte value = (byte) octet; if (this.closed) throw new IOException("Response has been transferred"); this.write(new byte[] { value }); } /** * This is used to write the provided array to the buffer. If the buffer is * full it will be flushed and the array is appended to the start of the * buffer. If however the buffer is zero length then this will write * directly to the underlying transport. * * @param array * this is the array of bytes to send to the client * @param off * this is the offset within the array to send from * @param size * this is the number of bytes that are to be sent */ @Override public void write(byte[] array, int off, int size) throws IOException { ByteBuffer buffer = ByteBuffer.wrap(array, off, size); if (size > 0) { this.write(buffer); } } /** * This is used to write the provided buffer to the buffer. If the buffer is * full it will be flushed and the buffer is appended to the start of the * buffer. If however the buffer is zero length then this will write * directly to the underlying transport. * * @param source * this is the byte buffer to send to the client * * @return this returns the number of bytes that have been sent */ @Override public int write(ByteBuffer source) throws IOException { int mark = source.position(); int size = source.limit(); if (mark > size) throw new TransferException("Buffer position greater than limit"); return this.write(source, 0, size - mark); } /** * This is used to write the provided buffer to the buffer. If the buffer is * full it will be flushed and the buffer is appended to the start of the * buffer. If however the buffer is zero length then this will write * directly to the underlying transport. * * @param source * this is the byte buffer to send to the client * @param off * this is the offset within the array to send from * @param size * this is the number of bytes that are to be sent * * @return this returns the number of bytes that have been sent */ public int write(ByteBuffer source, int off, int size) throws IOException { if (this.closed) throw new IOException("Response has been transferred"); int mark = source.position(); int limit = source.limit(); if ((limit - mark) < size) { // not enough data size = limit - mark; // reduce expectation } if ((this.count + size) > this.buffer.length) { this.flush(false); } if (size > this.buffer.length) { this.transfer.write(source); } else { source.get(this.buffer, this.count, size); this.count += size; } return size; } /** * This is used to expand the capacity of the internal buffer. If there is * already content that has been appended to the buffer this will copy that * data to the newly created buffer. This will not decrease the size of the * buffer if it is larger than the requested capacity. * * @param capacity * this is the capacity to expand the buffer to */ public void expand(int capacity) throws IOException { if (this.buffer.length < capacity) { int size = this.buffer.length * 2; int resize = Math.max(capacity, size); byte[] temp = new byte[resize]; System.arraycopy(this.buffer, 0, temp, 0, this.count); this.buffer = temp; } } /** * This is used to flush the contents of the buffer to the underlying * transport. Once the accumulator is flushed the HTTP headers are written * such that the semantics of the connection match the protocol version and * the existing response headers. */ @Override public void flush() throws IOException { this.flush(true); } /** * This is used to flush the contents of the buffer to the underlying * transport. Once the accumulator is flushed the HTTP headers are written * such that the semantics of the connection match the protocol version and * the existing response headers. * * @param flush * indicates whether the transport should be flushed */ private void flush(boolean flush) throws IOException { if (!this.flushed) { this.transfer.start(); } if (this.count > 0) { this.transfer.write(this.buffer, 0, this.count); } if (flush) { this.transfer.flush(); } this.flushed = true; this.count = 0; } /** * This will flush the buffer to the underlying transport and close the * stream. Once the accumulator is flushed the HTTP headers are written such * that the semantics of the connection match the protocol version and the * existing response headers. Closing this stream does not mean the * connection is closed. */ @Override public void close() throws IOException { if (!this.closed) { this.commit(); } this.flushed = true; this.closed = true; } /** * This will close the underlying transfer object which will notify the * server kernel that the next request is read to be processed. If the * accumulator is unflushed then this will set a Content-Length header such * that it matches the number of bytes that are buffered within the internal * buffer. */ private void commit() throws IOException { if (!this.flushed) { this.transfer.start(this.count); } if (this.count > 0) { this.transfer.write(this.buffer, 0, this.count); } this.transfer.close(); } }