C:CGIによる動的文書生成(C言語) 山口 実靖 インターネット技術特論 C:CGIによる動的文書生成(C言語) 山口 実靖 http://www.ns.kogakuin.ac.jp/~ct13140/inet/
CGI 動的文書生成
課題(ろ) 本学Webサーバに自作CGIプログラムを公開し,そのURLとプログラムをメールにて提出. 提出期限 13日後の23時59分 宛先"ct13140@ns.kogakuin.ac.jp" メールの件名(Subject:)は,必ず“インターネット技術特論課題ろ11"とすること. 途中に空白無し. ダブルクォーテーション不要. メール本文に,「学籍番号,研究科(学部),専攻(学科,コース),研究室,氏名,URL」を記述. 作成したプログラムをメールに添付. 提出期限 13日後の23時59分 質問は早めにMLに!
静的文書 と 動的文書 Webサーバにおける 静的文書 と 動的文書 静的文書 動的文書(生成) 毎回,固定的な内容を返す(表示). 動的文書(生成) 要求時にプログラムを起動し,文書を動的に(そのときに)生成する. 状況に応じて,ブラウザからの入力に応じて,返す内容(表示内容)を変更することが可能.
CGI CGI (Common Gateway Interface) Webサーバ計算機の内部でプログラムを起動し,応答内容(通常はHTML)を動的に生成し,それ(動的生成HTML)をWebブラウザに返す仕組み.
CGI ② プログラム 起動 ① HTTP リクエスト Web サーバ プログラム ③ 実行 ⑤ HTTP 応答 Webブラウザ データ入力 (環境変数 QUERY_STRING または 標準入力) ③ 実行 ⑤ HTTP 応答 ④ 実行結果出力 データ出力 (標準出力)
CGI での データ入力 GETの場合 POSTの場合 とりあえず,保留 環境変数 QUERY_STRING を読み込む. 標準入力 を読み込む. データ長は環境変数 CONTENT_LENGTH より取得. とりあえず,保留
CGI での データ出力 冒頭に "Content-type: text/html[改行][改行]" と記述してから,本文を出力する. 上記は,HTTP ヘッダであり,WebサーバとWebブラウザとの間の制御情報のやりとりに使用される. 実際はより多くの機能があり,上記は必要最低限の出力.文字コード情報など重要性の高いものもある. 詳細は,後述(したい…)
CGI での データ出力 (例0) "<h1>Hello</h1>"という本文を出力し,Webブラウザに渡したかったら, プログラムは "Content-type: text/html\n\n <h1>hello</h1>"と出力する必要がある.
CGI での データ出力 (例1) プログラムが, "Content-type: text/html\n\n <h2>HelloWorld</h2>" と出力した場合, Webブラウザには,"<h2>HelloWorld</h2>" と渡される. 正確には,(HTTP1.1使用時は)上記のHTTPヘッダもWebブラウザに渡される. ただし,Webブラウザは,それを(ユーザに対して)表示しない.
CGI での データ出力 (例2) プログラムが, "<h2>HelloWorld</h2>" と出力した場合, エラーとなる.
CGI プログラムの設置 以下は,工学院大学のWebサーバのルール ファイル名は,"*.cgi"である. 実行権限は "755" にしておく. Webサーバのポリシー (工学院大学の例) ファイル名が"~.html"であったら, → そのファイルを読み込んで,Webブラウザに返す. ファイル名が"~.cgi"であったら, → そのファイルを実行ファイルとして実行し, その出力(標準出力)をWebブラウザに返す. ファイル名が"~.cgi"だが,実行権限がなかったら, → 実行しようとして,実行できず,エラー
工学院大学のWebサーバの設定 ホスト www.ns.kogakuin.ac.jp の /usr/local/apache/conf/httpd.conf というファイルに記述されている.
最初のプログラム
000.c #include <stdio.h> void main(){ printf("Content-type: text/html\n\n"); printf("hello\n"); } 実行結果 Content-type: text/html hello 注意!! 通常,CGIプログラムはC言語では作りません. C言語は,Webアプリケーション開発に不向きです.
CGI プログラムの設置 ct13140@green[101]:pwd /home/ct13140/public_html/cgi/C ct13140@green[102]:cc 000.c -o 000.cgi ct13140@green[103]:ls -alF 000.cgi -rwxr-xr-x 1 ct13140 user (略) 000.cgi* ct13140@green[104]:./000.cgi Content-type: text/html hello ct13140@green[105]:
CGI プログラムの設置 成功 Webブラウザでアクセス ↓ 「hello」と表示される. http://www.ns.kogakuin.ac.jp/~ct13140/cgi/C/000.cgi ↓ 「hello」と表示される. 成功
001.c (ソース) #include <stdio.h> void main(){ int i; printf("Content-type: text/html\n\n"); for(i=0; i<10; i++){ printf("%d<br>\n", i); }
001.c (サーバ上で実行) Content-type: text/html 0<br> 1<br>
001.cgiの設置 成功 Webサーバにて Webブラウザでアクセス cc 001.c -o 001.cgi http://www.ns.kogakuin.ac.jp/~ct13140/cgi/C/001.cgi 0<br> 1<br> 2<br> 3<br> 4<br> 5<br> 6<br> 7<br> 8<br> 9<br> 1 2 3 4 5 6 7 8 9 ソースを表示 成功 Webブラウザ
002.c (ソース) #include <stdio.h> #include <time.h> int main(void) { time_t tim; struct tm *tm_tim; char *dow[] = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"}; time(&tim); tm_tim = localtime(&tim); printf("Content-type: text/html\n\n"); printf("%d/", tm_tim->tm_year+1900); printf("%d/",tm_tim->tm_mon+1); printf("%d",tm_tim->tm_mday); printf("(%s) ",dow[tm_tim->tm_wday]); printf("%02d:",tm_tim->tm_hour); printf("%02d:",tm_tim->tm_min); printf("%02d\n",tm_tim->tm_sec); }
002.c (サーバ上で実行) Content-type: text/html 2010/10/12(Tue) 14:40:01
002.cgiの設置 成功 Webサーバにて Webブラウザでアクセス cc 002.c -o 002.cgi http://www.ns.kogakuin.ac.jp/~ct13140/cgi/C/002.cgi 成功 2010/9/8(Tue) 14:40:01 ソースを表示 2010/9/8(Tue) 14:40:01 Webブラウザ
003.c (ソース) #include <stdio.h> int main(void) { printf("Content-type: text/html\n\n"); srand(time(NULL)); printf("rand=%d\n", rand()); }
サーバ内のファイルを読み込む CGIプログラム
004.c (ソース) "004.txt"を読み込んで, その内容を出力するプログラム. #include <stdio.h> int main(void) { FILE *fp; char txt[1024]; printf("Content-type: text/html\n\n"); fp = fopen("004.txt","rt"); // ファイル"004.txt"を読み込み形式でopen if( fp == NULL ){ // もし,open失敗なら. printf("<h1>File can not open!</h1>\n"); // エラーを表示して exit(0); // 終了 } printf("File open ok.<br>\n"); // open成功した事を表示. fread( txt, 1024, 1, fp); // ファイルから1024バイト読み込む txt[1023] = '\0'; // 配列の最後を終端記号にしておく. printf("file --> [%s]<br>\n\n", txt); // 読み込み内容を表示. fclose(fp); // ファイルをclose "004.txt"を読み込んで, その内容を出力するプログラム.
実行(A) : サーバ上で実行 ct13140@green[101]:ls -l 004.txt -rw-r--r-- 1 ct13140 user 6 Sep 11 12:34 004.txt ct13140@green[102]:cat 004.txt hello ct13140@green[103]:./004.cgi Content-type: text/html File open ok.<br> file --> [hello ]<br> ct13140@green[104]: "004.txt"の内容を包含する 出力が得られた.
実行(A) : Browserで閲覧 http://www.ns.kogakuin.ac.jp/~ct13140/cgi/C/004.cgi Webブラウザ上の表示 File open ok. file --> [hello ] HTMLソース File open ok.<br> file --> [hello ]<br> "004.txt"の内容を包含する 出力が得られた.
実行(B) : サーバ上で実行 ct13140@green[101]:ls -l 004.txt -rw-r--r-- 1 ct13140 user 15 Sep 11 12:35 004.txt ct13140@green[102]:cat 004.txt hello world ct13140@green[103]:./004.cgi Content-type: text/html File open ok.<br> file --> [hello world ]<br> "004.txt"の内容を変更して 再実行すると.
実行(B) : Browserで閲覧 http://www.ns.kogakuin.ac.jp/~ct13140/cgi/C/004.cgi Webブラウザ上の表示 File open ok. file --> [hello world ] HTMLソース File open ok.<br> file --> [hello world ]<br> "004.txt"の内容を変更して 再実行すると.
実行(C) : サーバ上で実行 ct13140@green[101]:chmod 600 004.txt ct13140@green[102]:ls -l 004.txt -rw------- 1 ct13140 user 6 Sep 11 12:34 004.txt ct13140@green[103]:cat 004.txt hello ct13140@green[104]:./004.cgi Content-type: text/html File open ok.<br> file --> [hello ]<br> ct13140@green[105]: "004.txt"のパーミッションが600. owner→read可 group→read不可 other→read不可 自分(owner)はread可能なので, "004.txt"の内容は読み込まれた.
実行(C) : Browserで閲覧 http://www.ns.kogakuin.ac.jp/~ct13140/cgi/004.cgi Webブラウザ上の表示 File can not open! HTMLソース <h1>File can not open!</h1> Webサーバプログラム(apache httpd)は,user WWW, group WWW の権限で動いている. "004.txt"のパーミッションが600. owner→read可 group→read不可 other→read不可 ←このルールが適用される Webサーバ(other)が004.cgiを動かし,004.cgiが004.txtを読み込もうとするが,読み込み権限がなく"読めなかった".
Webサーバ, httpd, apache 工学院大学にはApacheというWebサーバ(httpd)がインストールされている. httpdは,User "www",Group "www"の権限にて動作する. CGIプログラムがファイルにアクセスするには,otherに対するパーミッションが"許可"に成っていなくてはならない.
CGI と 静的HTML CGI(などのサーバサイドプログラムは) サーバ内でプログラムが動作する 結果として 毎回出力を変えることができる. 接続相手により出力を変えることができる. サーバ内のファイルの読み書きができる. サーバ内のRDBMSに接続することができる. 特に書き込みが重要.ユーザの情報を残しておける.
アクセスカウンター
005.c (ソース) 1 #include <stdio.h> 2 #include <stdlib.h> 3 void main(void) { 4 int cnt; 5 FILE *fp; 6 printf("Content-type: text/html\n"); 7 printf("\n"); 8 fp = fopen("005.txt", "rt"); 9 if( fp == NULL ){ 10 cnt == 0; 11 printf("file read NG.<br>\n"); 12 } else { 13 char buf[1024]; 14 fread( buf, 1, 1024, fp); 15 fclose(fp); 16 cnt = atoi(buf); 17 printf("file read OK.<br>\n"); 18 } 19 printf("<h1>%d</h1>\n", cnt); 20 cnt ++;
005.c (ソース) 21 fp = fopen("005.txt","wt"); 22 if( fp == NULL ){ 23 printf("file write NG.<br>\n"); 24 } else { 25 fprintf(fp, "%d", cnt); 26 printf("file write OK.<br>\n"); 27 fclose(fp); 28 } 29 }
データファイルのパーミッション ct13140@green[111]:ls -l 005.txt -rw-rw-rw- 1 ct13140 user 2 Sep 11 12:34 005.txt Q.ファイルのパーミッションを –rw-rw-rw- にするには? A.chmod 666 005.txt Q.赤字の部分(other)が重要なら,chmod 606 005.txt で -rw----rw- にしても良いのか? A.良い.むしろ,その方が好ましい.
実行(A) : サーバ上で実行 ct13140@green[334]:./005.cgi Content-type: text/html file read OK.<br> <h1>28</h1> file write OK.<br>
実行(B) : Browserで閲覧 http://www.ns.kogakuin.ac.jp/~ct13140/cgi/C/005.cgi Webブラウザ上の表示 file read OK. 29 file write OK. HTMLソース file read OK.<br> <h1>29</h1> file write OK.<br>
実行(B’): Browserで閲覧 http://www.ns.kogakuin.ac.jp/~ct13140/cgi/C/005.cgi 再読み込みすると... Webブラウザ上の表示 file read OK. 30 file write OK. HTMLソース file read OK.<br> <h1>30</h1> file write OK.<br>
数値を画像で表示するカウンタ 簡単なトリック(?)で実現可能. "0.gif", "1.gif", "2.gif", ... , "9.gif" を用意しておく. アクセス数が123なら, <img src="1.gif"> <img src="2.gif"> <img src="3.gif"> と出力すれば, ブラウザが上記ファイルを表示してくれる.
005_.c int i; char temp[1024]; sprintf( temp, "%d", cnt); for(i=0; i<strlen(temp); i++){ printf("<img src=\"%c.gif\">", temp[i]); } "005.c"の19行目の printf("<h1>%d</h1>\n", cnt); の代わりに上記を 書いておけばよい.
実行(A) : Browserで閲覧 http://www.ns.kogakuin.ac.jp/~ct13140/cgi/C/005_.cgi Webブラウザ上の表示 HTMLソース file read OK.<br> <img src="1.gif"><img src="2.gif"><img src="3.gif"><br> file write OK.<br>
ファイルのロック 本当は,ファイルをロックすべき 複数のユーザが同時に読み込むとデータが壊れる. Unix には ファイルロック機能がある. ただし、通常はRDBMSを用いる.「ファイルへの書き込み」や「ファイルからの読み込み」は行わない. 本講義では当面は放置.
ファイルのロック (幸運なとき) 56 57 CGIプログラムA cnt は 56. ↓Cntを1増やす cnt は 57. ファイル read open data read cnt は 56. close ↓Cntを1増やす cnt は 57. write open data write 57
ファイルのロック (不運なとき) 56 57 1 CGIプログラムA CGIプログラムB cnt は 56. ↓Cntを1増やす read open data read cnt は 56. close ↓Cntを1増やす cnt は 57. write open read open data read data write cnt は 0? close 57 close ↓Cntを1増やす cnt は 1? write open data write 1 close
環境変数を取得する
環境変数 CGIプログラムは,各種環境変数の値を調査することにより多くの情報を得ることが可能. 接続クライアント情報,サーバ情報など 環境変数はgetenv(環境変数名)で取得可能. 例 env = getenv( "REMOTE_ADDR" ); if( env != NULL ){ printf("REMOTE_ADDR : %s<br>\n", env); }
CGIの環境変数 RVER_SOFTWARE SERVER_NAME GATEWAY_INTERFACE SERVER_PROTOCOL SERVER_PORT REQUEST_METHOD PATH_INFO PATH_TRANSLATED SCRIPT_NAME QUERY_STRING REMOTE_HOST REMOTE_ADDR AUTH_TYPE REMOTE_USER REMOTE_IDENT CONTENT_TYPE CONTENT_LENGTH HTTP_ACCEPT HTTP_USER_AGENT 詳しくは、CGIの仕様書↓を! http://hoohoo.ncsa.illinois.edu/cgi/env.html
006.c (ソース) 接続してきたクライアントのIPアドレス情報 "REMOTE_ADDR"の内容を表示するプログラム. #include <stdio.h> #include <stdlib.h> void main(){ char *env; printf("Content-Type: text/html\n\n"); env = getenv( "REMOTE_ADDR" ); if( env != NULL ){ printf("REMOTE_ADDR : %s<br>\n", env); } else { printf("REMOTE_ADDR : NULL<br>\n"); } 接続してきたクライアントのIPアドレス情報 "REMOTE_ADDR"の内容を表示するプログラム. 接続元により表示内容を変えるプログラムも作成可能.
Browserからの入力を処理する
Browser から CGIプログラムへの データの流れ Web サーバ ② プログラム 起動 プログラム ① GET または POST Webブラウザ データ入力 (環境変数 QUERY_STRING または 標準入力) ③ 実行 ⑤ HTTP 応答 ④ 実行結果出力 データ出力 (標準出力)
データ入力可能なHTML ↑Browserでの表示 <form action="007.cgi" method="GET"> A:<input type="text" name="aa"><br> B:<input type="text" name="bb"><br> <input type="submit" value="OK"> <input type="reset" value="Reset"> </form> ユーザがここに入力したデータは, 「”aa”の値」として, サーバに送信される. ユーザがここに入力したデータは, 「”bb”の値」として, サーバに送信される. ↑Browserでの表示
データ入力可能なHTML <form action="007.cgi" method="GET"> </form> A:<input type="text" name="aa"><br> B:<input type="text" name="bb"><br> <input type="submit" value="OK"> <input type="reset" value="Reset"> </form> 上記のHTMLの赤字部の意味. <form>から</form>で受け付けた入力データを, 007.cgi に送信する.送信方法は”GET”である. (送信方法には,GETとPOSTがある)
データ入力可能なHTML <form action="007.cgi" method="GET"> A:<input type="text" name="aa"><br> B:<input type="text" name="bb"><br> <input type="submit" value="OK"> <input type="reset" value="Reset"> </form> 上記のHTMLの色文字の意味. “text”形式の入力をユーザから受け付ける. その入力データは”aa”と名がついてサーバに送られる. その入力データは”bb”と名がついてサーバに送られる.
データ入力可能なHTML <form action="007.cgi" method="GET"> A:<input type="text" name="aa"><br> B:<input type="text" name="bb"><br> <input type="submit" value="OK"> <input type="reset" value="Reset"> </form> 上記のHTMLの色文字の意味. “OK”の文字が記された「送信ボタン」が表示される. 「送信ボタン」とは,そのボタンを押したら入力データが サーバに送られるボタンのこと. “Reset”の文字が記された「Resetボタン」が表示される. 「Resetボタン」とは,そのボタンを押したら入力データが クリアさせるボタンのこと.
データ送信手法 GET , POST 送信方法は GET と POST の2種類がある. どちらを用いても,実現できることはほぼ同一. 詳細は後述.
Browserからの入力を 受け取るCGIプログラム 環境変数REQUEST_METHODに送信手法が"GET"または"POST"として格納される. データ送信手法が“GET”の場合 環境変数”QUERY_STRING” に(すべての)情報が格納されているので,それを読み込み解析する. データ送信手法が“POST”の場合 標準入力より転送データを読み込むことが可能. データ長(バイト数)は環境変数CONTENT_LENGTHに格納されている.
余談 バッファーオーバーフローに気をつけましょう. 例 int x[100]; と確保した場合は, x[0]~x[99]しか使用してはなりません. 当然なら x[-1]やx[100]やx[120]はNG. 特にStack上に確保した配列は危険. 配列のすぐ近くにreturn addressがあります. return addressを悪意あるユーザに操作されてしまうと,PCを任意の場所にとばされてしまいます.
Browserからの入力を 受け取るCGIプログラム #define BUFSIZE 1024 #include <stdio.h> #include <stdlib.h> #include <string.h> int main(void) { char *env, buf[BUFSIZE]; printf("Content-type: text/html\n"); printf("\n"); printf("<a href=\"007.html\">back</a><br>\n"); env = getenv( "REQUEST_METHOD" ); if( env == NULL ){ printf("(?_?) %s:%d\n", __FILE__, __LINE__); exit(1); }
Browserからの入力を 受け取るCGIプログラム if( ! strcmp(env,"POST") ){ int len; printf("POST<br>\n"); len = fread(buf, 1, BUFSIZE-1, stdin); buf[len] = '\0'; printf("[%s]<br>\n", buf); } else { printf("GET<br>\n"); env = getenv( "QUERY_STRING" ); if( env != NULL){ printf("[%s]<br>\n", env); printf("QUERY_STRING is NULL.<br>\n"); }
GET による送信例 A: Hoge B: Fuga GET [aa=Hoge&bb=Fuga] ユーザが左の様に入力し, OK(submit)を押すと, A: Hoge B: Fuga GET [aa=Hoge&bb=Fuga] http://www.ns.kogakuin.ac.jp/~ct13140/ cgi/C/007.cgi?aa=Hoge&bb=Fuga
GET による送信例 GET [aa=Hoge&bb=Piyo] GET [aa=Bar&bb=Foo] http://www.ns.kogakuin.ac.jp/~ct13140/ cgi/C/007.cgi?aa=Hoge&bb=Piyo URL欄に上の様に入力し,接続すると、 http://www.ns.kogakuin.ac.jp/ ~ct13140/cgi/C/ 007.cgi?aa=Bar&bb=Foo GET [aa=Hoge&bb=Piyo] GET [aa=Bar&bb=Foo]
データ受信に関する仕様 (GET) GETの場合. 環境変数REQUEST_METHODに"GET"が格納されている. 環境変数QUERY_STRINGに "nameA=valueA&nameB=valueB&nameC=valueC…"のフォーマットでデータ名とデータが格納さている. ただし,"データ名"はHTMLのフォームのnameで指定したデータ名
データ受信の方法 (GET) GETの場合. 環境変数REQUEST_METHODを調査. もし,"GET"であったら,以下を行う. "POST"であった場合は後述. (1) 環境変数QUERY_STRINGに格納されている文字列を読み込む. (2) 文字列を"&"で分割する. (3) それぞれを"="で分割して,入力されたデータを取得する.
データ受信の方法 (GET) (1) QUERY_STRINGを読み込む. (2) 文字列を"&"で分割. (3) それぞれを"="で分割 "aa=Hoge&bb=Piyo"であった. (2) 文字列を"&"で分割. "aa=Hoge"と"bb=Piyo" (3) それぞれを"="で分割 "aa"="Hoge" "bb"="Piyo"
POST による送信例 A: Hoge B: Fuga POST [aa=Hoge&bb=Fuga] ユーザが左の様に入力し, OK(submit)を押すと, A: Hoge B: Fuga POST [aa=Hoge&bb=Fuga] 後ろに何も付いていない http://www.ns.kogakuin.ac.jp/~ct13140/cgi/C/007.cgi
データ受信に関する仕様 (POST) POSTの場合. 環境変数REQUEST_METHODに"POST"が格納されている. データは標準入力より読込可能.データのバイト数は環境変数CONTENT_LENGTHに設定されている. "nameA=valueA&nameB=valueB&nameC=valueC…"のフォーマットでデータ名とデータが格納さている. これは"GET"と同様である.
データ受信の方法 (POST) POSTの場合. 環境変数REQUEST_METHODを調査. もし,"POST"であったら,以下を行う. (1) 標準入力よりCONTENT_LENGTHバイト読み込む. ただし,サンプルは読込量はバッファサイズにしている. (2) 文字列を"&"で分割する. (3) それぞれを"="で分割して,入力されたデータを取得する.
特殊記号,日本語のデータの処理 A: さね B: やす GET [aa=%82%B3%82%CB&bb=%82%E2%82%B7] ユーザが上の様に入力し, OK(submit)を押すと, GET [aa=%82%B3%82%CB&bb=%82%E2%82%B7] http://www.ns.kogakuin.ac.jp/~ct13140/ cgi/C/007.cgi?aa=%82%B3%82%CB&bb=%82%E2%82%B7
aa=%82%B3%82%CB&bb=%82%E2%82%B7 2バイト文字は,そのまま転送されず"%文字コード"の形式で転送される. ただし,文字コードは16進数表記 バイナリエディタ Bz http://www.zob.ne.jp/~c.mos/soft/bz.html
特殊記号,日本語のデータの処理 A: abc#d?e f<g&h=i+j B: GET ユーザが上の様に入力し, OK(submit)を押すと, GET [aa=abc%23d%3Fe+f%3Cg%26h%3Di%2Bj&bb=] http://www.ns.kogakuin.ac.jp/~ct13140/ cgi/C/007.cgi?aa=abc%23d%3Fe+f<g%26h%3Di&bb=
特殊記号,日本語のデータの処理 "半角スペース"は"+"に変換される 特殊文字,文字コード31以下の文字,文字コード128以上の文字は,"%文字コード"に変換される. 日本語文字の処理方法は,当面保留.