システムプログラム(第9週): WWWプログラミング

                                       筑波大学 システム情報工学研究科 
                                       コンピュータサイエンス専攻, 電子・情報工学系
                                       新城 靖
                                       <yas@is.tsukuba.ac.jp>

このページは、次の URL にあります。
http://www.coins.tsukuba.ac.jp/~syspro/2011/2011-06-22/index.html
あるいは、次のページから手繰っていくこともできます。
http://www.coins.tsukuba.ac.jp/~syspro/2011/
http://www.coins.tsukuba.ac.jp/~yas/

連絡事項

来週6月30日は、通常の講義を行う。試験は行わない。

今日の重要な話

WWW サーバ側のプログラム

CGIの考え方

WWW サーバは、普通は、ファイルに保存された HTML データやイメージ・データを 読み込み、WWWブラウザに返する。 CGI (Common Gateway Interface) では、ファイルが読み込まれる代わりにプログラムが実行され、その実行結果 がブラウザに返される。

Gateway とは、WWW で使われている HTTP というプロトコルへの入り口という 意味である。 プログラムの実行結果は、普通は、HTML にすることが多いが、普通のテキス トであることもイメージであることもある。

クライアント、WWWサーバ、CGIによるプロセス、ファイル

図? CGIの仕組み

CGI の利用例

CGIの設定(Apache)

情報科学類には、2つのサーバ・プロセスが動作している。

www.coins.tsukuba.ac.jp、ポート番号 80
~/public_html 以下を公開、CGI可能
www.coins.tsukuba.ac.jp、ポート番号 443
~/secure_html 以下を公開、CGI可能、SSL対応(http: ではなく https:)
いずれのサーバも、 Apache HTTP Server を 用いている。

CGI になるファイル名のパタンの例(設定により、これ以外も可能)。


/cgi-bin/name
/dir/name.cgi

cgi-hello.c

cgi-hello.c は、クライアントに HTML で hello. と返すCGI のプログラムで ある。

cgi-hello.cプログラム全体

   1:	/*
   2:	        cgi-hello.c --簡単な CGI のプログラム
   3:	        ~yas/syspro/www/cgi-hello.c
   4:	        Created on: 2002/06/23 18:21:34
   5:	*/
   6:	
   7:	#include <stdlib.h>     /* exit() */
   8:	#include <stdio.h>      /* printf() */
   9:	
  10:	extern void print_header();
  11:	extern void print_content();
  12:	
  13:	main()
  14:	{
  15:	        print_header();
  16:	        print_content();
  17:	}
  18:	
  19:	void print_header()
  20:	{
  21:	        printf("Content-Type: text/html\n");
  22:	        printf("\n");
  23:	}
  24:	
  25:	void print_content()
  26:	{
  27:	        printf("<HTML><HEAD></HEAD><BODY>\n");
  28:	        printf("hello.\n");
  29:	        printf("</BODY></HTML>\n");
  30:	}
CGI のプログラムは、Apache の場合、標準ではHTTP のヘッダ(一番最初の 「HTTP/1.0 200 OK」 ではなく、2行目以降)を標準出力に出力する所から始 まる。Content-Type: 行は、必ず付ける。空行がヘッダと本分の区切りである。 本文には、この場合は、HTML の文書を置いている。

cgi-hello.cの端末からの実行

$ mkdir ~/public_html/syspro-cgi-examples [←]
$ cd ~/public_html/syspro-cgi-examples/ [←]
$ cp ~yas/syspro/www/cgi-hello.c . [←]
$ cc cgi-hello.c -o cgi-hello.cgi [←]
$ ./cgi-hello.cgi  [←]
Content-Type: text/html

<HTML><HEAD></HEAD><BODY>
hello.
</BODY></HTML>
$ []
CGI のプログラムの場合、拡張子を .cgi にする。 CGI のプログラムを、シェルから実行することもできる。 引数の与え方については、後述する。

cgi-hello.cのWebブラウザからの実行

WWW ブラウザでは、次の URL を開く。

~ユーザ名/syspro-cgi-examples/cgi-hello.cgi

syspro-cgi-examples は、上の mkdir と合わせる。 mkdir せずに ~/public_html/ 直下の場合には、不要。

telnet では、次のようにして実行する。

$ telnet www.coins.tsukuba.ac.jp 80 [←]
Trying 130.158.86.1...
Connected to www.coins.tsukuba.ac.jp.
Escape character is '^]'.
GET /~ユーザ名/syspro-cgi-examples/cgi-hello.cgi HTTP/1.0[←]
[←]
HTTP/1.1 200 OK
Date: Mon, 20 Jun 2011 05:42:50 GMT
Server: Apache
Connection: close
Content-Type: text/html

<HTML><HEAD></HEAD><BODY>
hello.
</BODY></HTML>
Connection closed by foreign host.
$ []
GET の代りに POST でもよい。
$ telnet www.coins.tsukuba.ac.jp 80 [←]
Trying 130.158.86.1...
Connected to www.coins.tsukuba.ac.jp.
Escape character is '^]'.
GET /~ユーザ名/syspro-cgi-examples/cgi-hello.cgi HTTP/1.0[←]
[←]
HTTP/1.1 200 OK
Date: Mon, 20 Jun 2011 05:44:05 GMT
Server: Apache
Connection: close
Content-Type: text/html

<HTML><HEAD></HEAD><BODY>
hello.
</BODY></HTML>
Connection closed by foreign host.
$ []

CGIとフォーム

ブラウザからサーバに情報を送ってほしい時には、 フォーム(form)という機能を使う。

HTML記述例:

<H2><A NAME="cgi-example-get">CGI の GET メソッドを使う例</A></H2>

<FORM ACTION="cgi-printarg.cgi" method="get">
    <P>
    姓: <INPUT type="text" name="lastname">
    名: <INPUT type="text" name="firstname"><BR>
    <INPUT type="radio" name="sex" value="Male"> 男 <BR>
    <INPUT type="radio" name="sex" value="Female"> 女 <BR>
    <INPUT type="radio" name="sex" value="others"> その他 <BR>
    電子メール: <INPUT type="text" name="email"><BR>
    <INPUT type="submit" value="send"> <INPUT type="reset">
    </P>
</FORM>

<H2><A NAME="cgi-example-post">CGI の POST メソッドを使う例</A></H2>

<FORM ACTION="cgi-printarg.cgi" method="post">
    <P>
    姓: <INPUT type="text" name="lastname">
    名: <INPUT type="text" name="firstname"><BR>
    <INPUT type="radio" name="sex" value="Male"> 男 <BR>
    <INPUT type="radio" name="sex" value="Female"> 女 <BR>
    <INPUT type="radio" name="sex" value="others"> その他 <BR>
    電子メール: <INPUT type="text" name="email"><BR>
    <INPUT type="submit" value="send"> <INPUT type="reset">
    </P>
</FORM>
表示例:

CGI の GET メソッドを使う例

姓: 名:


その他
電子メール:

CGI の POST メソッドを使う例

姓: 名:


その他
電子メール:

<FORM></FORM>で括られた部 分が1つのフォームになる。(1つのページに複数のフォームを置くことがで きる。)

<INPUT>の部分が、ブラウザからサーバに送られるデー タを表わす。

send」ボタンが押されると、ブラウザは、指定された メソッドを使ってサーバにデータを送る。 メソッドとして GETが使われた時、Web ブラウザは、 普通のファイルを指定する時と同じように、URL に含めて送る。

GET /~syspro/2011/2011-06-22/cgi-printarg.cgi?lastname=姓&firstname=名&sex=Male&email=who@u-ust.ac.jp HTTP/1.0[←]
[←]
URL の長さの上限で制限されるので、長いデータは送れない。

メソッドとして POST が使われた時、Web ブラウザは、データ をHTTP の本文(ヘッダではない部分、空行の後)に入れて送る。

POST /~syspro/2011/2011-06-22/cgi-printarg.cgi HTTP/1.0[←]
[←]
lastname=姓&firstname=名&sex=Male&email=who@u-ust.ac.jp[←]

WWW サーバは、それを受け取ると、指定されたプログラムを実行する。実行さ れたプログラムは、 環境変数標準入力 からデータを受け取ることができる。

環境変数名 内容
REQUEST_METHOD データを送るのに使われたメソッド。POST か GET。
QUERY_STRING URLの指定で「?」以下に指定された文字列。
SCRIPT_NAME プログラムの名前。
CONTENT_LENGTH POSTメソッドが使われた時、標準入力から読み込めるデータのバイト数
データは、「&」、または、 「;」 で区切られた 「フィールド名=」の並びになっている。

例:

lastname=姓&firstname=名&sex=Male&email=who@u-ust.ac.jp
lastname=姓;firstname=名;sex=Male;email=who@u-ust.ac.jp
GETメソッドが使われた場合、CGI のプログラムは、環境変数 QUERY_STRING からデータを得る。 POSTメソッドが使われた場合、CGI のプログラムは、データを標準入力から受 け取る。データのバイト数は、環境変数CONTENT_LENGTH に指定 されている。

送られてくるパラメタの中に漢字、 「&」、 「;」、 その他 URLで使えない文字が含まれていた場合、 「%hh( hh は2桁の16進数)」という形になっている。 たとえば、「&」は、 「%26」として送られる。

これを元にもどすにはcgiparse というプログラム、 Ruby の CGI::unescape() や pack('H*')、 perl の命令 hex() が使われる。

cgi-printarg.c

次のプログラムは、CGI で実行された時に、その引数を表示するものである。 GET メソッド、および、POST メソッドの両方に対応している。なお、パラメタ の区切りとしては、「&」が使われることを期待している。

シェルからのCGIプログラムの実行

CGI のプログラムをシェルから実行してデバッグすることもできる。

GET の場合、環境変数 REQUEST_METHOD に GETと設定し、環境変数 QUERY_STRING にパラメタを設定する。

$ cp ~yas/syspro/www/cgi-printarg.c . [←]
$ cc cgi-printarg.c -o cgi-printarg.cgi  [←]
$ ./cgi-printarg.cgi [←]
Content-Type: text/html

<HTML><HEAD></HEAD><BODY><PRE>
REQUEST_METHOD=(null)
SCRIPT_NAME=(null)
QUERY_STRING=(null)
CONTENT_LENGTH=(null)
query_string:
(null)
Error while parsing query string
</PRE></BODY></HTML>
$ export REQUEST_METHOD=GET [←]
$ export QUERY_STRING='lastname=name1&firstname=name2&sex=Male&email=who@dom' [←]
$ ./cgi-printarg.cgi [←]
Content-Type: text/html

<HTML><HEAD></HEAD><BODY><PRE>
REQUEST_METHOD=GET
SCRIPT_NAME=(null)
QUERY_STRING=lastname=name1&amp;firstname=name2&amp;sex=Male&amp;email=who@dom
CONTENT_LENGTH=(null)
query_string:
lastname=name1&amp;firstname=name2&amp;sex=Male&amp;email=who@dom
qv[0]: lastname=name1(lastname=name1)
qv[1]: firstname=name2(firstname=name2)
qv[2]: sex=Male(sex=Male)
qv[3]: email=who@dom(email=who@dom)
</PRE></BODY></HTML>
$ []
POST の場合、まず、データをファイルに保存しておく。そのバイト数を wc コマンドなどで調べておく。環境変数 REQUEST_METHOD に POST と設定する。 環境変数 CONTENT_LENGTH にデータファイルの大きさを設定する(ここでは最 後の改行を取り除いたバイト数を指定している)。
$ unset QUERY_STRING [←]
$ export REQUEST_METHOD=POST [←]
$ echo 'lastname=name1&firstname=name2&sex=Male&email=who@dom' > data [←]
$ wc data  [←]
       1       1      54 data
$ export CONTENT_LENGTH=53 [←]
$ ./cgi-printarg.cgi < data  [←]
Content-Type: text/html

<HTML><HEAD></HEAD><BODY><PRE>
REQUEST_METHOD=POST
SCRIPT_NAME=(null)
QUERY_STRING=(null)
CONTENT_LENGTH=53
query_string:
lastname=name1&amp;firstname=name2&amp;sex=Male&amp;email=who@dom
qv[0]: lastname=name1(lastname=name1)
qv[1]: firstname=name2(firstname=name2)
qv[2]: sex=Male(sex=Male)
qv[3]: email=who@dom(email=who@dom)
</PRE></BODY></HTML>
$ []

cgi-printarg.cのmain()

   1:	
   2:	/*
   3:	        cgi-printarg.c -- CGI プログラムに対する引数を表示するプログラム
   4:	        ~yas/syspro/www/cgi-printarg.c
   5:	        Created on: 2002/06/23 18:21:34
   6:	*/
   7:	
   8:	#include <stdlib.h>     /* getenv(), malloc() */
   9:	#include <stdio.h>      /* printf() */
  10:	#include <string.h>     /* strlen() */
  11:	
  12:	extern void print_header(void);
  13:	extern void print_content(void);
  14:	extern char *get_query_string();
  15:	extern char *read_query_string();
  16:	extern void safe_printenv( char *name );
  17:	extern void safe_print_string( char *str );
  18:	extern char *html_escape( char *str );
  19:	extern char *decode_hex( char *str );
  20:	int string_split( char *str, char del, int *countp, char ***vecp  );
  21:	void free_string_vector( int qc, char **vec );
  22:	int countchr( char *s, char c );
  23:	
  24:	main()
  25:	{
  26:	        print_header();
  27:	        print_content();
  28:	}
  29:	
  30:	void print_header()
  31:	{
  32:	        printf("Content-Type: text/html\n");
  33:	        printf("\n");
  34:	}
  35:	
  36:	void print_content()
  37:	{
  38:	    char  *query_string ;
  39:	    char **qv ;
  40:	    int    qc ;
  41:	    int    i ;
  42:	
  43:	        printf("<HTML><HEAD></HEAD><BODY><PRE>\n");
  44:	
  45:	        safe_printenv("REQUEST_METHOD");
  46:	        safe_printenv("SCRIPT_NAME");
  47:	        safe_printenv("QUERY_STRING");
  48:	        safe_printenv("CONTENT_LENGTH");
  49:	
  50:	        query_string = get_query_string();
  51:	        printf("query_string:\n");
  52:	        safe_print_string( query_string ); printf("\n");
  53:	
  54:	        if(  string_split( query_string,'&',&qc, &qv ) < 0 )
  55:	        {
  56:	            printf("Error while parsing query string\n");
  57:	            printf("</PRE></BODY></HTML>\n");
  58:	            free( query_string );
  59:	            exit( -1 );
  60:	        }
  61:	        for( i=0 ; i<qc ; i++ )
  62:	        {
  63:	            char *decoded ;
  64:	            printf("qv[%d]: ",i);
  65:	            safe_print_string(qv[i]);
  66:	            decoded = decode_hex( qv[i] );
  67:	            printf("("); safe_print_string( decoded ); printf(")\n");
  68:	            if( decoded )
  69:	                free( decoded );
  70:	        }
  71:	
  72:	        printf("</PRE></BODY></HTML>\n");
  73:	        free_string_vector( qc, qv );
  74:	        free( query_string );
  75:	}
  76:	
これは、標準の CGI プログラムな ので、print_header()では、HTTP のヘッダのうち、Content-Type: 行だけを 出力している。

print_content() では、本文を出力している。

safe_printenv() で、環境変数 REQUEST_METHOD, SCRIPT_NAME, QUERY_STRING, CONTENT_LENGTH の内容を標準出力に出力している。

get_query_string() は、クライアントから送られてきたパラメタ(query string)を読込み、文字列として返す関数である。この結果を、 safe_print_string() で標準出力に出力している。

strint_split() で query string を区切り文字「&」で分解している。 この関数は、練習問題(705) HTTPサーバの要求解析 で使ったものと同じものである。 分解した結果は、main() の引数である int argc, char *argv[] と同じ形になっ ている。qc が、argc, qv が argv に対応する。

for ループでは、qv[] をsafe_print_string() で標準出力に出力している。 さらに、decode_hex() で、%hh (%hhは16進数)の形式を、バイトに元に戻すものであ る。

free_string_vector() は、string_split() で malloc() したものを free() するための手続きである。

Webブラウザの表示例

REQUEST_METHOD=GET
SCRIPT_NAME=/~syspro/2011/2011-06-22/cgi-printarg.cgi
QUERY_STRING=lastname=name1&firstname=name2&sex=Male&email=who%40dom
CONTENT_LENGTH=(null)
query_string:
lastname=name1&firstname=name2&sex=Male&email=who%40dom
qv[0]: lastname=name1(lastname=name1)
qv[1]: firstname=name2(firstname=name2)
qv[2]: sex=Male(sex=Male)
qv[3]: email=who%40dom(email=who@dom)

REQUEST_METHOD=POST
SCRIPT_NAME=/~syspro/2011/2011-06-22/cgi-printarg.cgi
QUERY_STRING=
CONTENT_LENGTH=55
query_string:
lastname=name1&firstname=name2&sex=Male&email=who%40dom
qv[0]: lastname=name1(lastname=name1)
qv[1]: firstname=name2(firstname=name2)
qv[2]: sex=Male(sex=Male)
qv[3]: email=who%40dom(email=who@dom)

cgi-printarg.cgiの内部関数

以下の内容は、飛ばしてよい。

get_query_string()

get_query_string() は、CGI でプログラムが実行された時にパラメタが含まれた文字列を読み込む プログラムである。 GET メソッドの場合()は、環境変数 REQUEST_METHOD から読み込む。POST メソッ ドの場合は、標準入力から読み込む。

html_escape()

safe_printenv() は、環境変数をgetenv() で検索し、その値を safe_print_string() で標準出力に出力している。

safe_print_string() は、html_escape() を使って文字列を安全なもの にして表示する。

html_escape()は、 クライアントから送られたような信頼できない文字列を クライアントに送り返す時に、不用意に JavaScript 等がクライアント側の ブラウザで動作しないようにするための関数である。 たとえば、<SCRIPT> のような文字列がクライアントから送られて きたとしても、クライアントには「&lt;SCRIPT&gt;」と 返すだけで、スクリプトは実行されない。

decode_hex()

decode_hex() は、「%hh(2桁の16進数)」を元のバイトに 戻すものである。isxdigit() は、16進数の文字として正しいかを調べてい る。strtol() で基底を 16 として変換している。

Apacheの機能

ステータス行も含むヘッダ(Apache non-parsed headers)

Apache では、標準では、サーバが最初のステータス行「HTTP/1.1 200 OK」を Apache が作成している。Status:という行を含めることで、ステータ ス行を記述することもできる。Status:がない場合、Location: というヘッダがあれば、302 Found (redirect)、それ以外の場合は、200 OKが返される。それ以外のヘッダも、Apache が自動的に生成して返す。

ステータス行も含めて、完全なヘッダを CGI で作成して返すこともできる。 この方法を、Apache では、nph (non-parsed headers) と呼んでいる。

Apache で、nph を使うには、nph- で始まるファイル名を用いる。こ れを使うと、Apache は、CGI プログラムが作成したヘッダを一切解釈(parse) せず、そのままクライアントに返す。

アクセスログとエラーログ

WWW サーバは、ページがアクセスされる度に記録を残す。この記録をアクセス ログという。アクセスログは、情報科学類のサーバ www (www.coins.tsukuba.ac.jp) では、次の場所にある。
http
/var/log/apache2/public/access*
/var/log/apache2/public/v6access*
https
/var/log/apache2/secure/access*
/var/log/apache2/secure/v6access*
この中で、数字が一番大きなもの、または、日付が新しいものが、 現在増加しているログである。
$ cd /var/log/apache2/public [←]
$ ls    access* | tail -1 [←]
$ ls -t access* | head -1 [←]
$ []
以下の例では、現在増加しているログは access_2011-06-20-00_50_43.log である。
$ cd /var/log/apache2/public [←]
$ ls    access* | tail -1 [←]
access_2011-06-20-00_50_43.log
$ ls -t access* | head -1 [←]
access_2011-06-20-00_50_43.log
$ []
以下は、アクセス・ログの内容の例である。
130.158.83.140 - - [20/Jun/2011:14:55:40 +0900] 
"GET /~syspro/2011/2011-06-22/index.html HTTP/1.1" 
304 - "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 
10.6; rv:2.0.1) Gecko/20100101 Firefox/4.0.1"

130.158.83.140 - - [20/Jun/2011:14:55:40 +0900] 
"GET /~syspro/2011/2011-06-22/images/www-cgi.png HTTP/1.1" 
304 - "-" "Mozilla/5.0 (Macintosh; Intel Mac OS 
X 10.6; rv:2.0.1) Gecko/20100101 Firefox/4.0.1"
1行が1つのページの転送を意味する。左端は、クライアントの IP アドレス である。どのような要求が来たか、クライアントのIPアドレスがわかる。

アクセスログの他に、エラーが起きた時にもログが作られる。これを エラーログ という。エラーログは、情報科学類のサーバ www (www.coins.tsukuba.ac.jp) では、次の場所にある。

http
/var/log/apache2/public/error*
/var/log/apache2/public/v6error*
https
/var/log/apache2/secure/error*
/var/log/apache2/secure/v6error*
CGI のプログラムをデバッグする時には、このエラーログを見て原因を探る。

その他に、次のようなログを取ることもある。

agent_log
WWW ブラウザの種類
referer_log
どの WWW ページから参照されているか
suexec_log (coins では /var/log/apache2/suexec_log)
suExec を使っていた時のログ
自分が単にWWWブラウザを使っているだけでも、さまざまな情報をサーバに 送っているとに注意しなさい。

CGI プログラムを実行するプロセスの権限

シェルからプロセスを生成しプログラムを実行する時には、その新しいプロセ スの権限(ユーザ属性、getuid() の結果)は、シェルのものからコピーされ る。すなわち、新しいプロセスはシェルと同じユーザの権限で動作する。CGI の場合、遠隔の WWW ブラウザのユーザは、その WWW サーバにログインする権 限はないが、CGI のプログラムを実行することができる。そのプログラムを、 どのユーザの権限で実行する方法としては、大きく次の2つ方法がある。 どちらの方式にも一長一短がある。 WWW サーバ・プロセスの権限で動作させた場合、

suEXEC を使う方法の場合、長所と短所は逆になる。

CGIでのその他の注意事項

ファイルのロック

アクセスカウンタなどデータ・ファイルを書き換えるような CGI プログラム の場合、複数のプログラムが同時に実行された時のことを考慮する必要がある。 必要に応じて、ファイルのロックを行うなどして、一度に1つのプロセスしか プログラムを実行しないようにする。このことを、相互排除という。

相互排除について詳しくは、 2学期のオペレーティング・システムで扱う。 特に、扱うべき資源が複数になった場合には、デッドロックに気をつける。

Unix では、ファイルに対するロックを扱うために、次のような機能を提供し ている。

flock() システムコール
ファイル単位のロック
fcntl() システムコールと F_GETLK, F_SETLK, F_SETLKW
レコード単位のロック
lockf() ライブラリ関数
レコード単位のロック
CGI のプログラムでなくても、ロックをしなければならないことがある。

セッション管理

CGI は、基本的には1回の操作で完結する。買い物かごを作るには、複数の CGI のプログラムの間で一連の操作を1つの連続したもの(セッション)とし て扱う必要がある。

セッション管理には、次のような方法がある。

セッション管理にクッキーを使う方法は、正しく使えば比較的安全である。し かし、クッキーはセッションを越えて有効なもの、異なるサイトに送られるも のも定義できる。また、クッキーにパスワードを埋め込むという幼稚で危険な 実装も時々見受けられる。

セッションは、認証とは別である。同じ人が複数のセッションを実行すること もある。

クッキー

cookieは、コンピュータ・サイエンスの専門用語。

協調して動作しているプログラムの間で、ある一連の作業を識別するための数 を意味する。

RPC、HTTP(WWW)で使われる。

RPCでのクッキー

2008年度3学期の「 分散システム」のページも参照。

WWWでのcookie

練習問題と課題

練習問題(901) WWW サーバ上のデータの観察

htttp://www.coins.tsukuba.ac.jp/ のページは、サーバ www (www.coins.tsukuba.ac.jp) 上の /var/www/htdocs/ 以下にある。

WWW ブラウザや telnet を使って得た結果と /var/www/htdocs/ 以下のファイ ルが同一であることをwww に ssh でログインして確認しなさい。

ssh コマンドを使ったことがない人は、 コンピュータリテラシの資料 を参考にして、ssh の使い方を学びなさい。

cosmos10:~ yas$ ssh www[←]
Password: パスワード(画面には表示されない)[←]
Last login: Sun Jun 19 21:37:41 2011 from www.coins.tsukuba.ac.jp
www:~ yas$ cd /var/www/htdocs/
www:htdocs yas$ lv index.html
www:htdocs yas$ []
ssh で遠隔ログインする前後に、プロンプトのホスト名の部分が変化すること に注意しなさい。

同様に https でアクセスできるページについても調べなさい。 トップのディレクトリは、/var/www/secure_htdocs/ 以下にある。

練習問題(902) アクセスログの観察

アクセスログは、サーバ(www,www.coins.tsukuba.ac.jp)上の /var/log/apache2/public/access_log.* というファイルにためられる。 これを、次のようにして観察しなさい。
  1. ssh で www (または、www.coins.tsukuba.ac.jp) にログインする。
  2. 次のように ls コマンドで、ログファイルを調べる。
    $ cd /var/log/apache2/public [←]
    $ ls    access* | tail -1 [←]
    access_2011-06-20-00_50_43.log
    $ ls -t access* | head -1 [←]
    access_2011-06-20-00_50_43.log
    $ lv   access_2011-06-20-00_50_43.log [←]
    $ head access_2011-06-20-00_50_43.log [←]
    $ tail access_2011-06-20-00_50_43.log [←]
    $ []
    
    この例では、access_2011-06-20-00_50_43.log が最新のものである。
  3. 次のように tail コマンドを実行する。tail コマンドは、 -f オプションを付けて実行すると終了しないので、 最後に ^C キーで終了させる。
    $ tail -f access_2011-06-20-00_50_43.log [←]
    
  4. WWW ブラウザ(Firefox など)を実行して、 http://www.coins.tsukuba.ac.jp/や自分の WWW ページを開く。
  5. 「再読込み」を行う。
  6. データが流れていく時には、適当な所で ^C で tail コマンドを終了させる。
  7. 自分が使っているコンピュータのホスト名が表示されるかを見る。端末 のスクロール機能を使う。
同様に、エラーログ についても調べなさい。

練習問題(903) 引数の表示

次の CGI プログラムに対して、フォームを定義し、何かデータを送ってみなさい。

http://www.coins.tsukuba.ac.jp/~syspro/2011/2011-06-22/cgi-printarg.cgi

たとえば、次のような記述を含む HTML ファイルを作成し、 ~/public_html/ 以下に置く。

<FORM ACTION="http://www.coins.tsukuba.ac.jp/~syspro/2011/2011-06-22/cgi-printarg.cgi" method="get">
    arg1: <INPUT type="text" name="arg1">
    arg2: <INPUT type="text" name="arg2">
    <INPUT type="submit">
    <INPUT type="reset">
</FORM>

練習問題(904) CGIによる足算

2つのデータを受け取り、その結果を加えた数を表示するようなCGI のプログ ラムを作成しなさい。

ヒント:

  1. "arg1=10&arg2=20"のように送るようなフォームを想定 してプログラムを作成する。
  2. cgi-printarg.cで、余計な表示をなくす。
  3. string_split() で分解した後、"arg1=" や "arg2=" があるパラメタを検索 する。
         char *arg1_s,*arg2_s;
         arg1_s = getparam(qc,qv,"arg1");
         arg2_s = getparam(qc,qv,"arg2");
    
    getparam() は、次のように自分で定義する。
    char *getparam( int qc, char *qv[], char *name /*変数名*/ )
    {
       int i;
    	for( i=0; i<qc; i++ ) {
    	    qv[i] の中で "変数名=値" となっているものを探す。
    	    見つければ "値" の部分を返す。
    	}
    	見つからなければ、NULL を返す。
    }
    
    ここで、"arg1arg2=10" のようなパラメタがある時に、"arg1" や "arg2" で引っ かからないようにしなさい。同じ変数名で複数のパラメタが指定場合、この課 題ではどのパラメタを返してもよい。
  4. 引数が足りかったり、不正な場合にはエラー・メッセージを表示して終了する。
  5. 得られた文字列を、strtol() 等で整数に変換し、足算を行う。結果を printf() の %d などで表示する。

  6. 作成したプログラムを、シェルから実行 して動作を確認する。
    $ export REQUEST_METHOD=GET [←]
    $ export QUERY_STRING='arg1=10&arg2=10' [←]
    $ ./a.out [←]
    $ []
    
    なお、一度設定した環境変数の値は、シェルを終了するまで残っている。 a.out を実行するたびに設定する必要はない。

余裕があれば、そのプログラムを CGI として動作させなさい。
  1. 実行形式 a.out を ~/public_html の下にコピーする。例:
    $ mkdir ~/public_html/syspro-cgi-examples [←]
    $ cp a.out ~/public_html/syspro-cgi-examples/calc.cgi [←]
    
  2. Web ブラウザで次のような URL を開く。例:
    $ open 'http://www.coins.tsukuba.ac.jp/~ログイン名/syspro-cgi-examples/calc.cgi?arg1=10&arg2=20' [←]
    
  3. "arg1=10&arg2=20"のように送るようなフォームを含むHTML ファ イルを作成する。 例:
    $ emacs ~/public_html/syspro-cgi-examples/calc.html [←]
    
  4. Web ブラウザでフォームを含む HTML ファイルを開き、実行する。例:
    $ open http://www.coins.tsukuba.ac.jp/~ログイン名/syspro-cgi-examples/calc.html [←]
    

練習問題(905) CGIによる加減乗除

練習問題(904) で、足算以外の計算もできるようにしなさ い。 演算の種類は、メニューやラジオボタンなどで選択できるようにしなさい。 この課題では、Web ブラウザで実行して、その結果とその時のアクセスログを 示しなさい。

練習問題(906) CGIによるカレンダの表示

fork() と execve() を使って、/usr/bin/cal を実行し、WWW ブラウザに今月 のカレンダを実行する CGI プログラムを作りなさい。cal コマンドは、引数を 付けずに実行すると、今月のカレンダーを表示する。
$ cal [←]
     June 2011
Su Mo Tu We Th Fr Sa
          1  2  3  4
 5  6  7  8  9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30

$ []

CGI プログラムを実行した月のカレンダーを表示しなさい。固定した表示する だけなら、CGI の意味はない。余計な表示は省略すること。

この課題では、system() や popen() を用いてはならない。

  1. ヘッダを表示する。
  2. fork(), execve(), wait() 等を用いて、cal コマンドを実行する。 標準出力をそのままにしておけば cal コマンドの結果がそのまま CGI の結果として返される。 その他に、パイプを用いて結果を受け取る方法も考えられる。

  3. 作成したプログラムを、シェルから実行 して動作を確認する。
    $ ./a.out [←]
    Content-Type: text/plain
    
         June 2011
    Su Mo Tu We Th Fr Sa
              1  2  3  4
     5  6  7  8  9 10 11
    12 13 14 15 16 17 18
    19 20 21 22 23 24 25
    26 27 28 29 30
    
    $ []
    
余裕があれば、そのプログラムを CGI として動作させなさい。
  1. ~/public_html の下にコピーする。
    $ mkdir ~/public_html/syspro-cgi-examples [←]
    $ cp a.out ~/public_html/syspro-cgi-examples/show-cal-one.cgi [←]
    
  2. Web ブラウザから次のようなURLを開き、CGI を実行する。
    $ open http://www.coins.tsukuba.ac.jp/~ログイン名/syspro-cgi-examples/calc-one.cgi [←]
    
  3. CGI のプログラムを実行するようなフォームを含むHTML ファイルを作成する。
    <FORM ACTION="http://www.coins.tsukuba.ac.jp/~ログイン名/syspro-cgi-examples/calc-one.cgi" method="get">
    <INPUT type="submit">
    </FORM>
    
  4. Web ブラウザからフォームを含む HTML ファイルを開き、CGI を実行する。

練習問題(907) CGIによるカレンダの表示(引数付き)

練習問題(906) で、今月のカレンダーではなく、任意の年・月のカレンダーを表示 しなさい。

ヒント:

  1. "year=2011&month=6"のように送るようなフォームを想定 してプログラムを作成する。
  2. cgi-printarg.cで、余計な表示をなくす。
  3. string_split() で分解した後、"year=" や "month=" があるパラメタを検索 する。
         char *year_s,*month_s;
         year_s = getparam("year");
         month_s = getparam("month");
    
    getparam() は、次のように自分で定義する。
    char *getparam( int qc, char *qv[], char *name /*変数名*/ )
    {
       int i;
    	for( i=0; i<qc; i++ ) {
    	    qv[i] の中で "変数名=値" となっているものを探す。
    	    見つければ "値" の部分を返す。
    	}
    	見つからなければ、NULL を返す。
    }
    
    ここで、"arg1arg2=10" のようなパラメタがある時に、"arg1" や "arg2" で引っ かからないようにしなさい。同じ変数名で複数のパラメタが指定場合、この課 題ではどのパラメタを返してもよい。
  4. 引数が足りかったり、不正な場合にはエラーメッセージを表示して終了する。
  5. 得られた文字列を、strtol() 等で整数に変換する。 引数の範囲をチェックする。たとえば、13月や-1年等が与えられた場合には、 エラーメッセージを表示して終了する。
  6. snprintf() 等で、cal コマンドにあたえる引数を文字列で作る。
  7. ヘッダを表示する。
  8. fork(), execve(), wait() 等を用いて、cal コマンドを実行する。 標準出力をそのままにしておけば cal コマンドの結果がそのまま CGI の結果として返される。 その他に、パイプを用いて結果を受け取る方法も考えられる。

  9. 作成したプログラムを、シェルから実行 して動作を確認する。
    $ export REQUEST_METHOD=GET [←]
    $ export QUERY_STRING='year=2011&month=6' [←]
    $ ./a.out [←]
    $ []
    
    なお、一度設定した環境変数の値は、シェルを終了するまで残っている。 a.out を実行するたびに設定する必要はない。 余裕があれば、そのプログラムを CGI として動作させなさい。
余裕があれば、そのプログラムを CGI として動作させなさい。
  1. ~/public_html の下にコピーする。
    $ mkdir ~/public_html/syspro-cgi-examples [←]
    $ cp a.out ~/public_html/syspro-cgi-examples/show-cal.cgi [←]
    
  2. Web ブラウザから次のようなURLを開き、CGI を実行する。
    $ open 'http://www.coins.tsukuba.ac.jp/~ログイン名/syspro-cgi-examples/calc.cgi?year=2011&month=6' [←]
    
  3. "year=2011&month=6"のように送るようなフォームを含む HTML ファイルを作成する。
  4. Web ブラウザからフォームを含む HTML ファイルを開き、CGI を実行する。

注意: CGI のパラメタは、最初は、文字列で受け取り、最終的に cal コマンドには文 字列で渡す。この時に、年月日としては不適切なものがクライアントから渡さ れる可能性がある。不適切なものが渡された場合には、エラー・メッセージを 表示しなさい。また、cal コマンドは実行してはならない。

練習問題(908) CGIプログラムの実行権限

CGI プログラムが、どのユーザの権限で動作しているかを調べるプログラムを つくりなさい。CGI として実行した時とシェルから実行した時の結果を比較し なさい。

ヒント:CGI のプログラムから getuid(), getgid(), getgroups() システム・ コールを実行する。uid や gid を文字列で表示したいなら、getpwuid(), getpwuid_r(), getgrgid(), getgrgid_r() を用いる。

getuid(), getgid(), getgroups() システム・コールを用いる代わりにid コマ ンドを実行する方法もある。

練習問題(909) 文字列を表示するアクセス・カウンタ

アクセスされる度に、数字が増えるようなページをCGI で作成しなさい。よく 使われるアクセス・カウンタは、HTML にインライン・イメージとして埋め込ま れる。この課題で作成するカウンタは、単体のページとして動作し、テキスト を表示するものである。

ヒント:

  1. 次のようなプログラムを作成する。
    main()
    {
    	ファイルから整数を読み出す(ファイルがなければ0とする)。
    	1 加える。
    	標準出力(画面)に整数値を表示する。
    	ファイルに整数を保存する。
    }
    
  2. 上のプログラムに、CGI として動作するようにヘッダを付ける。 Content-Type: は、text/plain にする。 Content-Type:text/html にする方法もある。 ただし、本文は、HTML として作成する必要がある。
  3. 最終的にそのプログラムが ~/public_html 以下にコピーされるこ とを想定して、整数を保存するファイルを修正する。 必要ならば、初期状態のファイルを作成して、環境に合わせて モードを適切なものにする(常に chmod 666 は誤り)。
  4. 拡張子を .cgi として、~/public_html 以下にコピーする。
  5. Web ブラウザから URL を指定して実行する。

練習問題(910) 文字列を表示するアクセス・カウンタ(ロック付き)

練習問題(909) で、ファイルに対するロックを行うようにしないさい。

ロックがきちんと動作していることを示しなさい。そのためには、ロックして getchar() 等でキーボードからの指示で(アンロックして)終了するプログラム を作成する方法がある。プログラムが終了すると自動的にアンロックされる。 次のプログラムを参考にしてもよい。

注意: 2010年4月-2014年3月、coins の環境では、www サーバからNFS経由でアクセス しているファイルに対して次のどの方法でも ロックが働く

他の環境では、NFS経由でのロックがうまく働かないこともあるので、使う前に 確認すること。2009年2月までの環境では、flock() や fcntl() が動作しなかっ た。

練習問題(911) ハッシュ表を使うライブラリ関数

メモリ中のハッシュ表を使うライブラリ関数 hcreate(), hsearch(), hdestroy() について調べなさい。

練習問題(912) 永続的ハッシュ表

永続的なハッシュ表とは、メモリ中のハッシュ表と同様に、キーを指定して値 をアクセスするための機能を提供するが、データはファイルに保存されている ので、次に同じプログラムを実行した時も、そのまま残されている所が異なる。 具体的な内部のアルゴリズムとしては、ハッシュの他にB木もよく使われる。 一般的なデータベースの場合、いくつかの制約(integrity rule)を満たすこと が重要となるが、永続的ハッシュ表の場合には、特にそのような制約はない。

永続的なハッシュ表を作成する方法として、dbm ライブラリを用いる方法があ る。これを利用してみなさい。ライブラリ関数としては、dbm_open(), dbm_store(), dbm_fetch(), dbm_delete(), dbm_close()がある。詳しくは、 man dbm を見なさい。

練習問題(913) 文字列を表示するアクセス・カウンタ(永続的ハッシュ表の利用)

練習問題(909) で、データを通常のファイルではなく、永続的カウンタを用いるように しなさい。
Last updated: 2011/06/22 10:23:48
Yasushi Shinjo / <yas@is.tsukuba.ac.jp>