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
Apacheを再起動して準備完了

service httpd restart

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が必要だ。