Comments
Description
Transcript
CGIの基礎の基礎 - Hiroshi Watanabe Home Page
平成 20 年 12 月 19 日 CGI の基礎の基礎 渡辺宙志 東京大学情報基盤センター 概要 CGI の動作の仕組みから、実際に CGI を作成するためのコツまで簡単に紹介する。 目次 1 はじめに 1.1 CGI とは . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2 2 2 サーバの設定 2 3 はじめての CGI 4 5 3.1 3.2 CGI の仕組み . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2 2 3 3.3 3.4 ファイル読み込み . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . CGI の実行権限 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3 5 データのやりとり 4.1 簡単な例 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4.2 4.3 4.4 入力フォームの作り方 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7 フォームの動作 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . URL デコード . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 本格的な CGI を作るために 5.1 5.2 6 実行してみる . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5 5 6 7 7 9 クロスサイトスクリプティング . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9 10 CGI ノウハウ集 6.1 セッション管理 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6.2 文字コード . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11 12 12 6.3 6.4 ファイルロック . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12 12 6.5 6.6 MVC 分離 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . CGI のデバッグ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12 13 実践 CGI データファイル隠蔽 おわりに 13 1 はじめに 1 CGI とは 1.1 CGI(Common Gateway Interface) とは、ブラウザからの要求でプログラムを実行し、その結果を表示 するための仕組みである。通常は、あらかじめ用意された文書を表示するだけであったのに対し、CGI は ユーザーからの要求に対応して動的にウェブページを作成して表示することができる。インターネットの 掲示板などが CGI の典型例である。CGI はサーバ上で動作するものであればどんな言語で実装されても 良いが、実際にはテキスト処理に長けた Perl などのスクリプト言語で書かれていることが多い。本稿では Ruby を用いた CGI の作り方について簡単に解説する。なお、HTML と Ruby の基礎的な知識は既知と する。 サーバの設定 2 CGI の実行にはサーバの設定が必要である。インターネットでは、ブラウザ (クライアント) からの要求に サーバがデータを返すことによってウェブサイトが閲覧できる。この通信には HTTP(Hypertext Transfer Protocol) というプロトコルが用いられる。ウェブサーバの実体は httpd と呼ばれる、バックグランドで 動作するプロセスであり、ほとんどの UNIX 系 OS において Apache と呼ばれるウェブサーバが用いられ ている。以下では Apache の設定について説明する。 Apache の設定ファイルは/etc/httpd/conf/httpd.conf である1 。ここで、拡張子が cgi なら cgi-script であることをサーバに教えるため、以下の行を付け加える2 。 ¨ AddHandler cgi-script .cgi § ¥ ¦ また、サーバに「このディレクトリでは cgi の実行を許す」という設定も必要となる。もし各自の pub- lic html 以下すべてで CGI の実行を許すなら、 ¨ <Directory /home/*/public_html> AllowOverride All Options MultiViews SymLinksIfOwnerMatch ExecCGI </Directory> § ¥ ¦ などのように Directory ディレクティブに ExecCGI オプションをつければ良い。セキュリティ上、CGI が 実行されるディレクトリを制限したい場合は、実行可能場所を public html 以下の cgi-bin ディレクトリだ けに限ることも良く行われる。その場合は以下のように設定する。 ¨ <Directory /home/*/public_html> AllowOverride All Options MultiViews SymLinksIfOwnerMatch </Directory> <Directory /home/*/public_html/cgi-bin> Options ExecCGI </Directory> § ¥ 設定が終わったら、httpd を再起動する。 ¨ $ sudo /etc/init.d/httpd restart § ¥ ¦ これで CGI を実行するための準備ができた。 はじめての CGI 3 3.1 ¦ 実行してみる まず、以下のようなスクリプトを public html 下の適当な場所に用意しよう。 1 環境によって若干場所が異なる。たとえば/usr/local/apache2/である可能性もある。 2 多くの場合コメントアウトされているので、そのコメントを外せばよい 2 List 1: Hello cgi ¨ #!/usr/bin/ruby ¥ print "Content-type: text/html\n\n"; print "<html><body><H1>Hello CGI </H1></body></html>\n"; § ¦ このスクリプトを hello.cgi などの名前をつけて保存し、パーミッションを 755 とする。 まず、コマンドライン上で実行できることを確認しよう。 ¨ $ ./hello.cgi Content-type: text/html <html><body><H1>Hello CGI </H1></body></html> § ¥ ¦ という結果が返ってくるはずである。 では実際に CGI として実行させてみよう。public html の直下にあれば、このファイルの URL は、 http://server/~username/hello.cgi になるはずである。これを表示させてみて、大きな文字で Hello CGI と表示されれば成功である。 3.2 CGI の仕組み 先ほどの CGI で実際に何が起きているか簡単に説明しよう。まず、ブラウザから http://server~/username/hello.cgi というファイルの表示要求がサーバに送られる。サーバは、拡張子が CGI であること、そのファイルの 場所に ExecCGI オプションが有効であることを確認し、その CGI を実行可能ファイルとして実行する。 CGI は最初に「Content-type: text/html(改行2回)」と出力し、以後の出力がテキスト形式であり、か つ HTML として処理すべき内容であることをブラウザに伝える。ブラウザはその内容を受け取り、HTML として処理することで、あたかも hello.cgi の内容が ¨ <html><body><H1>Hello CGI </H1></body></html> § ¥ ¦ であったかのように表示する。 要するに、最初に「Content-type: text/html\n\n」と表示し、その後 HTML を表示するスクリプ トを書けば CGI となる。なお、CGI の実行結果としてテキスト以外を返すこともできる。たとえば 「Content-type: image/gif\n\n」を出力すれば、その後のデータは gif ファイルとして処理される。ホー ムページのカウンタなどはこうやって作られている。 3.3 ファイル読み込み 標準出力に HTML さえ出力されれば、(パーミッションやライブラリが許す限り) CGI 内部でどのよう な処理を行っていても良い。たとえば、for 文を使った例を以下に示す。 List 2: for 文を使った例 ¨ #!/usr/bin/ruby ¥ print "Content-type: text/html\n\n"; print "<html><body>\n"; print "<ul>\n" for i in 1..10 print "<li>" + i.to_s + "\n"; end print "</ul>\n" print "</body></html>\n"; § ¦ 3 もう少し実用的な例として、データファイルを読み込んで整形して表示するスクリプトを作ってみよう。 与えるデータは以下のような csv ファイルとする。 ¨ apple,100,5 banana,150,15 orange,120,8 § ¥ ¦ それぞれ品目、単価、個数を表す。ファイル名は data.csv としておく。これを読み込み、品目ごとの小計 と、全体の合計を出力するスクリプトは以下の通りである。 List 3: ファイル読み込みの例 ¨ #!/usr/bin/ruby ¥ print "Content-type: text/html\n\n" print "<html><body>\n" print "<table border=1>" print "<tr><td>name</td><td>price</td><td>quantity</td><td>subtotal</td></tr>\n" f = open("data.csv","r") total = 0 f.each{|line| line.chomp! a = line.split(/\s*\,\s*/) name = a[0] price = a[1] num = a[2] subtotal = a[1].to_i * a[2].to_i total+= subtotal print "<tr>" print "<td>" + name + "</td>" print "<td>" + price + "</td>" print "<td>" + num + "</td>" print "<td>" + subtotal.to_s + "</td>" print "</tr>\n" } print "</table>\n" print "Total = " + total.to_s print "</body></html>\n" § ¦ いくつか注意点があるので列挙しておこう。ファイルを一行ずつ読み込む時、一緒に改行コードも読み 込まれる。したがって、 ¨ a = line.split(/\s*\,\s*/) § ¥ ¦ としてカンマで区切って配列に読み込むとき、このままでは最後のデータ a[2] には、5\n というデータが 読み込まれてしまう。そこで、ファイルを一行ずつ読み込む際に、line.chomp!によって行末の改行コー ドを削除してある。 chomp をしなくても今回の例ではブラウザの表示自体には問題が無いが、実際にコンソールで出力を見 ると ¨ % ./data.cgi Content-type: text/html ¥ <html><body> <table border=1><tr><td>name</td><td>price</td><td>quantity</td><td>subtotal</td></tr > <tr><td>apple</td><td>100</td><td>5 </td><td>500</td></tr> <tr><td>banana</td><td>150</td><td>15 </td><td>2250</td></tr> <tr><td>orange</td><td>120</td><td>8 4 </td><td>960</td></tr> </table> Total = 3710</body></html> § ¦ と、三つめのデータの改行がそのまま読み込まれていることがわかるだろう。これはデータの保存などで 問題を起こすことが多いので注意しておきたい。 3.4 CGI の実行権限 CGI の実行権限についても触れておこう。ブラウザから閲覧要求があったとき、サーバは CGI を実行 し、その結果をブラウザに返す。このとき、「CGI を誰が実行したのか」ということが問題になる。これ はサーバの設定によって異なり、それによってファイルに設定すべきパーミッションも異なる。 CGI の実行権限には、大きく分けて「nobody3 」「各ユーザ」の二種類の方法がある。nobody 権限で動 く場合には、nobody というユーザが実行できるようにパーミッションを空ける必要がある。たとえば CGI の実行ファイルなら、作ったユーザと nobody はユーザもグループも異なるため、other に executable 権 限を付与しなければならない。 各ユーザ権限で実行される場合は、~/user1/以下で実行される CGI は user1 権限で、~/user2/以下で 実行されるなら user2 権限で実行される。この場合は、ユーザ以外にパーミッションを空ける必要は無い ので、CGI ファイルは 700、すなわち group にも other にもなんら権限を付与する必要はない。 実際にどちらで動いているかを確認するには、以下の CGI を実行すればよい。 List 4: 実行権限の確認 ¨ #!/usr/bin/ruby require ’etc’ print "Content-type: text/plain\n\n" p Etc.getpwuid[0] § ¥ ¦ 実行結果がユーザ名になればユーザ権限で、nobody になれば nobody 権限で実行されている。 なお、いずれの場合にも group や other に writable(書き込み可能) 権限がついている場合には、他人に 改変される危険性があるため、セキュリティ上の問題から CGI は実行されない4 。 4 4.1 データのやりとり 簡単な例 CGI の魅力はユーザからの要求にこたえて動的にページを作ることにある。そのためには、CGI とユー ザの間でなんらかの形でデータのやり取りが必要になる。ユーザから CGI にデータを渡すには、フォー ムと呼ばれる特別な HTML を記述する。フォームのデータの送信の方法を METHOD と呼び、「POST」 と「GET」の二種類が存在する。 まずは簡単な例から見てみよう。以下のような HTML を用意する。 ¨ <H2>GET で送る</H2> <form method="GET" action="./form.cgi"> <input name="name" size=30> <input type="submit" value ="submit"> </form> <H2>POST で送る</H2> <form method="POST" action="./form.cgi"> <input name="name" size=30> 3 nobody 以外にも httpd や apache、www といったユーザ名が良く使われる。いずれもセキュリティの観点からログインシェ ルが与えられていないのが普通である。 4 つまり「とりあえずパーミッションを全部あけておけば動くだろう」と 777 にしたら動かないことになる。初心者が良くやる ミスである。 5 ¥ <input type="submit" value ="submit"> </form> § ¦ ブラウザで見ればこんな外見になるはずである。 また、同じディレクトリに以下のような CGI を書いて保存しておく。 List 5: フォームデータの表示 ¨ #!/usr/bin/ruby ¥ print "Content-type: text/html\n\n" print "<html><body>" method = ENV[’REQUEST_METHOD’] if method == ’GET’ print "METHOD = GET: " + ENV[’QUERY_STRING’] + "\n"; else print "METHOD = POST: " + gets(nil) + "\n"; end print "</body></html>\n" § ¦ まず、「GET で送る」のほうのテキストフィールドに何か (たとえば hoge) 書いて、submit ボタンを押 してみよう。画面が切り替わり、ブラウザの URL が「form.cgi?name=hoge」となり、かつ画面に ¨ METHOD = GET: name=hoge § ¥ ¦ と書いてあるはずである。 また、「POST で送る」で同様なことをすると、今度は URL には「form.cgi」のみが表示され、画面に ¨ ¥ METHOD = POST: name=hoge § ¦ と表示されるはずである。 4.2 入力フォームの作り方 まず、<form>タグには、method と action を指定する。method は GET もしくは POST を、action には 処理を実行する CGI ファイルを指定する。 <form>から</form>ではさまれた領域に、どのようなデータを入力するかを<input>の type により指 示する。たとえば、以下のような入力フォームが存在する。 • 投稿ボタン ¨ <input type="submit" value ="submit"> § ¥ ¦ 投稿ボタンを作る。投稿内容を確定し、CGI にデータを送る。 • テキストフィールド 6 ¨ <input name="name" size=30> § ¥ ¦ size で指定した長さのテキストフィールドを表示する。 • チェックボックス ¨ <input type="checkbox" name="cb" value="dog">dog § ¥ ¦ チェックボックスを作る。value には、そのチェックボックスが選択されたときの値を指定する。こ の例では、もしチェックされたら cb=dog が送信される。 • ラジオボタン ¨ <input type="radio" name="rb" value="dog">dog § ¥ ¦ ラジオボタンを作る。name で同じ名前を指定されたラジオボタンは、一度に一つしか選択できなく なる。後はチェックボックスと同様。 そのほかにもいろいろあるが、それは後述する。 4.3 フォームの動作 フォームによる CGI の動作について簡単に説明しておこう。フォームにいろいろ書き込まれた状態で submit ボタンを押すと、データはまとめて form の action で指定された CGI に送られる。データは、 name=value という形で送られてくる。複数のデータがある場合には name1=value1&name2=value2 とア ンパサンド (アンドマーク)「&」で区切られる。 メソッドが GET の場合は、form.cgi?name1=value1&name2=value2 と URL に直接書き込まれる5 。その URL の情報は環境変数 QUERY_STRING に入るので、ruby なら ENV[’QUERY_STRING’] で参照できる。また、 URL に直接データを書き込めるのを利用して、たとえば<a href="http://hoge/hoge.cgi?mode=viewall"> すべて見る</a>などのように、CGI にリンクする形で情報を渡すこともできる。 メソッドが POST の場合には、データは標準入力に送られてくるので、URL には何も表示されない。 メソッドがどちらかは、環境変数 REQUEST_METHOD に入っている。また、メソッドが POST の場合には、 環境変数 CONTENT_LENGTH に文字列の長さが入っている。 なお、POST にせよ GET にせよ、データは URL として許される文字列に限られる。したがって、た とえば日本語などは適切な形でエンコードされる。たとえば、先ほどの例で入力フォームに「ほげ」と入 力してみよう。name=%82%D9%82%B0 と表示されるはずである (SJIS なら)。これを URL エンコードと呼 ぶ。URL エンコードでは日本語だけでなく、空白や、一部の記号 (たとえば&や?など) といった ASCII 文 字もエンコードされる。 4.4 URL デコード フォームから入力されたデータは URL エンコードされているため、正しく表示するためにはデコード しなくてはならない。また、複数のデータが入ってきた場合にはアンドマークでつなげられているため、 これも切り分けなければならない。しかし、デコード実装はいろいろ面倒である。そこで、ruby にデフォ ルトで用意されている CGI クラスを利用する。 まず、いろんなデータを入力する例として以下のような HTML を作成してみよう。 List 6: フォームの例 ¨ <html><head><body> 5 たとえば ¥ google 検索がそうなっている。検索後の URL を見よ。 7 <H1>フォームいろいろ</H1> <form method="POST" action="./formetc.cgi"> 名前 <input name="name" size=30><BR> 趣味 <input name="hobby1" type=checkbox value="読書"> 読書 <input name="hobby2" type=checkbox value="映画"> 映画 <input name="hobby3" type=checkbox value="運動"> 運動 <BR> 職業 <input type="radio" name="job" value="医者" checked> 医者 <input type="radio" name="job" value="弁護士"> 弁護士 <input type="radio" name="job" value="その他"> その他 <BR> 地域 <select name="area"> <option value="tokyo">東京</option> <option value="osaka">大阪</option> <option value="nagoya">名古屋</option> </select> <BR> パスワード <input type="password" name="pass" ><BR> <BR> コメント<BR> <textarea name="comment" cols="50" rows="5"> コメントを記入してください。 </textarea> <P> <input type="submit" value ="submit"><input type="reset" value ="reset"> </P> </form></body></html> § ¦ ブラウザで見ればこんな外見になるはずである。 データをすべて出力するスクリプトを CGI クラスを利用して作成した例は以下の通り。 List 7: フォームの例 ¨ #!/usr/bin/ruby ¥ 8 require ’cgi’ print "Content-type: text/html\n\n" print "<html><body>" cgi = CGI.new hash = cgi.params hash.each_key{|key| print key + " = " + hash[key].to_s + "<BR>\n" } print "</body></html>\n" § ¦ 6 まず、cgi = CGI.new により、CGI クラスのインスタンスを作成する 。ここで、内部的にメソッドの違 いなどは CGI クラスが吸収し、かつデータのデコードも済んでいる。デコードされたデータをハッシュと して得るには CGI::params を参照すればよい。hash = cgi.params とすればハッシュとしてデータ得ら れるのであとは好きなように操作ができる。たとえば今回の例でいえば名前は hash[’name’]、パスワー ドは hash[’pass’] により参照できる。 本格的な CGI を作るために 5 前節までの知識で基本的にはどんな CGI を作ることも可能である。しかし、CGI のプログラムノウハ ウは一般のプログラムと異なる。また、全世界に公開する以上、セキュリティの確保も重要である。そこ で、最低限のセキュリティと、実際の CGI を作るためのノウハウについて紹介する。 5.1 クロスサイトスクリプティング CGI で最低限気をつけるべきは、クロスサイトスクリプティング (Cross Site Scripting, XSS) である。 入力されたデータをそのまま表示するような CGI では、HTML タグや JavaScript を混入されることによ り、任意のコードを実行できたり、情報を盗まれたりする可能性がある。 簡単な例で見てみよう。先ほどのフォームの例で、名前のところに<script>alert("alert")</script> と入力して submit してみよう。ブラウザが警告ダイアログを出すはずである。 Javascript はページの内容を任意に書き換えることが可能であり、かつ、ページ表示や移動などのイベ ントの検出もできる。たとえば、先ほどの CGI の例で「コメント」のところに以下のようなスクリプトを 入力してみよう。 ¨ <script type="text/javascript"> window.onload =function(){ document.write("テキスト乗っ取り"); } </script> § ¥ ¦ CGI を実行すると、「テキスト乗っ取り」とだけ表示されるはずである。このようにして、URL は別のド メイン (たとえば銀行) のまま、内容を任意に書き換えることができるため、フィッシング詐欺などに使わ れる。したがって、入力されたデータを何もせずにそのまま表示する CGI を世界に公開することは厳に 慎まなければならない7 。 これは「<」や「>」と言った文字がそのまま表示されるから起きる問題なので、これを防ぐにはそうい う文字列をエスケープしてしまえばよい。CGI クラスには CGI::escapeHTML というメソッドが用意され ているので、それを用いれば「<」や「>」が「<」や「>」に変換されるために問題は生じない。 以下に、クロスサイトスクリプティング対策を施した CGI スクリプトを載せておく。 List 8: エスケープの例 ¨ #!/usr/bin/ruby ¥ 6 ここでは分かりやすさのためにいちいちオブジェクトを作成しているが、Ruby 的には CGI.new.params.each key{ ... と一 行で書いてしまうところである。 7 ここでは例示のためにそのような CGI を公開しているが、.htaccess によりアクセス元を制限してある。 9 require ’cgi’ print "Content-type: text/html\n\n" print "<html><body>" cgi = CGI.new hash = cgi.params hash.each_key{|key| print key + " = " + CGI.escapeHTML(hash[key].to_s) + "<BR>\n" } print "</body></html>\n" § ¦ 単に表示する文字列を CGI::escapeHTML で囲んだだけであるが、これにより CGI のセキュリティは飛 躍的に高まる8 。 5.2 実践 CGI 簡単な掲示板システムを考えよう。掲示板入力を以下の手順で行うことにする。 1. 現在の書き込み状況と、書き込み用フォームを表示する (デフォルト表示)。 2. 書き込みが投稿されたら、投稿内容を確認する。 3. 投稿者が確認したら、実際に書き込みを行う。 ここで、投稿者が投稿ボタンを押すたびにそれまでの情報がクリアされ、毎回新たに CGI が起動される。 したがって、プログラムは自分がいまどのフェーズにいるのかを認識する必要がある。 このような処理するための定石は、mode という隠し変数を作り、その変数の値により処理を変えるこ とである。上記の例なら、mode が指定されていない場合はデフォルト表示、confirm なら確認用の表示、 regist なら書き込みを行うようにする。隠し変数は hidden タイプの input タグを用いる。 以上の例を実装した一行掲示板 CGI を以下に示す。 List 9: 一行掲示板 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 ¨ #!/usr/bin/ruby require ’cgi’ FILENAME="log.dat" ¥ def view print <<EOS <hr> <form action="./bbs.cgi" method="POST"> 名前 <input name="name" size="10"><BR> コメント <input name="comment" size="50"><BR> <input type="submit" value="submit"> <input type="reset" value="reset"> <input type="hidden" name="mode" value="confirm"> </form> <hr> EOS f = open(FILENAME,"r") print "<ul>\n" f.readlines.reverse.each{|line| line.chomp! a = line.split(/<>/) name = a[0] comment = a[1] print "<li>" + name + "さんのコメント「" + comment + "」\n" } print "</ul>\n" end 8 というか、クロスサイトスクリプティング対策を施していない 10 CGI は公害である。 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 def confirm(cgi) name = CGI.escapeHTML(cgi.params[’name’][0]) comment = CGI.escapeHTML(cgi.params[’comment’][0]) print "以下の内容で書き込みます。よろしいですか?<BR>\n" print "お名前 = " + name + "<BR>\n" print "コメント = " + comment + "<BR>\n" print "<form action=\"./bbs.cgi\" method=\"POST\">\n" printf "<input type=\"hidden\" name=\"name\" value=\"%s\">\n",name printf "<input type=\"hidden\" name=\"comment\" value=\"%s\">\n",comment print <<EOS <input type="hidden" name="mode" value="regist"> <input type="submit" value="submit"> </form> EOS end def regist(cgi) f = open(FILENAME,"a") name = CGI.escapeHTML(cgi.params[’name’][0]) comment = CGI.escapeHTML(cgi.params[’comment’][0]) f << name << "<>" << comment << "\n" f.close() print "無事に書き込みが終了しました。<BR>\n" print "<a href=\"./bbs.cgi\">掲示板に戻る</a>" end cgi = CGI.new mode = cgi.params[’mode’][0] print "Content-type: text/html\n\n" print "<html><body>" if mode == "confirm" confirm(cgi) elsif mode == "regist" regist(cgi) else view() end print "</body></html>\n" § ¦ 何か書き込んで「submit」ボタンを押すと、hidden 属性の mode 変数の値が confirm になっているた め、CGI は確認画面を表示する。たとえば、名前を「わたなべ」、コメントを「コメント」として投稿す ると、以下のような HTML が生成される。 ¨ <html><body>以下の内容で書き込みます。よろしいですか?<BR> お名前 = わたなべ<BR> コメント = コメント<BR> <form action="./bbs.cgi" method="POST"> <input type="hidden" name="name" value="わたなべ"> <input type="hidden" name="comment" value="コメント"> <input type="hidden" name="mode" value="regist"> <input type="submit" value="submit"> </form> </body></html> § 先ほど書き込んだ内容をもう一度 CGI に送ることができるように、確認内容とは別に、入力内容も hidden で保持してるのがわかるだろう。ここでもう一度 submit をすると、今度は mode の値が regist になって いるので実際の書き込み処理を行う。 6 CGI ノウハウ集 以下、CGI 作成について知っておくと便利なノウハウを列挙しておく。 11 ¥ ¦ 6.1 セッション管理 さきほどの掲示板の例では簡単のためにデータの受け渡しに hidden 属性の変数を用いたが、ユーザは自 由に HTML を改ざんできるため、hidden を使うと危ないことがある。フォームデータはクライアント側 で自由に改ざんが可能であることには注意しておかねばならない。改ざんを防ぐには、HTTP REFERER を見てチェックするなどの対策もあるが、確実な対策の行うにはセッション管理が必要となる。なお、改 ざん対策が施してあっても、常に入力内容に不正がないかチェックしておくべきである。 6.2 文字コード CGI に限らず、ウェブアプリケーションにおいて文字コードの問題はやっかいである。同じ漢字で表示 されていても、Shift JIS、EUC、JIS、UTF-8 などさまざまな可能性があり、どのコードで入力されてく るかは分からない。 文字コードを扱うために、Ruby には Kconv という NKF のラッパークラスが用意されている。冒頭で ¨ ¥ require ’kconv’ § ¦ と宣言すると、String クラスに tojis や toeuc、tosjis などのメソッドが追加される。これにより、データ の入力部で tosjis などを使うことで内部ではすべて Shift JIS で統一して処理する、ということが可能にな る。また、XML を扱う際には UTF に変換する必要があるので、その場合にも有用である。 6.3 ファイルロック 掲示板などでは、複数のユーザが同時にアクセスする可能性がある。その際、複数のプロセスが同時に 同じファイルにアクセスするとファイルに矛盾が生じたり、最悪の場合にはファイルが壊れたりする可能 性もある。そこで、あるプロセスがファイルを触っているときには他のプロセスが開けないように排他制 御をかけなければならない。 もっとも簡単に排他制御をかけるには、File クラスの flock メソッドを使う。 ¨ f = File.open(filename,"a") f.flock(File::LOCK_EX) #ファイルをロック #何か処理 f.flock(File::LOCK_UN) # ファイルをアンロック § ¥ ¦ 9 このようにすれば、他のプロセスはロックをかけたプロセスがロックを解除するまで待機する 。 6.4 データファイル隠蔽 先ほどの掲示板の例では、log.dat をブラウザで指定するとそのまま見えてしまう。これを防ぐには.htaccess を使うのが確実だが、商用サーバなどで.htaccess が使えない場合は、データファイルの拡張子を cgi にしてしまうのが簡単である。ブラウザからデータファイルを見ようとすると、拡張子が cgi なのでサー バが実行しようとしてエラーとなる。だが、CGI スクリプトから読む分には拡張子は関係ないので、動作 には影響しない。 6.5 MVC 分離 CGI の出力が複雑な HTML となる場合には、いちいち CGI を実行しながらレイアウトを調整するのは 大変であるし、MVC 分離の観点からもよくない。そこで出力する HTML のテンプレートを作っておき、 CGI からはテンプレートを読み込んで適切に処理することで、処理 (C) と表示 (V) を分けることにする。 以下のスクリプトは、ハッシュとファイル名を渡すと、指定されたファイルを読み込み、ハッシュの内 容から適切に文字列を置換した内容を返す。 9 実際には、ロックを明示的に解除しなくても、ファイルを閉じればロックは自動的に解除される。 12 List 10: テンプレート展開用関数 ¨ def get_replaced_text(filename,hash) f = open(filename) lines = f.read hash.each_key{|key| if key!=nil and hash[key] != nil id = ’%%’ + key + ’%%’ lines.gsub!(id,hash[key]) end } return lines end § ¥ 具体的には、 ¨ ID => 1 name => watanabe § ¥ ¦ ¦ という内容のハッシュデータを渡すと、指定されたファイルの「%%ID%%」が「1」に、 「%%name%%」が「watan- abe」に置換される。この関数を用いることで簡単なテンプレート展開を実現している。YAML や XML からテンプレート展開するライブラリなどもあるので興味を持ったら調べて欲しい。 CGI のデバッグ 6.6 CGI にエラーがあってもサーバは単に「500 Internal Error」しか返さないため、デバッグをサーバで 行うのは非効率的である10 。しかし、CGI にフォームデータを渡すためには URL エンコーディングが 必要であるため面倒である。そこで、Ruby の CGI クラスにはデバッグ機能がついている。具体的には、 require cgi を宣言した状態でスクリプトを実行すると標準入力からの入力待ちになる。そこで、 ¨ name1=value1 name2=value2 .... ^D § ¥ ¦ と、「名前=値 (改行)」を続けたあとに、「ˆD (コントロール+D)」を押せばそれを URL エンコーディン グしてスクリプトに渡してくれる。 また、ファイルからのリダイレクトもできる。たとえば、 ¨ name1=わたなべ name2=ひろし § ¥ ¦ という内容のテキストファイルを test.dat という名前で保存しておき、List. 6 のスクリプトにリダイレ クトすれば、 ¨ $ ./formetc.cgi <test.dat Content-type: text/html ¥ <html><body>name1 = わたなべ<BR> name2 = ひろし<BR> </body></html> § ¦ という結果が得られ、正しく URL エンコードされて渡されていることがわかる。 7 おわりに 以上、駆け足で CGI の基礎について解説した。Cookie やセッション管理、データベースとの接続など、 より実践的な内容についてかけなかったのが残念だが、基本がわかればあとは簡単なので各自で試みて欲 しい。 10 また、そもそもバグのある CGI を実行するとサーバに負担をかけるため、できるだけローカルでデバッグしておくべきである。 13