Package de.netseeker.ejoe.http

Source Code of de.netseeker.ejoe.http.HttpHeaderParser

/*********************************************************************
* HttpHeaderParser.java
* created on 02.04.2006 by netseeker
* $Source: /cvsroot/ejoe/EJOE/src/de/netseeker/ejoe/http/HttpHeaderParser.java,v $
* $Date: 2007/11/17 10:57:01 $
* $Revision: 1.2 $
*
* ====================================================================
*
*  Copyright 2006 netseeker aka Michael Manske
*
*  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.
* ====================================================================
*
* This file is part of the de.netseeker.ejoe.http framework.
* For more information on the author, please see
* <http://www.manskes.de/>.
*
*********************************************************************/

package de.netseeker.ejoe.http;

import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.Charset;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import de.netseeker.ejoe.cache.ByteBufferAllocator;

/**
* This is the main HTTP header parser of EJOE. It's able to validate an ByteBuffer if it contains a valid HTTP header
* and can detect:
* <ul>
* <li>the end of the HTTP header</li>
* <li>the length of the HTTP header in bytes</li>
* <li>upcoming content length</li>
* </ul>
* It's not possible to create an instance directly, instead use HttpRequestParser or HttpResponseParser to create
* concrete parser instances.
*
* @author netseeker
* @since 0.3.9.1
*/
public class HttpHeaderParser
{
    private static final Logger    logger                 = Logger.getLogger( HttpHeaderParser.class.getName() );

    protected static final String  LINE_SEP               = "\r\n";

    protected static final Charset csISO                  = Charset.forName( "ISO-8859-1" );

    protected static final Pattern pLineEnd               = Pattern.compile( "$", Pattern.MULTILINE );

    protected static final Pattern pContentLength         = Pattern.compile( ".*^Content-Length:\\s([^\\s]+)$.*",
                                                                             Pattern.CASE_INSENSITIVE
                                                                                     | Pattern.MULTILINE
                                                                                     | Pattern.DOTALL );

    protected static final Pattern pConnection            = Pattern.compile( ".*^Connection:\\s([^\\s]+)$.*",
                                                                             Pattern.CASE_INSENSITIVE
                                                                                     | Pattern.MULTILINE
                                                                                     | Pattern.DOTALL );

    private int                    contentLength;

    private int                    headerLength;

    private CharBuffer             charHeader;

    private ByteBuffer             byteHeader;

    private boolean                hasPrereadContent      = false;

    private boolean                isPersistentConnection = false;

    private boolean                hasCompression         = false;

    /**
     * Hidden constructor, only child classes are permitted to create a new instance
     *
     * @param buf ByteBuffer expected to contain a HTTP header [ + optional preread content ]
     */
    protected HttpHeaderParser(ByteBuffer buf)
    {
        this.byteHeader = buf;
        this.charHeader = csISO.decode( buf );
        this.byteHeader.rewind();
        this.contentLength = extractContentLength();
        this.isPersistentConnection = isConnectionPersistent();
        this.headerLength = extractHeaderLength( buf );
        this.hasPrereadContent = (byteHeader.limit() > headerLength);
        if ( this.hasPrereadContent )
        {
            resizeByteHeaderForFurtherReading();
        }
    }

    /**
     * Returns the content length
     *
     * @return the content length
     */
    public int getContentLength()
    {
        return contentLength;
    }

    /**
     * Returns the length of the HTTP header in bytes without any preread content
     *
     * @return the length of the header
     */
    public int getHeaderLength()
    {
        return headerLength;
    }

    /**
     * Checks if the HTTP header seems to be valid
     *
     * @return true if the HTTP header seems to be valid (at least for EJOE) otherwise false
     */
    public boolean isValid()
    {
        return ((headerLength > 0) && (contentLength >= 0));
    }

    /**
     * @return The decoded underlying buffer
     */
    public CharBuffer getCharHeader()
    {
        return charHeader;
    }

    /**
     * @return the underlying ByteBuffer
     */
    public ByteBuffer getByteHeader()
    {
        return byteHeader;
    }

    /**
     * @return true if the underlying ByteBuffer already contains one or more preread bytes of content data
     */
    public boolean hasPrereadContent()
    {
        return hasPrereadContent;
    }

    /**
     * Indicates whether the value for the HTTP header line "Connection:" was "close" or "open".
     *
     * @return true if the HTTP header did contain a "Connection:" header line and the value was "open" otherwise false
     */
    public boolean isPersistentConnection()
    {
        return isPersistentConnection;
    }

    /**
     * Returns the compression setting in the HTTP-Header.
     *
     * @return true if the HTTP header requested use of GZIP compression otherwise false
     */
    public boolean hasCompression()
    {
        return hasCompression;
    }

    protected void setCompression( boolean enable )
    {
        hasCompression = enable;
    }

    /**
     * Determines the length of the expected content as stated in the "Content-Length:" section of the header
     *
     * @return the content length
     */
    protected int extractContentLength()
    {
        try
        {
            Matcher matcher = pContentLength.matcher( getCharHeader() );
            if ( matcher.matches() )
            {
                return Integer.parseInt( matcher.group( 1 ).trim() );
            }

        }
        catch ( Exception e )
        {
            logger.log( Level.WARNING, "Failed to determine content length!", e );
        }

        return 0;
    }

    /**
     * Checks if the client has requested a persistent connection
     *
     * @return true if the client requested a persitent connection otherwise false
     */
    protected boolean isConnectionPersistent()
    {
        try
        {
            Matcher matcher = pConnection.matcher( getCharHeader() );
            if ( matcher.matches() )
            {
                return matcher.group( 1 ).trim().equalsIgnoreCase( "keep-alive" );
            }
        }
        catch ( Exception e )
        {
            logger.log( Level.WARNING, "Failed to determine content length!", e );
        }

        return false;
    }

    /**
     * Resizes and repositions the internal ByteBuffer for upcoming read operations which will append content data to
     * the buffer
     */
    private void resizeByteHeaderForFurtherReading()
    {
        this.byteHeader.position( headerLength );
        this.byteHeader.compact();
        if ( this.byteHeader.capacity() < getContentLength() )
        {
            this.byteHeader = ByteBufferAllocator.reAllocate( this.byteHeader, getContentLength() );
        }
        if ( this.byteHeader.limit() != getContentLength() )
        {
            this.byteHeader.limit( getContentLength() );
        }
    }

    /**
     * Searches the ByteBuffer for two line breaks (either "\r\n\r\n" or "\n\n") to detect the end of HTTP header data
     *
     * @param bb ByteBuffer containing HTTP header data
     * @return the length of the HTTP header in bytes
     */
    protected static int extractHeaderLength( ByteBuffer bb )
    {
        int length = -1;
        if ( bb.limit() >= 3 )
        {
            int pos = bb.position();
            for ( int i = 3; i < bb.limit(); i++ )
            {
                if ( ((bb.get( i - 3 ) == '\r') && (bb.get( i - 2 ) == '\n') && (bb.get( i - 1 ) == '\r') && (bb
                        .get( i ) == '\n'))
                        || ((bb.get( i - 1 ) == '\n') && (bb.get( i ) == '\n')) )
                {
                    length = i + 1;
                    break;
                }
            }

            bb.position( pos );
        }

        return length;
    }

    /**
     * Checks if the HTTP is complete. A header is then complete if either "\r\n\r\n" or "\n\n" are found within the
     * given ByteBuffer
     *
     * @param bb ByteBuffer containing HTTP header data
     * @return true if the HTTP header is complete (the buffer contains a terminated HTTP header [ + optional prearead
     *         content ]) otherwise false
     */
    public static boolean isComplete( ByteBuffer bb )
    {
        int pos = bb.position();
        int limit = bb.limit();
        bb.flip();
        boolean complete = (extractHeaderLength( bb ) != -1);
        bb.limit( limit );
        bb.position( pos );

        return complete;
    }
}
TOP

Related Classes of de.netseeker.ejoe.http.HttpHeaderParser

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.