summaryrefslogtreecommitdiffstats
path: root/simple/simple-http/src/main/java/org/simpleframework/http/core/ResponseBuffer.java
diff options
context:
space:
mode:
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.java303
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();
+ }
+}
+
+