前回は動的なWebページの最初ということでSSIでした。SSI利用時にもCGIハンドラにあたるべきものは動いていたのですが断片的でした。今回はWebページ全体を動的に生成できるように、「うさちゃん」式のCGIハンドラ記述のお作法を学んでいきたいと思います。マニュアル読んでサンプル読んでようやく少しわかった?知らんけど。
※「うさちゃんと一緒」投稿順indexはこちら
※うさちゃん、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つのメンバが含まれてました。
-
- state
- 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内ではステート遷移せず、即完了で戻ってます。)
この様子を以下に示します。
stateレベルでも、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を追加してみました。ビルドして実行したところが以下に。
まあ、目論見どおりに動いている気がします。これでうさちゃん用のCGIハンドラはバリバリ書ける?大丈夫か?