net.lightbody.bmp.proxy.jetty.http.HttpMessage.java Source code

Java tutorial

Introduction

Here is the source code for net.lightbody.bmp.proxy.jetty.http.HttpMessage.java

Source

// ========================================================================
// $Id: HttpMessage.java,v 1.41 2006/04/04 22:28:02 gregwilkins Exp $
// Copyright 199-2004 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// 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 net.lightbody.bmp.proxy.jetty.http;

import net.lightbody.bmp.proxy.jetty.log.LogFactory;
import net.lightbody.bmp.proxy.jetty.util.LogSupport;
import net.lightbody.bmp.proxy.jetty.util.QuotedStringTokenizer;
import net.lightbody.bmp.proxy.jetty.util.TypeUtil;
import org.apache.commons.logging.Log;

import java.io.*;
import java.util.*;

/* ------------------------------------------------------------ */
/** HTTP Message base.
 * This class forms the basis of HTTP requests and replies. It provides
 * header fields, content and optional trailer fields, while managing the
 * state of the message.
 *
 * @version $Id: HttpMessage.java,v 1.41 2006/04/04 22:28:02 gregwilkins Exp $
 * @author Greg Wilkins (gregw)
 */

public abstract class HttpMessage {
    private static Log log = LogFactory.getLog(HttpMessage.class);

    /* ------------------------------------------------------------ */
    public final static String __SCHEME = "http";
    public final static String __SSL_SCHEME = "https";

    /* ------------------------------------------------------------ */
    public final static String __HTTP_0_9 = "HTTP/0.9";
    public final static String __HTTP_1_0 = "HTTP/1.0";
    public final static String __HTTP_1_1 = "HTTP/1.1";
    public final static String __HTTP_1_X = "HTTP/1.";

    /* ------------------------------------------------------------ */
    public interface HeaderWriter {
        void writeHeader(HttpMessage httpMessage) throws IOException;
    }

    /* ------------------------------------------------------------ */
    /* ------------------------------------------------------------ */
    /** Message States.
     */
    public final static int __MSG_EDITABLE = 0, // Created locally, all set methods enabled
            __MSG_BAD = 1, // Bad message/
            __MSG_RECEIVED = 2, // Received from connection.
            __MSG_SENDING = 3, // Headers sent.
            __MSG_SENT = 4; // Entity and trailers sent.

    public final static String[] __state = { "EDITABLE", "BAD", "RECEIVED", "SENDING", "SENT" };

    /* ------------------------------------------------------------ */
    protected int _state = __MSG_EDITABLE;
    protected String _version;
    protected int _dotVersion;
    protected HttpFields _header = new HttpFields();
    protected HttpConnection _connection;
    protected String _characterEncoding;
    protected String _mimeType;
    protected Object _wrapper;
    protected Map _attributes;

    /* ------------------------------------------------------------ */
    /** Constructor. 
     */
    protected HttpMessage() {
    }

    /* ------------------------------------------------------------ */
    /** Constructor. 
     */
    protected HttpMessage(HttpConnection connection) {
        _connection = connection;
    }

    /* ------------------------------------------------------------ */
    /** Set a wrapper object.
     * A wrapper object is an object associated with this message and
     * presents it with an different interface. The
     * primary example of a HttpRequest facade is ServletHttpRequest.
     * A single facade object may be associated with the message with
     * this call and retrieved with the getFacade method.
     */
    public void setWrapper(Object wrapper) {
        _wrapper = wrapper;
    }

    /* ------------------------------------------------------------ */
    /** Get an associated wrapper object.
     * @return Wrapper message or null.
     */
    public Object getWrapper() {
        return _wrapper;
    }

    /* ------------------------------------------------------------ */
    protected void reset() {
        _state = __MSG_EDITABLE;
        _header.clear();
    }

    /* ------------------------------------------------------------ */
    public HttpConnection getHttpConnection() {
        return _connection;
    }

    /* ------------------------------------------------------------ */
    public InputStream getInputStream() {
        if (_connection == null)
            return null;
        return _connection.getInputStream();
    }

    /* ------------------------------------------------------------ */
    public OutputStream getOutputStream() {
        if (_connection == null)
            return null;
        return _connection.getOutputStream();
    }

    /* ------------------------------------------------------------ */
    /** Get the message state.
     * <PRE>
     * __MSG_EDITABLE = 0 - Created locally, all set methods enabled
     * __MSG_BAD      = 1 - Bad message or send failure.
     * __MSG_RECEIVED = 2 - Received from connection.
     * __MSG_SENDING  = 3 - Headers sent.
     * __MSG_SENT     = 4 - Entity and trailers sent.
     * </PRE>
     * @return the state.
     */
    public int getState() {
        return _state;
    }

    /* ------------------------------------------------------------ */
    /** Set the message state.
     * This method should be used by experts only as it can prevent
     * normal handling of a request/response.
     * @param state The new state
     * @return the last state.
     */
    public int setState(int state) {
        int last = _state;
        _state = state;
        return last;
    }

    /* ------------------------------------------------------------ */
    /** Get the protocol version.
     * @return return the version.
     */
    public String getVersion() {
        return _version;
    }

    /* ------------------------------------------------------------ */
    /** Get the protocol version.
     * @return return the version dot (0.9=-1 1.0=0 1.1=1)
     */
    public int getDotVersion() {
        return _dotVersion;
    }

    /* ------------------------------------------------------------ */
    /** Get field names.
     * @return Enumeration of Field Names
     */
    public Enumeration getFieldNames() {
        return _header.getFieldNames();
    }

    /* ------------------------------------------------------------ */
    /** Does the header or trailer contain a field?
     * @param name Name of the field
     * @return True if contained in header or trailer.
     */
    public boolean containsField(String name) {
        return _header.containsKey(name);
    }

    /* ------------------------------------------------------------ */
    /** Get a message field.
     * Get a field from a message header. If no header field is found,
     * trailer fields are searched.
     * @param name The field name
     * @return field value or null
     */
    public String getField(String name) {
        return _header.get(name);
    }

    /* ------------------------------------------------------------ */
    /** Get a multi valued message field.
     * Get a field from a message header. 
     * @param name The field name
     * @return Enumeration of field values or null
     */
    public Enumeration getFieldValues(String name) {
        return _header.getValues(name);
    }

    /* ------------------------------------------------------------ */
    /** Get a multi valued message field.
     * Get a field from a message header. 
     * @param name The field name
     * @param separators String of separators.
     * @return Enumeration of field values or null
     */
    public Enumeration getFieldValues(String name, String separators) {
        return _header.getValues(name, separators);
    }

    /* ------------------------------------------------------------ */
    /** Set a field value.
     * If the message is editable, then a header field is set. Otherwise
     * if the message is sending and a HTTP/1.1 version, then a trailer
     * field is set.
     * @param name Name of field 
     * @param value New value of field
     * @return Old value of field
     */
    public String setField(String name, String value) {
        if (_state != __MSG_EDITABLE)
            return null;

        if (HttpFields.__ContentType.equalsIgnoreCase(name)) {
            String old = _header.get(name);
            setContentType(value);
            return old;
        }

        return _header.put(name, value);
    }

    /* ------------------------------------------------------------ */
    /** Set a multi-value field value.
     * If the message is editable, then a header field is set. Otherwise
     * if the meesage is sending and a HTTP/1.1 version, then a trailer
     * field is set.
     * @param name Name of field 
     * @param value New values of field
     */
    public void setField(String name, List value) {
        if (_state != __MSG_EDITABLE)
            return;
        _header.put(name, value);
    }

    /* ------------------------------------------------------------ */
    /** Add to a multi-value field value.
     * If the message is editable, then a header field is set. Otherwise
     * if the meesage is sending and a HTTP/1.1 version, then a trailer
     * field is set.
     * @param name Name of field 
     * @param value New value to add to the field
     * @exception IllegalStateException Not editable or sending 1.1
     *                                  with trailers
     */
    public void addField(String name, String value) throws IllegalStateException {
        if (_state != __MSG_EDITABLE)
            return;
        _header.add(name, value);
    }

    /* -------------------------------------------------------------- */
    /** Get a field as an integer value.
     * Look in header and trailer fields.
     * Returns the value of an integer field, or -1 if not found.
     * The case of the field name is ignored.
     * @param name the case-insensitive field name
     */
    public int getIntField(String name) {
        return _header.getIntField(name);
    }

    /* -------------------------------------------------------------- */
    /** Sets the value of an integer field.
     * Header or Trailer fields are set depending on message state.
     * @param name the field name
     * @param value the field integer value
     */
    public void setIntField(String name, int value) {
        if (_state != __MSG_EDITABLE)
            return;
        _header.put(name, TypeUtil.toString(value));
    }

    /* -------------------------------------------------------------- */
    /** Adds the value of an integer field.
     * Header or Trailer fields are set depending on message state.
     * @param name the field name
     * @param value the field integer value
     */
    public void addIntField(String name, int value) {
        if (_state != __MSG_EDITABLE)
            return;
        _header.add(name, TypeUtil.toString(value));
    }

    /* -------------------------------------------------------------- */
    /** Get a header as a date value.
     * Look in header and trailer fields.
     * Returns the value of a date field, or -1 if not found.
     * The case of the field name is ignored.
     * @param name the case-insensitive field name
     */
    public long getDateField(String name) {
        return _header.getDateField(name);
    }

    /* -------------------------------------------------------------- */
    /** Sets the value of a date field.
     * Header or Trailer fields are set depending on message state.
     * @param name the field name
     * @param date the field date value
     */
    public void setDateField(String name, Date date) {
        if (_state != __MSG_EDITABLE)
            return;
        _header.putDateField(name, date);
    }

    /* -------------------------------------------------------------- */
    /** Adds the value of a date field.
     * Header or Trailer fields are set depending on message state.
     * @param name the field name
     * @param date the field date value
     */
    public void addDateField(String name, Date date) {
        if (_state != __MSG_EDITABLE)
            return;
        _header.addDateField(name, date);
    }

    /* -------------------------------------------------------------- */
    /** Sets the value of a date field.
     * Header or Trailer fields are set depending on message state.
     * @param name the field name
     * @param date the field date value
     */
    public void setDateField(String name, long date) {
        if (_state != __MSG_EDITABLE)
            return;
        _header.putDateField(name, date);
    }

    /* -------------------------------------------------------------- */
    /** Add the value of a date field.
     * Header or Trailer fields are set depending on message state.
     * @param name the field name
     * @param date the field date value
     * @exception IllegalStateException Not editable or sending 1.1
     *                                  with trailers
     */
    public void addDateField(String name, long date) {
        if (_state != __MSG_EDITABLE)
            return;
        _header.addDateField(name, date);
    }

    /* ------------------------------------------------------------ */
    /** Remove a field.
     * If the message is editable, then a header field is removed. Otherwise
     * if the message is sending and a HTTP/1.1 version, then a trailer
     * field is removed.
     * @param name Name of field 
     * @return Old value of field
     */
    public String removeField(String name) throws IllegalStateException {
        if (_state != __MSG_EDITABLE)
            return null;
        return _header.remove(name);
    }

    /* ------------------------------------------------------------ */
    /** Set the request version 
     * @param version the  HTTP version string (eg HTTP/1.1)
     * @exception IllegalStateException message is not EDITABLE
     */
    public void setVersion(String version) {
        if (_state != __MSG_EDITABLE)
            throw new IllegalStateException("Not EDITABLE");
        if (version.equalsIgnoreCase(__HTTP_1_1)) {
            _dotVersion = 1;
            _version = __HTTP_1_1;
        } else if (version.equalsIgnoreCase(__HTTP_1_0)) {
            _dotVersion = 0;
            _version = __HTTP_1_0;
        } else if (version.equalsIgnoreCase(__HTTP_0_9)) {
            _dotVersion = -1;
            _version = __HTTP_0_9;
        } else
            throw new IllegalArgumentException("Unknown version");
    }

    /* ------------------------------------------------------------ */
    /** Get the HTTP header fields.
     * @return Header or null
     */
    public HttpFields getHeader() {
        if (_state != __MSG_EDITABLE)
            throw new IllegalStateException("Can't get header in " + __state[_state]);

        return _header;
    }

    /* -------------------------------------------------------------- */
    public int getContentLength() {
        return getIntField(HttpFields.__ContentLength);
    }

    /* ------------------------------------------------------------ */
    public void setContentLength(int len) {
        setIntField(HttpFields.__ContentLength, len);
    }

    /* -------------------------------------------------------------- */
    /** Character Encoding.
     * The character encoding is extracted from the ContentType field
     * when set.
     * @return Character Encoding or null
     */
    public String getCharacterEncoding() {
        return _characterEncoding;
    }

    /* ------------------------------------------------------------ */
    /** Set Character Encoding. 
     * @param encoding An encoding that can override the encoding set
     * from the ContentType field.
     */
    public void setCharacterEncoding(String encoding, boolean setField) {
        if (isCommitted())
            return;

        if (encoding == null) {
            // Clear any encoding.
            if (_characterEncoding != null) {
                _characterEncoding = null;
                if (setField)
                    _header.put(HttpFields.__ContentType, _mimeType);
            }
        } else {
            // No, so just add this one to the mimetype
            _characterEncoding = encoding;
            if (setField && _mimeType != null) {
                _header.put(HttpFields.__ContentType,
                        _mimeType + ";charset=" + QuotedStringTokenizer.quote(_characterEncoding, ";= "));
            }
        }
    }

    /* -------------------------------------------------------------- */
    public String getContentType() {
        return getField(HttpFields.__ContentType);
    }

    /* ------------------------------------------------------------ */
    public void setContentType(String contentType) {
        if (isCommitted())
            return;

        if (contentType == null) {
            _mimeType = null;
            _header.remove(HttpFields.__ContentType);
        } else {
            // Look for encoding in contentType
            int i0 = contentType.indexOf(';');

            if (i0 > 0) {
                // Strip params off mimetype
                _mimeType = contentType.substring(0, i0).trim();

                // Look for charset
                int i1 = contentType.indexOf("charset=", i0);
                if (i1 >= 0) {
                    i1 += 8;
                    int i2 = contentType.indexOf(' ', i1);
                    _characterEncoding = (0 < i2) ? contentType.substring(i1, i2) : contentType.substring(i1);
                    _characterEncoding = QuotedStringTokenizer.unquote(_characterEncoding);
                } else // No encoding in the params.
                {
                    if (_characterEncoding != null)
                        // Add any previously set encoding.
                        contentType += ";charset=" + QuotedStringTokenizer.quote(_characterEncoding, ";= ");
                }
            } else // No encoding and no other params
            {
                _mimeType = contentType;
                // Add any previously set encoding.
                if (_characterEncoding != null)
                    contentType += ";charset=" + QuotedStringTokenizer.quote(_characterEncoding, ";= ");
            }

            _header.put(HttpFields.__ContentType, contentType);
        }
    }

    /* ------------------------------------------------------------ */
    public void updateMimeType() {
        _mimeType = null;
        _characterEncoding = null;

        String contentType = _header.get(HttpFields.__ContentType);
        if (contentType != null) {
            // Look for encoding in contentType
            int i0 = contentType.indexOf(';');

            if (i0 > 0) {
                // Strip params off mimetype
                _mimeType = contentType.substring(0, i0).trim();

                // Look for charset
                int i1 = contentType.indexOf("charset=", i0);
                if (i1 >= 0) {
                    i1 += 8;
                    int i2 = contentType.indexOf(' ', i1);
                    _characterEncoding = (0 < i2) ? contentType.substring(i1, i2) : contentType.substring(i1);
                    _characterEncoding = QuotedStringTokenizer.unquote(_characterEncoding);
                }
            } else {
                _mimeType = contentType;
            }
        }
    }

    /* -------------------------------------------------------------- */
    /** Mime Type.
     * The mime type is extracted from the contenttype field when set.
     * @return Content type without parameters
     */
    public String getMimeType() {
        return _mimeType;
    }

    /* ------------------------------------------------------------ */
    /** Recycle the message.
     */
    void recycle(HttpConnection connection) {
        _state = __MSG_EDITABLE;
        _version = __HTTP_1_1;
        _dotVersion = 1;
        _header.clear();
        _connection = connection;
        _characterEncoding = null;
        _mimeType = null;
        if (_attributes != null)
            _attributes.clear();
    }

    /* ------------------------------------------------------------ */
    /** Destroy the message.
     * Help the garbage collector by nulling everything that we can.
     */
    public void destroy() {
        recycle(null);
        if (_header != null)
            _header.destroy();
        _header = null;
    }

    /* ------------------------------------------------------------ */
    /** Convert to String.
     * The message header is converted to a String.
     * @return String
     */
    public synchronized String toString() {
        StringWriter writer = new StringWriter();

        int save_state = _state;
        try {
            _state = __MSG_EDITABLE;
            writeHeader(writer);
        } catch (IOException e) {
            log.warn(LogSupport.EXCEPTION, e);
        } finally {
            _state = save_state;
        }
        return writer.toString();
    }

    /* ------------------------------------------------------------ */
    /** Write the message header.
     * @param writer
     */
    abstract void writeHeader(Writer writer) throws IOException;

    /* ------------------------------------------------------------ */
    public boolean isCommitted() {
        return _state == __MSG_SENDING || _state == __MSG_SENT;
    }

    /* ------------------------------------------------------------ */
    /** 
     * @return true if the message has been modified. 
     */
    public boolean isDirty() {
        HttpOutputStream out = (HttpOutputStream) getOutputStream();
        return _state != __MSG_EDITABLE || (out != null && out.isWritten());
    }

    /* ------------------------------------------------------------ */
    /** Get a request attribute.
     * @param name Attribute name
     * @return Attribute value
     */
    public Object getAttribute(String name) {
        if (_attributes == null)
            return null;
        return _attributes.get(name);
    }

    /* ------------------------------------------------------------ */
    /** Set a request attribute.
     * @param name Attribute name
     * @param attribute Attribute value
     * @return Previous Attribute value
     */
    public Object setAttribute(String name, Object attribute) {
        if (_attributes == null)
            _attributes = new HashMap(11);
        return _attributes.put(name, attribute);
    }

    /* ------------------------------------------------------------ */
    /** Get Attribute names.
     * @return Enumeration of Strings
     */
    public Enumeration getAttributeNames() {
        if (_attributes == null)
            return Collections.enumeration(Collections.EMPTY_LIST);
        return Collections.enumeration(_attributes.keySet());
    }

    /* ------------------------------------------------------------ */
    /** Remove a request attribute.
     * @param name Attribute name
     */
    public void removeAttribute(String name) {
        if (_attributes != null)
            _attributes.remove(name);
    }
}