WEB2IFSYJL – WEB TO IFS RPG USING YAJL (JSON)
AKA - A webservice to receive JSON from the Internet and save it to the IFS
Let's do this in reverse! What does that mean? Let's show the entire program, let you have a look and then we will go through it line by line and break it down.
The basic premise of this program is to:
The SQL RPGLE in more detail:
/title WEB2IFSYJL.sqlrpgle - Webservice listening for incoming JSON and storing in IFS
//
// Service - WEB2IFSYJL.sqlrpgle
//
// Function - This interface will be used to receive incoming
// connection containing JSON payload. The webservice body
// of JSON will be written to IFS(/HOME/NICKLITTEN)
//
// COMPILE NOTES:
//
// To compile:
// (1) Create a library called WEBSERVICE (this is referenced in
// the HTTP SERVER configuration. This is the library that
// all of these YAJL web services reside in)
// (2) compile the SQLRPGLE PGM into LIB(WEBSERVICE)
//
// CRTSQLRPGI OBJ(WEBSERVICE/WEB2IFSYJL)
// SRCSTMF('/home/nicklitten/source-webservices/WEB2IFSYJL.sqlrpgle')
// OBJTYPE(*PGM) DBGVIEW(*SOURCE) CVTCCSID(*JOB)
//
// Modification History:
// 2017-06-31 V1.0 Created by Nick Litten
// 2021-01-23 V1.1 Modernize to all free format
// 2024-09-28 V1.2 Add POST code for example data WRITE using JSON
// 2024-09-29 V1.3 Extra logic for GET/PUT/POST/DELETE
// 2024-10-02 V1.4 RPG standardize for webcourse on nicklitten.com
// 2024-10-11 V1.5 Add simple srvpgm.sqlrpgle for debug logging
ctl-opt
Main(WEB2IFSYJL)
pgminfo(*PCML:*MODULE:*DCLCASE)
option(*srcstmt:*nodebugio:*noshowcpy)
decedit('0.')
bnddir('YAJL':'QC2LE')
/if Defined(*CRTSQLRPGI)
dftactgrp(*no) actgrp('NICKLITTEN')
/endIf
copyright('WEB2IFSYJL: Version 1.4 October 2024');
// declare a good old PRINTER for easy debugging
dcl-f QPRINT printer(132) usropn;
dcl-ds line len(132) inz qualified;
printString char(132);
end-ds;
dcl-s method varchar(10);
dcl-s request char(500);
dcl-s env pointer;
dcl-s errMsg varchar(500) inz('Oops');
dcl-s webservice varchar(100);
dcl-s debug char(1);
// include the YAJL magic (this srcmbr lives in YAJL/QRPGLESRC by default)
/include YAJL_H
// declare a data structure that is used to store the SUCCESS/FAIL status and
// the ERRORMSG value. This template is referenced through this program.
dcl-ds rtnCode_Template qualified template;
success ind inz(*off);
errorMsg varchar(500) inz('');
end-ds;
// This is the variable we will populate with the IFS location that the
// incoming JSON payload will be stored at
dcl-s payloadIFS varchar(500);
// Global Data Structure to webservice return codes
dcl-ds ds_RtnCode likeds(rtnCode_Template) inz(*likeds);
dcl-ds psds PSDS qualified;
program char(10) pos(1);
procedureName *PROC;
statusCode *STATUS;
prvStatus zoned(5) pos(16);
srcListLineNum char(8) pos(21);
routineName *ROUTINE;
nbrPassedParms *PARMS;
exceptionType char(3) pos(40);
exceptionNumber char(4) pos(43);
pgmLib char(10) pos(81);
ExceptionData char(80) pos(91);
messageText char(80) pos(91);
ExceptionId char(4) pos(171);
date char(8) pos(191);
year zoned(2) pos(199);
lastFileUsed char(8) pos(201);
fileErrorInfo char(35) pos(209);
rpgFileRoutine char(6) pos(214);
rpgStatement char(6) pos(214);
fileRecordFormat char(8) pos(236);
jobUserNumber char(26) pos(244);
job char(10) pos(244);
jobName char(10) pos(244);
User char(10) pos(254);
jobUser char(10) pos(254);
number zoned(6) pos(264);
jobnumber zoned(6) pos(264);
jobnumberalpha char(6) pos(264);
jobDate zoned(6) pos(270);
runDate zoned(6) pos(276);
runTime zoned(6) pos(282);
runtimeHour char(2) pos(282);
runtimeMinute char(2) pos(284);
runtimeSecond char(2) pos(286);
createDate char(6) pos(288);
createTime char(6) pos(294);
compilerLevel char(4) pos(300);
srcFile char(10) pos(304);
srcLib char(10) pos(314);
srcMbr char(10) pos(324);
currentPgmProc char(10) pos(334);
currentModule char(10) pos(344);
currentUser char(10) pos(358);
systemName char(8) pos(396);
end-ds;
//-- WEB2IFSYJL - Process the incoming webservice JSON payload
//-- If a POST was requested (write) then read JSON payload and save to the IFS location
//-- returns *ON if successful, *OFF otherwise
dcl-proc WEB2IFSYJL;
dcl-pi WEB2IFSYJL end-pi;
monitor;
// Set SQL option, mainly to force cursor to close at endmodule
exec sql
set option naming=*sys,
commit=*none,
usrprf=*user,
dynusrprf=*user,
datfmt=*iso,
closqlcsr=*endmod;
// Initialize program variables
init();
// Now process the different incoming methods
select;
when method='GET';
methodGET(ds_RtnCode);
when method='PUT';
methodPUT(ds_RtnCode);
when method='POST';
methodPOST(ds_RtnCode);
when method='DELETE';
methodDELETE(ds_RtnCode);
other;
ds_RtnCode.errorMsg='* Fail - method('+method+') is not supported';
endsl;
if debug <> '';
line.printstring='Final:'+ds_RtnCode.errorMsg;
write QPRINT line;
close qprint;
endif;
// Send the JSON response document
sendBasicResponse(ds_RtnCode);
return;
on-error ;
dump(a);
dsply ('** ENDED ABNORMALLY ** '+webservice);
endmon ;
end-proc;
//-- methodGET() : If a GET was requested (READ) then read a file based
//-- in the input parm CUSTOMER payload and save to the IFS location
dcl-proc methodGET;
dcl-pi *n ind;
ds_methodGET likeds(rtnCode_Template);
end-pi;
ds_methodGET.success=*off;
ds_methodGET.errorMsg='* Fail - method(GET) is Invalid';
return ds_methodGET.success;
end-proc;
//-- methodPUT() : If a PUT was requested (write) then read JSON payload
//-- and save to the IFS location
dcl-proc methodPUT;
dcl-pi *n ind;
ds_methodPUT likeds(rtnCode_Template);
end-pi;
dcl-s docNode like(yajl_val);
dcl-s errMsg varchar(500);
dcl-s payload_ifs varchar(500);
payload_ifs='/home/nicklitten/WEB2IFSRPG-new-'+%char(%timestamp:*iso0)+'-PUT.json';
// get the JSON document loaded into memory
// **AND** save the payload direct to the IFS location
docNode=yajl_stdin_load_tree(*on:errMsg:payload_ifs);
if (docNode=*NULL) or (errMsg <> ' ');
ds_methodPUT.success=*off;
ds_methodPUT.errorMsg='* Fail - '+errMsg;
else;
ds_methodPUT.success=*on;
ds_methodPUT.errorMsg='Success. Json successfully stored at:'+
payload_ifs;
//++++++++++++++++++++++++++++++++++++++++++++++
// Optionally, you could process JSON right now!
//++++++++++++++++++++++++++++++++++++++++++++++
endif;
yajl_tree_free(docNode);
return ds_methodPUT.success;
end-proc;
//-- methodPOST() : If a POST was requested (write) then read JSON
//-- payload and save to the IFS location
dcl-proc methodPOST;
dcl-pi *n ind;
ds_methodPOST likeds(rtnCode_Template);
end-pi;
dcl-s docNode like(yajl_val);
dcl-s errMsg varchar(500);
dcl-s payload_ifs varchar(500);
payload_ifs='/home/nicklitten/WEB2IFSRPG-new-'+
%char(%timestamp:*iso0)+'-POST.json';
// get the JSON document loaded into memory
// **AND** save the payload direct to the IFS location
docNode=yajl_stdin_load_tree (*on: errMsg : payload_ifs);
if (docNode=*NULL) or (errMsg <> ' ');
ds_methodPOST.success=*off;
ds_methodPOST.errorMsg='* Webservice('+%trim(psds.program)+
') failed with error: '+errMsg;
else;
ds_methodPOST.success=*on;
ds_methodPOST.errorMsg='Webservice('+%trim(psds.program)+
') Success. Json successfully stored at:'+
payload_ifs;
//++++++++++++++++++++++++++++++++++++++++++++++
// Optionally, you could process JSON right now!
//++++++++++++++++++++++++++++++++++++++++++++++
endif;
yajl_tree_free(docNode);
return ds_methodPOST.success;
end-proc;
//-- methodDELETE() : If a DELETE was requested (DELETE) then
//-- delete the requested data
dcl-proc methodDELETE;
dcl-pi *n ind;
ds_methodDELETE likeds(rtnCode_Template);
end-pi;
ds_methodDELETE.success=*off;
ds_methodDELETE.errorMsg='Webservice('+%trim(psds.program)+' method(DELETE) is Invalid';
return ds_methodDELETE.success;
end-proc;
//-- init() : program Initilisation
dcl-proc init;
dcl-pi *n ind end-pi;
// This procedure will return the webservice METHOD
dcl-pr getenv pointer extproc(*dclcase);
var pointer value options(*string);
end-pr;
// This is optional - because you can set the *LIBL in the webservice
// setup within the Integrated Webserver Setup.
// I prefer to use a LIBL Control Application here but for this
// example I *could* do a simple CHGLIBL.
exec sql
call qsys2.qcmdexc('CHGLIBL (QTEMP NICKLITTEN PROJEX3RD QGPL)');
clear ds_RtnCode;
// if the DEBUG=Y parm has come in from the webserver start the debug
// print and store a more detailed version of variable 'webserver'
if debug <> '';
open qprint;
webservice=%trim(psds.program)+'('+%trim(psds.jobNumber)+
'/'+%trim(psds.jobuser)+'/'+%trim(psds.job)+'): ';
line.printstring='Debug for:'+webservice;
write QPRINT line;
else;
webservice=%trim(psds.jobNumber)+': ';
endif;
// set IFS location for the payload JSON to be stored
payloadIFS='/home/webservice/'+%trim(psds.program)+'-'+%char(%timestamp:*iso0)+'.txt';
// Retrieve the HTTP method - Default to GET if not provided
env=getenv('REQUEST_METHOD');
if env <> *null;
method=%upper(%str(env));
else;
method='GET';
endif;
if debug <> '';
CALL QSYS2.IFS_WRITE('DEBUG-AFTER method:'+method,
END_OF_LINE => 'CRLF')
endif;
return *on;
end-proc;
//-- sendBasicResponse() : Send the Basic JSON response to the calling
dcl-proc sendBasicResponse;
dcl-pi *n ind;
ds_sendBasicResponse likeds(rtnCode_Template) const;
end-pi;
yajl_genOpen(*on);
yajl_beginObj();
yajl_addBool('success': ds_sendBasicResponse.success);
yajl_addChar('errorMsg': ds_sendBasicResponse.errorMsg);
if debug <> '';
yajl_addChar('DEBUG': webservice);
endif;
yajl_endObj();
errMsg=ds_sendBasicResponse.errorMsg;
if ds_sendBasicResponse.success;
yajl_writeStdout(200: errMsg);
else;
yajl_writeStdout(500: errMsg);
endif;
yajl_genClose();
return ds_sendBasicResponse.success;
end-proc;
//-- sendDataResponse() : Send the Basic JSON response to the calling
dcl-proc sendDataResponse;
dcl-pi *n ind;
sendDataResponse likeds(rtnCode_Template) const;
end-pi;
yajl_genOpen(*on);
yajl_beginObj();
yajl_beginArray(%trim(psds.program));
yajl_beginObj();
yajl_addBool('success': sendDataResponse.success);
yajl_addChar('errorMsg': sendDataResponse.errorMsg);
yajl_endObj();
yajl_endArray();
yajl_endObj();
errMsg=sendDataResponse.errorMsg;
if sendDataResponse.success;
yajl_writeStdout(200: errMsg);
else;
yajl_writeStdout(500: errMsg);
endif;
yajl_genClose();
return sendDataResponse.success;
end-proc;
Next Step - Testing this webservice!!
Postman is a popular tool used for testing APIs (Application Programming Interfaces). It provides a user-friendly interface that makes it easy to send HTTP requests and view the responses. With Postman, you can test various aspects of an API such as request/response structure, error handling, authentication, and performance. It supports various HTTP methods like GET, POST, PUT, DELETE, and more. Additionally, Postman allows you to save your requests, organize them into collections, and share them with others. This makes it a valuable tool for API developers, testers, and consumers alike.
Dive into the Testing Webservices Lesson to do some testing with me
An Alternative Idea
I dont want to use the IFS - I want to process the JSON immediately!
The function JSON_copyBuf() transfers JSON data into a memory buffer or a program variable, rather than writing it to the IFS. This allows for the processing of the JSON and the execution of any business logic in real-time.