National Institute of Advanced Industrial Science and Technology (AIST) This page is a page of the former research institute. We stopped updating on March 31.2001.
E-mail to webmaster (Japanese) E-mail to webmaster (English)

Web Accessible PostgreSQL Database by FCGI/EusLisp

Web-EusLisp-DB (WELD)
June 20, 2000
Toshihiro Matsui, matsui@etl.go.jp
Agency of Industrial Science and Technologies (AIST), Japan

Quick-Start for Impatients (digest for building a people table FCGI)

Example: Country code database made of FCGI in EusLisp connecting to PostgreSQL

1. EusLisp for UNIX script programming
通常、Lispを用いるときは、キーボードから式を与えてその評価結果を受け取るインタラクティブなサイクルを繰り返しますが、CGIのプログラムは、そのようなインタラクティブな動作ではなく、一発芸で実行されるようなunixのコマンド形式にプログラムされる必要があります。EusLispは、起動時の引数をプログラムファイル名だと思ってロード(実行)します。例として、current directoryにあるファイルの個数を出力するプログラムを考えます。

% cat filecount.l
(print (length (directory)))

% cat filecount
#! /bin/csh
setenv EUSDIR /usr/local/eus
setenv LD_LIBRARY_PATH /usr/local/eus/Linux/lib
/usr/local/bin/eus filecount.l
% chmod a+x filecount

ここで、filecount を実行すると、ファイルの個数が表示されます。二つあるsetenvは、.cshrcの中に定義されていれば不要です。そのときは、#! /bin/cshの代わりに#! /usr/local/bin/eus とすることも可能です。

% cat filecount
#! /usr/local/bin/eus
(print (length (directory)))
% ./filecount </dev/null

euslispは、標準入力がttyの場合は、初期化されるモジュールを表示します。これをなくすには、/dev/nullを標準入力に指定するか、ソースプログラムが標準入力から読み込まれるようにします。

% cat filecount2
#! /bin/csh
setenv EUSDIR /usr/local/eus
setenv LD_LIBRARY_PATH /usr/local/eus/Linux/lib
/usr/local/bin/eus <<EOF
(print (length (directory)))
EOF
ただし、このようにして標準入力にプログラムを与える方法は、CGIスクリプトの記述には不適当です。CGIでは、クライアントからのリクエストがPOSTされると、引数が標準入力から流し込まれるからです。
さらに、表示をxwindowに出すのであれば、次のようにします。
% cat filecount
#! /bin/csh
setenv EUSDIR /usr/local/eus
setenv LD_LIBRARY_PATH /usr/local/eus/Linux/lib
/usr/local/bin/eusx <<EOF
(setq xw (instance x:textwindow :create))
(send xw :put-line 1 1 (format nil "filecount=‾d‾%" (length (directory))))
(xflush)
(unix:sleep 5)
EOF
引数のあるコマンドしたい場合、$1, $2 などのシェル変数で受け取ります。次の例は、引数に与えられた文字列をローマ字からひらがなに変換するものです。
% cat kana
#! /bin/csh
/usr/local/bin/eus <<EOF
(load "lib/llib/kana_euc.l")
(print (romkan "$1"))
EOF
% chmod a+x kana
% ./kana matsui
"まつい"

2. EusLispからPostgreSQLを使う
PostgreSQLは、Linuxを含むUNIX上でフリーで利用できる、 リレーショナルデータベースです。 研究目的なので、型の定義やクラスの継承など、 意欲的な機能が取り込まれています。 フリーなだけに文句は言えませんが、 oid (object ID) の回収(GC)が完全には行われないと言う、 Lisp派から見ると容認しがたい問題もあります。 (GCで悩んだりSQLなんていうへんてこりんな言語の解釈にわずらわされる くらいならLispで実装すればよいのに。) ここではあまり気にせず、まず、postgres (pgsql) をインストールします。 インストールの方法は、 http://www2.gol.com/users/tosiyuki/linux/index.html などを参考にして下さい。 以下では、version 6.5.3 を想定しています。適当なユーザー名をcreateuserで定義し、適当なデータベースをcreatedbで作成して下さい。ここでは、ログイン名でcreateuser, createdbしてあるものと仮定します。また、postmaster -i & などのコマンドで、postgreSQLサーバーをバックグラウンドで走らせておきます。正しく動作することをpsqlコマンドで確認します。
EusLispは、postgresとインタフェースするために、 libpq.so というライブラリを使用します。 libpq.soは、postgresが正しくインストールされれば、 /usr/local/pgsql/lib/libpq.soとして作成されているはずです。 これをそのまま用いても良いですし、 /usr/local/libなどにコピーしても良いでしょう。 libpq.soをリンクし、外部関数として定義し、 データベースとの接続を管理するクラスを作成するeuslispのプログラムが、 /usr/local/eus/lib/llib/pgsql.l にあります。 libpq.soの場所は、pgsql.lの中に書かれていますので、 ライブラリの参照は、必要ならば書き換えます。 pgsql.lは、"PQ"というパッケージを作成し、 種々のオブジェクトをその中に定義します。 pgsql.lがロードされると、次のようにしてpostgreSQLサーバと接続します。

(setq db (instance pq:pgsql :init))
データベースやユーザーの名前がデフォルト(ログイン名)と異なる場合や、postgreSQLサーバが別のマシンで走っているときは、次のようにして指定します。
(setq db (instance pq:pgsql :init 
	 :dbname "utyo"
	 :user "skagami"
	 :host "jsk.t.u-tokyo.ac.jp"))

さらに、:passwordを指定することも可能ですが、パスワードがプログラム中にあからさまに表示されることには抵抗もあるでしょう。
PostgreSQLのユニークで興味深い特徴は、データ型を定義できることです。主なデータ型には、整数、浮動小数、テキストなどがあります。テキストは、Lispの文字列(string)に対応させるのが適当ですが、Lispのsymbolに対応するデータ型がありません。そこで、symbol型を定義します。symbol型は、textによく似ていますが、小文字と大文字の区別をしない、区別をするためには|や¥を使ってescapeする、というものです。そのためのファイルが、symbol_io.c, symbol_io.sqlとして用意してあります。symbol_io.cは、shared objectとしてコンパイルし、/usr/local/pgsql/libなどに置きます。symbol_io.sqlの中では、symbol_io.cをコンパイルしたsymbol_io.soの在処を指定します。そして、symbol_io.sqlが置かれたディレクトリで、次のようにしてPostgreSQLサーバーに読み込ませます。このローディングの作業は、データベースに対して一回実行すれば良く、postmasterを起動するたびに行う必要はありません。
% psql
¥i /tmp/symbol_io.sql

以下での説明のため、peopleというテーブルを作成しておきます。
create table (id sequence, name text, secretary name, age int4, update_date date);

pq:pgsqlオブジェクトは、PostgreSQLサーバーとの接続を維持し、SQLコマンドを送り出し、結果を受け取るポートになります。:initの引数にデータベースを指定することからわかるように、同時に複数のデータベースと接続するには、それに対応したpq:pgsqlオブジェクトを作成します。
people表のすべてのレコードを取り出すには、次のようなpq:query関数によってSQLコマンドを発行します。第1引数は、databaseとの接続を表すpq:pgsqlオブジェクト、第2引数は、SQL処理を非同期で行うときに指定する、ハンドラー関数ですが、ここではNILにしておきます。みっつめは、postgresサーバーに送られるSQLコマンドです。SQLは文字列で表現するので、(format nil " ..." ...) という形式がよく使われます。
(pq:query db nil "select * from people")
select文は頻繁に使われるので、次のような簡便な形式も用意してあります。
(pq:select db 'secretary 'people :where "name='matsui'")
表のフィールド定義を読み出すには、次のtable-fields関数を使います。
(pq:table-fields db 'people)

3. HTTPクライアント
EusLispでHTTPクライアントプログラムを書くために、pathnameクラスを拡張したurl-pathnameクラス、base64エンコーディング関数、文字列暗号化関数、HTML解釈モジュール、が用意してあります。また、時刻・日付表現のためのinterval-timeクラス、calender-timeクラスは、HTTPでの表記ならびにpgsqlでのISO表記と互換性が取れるように定義されています。
HTTPのクライアントプログラムは、webサーバーにTCP接続し、GETコマンドを送ってHTMLテキストを受け取ります。HTTPを完全に解釈するライブラリがあれば、webブラウザをEusLispで記述することも可能ですが、現在の実装は、簡単なHTMLテキストを解釈できるだけです。HTMLテキストを走査して、タグを取り出します。(load "lib/llib/http.l") の後、(read-http "http://www.etl.go.jp") とすることによってHTMLヘッダと本体のテキストがリストになって返されます。

4. CGIスクリプト
CGIスクリプトをEusLispで記述するというのは、基本的に1で述べたようなUNIXのコマンドとして実行できるEusLispプログラムを作成することです。その簡単なプログラムは以下のようになります。
% cat simple.cgi
#! /bin/csh
setenv EUSDIR /usr/local/eus/
setenv LD_LIBRARY_PATH /usr/local/eus/Linux/lib
/usr/local/bin/eus /usr/local/eus/lib/demo/simple-cgi.l

% cat /usr/local/eus/lib/demo/simple-cgi.l
(load "/usr/local/eus/lib/llib/httpcgi.l")
(let ((query))
(html-header)
(gen "‾%‾%")
(gen (html "<html><body><h2>EusLisp</h2><br>‾%"
"Current time is <FONT COLOR=#ff0000>"
(UNIX:asctime (unix:localtime))
"</FONT><BR>‾%"))
(pprint (unix::environ) *cgi-out*)
(setq query (get-cgi-query))
(gen (html "<br><p>query_string=" query "<br>‾%"))
(setq query (parse-http-query query))
(gen (html "<br><p>query form=" query "<br>‾%"))
)
simple.cgiは、/home/httpd/cgi-bin/などの、cgiプログラムを置くべきディレクトリに入れます。apache web serverでは、どのディレクトリで cgiプログラムを走らせることができるかは、httpd.conf, srm.confなどで定義します。simple.cgiは、chmod a+xなどとして実行パーミションを設定します。cgiプログラムは、httpdプロセスの実効uidで実行されます。デフォルトでは、nobodyで走ることになりますので、少なくともnobodyで走れるような実行権限を与えておく必要があります。また、nobodyユーザーの環境変数が適当に設定されているかどうか不明なので、simple.cgiの冒頭で必要な環境変数やPATHの設定が必要です。
simple-cgi.lは、simple.cgiでロードされるプログラムなので、cgiプログラムから見えるディレクトリならどこに置かれても構いません。このプログラムは、htmlテキストを生成し、*cgi-out*に出力します。*cgi-out*は、実は、標準出力になっています。最初に、httpcgi.lをロードしています。これは、htmlを生成するのに便利な関数をいくつか定義しています。(html-header)は、Content-type: text/htmlという一行を生成します。次の(gen "‾%‾%")は、空行を2行、*cgi-out*に出力します。次に現在時間を表示します。さらに、このEusLisp のcgiプログラムがweb serverから受け取る環境変数をすべて表示します。次に、その中でもcgiプログラムに興味のあるリクエスト引数であるquery_stringを取り出し、それをパージングした後のリストを表示しています。これらを呼び出すURIは、http://www.site.jp/cgi-bin/simple.cgiなどとなります。この後に、?command=list&table=people などの引数を付けて呼び出してみると、取り出された引数が表示されるはずです。これで、引数リストを取り出して処理する方法の概要がわかるはずです。

5. PostgreSQLデータベースを操作する CGI
以上の機能を合わせて、PostgreSQLデータベースを操作する CGIがlib/demo/httpdb.lとして定義されています。これをロードして(httpdb)を実行するプログラムを用意します。
% cat /home/httpd/cgi-bin/eusdb.cgi
#! /bin/csh
setenv EUSDIR /usr/local/eus/
setenv LD_LIBRARY_PATH /usr/local/eus/Linux/lib
/usr/local/bin/eus /usr/local/eus/lib/demo/httpdb-cgi.l

% cat /usr/local/eus/lib/demo/httpdb-cgi.l
(load "/usr/local/eus/lib/demo/httpdb.l")
(html-header)
(gen "‾%‾%")
(httpdb)

これらを用意した上で、http://www.site.jp/cgi-bin/eusdb.cgiとすると、さきほどのsimple.cgiに似た表示が出ます。引数がないときの動作が、環境変数を表示することになっているからで、デバッグに使います。?command=loginという引数を付けて呼び出すと(通常はそのようなhttpリンクをweb pageに作成しておく)、データベースへのログイン画面が表示されるはずです。(注意:linuxのNetscapeは、入力フォームで初期値が適切に表示されません。resetボタンを押して初期値を強制的に入れるか、MacintoshなどのNetscapeを使ってみて下さい)。
hostcomputer, port番号、データベース名 (createdbで指定した名前)、user名 (createuserで指定した名前)、password(現在は無視されます)を入力し、tableあるいはlistボタンを押します。tableボタンを押すと、指定されたデータベースに定義されたすべてのテーブルの概要がわかります。さらにテーブル名のリンクをクリックすれば、テーブルの内容を表示する画面に移行します。
listボタンは、そのデータベースにtable_policyというテーブルがあるときだけ意味を持ちます。詳細は次の節で述べます。


6. table_policyによるデータベースアクセスと表示の制御
table_policyは、PostgreSQLデータベースのテーブルをhttpdb.lを使って外部に公開するときの画面を制御します。次のようなsqlコマンドをpsqlの¥iコマンドで読み込ませることによりとりあえずの初期データを作ることができます。

drop table table_policy;
drop sequence table_policy_id_seq;

create table table_policy (
id serial primary key,
password text,
tablename text,
screen text,
auth text,
fields text,
links text,
title text,
description text);

-- records in the table_policy

insert into table_policy
(tablename, screen, fields, links, title)
values
('table_policy',
'top',
'(id)',
'(("Search" search) ("List" list))',
'table_policy top');

insert into table_policy
(tablename, screen, fields, links, title)
values
('table_policy',
'list',
'(id tablename screen fields links title)',
'(("Search" search) ("List" list) ("insert" insert) list-next list-previous)',
'table_policy list');

insert into table_policy
(tablename, screen, fields, links, title)
values
('table_policy',
'search',
'(tablename screen fields links title)',
'(("List" list))',
'table_policy search');

insert into table_policy
(tablename, screen, fields, links, title)
values
('table_policy',
'insert',
'(id tablename screen fields links)',
'(("List" list))',
'table_policy insert');

insert into table_policy
(tablename, screen, fields, links, title)
values
('table_policy',
'detail',
'(id tablename screen fields links title description)',
'(("List" list) change ("Delete" delete) change-password)',
'table_policy detail');

insert into table_policy
(tablename, screen, fields, links, title)
values
('table_policy',
'change',
'(tablename screen fields links title description)',
'(("List" list) ("Delete" delete))',
'update a table_policy record');

---- weather_report

insert into table_policy
(tablename, screen, fields, links, title)
values
('weather_report',
'top',
'(id)',
'(search list)',
'Weather_Report');


insert into table_policy
(tablename, screen, fields, links, title)
values
('weather_report',
'detail',
'(id district time abstract today tomorrow max_temp min_temp)',
'(("search" search) ("Delete" delete))',
'Detail of weather_report');

insert into table_policy
(tablename, screen, fields, links, title)
values
('weather_report',
'search',
'(id district time max_temp min_temp)',
'(("List" list))',
'Search in weather_report');

insert into table_policy
(tablename, screen, fields, links, title)
values
('weather_report',
'list',
'(id district abstract min_temp max_temp)',
'(("Search" search) ("Insert" insert))',
'list of weather_report');


-- people

insert into table_policy
(tablename, screen, fields, links, title)
values
('people',
'top',
'(id)',
'(("Search" search) ("List" list) ("Insert" insert))',
'People table');

insert into table_policy
(tablename, screen, fields, links, title)
values
('people',
'list',
'(id account kanji_lastname kanji_firstname email )',
'( search insert download )',
'list of people');

insert into table_policy
(tablename, screen, fields, links, title)
values
('people',
'detail',
'(id account kanji_lastname kanji_firstname romanji_firstname
romanji_lastname email tel_number secretary office)',
'(change delete search list)',
'Detail of a people record');

insert into table_policy
(tablename, screen, fields, title)
values
('people',
'search',
'(id account kanji_lastname kanji_firstname romanji_firstname
romanji_lastname email tel_number secretary office)',
'Search in the people table');

insert into table_policy
(tablename, screen, fields, title)
values
('people',
'change',
'(id account kanji_lastname kanji_firstname romanji_firstname
romanji_lastname email tel_number secretary office)',
'Search in the people table');

insert into table_policy
(tablename, screen, fields, title)
values
('people',
'insert',
'(id account kanji_lastname kanji_firstname romanji_firstname
romanji_lastname email tel_number secretary office)',
'Insert a new people record');

table_policyの主要なフィールドには、tablename, screen, fields, links, titleがあります。tablenameは、文字通り、動作制御の対象となるテーブルの名前を指定します。screenは、表示画面の種類です。データベースとwebの組み合わせには、アプリケーションに応じて無限の利用法がありますが、主な基本操作は、一覧表の表示、詳細表示、検索、レコードの追加と削除、データの更新、などになります。それらに対応して、list, detail, search, insert, changeなどのscreenを定義します。さらに、テーブルの付加情報を定義するために、仮想的にtopというscreenを用意しています。fields には、そのscreenで表示するフィールド名をかっこで囲まれたリストで表現します。linksには、ボタンでリンクする画面や制御を指定します。例として、list screenのlinksに、(search insert list-next list-previous) というリストを指定すると、探索画面、 新規レコード作成画面、次の一覧表、前の一覧表、にリンクするボタンが表示されます。mailを指定すると、選択されたレコードにemailフィールドがあれば、それらにemailを送信する画面になります。
list screen (一覧表示)では、レコードの先頭にあるidを選択すると、detail 画面に移行し、そのレコードの詳細が表示されます。detail screenのlinksに、delete, changeがあれば、各々、レコード削除、レコード更新の画面に移行します。一覧画面で、フィールド名をクリックすると、そのフィールドの内容でソートされます。テーブルの中には、idフィールドがなければなりません。idフィールドは、その値を決定すると、どのレコードかがユニークに制約されるものでなければなりません。たとえば、シリアル番号を与えるために、serial 型を宣言したフィールドなどが適当です。どのフィールドをidとするかは、top screenのfields リストの最初の要素で指定します。また、top screenのfieldsリストの二つ目の要素は、更新時間を記録するフィールドを指定します。idフィールドは、一覧表では必ず表示されるようにlist screenのfieldsに含めておく必要があります。さもないと、そのレコードの詳細情報を表示できません。
これらの情報をうまくtable_policyに記述しておくと、それだけで、各種のテーブルをよく似た方法で閲覧・更新・管理することが可能になります。table_policyは、ブラウザに表示される画面の動作を記述するので、一種のtable駆動型のプログラムといえます。table_pollicyもテーブルの一種であり、このテーブルを操作する方法がtable_policy自体の中に書かれている、というのは一種のreflectionともいえます。
そのときのURIはいずれも、http://www.site.jp/cgi-bin/eusdb.cgiであり、例のデータベースへのログイン画面からたどって行ってもよいのですが、普通の応用では、特定のデータベースの特定のテーブルに直行したいものです。どのテーブルのどの画面を見せるかは、?以下の引数で指定します。http://www.site.jp/cgi-bin/eusdb.cgi?database=matsui&user=matsui&port=5432&table=people&command=list は、peopleテーブルの一覧表を見せるURLとなります。

7. Fast-CGI
Apache web serverには、Fast-CGIという、CGIの起動効率を改善する仕組みを組み込むことができます。すなわち、通常のCGIが、リクエストの度にhttpdがCGI処理プロセスをforkして引数などを標準入力から送り、結果のhtmlドキュメントを標準出力から受け取るするのに対し、Fast-CGIは、Fast-CGI処理プロセスをずっと生きたままに維持し、ソケットで通信することでプロセスの起動にかかる時間を節約します。何もしないで終わるperlの起動には、400MHzのPentium-IIのLinux2.2で約0.03秒、eusの起動には約0.3秒かかります。pgsql.lをロードし、postgreSQLデータベースと接続すると、一回の起動に必要な最小の時間は0.45秒となります。euslispでCGIを書くと、毎秒1回以上の速度のリクエストには支障を来すことになります。さらに、一つのpostgreSQLテーブルを連続して操作する場合、それまでの操作の履歴を使いたくなりますが、CGIでは、毎回接続が切れますので、状態を維持できません。したがって、URIとして長々とした引数を毎回送る必要が生じます。これは、効率が悪いだけでなく、パスワードのような秘匿情報をたびたび送ることになり、セキュリティ上も問題となります。fast-CGIは、これらの問題を一挙に解決します(ただし、デバッグは多少面倒になります)。servletも似た機能を提供しますが、独立したプロセスになっていて、記述言語を選ばない分、fast-CGIの方が汎用的でしょう。
fast-cgiを利用するためには、まず、apacheに、mod_fastcgiをロードしておく必要があります。mod_fastcgiは、www.fastcgi.orgから入手できます。httpd.confにLoadModule fastcgi_module /usr/libexec/mod_fcgi.so, AddModule mod_fastcgi.c の2行を追加します。(LoadModuleは、shared objectの名前、AddModuleにはソースファイルの名前を指定するというのは、いかにもパッチワークですね。)また、srm.confには、fcgiのプログラムが置かれた場所とそのプログラムの名前を指定します。つまり、<Location /fcgi> SetHandler fastcgi-script </Location>を3行で、またAppClass /home/httpd/html/fcgi/eusdb.fcgi -port 9000のような行を定義します。
euslisp fcgiプログラムは、libfcgi.soをリンクします。libfcgi.soは、www.fastcgi.orgからダウンロードできるfcgi-devkit-2.1から作成します。普通にmake すると、libfcgi.aができてしまうので、shared objectが生成されるようにMakefileを作り替える必要があります。
EusLispでfcgi プログラムを記述する場合は、冒頭でlib/llib/httpfcgi.lをロードするようにします。このプログラムは、libfcgi.soをリンクして標準入出力をソケットに置き換え、apacheからの接続を待ちます。そのときのポート番号は、srm.confで指定した番号になります。接続が確立した後ですることは、通常のCGIプログラムとだいたい同じです。htmlヘッダを出力し、データベースを操作するのであれば、httpdbを呼び出します。このようなサイクルは、fcgi-loopというマクロで簡単に書けるので、次のようなソースプログラムを用意して置いて、httpd/html/fcgi/eusdb.fcgiなどのスクリプトから呼び出します。

% cat eusdb.fcgi
#! /bin/csh
setenv EUSDIR /usr/local/eus ; setenv LD_LIBRARY_PATH /usr/local/eus/Linux/lib
/usr/local/bin/eus /usr/local/eus/lib/demo/httpdb-fcgi.l
% cat /usr/local/eus/lib/demo/httpdb-fcgi.l
(load "/usr/local/eus/lib/llib/httpfcgi.l")
(http-loop (html-header) (gen "‾%‾%") (httpdb))


8. cookieによる接続の維持
先に述べたように、起動の高速性に加えて、fcgiを使う利点は、接続のステータスを保持できる点にあります。接続ステータスは、euslispでは、httpfcgi.lに定義された、fcgi-connectionというオブジェクトに保持されます。特定のweb browserからのリクエストが、どの接続に対応するかを識別するために、cookieを用います。cookieは、CGIあるいはFCGIプログラムからのHTTPヘッダの中でSet-Cookieディレクティブによってクライアントに伝えられ、web browserのメモリの中、あるいはクライアントPC上の特定のファイルの中に保持されます。cookieは、keyとvalueのペアを定義します。また、そのcookieが有効な期限、ドメインを定義します。CGIあるいはFCGIプログラムは、(html-header)を送出し、改行を連続して二つ送る前に(http ヘッダの中で)、set-cookie関数によってcookieの保存をブラウザに要請します。ブラウザは、URLを参照するたびにcookieに指定された有効期間と有効ドメインを検査し、有効なcookieをすでに蓄積して持っていれば、それをweb serverに送り出します。たとえば、www.eus.go.jpというweb serverが、connection-idをkeiとし、12345をvalueとしたcookieを保存しているとします。ユーザーがこのサーバーをホストフィールドに含むURLを参照し、cookieの有効期限がexpireしていなければ、このcookieをweb サーバーに送り返します。
web serverがクライアントからのcookieを読み込むと、それをHTTP_COOKIEという環境変数にセットして、CGIあるいはFCGIプログラムを呼び出します。EusLisp FCGI スクリプトは、接続を受け付けた直後にget-cookie関数を呼び出すことによって、*cookies*に、((key1 value1) (key2 value2) ...) というcookieのリストを作成します。同じweb serverの中の別のFCGIプログラムが送ったcookieと混同しないよう、アプリケーション (FCGIプログラム)は、互いに異なるkeyにvalueを対応づけなければなりません。
こうして、cookieが得られると、これを識別子にして*fcgi-connections*リストを走査して、指定されたkeyとvalueのペアを持つfcgi-connectionオブジェクトを探します。その際、cookieを送り出してからあまりに長時間が経過した接続オブジェクトがあれば、それを消去します。現在の実装では、cookieは、fcgiプロセスのメモリ中にだけ保持され、fcgiプロセスがエラーなどでリスタートすると、以前の接続ステータスは失われます。fcgi-connectionの中に維持される情報は、PostgreSQLデータベースとの接続(pq:pgsqlオブジェクト)とパスワードの認証履歴です。

9. パスワードと認証
Webから互いに見ず知らずの多数の人がCGIを経由してPostgreSQLのテーブルを操作すると、テーブルやレコードを不正なアクセスから保護する機構が必要になります。WELDでは、テーブル単位とレコード単位の2種類のプロテクションを行います。テーブルへのレコードの追加と削除には、そのテーブルに対するパスワード認証を必要とします。あるレコードの内容を書き換えるには、そのレコード固有のアクセスパスワードが必要です。前者をスクリーンパスワード、後者をレコードパスワードと呼びます。スクリーンパスワードは、table_policyの、delete および insert screenが記録されているレコードのpasswordフィールドに格納します。レコードパスワードは、保護したいテーブルのpasswordというフィールドに保存されます。つまり、レコードごとにパスワードでの保護をかけたいのなら、そのテーブルにはtext型の'password' フィールドが定義されていなければなりません。いずれのパスワードも、データベース中では暗号化されたテキストとして保存されています。したがって、忘れてしまったパスワードを思い出すことはできません。
新しいレコードを追加するときは、そのテーブルのdelete screenのパスワードに指定されたパスワードを入力します。レコードの内容を変更するときは、そのレコードのレコードパスワードの入力を求められます。レコードにパスワードが指定されていないときは、テーブルのchange screenのパスワードの入力を求めます。同様に、レコードを削除するときも、レコードパスワード、スクリーンパスワードの順に入力を求めます。

ここでで述べたパスワード認証は、FCGI/EusLispが独自に行う認証方法であって、PostgreSQLがデータベース接続をパスワードで保護するのとは異なります。参考までに、PostgreSQLのパスワード認証の方法を記しておきます。
PostgreSQLのアクセス制御は、データベースとホスト計算機とユーザーの組み合わせで行うものと、テーブルとユーザーの組み合わせで行うものの2種類があります。前者は、$PGSQL/data/pg_hba.confファイルで、後者は、grant/revokeなどのSQLコマンドで設定します。
ホスト計算機は、PostgreSQLサーバーが走っているそのマシンと、ネットワーク接続される他のマシンに分けられます。実際は、前者はunixドメインのソケットで接続され、後者はInternetドメインのソケットで接続されるという違いなので、PostgreSQLと同一マシンで走っていても、IP接続するものは後者と同様に扱われます。ホストコンピュータは、IPアドレスにマスクをかけることで、あるLANに接続されたホストのグループとして識別します。そのようなホストのいずれかが、あるデータベース、あるいはすべてのデータベースに接続しようとしたとき、そのマシンを信頼してアクセスを許すか (trust)、拒絶するか (reject)、それともパスワードでユーザーごとの認証をするか(password)、を選択します。passwordを選択するときは、pg_passwordプログラムによって、ユーザー名と暗号化されたパスワードを関連づけるファイルを作成し、それを$PGDATAディレクトリに置きます。
データベースjに接続した後、その中の特定のテーブルを見たり変更を加えたりできるかは、grant コマンドで設定します。grantを得たユーザーは、パスワードを入力することなくテーブルにアクセスできます。したがって、特定のユーザーにだけgrantを出したとしても、そのユーザーがホスト-データベース-パスワードの認証を経ずにデータベースに接続できる状態になっているとすると、そのユーザーの名前を知っている者は誰でもgrantを得ているのに等しくなります。したがって、grantを得たユーザーは、上記のようにそのデータベースに接続するときはパスワードの認証を経るようにし、pg_passwordによってパスワードを設定しておかなければ意味がありません。PostgreSQLでできるのは、テーブル単位でのアクセス制御までで、レコードごとの制御はできません。


Back To EusLisp Home