summaryrefslogtreecommitdiffstats
path: root/simple/simple-http/src/main/java/org/simpleframework/http/core/ResponseObserver.java
diff options
context:
space:
mode:
Diffstat (limited to 'simple/simple-http/src/main/java/org/simpleframework/http/core/ResponseObserver.java')
-rw-r--r--simple/simple-http/src/main/java/org/simpleframework/http/core/ResponseObserver.java238
1 files changed, 238 insertions, 0 deletions
diff --git a/simple/simple-http/src/main/java/org/simpleframework/http/core/ResponseObserver.java b/simple/simple-http/src/main/java/org/simpleframework/http/core/ResponseObserver.java
new file mode 100644
index 0000000..4a4b2c7
--- /dev/null
+++ b/simple/simple-http/src/main/java/org/simpleframework/http/core/ResponseObserver.java
@@ -0,0 +1,238 @@
+/*
+ * ResponseObserver.java February 2007
+ *
+ * Copyright (C) 2007, 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 static java.util.concurrent.TimeUnit.MILLISECONDS;
+import static org.simpleframework.http.core.ContainerEvent.ERROR;
+import static org.simpleframework.http.core.ContainerEvent.RESPONSE_FINISHED;
+
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import org.simpleframework.http.message.Entity;
+import org.simpleframework.transport.Channel;
+import org.simpleframework.transport.ByteWriter;
+import org.simpleframework.transport.trace.Trace;
+
+/**
+ * The <code>ResponseObserver</code> is used to observe the response
+ * streams. If there is an error or a close requested this will
+ * close the underlying transport. If however there is a successful
+ * response then this will flush the transport and hand the channel
+ * for the pipeline back to the server kernel. This ensures that
+ * the next HTTP request can be consumed from the transport.
+ *
+ * @author Niall Gallagher
+ */
+class ResponseObserver implements BodyObserver {
+
+ /**
+ * This is used to determine if the response has committed.
+ */
+ private AtomicBoolean committed;
+
+ /**
+ * This flag determines whether the connection was closed.
+ */
+ private AtomicBoolean closed;
+
+ /**
+ * This flag determines whether the was a response error.
+ */
+ private AtomicBoolean error;
+
+ /**
+ * This is the controller used to initiate a new request.
+ */
+ private Controller controller;
+
+ /**
+ * This is the channel associated with the client connection.
+ */
+ private Channel channel;
+
+ /**
+ * This is the trace used to observe the state of the stream.
+ */
+ private Trace trace;
+
+ /**
+ * This represents a time stamp that records the finish time.
+ */
+ private Timer timer;
+
+ /**
+ * Constructor for the <code>ResponseObserver</code> object. This
+ * is used to create an observer using a HTTP request entity and an
+ * initiator which is used to reprocess a channel if there was a
+ * successful deliver of a response.
+ *
+ * @param controller the controller used to process channels
+ * @param entity this is the entity associated with the channel
+ */
+ public ResponseObserver(Controller controller, Entity entity) {
+ this.timer = new Timer(MILLISECONDS);
+ this.committed = new AtomicBoolean();
+ this.closed = new AtomicBoolean();
+ this.error = new AtomicBoolean();
+ this.channel = entity.getChannel();
+ this.trace = channel.getTrace();
+ this.controller = controller;
+ }
+
+ /**
+ * This is used to close the underlying transport. A closure is
+ * typically done when the response is to a HTTP/1.0 client
+ * that does not require a keep alive connection. Also, if the
+ * container requests an explicit closure this is used when all
+ * of the content for the response has been sent.
+ *
+ * @param writer this is the writer used to send the response
+ */
+ public void close(ByteWriter writer) {
+ try {
+ if(!isClosed()) {
+ closed.set(true);
+ timer.set();
+ trace.trace(RESPONSE_FINISHED);
+ writer.close();
+ }
+ } catch(Exception cause) {
+ trace.trace(ERROR, cause);
+ fail(writer);
+ }
+ }
+
+ /**
+ * This is used when there is an error sending the response. On
+ * error RFC 2616 suggests a connection closure is the best
+ * means to handle the condition, and the one clients should be
+ * expecting and support. All errors result in closure of the
+ * underlying transport and no more requests are processed.
+ *
+ * @param writer this is the writer used to send the response
+ */
+ public void error(ByteWriter writer) {
+ try {
+ if(!isClosed()) {
+ error.set(true);
+ timer.set();
+ trace.trace(RESPONSE_FINISHED);
+ writer.close();
+ }
+ } catch(Exception cause) {
+ trace.trace(ERROR, cause);
+ fail(writer);
+ }
+ }
+
+ /**
+ * This is used when the response has been sent correctly and
+ * the connection supports persisted HTTP. When ready the channel
+ * is handed back in to the server kernel where the next request
+ * on the pipeline is read and used to compose the next entity.
+ *
+ * @param writer this is the writer used to send the response
+ */
+ public void ready(ByteWriter writer) {
+ try {
+ if(!isClosed()) {
+ closed.set(true);
+ writer.flush();
+ timer.set();
+ trace.trace(RESPONSE_FINISHED);
+ controller.start(channel);
+ }
+ } catch(Exception cause) {
+ trace.trace(ERROR, cause);
+ fail(writer);
+ }
+ }
+
+ /**
+ * This is used to purge the writer so that it closes the socket
+ * ensuring there is no connection leak on shutdown. This is used
+ * when there is an exception signalling the state of the writer.
+ *
+ * @param writer this is the writer that is to be purged
+ */
+ private void fail(ByteWriter writer) {
+ try {
+ writer.close();
+ } catch(Exception cause) {
+ trace.trace(ERROR, cause);
+ }
+ }
+
+ /**
+ * This is used to notify the observer that the HTTP response is
+ * committed and that the header can no longer be changed. It
+ * is also used to indicate whether the response can be reset.
+ *
+ * @param writer this is the writer used to send the response
+ */
+ public void commit(ByteWriter writer) {
+ committed.set(true);
+ }
+
+ /**
+ * This can be used to determine whether the response has been
+ * committed. If the response is committed then the header can
+ * no longer be manipulated and the response has been partially
+ * send to the client.
+ *
+ * @return true if the response headers have been committed
+ */
+ public boolean isCommitted() {
+ return committed.get();
+ }
+
+ /**
+ * This is used to determine if the response has completed or
+ * if there has been an error. This basically allows the writer
+ * of the response to take action on certain I/O events.
+ *
+ * @return this returns true if there was an error or close
+ */
+ public boolean isClosed() {
+ return closed.get() || error.get();
+ }
+
+ /**
+ * This is used to determine if the response was in error. If
+ * the response was in error this allows the writer to throw an
+ * exception indicating that there was a problem responding.
+ *
+ * @return this returns true if there was a response error
+ */
+ public boolean isError(){
+ return error.get();
+ }
+
+ /**
+ * This represents the time at which the response was either
+ * ready, closed or in error. Providing a time here is useful
+ * as it allows the time taken to generate a response to be
+ * determined even if the response is written asynchronously.
+ *
+ * @return the time when the response completed or failed
+ */
+ public long getTime() {
+ return timer.get();
+ }
+}