HttpHeaderParser(2012/08/08)


HTTPリクエストのヘッダーパーサー

 ウェブソケットはハンドシェイクの際に、以下のようなHTTPヘッダーを送信してくる。この情報に基づいて、サーバー側では接続とハンドシェイクを行う。
 送られてきたHTTPヘッダーはパーサーを介してサーバープログラムで簡易に使用出来るようにする。

WebSocketクライアントから送られてくるHTTPヘッダ
GET /chat HTTP/1.1\r\n
Host: server.example.com\r\n
Upgrade: websocket\r\n
Connection: Upgrade\r\n
Sec-WebSocket-Key: XXXXXXXXXXXXXXXXXXXXXX==\r\n
Origin: http://example.com\r\n
Sec-WebSocket-Protocol: chat, superchat\r\n
Sec-WebSocket-Version: 13\r\n

 以下にHttpHeaderParserのソースコードを掲載する。
 使い方としては  となる。

HttpHeaderParser.h
#pragma once

#include "Tool.h"

class CHttpHeaderElement
{
    friend class CHttpHeaderParser;

protected:
    char * m_pszField;
    char * m_pszValue;
    CHttpHeaderElement * m_pNextElement;

public:
    CHttpHeaderElement();
    ~CHttpHeaderElement();

    void SetField( const char * pcszField , int iStartIndex, int iLength );
    void SetValue( const char * pcszValue , int iStartIndex, int iLength );
};


class CHttpHeaderParser
{
protected:
    CHttpHeaderElement * m_pHeaderElement, *m_pHeaderElementLast;
    bool m_bFirstData;
    char * m_pszMethod;
    char * m_pszRequestURI;
    char * m_pszHTTPVersion;

    void SetFieldAndValue( const char * pcszData, int iStartIndex, int iLength );
    void SetRequestHeader( const char * pcszData, int iStartIndex, int iLength );

public:
    CHttpHeaderParser();
    ~CHttpHeaderParser();

    void ClearData();
    void AddData( const char * pcszData );
    void AddData( const char * pcszData , int iLength);
    const char * GetFieldValue( const char * pcszField );

    const char * GetMethod();
    const char * GetRequestURI();
    const char * GetHTTPVersion();

};

HttpHeaderParser.cpp
#include <string.h>
#include "HttpHeaderParser.h"
#include "Tool.h"

//
//    コンストラクタ
//
//        内部データの初期化
//
CHttpHeaderElement::CHttpHeaderElement()
{
    m_pszField = NULL;
    m_pszValue = NULL;
    m_pNextElement = NULL;
}

//
//    デストラクタ
//
CHttpHeaderElement::~CHttpHeaderElement()
{
    CTool::ReleaseStringA( &m_pszField );
    CTool::ReleaseStringA( &m_pszValue );
}

//
//    フィールドデータの登録
//    
//    引数:
//        pcszField    (IN)    登録するフィールドデータ
//
void CHttpHeaderElement::SetField( const char * pcszField , int iStartIndex, int iLength)
{
    CTool::SetStringA( &m_pszField, pcszField , iStartIndex, iLength);
}

//
//    バリューデータの登録
//
//    引数:
//        pcszValue    (IN)    登録する値データ
//
void CHttpHeaderElement::SetValue( const char * pcszValue , int iStartIndex, int iLength)
{
    CTool::SetStringA( &m_pszValue, pcszValue , iStartIndex, iLength);
}

///////////////////////////////////////////////////////////////////////////////

//
//    コンストラクタ
//
CHttpHeaderParser::CHttpHeaderParser()
{
    m_pHeaderElement = NULL;
    m_pszMethod = m_pszRequestURI = m_pszHTTPVersion = NULL;
    m_bFirstData = true;
}

//
//    デストラクタ
//
CHttpHeaderParser::~CHttpHeaderParser()
{
    ClearData();
}

//
//    内部データをクリアする
//
//        リクエストデータおよびフィールド&バリューを削除する
//
void CHttpHeaderParser::ClearData()
{
    while( m_pHeaderElement )
    {
        CHttpHeaderElement * pDelete = m_pHeaderElement;
        m_pHeaderElement = m_pHeaderElement->m_pNextElement;
        delete pDelete;
    }

    CTool::ReleaseStringA( &m_pszMethod );
    CTool::ReleaseStringA( &m_pszRequestURI );
    CTool::ReleaseStringA( &m_pszHTTPVersion );

    m_pHeaderElementLast = NULL;
    m_bFirstData = true;
}

//
//    ヘッダー文字列を使って、データを登録する
//
//    引数:
//        pcszData    (IN)    ヘッダーデータ
//
void CHttpHeaderParser::AddData( const char * pcszData )
{
    AddData( pcszData, (int)strlen( pcszData ) );
}
void CHttpHeaderParser::AddData( const char * pcszData , int iLength)
{
    int iStartIndex = 0;
    bool bSetStartIndex = true;

    for( int i=0 ; i<iLength ; i++ )
    {
        if( bSetStartIndex )
        {
            iStartIndex = i;
        }

        bSetStartIndex = false;

        //改行コードの場合
        if( pcszData[i] == '\r' && pcszData[i+1] == '\n' )
        {
            if( m_bFirstData )
            {
                SetRequestHeader( pcszData, iStartIndex, i-iStartIndex+1 );
                m_bFirstData = false;
            }
            else
            {
                SetFieldAndValue( pcszData, iStartIndex, i-iStartIndex+1);
            }
            i++;
            bSetStartIndex = true;
        }
    }
}

//
//    先頭行(リクエストヘッダ)をメソッド、URI、HTTPバージョンに分割して登録する
//
void CHttpHeaderParser::SetRequestHeader( const char * pcszData, int iStartIndex, int iLength )
{
    int iSectionStartIndex = iStartIndex;
    int iCounter = 0;

    for( int i=iStartIndex ; i<iLength+1 ; i++ )
    {
        if( i == iLength || pcszData[i] == ' ' )
        {
            char ** ppszRegist = NULL;
            switch( iCounter )
            {
            //メソッドの場合
            case 0:    ppszRegist = &m_pszMethod;    break;
            case 1:    ppszRegist = &m_pszRequestURI;    break;
            case 2:    ppszRegist = &m_pszHTTPVersion;    break;
                break;
            }

            if( ppszRegist )
            {
                CTool::SetStringA( ppszRegist, pcszData, iSectionStartIndex, i-iSectionStartIndex+1 );
            }
            iSectionStartIndex = i+1;
            iCounter++;
        }
    }
}

//
//    1行データを解析し、フィールドと値に分けてCHttpHeaderElementに変換後内部リストに登録する
//
//    引数:
//        pcszData    (IN)    
void CHttpHeaderParser::SetFieldAndValue( const char * pcszData, int iStartIndex, int iLength )
{
    int iFieldStart = -1;
    int iFieldLength = -1;
    int iValueStart = -1;
    int iValueLength = -1;

    for( int i=iStartIndex ; i<iStartIndex+iLength ; i++ )
    {
        char cTemp = pcszData[i];

        //フィールド区切りの場合
        if( cTemp == ':' )
        {
            //フィールド開始が登録されていない場合(行にいきなりフィールド区切りが出てくる)
            if( iFieldStart < 0 )
            {
                return;
            }

            iFieldLength = i - iStartIndex;
        }

        //空白スペースでない場合
        else if( cTemp != ' ' && cTemp != '\t' )
        {
            //フィールド開始が登録されていない場合(フィールド開始の登録)
            if( iFieldStart < 0 )
            {
                iFieldStart = i;
            }
            //フィールド長さが登録されており、値開始が登録されていない場合(値開始の登録)
            else if( iFieldLength >= 0 && iValueStart < 0 )
            {
                iValueStart = i;
            }
        }
    }

    //値開始が正常に登録されている場合は値長さを文字列終端とする。
    if( iValueStart >= 0 )
    {
        iValueLength = iStartIndex + iLength - iValueStart - 1;
    }

    //フィールド部分のトリム処理
    for( int i=iFieldStart+iFieldLength-1 ; i>=iFieldStart ; i-- )
    {
        if( pcszData[i] == ' ' || pcszData[i] == '\t' )
        {
            iFieldLength--;
        }
        else
        {
            break;
        }
    }

    //トリムの結果フィールド長さが無くなってしまった場合
    if( iFieldLength <= 0 )
    {
        return;
    }

    //データの追加登録処理
    CHttpHeaderElement * pElement = new CHttpHeaderElement();
    pElement->SetField( pcszData, iFieldStart, iFieldLength );
    pElement->SetValue( pcszData, iValueStart, iValueLength );

    //リストに登録する
    if( m_pHeaderElement )
    {
        m_pHeaderElementLast->m_pNextElement = pElement;
        m_pHeaderElementLast = pElement;
    }
    else
    {
        m_pHeaderElement = m_pHeaderElementLast = pElement;
    }

}

//
//    指定フィールドの値を取得する
//
//    引数:
//        pcszField    (IN)    値を取得するフィールド名
//
//    戻り値:
//        !=NULL    :    フィールドに対応する値
//        ==NULL    :    該当フィールドデータなし
//
const char * CHttpHeaderParser::GetFieldValue( const char * pcszField )
{
    for( CHttpHeaderElement * pElement = m_pHeaderElement
        ; pElement
        ; pElement = pElement->m_pNextElement )
    {
        if( strcmp( pElement->m_pszField, pcszField ) == 0 )
        {
            return pElement->m_pszValue;
        }
    }
    return NULL;
}

//
//    メソッドのゲッタ
//
const char * CHttpHeaderParser::GetMethod()
{
    return m_pszMethod;
}

//
//    リクエストURIのゲッタ
//
const char * CHttpHeaderParser::GetRequestURI()
{
    return m_pszRequestURI;
}

//
//    HTTPバージョンのゲッタ
//
const char * CHttpHeaderParser::GetHTTPVersion()
{
    return m_pszHTTPVersion;
}