UP | HOME

ソフトウェア開発法 – CGIプログラミング概論

目次

1 概要

本資料では,Webアプリケーションに必要な基本的知識として, サーバサイドのCGIプログラムとクライアントサイドの JavaScript との関係について述べる.

Common Gateway Interface (CGI) とは, Webサーバが外部プログラムの出力結果を利用してクライアントに情報を提供する際の Webサーバと外部プログラムのインタフェースを取り決めたものである. 詳細は,RFC3875として規定されている.

CGIインタフェースを実現したプログラムをCGIプログラムと呼ぶ. CGIプログラムは,Perlなどのいわゆるスクリプト言語で記述されることが多いが, 本来は言語を問わない.C言語でも記述可能である. ただし,文字列処理を得意とする言語によって記述されることが多い.それは,以下の要因が考えられる.

  1. CGI自身が文字列を主体としたインタフェースである
  2. CGIの実行結果によってHTML文字列を出力することが多い
  3. 実行速度をあまり問われない

以下では,CGIと Ruby 言語でのCGIプログラム実装例, RubyでCGIプログラムを実装する際に便利なCGIライブラリについて概略を示す. 詳しくは参考図書や URLを参照して自学して欲しい.

2 CGIプログラム

2.1 Webサーバとブラウザの関係

http://www.okayama-u.ac.jp にアクセスする場合のクライアント (Webブラウザ) と Webサーバのやりとりは,おおよそ以下の通りである.

  1. ユーザが http://www.okayama-u.ac.jp/ を URL として入力する.
  2. クライアント(Webブラウザ)は, http://www.okayama-u.ac.jp/ から処理内容を解釈する.
    • 通信プロトコル: HTTP
    • 通信先(サーバ): www.okayama-u.ac.jp
    • サーバへの要求: / を GET する
  3. クライアントは,www.okayama-u.ac.jp というホストに接続する.
  4. サーバはクライアントを受け付ける.
  5. クライアントはサーバに HTTP プロトコルを利用して GET / というコマンドを発行する.
  6. サーバは,/ に対応する内容をクライアントに送信する.
  7. 接続を切断する.
  8. クライアントは,受信した内容を解釈して,処理する. クライアントがブラウザであれば,整形(レンダリングという)して表示する.

ここで,上記6において, 「サーバが / に対応する内容をクライアントに送信する」 とある. この動作は,サーバに依存している.Apache を代表とする多くの Webサーバは,

サーバが管理するディレクトリのトップ ('/') にある
index.html というファイルをクライアントに送信する

という動作がデフォルトで対応付けられており, 結果として index.html に書かれた内容がブラウザに表示される.

2.2 動的コンテンツとは

これまでの説明では,Webサーバの仕事は,クライアントが要求する URL に対応する静的なファイルを送信することであった. それは,前述の

サーバが管理するディレクトリのトップ ('/') にある
index.html というファイルをクライアントに送信する

なるルールがあるためである.しかし,このルールは固定的ではない. このルールを変更して,

もし,要求が GET / だったら,現在の時刻を HTML で返す

とすれば,Webブラウザから見ると http://www.okayama-u.ac.jp/ にアクセスするたびに 現在時刻が表示されるようになるだろう. つまり,URL とそれによって何が返されるかの対応は, サーバによって自由に変更可能なのである.

では,ここで,特定の URL 例えば http://www.okayama-u.ac.jp/weather-report にアクセスすると今日の岡山大学付近の天気を表示してくれるサービスを考えるとする. その場合のサーバは,以下のような処理をすることになるだろう.

1: if (サーバへの要求 == "GET /weather-report")
2:   返却内容 = 天気を取得してHTMLに変換;
3: else
4:   返却内容 = 対応するファイルを取得(デフォルト処理);
5: 
6: 返却内容をクライアントに送信;

このように現在時刻を表示したり,天気を表示したりするページ(コンテンツ)は, 通常の静的コンテンツに対して,動的コンテンツ呼ばれる. 現在のインターネットでは,様々なWebページがこの動的コンテンツとして実現されている.

2.3 WebサーバとCGI

動的コンテンツを生成するために,Webサーバは何をしなければならないだろうか. それは,コンテンツに依存する.つまり,航空券の予約をしたり, 天気を取得したりといった全ての作業をWebサーバ中にプログラム(たとえば関数) として実現することが考えられる.

しかし,これは,不必要にWebサーバプログラムが大きく複雑になる. しかも世の中のサービス全般を考えた場合,現実的ではない.

そこで, これらの動的コンテンツを生成するために必要な処理を外部のプログラムとして実現し, そのプログラムの実行結果を Webサーバがクライアントに返却するという方法が考えられる. つまりこういう方法だ.

1: if (サーバへの要求 == "GET /何とか何とか.cgi")
2:   返却内容 = 何とか何とか.cgi を実行した結果;
3: else
4:   返却内容 = 対応するファイルを取得(デフォルト処理);
5: 
6: 返却内容をクライアントに送信;

こうしておいて,weather-report.cgi という天気を表示するプログラムを用意しておけば, クライアントの http://www.okayama-u.ac.jp/weather-report.cgi に対する要求を外部プログラムによって実現できる.

では, weather-report.cgi は,どのように書けばいいのであろうか. どのように引数を受け取って,どのような形式で出力をするのだろうか. それは,Webサーバに依存する? いや,依存してしまっては,Webサーバ毎にプログラムが必要となるので, この間の取り決めが必要である.それが先述の Common Gateway Interface つまり CGIである.

2.4 最初のCGIプログラム

では,CGIプログラムを書いてみよう.CGIプログラムは,

最初の行に Content-Type: MIME規定のタイプ (HTMLを返却したいなら text/html) 次の行に空行,3行目から実際のコンテンツを出力すればいいだけである.

1: #include <stdio.h>
2: 
3: main()
4: {
5:   printf("Content-Type: text/html\n");
6:   printf("\n");
7:   printf("<html>Hello world.<html>\n");
8: }

これをコンパイルして hello.cgi としてWebサーバが管理するディレクトリに保存する. Webブラウザで対応するURL たとえば, http://localhost/hello.cgi にアクセスすると,

Hello world.

と表示される.ちなみに,Ruby で書くと,

1: #!/usr/bin/ruby
2: 
3: print "Content-Type: text/html\n"
4: print "\n"
5: print "<html>Hello world.<html>\n"

となる.同じファイルで保存しておく.1行目は,Ruby の実行環境(OS等)に依存する.

2.5 引数のあるCGI

では,Webサーバは,CGIプログラムにどのような引数を渡すのだろうか. 以下のように第1,2引数を表示できるようにして,

1: #include <stdio.h>
2: 
3: main(int argc, char *argv[])
4: {
5:   printf("Content-Type: text/html\n");
6:   printf("\n");
7:   printf("<html>Hello world. -- %s,%s</html>\n", argv[1], argv[2]);
8: }

http://localhost/hello.cgi の代わりに http://localhost/hello.cgi?your+friend とすると,

Hello world. -- your,friend

と表示される.つまり,

  • ? 以降が引数
  • 各引数は,+ で区切られる

と分かる.このように, ? 以降が CGIプログラムのパラメータ (Query String) として何らかの方法で渡されていることが分かる.

しかし,CGIはそれほど単純でもない.例えば,この方法では, + 自身を渡したりする場合,どうすればいいのであろうか. また,日本語は URL に書いてもいいのだろうか.

実際のCGI規約には,もっと複雑なルールがある. たとえば, http://localhost/hello.cgi?name=nomura&school=OkayamaUniv とした場合は,プログラムの引数ではなく環境変数という方法で渡されたり, 標準入力として渡される場合もある. 漢字は,ASCIIコード列にエンコードされて渡されるので, CGIプログラムはそれをデコードしなければならない. また,掲示板などによくあるフォームに入力して submitボタンを押したときに呼び出される CGI プログラムは,どう引数を渡されているのだろうか.

このような取り決めは,前述のRFC3875に規定されているが, これを全て自前のプログラムで実現しようとすると, 多大な努力を必要とする.そこで,多くの言語では, CGIにまつわる面倒を一手に引き受けるライブラリが用意されている.

例えば,Ruby にも標準で CGIのライブラリが付属する.

 1: #!/usr/bin/ruby
 2: 
 3: require 'cgi'
 4: 
 5: params = CGI.new
 6: name = params['name']
 7: addr = params['addr']
 8: 
 9: print "Content-Type: text/html\n\n"
10: print "<html>\n"
11: print "Name: #{name}<br>\n"
12: print "Addr: #{addr}<br>\n"
13: print "</html>\n"

このように,いわゆる連想配列(Ruby ではハッシュと呼ぶ)として引数を取り出せる. http://localhost/hello.cgi?name=Nomura&addr=Okayama にアクセスすると,

Name: Nomura
Addr: Okayama

と表示されるだろう.

Webフォームデータのやりとりなどは, 末尾の参考文献などを参考にして自学して欲しい.

3 練習課題

簡単な電卓プログラムをWeb CGI として Ruby で作成せよ. フォームから四則演算の数式を入力すると, その答を表示する Web CGI プログラムである.

回答は,グループ毎にメールで送信のこと. 添削が楽になるように,1つのファイルとして簡潔に実現すること.

  • 作例: calc.cgi

     1: #!/usr/bin/ruby
     2: 
     3: require 'cgi'
     4: 
     5: def html_header
     6:   return <<-EOF_HEADER
     7: Content-Type: text/html
     8: 
     9:   <html>
    10:   <title>simple calculator</title>
    11:   <body>
    12:    input:
    13: EOF_HEADER
    14: end
    15: 
    16: def html_footer
    17:   return <<-EOF_FOOTER
    18:   </html>
    19:   </body>
    20: EOF_FOOTER
    21: end
    22: 
    23: def html_form
    24:   return <<-EOF_FORM
    25:   <form action="calc.cgi" method="post">
    26:     <input name="exp" size="25" />
    27:   </form>
    28: EOF_FORM
    29: end
    30: 
    31: ################################################################
    32: ### main
    33: 
    34: content = []
    35: 
    36: params = CGI.new
    37: exp = params['exp'].to_s
    38: 
    39: content << html_header
    40: 
    41: if exp =~ /^$/
    42:   # initial state
    43:   msg = ''
    44:   content << msg << html_form
    45: 
    46: elsif exp =~ /\A[\d\/*+-]+\z/
    47:   # got user input
    48:   msg = eval exp
    49:   content << msg << html_form
    50: 
    51: else
    52:   # invalid input
    53:   msg = 'invalid expression.'
    54:   content << msg << html_form
    55: end
    56: 
    57: content << html_footer
    58: 
    59: print content.join
    

4 CGI と RESTful API

4.1 Query String vs URL PATH_INFO

引数のあるCGI で述べたように,URL (URI) の記法では, ? 以降をパラメータ (query string) と呼び,CGI に引数として引き渡される.

たとえば, http://localhost/hello.cgi?name=nomura&school=OkayamaUniv と書くと, hello.cginame が nomura で school が OkayamaUniv という情報が渡る.

しかし,世の中には query string ( ? 以下) を含まないにもかかわらず, 動的ページを生成しているケースもある.

例えば, Ruby On Rails プロジェクトのページ https://github.com/rails/rails は,動的に生成されたページに見えるが, URL には query string が含まれていない.このような挙動を CGI で実現するには,どうすればいいのだろうか.

以下のプログラムは,自身に与えられたパラメータや環境変数を表示して見せる CGI である.

dump-param.cgi

 1: #!/usr/bin/ruby
 2: 
 3: require 'cgi'
 4: require 'pp'
 5: 
 6: print "Content-Type: text/plain\n\n"
 7: params = CGI.new
 8: pp params
 9: pp ENV
10: exit 0

このプログラムに色々なパラメータを与えて,結果を観察してみて欲しい. また,cgi の末尾に更に / を足してアクセスしてみて欲しい. たとえば, .../dump-param.cgi/a/b/c という URL にアクセスすると, 以下のようになるだろう.(一部抜粋)

#<CGI:0x2afeffb050f8
 @cookies={},
 @multipart=false,
 @output_cookies=nil,
 @output_hidden=nil,
 @params={}>
{"HTTP_HOST"=>"www.example.com",
 "SERVER_NAME"=>"www.example.com",
 "SERVER_PORT"=>"80",
 "DOCUMENT_ROOT"=>"/var/www/html",
 "SCRIPT_FILENAME"=>
  "/home/nom/public_html/lect/sdm/dyn/examples/dump-param.cgi",
 "GATEWAY_INTERFACE"=>"CGI/1.1",
 "SERVER_PROTOCOL"=>"HTTP/1.1",
 "REQUEST_METHOD"=>"GET",
 "QUERY_STRING"=>"",
 "REQUEST_URI"=>"/~nom/lect/sdm/dyn/examples/dump-param.cgi/a/b/c",
 "SCRIPT_NAME"=>"/~nom/lect/sdm/dyn/examples/dump-param.cgi",
 "PATH_INFO"=>"/a/b/c",
 "PATH_TRANSLATED"=>"/var/www/html/a/b/c"}

ここから分かることは,

  • .../dump-param.cgi/a/b/c という URL にアクセスしても, dump-param.cgi が起動される.
  • PATH_INFO を見れば,自身が /a/b/c の sub path を伴って起動されたことが分かる.

つまり,query string を利用しないで, PATH_INFO から得られる sub path を パラメータとみなすことで処理を動的に変更すれば,動的ページの生成が可能であると分かる.

なお, http://www.example.com/~nom/lect/sdm/dyn/examples/dump-param.cgi/a/b/c.cgi が含まれているので見た目が悪い.これが気になる場合は, Web サーバによる動的URI書換えを利用して隠すこともできる. Apache であれば, .htaccess を以下のように書いて examples/ 以下に配置し,

RewriteEngine On
RewriteBase /~nom/lect/sdm/dyn/examples
RewriteRule ^dump-param/(.*) cgi-bin/dump-param.cgi/$1 [L]

dump-param.cgiexamples/cgi-bin/dump-param.cgi に移動すればよいだろう. その際のダンプ結果は,以下のようになる.

#<CGI:0x2ad2a1465160
 @cookies={},
 @multipart=false,
 @output_cookies=nil,
 @output_hidden=nil,
 @params={}>
{"HTTP_HOST"=>"163.43.140.241",
 "SERVER_NAME"=>"163.43.140.241",
 "SERVER_PORT"=>"80",
 "DOCUMENT_ROOT"=>"/var/www/html",
 "SCRIPT_FILENAME"=>
  "/home/nom/public_html/lect/sdm/dyn/examples/cgi-bin/dump-param.cgi",
 "REDIRECT_URL"=>"/~nom/lect/sdm/dyn/examples/dump-param/a/b/c",
 "GATEWAY_INTERFACE"=>"CGI/1.1",
 "SERVER_PROTOCOL"=>"HTTP/1.1",
 "REQUEST_METHOD"=>"GET",
 "QUERY_STRING"=>"",
 "REQUEST_URI"=>"/~nom/lect/sdm/dyn/examples/dump-param/a/b/c",
 "SCRIPT_NAME"=>"/~nom/lect/sdm/dyn/examples/cgi-bin/dump-param.cgi",
 "PATH_INFO"=>"/a/b/c",
 "PATH_TRANSLATED"=>"/var/www/html/a/b/c"}

4.2 HTTP Method の利用

もともと HTTP は,リソース (URI で表現される) を取得 (GET) するために考案された. そのため,新規にリソースを作成したり削除したりする明示的な方法がなかった. そこで,GET の副作用を利用してリソースの作成や削除を実現することがあった. これは,今でもよく行われている.

具体的には,URI を GET することで対応する CGI を起動し, その際に query string の中に新規作成や削除の情報を埋め込む方法である.

その後,HTTP 1.0 でリソースの新規作成 (POST) という操作が加わり, クライアントから(フォームから) 情報をポストすることでリソースを新規に作れるようになった.

現在主流の HTTP 1.1 では,更にリソースの更新 (PUT),削除 (DELETE) 操作が加わり, HTTP の枠組で CRUD に相当する操作が可能となった. これら4操作 POST/GET/PUT/DELETE は,HTTP メソッドと呼ばれ,HTTP のプロトコルで陽に規定されている.

モダンな Web アプリケーションでは,GET + query string の副作用に頼らずに HTTP メソッドで明示的に リソースを操作することがよいとされている. これは,サービス間でのデータ交換において, HTTP プロトコルを基盤とした共通の意味を提供するという点において利点がある.

4.3 RESTful なサービス

Web アプリケーションの作成において REST を意識した設計が主流になっている.具体的には,以下を指針とする.

  • query string を多用せず, PATH_INFO を使うことで,リソースを強く意識した URI を構成する.
  • GET の副作用ではなく,明示的に HTTP Method (POST/GET/PUT/DELETE) を利用することで直感的な操作(API)を提供する.

具体的には,CGI プログラムは, PATH_INFO により操作対象リソースを同定し, REQUEST_METHOD によって (POST/GET/PUT/DELETE) を判別し, 対応する操作を実現することが望ましい. query string は, GET に対する副次的な情報として,検索などの目的で利用する.

5 参考文献 と 参考 URL

日付: 2021-04-12

著者: Yoshinari Nomura