/*
* 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;
}
}