うさちゃんと一緒(14) 動的なWebページその2、CGIハンドラ記述のRabbit式お作法

Joseph Halfmoon

前回は動的なWebページの最初ということでSSIでした。SSI利用時にもCGIハンドラにあたるべきものは動いていたのですが断片的でした。今回はWebページ全体を動的に生成できるように、「うさちゃん」式のCGIハンドラ記述のお作法を学んでいきたいと思います。マニュアル読んでサンプル読んでようやく少しわかった?知らんけど。

※うさちゃん、RabbitシリーズのCPUは、Z80のほぼほぼ上位互換の8ビットCPUです。8ビットのクセ?にEthernetインタフェースなども搭載、かなり強力、最強のZ80(個人の感想です。)本稿では、Rabbitシリーズのうち、現行機種の一番お求めやすいクラス?らしいRabbit4000を使用しております。

SSPEC_RESOURCE_FUNCTION

前回はうさちゃんのソフトウエアの奥の方にZserverというリソースを管理しているプログラムがいることを知りました。それに対して以下のマクロを使ってcgiを発動させる?ためのPathと、実際に起動すべき関数名を登録しておけば、そこにアクセスしたときにCの関数(以下の場合は led1toggle)が走りだすことが分かりました。

SSPEC_RESOURCE_FUNCTION("/led1tog.cgi", led1toggle)

しかし、フォームを使って何かデータをPOSTしたりするときはどうしたら良いの?

CGIハンドラ記述のお作法

Dynamic C所蔵のサンプルプログラムや、マニュアルを読んでいると、CGIハンドラを記述する定石があることが分かってきました。絶対そうしないといけない、という鉄の掟ではないようなので「お作法」と呼びたいと思います(SSIを使ったときの「ハンドラ」は短い断片的なもので、全然お作法にのっとってなかったです。)お作法の中心は、

ハンドラはステートマシンとして実装する

という1点です。ハンドラは HttpState という構造体へのポインタを引数にとって呼び出されるのですが、この中にステート管理のための2つのメンバが含まれてました。

    1. state
    2. substate

そのものズバリなメンバですが、2個あるので2レイヤーのステートマシン構造が構築できるようになっています。これでやれ、と。

今回は第9回で既に動かしているPOSTメソッドを使って動的にWebページを生成しているサンプルプログラム POST.C のケースを題材にして調べたいと思います。

POST.C で cgi として呼び出されている関数は、Webページ上のSUBMITボタンを押すと呼び出される、以下の名前の関数です。

submit()

WebページからSUBMITされた(メソッドはPOST)入力データを、起動された時点では上記のsubmit()関数は知りません。そこで、submit()関数の「下請け」として parse_post() 関数を呼び出してPOSTされたデータを取り出してsubmit()関数が扱える場所にセットしています。

ただ上記のような処理は一気に起こるわけではなく、submit()関数は何度も繰り返し呼び出されており、「小分け」に少しずつ処理が進みます。このとき、状態を管理するために state メンバが活用されます。また、submit()関数の中から呼び出される parse_post()関数も実はステートマシン管理できるようになっていて、substate メンバがこれに割り当てられています(POST.C内ではステート遷移せず、即完了で戻ってます。)

この様子を以下に示します。

2layerStateMachinestateレベルでも、substateレベルでも、呼び出された関数の戻り値が0であった場合、その関数は完了と判断せず再度呼び出されます。その際、stateなりsubstateが+1されていれば、次回の呼び出し時には、次のステートへ遷移して別な作業を行います。しかし、ステートの価が変わらない場合は同じステートの動作を繰り返します。

たとえば上記のSTATE 2 では、SUB STATEの関数を呼び出していますが、SUB STATEの関数から戻り値1が返るまで自信のSTATEは更新しません。よって、SUB STATE側がSUB STATE2に至るまで、STATE階層の関数は呼び出される度、STATE2を実行(そしてSUB STATEを呼び出す)ことになります。

POST.Cのコードから、submit()関数の骨組みのみ抽出したコードは以下のようです。

int submit(HttpState* state)
{
    /* 削除 */
    if(state->length) {
        /* 削除 */
    } else {
        switch(state->substate) {
        case 0:
            /* 削除 */
            state->substate++;
            break;
        case 1:
            /* 削除 */
            state->substate++;
            break;
        case 2:
            /* 削除 */
            state->substate++;
            break;
        case 3:
            if(parse_post(state)) {
                /* 削除 */
                state->substate++;
            } else {
            }
            break;
        case 4:
            /* 削除 */
            state->substate++;
            break;
        default:
            state->substate = 0;
            return 1;
        }
    }

    return 0;
}

なんとなく、うさちゃんのお作法、分かってきたような気がします。ホントか?

ソース勝手改造して動作確認

submit() 関数が何度も呼び出され、その度にステート遷移していることを確かめるため、上記の各 case の後に、デバッグ用の stdio にステートが分かるようにprintf文を表示してみました。

本来ステートは0から4なのですが、さらに何もしないステート、5を追加してみました。ビルドして実行したところが以下に。

stdout

 

まあ、目論見どおりに動いている気がします。これでうさちゃん用のCGIハンドラはバリバリ書ける?大丈夫か?

うさちゃんと一緒(13) 動的なWebページ最初の一歩、SHTMLでSSI に戻る

うさちゃんと一緒(15) 使えるピンはどれ?ボード上の接続、ポートの属性の違い へ進む