diff options
Diffstat (limited to 'simple/simple-http/src/main/java/org/simpleframework/http/core/ResponseBuffer.java')
-rw-r--r-- | simple/simple-http/src/main/java/org/simpleframework/http/core/ResponseBuffer.java | 303 |
1 files changed, 303 insertions, 0 deletions
diff --git a/simple/simple-http/src/main/java/org/simpleframework/http/core/ResponseBuffer.java b/simple/simple-http/src/main/java/org/simpleframework/http/core/ResponseBuffer.java new file mode 100644 index 0000000..d2a9f80 --- /dev/null +++ b/simple/simple-http/src/main/java/org/simpleframework/http/core/ResponseBuffer.java @@ -0,0 +1,303 @@ +/* + * 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.ResponseEncoder + */ +class ResponseBuffer extends OutputStream implements WritableByteChannel { + + /** + * This is the transfer object used to transfer the response. + */ + private ResponseEncoder encoder; + + /** + * 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 observer this is used to notify of response completion + * @param response this is the response header for this buffer + * @param support this is used to determine the response semantics + * @param entity this is used to acquire the underlying transport + */ + public ResponseBuffer(BodyObserver observer, Response response, Conversation support, Entity entity) { + this(observer, response, support, entity.getChannel()); + } + + /** + * 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 observer this is used to notify of response completion + * @param response this is the response header for this buffer + * @param support this is used to determine the response semantics + * @param channel this is the channel used to write the data to + */ + public ResponseBuffer(BodyObserver observer, Response response, Conversation support, Channel channel) { + this.encoder = new ResponseEncoder(observer, response, support, channel); + 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 + */ + public boolean isOpen() { + return !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(flushed) { + throw new IOException("Response has been flushed"); + } + 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 + */ + public void write(int octet) throws IOException { + byte value = (byte) octet; + + if(closed) { + throw new IOException("Response has been transferred"); + } + 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 + */ + public void write(byte[] array, int off, int size) throws IOException { + ByteBuffer buffer = ByteBuffer.wrap(array, off, size); + + if(size > 0) { + 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 + */ + public int write(ByteBuffer source) throws IOException { + int mark = source.position(); + int size = source.limit(); + + if(mark > size) { + throw new ResponseException("Buffer position greater than limit"); + } + return 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(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(count + size > buffer.length) { + flush(false); + } + if(size > buffer.length){ + encoder.write(source); + } else { + source.get(buffer, count, size); + 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(buffer.length < capacity) { + int size = buffer.length * 2; + int resize = Math.max(capacity, size); + byte[] temp = new byte[resize]; + + System.arraycopy(buffer, 0, temp, 0, count); + 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. + */ + public void flush() throws IOException { + 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(!flushed) { + encoder.start(); + } + if(count > 0) { + encoder.write(buffer, 0, count); + } + if(flush) { + encoder.flush(); + } + flushed = true; + 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. + */ + public void close() throws IOException { + if(!closed) { + commit(); + } + flushed = true; + 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(!flushed) { + encoder.start(count); + } + if(count > 0) { + encoder.write(buffer, 0, count); + } + encoder.close(); + } +} + + |