diff options
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.java | 358 |
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 |