summaryrefslogtreecommitdiffstats
path: root/simple/simple-http/src/main/java/org/simpleframework/http/core/Conversation.java
diff options
context:
space:
mode:
Diffstat (limited to 'simple/simple-http/src/main/java/org/simpleframework/http/core/Conversation.java')
-rw-r--r--simple/simple-http/src/main/java/org/simpleframework/http/core/Conversation.java358
1 files changed, 358 insertions, 0 deletions
diff --git a/simple/simple-http/src/main/java/org/simpleframework/http/core/Conversation.java b/simple/simple-http/src/main/java/org/simpleframework/http/core/Conversation.java
new file mode 100644
index 0000000..07318ce
--- /dev/null
+++ b/simple/simple-http/src/main/java/org/simpleframework/http/core/Conversation.java
@@ -0,0 +1,358 @@
+/*
+ * Conversation.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 static org.simpleframework.http.Method.CONNECT;
+import static org.simpleframework.http.Method.HEAD;
+import static org.simpleframework.http.Protocol.CHUNKED;
+import static org.simpleframework.http.Protocol.CLOSE;
+import static org.simpleframework.http.Protocol.CONNECTION;
+import static org.simpleframework.http.Protocol.CONTENT_LENGTH;
+import static org.simpleframework.http.Protocol.KEEP_ALIVE;
+import static org.simpleframework.http.Protocol.TRANSFER_ENCODING;
+import static org.simpleframework.http.Protocol.UPGRADE;
+import static org.simpleframework.http.Protocol.WEBSOCKET;
+
+import org.simpleframework.http.RequestHeader;
+import org.simpleframework.http.ResponseHeader;
+
+/**
+ * The <code>Conversation</code> object is used to set and interpret
+ * the semantics of the HTTP headers with regard to the encoding
+ * used for the response. This will ensure the the correct headers
+ * are used so that if chunked encoding or a connection close is
+ * needed that the headers are set accordingly. This allows both the
+ * server and client to agree on the best semantics to use.
+ *
+ * @author Niall Gallagher
+ *
+ * @see org.simpleframework.http.core.ResponseBuffer
+ * @see org.simpleframework.http.core.ResponseEncoder
+ */
+public class Conversation {
+
+ /**
+ * This is the response object that requires HTTP headers set.
+ */
+ private final ResponseHeader response;
+
+ /**
+ * This contains the request headers and protocol version.
+ */
+ private final RequestHeader request;
+
+ /**
+ * Constructor for the <code>Conversation</code> object. This is
+ * used to create an object that makes use of both the request
+ * and response HTTP headers to determine how best to deliver
+ * the response body. Depending on the protocol version and the
+ * existing response headers suitable semantics are determined.
+ *
+ * @param request this is the request from the client
+ * @param response this is the response that is to be sent
+ */
+ public Conversation(RequestHeader request, ResponseHeader response) {
+ this.response = response;
+ this.request = request;
+ }
+
+ /**
+ * This provides the <code>Request</code> object. This can be
+ * used to acquire the request HTTP headers and protocl version
+ * used by the client. Typically the conversation provides all
+ * the data needed to determine the type of response required.
+ *
+ * @return this returns the request object for the conversation
+ */
+ public RequestHeader getRequest() {
+ return request;
+ }
+
+ /**
+ * This provides the <code>Response</code> object. This is used
+ * when the commit is required on the response. By committing
+ * the response the HTTP header is generated and delivered to
+ * the underlying transport.
+ *
+ * @return this returns the response for the conversation
+ */
+ public ResponseHeader getResponse() {
+ return response;
+ }
+
+ /**
+ * This is used to acquire the content length for the response.
+ * The content length is acquired fromt he Content-Length header
+ * if it has been set. If not then this will return a -1 value.
+ *
+ * @return this returns the value for the content length header
+ */
+ public long getContentLength() {
+ return response.getContentLength();
+ }
+
+ /**
+ * This is used to determine if the <code>Response</code> has a
+ * message body. If this does not have a message body then true
+ * is returned. This is determined as of RFC 2616 rules for the
+ * presence of a message body. A message body must not be
+ * included with a HEAD request or with a 304 or a 204 response.
+ * If when this is called there is no message length delimiter
+ * as specified by section RFC 2616 4.4, then there is no body.
+ *
+ * @return true if there is no response body, false otherwise
+ */
+ public boolean isEmpty() {
+ int code = response.getCode();
+
+ if(code == 204){
+ return true;
+ }
+ if(code == 304){
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * This is used to determine if the request method was HEAD. This
+ * is of particular interest in a HTTP conversation as it tells
+ * the response whether a response body is to be sent or not.
+ * If the method is head the delimeters for the response should
+ * be as they would be for a similar GET, however no body is sent.
+ *
+ * @return true if the request method was a HEAD method
+ */
+ public boolean isHead() {
+ String method = request.getMethod();
+
+ if(method != null) {
+ return method.equalsIgnoreCase(HEAD);
+ }
+ return false;
+ }
+
+ /**
+ * This is used to determine if the method was a CONNECT. The
+ * connect method is typically used when a client wishes to
+ * establish a connection directly with an origin server. Such a
+ * direct connection is useful when using TLS as it ensures there
+ * is not man in the middle with respect to key exchanges.
+ *
+ * @return this returns true if the method was a CONNECT method
+ */
+ public boolean isConnect() {
+ String method = request.getMethod();
+
+ if(method != null) {
+ return method.equalsIgnoreCase(CONNECT);
+ }
+ return false;
+ }
+
+ /**
+ * This is used to set the content length for the response. If
+ * the HTTP version is HTTP/1.1 then the Content-Length header is
+ * used, if an earlier protocol version is used then connection
+ * close semantics are also used to ensure client compatibility.
+ *
+ * @param length this is the length to set HTTP header to
+ */
+ public void setContentLength(long length) {
+ boolean keepAlive = isKeepAlive();
+
+ if(keepAlive) {
+ response.setValue(CONNECTION, KEEP_ALIVE);
+ } else {
+ response.setValue(CONNECTION, CLOSE);
+ }
+ response.setLong(CONTENT_LENGTH, length);
+ }
+
+ /**
+ * This checks the protocol version used in the request to check
+ * whether it supports persistent HTTP connections. By default the
+ * HTTP/1.1 protocol supports persistent connnections, this can
+ * onlyy be overridden with a Connection header with the close
+ * token. Earlier protocol versions are connection close.
+ *
+ * @return this returns true if the protocol is HTTP/1.1 or above
+ */
+ public boolean isPersistent() {
+ String token = request.getValue(CONNECTION);
+
+ if(token != null) {
+ return token.equalsIgnoreCase(KEEP_ALIVE);
+ }
+ int major = request.getMajor();
+ int minor = request.getMinor();
+
+ if(major > 1) {
+ return true;
+ }
+ if(major == 1) {
+ return minor > 0;
+ }
+ return false;
+ }
+
+ /**
+ * The <code>isKeepAlive</code> method is used to determine if
+ * the connection semantics are set to maintain the connection.
+ * This checks to see if there is a Connection header with the
+ * keep-alive token, if so then the connection is keep alive, if
+ * however there is no connection header the version is used.
+ *
+ * @return true if the response connection is to be maintained
+ */
+ public boolean isKeepAlive() {
+ String token = response.getValue(CONNECTION);
+
+ if(token != null) {
+ return !token.equalsIgnoreCase(CLOSE);
+ }
+ return isPersistent();
+ }
+
+ /**
+ * The <code>isChunkable</code> method is used to determine if
+ * the client supports chunked encoding. If the client does not
+ * support chunked encoding then a connection close should be used
+ * instead, this allows HTTP/1.0 clients to be supported properly.
+ *
+ * @return true if the client supports chunked transfer encoding
+ */
+ public boolean isChunkable() {
+ int major = request.getMajor();
+ int minor = request.getMinor();
+
+ if(major >= 1) {
+ return minor >= 1;
+ }
+ return false;
+ }
+
+ /**
+ * This is used when the output is encoded in the chunked encoding.
+ * This should only be used if the protocol version is HTTP/1.1 or
+ * above. If the protocol version supports chunked encoding then it
+ * will encode the data as specified in RFC 2616 section 3.6.1.
+ */
+ public void setChunkedEncoded() {
+ boolean keepAlive = isKeepAlive();
+ boolean chunkable = isChunkable();
+
+ if(keepAlive && chunkable) {
+ response.setValue(TRANSFER_ENCODING, CHUNKED);
+ response.setValue(CONNECTION, KEEP_ALIVE);
+ } else {
+ response.setValue(CONNECTION, CLOSE);
+ }
+ }
+
+ /**
+ * This is used to set the response to a connection upgrade. The
+ * response for an upgrade contains no payload delimeter such as
+ * content length or transfer encoding. It is typically used when
+ * establishing a web socket connection or a HTTP tunnel.
+ */
+ public void setConnectionUpgrade() {
+ response.setValue(TRANSFER_ENCODING, null);
+ response.setValue(CONTENT_LENGTH, null);
+ response.setValue(CONNECTION, UPGRADE);
+ }
+
+ /**
+ * This will remove all explicit transfer encoding headers from
+ * the response header. By default the identity encoding is used
+ * for all connections, it basically means no encoding. So if the
+ * response uses a Content-Length it implicitly assumes tha the
+ * encoding of the response is identity encoding.
+ */
+ public void setIdentityEncoded() {
+ response.setValue(TRANSFER_ENCODING, null);
+ }
+
+ /**
+ * The <code>isChunkedEncoded</code> is used to determine whether
+ * the chunked encoding scheme is desired. This is enables data to
+ * be encoded in such a way that a connection can be maintained
+ * without a Content-Length header. If the output is chunked then
+ * the connection is keep alive.
+ *
+ * @return true if the response output is chunked encoded
+ */
+ public boolean isChunkedEncoded() {
+ String token = response.getValue(TRANSFER_ENCODING);
+
+ if(token != null) {
+ return token.equalsIgnoreCase(CHUNKED);
+ }
+ return false;
+ }
+
+ /**
+ * This is used to determine if a WebSocket upgrade was requested
+ * and established. An upgrade to use a WebSocket is done when the
+ * client requests the upgrade and the server responds with an
+ * upgrade confirmation, this is the basic handshake required.
+ *
+ * @return this returns true if a WebSocket handshake succeeded
+ */
+ public boolean isWebSocket() {
+ String token = request.getValue(UPGRADE);
+ int code = response.getCode();
+
+ if(token != null && code == 101) {
+ String reply = response.getValue(UPGRADE);
+
+ if(token.equalsIgnoreCase(WEBSOCKET)) {
+ return token.equalsIgnoreCase(reply);
+ }
+ }
+ return false;
+ }
+
+ /**
+ * This is used to determine if a tunnel should be established.
+ * A tunnel is where the the HTTP server no longer processes any
+ * HTTP requests, it simply forms a byte stream with the client.
+ * Scenarios where tunnels are useful are when WebSockets are
+ * required or when a client wants a TLS connection to an origin
+ * server through a proxy server.
+ *
+ * @return this returns true if a tunnel has been established
+ */
+ public boolean isTunnel() {
+ boolean socket = isWebSocket();
+
+ if(!socket) {
+ int code = response.getCode();
+
+ if(code < 200) {
+ return false;
+ }
+ if(code >= 300) {
+ return false;
+ }
+ return isConnect();
+ }
+ return true;
+ }
+} \ No newline at end of file