diff options
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.java | 238 |
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(); + } +} |