筑波大学 システム情報工学研究科 コンピュータサイエンス専攻, 電子・情報工学系 新城 靖 <yas@is.tsukuba.ac.jp>
このページは、次の URL にあります。
http://www.coins.tsukuba.ac.jp/~syspro/2009/No9.html
あるいは、次のページから手繰っていくこともできます。
http://www.coins.tsukuba.ac.jp/~syspro/2009/
http://www.coins.tsukuba.ac.jp/~yas/
本日の宿題の提出期限は、通常よりも延長する。
Emacs で、部分的にコピーすること。
途中で Emacs を終了しないこと。 画面には表示されていなくても、内部的には保持している。 C-x b で、編集しているファイル(バッファ)を切り替えることができる。
複数のウインドウを使う方法もある。
くわしくは、 手引きをみなさい。
http://www.coins.tsukuba.ac.jp/ce/
授業時間は、Web検索禁止。代わりに TA や教員に聞きなさい。
Gateway とは、WWW で使われている HTTP というプロトコルへの入り口という 意味である。 プログラムの実行結果は、普通は、HTML にすることが多いが、普通のテキス トであることもイメージであることもある。
CGI の利用例
情報学類には、2つのサーバ・プロセスが動作している。
CGI になるファイル名のパタンの例(設定により、これ以外も可能)。
/cgi-bin/name /dir/name.cgi
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 の文書を置いている。
% 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 のプログラムを、シェルから実行することもできる。
引数の与え方については、後述する。
http://www.coins.tsukuba.ac.jp/~syspro/2009/No9_files/cgi-hello.cgi
telnet では、次のようにして実行する。
% telnet www.coins.tsukuba.ac.jp 80
Trying 130.158.86.207...
Connected to www.coins.tsukuba.ac.jp.
Escape character is '^]'.
GET /~syspro/2009/No9_files/cgi-hello.cgi HTTP/1.0
HTTP/1.1 200 OK
Date: Tue, 12 Jun 2009 10:50:16 GMT
Server: Apache/2.0.55 (Unix) PHP/4.4.2
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.207...
Connected to www.coins.tsukuba.ac.jp.
Escape character is '^]'.
POST /~syspro/2009/No9_files/cgi-hello.cgi HTTP/1.0
HTTP/1.1 200 OK
Date: Tue, 12 Jun 2009 10:51:47 GMT
Server: Apache/2.0.55 (Unix) PHP/4.4.2
Connection: close
Content-Type: text/html
<HTML><HEAD></HEAD><BODY>
hello.
</BODY></HTML>
Connection closed by foreign host.
%
HTML記述例:
<H2><A NAME="cgi-example-get">CGI の GET メソッドを使う例</A></H2> <FORM ACTION="No9_files/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="No9_files/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>表示例:
<FORM>
と</FORM>
で括られた部
分が1つのフォームになる。(1つのページに複数のフォームを置くことがで
きる。)
<INPUT>
の部分が、ブラウザからサーバに送られるデー
タを表わす。
「send
」ボタンが押されると、ブラウザは、指定された
メソッドを使ってサーバにデータを送る。
メソッドとして GET
が使われた時、Web ブラウザは、
普通のファイルを指定する時と同じように、URL に含めて送る。
GET /~syspro/2009/No9_files/cgi-printarg.cgi?lastname=姓&firstname=名&sex=Male&email=who@u-ust.ac.jp HTTP/1.0
URL の長さの上限で制限されるので、長いデータは送れない。
メソッドとして POST
が使われた時、Web ブラウザは、データ
をHTTP の本文(ヘッダではない部分、空行の後)に入れて送る。
POST /~syspro/2009/No9_files/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
GETメソッドが使われた場合、CGI のプログラムは、環境変数
QUERY_STRING
からデータを得る。
POSTメソッドが使われた場合、CGI のプログラムは、データを標準入力から受
け取る。データのバイト数は、環境変数CONTENT_LENGTH
に指定
されている。
送られてくるパラメタの中に漢字、
(&自身)、
URLで使えない文字が含まれていた場合、
「%hh
( hh は2桁の16進数)」という形になっている。
これを元にもどすにはcgiparse
というプログラム、
Ruby
の CGI::unescape() や pack('H*')、
perl
の命令 hex() が使われる。
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:これは、標準(nphではない)の 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() で標準出力に出力している。
for ループでは、qv[] をsafe_print_string() で標準出力に出力している。
さらに、decode_hex() で、%hh
(%hh
は16進数)の形式を、バイトに元に戻すものであ
る。
safe_print_string() は、html_escape() を使って文字列を安全なもの にして表示する。
html_escape()は、 クライアントから送られたような信頼できない文字列を クライアントに送り返す時に、不用意に JavaScript 等がクライアント側の ブラウザで動作しないようにするための関数である。 たとえば、<SCRIPT> のような文字列がクライアントから送られて きたとしても、クライアントには「<SCRIPT>」と 返すだけで、スクリプトは実行されない。
%hh
(2桁の16進数)」を元のバイトに
戻すものである。isxdigit() は、16進数の文字として正しいかを調べてい
る。strtol() で基底を 16 として変換している。
string_split() は、引数の文字列を、引数の区切り記号で分解する関数である。 分解した結果は、main() の引数である int argc, char *argv[] と同じ形になっ ている。qc が、argc, qv が argv に対応する。
free_string_vector() は、string_split() で malloc() したものを free() するための手続きである。
countchr() は、文字列の中でその文字がいくつ出てくるかを数えるものである。
この関数は、練習問題(705) HTTPサーバの要求解析 で使ったものと同じものである。
GET の場合、環境変数 REQUEST_METHOD に GETと設定し、環境変数 QUERY_STRING にパラメタを設定する。
% setenv REQUEST_METHOD GET
% setenv 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&firstname=name2&sex=Male&email=who@dom
CONTENT_LENGTH=(null)
query_string:
lastname=name1&firstname=name2&sex=Male&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 にデータファイルの大きさを設定する(ここでは最
後の改行を取り除いたバイト数を指定している)。
% echo 'lastname=name1&firstname=name2&sex=Male&email=who@dom' > data
% wc data
1 1 54 data
% setenv REQUEST_METHOD POST
% unsetenv QUERY_STRING
% setenv 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&firstname=name2&sex=Male&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>
%
REQUEST_METHOD=GET SCRIPT_NAME=/~syspro/2009/No9_files/cgi-printarg.cgi QUERY_STRING=lastname=arg1&firstname=arg2&sex=Male&email=arg3 CONTENT_LENGTH=(null) query_string: lastname=arg1&firstname=arg2&sex=Male&email=arg3 qv[0]: lastname=arg1(lastname=arg1) qv[1]: firstname=arg2(firstname=arg2) qv[2]: sex=Male(sex=Male) qv[3]: email=arg3(email=arg3)
REQUEST_METHOD=POST SCRIPT_NAME=/~syspro/2009/No9_files/cgi-printarg.cgi QUERY_STRING= CONTENT_LENGTH=48 query_string: lastname=arg1&firstname=arg2&sex=Male&email=arg3 qv[0]: lastname=arg1(lastname=arg1) qv[1]: firstname=arg2(firstname=arg2) qv[2]: sex=Male(sex=Male) qv[3]: email=arg3(email=arg3)
Status:
という行を含めることで、ステータ
ス行を記述することもできる。Status:
がない場合、Location:
というヘッダがあれば、302 Found
(redirect)、それ以外の場合は、200
OK
が返される。それ以外のヘッダも、Apache が自動的に生成して返す。
ステータス行も含めて、完全なヘッダを CGI で作成して返すこともできる。 この方法を、Apache では、nph (non-parsed headers) と呼んでいる。
Apache で、nph を使うには、nph-
で始まるファイル名を用いる。こ
れを使うと、Apache は、CGI プログラムが作成したヘッダを一切解釈(parse)
せず、そのままクライアントに返す。
/var/log/httpd/www.coins80/access_log.* /var/log/httpd/www.coins443/access_log.*この中で、数字が一番大きなもの、または、日付が新しいものが、 現在増加しているログである。
% cd /var/log/httpd/www.coins80
% ls access_log.* | tail -1
% ls -t access_log.* | head -1
%
以下は、アクセスログの例である。
130.158.83.140 - - [10/Jun/2009:21:47:44 +0900] "GET /~syspro/2009/shui/common_files/Ctrl-Z.png HTTP/1.1" 200 189 130.158.83.140 - - [10/Jun/2009:21:53:52 +0900] "GET /~yas/coins/syspro-2009/No9.html HTTP/1.1" 200 570761行が1つのページの転送を意味する。左端は、クライアントの IP アドレス である。どのような要求が来たか、クライアントのIPアドレスがわかる。
アクセスログの他に、エラーが起きた時にもログが作られる。これをエラーロ グという。エラーログは、情報学類のサーバ www (orchid-nwd) では、次の場所にある。
/var/log/httpd/www.coins80/error_log.* /var/log/httpd/www.coins443/error_log.*CGI のプログラムをデバッグする時には、このエラーログを見て原因を探る。
その他に、次のようなファイルがある。
agent_log
referer_log
suexec_log
rw-rw-rw-
や 777
rwxrwxrwx
にする)。このため、そのコンピュータにログイン
でき人ならば、そのファイルの名前を知っていれば、データを取り出したり内
容を破壊したりできる。
suEXEC を使う方法の場合、長所と短所は逆になる。
アクセスカウンタなどデータ・ファイルを書き換えるような CGI プログラム の場合、複数のプログラムが同時に実行された時のことを考慮する必要がある。 必要に応じて、ファイルのロックを行うなどして、一度に1つのプロセスしか プログラムを実行しないようにする。このことを、相互排除という。
相互排除について詳しくは、 2学期のオペレーティング・システムで扱う。 特に、扱うべき資源が複数になった場合には、デッドロックに気をつける。
Unix では、ファイルに対するロックを扱うために、次のような機能を提供し ている。
セッション管理には、次のような方法がある。
セッション管理にクッキーを使う方法は、正しく使えば比較的安全である。し かし、クッキーはセッションを越えて有効なもの、異なるサイトに送られるも のも定義できる。また、クッキーにパスワードを埋め込むという幼稚で危険な 実装も時々見受けられる。
セッションは、認証とは別である。同じ人が複数のセッションを実行すること もある。
協調して動作しているプログラムの間で、ある一連の作業を識別するための数 を意味する。
RPC、HTTP(WWW)で使われる。
WWW ブラウザや telnet を使って得た結果と /var/www/htdocs/ 以下のファイ ルが同一であることをwww に ssh でログインして確認しなさい。
[azalea59:~] yas% ssh www
Last login: Tue Jun 17 15:03:11 2009 from sharon.hlla.is.
Welcome to Darwin!
[orchid-nwd:~] yas% cd /var/www/htdocs/
[orchid-nwd:/var/www/htdocs] yas% ls -l index.html
-rw-rw-r-- 1 kawasima c-kikaku 3737 Apr 16 11:49 index.html
[orchid-nwd:/var/www/htdocs] yas% lv index.html
[orchid-nwd:/var/www/htdocs] yas%
同様に https でアクセスできるページについても調べなさい。 トップのディレクトリは、/var/www/secure_htdocs/ 以下にある。
/var/log/httpd/www.coins80/access_log.*
というファイルにためられる。
これを、次のようにして観察しなさい。
% cd /var/log/httpd/www.coins80
% ls access_log.* | tail -1
access_log.200906181117
% ls -t access_log.* | head -1
access_log.200906181117
%
この例では、access_log.200906181117
が最新のものである。
% tail -f access_log.200906181117
http://www.coins.tsukuba.ac.jp/
や自分の WWW ページを開く。
次の CGI プログラムに対して、フォームを定義し、何かデータを送ってみなさい。
http://www.coins.tsukuba.ac.jp/~syspro/2009/No9_files/cgi-printarg.cgi
ヒント:
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 を返す。 }
% setenv REQUEST_METHOD GET
% setenv QUERY_STRING 'arg1=10&arg2=10'
% ./a.out
%
一度行った setenv の結果は残っている。
% mkdir ~/public_html/syspro-cgi-examples/
% cp a.out ~/public_html/syspro-cgi-examples/calc.cgi
% cal
June 2009
S M Tu W Th F S
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() を用いてはならない。
ヒント:
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 を返す。 }
% setenv REQUEST_METHOD GET
% setenv QUERY_STRING 'year=2009&month=6'
% ./a.out
%
一度行った setenv の結果は残っている。
% mkdir ~/public_html/syspro-cgi-examples/
% cp a.out ~/public_html/syspro-cgi-examples/show-cal.cgi
注意: CGI のパラメタは、最初は、文字列で受け取り、最終的に cal コマンドには文 字列で渡す。この時に、年月日としては不適切なものがクライアントから渡さ れる可能性がある。不適切なものが渡された場合には、エラー・メッセージを 表示しなさい。また、cal コマンドは実行してはならない。
CGI プログラムが、どのユーザの権限で動作しているかを調べるプログラムをつくりなさい。
ヒント:CGI のプログラムから id コマンドを実行させる。
ヒント:
main() { ファイルから整数を読み出す(ファイルがなければ0とする)。 1 加える。 標準出力(画面)に整数値を表示する。 ファイルに整数を保存する。 }
Content-Type:
は、text/plain
にする。
Content-Type:
を text/html
にする方法もある。
ただし、本文は、HTML として作成する必要がある。
~/public_html
以下にコピーされるこ
とを想定して、整数を保存するファイルを修正する。
必要ならば、初期状態のファイルを作成して、環境に合わせて
モードを適切なものにする(常に chmod 666 は誤り)。
.cgi
として、~/public_html
以下にコピーする。
ロックがきちんと動作していることを示しなさい。そのためには、ロックして getchar() 等でキーボードからの指示で(アンロックして)終了するプログラム を作成する方法がある。プログラムが終了すると自動的にアンロックされる。
注意:
永続的なハッシュ表を作成する方法として、dbm ライブラリを用いる方法があ る。これを利用してみなさい。ライブラリ関数としては、dbm_open(), dbm_store(), dbm_fetch(), dbm_delete(), dbm_close()がある。詳しくは、 man dbm を見なさい。