2.基本的な入出力


HOME] [0章] [1章] [2章] [3章] [4章] [5章] [?章

(1)入出力のしくみ

a)フォームからの入力
 フォームから送信されたデータは、WWWサーバーを通じてCGIプログラム に渡されます。データの経路は、GETメソッドとPOSTメソッドでは 少し違います。
 FORMタグのMETHODをGETにした場合には、データの文字列は環境変数の QUERY_STRINGにセットされます。したがって、これをプログラムで 読むためには、getenv()をコールすれば良いわけです。
 一方、METHODをPOSTにした場合には、データの文字列自体はプログラムの 標準入力ストリームに渡され、文字列の長さが、環境変数のCONTENT_LENGTH にセットされます。これを読み込むためには、CONTENT_LENGTHの大きさを持つ バッファをmalloc()で準備して、このバッファにscanf()などで、 データを読みます。

b)ブラウザへの出力
 printf()などで、標準出力に書き込めば、WWWサーバーを通じて、 ユーザーのブラウザにデータが送られます。
 MIMEヘッダを忘れずに付けて出力します。
次の項で、プログラム例を示します。


(2)フォームからの入力を読んでみよう

 まず、フォームからのデータをただ読んでみましょう。 (次の項で、ちゃんとデータをデコードします)
a)GETメソッドの場合
図のような簡単なフォームを作って、データをCGIプログラムで 読み込み、受け取ったままのデータをブラウザに表示してみましょう。
このままでは、まだデコードしていないので、訳が分かりませんが、 入力データがどのように送られてくるのかを見てください。


<HTML>
<HEAD>
<TITLE>テストページ</TITLE>
</HEAD>
<BODY BGCOLOR=#a0d0ff>
<FORM METHOD="get" ACTION="test2.cgi">
        <INPUT TYPE="text" NAME="fieldname" SIZE="50"><BR>
        <INPUT TYPE="text" NAME="fieldname2" SIZE="50"><BR>
        <INPUT TYPE="text" NAME="fieldname3" SIZE="50"><BR>
        <INPUT TYPE="submit" VALUE="書き込み">
</FORM>
</BODY>
</HTML>
左が、上に示したフォームのためのHTMLドキュメントです。 フォームから3つのデータを受け取って、test2.cgiという CGIプログラムを起動します。GETメソッドを指定しているので、 データの文字列は環境変数QUERY_STRINGにセットされます。

#include <stdio.h>
#include <stdlib.h>

#define getenv1(a) ((pdata=getenv(a)) ? pdata : "(NULL)") 

main()
{
        int len;
        printf("Content-type: text/html\n\n");
        printf("<HTML><HEAD></HEAD><BODY>\n");
        printf("%s<BR>\n",getenv("QUERY_STRING"));
        printf("</BODY></HTML>\n");
}       
このプログラムについては、ほとんど説明不要と思います。 環境変数QUERY_STRINGを受け取ってプリントしているだけです。
getenv()の戻値がNULLであったときの対応をしていますが、 これは古いCコンパイラで、printf()にnullポインタが渡されると、 不正な動作をするためです。

cc -o test2.cgi test2.c
でコンパイルした後、次のように、nobodyにも実行出来るようにします。
chmod 755 test2.cgi

このままWWWでフォームから実行する前に、シェルでテストしましょう。 QUERY_STRINGの値が、HTMLタグとともに出力されれば成功です。
以下にテストの例を示します。QUERY_STRINGの値は、シェルでセットします。
% setenv QUERY_STRING tatoeba
% ./test2.cgi
Content-type: text/html

<HTML><HEAD></HEAD><BODY>
tatoeba<BR>
</BODY></HTML>
うまくいったら、WWWで実行してみましょう。 上で作ったフォームのそれぞれのフィールドに以下のように入力して、 送信ボタンを押して下さい
123
abc
ABC
次のように表示されたでしょう。
fieldname=123&fieldname2=abc&fieldname3=ABC
フォームからの入力は、
フィールド名1=値1&フィールド名2=値2・・・
というようにエンコードされて、サーバーに渡されるのです、 このページの(3)でこのデータをデコードする例を示します。

b)POSTメソッドの場合

<HTML>
<HEAD>
<TITLE>テストページ</TITLE>
</HEAD>
<BODY BGCOLOR=#a0d0ff>
<FORM METHOD="post" ACTION="test1.cgi">
        <INPUT TYPE="text" NAME="fieldname" SIZE="50"><BR>
        <INPUT TYPE="text" NAME="fieldname2" SIZE="50"><BR>
        <INPUT TYPE="text" NAME="fieldname3" SIZE="50"><BR>
        <INPUT TYPE="submit" VALUE="書き込み">
</FORM>
</BODY>
</HTML>
左が、上に示したフォームのためのHTMLドキュメントです。 GETメソッドにしめしたドキュメントと同様のフォームを生成します。 フォームから3つのデータを受け取って、test1.cgiという CGIプログラムを起動します。POSTメソッドを指定しているので、 データの文字列は標準入力ストリームに流され、文字列の長さが、 環境変数CONTENT_LENGTHにセットされます。

#include <stdio.h>
#include <stdlib.h>

main()
{
        int len;
        char *clen;
        char *data;
        clen=getenv("CONTENT_LENGTH");
        if(clen==NULL){
                printf("no contents.\n");
                exit(1);
        }
        len=atol(clen);
        data=malloc(len+1);
        scanf("%s",data);
        data[len]='\0';
        printf("Content-type: text/html\n\n");
        printf("<HTML><HEAD></HEAD><BODY>\n");
        printf("%s<BR>\n",data);
        printf("</BODY></HTML>\n");
}        
POSTメソッドなので、環境変数CONTENT_LENGTHがら、 文字列の長さを受け取って、バッファを作成します。 次にscanf()でデータを標準入力から取得します。
cc -o test1.cgi test1.c
でコンパイルした後、次のように、nobodyにも実行出来るようにします。
chmod 755 test1.cgi
ここでも、WWWサーバーからコールする前に、シェルでテストしましょう。 CONTENT_LENGTHをシェルで設定して、POSTで送られる文字列は、 パイプを通して、test1.cgiに送ります。
% setenv CONTENT_LENGTH 7
% echo "tatoeba"|./test1.cgi 
Content-type: text/html

<HTML><HEAD></HEAD><BODY>
tatoeba<BR>
</BODY></HTML>
シェルでうまくいったら、WWWでやってみましょう。


(3)フォームからのデータのデコード

フォームから読み込んだ生のデータを利用するためには、 以下の手続きが必要になります。

(a)データをフィールド名と値の区切る
(2)の項で見たフォームのデータは”=”と”&”で区切られて、 フィールドの名前と値がずらずらと並んでいましたね。ここから、 それぞれのフィールド名、データの値を切り分けます。

(b)16進コードをデコードする
(2)項でのサンプルプログラムでフォームにたとえば”富士山”と 入力してみましょう。ブラウザのエンコードがシフトJISの場合は、 ”富士山”が”%95x%8Em%8ER”に化けてますね。EUCのときは、 ”%C9%D9%BB%CE%BB%B3”と化けているでしょう。これをデコードして、 日本語コードに戻します。

(c)複数文字コードを統一する
ゲストブックのようなアプリを考えてみましょう。 ゲストブックの見出しに、私がシフトJISで日本語の見出しを書きます。 ここにAさんが、EUCコードで書き込みをすると、シフトJISと EUCが混在したファイルになってしまいます。 こうなると、このゲストブックを表示した時には、Aさんの書き込みは 派手に文字化けしてしまいます。 まあ、たいていはシフトJISで書かれた私のページを表示した段階で ブラウザのコードはシフトJISになっているでしょうから、 こんなことはまれと思いますが。ゲストブックのようなアプリでは、 データファイルのコードを統一したほうが良いでしょう。

(a,b)データをフィールドと値に区切り16進コードをデコードする

<HTML>
<HEAD>
<TITLE>テストページ</TITLE>
</HEAD>
<BODY BGCOLOR=#a0d0ff>
<FORM METHOD="post" ACTION="test.cgi">
        <INPUT TYPE="text" NAME="fieldname" SIZE="50"><BR>
        <INPUT TYPE="text" NAME="fieldname2" SIZE="50"><BR>
        <INPUT TYPE="text" NAME="fieldname3" SIZE="50"><BR>
        <INPUT TYPE="submit" VALUE="書き込み">
</FORM>
</BODY>
</HTML>
左に示したHTMLはフォームデータを 以下に示すCGIプログラムに送信する ものです。特に説明は要らないですね。



#include <stdio.h>
#include <stdlib.h>
#include <string.h>

main()
{
        char *inputstring,*agent,*cLength,*name[20],*value[20];
        int nfield;
        long i,length,MAXLEN=4096;
        int decode_form(),parse_form();

        cLength=(char*)getenv("CONTENT_LENGTH");
        length=atol(cLength);
        if(length>MAXLEN) {printf("CONTENT_LENGTH>MAXLEN\n");exit(0);}
        inputstring=(char*)malloc(length+1);
        scanf("%s",inputstring);
        printf("%s","Content-type: text/html\n\n\0");
        printf("%s","<HTML><HEAD></HEAD><BODY>\n\0");
        parse_form(inputstring,MAXLEN,name,value,&nfield);
        for(i=0;i<nfield;i++){
                decode_form(name[i],strlen(name[i]));
                decode_form(value[i],strlen(value[i]));
                printf("%s ---> %s<BR>\n",name[i],value[i]);
        }
        printf("%s","\n</BODY></HTML>\n\0");
        free(inputstring);
        exit(0);
}
フォームからPOSTメソッドで入力を受け取り、 デコードして表示するプログラム例です。 環境変数CONTENT_LENGTHを取得して、バッファを準備し、 そこにフォームの内容を読み込みます。 後は、下に示すルーチンでフォームの切り分け(parse_form())と、 デコード(decode_form())を行います。

 
int parse_form(char* s_in,long maxl,char* name[],char* value[],int *p_nfld)
{
        int i,cur_field;
        *p_nfld=0;
        i=0;
        cur_field=0;
        if(s_in[0]==NULL) return(-1);
        name[0]=s_in;
        while((s_in[++i]!=NULL)&&(i<maxl)){
                if(s_in[i]=='='){
                        s_in[i]=NULL;
                        value[cur_field]=s_in+i+1;
                }
                else if(s_in[i]=='&'){
                        s_in[i]=NULL;
                        cur_field++;
                        name[cur_field]=s_in+i+1;
                }
        }
        *p_nfld=cur_field+1;
        return(0);
}

フォームを切り分けるルーチンの例です。 s_inは入力文字列へのポインタです。 このルーチンでは、入力文字列のバッファを出力文字列の バッファとしても再利用しています。 気持ちの悪い人は、書き直して下さい。 maxlは最大の入力の長さ、name[]、value[]は、それぞれ フィールド名、値のポインタの配列、p_nfldは解析された フィールド数へのポインタです。

       
int decode_form(char* s,long len)
{
        int i,j;
        char buf,*s1;
        if(len==0)return(-1);
        s1=(char*)malloc(len);
        for(i=0,j=0;i<len;i++,j++)
        {
                if(s[i]=='+'){s1[j]=' ';continue;}
                if(s[i]!='%') {s1[j]=s[i];continue;}
                buf=((s[++i]>='A') ? s[i]-'A'+10 : s[i]-'0');
                buf*=16;
                buf+=((s[++i]>='A') ? s[i]-'A'+10 : s[i]-'0');
                s1[j]=buf;
        }
        for(i=0;i<j;i++) s[i]=s1[i];
        s[i]='\0';
        free(s1);
        return(0);
}

16進のコードをデコードするルーチンです。 %16進数というところに当たると、 ここの部分を通常のバイナリに置き換えます。 このルーチンもあまり説明は要らないと思います。

実用のコードでは、サブルーチンの部分は、別にコンパイルしておいて あとでリンクすると良いでしょう。
こんどは、フォームに日本語を入力してもちゃんと表示されますね。

(c)複数文字コードを統一する

上述したような理由で、この操作はやらなくとも大抵の場合は 大丈夫であると思いますが、ゲストブックのようなアプリの場合は やっておく方が安全でしょう。CGIの解説書では、 だいたいnkfでやっていますが、これはシェル上のフィルタですので、 このままではCプログラムからコールできません。 (まあ、やろうと思えば、Cのルーチンからpopen()などで、 オリジナルのnkfにパイプして、出力ファイルを Cのルーチンから読むなどということもできますが、 あまりエレガントではないでしょう) ちょっと手を加えてサブルーチンにしたものを ここ に置いときますので、参考にして下さい。 Windowsな方は改行コードをCR/LFにしたものを ここから とってくださいな。 (nkfの作者は自由に改変してよいという広い心の持ち主なのであった)
このサブルーチンのプロトタイプは、
nkfsub(char* cp,char* s_in,char* s_out)
です。
最初のパラメータにはオリジナルのnkfのように、(ただし-をぬいた) ターゲットの種別を以下のように指定して下さい。
”s”  シフトJIS
”j”   JIS
”e”   EUC
”j”を指定する時には(まああまりないと思いますが) ターゲットの文字列はシフトJISやEUCの倍くらいになりますので、 ターゲット用のバッファは十分大きく取って下さい。
s_inは入力文字列へのポインタ、s_outはターゲット文字列のバッファ へのポインタです。

Copyright 1999 Motoi Fujita