Entry

プログラミングメモ - 定番のHTTPクエリ文字列解析ルーチン

2008年08月16日

まぁ,定番だから,あちこちに似たようなもんが紹介されているとは思うんですけど,一応ご紹介。文字列 query を解析して,map コンテナのインスタンス変数 params_ に key-value のペアで詰め込むルーチンです。C++ で書いてみました。

こんな感じ。かなり前にも似たようなもんを作ったんですけど,そのときは URL エンコードされた文字列をデコードするとこまではしてなかったのでした。今回はちゃんとデコードしています。

int
Request::parseQueryString(const std::string& query) {
    int num = 0;
    std::string::const_iterator itr = query.begin();
    std::string::const_iterator key = query.end();
    std::string::const_iterator val = query.end();

    for ( ; itr != query.end(); itr++) {
        switch(*itr) {
        case '?':
            key = itr + 1;
            break;
        case '=':
            val = (val == query.end()) ? itr + 1 :  val;
            break;
        case '&':
            // insert
            insertParam(query, itr, key, val);
            num++;
            // init for new params
            key = itr + 1;
            val = query.end();
            break;
        default:
            key = (itr == query.begin()) ? itr : key;
            break;
        }
    }
    // get the last param
    if (key != query.end()) {
        insertParam(query, itr, key, val);
        num++;
    }
    
    return num;
}

void
Request::insertParam(const std::string& query,
                     const std::string::const_iterator& itr,
                     const std::string::const_iterator& key,
                     const std::string::const_iterator& val) {
    int keyPos = 0;
    int valPos = 0;
    int keyNum = 0;
    int valNum = 0;
    std::string keyString;
    std::string valString;

    // split string
    keyPos = (key - query.begin());
    valPos = (val == query.end())
           ? (itr - query.begin())
           : (val - query.begin());
    keyNum = (val == query.end() && *(val - 1) != '=')
           ? (valPos - keyPos)
           : (valPos - keyPos - 1);
    valNum = (val == query.end())
           ? 0
           : ((itr - query.begin()) - valPos);
    keyString = std::string(query.c_str(), keyPos, keyNum);
    valString = std::string(query.c_str(), valPos, valNum);
    // decode
    decodeUrlString(keyString);
    decodeUrlString(valString);
    // insert
    if (keyString.length() > 0) {
        params_.insert(
            std::pair<std::string, std::string>(keyString, valString));
    }
    return;
}

void
Request::decodeUrlString(std::string& url) {
    std::string::iterator si = url.begin();
    std::string::iterator di = si;
    size_t len = url.length();
    size_t cnt = 0;

    for ( ; si != url.end(); si++, di++) {
        switch (*si) {
        case '%':
            if ((si + 1) != url.end() && (si + 2) != url.end()) {
                si++;
                *di = (hexNum(*si) << 4);
                si++;
                *di += (hexNum(*si));
            } else {
                // assert!
            }
            break;
        case '+':
            *di = ' ';
            break;
        default:
            *di = *si;
            break;
        }
        cnt++;
    }
    url.resize(cnt, (len - 1));

    return;
}

char
Request::hexNum(const char ch) {
    char ret;

    if (ch >= '0' && ch <= '9') {
        ret = ch - '0';
    } else if (ch >= 'A' && ch <= 'F') {
        ret = ch - 'A' + 0x0A;
    } else if (ch >= 'a' && ch <= 'f') {
        ret = ch - 'a' + 0x0A;
    } else {
        // assert
    }

    return ret;
}

あと,前回はクエリ文字列を一旦バッファにコピーして,"&"や"="をヌル文字に置き換えたりして解析していたんですけど,これは効率からしてあまりキレイな方法じゃないのでやめました。発想は分かりやすいんですけどね。同じ文字列を何度も走査するのは,やっぱりみっともないです。バッファの長さを数えるので1回,バッファを確保するのに1回,バッファにコピーするので1回,"&"を探すので1回,"="を探すので1回,とかいった具合。

今回は,もとの文字列はいぢらずに,欲しい文字列をイテレータでマークアップすることで,解析することにしました。ここから key ですよ,とか,ここまで value ですよ,とかいった具合にマークづけして,マークにしたがって map コンテナに格納していく方法です。これなら解析に必要な走査は1回のfor-ループで済みます。まぁ,もともと,クエリ文字列がボトルネックになることはあまりないんでしょうけどね。気分の問題です。

今のところ,エラー処理はおざなりにしてるんですけど,これはプログラム全体の例外処理が決まったら書くつもり。頭の中ではかなり考えたんですけど,案外簡単にできちゃったので,ちょっと拍子抜けです。

Trackback
Trackback URL:
Ads
About
Search This Site
Ads
Categories
Recent Entries
Log Archive
Syndicate This Site
Info.
クリエイティブ・コモンズ・ライセンス
Movable Type 3.36
Valid XHTML 1.1!
Valid CSS!
ブログタイムズ

© 2003-2012 AIAN