mod_owa を使ってみる
mod_plsqlと(ほぼ)互換性があるPL/SQLゲートウェイにmod_owa(Apache PL/SQL Gateway Module)がある。
mod_plsqlはOracleのアプリケーションサーバに同梱されているが、どうやらOracle版のApacheでしか動作してくれない。つまり、mod_plsqlのモジュールを他のApacheに持って行って使うことができない。Oracle版のApacheを使うにもいろいろとダウンロードやセットアップが必要なのでどうにも面倒だ。mod_plsql単独で使いたい・・・が使えない。それならば、ということで探して出て来たのがmod_owaだ。
このmod_owaは特にOracle版ではない通常のApacheでも使うことができる。httpd.confでLoadModuleし、設定をLocationディレクティブに追加すれば即使用できるというシンプルな作りだ。
導入
モジュールの入手
Apache PL/SQL Gateway Moduleの先頭にあるFilesからunix_all.tgz(64bit Linux用のバイナリを含む)をダウンロードする。
モジュールの配置
ダウンロードしたモジュールを展開し、/opt/modowa として配置する。
httpd.conf への組み込み
/etc/httpd/conf.d/mod_owa.conf を以下のとおり作成する。
- Apache 2.2系であったので、/opt/modowa/apache22/mod_owa.so を使用する。(2.4系の場合はapache24とする)
- OwaUseridには接続するDBユーザ/パスワード@TNS名を指定する。sqlplusで接続するときと同じ。
LoadModule owa_module /opt/modowa/apache22/mod_owa.so <Location /owa> Options None Order deny,allow Allow from all SetHandler owa_handler OwaUserid sandbox/password@tnsname OwaNLS Japanese_Japan.AL32UTF8 OwaLog "/var/log/httpd/mod_owa.log" OwaCharset "UTF8" OwaUploadMax 20M OwaDocLobs BIN OwaDocPath download OwaDocProc MOD_OWA_SANDBOX.DOWNLOAD </Location>
Apacheの環境変数に ORACLE_HOME と LD_LIBRARY_PATH を追加
/etc/sysconfig/httpd に環境変数設定を追加する。
# for mod_owa configuration export ORACLE_HOME=/opt/oracle/product/dbms.1 export LD_LIBRARY_PATH="$ORACLE_HOME/lib:$LD_LIBRARY_PATH" export NLS_LANG=Japanese_Japan.AL32UTF8
glibcのバージョンに関する留意点
glibc 2.12 の環境で service httpd start して起動したときにglibc 2.14 が必要ということで起動に失敗した。
Starting httpd: httpd: Syntax error on line 221 of /etc/httpd/conf/httpd.conf:
Syntax error on line 1 of /etc/httpd/conf.d/mod_owa.conf:
Cannot load /opt/modowa/apache22/mod_owa.so into server:
/lib64/libc.so.6: version `GLIBC_2.14' not found (required by /opt/modowa/apache22/mod_owa.so)
再リンクで解決する。apache22ディレクトリ内の mod_owa.so を削除して、同ディレクトリ内で make -kf modowa.mk にて再リンクすればよい。
sandbox ユーザの環境準備
DBAユーザでユーザの作成
CREATE USER SANDBOX IDENTIFIED BY password DEFAULT TABLESPACE USERS TEMPORARY TABLESPACE TEMP ACCOUNT UNLOCK; ALTER USER SANDBOX QUOTA UNLIMITED ON USERS; GRANT CREATE SESSION, CREATE TABLE, CREATE VIEW, CREATE SYNONYM, CREATE PROCEDURE TO SANDBOX;
sandbox ユーザのオブジェクト作成
--BLOB保存用 CREATE TABLE BLOB_REPOSITORY ( name VARCHAR2(100), mime VARCHAR2(100), blobdata BLOB, CONSTRAINT BLOB_REPOSITORY_PK PRIMARY KEY (name) ) LOB (blobdata) STORE AS BASICFILE; --アップロード処理用の一時表 CREATE GLOBAL TEMPORARY TABLE SESSION_STORE ( g VARCHAR2(4000), k VARCHAR2(4000), v VARCHAR2(4000) ) ON COMMIT PRESERVE ROWS; CREATE PROCEDURE CLEAR_SESSION_STORE( g_ VARCHAR2 := NULL ) IS BEGIn IF g_ IS NOT NULL THEN DELETE FROM SESSION_STORE WHERE g = g_; ELSE DELETE FROM SESSION_STORE; END IF; END; / CREATE PROCEDURE SET_SESSION_STORE( k_ VARCHAR2, v_ VARCHAR2, g_ VARCHAR2 := '-' ) IS cnt NUMBER; BEGIN SELECT count(*) INTO cnt FROM SESSION_STORE WHERE g = g_ AND k = k_; IF cnt > 0 THEN DELETE FROM SESSION_STORE WHERE g = g_ AND k = k_; END IF; INSERT INTO SESSION_STORE VALUES(g_, k_, v_); END; / CREATE FUNCTION GET_SESSION_STORE( k_ VARCHAR2, g_ VARCHAR2 := '-' ) RETURN VARCHAR2 IS v_ VARCHAR2(4000); BEGIN SELECT v INTO v_ FROM SESSION_STORE WHERE g = g_ AND k = k_; return v_; EXCEPTION WHEN NO_DATA_FOUND THEN return NULL; END; /
一通りの処理をmod_owaで実装してみる
CREATE OR REPLACE PACKAGE MOD_OWA_SANDBOX IS PROCEDURE HELLOWORLD; PROCEDURE HELLOWORLDNAME( name VARCHAR2 ); PROCEDURE FLEXIBLE( names IN OWA.vc_arr, vals IN OWA.vc_arr ); PROCEDURE DOWNLOAD( argc NUMBER, names IN OWA.vc_arr, vals IN OWA.vc_arr, res IN OWA.vc_arr, mimetype OUT VARCHAR2, blobloc OUT BLOB ); PROCEDURE UPLOAD; PROCEDURE UPLOAD( argc NUMBER, names IN OWA.vc_arr, vals IN OWA.vc_arr, res IN OWA.vc_arr, mimetype IN OUT VARCHAR2, blobloc OUT BLOB ); END; / CREATE OR REPLACE PACKAGE BODY MOD_OWA_SANDBOX IS --とりあえず http://hostname/owa/MOD_OWA_SANDBOX.HELLOWORLD PROCEDURE HELLOWORLD AS BEGIn HTP.P('HELLOWORLD2014'); END; --パラメータ有り http://hostname/owa/MOD_OWA_SANDBOX.HELLOWORLDNAME?name=ikeyanagi PROCEDURE HELLOWORLDNAME( name VARCHAR2 ) AS BEGIn HTP.P('HELLOWORLD, '||name); END; --任意のパラメータ渡し http://hostname/owa/!MOD_OWA_SANDBOX.FLEXIBLE?name=ikeyanagi&phone=090-0000-0000 PROCEDURE FLEXIBLE( names IN OWA.vc_arr, vals IN OWA.vc_arr ) AS BEGIN HTP.HTMLOPEN; HTP.HEADOPEN; HTP.TITLE('FLEXIBLE PARAMETER PASSING'); HTP.HEADCLOSE; HTP.BODYOPEN; HTP.TABLEOPEN; HTP.TABLEROWOPEN; HTP.TABLEHEADER('NAME'); HTP.TABLEHEADER('VALUE'); HTP.TABLEROWCLOSE; FOR i IN 1 .. names.count LOOP HTP.TABLEROWOPEN; HTP.TABLEDATA(names(i)); HTP.TABLEDATA(vals(i)); HTP.TABLEROWCLOSE; END LOOP; HTP.TABLECLOSE; HTP.BODYCLOSE; HTP.HTMLCLOSE; END; --BLOBデータのダウンロード http://hostname/owa/download/takigawa.jpg PROCEDURE DOWNLOAD( argc NUMBER, names IN OWA.vc_arr, vals IN OWA.vc_arr, res IN OWA.vc_arr, mimetype OUT VARCHAR2, blobloc OUT BLOB ) IS filename VARCHAR2(4000); BEGIN --OwaDocPathに指定したパスも含めて渡されるため、OwaDocPathのパスを削除する filename := REPLACE(vals(1),'/download/'); --bloblocにセットすれば呼び出し元で読み取り返してくれる SELECT mime, blobdata INTO mimetype, blobloc FROM BLOB_REPOSITORY WHERE name = filename; EXCEPTION WHEN NO_DATA_FOUND THEN --エラーが起こって返せない場合はbloblocにNULLをセットする --ここで返したページが表示される blobloc := NULL; HTP.HTMLOPEN; HTP.HEADOPEN; HTP.TITLE('DOWNLOAD CONTENTS NOT FOUND'); HTP.HEADCLOSE; HTP.BODYOPEN; HTP.HEADER(1, 'FILE NOT FOUND'); HTP.TABLEOPEN; HTP.TABLEROWOPEN; HTP.TABLEHEADER('NAME'); HTP.TABLEHEADER('VALUE'); HTP.TABLEROWCLOSE; FOR i IN 1 .. names.count LOOP HTP.TABLEROWOPEN; HTP.TABLEDATA(names(i)); HTP.TABLEDATA(vals(i)); HTP.TABLEROWCLOSE; END LOOP; HTP.TABLECLOSE; HTP.BODYCLOSE; HTP.HTMLCLOSE; END; --ファイルのアップロード http://hostname/owa/MOD_OWA_SANDBOX.UPLOAD PROCEDURE UPLOAD IS BEGIN HTP.p(' <html> <head> <title>UPLOAD</title> </head> <body>'); FOR r IN (SELECT * FROM SESSION_STORE WHERE g = 'UPLOAD' ORDER BY k) LOOP HTP.p('<div style="border: solid 2px gold; background-color: beige;">UPLOADED! '||r.v||'</div>'); END LOOP; CLEAR_SESSION_STORE('UPLOAD'); COMMIT; HTP.p(' <form action="MOD_OWA_SANDBOX.UPLOAD" enctype="multipart/form-data" method="POST"> <p>File to upload: <input type="file" name="file1"></p> <p>File to upload: <input type="file" name="file2"></p> <p><input type="submit" value="start upload"></p> </form>'); HTP.p(' <div>Uploaded files</div> <ul>'); FOR r IN (SELECT * FROM BLOB_REPOSITORY) LOOP HTP.p('<li><a href="download/'||r.name||'">'||r.name||'</a></li>'); END LOOP; HTP.p(' </ul>'); HTP.p(' </body> </html>'); END; PROCEDURE UPLOAD( argc NUMBER, names IN OWA.vc_arr, vals IN OWA.vc_arr, res IN OWA.vc_arr, mimetype IN OUT VARCHAR2, blobloc OUT BLOB ) IS BEGIN IF names(2) IS NULL THEN --アップロード完了 UPLOAD; RETURN; END IF; IF vals(2) IS NOT NULL THEN --ファイル指定が無い場合はNULLになるので無視 SET_SESSION_STORE(names(2),vals(2),'UPLOAD'); INSERT INTO BLOB_REPOSITORY VALUES(vals(2), mimetype, EMPTY_BLOB()) RETURN blobdata INTO blobloc; END IF; END; END; /
ファイルのアップロード・ダウンロード
アップロードの仕組みは mod_plsql と mod_owa で大きく異なる。mod_plsql は専用の表に mod_plsql 側で格納し、格納した行を識別する為のキーをプロシージャは受け取っていた。対して mod_owa ではファイルのアップロードが必要な都度、ACTIONで指定されたプロシージャが呼び出され、保存先としてBLOBのロケータを返さなければならない。全てのファイルのアップロードが終わると、ファイル名(names(2))が空の状態でACTIONで指定されたプロシージャが呼び出されるので、レスポンスを返す。
ダウンロードは mod_owa.conf の「OwaDocPath」に指定したパス(ここでは download)配下へのアクセスが発生した際に「OwaDocProc」に指定したプロシージャ(ここでは MOD_OWA_SANDBOX.DOWNLOAD)が呼び出されることを利用する。
アップロード時にアップロードされたファイルの名前などが、最後の呼び出しの際にはわからなくなってしまう。(namesやvalsにはアップロードしたファイルの名前や、BLOBを識別する情報が無い) そのため、mod_owa とは別のところで管理を行わなければならず、なかなか面倒になっている。今回は一時表を使って実装してみた。ACTIONで指定されたプロシージャは複数回(ファイル数+最後の1回)呼び出されるが、DBのセッションとしては同一であるため、セッションレベルの一時表であれば情報を引き継ぐ事ができる。ただし、HTTPリクエストごとにコネクションが作成されているわけではなく、コネクションは(1接続/1プロセスであれ)プールされているため、明示的にDELETEが必要だ。