■概略
オープンソースエンジニア歴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¶m2=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 -