0
0

(CGI1)IBMiのAPIサーバをFFRPGのCGIで作成する(1)

Posted at

■概略

オープンソースエンジニア歴30年超の筆者が2023年からIBMiを学びだした学習記録です
IBMiをAPIサーバ(JSON)にするために、FFRPGでCGIを書いてみます

■IBMiでCGI

◯CGIサンプルソース(RPGLE)

CGIはCommon Gateway Interfaceの略でWWWサーバが動的コンテンツを返すプログラムを
呼び出す規格

*標準入力からパラメータを読込む
*標準出力にHTML,JSON等出力しWWWサーバ経由で返す

プログラムが標準入出力を扱える必要があるが、IBMiでは標準入出力が扱えない
そこで qhttpsvr/qzhbcgi というIBM提供のサービスプログラムを利用して
標準入出力を扱えるようにしたサンプルが下記

参考URL : https://www.ibm.com/support/pages/ile-rpg-cgi-programming-example
※ソースコードは上記リンクのsamplerpg.txt

◯FFRPGでCGIサンプルソース(RPGLE)を書き換える

*上記はRPGLEなのでFFRPGに書き換えた
*オープンソースの書き方にリファクタリングして、
 getCGIVars()とresponseHTTP()の関数にまとめてサービスプログラム化した
*getCGIVars()はCGIから読み込んだGET/POSTのパラメータをキー・バリューで取得する
*responseHTTP()はHTTPプロトコルで標準出力に出力する
*getCGIVars()とresponseHTTP()のソースコードは後述する
*呼出のソースは「(CGI1)IBMiのAPIサーバをFFRPGのCGIで作成する(1)」で記載する

◯HTTPプロトコル

responseHTTP()でCGIプログラムがコンテンツを応答する際、先頭に

Content-type: text/html\n\n(改行が2つ)

をつける必要がある
EBCDICでの改行は x'15' である

◯GETとPOST

CGIの呼出し方法は2つある

GET
1行のURLで呼び出す
http://{ホスト名}/cgi-bin/{PGM名}.PGM?param1=value1&param2=value2
PGM名の後 ? でパラメータ開始
param1=value1 の形式になり、起動や漢字はURLEncodeingする
param2以後は & でつなげる

POST
パラメータはURLには現れない
以下のHTMLで表示される、「送信」ボタンを押すとPGM名が呼び出される
param1とparam2がparam=valueで渡される
POSTはHTMLからのページ遷移が必要なので、API利用には原則GETを利用することになる
ただし元のサンプルがPOST実装もしてあったのでPOSTの値も受け取れる

呼出し元HTMLソース

<form action=/cgi-bin/{PGM名}.PGM method=post>
 <input type=text name=param1 value="value1">
 <input type=text name=param2 value="value2">
 <input type=submit value="送信">
</form>

■FFRPGでCGIのサービスプログラム

◯先頭部分

**free
// 2024/06/26 えむぼま高見禎成 作成
//
//【注意】
// POST & GETの読み込み最大長は1024バイト(prototype.ffrpgで指定)
// CGIの出力最大長は2048000バイト=2MB(prototype.ffrpgで指定)
// CGIで引き渡すパラメータは最大10まで(prototype.ffrpgで指定)
// Qtmh*呼出し時はvarchar不可。charでないとCGI実行エラーになる!
//
**free
ctl-opt nomain;
ctl-opt text('CGI共通ライブラリ');

/include ./prototype.ffrpg

◯getCGIVars()

// POST or GETから値を取得するサブプロシージャー
dcl-proc getCGIVars export;
    // 引数
    dcl-pi *n;
        CGIkeys   char(bufinlen) dim(cgivarsnum);
        CGIvalues char(bufinlen) dim(cgivarsnum);
    end-pi;

    // 変数
    dcl-s  datalen    bindec(9:0) inz(0);
    dcl-s  maxdatalen bindec(4:0) inz(1024);
    dcl-s  BufIn      char(bufinlen);
    dcl-s  i          packed(4:0);
    dcl-s  keyvalue   varchar(bufinlen);
    dcl-s  key        varchar(bufinlen);
    dcl-s  value      varchar(bufinlen);
    dcl-s  and_pos    packed(4:0) inz(0);
    dcl-s  eq_pos     packed(4:0) inz(0);
    dcl-s  count      packed(4:0) inz(1);
    // QtmhRdStIn(POST)
    dcl-s  StdIn      char(bufinlen);
    dcl-s  StdInLen   bindec(9:0);
    // QtmhGetEnv(GET)
    dcl-s  EnvIn      char(bufinlen);
    dcl-s  EnvLen     bindec(9:0);
    dcl-s  EnvName    char(25)    inz('CONTENT_LENGTH');
    dcl-s  APIErr     char(8000);
    dcl-s  tmp        char(bufinlen);

    // 環境変数REQUEST_METHODを読込む
    EnvName = 'REQUEST_METHOD';
    QtmhGetEnv(EnvIn : %len(EnvIn) : EnvLen : EnvName : %len(%trim(EnvName)) : APIErr);
    // POSTならCONTETN_LENGTH分標準入力から読込む
    if %subst(EnvIn:1:4) = 'POST';
        // 環境変数CONTENT_LENGTHを読込み最大長より大きければ最大長にする
        EnvName = 'CONTENT_LENGTH';
        QtmhGetEnv(EnvIn : %size(EnvIn) : EnvLen : EnvName : %len(%trim(EnvName)) : APIErr);
        //debug('envin='+EnvIn : 'TAKAMI');
        //debug('envlen='+%char(EnvLen) : 'TAKAMI');
        datalen = %dec(%subst(EnvIn:1:EnvLen):9:0);
        if maxdatalen < datalen;
            datalen = maxdatalen;
        endif;
        // 標準入力を読込む
        QtmhRdStin(StdIn : datalen : StdInLen : APIErr);
        BufIn = StdIn;
        datalen = StdInLen;
    // POST以外=:GETならQUERY_STRINGから読込む。最大長より大きければエラーメッセージにする
    else;
        EnvName = 'QUERY_STRING';
        QtmhGetEnv(EnvIn : %size(EnvIn) : EnvLen : EnvName : %len(%trim(EnvName)) : APIErr);
        if EnvLen > maxdatalen;
            BufIn = 'Data buffer not big enough for available input data';
            datalen = %len(BufIn);
        else;
            BufIn = EnvIn;
            datalen = EnvLen;
        endif;
    endif;

    // BufInの分解
    for i = 1 to %len(BufIn);
        and_pos = %scan('&' : BufIn : i);
        // &までをキー値ペアで取り出す
        if and_pos > 0;
            keyvalue = %subst(BufIn : i : and_pos - i );
            //debug('keyvalue:'+keyvalue+' i='+%char(i)+' and_pos='+%char(and_pos) : 'TAKAMI');
            // = でキーと値を分ける
            eq_pos   = %scan('=' : keyvalue : 1);
            if eq_pos > 0;
                key   = %subst(keyvalue : 1 : eq_pos - 1 );
                value = %subst(keyvalue : eq_pos + 1 : %len(keyvalue) - eq_pos );
                CGIkeys(count)   = key;
                CGIvalues(count) = value;
                //debug('key:'+key+' eq_pos='+%char(eq_pos) : 'TAKAMI');
                //debug('value:'+value+' eq_pos='+%char(eq_pos) : 'TAKAMI');
            endif;
            i = and_pos;
            count += 1;
        // &がなければ末尾までをキー値ペアで取り出す
        else;
            keyvalue = %subst(BufIn : i : %len(BufIn) - i);
            //debug('keyvalue:'+keyvalue+' i='+%char(i)+' and_pos='+%char(and_pos) : 'TAKAMI');
            // = でキーと値を分ける
            eq_pos   = %scan('=' : keyvalue : 1);
            if eq_pos > 0;
                key   = %subst(keyvalue : 1 : eq_pos - 1 );
                value = %subst(keyvalue : eq_pos + 1 : %len(keyvalue) - eq_pos );
                // 末尾の場合x'00'が入っていることがあるので除去する
                value = %trim(%replace('':value:%scan(x'00':value) : 1));
                CGIkeys(count)   = key;
                CGIvalues(count) = value;
                //debug('key:'+key+' eq_pos='+%char(eq_pos) : 'TAKAMI');
                //debug('value:'+value+' eq_pos='+%char(eq_pos) : 'TAKAMI');
            endif;
            leave;
        endif;
    endfor;
    return;
end-proc;

◯responseHTTP()

// 標準出力に出力するサブプロシージャー
dcl-proc responseHTTP export;
    // 引数
    dcl-pi *n;
        BufOut  char(bufoutlen);
    end-pi;

    // 定数
    dcl-c  constNL    x'15'; // EBCDICのNewlineを送ると改行になる。LFやCRではない
    dcl-s  httpHEADER varchar(80) inz('Content-type: text/html');
    // 変数
    dcl-s  APIErr     char(8000);

    // 先頭にHTTPヘッダーを追加する
    httpHEADER = httpHEADER + constNL + constNL;
    BufOut = httpHEADER + BufOut;
    QtmhWrStout(%trim(BufOut) : %size(%trim(BufOut)) : APIErr);
    return;
end-proc;

◯debug()

// デバッグプリント
// 表示メッセージは40桁まで
dcl-proc debug export;
    dcl-pi *N;
        message varchar(40) const; // デバッグ表示する文字列
        msgq    varchar(10) const; // 出力先メッセージキュー
    end-pi;

    dcl-s dt date(*JIS);
    dcl-s tm time(*JIS);
    dcl-s timestamp varchar(19);
    dcl-s debug varchar(50);

    dt  = %DATE();
    tm  = %TIME();
    //timestamp = %CHAR(dt) + ' ' + %CHAR(tm);
    timestamp = %CHAR(tm);
    debug = '[' + timestamp + ']' + message;
    dsply debug msgq;
end-proc;

◯prototype.ffrpg

**free
// 2024/05/14 えむぼま高見禎成 作成
//
// CGI(qhttpsvr/qzhbcgi)
//
dcl-c  END_OF_FILE  const('02000');
dcl-c  SYNTAX_ERROR const('42601');
dcl-c  BufInLen   1024;
dcl-c  BufOutLen  2048000;
dcl-c  CGIVarsNum 10;

dcl-pr QtmhRdStin ExtProc('QtmhRdStin');
    *n char(BufInLen);
    *n bindec(9:0) const;
    *n bindec(9:0);
    *n char(8000);
end-pr;
dcl-pr QtmhWrStout ExtProc('QtmhWrStout');
    *n char(BufOutLen)  const;
    *n bindec(9:0) const;
    *n char(8000);
end-pr;
dcl-pr QtmhGetEnv ExtProc('QtmhGetEnv');
    *n char(BufInLen);
    *n bindec(9:0) const;
    *n bindec(9:0);
    *n char(25)    const;
    *n bindec(9:0) const;
    *n char(8000);
end-pr;

// cgilib
dcl-pr getCGIVars;
    *n char(BufInLen) dim(CGIVarsNum);
    *n char(BufInLen) dim(CGIVarsNum);
end-pr;
dcl-pr responseHTTP;
    *n char(BufOutLen);
end-pr;

// デバッグプリント
dcl-pr debug;
    message varchar(40) const; // デバッグ表示する文字列
    msgq    varchar(10) const; // 出力先メッセージキュー
end-pr;

■サービスプログラムのコンパイルbash

◯個人ライブラリへ出力

#!/bin/bash
USER=`whoami`
#---
TARGET_LIB=${USER}"LIB"
MOD1="CGILIB"
SRVPGM="qhttpsvr/qzhbcgi"
DIR="/home/${USER}/CGIs/QFFRPGSRC"
#---
# CGIモジュール
file=$DIR"/"$MOD1".SRVFFRPG"
target=$TARGET_LIB"/"$MOD1
system "DLTMOD MODULE($target)"
system "CRTRPGMOD MODULE($target) SRCSTMF('$file') TGTCCSID(5035)" 2>&1

# サービスプログラム
system "DLTSRVPGM SRVPGM($target)"
system "CRTSRVPGM SRVPGM($target) MODULE($target) BNDSRVPGM($SRVPGM) EXPORT(*ALL)" 2>&1

◯developブランチをCGILIBへ出力

#!/bin/bash
USER=`whoami`
#---
TARGET_LIB='CGILIB'
MOD1="CGILIB"
SRVPGM="qhttpsvr/qzhbcgi"
DIR="/home/${USER}/CGIs/QFFRPGSRC"
#---
# gitでdevelopに変更し最新に更新する
git checkout develop && git pull origin --ff-only

# CGIモジュール
file=$DIR"/"$MOD1".SRVFFRPG"
target=$TARGET_LIB"/"$MOD1
system "DLTMOD MODULE($target)"
system "CRTRPGMOD MODULE($target) SRCSTMF('$file') TGTCCSID(5035)" 2>&1
# サービスプログラム
system "CRTSRVPGM SRVPGM($target) MODULE($target) BNDSRVPGM($SRVPGM) EXPORT(*ALL)" 2>&1

# 元のbranchに戻る
git checkout -
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0