For Development HEAD DRAFTSearch (procedure/syntax/module):

12.97 www.cgi - CGIユーティリティ

Module: www.cgi

CGIスクリプトを書くのに便利ないくつかの基本的な手続きを提供します。

CGIスクリプトを手軽に書くにはこのモジュールの他に、 rfc.uri (rfc.uri - URIの解析と作成)、 text.html-lite (text.html-lite - シンプルなHTMLドキュメントの構築)、 text.tree (text.tree - 怠惰なテキスト構築) 等のモジュールを併せて 使うとよいでしょう。

注:現在有効な、CGIに関する「正式な」仕様というのはどうも無いようです。 http://w3c.org/CGI/あたりを参照して下さい。

メタ変数

Parameter: cgi-metavariables :optional metavariables

{www.cgi} 通常、httpdはcgiプログラムに様々な情報を環境変数経由で渡します。 www.cgi中の多くの手続きはその情報(メタ変数)を参照します。 しかし、cgiに関連するプログラムを開発中に環境変数にアクセスするのは 不便な場合もあります。 このパラメータを使うと、メタ変数をオーバライドすることができます。

Metavariablesは2要素のリストのリストです。 内側のリストは、最初の要素が変数名を、2つめの要素がその値を、それぞれ 文字列で与えます。

例えば次のコードはREQUEST_METHODQUERY_STRINGのメタ変数をmy-cgi-procedureの実行期間中に 上書きします。(parameterizeの詳細については パラメータを参照して下さい)。

(parameterize ((cgi-metavariables '(("REQUEST_METHOD" "GET")
                                    ("QUERY_STRING" "x=foo"))))
  (my-cgi-procedure))
Function: cgi-get-metavariable name

{www.cgi} nameで指定されるCGIメタ変数の値を返します。 この関数はまずパラメータcgi-metavariablesを探し、 指定されたメタ変数が見つからなければsys-getenvを呼びます。

CGIスクリプトは、なるべくsys-getenvを直接呼ぶのではなく cgi-get-metavariableを使うのが良いでしょう。 スクリプトの再利用もしやすくなります。

パラメータの取得

Function: cgi-parse-parameters :key :query-string :merge-cookies :part-handlers

{www.cgi} CGIプログラムに渡されたquery stringをパーズして、パラメータの連想リストにして 返します。文字列がキーワード引数query-stringに与えられればそれがパーズすべき query stringとなります。その引数が渡されなければこの手続きは メタ変数REQUEST_METHODを参照し、その値によって標準入力もしくは メタ変数QUERY_STRINGからquery stringが取られます。 そのようなメタ変数が定義されておらず、かつ現在の入力ポートが端末である場合、 インタラクティブにデバッグをしているものと考えて、 この手続きはプロンプトを出してユーザにパラメータの入力を促します。

(単にURLクエリ文字列を分解したいだけなら、rfc.uriモジュールの uri-decompose-queryを使うと良いでしょう (rfc.uri - URIの解析と作成参照)。 この手続きはCGIスクリプトに便利な機能をたくさん追加しています。)

REQUEST_METHODPOSTの場合、この手続きはenctypeとして application/x-www-form-urlencodedmultipart/form-dataの 両方を処理できます。後者は通常、ファイルアップロード機能を持つフォームに使われます。

POSTデータがmultipart/form-dataで送られて来た場合、 各パートの内容がパラメータの値となります。すなわち、アップロードされた ファイルはその内容がひとつの文字列として得られることになります。 元のファイル名のようなその他の情報は捨てられます。これが望ましい動作で ない場合は、part-handlers引数によって動作をカスタマイズすることができます。 詳しくは下の「ファイルアップロードの処理」で説明します。

キーワード引数merge-cookiesに真の値が与えられた場合は、 メタ変数HTTP_COOKIEからクッキーの値が読まれ、解析されて 結果に追加されます。

パラメータは複数の値を取り得るため、結果のパラメータに対応する値は常にリストになります。 パラメータに値が与えられていなければ、結果のパラメータに対する値には#tが置かれます。 次の例を参照して下さい。

(cgi-parse-parameters
  :query-string "foo=123&bar=%22%3f%3f%22&bar=zz&buzz")
 ⇒ (("foo" "123") ("bar "\"??\"" "zz") ("buzz" #t))
Function: cgi-get-parameter name params :key :default :list :convert

{www.cgi} cgi-parse-parametersが返す、パーズされたQuery文字列paramsから、 名前nameを持つパラメータの値を簡単に取り出すための手続きです。 nameは文字列です。

キーワード引数listに真の値が与えられていなければ、 返される値はスカラー値です。パラメータnameに複数の値が与えられた場合でも、 最初の値のみが返されます。listに真の値が与えられれば、返されるのは 常に値のリストとなります。

キーワード引数convertに手続きを与えると、対応する値が取り出された後でその 手続きが値を引数として呼ばれます。これによって値を文字列から必要な型へと変換することが できます。listに真の値が与えられている場合、変換手続きは各値に対して呼ばれ、 その結果のリストがcgi-get-parameterから返されます。

パラメータnameがQuery中に現れなかった場合は、 defaultに与えられた値がそのまま 返されます。defaultが省略された場合、listが偽であれば#fが、 真であれば()が返されます。

出力の生成

Function: cgi-header :key status content-type location cookies

{www.cgi} HTTPリプライメッセージのヘッダを、テキストツリー形式(text.tree - 怠惰なテキスト構築参照) で作成して返します。最も簡単な呼び出しでは次のようになります。

(tree->string (cgi-header))
  ⇒ "Content-type: text/html\r\n\r\n"

キーワード引数content-typeによってContent typeを指定できます。 また、cookiesにクッキー文字列のリストを渡すことにより、 クライアントにクッキーを設定できます。クッキー文字列を構築するには手続き construct-cookie-string (rfc.cookie - HTTPクッキー参照) が使えます。

キーワード引数locationは、Locationヘッダを作成して クライアントを別のURIに誘導するのに使えます。また、Statusヘッダを 指定するためにstatusキーワード引数が使えます。クライアントを 別URIに転送するよくある方法は次のようなものです。

(cgi-header :status "302 Moved Temporarily"
            :location target-uri)
Parameter: cgi-output-character-encoding :optional encoding

{www.cgi} このパラメータの値は次に説明するcgi-mainが出力するデータの 文字符合化法(CES)を指定します。デフォルトの値はGaucheのネイティブエンコーディング です。それ以外の値がセットされている場合、cgi-maingauche.charconvモジュールを用いて出力のエンコーディングの変換を 行います。 (gauche.charconv - 文字コード変換参照)。

便利な手続き

Function: cgi-main proc :key on-error merge-cookies output-proc part-handlers

{www.cgi} CGIスクリプトのための便利なラッパー手続きです。 この手続きは、まずcgi-parse-parametersを呼び出してCGIスクリプトに 渡されたパラメータを解析し、続いてその結果を引数としてprocを呼び出します。 キーワード引数merge-cookiesは、与えられればそのまま cgi-parse-parametersに渡されます。

手続きprocはHTTPヘッダを含むドキュメントを テキストツリー構造(text.tree - 怠惰なテキスト構築参照)で 返さなければなりません。cgi-mainはそれをwrite-treeを使って 現在の出力ポートに書き出し、0を返します。

もしproc内でエラーが起こった場合、そのエラーは捕捉されて、エラーを報告する HTMLページが作成されて出力されます。このエラーページは、on-errorキーワード引数に 手続きを渡すことでカスタマイズできます。on-errorに渡された手続きは エラー発生時に<condition>オブジェクト(コンディション参照) を引数として呼ばれ、HTTPヘッダを含むドキュメントをテキストツリー構造で返さねばなりません。

cgi-mainは最終的な結果を出力を書き出す時に パラメータcgi-output-character-encodingを参照し、 必要ならば出力の文字エンコーディングを変換します。

cgi-mainの出力のふるまいはキーワード引数output-procで カスタマイズできます。output-procが渡された場合、それは procの戻り値、あるいはエラーハンドラが作成したテキストツリー構造を 受け取る手続きでなければなりません。その手続きはテキストツリーを フォーマットして現在の出力ポートに出力しなければなりません。 必要ならば文字エンコーディングの変換もその手続き内で行います。

キーワード引数part-handlersは、そのままcgi-parse-parameters に渡されます。この引数によって、ファイルアップロードの際の動作をカスタマイズ できます。詳しくは下の「ファイルアップロードの処理」の項を参照して下さい。

この引数で、一時ファイルを使うように指定した場合、cgi-mainprocから抜ける際に(エラーでも正常終了でも)一時ファイルを 消去します。この機能を他でも利用するにはcgi-add-temporary-fileの項を 参考にして下さい。

procを呼ぶ前に、cgi-mainはカレントエラーポートの バッファリングモードを:lineに変更します。 (バッファリングモードの詳細についてはポート共通の操作port-bufferingの項を参照してください)。 これはwebサーバがcgiスクリプトのエラー出力を捕捉しやすくするためです。

以下の例はCGIに渡されたパラメータ全てをテーブルにして表示します。

#!/usr/local/bin/gosh

(use text.html-lite)
(use www.cgi)

(define (main args)
  (cgi-main
    (lambda (params)
      `(,(cgi-header)
        ,(html-doctype)
        ,(html:html
          (html:head (html:title "Example"))
          (html:body
           (html:table
            :border 1
            (html:tr (html:th "Name") (html:th "Value"))
            (map (lambda (p)
                   (html:tr
                    (html:td (html-escape-string (car p)))
                    (html:td (html-escape-string (x->string (cdr p))))))
                 params))))
       ))))
Function: cgi-add-temporary-file filename

{www.cgi} この手続きはcgi-mainに渡されるproc中で呼ばれることを 想定しています。 この手続きは、filenameを一時ファイルとして登録し、procが 終了する際に消去されるようにします。cgiスクリプトがエラー終了した場合 などでもごみを残さないようにする便利な方法です。 この手続きを呼んだ後で、procfilenameを消去したり 名前を変えたりしても構いません。

Parameter: cgi-temporary-files

{www.cgi} cgi-add-temporary-fileで登録された一時ファイルを保持するパラメータです。

ファイルアップロードの処理

cgi-parse-parametersの項で説明したように、ファイルアップロードは デフォルトでは透過的に扱われます。すなわち、アップロードされた ファイルの内容がパラメータの値となります。 これは望みの動作ではないかもしれません。例えばアップロードされるファイルが 巨大であることが予想されるなら、それを全てメモリに読み込んで持ち回りたくは ないかもしれません。cgi-parse-parameterscgi-mainpart-handlers引数によって、ファイルアップロードの 処理をカスタマイズすることが可能です。 (この引数は、フォームデータがmultipart/form-data enctypeで 送られた場合にのみ意味を持ちます)。

part-handlers引数が与えられている場合、それはリストのリストで、 内部のリストは(name-pattern action kv-list …) の形式で なければなりません。 アップロードされたファイルは、そのパラメータ名がname-patternに マッチした場合にactionで指示されるように処理されます。 (ここで、パラメータ名とはsubmitされたフォームのinput要素に与えられた ’name’属性のことです。アップロードされたファイルの名前ではありません)。

name-patternは文字列のリストか、正規表現か、#tです。 文字列のリストの場合はそれのいずれかとパラメータ名が等しければマッチと みなされます。#tは全てのものにマッチします。

actionは次のいずれかの値でなければなりません。

#f

デフォルトのアクションです。すなわち、アップロードされたファイルの内容が 文字列として読み込まれ、パラメータの値となります。

ignore

アップロードされたファイルの内容を無視します。

file

アップロードされたファイルの内容は一時ファイルへと格納されます。 パラメータの値は、一時ファイルの名前となります。

このアクションを使う場合は、エントリを (name-pattern file prefix) のように書くことも でき、その場合はprefixが一時ファイルのパス名のプリフィクスとして 使われます。例えば("image" file "/var/mycgi/incoming/img") のようにしておくと、"image"パラメータとしてアップロードされた ファイルが/var/mycgi/incoming/img49g2Uaのような一時ファイルに 格納されることになります。

アプリケーションは、この一時ファイルを(必要ならば)適切な場所に 移動しなければなりません。cgi-mainを用いている場合は、 一時ファイルはcgi-mainを抜ける際に(まだあれば)unlinkされます。

file+name

fileと同様ですが、パラメータの値が一時ファイル名と クライアントが送ってきたファイル名からなるリストになります。 クライアントが送信したファイル名を利用したい場合に便利です (ただ、クライアントが常に正しいファイル名を送って来ると仮定しては いけません。例えば、アップロードされたファイルを チェック無しにクライアントが送ってきた名前にrenameするというような ことは避けてください)。

procedure

この場合、アップロードされた内容を処理するために、手続きprocedureが 呼ばれます。手続きは4つの引数を伴って呼ばれます: (procedure name filename part-info iport).

nameはパラメータの名前、filenameはオリジナルファイルの名前 (クライアント側でのパス名)です。part-info<mime-part>オブジェクトで、 このMIMEパートの情報を保持しており、そしてiportは内容を読むための入力ポートです。 これらの引数の詳しい意味についてはrfc.mime - MIMEメッセージ処理を 参照して下さい。独自のprocedureを書く際に、rfc.mimemime-retrieve-bodyのような手続きが使えるかもしれません。

procedure内で一時ファイルを作る場合は、それを cgi-add-temporary-fileで登録しておけば、cgi処理中に エラーが起きた場合でも一時ファイルが消去されるようにすることができます。

actionの後ろにkv-listが与えられた場合、それは キーワード-値リストでなければなりません。次のキーワードがサポートされています。

:prefix

actionfilefile+nameの時のみ有効です。 一時ファイルのプリフィクスを指定します。例えば:prefix "/tmp/foo"を 与えると、ファイルは/tmp/fooxAgjeQのような名前でセーブされます。

:mode

actionfilefile+nameの時のみ有効です。 一時ファイルのモードをunix式の整数で指定します。デフォルトは#o600です。

ファイルアップロード以外のパラメータはpart-handlersの対象外である ことに注意して下さい。それらのパラメータの値は常に文字列へと変換されます。

簡単な例を示します。例えば次のようなフォームがあったとします。

<form enctype="multipart/form-data" method="POST" action="mycgi.cgi">
<input type="file" name="imagefile" />
<input type="text" name="description" />
<input type="hidden" name="mode" value="normal" />
</form>

mycgi.cgi内で、cgi-parse-parameterspart-handlers引数なしで使った場合は、 例えば次のようなリストがパラメータパージングの結果として得られるでしょう。 (実際の値は、webクライアントがどのようにフォームを埋めたかに依存します)。

(("imagefile" #**".....(image file content as a string)....")
 ("description" "my image")
 ("mode" "normal"))

ここでもし、'(("imagefile" file :prefix "/tmp/mycgi"))part-handlersに 渡したなら、替わりに次のような結果が得られるでしょう。 ここで、アップロードされたファイルは/tmp/mycgi7gq0Bにセーブ されていることになります。

(("imagefile" "/tmp/mycgi7gq0B")
 ("description" "my image")
 ("mode" "normal"))

上の例でシンボルfileのかわりにfile+nameを使えば、 例えば"imagefile"の値として("/tmp/mycgi7gq0B" "logo.jpg") のようなものが得られるでしょう。ここで"logo.jpg"は アップロードされたファイルのクライアント側でのパス名です。 (注意:クライアントは任意の文字列をファイル名として送信することが できるため、その文字列が有効なパス名であることを仮定してはなりません。)



For Development HEAD DRAFTSearch (procedure/syntax/module):
DRAFT