/* * 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.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 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.Transfer */ 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 this.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 this.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 this.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 = this.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 = this.request.getMethod(); if (method == null) return false; return method.equalsIgnoreCase(HEAD); } /** * 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 = this.isKeepAlive(); if (keepAlive) { this.response.setValue(CONNECTION, KEEP_ALIVE); } else { this.response.setValue(CONNECTION, CLOSE); } this.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 = this.request.getValue(CONNECTION); if (token != null) return token.equalsIgnoreCase(KEEP_ALIVE); int major = this.request.getMajor(); int minor = this.request.getMinor(); if (major >= 1) return minor >= 1; 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 = this.response.getValue(CONNECTION); if (token != null) return token.equalsIgnoreCase(KEEP_ALIVE); return this.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 = this.request.getMajor(); int minor = this.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 = this.isKeepAlive(); boolean chunkable = this.isChunkable(); if (keepAlive && chunkable) { this.response.setValue(TRANSFER_ENCODING, CHUNKED); this.response.setValue(CONNECTION, KEEP_ALIVE); } else { this.response.setValue(CONNECTION, CLOSE); } } /** * 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() { this.response.remove(TRANSFER_ENCODING); } /** * 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 = this.response.getValue(TRANSFER_ENCODING); if (token != null) return token.equalsIgnoreCase(CHUNKED); return false; } }