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

# <u>■概略</u>
オープンソースエンジニア歴30年超の筆者が2023年からIBMiを学びだした学習記録です
[(CGI1)IBMiのAPIサーバをFFRPGのCGIで作成する(1)」](https://qiita.com/takami_embomajp/items/86730e2403b9265a39f2) で作成したサービスプログラム内の
関数を呼び出します

# ■CGIの要件
・以下のテーブルから指定した物件CDに該当する戸数を取得しJSONで返す
・物件CD未指定ときは 0001 を返す
・POST & GETの読み込み最大長は1024バイト(prototype.ffrpgで指定)  
・CGIの出力最大長は2048000バイト=2MB(prototype.ffrpgで指定)  
・CGIで引き渡すパラメータは最大10まで(prototype.ffrpgで指定)  

### <u>◯対象テーブルとフィールド</u>
BKNLIBF.GMBKAF(物件マスター)
<table border="1" style="border-collapse:collapse;width:800px">
<tr bgcolor=silver><th>COLUMN_NAME</th><th>COLUMN_TEXT</th><th>DATA_TYPE</th><th>LENGTH</th></tr>
<tr><td>BKA001</td><td>物件CD</td><td>CHAR</td><td>4</td></tr>
<tr><td>BKA002</td><td>物件枝番</td><td>NUMERIC</td><td>2</td></tr>
<tr><td>BKA003</td><td>戸数</td><td>NUMERIC</td><td>4</td></tr>
</table>

### <u>◯SQL</u>
```
select * from BKNLIBF.GMBKAF
 where BKA001='{物件CD}'
```

### <u>◯URL(入力条件)</u>
http://{サーバ}:8080/cgi-bin/BKNKOSU.PGM?bukken_cd={物件CD}  

### <u>◯JSON(出力条件)</u>
[{"bukken_cd":"物件CD","KOSU":"戸数"}]

# ■CGIのソースコード
### ◯POSTとGETから物件CD取得
getCGIVars()関数を呼び、パラメータをCGIkeysとCGIvaluesにいれる  

メイン
```
exsr srGetCGIVars;    // POSTとGETからパラメータを取得
```
サブルーチン  
```
begsr srGetCGIVars;
    getCGIVars(CGIkeys : CGIvalues);
    for i = 1 to CGIVarsNum;
        if     CGIkeys(i) = 'bukken_cd';
            bukken_cd = %trim(CGIvalues(i));
        endif;
    endfor;
endsr;
```

### ◯SQL生成
実行するSQLを文字列として生成する  

**注意) '(シングルクォーテーション)の扱い**  
　文字列フィールドを条件に指定する場合は、値を ' で囲む必要がある  
　例 : where 文字列フィールド='値'  
　RPGの文字列変数の値は'(シングルクォーテーション)で囲む必要がある  
　"(ダブルクォーテーション)は使えない  
　よって文字列に '(シングルクォーテーション)を含めるには、
　'' とシングルクォーテーションを2回繰り返すことでエスケープになる  
　文字列変数 = '''' → 文字列変数の値は '(シングルクォーテーション)1個  

メイン
```
exsr srCreateSQL;     // SQL生成
```
サブルーチン  
```
begsr srCreateSQL;
    sql_str   = 'select * from BKNLIBF.GMBKAF';
    where_str = ' where BKA001=''' + bukken_cd + '''';
    if bukken_cd <> '';
        sql_str += where_str;
    endif;
endsr;
```
### ◯SQL実行と結果変数resultの取得
SQL組込み関数のカーソル機能で複数行を取得し、結果配列変数resultに代入する  

なおresultはOS7.4から利用可能な動的配列を利用している  
結果数は不明なので、初期配列数は0、最大20000まで動的に拡張する  

メイン
```
exsr srExecSQL;       // SQLを実行しresultを取得する
```
サブルーチン  
```
begsr srExecSQL;
    exec sql prepare sql1 from :sql_str;
    exec sql declare csr1 cursor for sql1;
    exec sql open csr1;
    dou SQLSTATE = END_OF_FILE;
        exec sql fetch csr1 into :dsGMBKAF;
        if SQLSTATE <> END_OF_FILE;
            i = %elem(result) + 1;
            result(i).key1 = 'bukken_cd';
            result(i).val1 = %char(dsGMBKAF.BKA001);
            result(i).key2 = 'KOSU';
            result(i).val2 = %char(dsGMBKAF.BKA003);
        endif;
    enddo;
    exec sql close csr1;
endsr;
```
### ◯結果変数resultをJSONに変換し出力

メイン
```
exsr srConvertJSON;   // resultをJSON変換しBufOutに入れる
responseHTTP(BufOut); // 出力する    
```
サブルーチン  
```
begsr srConvertJSON;
    BufOut = '[';
    for i=1 to %elem(result);
        BufOut = %trim(BufOut) + '{';
        BufOut = %trim(BufOut) +
            '"' + result(i).key1 +'":' +
            '"' + result(i).val1 +'",' +
            '"' + result(i).key2 +'":' +
            '"' + result(i).val2 +'"';
        // JSON末尾の , はトル
        if i = %elem(result);
            BufOut = %trim(BufOut) + '}';
        else;
            BufOut = %trim(BufOut) + '},';
        endif;
    endfor;
    BufOut = %trim(BufOut) + ']';
endsr;
```

### ◯CGIの全ソースコード
prototype.ffrpgは(CGI1)IBMiのAPIサーバをFFRPGのCGIで作成する(1)と同じ
```
**free
// 2024/06/26 えむぼま高見禎成 作成
//
//【注意】
// POST & GETの読み込み最大長は1024バイト(prototype.ffrpgで指定)
// CGIの出力最大長は2048000バイト=2MB(prototype.ffrpgで指定)
// CGIで引き渡すパラメータは最大10まで(prototype.ffrpgで指定)
// Qtmh*呼出し時はvarchar不可。charでないとCGI実行エラーになる!
//
ctl-opt main(main);
ctl-opt text('物件別戸数取得CGI');

// プロトタイプ
/include ./prototype.ffrpg

// メイン関数
dcl-proc main;
    // CGI用変数(大文字始まりは外部連携利用)
    dcl-s  CGIkeys    char(BufInLen) dim(CGIVarsNum);
    dcl-s  CGIvalues  char(BufInLen) dim(CGIVarsNum);
    dcl-s  BufOut     char(BufOutLen);
    // 変数
    dcl-s  i          packed(4:0);
    dcl-s  bukken_cd  char(4) inz('0001');
    dcl-s  sql_str    varchar(255) inz('');
    dcl-s  where_str  varchar(255) inz('');
    // レコード様式 [JSON形式に応じて要変更]
    dcl-ds dsGMBKAF extname('GMBKAF') qualified;
    end-ds;
    // JSON展開前の結果変数 [JSON形式に応じて要変更]
    // 動的配列の最大サイズの16MBを超えないように定義すること
    // 物件数は7000強なので最大20000にした
    dcl-ds result     dim(*auto : 20000) qualified;
        key1 varchar(20) inz('');
        val1 varchar(20) inz('');
        key2 varchar(20) inz('');
        val2 varchar(20) inz('');
    end-ds;

    exsr srGetCGIVars;    // POSTとGETからパラメータを取得
    exsr srCreateSQL;     // SQL生成
    exsr srExecSQL;       // SQLを実行しresultを取得する
    exsr srConvertJSON;   // resultをJSON変換しBufOutに入れる
    responseHTTP(BufOut); // 出力する
    return;

    // -----------------サブルーチン---------------------
    // POSTとGETからパラメータを取得
    begsr srGetCGIVars;
        getCGIVars(CGIkeys : CGIvalues);
        for i = 1 to CGIVarsNum;
            if     CGIkeys(i) = 'bukken_cd';
                bukken_cd = %trim(CGIvalues(i));
            elseif CGIkeys(i) = 'token';
                token = %trim(CGIvalues(i));
            endif;
        endfor;
    endsr;

    // SQL生成 --ここにSQLを組込む
    // bukken_cdがなければ全件(whereなし)
    begsr srCreateSQL;
        sql_str   = 'select * from PMLIBF.GMBKAF';
        where_str = ' where BKA001=''' + bukken_cd + '''';
        if bukken_cd <> '';
            sql_str += where_str;
        endif;
    endsr;

    // SQLカーソルでresult取得 
    begsr srExecSQL;
        exec sql prepare sql1 from :sql_str;
        if SQLSTATE = SYNTAX_ERROR;
            leavesr;
        endif;
        exec sql declare csr1 cursor for sql1;
        exec sql open csr1;
        dou SQLSTATE = END_OF_FILE;
            exec sql fetch csr1 into :dsGMBKAF;
            if SQLSTATE <> END_OF_FILE; 
                i = %elem(result) + 1;
                result(i).key1 = 'bukken_cd';
                result(i).val1 = %char(dsGMBKAF.BKA001);
                result(i).key2 = 'KOSU';
                result(i).val2 = %char(dsGMBKAF.BKA003);
            endif;
        enddo;
        exec sql close csr1;
    endsr;

    // JSONに変換 
    begsr srConvertJSON;
        BufOut = '[';
        for i=1 to %elem(result);
            BufOut = %trim(BufOut) + '{';
            BufOut = %trim(BufOut) +
                '"' + result(i).key1 +'":' + 
                '"' + result(i).val1 +'",' + 
                '"' + result(i).key2 +'":' + 
                '"' + result(i).val2 +'"';
            // JSON末尾の , はトル
            if i = %elem(result);
                BufOut = %trim(BufOut) + '}';
            else;
                BufOut = %trim(BufOut) + '},';
            endif;
        endfor;
        BufOut = %trim(BufOut) + ']';
    endsr;
end-proc;
```

# ■CGIのコンパイルbash
### ◯個人ライブラリにコンパイル
```
#!/bin/bash
USER=`whoami`
#---
TARGET_LIB=${USER}"LIB"
PGM="BKNKOSU"
SRVPGM="CGILIB"
DIR="/home/${USER}/CGIs/QFFRPGSRC"
#---
# メインモジュール
file=$DIR"/"$PGM".SQLFFRPG"
target=$TARGET_LIB"/"$PGM
target_srv=$TARGET_LIB"/"$SRVPGM
system "DLTMOD MODULE($target)"
system "CRTSQLRPGI OBJ($target) SRCSTMF('$file') OBJTYPE(*MODULE) COMPILEOPT('TGTCCSID(5035)') RPGPPOPT(*LVL2)"

# プログラム
system "DLTPGM PGM($target)"
system "CRTPGM PGM($target) MODULE($PGM) BNDSRVPGM($SRVPGM)" 2>&1
```

### ◯developブランチをCGILIBへ出力
```
#!/bin/bash
USER=`whoami`
#---
TARGET_LIB='CGILIB'
PGM="CGITemplate"
SRVPGM="CGILIB"
DIR="/home/${USER}/CGIs/QFFRPGSRC"
#---
# gitでdevelopに変更し最新に更新する
git checkout develop && git pull origin --ff-only

# メインモジュール
file=$DIR"/"$PGM".SQLFFRPG"
target=$TARGET_LIB"/"$PGM
target_srv=$TARGET_LIB"/"$SRVPGM
system "DLTMOD MODULE($target)"
system "CRTSQLRPGI OBJ($target) SRCSTMF('$file') OBJTYPE(*MODULE) COMPILEOPT('TGTCCSID(5035)') RPGPPOPT(*LVL2)"

# プログラム
system "DLTPGM PGM($target)"
system "CRTPGM PGM($target) MODULE($PGM) BNDSRVPGM($SRVPGM)" 2>&1

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