Welcome to XMLSERVICE’s documentation!¶
XMLSERVICE Introduction¶
XMLSERVICE Introduction¶
XMLSERVICE is Open Source RPG, created for web programming simplicity and deployment flexibility, any language, any transport, avoiding complexities dealing with big package ‘web services’. Internally, XMLSERVICE is designed Plain Old XML (POX), which enables simple REST XML protocol, avoiding complexity of SOAP/WSDL based Web Services. XMLSERVICE is 100% RPG, never requires Java, PHP, or any other front end language to serve your IBM i RPG programs to the web. In fact, XMLSERVICE is so simple, that you can almost run your entire IBM i machine using only simple HTML/XML (demonstrated later section).
XMLSERVICE is a single library of Open Source RPG code that enables XML scripting calls of System i resources. XMLSERVICE RPG server library does not require other language products to run on your IBM i, however language teams often provide a client toolkit to greatly simplify XML calls to XMLSERVICE.
XMLSERVICE, as name implies, enables XML services on your IBM i machine. XMLSERVICE library is simply
a collection of Open Source RPG modules that essentially allow your to access anything on your IBM i machine,
assuming proper profile authority. Simply stated, XMLSERVICE accepts XML document containing actions/parameters
(<pgm>,<cmd>,<sh>,<sql>,etc.)
, performs requested operations on IBM i, then sends an XML document of results back to the client. This simple concept has become a powerful tool for scripting language clients on IBM i including Zend Server PHP Toolkit and PowerRuby Toolkit. However, PHP and Ruby are not unique to XMLSERVICE, XML is a universal protocol support by nearly every language, therefore nearly any language can use XMLSERVICE, and most any transport between client/server.
Installation¶
Build requirements
Building requires Python 3 and GNU make. These can be installed with yum: yum install python3 make-gnu
You will also need the ILE RPG compiler installed (5770-WDS option 31) along with the following PTFs:
7.3: SI62605
7.2: SI62604
7.1: SI62580
Building
PATH=/QOpenSys/pkgs/bin:$PATH
git clone https://github.com/IBM/xmlservice.git
cd xmlservice
python3 ./configure
make
Customizing the Build
You can customize the build by passing options to configure:
--library: set the build library
--debug: set the debug level (DBGVIEW CL parameter)
REST interface¶
Optional: Set up your Apache to enable XMLSERVICE REST using RPG program XMLCGI.PGM included in XMLSERVICE installation, then restart your Apache instance.
/www/apachedft/conf/httpd.conf:
ScriptAlias /cgi-bin/ /QSYS.LIB/XMLSERVICE.LIB/
<Directory /QSYS.LIB/XMLSERVICE.LIB/>
AllowOverride None
order allow,deny
allow from all
SetHandler cgi-script
Options +ExecCGI
</Directory>
stop/start Apache instance:
endTCPSVR SERVER(*HTTP) HTTPSVR(APACHEDFT)
STRTCPSVR SERVER(*HTTP) HTTPSVR(APACHEDFT)
Operation and transports¶
XMLSERVICE XML documents can be transported between IBM i and clients over any connection, any language.
XMLSERVICE APIs (included)¶
XMLSERVICE library includes language transports for popular REST and DB2 connections, which fulfills needs for most internet services applications.
XMLSERVICE/XMLCGI.PGM– RPG CGI HTTP/REST method GET or POST (traditional web service interface)
XMLSERVICE/XMLSTOREDP.SRVPGM – RPG DB2 stored procedure (IBM i’s premier DB2 for i)
- DB2 drivers local/remote with stored procedure IN/OUT capabilities (traditional DB2 interface)
iPLUG4K (IN IPC CHAR(1024), IN CTL CHAR(1024), IN XMLIN CHAR(4064), OUT XMLOUT CHAR(4064)) iPLUG32K (IN IPC CHAR(1024), IN CTL CHAR(1024), IN XMLIN CLOB(32000), OUT XMLOUT CLOB(32000)) iPLUG65K (IN IPC CHAR(1024), IN CTL CHAR(1024), IN XMLIN CLOB(65K), OUT XMLOUT CLOB(65K)) iPLUG512K(IN IPC CHAR(1024), IN CTL CHAR(1024), IN XMLIN CLOB(512K), OUT XMLOUT CLOB(512K)) iPLUG1M (IN IPC CHAR(1024), IN CTL CHAR(1024), IN XMLIN CLOB(1M), OUT XMLOUT CLOB(1M)) iPLUG5M (IN IPC CHAR(1024), IN CTL CHAR(1024), IN XMLIN CLOB(5M), OUT XMLOUT CLOB(5M)) iPLUG10M (IN IPC CHAR(1024), IN CTL CHAR(1024), IN XMLIN CLOB(10M), OUT XMLOUT CLOB(10M)) iPLUG15M (IN IPC CHAR(1024), IN CTL CHAR(1024), IN XMLIN CLOB(15M), OUT XMLOUT CLOB(15M))
- DB2 drivers local/remote without stored procedure IN/OUT capabilities (loop fetch required)
iPLUGR4K (IN IPC CHAR(1024), IN CTL CHAR(1024), IN XMLIN CHAR(4064)) iPLUGR32K (IN IPC CHAR(1024), IN CTL CHAR(1024), IN XMLIN CLOB(32000)) iPLUGR65K (IN IPC CHAR(1024), IN CTL CHAR(1024), IN XMLIN CLOB(65K)) iPLUGR512K(IN IPC CHAR(1024), IN CTL CHAR(1024), IN XMLIN CLOB(512K)) iPLUGR1M (IN IPC CHAR(1024), IN CTL CHAR(1024), IN XMLIN CLOB(1M)) iPLUGR5M (IN IPC CHAR(1024), IN CTL CHAR(1024), IN XMLIN CLOB(5M)) iPLUGR10M (IN IPC CHAR(1024), IN CTL CHAR(1024), IN XMLIN CLOB(10M)) iPLUGR15M (IN IPC CHAR(1024), IN CTL CHAR(1024), IN XMLIN CLOB(15M))
XMLSERVICE/XMLSTOREDP.SRVPGM – optional custom transport (programmers only)
- if included XMLSERVICE transports do not fill your need, please feel free to create your own (sockets, data queues, ftp, etc.). Multiple entry APIs exist in XMLSERVICE that you may find useful:
xmlstoredp.srvpgm - *SRVPGM interface for calls Native stored procedure call target (iPLUG4K - iPLUG15M): D iPLUG4K PR 1N extproc(*CL:'iPLUG4K') D pIPC 1024A D pCtl 1024A D pXmlIn * D pXmlOut * RPG call target: D runClient PR 1N D pIPCSP 1024A D pCtl 1024A D pIClob * D szIClob 10i 0 D pOClob * D szOClob 10i 0 PASE call target (also use RPG when CCSID issues): D runASCII PR 1N D pIPCSP2 * D szIPCSP2 10i 0 D pCtlSP2 * D szCtlSP2 10i 0 D pIClob2 * D szIClob2 10i 0 D pOClob2 * D szOClob2 10i 0 D ccsidPASE2 10i 0 D ccsidILE2 10i 0
IBM i CCSID 65535¶
XMLSERVICE REST and DB2 connections have implicit CCSID conversion between client and server, therefore your XMLIN/XMLOUT XML document will be implicitly CCSID converted by the transport layer, to wit, XMLSERVICE should just work. However, IBM i CCSID 65535 (hex), will destroy the entire XMLSERVICE scheme, and you will witness horrible hangs, junk data, dead processes, etc., so please take action on your IBM i to live with the modern ASCII world (all clients, all new scripting languages, remote/local including PASE).
Check your IBM i for CCSID convert safety.
If you see DSPSYSVAL 65535, you have a big problem with the ASCII world, but you can take corrective IBM i action.
DSPSYSVAL SYSVAL(QCCSID)
Coded character set
identifier . . . . . : 65535 1-65535
Here are some corrective IBM i suggestions (CCSID 37 an example), but remember you have to end current running jobs and restart for the CCSID changes to enable (including XTOOLKIT jobs):
- change system ccsid
CHGSYSVAL SYSVAL(QCCSID) VALUE(37)
- change Apache instances (/www/instance/httpd.conf)
# protect FastCGI against bad CCSID (dspsysval qccsid 65535)
DefaultFsCCSID 37
CGIJobCCSID 37
- change user profile(s)
CHGUSRPRF USRPRF(FRED) CCSID(37)
Connection public or private¶
XMLSERVICE parameters CTL and IPC enable two types of connections.
- public connection – XMLSERVICE stateless, many jobs, every use a fresh start
CTL='*here' , IPC="*NA"
- “public” because any IBM i profile use is a fresh start
- life scope – life of a single XML IN/OUT request
- private connection – XMLSERVICE state full, single job, where a given profile is routed to XTOOLKIT job(s)
CTL='*sbmjob' , IPC="/tmp/myjob1"
CTL='*sbmjob'
– if not running, submit a new XTOOLKIT jobIPC="/tmp/myjob1"
– all requests of IPC route to XTOOLKIT job (*sbmjob)- “private” because ONE IBM i profile owns XTOOLKIT job, any other IBM i profile will fail to attach
- life scope – forever, until ended by operator or user program ends XTOOLKIT job (like a 5250)
When to use XMLSERVICE public or private?
Of course any discussion that presumes to predict usage is likely to have legitimate user exception, but a guideline may be appropriate for use of XMLSERVICE public vs. private connection.
Web programming style (public, stateless)
XMLSERVICE public connections are generally used when IBM i resource can be called once by many different user profiles, current data returned, no lasting persistent data needed by the IBM i resource. The programmer meta description of transient resource services is “stateless programming”.
XMLSERVICE public “stateless” (CTL=’*here’, IPC=’*NA’)
- profile FRED (any public QSQ)
- profile SALLY (any public QSQ)
- profile RITA (any public QSQ)
- profile XAVIER (any public QSQ)
XMLSTOREDP->XMLSERVICE (QSQ)
- QSQ temporary profile use (stateless)
- QSQ return to pool on script end
- XMLSERVICE restart every request (web style)
Although very handy, clean wrkactjob, no hanging locks, etc., public connection is not a high performance use of XMLSERVICE.
- public connection – XMLSERVICE stateless, many jobs, every use a fresh start
CTL='*here' , IPC="*NA"
– profile FRED (RPG company stock service)CTL='*here' , IPC="*NA"
– profile SALLY (RPG IRS flat tax rate service)CTL='*here' , IPC="*NA"
– profile RITA (RPG calculate pound to kilo)CTL='*here' , IPC="*NA"
– profile XAVIER (RPG company stock service)
Traditional programming style (private, state full)
XMLSERVICE private connections are generally used when IBM i resource will be called many times by the same user profile, lasting persistent data needed by the IBM i resource (RPG variables, open files, etc.). The programmer meta description of required data services is “state full programming”.
XMLSERVICE private “state full” (CTL=’*sbmjob’, IPC=’/tmp/xxxx’)
- profile FRED XTOOLKIT myjob1,myjob2 (private)
- profile SALLY XTOOLKIT sallyjob1 (private)
- profile RITA XTOOLKIT nursejob (private)
- profile XAVIER XTOOLKIT xjob1 - xjob5 (private)
XMLSTOREDP (QSQ)
- QSQ temporary profile use (stateless)
- QSQ return to pool on script end
XMLSERVICE (XTOOLKIT)
- XTOOLKIT owned by profile (private)
- XTOOLKIT job never ends (until killed)
- XTOOLKIT full state programming (5250 style)
Traditional RPG programs usually need to track local variables, open files, etc., between requests, both for correct functionality and performance. The XTOOLKIT jobs that result from CTL='*sbmjob' , IPC="/tmp/xxxx"
are similar to 5250 jobs, where a user profile signs on, then uses 5250 job to run other programs (aka XMLSERVICE design), also, like multiple 5250 sessions (PC emulators), many different XTOOLKIT jobs can be used by the same user profile.
private connection – XMLSERVICE state full, single job, where a given profile is routed to same XTOOLKIT job
CTL='*sbmjob' , IPC="/tmp/myjob1"
– profile FRED XTOOLKIT myjob1 (Fred’s use only RPG payroll application)CTL='*sbmjob' , IPC="/tmp/myjob2"
– profile FRED XTOOLKIT myjob2 (Fred’s use only RPG inventory application)CTL='*sbmjob' , IPC="/tmp/sallyjob1"
– profile SALLY XTOOLKIT sallyjob1 (Sally’s use only RPG admissions application)CTL='*sbmjob' , IPC="/tmp/nursejob"
– profile RITA XTOOLKIT nursejob (Rita’s use only RPG nurse scheduling application)CTL='*sbmjob' , IPC="/tmp/xjob1"
– profile XAVIER XTOOLKIT xjob1 (Xavier’s use only RPG payroll application)CTL='*sbmjob' , IPC="/tmp/xjob2"
– profile XAVIER XTOOLKIT xjob2 (Xavier’s use only RPG inventory application)CTL='*sbmjob' , IPC="/tmp/xjob3"
– profile XAVIER XTOOLKIT xjob3 (Xavier’s use only RPG admissions application)CTL='*sbmjob' , IPC="/tmp/xjob4"
– profile XAVIER XTOOLKIT xjob4 (Xavier’s use only RPG nurse scheduling application)CTL='*sbmjob' , IPC="/tmp/xjob5"
– profile XAVIER XTOOLKIT xjob5 (Xavier’s use only RPG super hero application)- IBM i programmer: Profile Xavier is using many XTOOLKIT jobs (xjob1 - xjob5), many different applications, but Xavier cannot use Fred’s, Sally’s or Rita’s XTOOLKIT jobs (myjob1,myjob2,sallyjob1,nursejob), because Xavier does not own other profile XTOOLKIT jobs. XTOOLKIT jobs should be an easy pattern for RPG programmers familiar with single session 5250 job(s), owned by a profile, one thing at a time (not threaded), long running RPG programs, many IBM i files open, etc.
- However, XTOOLKIT jobs have an interesting characteristic that 5250 emulator jobs cannot match, profile owned XTOOLKIT jobs can be accessed by many different client devices all at the same time, to wit, Xavier can use a laptop and a smart phone to all jobs at the same time (xjob1 - xjob5), or Xavier can leave his work laptop running (connected), go home, have dinner, and continue working all XTOOLKIT jobs from the family iPAD.
- IBM i operator: You may wrkactjob and kill
*immed
XTOOLKIT jobs (same as 5250).- However, an ipcs administrative cleaner solution may suggest you write a custom XMLSERVICE program
CTL='*immed' , IPC="/tmp/myjob1"
, to remove “in the way” XTOOLKIT jobs (suggestion only).
- However, an ipcs administrative cleaner solution may suggest you write a custom XMLSERVICE program
Open Source goal¶
XMLSERVICE is constantly growing Open Source, so new functions are added over time. XMLSERVICE goal is never impact existing applications with new features, to date, XML’s natural ability to add keywords and attributes has been very successful in keeping this goal.
XMLSERVICE CMDs¶
<?xml version='1.0'?>
<script>
<cmd exec='rexx'>RTVJOBA USRLIBL(?) SYSLIBL(?)</cmd>
<cmd>DLTDTAQ DTAQ(MYLIB/MYDATAQ)</cmd>
<cmd>CRTDTAQ DTAQ(MYLIB/MYDATAQ) MAXLEN(100) AUT(*EXCLUDE)</cmd>
</script>
XMLSERVICE PASE¶
<?xml version='1.0'?>
<script>
<sh rows='on'>/QOpenSys/usr/bin/ls -l /tmp</sh>
<sh rows='on'>/QOpenSys/usr/bin/system -i 'wrkactjob'</sh>
</script>
XMLSERVICE DB2¶
<?xml version='1.0'?>
<script>
<sql>
<options options='noauto' autocommit='off'/>
<connect conn='myconn' options='noauto'/>
<query conn='myconn' stmt='myupdate'>UPDATE animal SET id = 9 where ID = 3</query>
<query conn='myconn' stmt='myselect'>select count(*) from animal where ID = 9</query>
<fetch stmt='myselect' block='all' desc='off'/>
<free stmt='myselect'/>
<commit conn='myconn' action='rollback'/>
<query conn='myconn' stmt='myselect'>select count(*) from animal where ID = 9</query>
<fetch stmt='myselect' block='all' desc='off'/>
<free/>
</sql>
</script>
XMLSERVICE PGMs, SRVPGMs, APIs¶
PGM:
<?xml version='1.0'?>
<script>
<pgm name='ZZCALL' lib='XMLSERVICE'>
<parm io='both'>
<data type='1A' var='INCHARA'>a</data>
</parm>
<parm io='both'>
<data type='1A' var='INCHARB'>b</data>
</parm>
<parm io='both'>
<data type='7p4' var='INDEC1'>11.1111</data>
</parm>
<parm io='both'>
<data type='12p2' var='INDEC2'>222.22</data>
</parm>
<parm io='both'>
<ds>
<data type='1A' var='INDS1.DSCHARA'>x</data>
<data type='1A' var='INDS1.DSCHARB'>y</data>
<data type='7p4' var='INDS1.DSDEC1'>66.6666</data>
<data type='12p2' var='INDS1.DSDEC2'>77777.77</data>
</ds>
</parm>
<return>
<data type='10i0'>0</data>
</return>
</pgm>
</script>
SRVPGM:
<?xml version='1.0'?>
<script>
<pgm name='ZZSRV' lib='XMLSERVICE' func='ZZVARY'>
<parm comment='search this name' io='in'>
<data var='myName' type='10A' varying='on'><![CDATA[<Ranger>]]></data>
</parm>
<return>
<data var='myNameis' type='20A' varying='on'><![CDATA[<Mud>]]></data>
</return>
</pgm>
</script>
System APIs:
<?xml version='1.0'?>
<script>
<pgm name='QSNDDTAQ'>
<parm io='in'>
<data type='10A'>MYDATAQ</data>
</parm>
<parm io='in'>
<data type='10A'>XMLSERVICE</data>
</parm>
<parm io='in'>
<data type='5p0'>50</data>
</parm>
<parm io='in'>
<data type='100A'>System i data queues forever</data>
</parm>
</pgm>
</script>
XMLSERVICE HTML/XML interface¶
Using your IBM i and XMSERVICE download (see installation), without writing one line of code in any language, we can already check out XMLSERVICE functions using HTML/XML forms. We should note, that by any standard the following trivial XMLSERVICE example is clearly REST web services, but no SOAP, no WSDL, no Java, no PHP, no Ruby, nothing but 2 cents worth of HTML/XML.
XMLSERVICE input - Plain Old XML input to XMLSERVICE for request select * from db2/animals
.
<myscript>
<sql>
<query>select * from db2/animals</query>
<fetch block='all' desc='on'></fetch>
</sql>
</myscript>
XMLSERVICE output - Plain Old XML output from XMLSERVICE with records returned from db2/animals
.
<myscript>
<query conn='conn1' stmt='stmt1'>
<success><![CDATA[+++ success select * from db2/animals]]></success>
</query>
<fetch block='all' desc='on' stmt='stmt1'>
<row>
<data desc='ID'><![CDATA[0]]></data>
<data desc='BREED'><![CDATA[cat]]></data>
<data desc='NAME'><![CDATA[Pook]]></data>
<data desc='WEIGHT'><![CDATA[3.20]]></data>
</row>
</myscript>
Instructions for your IBM i machine¶
Step 1) - Add XMLSERVICE to any Apache instance (APACHEDFT)
Set up your Apache to enable XMLSERVICE REST using RPG program XMLCGI.PGM included in XMLSERVICE installation, then restart your Apache instance.
/www/apachedft/conf/httpd.conf:
ScriptAlias /cgi-bin/ /QSYS.LIB/XMLSERVICE.LIB/
<Directory /QSYS.LIB/XMLSERVICE.LIB/>
AllowOverride None
order allow,deny
allow from all
SetHandler cgi-script
Options +ExecCGI
</Directory>
start Apache instance:
STRTCPSVR SERVER(*HTTP) HTTPSVR(APACHEDFT)
Step 2) - Ready to use XMLSERVICE for HTML/XML
Cut/Paste following HTML/XML form to your laptop Desktop/strsql.html:
- change action target to your actual machine
action="http://myibmi/cgi-bin/xmlcgi.pgm"
- point your favorite browser at the HTML file
file:///home/adc/Desktop/strsql.html
- enter database (*LOCAL), user (your profile), password (your password)
- enter a SQL query in HTML strsql command and press button
STRSQL
desktop/strsql.html:
<html>
<head>
<script>
function getVal() {
xml = "<?xml version='1.0'?>";
xml += "<myscript>";
xml += "<sql>";
xml += "<query>";
xml += document.getElementById('strsql').value;
xml += "</query>";
xml += "<fetch block='all' desc='on'>";
xml += "</fetch>";
xml += "</sql>";
xml += "</myscript>";
document.getElementById('xmlin').value = xml;
}
</script>
</head>
<body>
<h3>STRSQL</h3>
<form onsubmit="getVal();" name="input" action="http://myibmi/cgi-bin/xmlcgi.pgm" method="post">
<br><input type="text" name="db2" value="*LOCAL" size="40" > database
<br><input type="text" name="uid" value="MYUID" size="40" > user
<br><input type="password" name="pwd" value="MYPWD" size="40" > password
<input type="hidden" name="ipc" value="*NA">
<input type="hidden" name="ctl" value="*here *cdata">
<input type="hidden" name="xmlin" id="xmlin" value="na">
<input type="hidden" name="xmlout" value="500000">
<br><input type="text" name="strsql" id="strsql" size="40" /> strsql command (select * from db2/animals)
</table>
<br><br><input type="submit" value="STRSQL" />
</form>
</body>
</html>
desktop/strsql.html example
As strsql.html name implies, this simple html enables STRSQL running from your laptop to your IBM i. Enter any SQL statement you wish in the html form and XMLSERVICE will run just like STRSQL green screen when you press the STRSQL
button. XMLSERVICE output returned will be XML (of course), so if your browser has issues displaying XML, you may have to view page source.
HTML form strsql.html uses simple JavaScript function getVal()
with document.getElementById('strsql').value
, which reads HTML text input <input id='strsql'
and adds user SQL request to XML document to HTML text input <input id="xmlin"
. All XMLSERVICE required REST tag elements can bee seen in the following HTML form http://myibmi/cgi-bin/xmlcgi.pgm?db2=*LOCAL@uid=MYUID@pwd=MYPWD@ipc=*NA@ctl="*here *cdata"@xmlin=(see JavaScript)@xmlout=500000
, but we will cover this in later sections.
What is happening?
If we change method="post"
to method="get"
, the fully encoded document will appear on the browser URL line. As you can see, when input arrives from our browser (or REST client), XMLSERVICE/XMLCGI.PGM has much HTTP decoding before actually parsing the XML document and servicing the request.
<form onsubmit="getVal();" name="input" action="http://myibmi/cgi-bin/xmlcgi.pgm" method="post">
-- change --
<form onsubmit="getVal();" name="input" action="http://myibmi/cgi-bin/xmlcgi.pgm" method="get">
The follow cut/paste is one continuous browser line (split for viewing):
http://myibmi/cgi-bin/xmlcgi.pgm?db2=*LOCAL
&uid=MYUID
&pwd=MYPWD
&ipc=*NA
&ctl=*here+*cdata
&xmlin=%3C%3Fxml+version%3D%271.0%27%3F%3E
%3Cmyscript%3E
%3Csql%3E
%3Cquery%3Eselect+*+from+db2%2Fanimals
%3C%2Fquery%3E
%3Cfetch+block%3D%27all%27+desc%3D%27on%27%3E
%3C%2Ffetch%3E
%3C%2Fsql%3E
%3C%2Fmyscript%3E
&xmlout=500000
&strsql=select+*+from+db2%2Fanimals
The flow:
- We point our browser
file:///home/adc/Desktop/strsql.html
, enter SQL query and pressSTRSQL
button - IBM i Apache XMLCGI.PGM receives our encoded HTML/XML form
action="http://myibmi/cgi-bin/xmlcgi.pgm"
- XMLCGI.PGM calls XMLSERVICE.PGM (using DB2 stored procedures iPLUGxxx, but you do not need to know this yet).
- XMLSERVICE.PGM parses XML input and runs internal DB2 driver
<sql><query>...</query></sql>
. - XMLSERVICE.PGM parses result set from DB2 driver into output XML document
<sql><fetch/></sql>
- browser sees XML return of DB2 data
XMLSERVICE is Open Source, so you can examine internals of XMLCGI.PGM, for now , we simply need to understand XMLCGI.PGM decodes HTML/XML document, passes XML document request to XMLSERVICE.PGM (DB2 request example), and returns XML output to client (browser). If REST client is not a browser, but a scripting language like PHP or Ruby, exact same sequence occurs, except additionally most languages offer an XML parser to parse output XML into variables or structures (PHP Toolkit or Ruby Toolkit).
Quick test functions HTML/XML¶
XMLSERVICE HTML/XML technique can be used for nearly anything on IBM i machine CMD, PGM, SRVPGM, system APIs, PASE utilities, DB2, etc. Feel free to copy strsql.html form, modify, and try other XMLSERVICE functions <myscript>other functions</myscript>
. HTML/XML technique is a very handy testing a potential XMLSERVICE program service without writing a line of code, and, clearly demonstrates elegant simplicity embodied by XMLSERVICE.
XMLSERVICE DB2 interface¶
DB2 connection is not a web service, but many languages support high speed DB2 local/remote requests, so XMLSERVICE included a stored procedures interface (iPLUG4K - iPLUG15M). The nature of DB2 stored procedures requires a size specified on in/out parameters, therefore XMLSERVICE library includes various iPLUGxx sizes to fit your XML document data needs (4K, 32K, 65K, 512K, 1M, 5M, 10M, up to 15M).
We should note, XMLSERVICE DB2 is much faster over REST interface, so many language toolkits offer DB2 connection as the premier service.
- RPG DB2 (no toolkit)
myIPC = '/tmp/thebears1';
myCtl = '*sbmjob';
// call XMLSERVICE/ZZCALL(...) using XML only
myXmlIn =
'<?xml version="1.0" encoding="ISO-8859-1"?>' + x'0D'
+ '<script>' + x'0D'
+ '<pgm name="ZZCALL" lib="XMLSERVICE">' + x'0D'
+ '<parm><data type="1a" v="1">Y</data></parm>' + x'0D'
+ '<parm><data type="1a" v="2">Z</data></parm>' + x'0D'
+ '<parm><data type="7p4" v="3">001.0001</data></parm>' + x'0D'
+ '<parm><data type="12p2" v="4">0003.04</data></parm>' + x'0D'
+ '<parm>' + x'0D'
+ ' <ds>' + x'0D'
+ ' <data type="1a" v="5">A</data>' + x'0D'
+ ' <data type="1a" v="6">B</data>' + x'0D'
+ ' <data type="7p4" v="7">005.0007</data>' + x'0D'
+ ' <data type="12p2" v="8">0000000006.08</data>' + x'0D'
+ ' </ds>' + x'0D'
+ '</parm>' + x'0D'
+ '</pgm>' + x'0D'
+ '</script>' + x'00';
myXmlOut = *BLANKS;
// make call to XMLSERVICE provided stored procedure(s)
// sizes from iPLUG4k to iPLUG15M (see crtsql xmlservice package)
Exec Sql call XMLSERVICE/iPLUG4K(:myIPC,:myCtl,:myXmlIn,:myXmlOut);
- PHP DB2 (toolkit)
require_once("ToolkitService.php");
try { $ToolkitServiceObj = ToolkitService::getInstance($database, $user, $password); }
catch (Exception $e) { die($e->getMessage()); }
$ToolkitServiceObj->setToolkitServiceParams(
array('InternalKey'=>$ipc, // route to same XMLSERVICE job /tmp/myjob1
'subsystem'=>"QGPL/QDFTJOBD", // subsystem/jobd to start XMLSERVICE (if not running)
'plug'=>"32K")); // max size data i/o (iPLUG4K,32K,65K, 512K,1M,5M,10M,15M)
$param[] = $ToolkitServiceObj->AddParameterChar ('both', 1, 'INCHARA', 'var1', 'Y');
$param[] = $ToolkitServiceObj->AddParameterChar ('both', 1, 'INCHARB', 'var2', 'Z');
$param[] = $ToolkitServiceObj->AddParameterPackDec('both', 7,4,'INDEC1', 'var3', '001.0001');
$param[] = $ToolkitServiceObj->AddParameterPackDec('both', 12,2,'INDEC2', 'var4', '0000000003.04');
$ds[] = $ToolkitServiceObj->AddParameterChar ('both', 1, 'DSCHARA', 'ds1', 'A');
$ds[] = $ToolkitServiceObj->AddParameterChar ('both', 1, 'DSCHARB', 'ds2', 'B');
$ds[] = $ToolkitServiceObj->AddParameterPackDec('both', 7,4,'DSDEC1', 'ds3', '005.0007');
$ds[] = $ToolkitServiceObj->AddParameterPackDec('both', 12,2,'DSDEC1', 'ds4', '0000000006.08');
$param[] = $ToolkitServiceObj->AddDataStruct($ds);
$result = $ToolkitServiceObj->PgmCall('ZZCALL', $testLib, $param, null, null);
echo "good so far ...\n";
var_dump($result);
- PHP DB2 (without toolkit)
$fast = false;
$ipc = "*NA";
$ctl = "*here *cdata";
$xmlIn = "<?xml version='1.0' encoding='ISO-8859-1'?>
<script>
<pgm name='ZZCALL' lib='ZENDSVR'>
<parm><data type='1a'>Y</data></parm>
<parm><data type='1a'>Z</data></parm>
<parm><data type='7p4'>001.0001</data></parm>
<parm><data type='12p2'>0000000003.04</data></parm>
<parm>
<ds>
<data type='1a'>A</data>
<data type='1a'>B</data>
<data type='7p4'>005.0007</data>
<data type='12p2'>0000000006.08</data>
</ds>
</parm>
</pgm>
</script>";
$xmlOut = '';
if ($fast) $conn = db2_pconnect($db, $user, $pass); // persistent/pooled connection
else $conn = db2_connect($db, $user, $pass); // full open/close connection
if (!$conn) die("Bad connect: $db, $user");
$stmt = db2_prepare($conn, "call $lib.$plug(?,?,?,?)"); // Call XMLSERVICE
// stored procedure interface
// in/out parameter (xmlOut)
// sizes: iPLUG4K - iPLUG15M
if (!$stmt) die("Bad prepare: ".db2_stmt_errormsg());
$ret=db2_bind_param($stmt, 1, "ipc", DB2_PARAM_IN); // ? - /tmp/raw_$user (*sbmjob)
$ret=db2_bind_param($stmt, 2, "ctl", DB2_PARAM_IN); // ? - *here or *sbmjob
$ret=db2_bind_param($stmt, 3, "xmlIn", DB2_PARAM_IN); // ? - XML input script
$ret=db2_bind_param($stmt, 4, "xmlOut", DB2_PARAM_OUT); // ? - XML output return
$ret=db2_execute($stmt);
if (!$ret) die("Bad execute: ".db2_stmt_errormsg());
var_dump($xmlOut);
PowerRuby DB2 (toolkit)
-----------------------
require 'active_record'
require 'xmlservice'
ActiveRecord::Base.establish_connection(
:adapter => 'ibm_db',
:database => '*LOCAL',
:username => 'MYUID',
:password => 'MYPWD'
)
ActiveXMLService::Base.establish_connection(
:connection => 'ActiveRecord',
:install => "XMLSERVICE",
:ctl => "*here *cdata",
:ipc => "*NA",
:size => 4096,
:head => "<?xml version='1.0'?>"
)
zzcall = XMLService::I_PGM.new("zzcall","xmlservice")
zzcall << XMLService::I_a.new('inchara',1,'a')
zzcall << XMLService::I_a.new('incharb',1,'b')
zzcall << XMLService::I_p.new('indec1',7,4,11.1111)
zzcall << XMLService::I_p.new('indec2',12,2,222.22)
zzcall << XMLService::I_DS.new('inds1',1,
[XMLService::I_a.new('dschara',1,'x'),
XMLService::I_a.new('dscharb',1,'y'),
XMLService::I_p.new('dsdec1',7,4,66.6666),
XMLService::I_p.new('dsdec2',12,2,77777.77)])
zzcall.xmlservice
puts zzcall.out_xml
- PowerRuby DB2 (without toolkit)
require 'active_record'
ipc = "*NA"
ctl = "*here *cdata"
xmlin = '<?xml version="1.0"?>
<script>
<pgm name="ZZCALL" lib="XMLSERVICE">
<parm io="both">
<data type="1A">a</data>
</parm>
<parm io="both">
<data type="1A">b</data>
</parm>
<parm io="both">
<data type="7p4">11.1111</data>
</parm>
<parm io="both">
<data type="12p2">222.22</data>
</parm>
<parm io="both">
<ds>
<data type="1A">x</data>
<data type="1A">y</data>
<data type="7p4">66.6666</data>
<data type="12p2">77777.77</data>
</ds>
</parm>
<return>
<data type="10i0">0</data>
</return>
</pgm>
</script>'
xmlout = ""
ActiveRecord::Base.establish_connection(
:adapter => 'ibm_db',
:database => '*LOCAL',
:username => 'MYUID',
:password => 'MYPWD'
)
conn = ActiveRecord::Base.connection.connection
stmt = IBM_DB::prepare(conn, 'CALL XMLSERVICE.iPLUG512K(?,?,?,?)')
ret = IBM_DB::bind_param(stmt, 1, "ipc", IBM_DB::SQL_PARAM_INPUT)
ret = IBM_DB::bind_param(stmt, 2, "ctl", IBM_DB::SQL_PARAM_INPUT)
ret = IBM_DB::bind_param(stmt, 3, "xmlin", IBM_DB::SQL_PARAM_INPUT)
ret = IBM_DB::bind_param(stmt, 4, "xmlout", IBM_DB::SQL_PARAM_OUTPUT)
ret = IBM_DB::execute(stmt)
puts xmlout
- Java DB2 (no toolkit)
import java.io.*;
import java.util.*;
import java.sql.*;
import java.math.*;
public class javaXMLserviceDemoJDBC {
public static void main(String[] args) {
Connection conn = null;
Statement stmt=null;
CallableStatement cstmt = null ;
PreparedStatement pstmt = null;
String url = "jdbc:db2://localhost"; // Set URL for data source
String user = "MYUID";
String password = "MYPWD";
try
{ // Load the DB2(R) Universal JDBC Driver with DriverManager
Class.forName("com.ibm.db2.jdbc.app.DB2Driver");
conn = DriverManager.getConnection(url, user, password);
String inputClob =
"<?xml version='1.0'?>"
+ " <script>"
+ " <pgm name='ZZCALL' lib='XMLSERVICE'>"
+ " <parm><data type='1A'>a</data></parm>"
+ " <parm><data type='1A'>b</data></parm>"
+ " <parm><data type='7p4'>11.1111</data></parm>"
+ " <parm><data type='12p2'>222.22</data></parm>"
+ " <parm>"
+ " <ds>"
+ " <data type='1A'>x</data>"
+ " <data type='1A'>y</data>"
+ " <data type='7p4'>66.6666</data>"
+ " <data type='12p2'>77777.77</data>"
+ " </ds>"
+ " </parm>"
+ " <return><data type='10i0'>0</data></return>"
+ " </pgm>"
+ " </script>";
String sql="CALL XMLSERVICE.iPLUG512K(?,?,?,?)";
cstmt = conn.prepareCall(sql);
System.out.println("Calling with valid name");
cstmt.setString(1,"/tmp/packers01");
cstmt.setString(2,"*sbmjob");
cstmt.setString(3,inputClob);
cstmt.registerOutParameter(4, Types.CLOB);
cstmt.execute();
String doc = cstmt.getString(4);
System.out.println("****** Documento XML: **********");
System.out.println(doc);
}
catch (Exception e)
{ System.out.println("******* Eccezione !!! *********");
e.printStackTrace();
}
}
}
- perl DB2 (no toolkit)
use DBI;
use DBD::DB2::Constants;
use DBD::DB2;
$dbh = DBI->connect("dbi:DB2:*LOCAL")
or die $DBI::errstr;
$stmt = 'call XMLSERVICE.iPLUG65K(?,?,?,?)';
$sth = $dbh->prepare($stmt)
or die "prepare got error " . $dbh->err;
$ipc = "/tmp/perlme";
$sth->bind_param(1, $ipc)
or die "bind 1 got error " . $dbh->err;
$ctl = "*sbmjob";
$sth->bind_param(2, $ctl)
or die "bind 2 got error " . $dbh->err;
$xmlin = '<?xml version="1.0"?>
<script>
<pgm name="ZZCALL" lib="XMLSERVICE">
<parm io="both">
<data type="1A">a</data>
</parm>
<parm io="both">
<data type="1A">b</data>
</parm>
<parm io="both">
<data type="7p4">11.1111</data>
</parm>
<parm io="both">
<data type="12p2">222.22</data>
</parm>
<parm io="both">
<ds>
<data type="1A">x</data>
<data type="1A">y</data>
<data type="7p4">66.6666</data>
<data type="12p2">77777.77</data>
</ds>
</parm>
<return>
<data type="10i0">0</data>
</return>
</pgm>
</script>
';
$sth->bind_param(3, $xmlin)
or die "bind 3 got error " . $dbh->err;
$xmlout = "";
$xmloutlen = 4096;
$sth->bind_param_inout(4, \$xmlout, $xmloutlen)
or die "bind 4 got error " . $dbh->err;
$sth->execute()
or die "execute got error" . $dbh->err;
XMLSERVICE REST interface¶
XMLSERVICE includes a simple REST interface (XMLCGI.PGM), we demonstrated using the REST service using only HTML/XML in a previous section. Most languages support REST calls, so XMLSERVICE REST interface can be very useful for cloud applications where DB2 drivers are not available. XMLSERVICE has REST production clients in most every scripting language you can imagine (PHP, Ruby, perl, python, etc.).
- JavaScript REST (no toolkit)
<html>
<head>
<script src="http://ajax.googleapis.com/ajax/libs/dojo/1.5/dojo/dojo.xd.js" type="text/javascript"></script>
<script language="javascript">
dojo.require("dojox.xml.parser");
// you will need actual uid/pwd
// *NONE not enabled by default
var msgin = "xmlservice input";
var msgout = "xmlservice output";
var xmlhttp = null;
var url = encodeURI("http://"
+ self.location.hostname
+ "/cgi-bin/xmlcgi.pgm?"
+ "db2=*LOCAL"
+ "&uid=MYUID"
+ "&pwd=MYPWD"
+ "&ipc=/tmp/rangerhtmlonly"
+ "&ctl=*sbmjob"
+ "&xmlin="
+ "<?xml version='1.0'?>"
+ " <myscript>"
+ " <pgm name='ZZCALL' lib='XMLSERVICE'>"
+ " <parm var='p1'><data type='1A' var='d1'>a</data></parm>"
+ " <parm var='p2'><data type='1A' var='d2'>b</data></parm>"
+ " <parm var='p3'><data type='7p4' var='d3'>11.1111</data></parm>"
+ " <parm var='p4'><data type='12p2' var='d4'>222.22</data></parm>"
+ " <parm var='p5'>"
+ " <ds var='myds'>"
+ " <data type='1A' var='ds1'>x</data>"
+ " <data type='1A' var='ds2'>y</data>"
+ " <data type='7p4' var='ds3'>66.6666</data>"
+ " <data type='12p2' var='ds4'>77777.77</data>"
+ " </ds>"
+ " </parm>"
+ " <return var='rc'><data type='10i0' var='d1'>0</data></return>"
+ " </pgm>"
+ " </myscript>"
+ "&xmlout=32768");
function readNode(baseNode,output)
{ // @copy: http://blog.char95.com/post/simple-javascript-xml2array-parser/
var node = baseNode.firstChild;
if (output==undefined) var output = {};
while(node)
{ var nodeData = {};
if (node.attributes)
{ var nNodes = node.attributes.length;
while(nNodes--) nodeData['$'+node.attributes[nNodes].nodeName] = node.attributes[nNodes].nodeValue;
}
if (output[node.nodeName]==undefined) output[node.nodeName] = new Array(nodeData);
else output[node.nodeName].push(nodeData);
var id = output[node.nodeName].length-1;
output[node.nodeName][id] = readNode(node,output[node.nodeName][id]);
if (node.firstChild) nodeData['#text'] = node.firstChild.nodeValue;
node = node.nextSibling;
}
return output;
}
function processXMLSERVICE2()
{ var args =
{ url:url,
handleAs:"xml",
preventCache:true,
load:function(data)
{ var xmlArray = readNode(data);
var table = document.createElement('table');
var output =
"<th>parm</th>"
+ "<th>ds</th>"
+ "<th>var</th>"
+ "<th>value</th>\n";
parms = xmlArray['myscript'][0]['pgm'][0]['parm'];
for (var i=0;i<parms.length;i++)
{ if (i<parms.length - 1)
{ output += "<tr>"
+ "<td>" + parms[i].$var + "</td>\n"
+ "<td>" + "(na)" + "</td>\n"
+ "<td>" + parms[i]['data'][0].$var + "</td>\n"
+ "<td>" + parms[i]['data'][0]['#text'] + "</td>\n"
+ "</tr>";
}
else
{ dsvar = parms[i]['ds'][0].$var;
dsdata = parms[i]['ds'][0]['data'];
for (var j=0;j<dsdata.length;j++)
{ output += "<tr>"
+ "<td>" + parms[i].$var + "</td>\n"
+ "<td>" + dsvar + "</td>\n"
+ "<td>" + dsdata[j].$var + "</td>\n"
+ "<td>" + dsdata[j]['#text'] + "</td>\n"
+ "</tr>";
}
}
}
output += "</table>\n";
table.setAttribute("border","1")
table.innerHTML = output;
document.getElementById("addtable").appendChild(table);
},
error:function(error)
{ alert("Error:" + error);
}
};
var ajaxCall = dojo.xhrGet(args);
}
</script>
</head>
<body>
<p>This page demonstrates calling XMLSERVICE by JavaScript. Display source in your browser to see JavaScript used.</p>
<form>
<ul>
<li><a href="javascript: processXMLSERVICE2();">{XMLSERVICE JavaScript table (click me)}</a> - DoJo REST call RPG build table element</li>
</eul>
<p>
<div id="addtable"></div>
</p>
</form>
</body>
</html>
- PHP REST (no toolkit)
<?php
function getxml() {
$clob = <<<ENDPROC
<?xml version='1.0'?>
<script>
<pgm name='ZZCALL' lib='XMLSERVICE'>
<parm><data type='1A'>a</data></parm>
<parm><data type='1A'>b</data></parm>
<parm><data type='7p4'>11.1111</data></parm>
<parm><data type='12p2'>222.22</data></parm>
<parm>
<ds>
<data type='1A'>x</data>
<data type='1A'>y</data>
<data type='7p4'>66.6666</data>
<data type='12p2'>77777.77</data>
</ds>
</parm>
<return><data type='10i0'>0</data></return>
</pgm>
</script>
ENDPROC;
return $clob;
}
// make the call
$i5persistentconnect = false;
$database = "*LOCAL";
$user = "MYUID";
$password = "MYPWD";
$libxmlservice = "XMLSERVICE";
$ipc = '/tmp/rangerhtmlonly';
$ctl = '*sbmjob';
$clobIn = getxml();
$clobOut = "";
$clobOutMax = "32000";
$i5rest = "http://174.79.32.155/cgi-bin/xmlcgi.pgm";
$postdata = http_build_query(
array(
'db2' => $database,
'uid' => $user,
'pwd' => $password,
'ipc' => $ipc,
'ctl' => $ctl,
'xmlin' => $clobIn,
'xmlout' => $clobOutMax // size expected XML output
)
);
$opts = array('http' =>
array(
'method' => 'POST',
'header' => 'Content-type: application/x-www-form-urlencoded',
'content' => $postdata
)
);
$context = stream_context_create($opts);
$clobOut = file_get_contents($i5rest, false, $context);
?>
- PowerRuby REST (no toolkit)
require 'adapters/abstract_adapter'
require 'net/http'
require 'uri'
xmlin = '<?xml version="1.0"?>
<script>
<pgm name="ZZCALL" lib="XMLSERVICE">
<parm io="both">
<data type="1A">a</data>
</parm>
<parm io="both">
<data type="1A">b</data>
</parm>
<parm io="both">
<data type="7p4">11.1111</data>
</parm>
<parm io="both">
<data type="12p2">222.22</data>
</parm>
<parm io="both">
<ds>
<data type="1A">x</data>
<data type="1A">y</data>
<data type="7p4">66.6666</data>
<data type="12p2">77777.77</data>
</ds>
</parm>
<return>
<data type="10i0">0</data>
</return>
</pgm>
</script>'
xmlout = ""
post_args = {
:db2 => "*LOCAL",
:uid => "MYUID",
:pwd => "MYPWD",
:ipc => "*NA",
:ctl => "*here *cdata",
:xmlin => xmlin,
:xmlout => 4096
}
uri = URI("http://myibmi/cgi-bin/xmlcgi.pgm")
res = Net::HTTP.post_form(uri, post_args)
xmlout = res.body
puts xmlout
- Java REST (no toolkit)
import java.net.*;
import java.io.*;
public class javaXMLserviceDemoREST {
public static void main(String[] args)
{ try
{ // The URL class does not itself encode or decode any URL components according to the escaping mechanism defined in RFC2396.
// It is the responsibility of the caller to encode any fields, which need to be escaped prior to calling URL,
// and also to decode any escaped fields, that are returned from URL. Furthermore, because URL has no knowledge of URL escaping,
// it does not recognise equivalence between the encoded or decoded form of the same URL
String inputURL =
"http://174.79.32.155/cgi-bin/xmlcgi.pgm?"
+ java.net.URLEncoder.encode(
"db2=*LOCAL"
+ "&uid=MYUID"
+ "&pwd=MYPWD"
+ "&ipc=/tmp/rangerhtmlonly"
+ "&ctl=*sbmjob"
+ "&xmlin="
+ "<?xml version='1.0'?>"
+ " <script>"
+ " <pgm name='ZZCALL' lib='XMLSERVICE'>"
+ " <parm><data type='1A'>a</data></parm>"
+ " <parm><data type='1A'>b</data></parm>"
+ " <parm><data type='7p4'>11.1111</data></parm>"
+ " <parm><data type='12p2'>222.22</data></parm>"
+ " <parm>"
+ " <ds>"
+ " <data type='1A'>x</data>"
+ " <data type='1A'>y</data>"
+ " <data type='7p4'>66.6666</data>"
+ " <data type='12p2'>77777.77</data>"
+ " </ds>"
+ " </parm>"
+ " <return><data type='10i0'>0</data></return>"
+ " </pgm>"
+ " </script>"
+ "&xmlout=32768",
"ISO-8859-1");
// make REST call to XMLSERVICE
URL restUrl = new URL(inputURL);
URLConnection conn = restUrl.openConnection();
// output processing
System.out.println("Content type: " + conn.getContentType());
System.out.println("Content length: " + conn.getContentLength());
BufferedReader strm = new BufferedReader(new InputStreamReader(conn.getInputStream()));
String inputLine;
String doc = "";
while ((inputLine = strm.readLine()) != null) {
doc = doc + inputLine;
}
System.out.println("****** Documento XML: **********");
System.out.println(doc);
}
catch (Exception e)
{ System.out.println("******* Eccezione !!! *********");
e.printStackTrace();
}
}
}
``
RPG and XMLSERVICE¶
RPG is usually considered server side of XMLSERVICE, a called RPG PGM or SRVPGM (web service), but RPG is well versed in DB2, therefore can also be used as RPG client to XMLSERVICE. In fact, client RPG DB2 connection to XMLSERVICE will likely be fastest and easiest choice, especially for your remote IBM i systems (WRKRDBDIRE).
The RPG client examples will demonstrate RPG CGI using XMLSERVICE via Apache configuration, but the techniques also work as batch or green screen application, after using XMLSERVICE for a while, you may realize just how little code you have to write to do a great deal of work.
- Apache CGI configuration (httpd.conf)
ScriptAlias /cgi-bin/ /QSYS.LIB/XMLSERVICE.LIB/
<Directory /QSYS.LIB/XMLSERVICE.LIB/>
order allow,deny
allow from all
SetHandler cgi-script
Options +ExecCGI
</Directory>
- RPG client XMLSERVICE (*PGM)
RPG program ZZRPGSQL.PGM demonstrates client use of XMLSERVICE calling a *PGM (ZZCALL.pgm).
XMLSERVICE/ZZCALL (*PGM)
D INCHARA S 1a
D INCHARB S 1a
D INDEC1 S 7p 4
D INDEC2 S 12p 2
D INDS1 DS
D DSCHARA 1a
D DSCHARB 1a
D DSDEC1 7p 4
D DSDEC2 12p 2
*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
* main(): Control flow
*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
C *Entry PLIST
C PARM INCHARA
C PARM INCHARB
C PARM INDEC1
C PARM INDEC2
C PARM INDS1
The simple example is of course a bit contrived, simply extracting strings between <data>output</data>
returned XMLSERVICE output, but serves to demonstrate XMLSERVICE call using Exec Sql call XMLSERVICE/iPLUG4K(:myIPC,:myCtl,:myXmlIn,:myXmlOut);
.
ZZRPGSQL.PGM calling XMLSERVICE via Exec Sql call XMLSERVICE/iPLUG4K
H AlwNull(*UsrCtl)
H BNDDIR('QC2LE')
* vars
D myIPC s 1024A inz(*BLANKS)
D myCtl s 1024A inz(*BLANKS)
D myXmlIn s 4096A inz(*BLANKS)
D myXmlOut s 4096A inz(*BLANKS)
D i s 10i 0 inz(0)
D rn s 10i 0 inz(0)
D data s 65000A inz(*BLANKS)
D xml1 s 65000A inz(*BLANKS)
D xml2 s 65000A inz(*BLANKS)
D xml3 s 65000A inz(*BLANKS)
D xml4 s 65000A inz(*BLANKS)
D strstr PR * ExtProc('strstr')
D pVal1 * Value options(*string)
D pVal2 * Value options(*string)
D strlen PR 10I 0 ExtProc('strlen')
D pVal * Value options(*string)
D writeIFS PR 20I 0 ExtProc('write')
D fd 10I 0 value
D buf * value
D size 10I 0 value
D xmlfind PR 65000A
D xml * value
D nbr 10i 0 value
D xbeg 32A value
D xbeg1 32A value
D xend 32A value
D Main PR ExtPgm('ZZRPGSQL')
*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
* main(): Control flow
*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
D Main PI
/free
Monitor;
// -----
// input
myIPC = '*NA';
myCtl = '*here';
myXmlIn =
'<?xml version="1.0" encoding="ISO-8859-1"?>' + x'0D'
+ '<script>' + x'0D'
+ '<pgm name="ZZCALL" lib="XMLSERVICE">' + x'0D'
+ '<parm><data type="1a" var="INCHARA">Y</data></parm>' + x'0D'
+ '<parm><data type="1a" var="INCHARB">Z</data></parm>' + x'0D'
+ '<parm><data type="7p4" var="INDEC1">001.0001</data></parm>' + x'0D'
+ '<parm><data type="12p2" var="INDEC2">0003.04</data></parm>' + x'0D'
+ '<parm>' + x'0D'
+ ' <ds>' + x'0D'
+ ' <data type="1a" var="DSCHARA">A</data>' + x'0D'
+ ' <data type="1a" var="DSCHARB">B</data>' + x'0D'
+ ' <data type="7p4" var="DSDEC1">005.0007</data>' + x'0D'
+ ' <data type="12p2" var="DSDEC2">0000000006.08</data>' + x'0D'
+ ' </ds>' + x'0D'
+ '</parm>' + x'0D'
+ '</pgm>' + x'0D'
+ '</script>' + x'00';
myXmlOut = *BLANKS;
// -----
// sql call XMLSERVICE provided stored procedure(iPLUG4k -iPLUG15M)
Exec Sql call XMLSERVICE/iPLUG4K(:myIPC,:myCtl,:myXmlIn,:myXmlOut);
// -----
// output (CGI)
// -----
// send header + end (LFLF)
data = 'Content-type: text/html' + x'15' + x'15' + x'00';
rn = writeIFS(1:%addr(data):strlen(%addr(data)));
// -----
// send return data
// HTML table
data = '<h3>RPG call XMLSERVICE</h3>' + x'0D';
data = %trim(data) + '<table border="1">' + x'0D';
data = %trim(data) + '<th>Parameter name</th>' + x'0D';
data = %trim(data) + '<th>Type value</th>' + x'0D';
data = %trim(data) + '<th>Input value</th>' + x'0D';
data = %trim(data) + '<th>Output value</th>' + x'0D';
for i = 1 to 8;
xml1 = xmlfind(%addr(myXmlIn): i:'var=' :'"':'"');
xml2 = xmlfind(%addr(myXmlIn): i:'type=':'"':'"');
xml3 = xmlfind(%addr(myXmlIn): i:'<data':'>':'</data>');
xml4 = xmlfind(%addr(myXmlOut):i:'<data':'>':'</data>');
// HTML table row
data = %trim(data) + '<tr>' + x'0D';
data = %trim(data) + '<td>' + %trim(xml1) + '</td>' + x'0D';
data = %trim(data) + '<td>' + %trim(xml2) + '</td>' + x'0D';
data = %trim(data) + '<td>' + %trim(xml3) + '</td>' + x'0D';
data = %trim(data) + '<td>' + %trim(xml4) + '</td>' + x'0D';
data = %trim(data) + '</tr>' + x'0D';
endfor;
data = %trim(data) + '</table>' + x'00';
rn = writeIFS(1:%addr(data):strlen(%addr(data)));
On-error;
Endmon;
return;
/end-free
*****************************************************
* xmlfind
*****************************************************
P xmlfind B
D xmlfind PI 65000A
D xml * value
D nbr 10i 0 value
D xbeg 32A value
D xbeg1 32A value
D xend 32A value
* vars
D i s 10i 0 inz(0)
D cbeg s 33A inz(*BLANKS)
D cbeg1 s 33A inz(*BLANKS)
D cend s 33A inz(*BLANKS)
D pbeg s * inz(*NULL)
D pend s * inz(*NULL)
D xmlFragment s 65000A inz(*BLANKS)
/free
cbeg = %trim(xbeg) + x'00';
cbeg1 = %trim(xbeg1) + x'00';
cend = %trim(xend) + x'00';
pbeg = xml;
for i = 1 to nbr;
// <item stuff>123</item>
// x
if pbeg <> *NULL;
pbeg = strstr(pbeg + 1:%addr(cbeg));
endif;
endfor;
// <item stuff>123</item>
// x
if pbeg <> *NULL;
pbeg = strstr(pbeg:%addr(cbeg1));
endif;
if pbeg <> *NULL;
// <item stuff>123</item>
// x x
pbeg += 1;
pend = strstr(pbeg:%addr(cend));
if pend <> *NULL and pend > pbeg;
xmlFragment = %str(pbeg:pend - pbeg);
endif;
endif;
return xmlFragment;
/end-free
P E
What is happening?
The flow of ZZRPGSQL.PGM is similar to HTML/XML, except we are using the XMLSERVICE provided stored procedure interface to call XMLSERVICE.
- We point our browser to
http://lp0364d:10022/cgi-bin/zzrpgsql.pgm
, which is RPG CGI program ZZRPGSQL.PGM. - ZZRPGSQL.PGM uses XMLSERVICE DB2 interface
Exec Sql call XMLSERVICE/iPLUG4K(:myIPC,:myCtl,:myXmlIn,:myXmlOut);
, passing XML input request myXmlIn to XMLSERVICE. - XMLSERVICE.PGM parses XML input and dynamically loads/activates/calls target ZZCALL.PGM with included parameters
<parm>...</parm>
. - ZZCALL.PGM runs using normal in/out parameters (memory)
- XMLSERVICE.PGM parses in/out parameters from ZZCALL.PGM into output XML document
- ZZRPGSQL.PGM myXmlOut variable contains XML output document
- ZZRPGSQL.PGM parses myXmlOut into a HTML table output (writeIFS)
- browser sees the HTML table of ZZCALL data
- RPG client XMLSERVICE (*SRVPGM)
RPG program ZZVRYSQL.PGM demonstrates client use of XMLSERVICE calling a *SRVPGM (ZZSRV.ZZVARY). As you read source code for ZZVRYSQL.PGM, you will see it is nearly identical to previous example ZZRPGSQL.PGM.
XMLSERVICE/ZZSRV.ZZVARY (*SRVPGM)
P zzvary B export
D zzvary PI 20A varying
D myName 10A varying
XMLSERVICE called ZZSRV.ZZARRY SRVPGM demonstrates advance functions problematic for PCML based web services.
- difficult – parameter
10A varying
- PCML requires multiple XML definitions for length, data - impossible – return
20A varying
- PCML has no ability to return complex data (only single integer)
ZZVRYSQL.PGM calling XMLSERVICE via Exec Sql call XMLSERVICE/iPLUG4K
H AlwNull(*UsrCtl)
H BNDDIR('QC2LE')
* vars
D myIPC s 1024A inz(*BLANKS)
D myCtl s 1024A inz(*BLANKS)
D myXmlIn s 4096A inz(*BLANKS)
D myXmlOut s 4096A inz(*BLANKS)
D i s 10i 0 inz(0)
D rn s 10i 0 inz(0)
D data s 65000A inz(*BLANKS)
D xml1 s 65000A inz(*BLANKS)
D xml2 s 65000A inz(*BLANKS)
D xml3 s 65000A inz(*BLANKS)
D xml4 s 65000A inz(*BLANKS)
D strstr PR * ExtProc('strstr')
D pVal1 * Value options(*string)
D pVal2 * Value options(*string)
D strlen PR 10I 0 ExtProc('strlen')
D pVal * Value options(*string)
D writeIFS PR 20I 0 ExtProc('write')
D fd 10I 0 value
D buf * value
D size 10I 0 value
D xmlfind PR 65000A
D xml * value
D nbr 10i 0 value
D xbeg 32A value
D xbeg1 32A value
D xend 32A value
D Main PR ExtPgm('ZZVRYSQL')
*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
* main(): Control flow
*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
D Main PI
/free
Monitor;
// -----
// input
// P zzvary B export
// D zzvary PI 20A varying
// D myName 10A varying
myIPC = '*NA';
myCtl = '*here';
myXmlIn =
'<?xml version="1.0" encoding="ISO-8859-1"?>' + x'0D'
+ '<script>' + x'0D'
+ '<pgm name="ZZSRV" lib="XMLSERVICE" func="ZZVARY">' + x'0D'
+ '<parm>' + x'0D'
+ '<data type="10a" var="myName" varying="on">Ranger</data>' + x'0D'
+ '</parm>' + x'0D'
+ '<return>' + x'0D'
+ '<data var="retName" type="20A" varying="on">Mud</data>' + x'0D'
+ '</return>' + x'0D'
+ '</pgm>' + x'0D'
+ '</script>' + x'00';
myXmlOut = *BLANKS;
// -----
// sql call XMLSERVICE provided stored procedure(iPLUG4k -iPLUG15M)
Exec Sql call XMLSERVICE/iPLUG4K(:myIPC,:myCtl,:myXmlIn,:myXmlOut);
// -----
// output (CGI)
// -----
// send header + end (LFLF)
data = 'Content-type: text/html' + x'15' + x'15' + x'00';
rn = writeIFS(1:%addr(data):strlen(%addr(data)));
// -----
// send return data
// HTML table
data = '<h3>RPG call XMLSERVICE</h3>' + x'0D';
data = %trim(data) + '<table border="1">' + x'0D';
data = %trim(data) + '<th>Parameter name</th>' + x'0D';
data = %trim(data) + '<th>Type value</th>' + x'0D';
data = %trim(data) + '<th>Input value</th>' + x'0D';
data = %trim(data) + '<th>Output value</th>' + x'0D';
for i = 1 to 2;
xml1 = xmlfind(%addr(myXmlIn): i:'var=' :'"':'"');
xml2 = xmlfind(%addr(myXmlIn): i:'type=':'"':'"');
xml3 = xmlfind(%addr(myXmlIn): i:'<data':'>':'</data>');
xml4 = xmlfind(%addr(myXmlOut):i:'<data':'>':'</data>');
// HTML table row
data = %trim(data) + '<tr>' + x'0D';
data = %trim(data) + '<td>' + %trim(xml1) + '</td>' + x'0D';
data = %trim(data) + '<td>' + %trim(xml2) + '</td>' + x'0D';
data = %trim(data) + '<td>' + %trim(xml3) + '</td>' + x'0D';
data = %trim(data) + '<td>' + %trim(xml4) + '</td>' + x'0D';
data = %trim(data) + '</tr>' + x'0D';
endfor;
data = %trim(data) + '</table>' + x'00';
rn = writeIFS(1:%addr(data):strlen(%addr(data)));
On-error;
Endmon;
return;
/end-free
*****************************************************
* xmlfind
*****************************************************
P xmlfind B
D xmlfind PI 65000A
D xml * value
D nbr 10i 0 value
D xbeg 32A value
D xbeg1 32A value
D xend 32A value
* vars
D i s 10i 0 inz(0)
D cbeg s 33A inz(*BLANKS)
D cbeg1 s 33A inz(*BLANKS)
D cend s 33A inz(*BLANKS)
D pbeg s * inz(*NULL)
D pend s * inz(*NULL)
D xmlFragment s 65000A inz(*BLANKS)
/free
cbeg = %trim(xbeg) + x'00';
cbeg1 = %trim(xbeg1) + x'00';
cend = %trim(xend) + x'00';
pbeg = xml;
for i = 1 to nbr;
// <item stuff>123</item>
// x
if pbeg <> *NULL;
pbeg = strstr(pbeg + 1:%addr(cbeg));
endif;
endfor;
// <item stuff>123</item>
// x
if pbeg <> *NULL;
pbeg = strstr(pbeg:%addr(cbeg1));
endif;
if pbeg <> *NULL;
// <item stuff>123</item>
// x x
pbeg += 1;
pend = strstr(pbeg:%addr(cend));
if pend <> *NULL and pend > pbeg;
xmlFragment = %str(pbeg:pend - pbeg);
endif;
endif;
return xmlFragment;
/end-free
P E
RPG client XMLSERVICE (DataQueue)¶
RPG program ZZQUESQL.PGM demonstrates client use of XMLSERVICE calling a CMDs and System APIs for Data Queue. As you read source code for ZZQUESQL.PGM, you will see it is nearly identical to previous example ZZRPGSQL.PGM.
DLTDTAQ DTAQ(XMLSERVICE/MYDATAQ)
CRTDTAQ DTAQ(XMLSERVICE/MYDATAQ) MAXLEN(100)
***************************************
* Send Data Queue (QSNDDTAQ) API
***************************************
* 1 Data queue name Input Char(10)
* 2 Library name Input Char(10)
* 3 Length of data Input Packed(5,0)
* 4 Data Input Char(*) Input
***************************************
* Receive Data Queue (QRCVDTAQ) API
***************************************
* 1 Data queue name Input Char(10)
* 2 Library name Input Char(10)
* 3 Length of data Input Packed(5,0)
* 4 Data Char(*) Output
* 5 Wait time Input Packed(5,0)
ZZQUESQL.PGM calling XMLSERVICE via Exec Sql call XMLSERVICE/iPLUG4K
H AlwNull(*UsrCtl)
H BNDDIR('QC2LE')
* vars
D myIPC s 1024A inz(*BLANKS)
D myCtl s 1024A inz(*BLANKS)
D myXmlIn s 4096A inz(*BLANKS)
D myXmlOut s 4096A inz(*BLANKS)
D i s 10i 0 inz(0)
D rn s 10i 0 inz(0)
D data s 65000A inz(*BLANKS)
D xml1 s 65000A inz(*BLANKS)
D xml2 s 65000A inz(*BLANKS)
D xml3 s 65000A inz(*BLANKS)
D xml4 s 65000A inz(*BLANKS)
D strstr PR * ExtProc('strstr')
D pVal1 * Value options(*string)
D pVal2 * Value options(*string)
D strlen PR 10I 0 ExtProc('strlen')
D pVal * Value options(*string)
D writeIFS PR 20I 0 ExtProc('write')
D fd 10I 0 value
D buf * value
D size 10I 0 value
D xmlfind PR 65000A
D xml * value
D nbr 10i 0 value
D xbeg 32A value
D xbeg1 32A value
D xend 32A value
D Main PR ExtPgm('ZZVRYSQL')
*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
* main(): Control flow
*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
D Main PI
/free
Monitor;
// -----
// input
// ***************************************
// * Send Data Queue (QSNDDTAQ) API
// ***************************************
// * 1 Data queue name Input Char(10)
// * 2 Library name Input Char(10)
// * 3 Length of data Input Packed(5,0)
// * 4 Data Input Char(*) Input
// ***************************************
// * Receive Data Queue (QRCVDTAQ) API
// ***************************************
// * 1 Data queue name Input Char(10)
// * 2 Library name Input Char(10)
// * 3 Length of data Input Packed(5,0)
// * 4 Data Char(*) Output
// * 5 Wait time Input Packed(5,0)
myIPC = '*NA';
myCtl = '*here';
myXmlIn =
'<?xml version="1.0" encoding="ISO-8859-1"?>' + x'0D'
+ '<script>' + x'0D'
+ '<cmd error="fast">DLTDTAQ DTAQ(XMLSERVICE/MYDATAQ)</cmd>' + x'0D'
+ '<cmd error="fast">CRTDTAQ DTAQ(XMLSERVICE/MYDATAQ) MAXLEN(100)</cmd>'
+ x'0D'
+ '<pgm name="QSNDDTAQ">' + x'0D'
+ ' <parm>' + x'0D'
+ ' <data type="10A" var="sndque">MYDATAQ</data>' + x'0D'
+ ' </parm>' + x'0D'
+ ' <parm>' + x'0D'
+ ' <data type="10A" var="sndlib">XMLSERVICE</data>' + x'0D'
+ ' </parm>' + x'0D'
+ ' <parm>' + x'0D'
+ ' <data type="5p0" var="sndlen">50</data>' + x'0D'
+ ' </parm>' + x'0D'
+ ' <parm>' + x'0D'
+ ' <data type="100A" var="snddata">i data queues</data>' + x'0D'
+ ' </parm>' + x'0D'
+ '</pgm>' + x'0D'
+ '<pgm name="QRCVDTAQ">' + x'0D'
+ ' <parm>' + x'0D'
+ ' <data type="10A" var="rcvque">MYDATAQ</data>' + x'0D'
+ ' </parm>' + x'0D'
+ ' <parm>' + x'0D'
+ ' <data type="10A" var="rcvlib">XMLSERVICE</data>' + x'0D'
+ ' </parm>' + x'0D'
+ ' <parm>' + x'0D'
+ ' <data type="5p0" var="rcvlen">50</data>' + x'0D'
+ ' </parm>' + x'0D'
+ ' <parm>' + x'0D'
+ ' <data type="100A" var="rcvdata">bad stuff</data>' + x'0D'
+ ' </parm>' + x'0D'
+ ' <parm>' + x'0D'
+ ' <data type="5p0" var="rcvwait">0</data>' + x'0D'
+ ' </parm>' + x'0D'
+ '</pgm>' + x'0D'
+ '<cmd error="fast">DLTDTAQ DTAQ(XMLSERVICE/MYDATAQ)</cmd>' + x'0D'
+ '</script>' + x'00';
myXmlOut = *BLANKS;
// -----
// sql call XMLSERVICE provided stored procedure(iPLUG4k -iPLUG15M)
Exec Sql call XMLSERVICE/iPLUG32K(:myIPC,:myCtl,:myXmlIn,:myXmlOut);
// -----
// output (CGI)
// -----
// send header + end (LFLF)
data = 'Content-type: text/html' + x'15' + x'15' + x'00';
rn = writeIFS(1:%addr(data):strlen(%addr(data)));
// -----
// send return data
// HTML table
data = '<h3>RPG call XMLSERVICE</h3>' + x'0D';
data = %trim(data) + '<table border="1">' + x'0D';
data = %trim(data) + '<th>Parameter name</th>' + x'0D';
data = %trim(data) + '<th>Type value</th>' + x'0D';
data = %trim(data) + '<th>Input value</th>' + x'0D';
data = %trim(data) + '<th>Output value</th>' + x'0D';
for i = 1 to 9;
xml1 = xmlfind(%addr(myXmlIn): i:'var=' :'"':'"');
xml2 = xmlfind(%addr(myXmlIn): i:'type=':'"':'"');
xml3 = xmlfind(%addr(myXmlIn): i:'<data':'>':'</data>');
xml4 = xmlfind(%addr(myXmlOut):i:'<data':'>':'</data>');
// HTML table row
data = %trim(data) + '<tr>' + x'0D';
data = %trim(data) + '<td>' + %trim(xml1) + '</td>' + x'0D';
data = %trim(data) + '<td>' + %trim(xml2) + '</td>' + x'0D';
data = %trim(data) + '<td>' + %trim(xml3) + '</td>' + x'0D';
data = %trim(data) + '<td>' + %trim(xml4) + '</td>' + x'0D';
data = %trim(data) + '</tr>' + x'0D';
endfor;
data = %trim(data) + '</table>' + x'00';
rn = writeIFS(1:%addr(data):strlen(%addr(data)));
On-error;
Endmon;
return;
/end-free
*****************************************************
* xmlfind
*****************************************************
P xmlfind B
D xmlfind PI 65000A
D xml * value
D nbr 10i 0 value
D xbeg 32A value
D xbeg1 32A value
D xend 32A value
* vars
D i s 10i 0 inz(0)
D cbeg s 33A inz(*BLANKS)
D cbeg1 s 33A inz(*BLANKS)
D cend s 33A inz(*BLANKS)
D pbeg s * inz(*NULL)
D pend s * inz(*NULL)
D xmlFragment s 65000A inz(*BLANKS)
/free
cbeg = %trim(xbeg) + x'00';
cbeg1 = %trim(xbeg1) + x'00';
cend = %trim(xend) + x'00';
pbeg = xml;
for i = 1 to nbr;
// <item stuff>123</item>
// x
if pbeg <> *NULL;
pbeg = strstr(pbeg + 1:%addr(cbeg));
endif;
endfor;
// <item stuff>123</item>
// x
if pbeg <> *NULL;
pbeg = strstr(pbeg:%addr(cbeg1));
endif;
if pbeg <> *NULL;
// <item stuff>123</item>
// x x
pbeg += 1;
pend = strstr(pbeg:%addr(cend));
if pend <> *NULL and pend > pbeg;
xmlFragment = %str(pbeg:pend - pbeg);
endif;
endif;
return xmlFragment;
/end-free
P E
XMLSERVICE Functions¶
XMLSERVICE features¶
One sample worth 1,000 words¶
XMLSERVICE can handle multiple requests in a single XML input document.
XMLSERVICE works with standard
https://
, wherein, “secure” as any other web facility SSL encrypted (actual user profile, see xmlcgi.pgm).XMLSERVICE works with Basic Auth, etc., because XMLSERVICE is just another RPG CGI (*NONE user/pwd, see crtnone.clp/xmlnone.pgm).
XMLSERVICE REST example is simple html/xml, but any language will do, with/without a language toolkit (Common lab):
<form id="myForm" name="myForm" action="https://common1.frankeni.com:47700/cgi-bin/xmlcgi.pgm" method="post"> <input type="hidden" name="db2" value="*LOCAL"> <br>User: <input type="input" name="uid" value=""> (see instructor) <br>Password: <input type="password" name="pwd" value=""> <input type="hidden" name="ipc" value="*na"> <input type="hidden" name="ctl" value="*here"> <br>XML Input: <br><textarea readonly name="xmlin" rows="20" cols="100"> <?xml version='1.0'?> <xmlservice> <cmd>CHGLIBL LIBL(XMLSERVICE) CURLIB(XMLSERVICE)</cmd> <pgm name='ZZCALL'> <parm><data type='1A'>a</data></parm> <parm><data type='1A'>b</data></parm> <parm><data type='7p4'>11.1111</data></parm> <parm><data type='12p2'>222.22</data></parm> <parm> <ds> <data type='1A'>x</data> <data type='1A'>y</data> <data type='7p4'>66.6666</data> <data type='12p2'>77777.77</data> </ds> </parm> </pgm> <sql> <query>select * from QIWS.QCUSTCDT where LSTNAM='Jones'</query> <fetch block='all'/> </sql> </xmlservice> </textarea> <input type="hidden" name="xmlout" value="512000"> <br><input type="submit" name=submit" value="submit" /> </form> </body> </html>
XMLSERVICE interfaces (with download)¶
XMLSERVICE includes two basic interfaces, REST (Apache), and, database (DB2). Either interface receives XML input and returns XML output. The samples below show XMLSERVICE library (download/test library), but multiple language products ship XMLSERVICE in different libraries, so adjust your LIB accordingly (PHP - ZENDSVR/ZENDSVR6, Ruby-POWERRUBY, etc.). XMLSERVICE uses hard coded library schemes (PLUGCONF), to avoid interfering with user set library lists, therefore, you can only SAV/RST XMLSERVICE to the original library. If you move XMLSERVICE non-originating libraries it will fail at runtime.
- REST interface (xmlcgi.pgm)
http://myibmi/cgi-bin/xmlcgi.pgm?db2=x&uid=x&pwd=x&ipc=x&ctl=x&xmlin=x&xmlout=x&persis=x
db2 - what database (*LOCAL tested)
uid - user profile (*NONE - no uid version 1.5+)
pwd - profile password (*NONE - no password version 1.5+)
ipc - IPC key name/security route to XMLSERVICE job (/tmp/fred01, etc.)
ctl - CTL admin control XMLSERVICE job (see control below)
xmlin - XML input document (request)
xmlout - expected size of XML output document (response size in bytes)
optional:
persis - name persistent DB2 connection 8 chars or less (not often used)
Configure (LIB match actual product, php, ruby, etc.):
/www/myinstance/conf/httpd.conf
ScriptAlias /cgi-bin/ /QSYS.LIB/XMLSERVICE.LIB/
<Directory /QSYS.LIB/XMLSERVICE.LIB/>
AllowOverride None
order allow,deny
allow from all
SetHandler cgi-script
Options +ExecCGI
</Directory>
- DB2 stored procedure interface (lib/iPLUGxxx - xmlstoredp.srvpgm)
call XMLSERVICE.iPLUG512K(ipc, ctl, xmlin [, xmlout])
IN IPC CHAR(1024) - IPC key name/security
IN CTL CHAR(1024) - CTL admin control XMLSERVICE job
IN CI CLOB(15M) - XML input document (request)
OUT CO CLOB(15M) - XML output document (response)
Note: iPLUGRxxx procedures return a result set that is collected by fetch.
call XMLSERVICE.iPLUG512K(ipc, ctl, xmlin xmlout)
---
XMLSERVICE.iPLUG4K(IN IPC CHAR(1024), IN CTL CHAR(1024),IN CI CHAR(4064), OUT C0 CHAR(4064))
XMLSERVICE.iPLUG32K(IN IPC CHAR(1024), IN CTL CHAR(1024), IN CI CLOB(32000), OUT CO CLOB(32000))
XMLSERVICE.iPLUG65K(IN IPC CHAR(1024), IN CTL CHAR(1024), IN CI CLOB(65K), OUT CO CLOB(65K))
XMLSERVICE.iPLUG512K(IN IPC CHAR(1024), IN CTL CHAR(1024), IN CI CLOB(512K), OUT CO CLOB(512K))
XMLSERVICE.iPLUG1M(IN IPC CHAR(1024), IN CTL CHAR(1024), IN CI CLOB(1M), OUT CO CLOB(1M))
XMLSERVICE.iPLUG5M(IN IPC CHAR(1024), IN CTL CHAR(1024), IN CI CLOB(5M), OUT CO CLOB(5M))
XMLSERVICE.iPLUG10M(IN IPC CHAR(1024), IN CTL CHAR(1024), IN CI CLOB(10M), OUT CO CLOB(10M))
XMLSERVICE.iPLUG15M(IN IPC CHAR(1024), IN CTL CHAR(1024), IN CI CLOB(15M), OUT CO CLOB(15M))
stmt = call XMLSERVICE.iPLUG512K(ipc, ctl, xmlin)
while (row = fetch(stmt)) xmlout += row[0]
---
XMLSERVICE.iPLUGR4K(IN IPC CHAR(1024), IN CTL CHAR(1024), IN CI CHAR(4096))
XMLSERVICE.iPLUGR32K(IN IPC CHAR(1024), IN CTL CHAR(1024), IN CI CHAR(32000))
XMLSERVICE.iPLUGR65K(IN IPC CHAR(1024), IN CTL CHAR(1024), IN CI CLOB(65K))
XMLSERVICE.iPLUGR512K(IN IPC CHAR(1024), IN CTL CHAR(1024), IN CI CLOB(512K))
XMLSERVICE.iPLUGR1M(IN IPC CHAR(1024), IN CTL CHAR(1024), IN CI CLOB(1M))
XMLSERVICE.iPLUGR5M(IN IPC CHAR(1024), IN CTL CHAR(1024), IN CI CLOB(5M))
XMLSERVICE.iPLUGR10M(IN IPC CHAR(1024), IN CTL CHAR(1024), IN CI CLOB(10M))
XMLSERVICE.iPLUGR15M(IN IPC CHAR(1024), IN CTL CHAR(1024), IN CI CLOB(15M))
Enable DB2 drivers with no LOB support (JTOpen lite enabler - 1.9.0+):
XMLSERVICE.iPLUGRC32K(IN IPC CHAR(1024), IN CTL CHAR(1024), IN CI VARCHAR(32700), IN CNT INTEGER)
Note iPLUGRC32K:
XML input VARCHAR(32700) can be a limit, therefore interface allows
accumulated sends of partial XML document. Any call with non-zero
counter (CNT > 0), XMLSERVICE assumes to be XML document partial
accumulation. When counter reaches zero (CNT = 0),
XMLSERVICE will process the request.
XMLSERVICE Control words¶
Control (CTL) keywords for operator control over XMLSERVICE jobs.
- stateless connection – xmlservice here
($ctl="*here";)
– run in QSQSRVR job, db2_connect current profile control. *here overrides/ignores ipc, runs “*here” in the QSQSRVR job. - private connection 1 – xmlservice spawn
($ctl=""; $ipc="/tmp/user";)
– inherit parent job attributes, include QSQ job profile, and, job description. Per manual (below), xmlservice spawn server job has always been default when ipc included (internalKey), but missing *sbmjob.- Change: 1.9.3 - Rod asked QSQSRVR name changed on spawn (spawn child name also QSQSRVR), therefore, new name spawn child will be XMLSERVICE (XMLSERVICE PGM-XMLSERVICE).
- private connection 2 – xmlservice sbmjob
($ctl="*sbmjob"; $ipc="/tmp/user";)
– control sbmjob, job description, etc. *sbmjob allows control of job description for xmlservice server job.- Alternative *sbmjob, xmlservice allows full control via
<sbmjob>SBMJOB CMD(CALL PGM(XMLSERVICE/XMLSERVICE) PARM('/tmp/user') ... other sbmjob parms ...)</sbmjob>
(XTOOLKIT PGM-XMLSERVICE).
- Alternative *sbmjob, xmlservice allows full control via
*****************************************************
* IPC control options:
*----------------------------------------------------
*--- very high peformance ignore all flag parsing (loop calls, etc.)
* *ignore
* - do not parse flags (high performance)
* example: $ctl="*ignore";
*----------------------------------------------------
*--- kill XMLSERVICE job
* *immed
* - end server immed destroy IPC
* example: $ctl="*immed";
*----------------------------------------------------
*--- misc functions XMLSERVICE
* *license
* - return license for this code
* *session
* - retrieve session key (IPC name /tmp/fred042)
* *clear
* - clear internal XMLSERVICE caches,
* but will not deactivate loaded PGM/SRVPGM, etc.
*----------------------------------------------------
* -- if you need to fix result set return drivers (iPLUGRxxx)
* hack for pesky DB2 drivers adding "junk" to back of records.
* *hack
* - add </hack> each record of a result set
* example: $ctl="*hack";
* - iPLUGRxxx XMLSERVICE stored procedures
* return result sets of 3000 byte records,
* you must loop fetch and concat records
* to recreate output XML ... except
* some DB2 drivers have junk end ...
* - enable easy rec truncate ill behaved drivers
* remove all past </hack> during concat
* loop fetch records 1-n
* rec1: <script>....</hack> (3000 bytes)
* rec2: ............</hack> (3000 bytes)
* recn: ...</script></hack> (<3000 bytes)
*----------------------------------------------------
* -- pause XMLSERVICE job(s) for debugger attach (message to qsysopr)
* *debug
* - stop call server with message qsysopr (XMLSERVICE)
* example: $ctl="*debug";
* *debugproc
* - stop stored proc with message qsysopr (client QSQSRVR)
* *debugcgi
* - stop CGI with message qsysopr (XMLCGI only)
* *test[(n)]
* - test parse XML in/out and report n level information
*----------------------------------------------------
* -- override default XMLSERVICE client/server spawn child behaviour
* *sbmjob[(lib/jobd/job/asp)]
* - sbmjob job (instead of XMLSERVICE default spawn)
* example: $ctl="*sbmjob";
* example: $ctl="*sbmjob(QSYS/QSRVJOB/XTOOLKIT)";
* example: $ctl="*sbmjob(ZENDSVR/ZSVR_JOBD/XTOOLKIT)";
* - default values provided plugconf.rpgle
* - optional asp INLASPGRP(ASP1) (added 1.6.5)
* -- Notes:
* - See embedded XML overrides for user full control
* of XMLSERVICE start behavior SBMJOB settings
* *here
* - run stateless in stored proc job (client only job)
* example: $ctl="*here";
* - commonly known as running in PHP job, but in fact
* more likely runs in database job you connected
* on/off machine DRDA/ODBC/PASE (QSQSRVR, etc.)
* - generally runs slower using "one process"
* because XMLSERVICE has to restart itself,
* wake up PASE, find/load your PGM call, etc.
* *nostart
* - disallow spawn and sbmjob (web not start anything)
* example: $ctl="*nostart";
* - probably prestart all your XMLSERVICE jobs
* SBMJOB CMD(CALL PGM(XMLSERVICE/XMLSERVICE)
* PARM('/tmp/db2ipc042')) USER(DB2)
* - consider using a custom plugconf to disable
* issues with timeout defaults (*idle/*wait)
* *java (1.9.2)
* - start JVM allowing user classpath
* <cmd>ADDENVVAR ENVVAR(CLASSPATH) VALUE('$ours')
* REPLACE(*YES)</cmd>
* <pgm>... calling my RPG w/JAVA ... </pgm>
* *sqljava or *dbgjava (port 30000) (1.9.2)
* - start JVM allowing DB2 classpath (no user control)
* SQLJ.INSTALL_JAR into schema
* /QIBM/UserData/OS400/SQLLib/Function/jar/(schema)
*----------------------------------------------------
* -- CDATA xml output
* *cdata(on|off)
* - xml data fields returned with cdata
* <data><![CDATA[...]]></data>
* - on - CDATA (default 1.6.2)
* - off - CDATA off
* -- escape xml reserved chars
* *escp
* 5 chars will be escaped in the output
* & --> &
* > --> >
* < --> <
* ' --> '
* " --> "
* -- special hex encoded XML document
* *hex(in/out/both)
* - xml document hex encoded avoiding conversions
* - in - HEX convert input side only
* - out - HEX convert all output
* - both - HEX convert input and output
* example (php sudo):
* $doc = "<?xml version='1.0'> ... </script>";
* $hexin = bin2hex($doc); // doc-2-hexin
* XMLSERVICE($ipc, $ctl="*hex", $hexin, $hexout);
* $doc = pack("H*", $hexout); // hexout-2-doc
* -- Notes
* - hex string is '0123456789ABCDEF' or '...abcdef'
* which are CCSID immutable (or upper most always)
* which will arrive at XMLSERVCE in ebcdic via
* "natural" clob/rest interface transport conversion
* (ie., iBLOBxxx would not work *hex, arrive ascii)
* YES (clob): ebcdic (x'F0F1...C6') or (x'...86')
* NO (blob): ascii (x'3031...46') or (x'...66')
* - hex option is useful with CCSID below
* with following order of operations:
* - input : *hex followed by *before
* - output: *after followed by *hex
* -- CCSID conversion XMLSERVICE (1.6.2)
* *before(CCSIDFrom/CCSIDTo[/action])
* - conversion before calling XML processing
* - CCSIDFrom - XML document client CCSID
* - CCSIDTo - XML document XMLSERVICE CCSID
* - action
* call - call XMLSERVICE (default)
* nocall - convert only (no call)
* *after(CCSIDFrom/CCSIDTo)
* - conversion after calling XML processing
* - CCSIDFrom - XML document XMLSERVICE CCSID
* - CCSIDTo - XML document client CCSID
* *pase(CCSIDpase/CCSIDile)
* - conversion PGM, LIB, etc, names call processing
* - CCSIDpase - name CCSID PASE side (ascii)
* - CCSIDile - name CCSID ILE side (ebcdic)
*
* example: $ctl="*before(819/37) *after(37/819)";
* - DB2 interface provides many possibilities
* iPLUGxxx - original CLOB automatic DB2 converts
* iBLOBxxx - raw binary no DB2 converts (1.6.2)
* - action='nocall' is used to convert
* anything on IBM i and send it back
* without calling XMLSERVICE
* - DB2 interface conversion effects are
* virtually unlimited performed on IBM i
* avoiding additional "code tables" on client
* - *pase default (should work)
* ILE ccsid 0 means job CCSID (all ebcdic)
* PASE csid default Qp2paseCCSID (see API)
*----------------------------------------------------
* -- server time out jobs XMLSERVICE (1.6.2)
* *wait[(seconds[/action])]
* - client side wait for XMLSERVICE call (client side)
* example: $ctl="*wait(10)";
* - default action *wait(60/busy) (see plugconfx)
* *call[(seconds[/action[/duration[/job]]])]
* - client/server side XMLSERVICE call wait (PGM, SRVPGM, PASE, etc)
* example: $ctl="*wait(10) *call(5/busy/client)";
* - default for both client/server is *call(0)
* means wait on call forever (user code flawless),
* but can be overriden client/server/both
* *idle[(seconds[/action[/duration]])]
* - server side XMLSERVICE idle no activity
* example: $ctl="*wait(10/kill) *call(30/kill) *idle(30/kill/perm)";
* - default action *idle(1800/kill) (see plugconfx)
* -- time out parameters
* seconds:
* -1 - current default timer
* 0 - no timer, no timeout, wait forever
* n - idle timer "pop" seconds
* action:
* kill - end job immed
* user - user override signal behaviour (see plugconfx)
* busy - return busy XML (client side)
* busy response (1301050):
* <error>
* <errnoxml>1301050</errnoxml>
* <xmlerrmsg>IPC timeout busy</xmlerrmsg>
* </error>
* duration:
* perm - set and use new defaults all requests
* orig - reset and use original compile defaults (see plugconfx)
* job:
* client - *call action applies client side
* server - *call action applies server side
* -- Notes:
* - default timeout/action provided plugconf.rpgle,
* but each request may override/reset to fit task(s)
* - signal SIGALRM used with this function
* can affect user program calls,
* *call(0) may be used to turn off timer
* during user program calls
* - action 'user' allows for custom signal
* processing in the RPG code (see plugconfx)
* - if duration not specified, attributes
* *wait(),*call(),*idle() are temporary
* for this call only and return to last defaults.
* - if 'job' not specified on *call(),
* attribute settings apply to both sides
* - end job immed kills XMLSERVICE job (server)
* and destroys IPC, so any waiting client is
* released with an IPC missing error.
*----------------------------------------------------
* -- batch XMLSERVICE processing (version 1.6.2)
* *batch
* - use a free batch slot 1 - 16 (release client)
* responses:
* <id status='set'>1-16</id> - set batch processing
* <id status='full'>0</id> - no slots available
* example: $ctl="*sbmjob *batch";
* - *batch holds output XML memory until retrieved
* - *get(n) retrieve of XML memory (some time later)
* - *batch releases XMLSERVICE client (caller), and
* returns batch slot number (n) assigned for work.
* *get[(n)]
* - get XML results from batch slot 1 - 16 (release slot)
* responses (report not available):
* <id status='small'>1-16</id> - buffer too small
* <id status='done'>1-16</id> - complete removed
* example: $ctl="*get";
* example: $ctl="*get(3)";
* - use with *wait(sec/busy) to avoid hang during
* *batch running (do something else while waiting)
* - *get without slot number will get one result
* any completed batch slot
* - *get(3) will only get result of batch slot 3
*----------------------------------------------------
* -- flight data options (affects performance, state of disrepair) --
* *rpt
* - performance report last call
* *fly
* - flight record performance
* *nofly
* - no flight record performance (default)
* *justproc
* - call stored proc, get into XMLSERVICE client,
* do nothing while in XMLSERVICE, return back
* - used to check transport speed only
* -- log to database file (1.7.1) --
* *log[(key)]
* - log records into database
* *nolog
* - no log records into database (default)
* Note:
* - *log key is unique allowing both PHP and XMLSERVICE
* to record event log data and produce queries of collected
* reports.
* Log file layout:
* create table XMLSERVLOG/LOG (
* key varchar(64) NOT NULL WITH DEFAULT,
* log TIMESTAMP NOT NULL WITH DEFAULT,
* text varchar(64) NOT NULL WITH DEFAULT)
* Supplemental log dump XML data layout:
* create table XMLSERVLOG/DUMP (
* key varchar(64) NOT NULL WITH DEFAULT,
* log TIMESTAMP NOT NULL WITH DEFAULT,
* text clob(15M) NOT NULL WITH DEFAULT)
* - programers/vendors can alter xmlservice log database
* with plugconf (or custom)
*****************************************************
* embedded XML control overrides and function
*****************************************************
* sbmjob full user override of SBMJOB for XMLSERVICE start-up -- (1.7.0+)
* <sbmjob>SBMJOB CMD(CALL PGM(ZENDSVR/XMLSERVICE) PARM('/tmp/override'))</sbmjob>
* Where SBMJOB can be any user settings (cut/paste green screen command) ...
* ... required parameters for for XMLSERVICE to start CALL + PARM
* example:
* CMD(CALL PGM(ZENDSVR/XMLSERVICE) PARM('/tmp/xxxxxx')) <-- xmlservice test lib
* -- or --
* CMD(CALL PGM(ZENDSVR/XMLSERVICE) PARM('/tmp/xxxxxx')) <-- Zend Server production lib
* -- all other sbmjob parms at your control --
* Note:
* - SBMJOB full control allows user to set any type of LIBL,
* or SBMJOB options, or even custom PGM to call XMLSERVICE
*****************************************************
* start/use/stop exclusive use shared IPC (hotel reservation) -- (1.6.8+)
* <start>KEY</start> - acquire IPC -- first request
* <use>KEY</use> - match IPC -- each request
* <stop>KEY</stop> - release IPC -- last request
* Where KEY anything managed by user (key-2-IPC session data) ...
* ... random based key -- scaling open any users,
* want benefit of private RPG call (open files, etc),
* do not care about reservation multi-request transaction
* -> hybrid stateless/private call,
* hold IPC for life of script only and release,
* but limit jobs
* ... user based key -- scaling come/go users,
* want benefit of private RPG call (open files, etc),
* also want reservation transactions
* -> hybrid persistent/private,
* transaction across multi-request (browser clicks),
* but limited jobs
* ... task based key -- everyone uses same task/tasks
* limited pool jobs and all must wait a turn
* -> hybrid private/persistent with pre-start pool,
* transaction across multi-request (browser clicks),
* load balancing design to limit machine stress
* example many requests exclusive use IPC
* -- no time out --
* $ctl .= " *idle(0)"
* -- request 1 --
* <?xml version="1.0"?>
* <script>
* <start>unique-user-key</start>
* </script>
* -- request 2 (two minutes later) --
* <?xml version="1.0"?>
* <script>
* <use>unique-user-key</use>
* <cmd exec='rexx'>RTVJOBA USRLIBL(?)</cmd>
* </script>
* -- request 3 (1/2 hour later) --
* <?xml version="1.0"?>
* <script>
* <use>unique-user-key</use>
* <pgm name='ZZCALL'>
* <parm>
* <data type='1A'>a</data>
* </parm>
* <return>
* <data type='10i0'>0</data>
* </return>
* </pgm>
* </script>
* -- request n (2 hours later) --
* <?xml version="1.0"?>
* <script>
* <stop>unique-user-key</stop>
* </script>
* Note:
* - <start>unique-user-key</start>
* acquire exclusive IPC if available,
* - <use>unique-user-key</use>
* must appear XML every request
* job held forever until see <stop>
* - <stop>unique-user-key</stop>
* release IPC for any other use
* - <start>no-match-key</start>
* or <use>unique-user-key</use>
* non-matching key results in error
* almost instantly (no wait)
* busy response (1301060):
* <error>
* <errnoxml>1301060</errnoxml>
* <xmlerrmsg>IPC owner busy</xmlerrmsg>
* </error>
* - thoughtful setting server idle timeout
* can control unwanted reservation hangs
* due to careless users or errors
* $ctl .= " *idle(60)"
*************************************************************************
XMLSERVICE call CMD¶
Syntax can largely be cut/paste from a 5250 command line.
*************************************************************************
* 1) call i CMD
* XMLSERVICE allows calls of *CMDS on IBM i. Typically, you cut/paste
* from a 5250 QCMD line using prompt (F4). You may use choose the utility
* to run your command with attribute 'exec'. However, *CMDS with
* in/out parameters, like RTVJOBA., you must use 'exec'='rexx'.
* ---
* <cmd [exec='cmd|system|rexx'
* hex='on'
* before='cc1/cc2/cc3/cc4'
* after='cc4/cc3/cc2/cc1'
* error='on|off|fast'
* ]>values (see IBM i *CMD)</cmd>
* ---
* cmd - command tag
* values - (see IBM i *CMD IBM i- 5250 cut/paste)
* options
* exec
* cmd - qcmdexe only return true/false (default)
* system - system utility return CPFxxxx
* rexx - rexx output parms and return CPFxxxx
* (?) character type
* (?N) explicit cast numeric
* hex (1.6.8)
* on - input character hex (5147504C20202020)
* before
* cc(n) - input ccsid1->ccsid2->ccsid3->ccsid4
* after
* cc(n) - output ccsid1->ccsid2->ccsid3->ccsid4
* error (1.7.6)
* on - script stops, full error report
* off - script continues, job error log (default)
* fast - script continues, brief error log
* ---
* example run command (original)
* <?xml version="1.0"?>
* <xmlservice>
* <cmd>ADDLIBLE LIB(DB2) POSITION(*FIRST)</cmd>
* </xmlservice>
* ---
* example output command (exec='rexx')
* <?xml version='1.0'?>
* <xmlservice>
* <cmd exec='rexx'>RTVJOBA USRLIBL(?) SYSLIBL(?)</cmd>
* <cmd exec='rexx'>RTVJOBA CCSID(?N) OUTQ(?)</cmd>
* <cmd exec='rexx'>RTVSYSVAL SYSVAL(QDATETIME) RTNVAR(?)</cmd>
* </xmlservice>
* ---
* Note:
* - <cmd>command</cmd> should be all on one line (no LFs)
* - <cmd> run in XMLSERVICE job.
* cmd - qcmdexe only return true/false (default)
* system - system utility return CPFxxxx (1.5.2)
* <cmd exec='system'><error>CPF2103</error></cmd>
* rexx - rexx output parms and return CPFxxxx (1.5.2)
* <cmd exec='rexx'><error>CPF2103</error></cmd>
* - exec='rexx'
* All parms are assume to be character unless
* (?N) to explicit cast to numeric (rtvjoba). Most
* RTVxxxx that ask for a CL variable RTNVAR will
* not require the (?N) cast (IBM i manuals).
* QTEMP/XMLREXX(HOW) is created on demand
* by RPG module plugile (Github download).
* QTEMP/OUTREXX(OUTREXX) is created for
* command temp data between RPG and REXX.
* - Up to four conversions can take place
* for the truly complex ccsid issues (1.6.8)
* <cmd hex='on' before='cc1/cc2/cc3/cc4' after='cc4/cc3/cc2/cc1'>
* flow:
* -> PHP client bin2hex('wild_ascii_raw_chars')
* -> xmlservice hex2bin back to 'wild_ascii_raw_chars'
* -> xmlservice convert cc1->cc2->cc3->cc4 (before)
* -> xmlservice make ILE call
* -> xmlservice convert cc4->cc3->cc2->cc1 (after)
* -> xmlservice tohex "xml_hex_back"
* -> PHP client $chars = pack('H*',"xml_hex_back")
* output (incompatible change hex/ccsid 1.7.4+):
* <cmd exec='rexx' hex='on' before='819/37' after='37/819'>
* <success><![CDATA[+++ success RTVJOBA USRLIBL(?) SYSLIBL(?)]]></success>
* <row><data desc='USRLIBL'><hex><![CDATA[5147504C20202020202020]]></hex></data></row>
* <row><data desc='SYSLIBL'><hex><![CDATA[5153595320202020202020]]></hex></data></row>
* </cmd>
* - error='on,off,fast' (1.7.6)
* on - script stops, full error report
* off - script continues, job error log (default)
* fast - script continues, brief error log
*************************************************************************
XMLSERVICE call PASE¶
Syntax can mostly be cut/paste from PASE shell (call qp2term).
*************************************************************************
* 2) call PASE utility
* XMLSERVICE allows calls of PASE utilities on IBM i. Typically, you cut/paste
* from a PASE command line (call qp2term). PASE shell 'sh' is used for
* execution of your utilities, which, is default behavior of PASE popen() API.
* ---
* <sh [rows='on|off'
* hex='on'
* before='cc1/cc2/cc3/cc4'
* after='cc4/cc3/cc2/cc1'
* error='on|off|fast'
* ]>values (see PASE utility)</sh>
* ---
* sh - shell tag
* values - (see PASE utility - call qp2term cut/paste)
* options
* rows
* on - return rows lines
* off - return one string (default)
* hex (1.7.4)
* on - input character hex (5147504C20202020)
* before
* cc(n) - input ccsid1->ccsid2->ccsid3->ccsid4
* after
* cc(n) - output ccsid1->ccsid2->ccsid3->ccsid4
* ---
* error (1.7.6)
* on - script stops, full error report
* off - script continues, job error log (default)
* fast - script continues, brief error log
* ---
* example run PASE shell
* <?xml version="1.0"?>
* <xmlservice>
* <sh rows='on'>/QOpenSys/usr/bin/system 'wrkactjob' | grep -i fr</sh>
* </xmlservice>
* ---
* Note:
* - syntax looks as if typed on console (call qp2term)
* <sh>pase utility</sh> runs "slower" because a child job
* is created to run each PASE utility (normal Unix behavior).
* All other XML/ILE functions run within XMLSERVICE job.
* - Using nested shells within this sh shell may
* produce unpredictable results.
* - hex='on' before='' after='' -- same as <cmd> (1.7.0)
* output (incompatible change hex/ccsid 1.7.4+):
* <sh rows='on' hex='on' before='819/37' after='37/819'>
* <row><hex>746F74616C2031363636313034</hex></row>
* </sh>
* output (rows='off' 1.7.4+):
* <sh hex='on' before='819/37' after='37/819'>
* <hex>746F74616C2031363636313034</hex>
* </sh>
* - error='on,off,fast' (1.7.6)
* on - script stops, full error report
* off - script continues, job error log (default)
* fast - script continues, brief error log
*************************************************************************
XMLSERVICE call QSH (1.9.8+)¶
Syntax can mostly be cut/paste from QSH shell (qsh).
*************************************************************************
* 2.5) call QSH utility (1.9.8+)
* XMLSERVICE allows calls of QSH utilities on IBM i. Typically, you cut/paste
* from a QSH command line. STRQSH is used for execution of your utilities.
* ---
* <qsh [rows='on|off'
* hex='on'
* before='cc1/cc2/cc3/cc4'
* after='cc4/cc3/cc2/cc1'
* error='on|off|fast'
* ]>values (see QSH utility)</qsh>
* ---
* qsh - shell tag
* values - (see QSH utility - qsh cut/paste)
* options
* rows
* on - return rows lines
* off - return one string (default)
* hex (1.7.4)
* on - input character hex (5147504C20202020)
* before
* cc(n) - input ccsid1->ccsid2->ccsid3->ccsid4
* after
* cc(n) - output ccsid1->ccsid2->ccsid3->ccsid4
* ---
* error (1.7.6)
* on - script stops, full error report
* off - script continues, job error log (default)
* fast - script continues, brief error log
* ---
* example run QSH shell
* <?xml version="1.0"?>
* <xmlservice>
* <qsh rows='on'>/usr/bin/system 'wrkactjob' | /usr/bin/grep -i fr</qsh>
* </xmlservice>
* ---
* Note:
* - Recommend qualify qsh utilities with /usr/bin.
* This will avoid ccsid conversion between PASE/QSH utilities.
* - syntax looks as if typed on console (qsh)
* <qsh>QSH utility</qsh> runs "slower" because a child job
* is created to run each QSH utility (normal Unix behavior).
* - Using nested shells within this qsh shell may
* produce unpredictable results.
* - hex='on' before='' after='' -- same as <cmd> (1.7.0)
* - error='on,off,fast' (1.7.6)
* on - script stops, full error report
* off - script continues, job error log (default)
* fast - script continues, brief error log
XMLSERVICE call PGM¶
Call PGM, SRVPGM, or system API, using XML syntax.
- Call PGM using this XML syntax.
- Call SRVPGM using this XML syntax.
*************************************************************************
* 3) call PGM/SRVPGM
* XMLSERVICE allows calls of *PGM and *SRVPGM on IBM i. Typically, you match
* call parameters, including data structures, and/or simple data elements.
* ---
* pgm name (*PGM or *SRVPGM)
* <pgm name=''
* [lib=''
* func=''
* mode='opm|ile'
* error='on|off|fast'
* ]>values (see <parm> and <return>) </pgm>
* ---
* pgm - IBM i *PGM or *SRVPGM name (tag)
* values - (see parm and return)
* options
* lib
* library - IBM i library name
* func
* function- IBM i *SRVPGM function name
* mode
* ile - ILE and PASE memory (default)
* opm - ILE only memory (PASE can not view)
* error (1.7.6)
* on - script stops, full error report
* off - script continues, job error log (default)
* fast - script continues, brief error log
*
* ---
* pgm parameters
* <parm [io='in|out|both|omit'
* by='val|ref'
* ]>values (see <ds> and <data>)</parm>
* ---
* parm - parm name (tag)
* values - (see ds or data)
* options
* io
* in - input only
* out - output only
* both - input/output only (default)
* omit - omit (1.2.3)
* by
* ref - pass by reference (default)
* val - pass by value (1.9.9.3+)
*
* ---
* pgm return
* <return>values (see <ds> and <data>)</return>
* ---
* return - return tag
* values - (see ds or data)
* options
* na - no options
*
* ---
* pgm data structure
* <ds [dim='n' dou='label'
* len='label'
* data='records'
* ]>values (see <ds> or <data>)</ds>
* ---
* ds - data structure tag
* values - (see ds or data)
* options
* dim
* n - array dimension value (default dim1)
* dou
* label - match array dou terminate parm label (see data)
* len (1.5.4)
* label - match calculate length of ds parm lable (see data)
* data (1.7.5)
* records - data in records tag
*
* ---
* pgm data elements
* <data type='data types'
* [dim='n'
* varying='on|off|2|4'
* enddo='label'
* setlen='label'
* offset='label'
* hex='on|off' before='cc1/cc2/cc3/cc4' after='cc4/cc3/cc2/cc1'
* trim='on|off'
* next='label'
* ]>(value)</data>
* ---
* data - data value name (tag)
* values - value,
* type
* 3i0 int8/byte D myint8 3i 0
* 5i0 int16/short D myint16 5i 0
* 10i0 int32/int D myint32 10i 0
* 20i0 int64/int64 D myint64 20i 0
* 3u0 uint8/ubyte D myint8 3u 0
* 5u0 uint16/ushort D myint16 5u 0
* 10u0 uint32/uint D myint32 10u 0
* 20u0 uint64/uint64 D myint64 20u 0
* 32a char D mychar 32a
* 32a {varying2} varchar D mychar 32a varying
* 32a {varying4} varchar4 D mychar 32a varying(4)
* 12p2 packed D mydec 12p 2
* 12s2 zoned D myzone 12s 2
* 4f2 float D myfloat 4f
* 8f4 real/double D myfloat 8f
* 3b binary D mybin (any)
* 40h hole (no out) D myhole (any)
* options
* dim
* n - array dimension value (default dim1)
* varying
* on - character varying data (same as varying2)
* off - character non-varying data (default)
* 2 - character varying data
* 4 - character varying data
* enddou
* label - match array dou terminate parm label (see ds)
* setlen (1.5.4)
* label - match calculate length of ds parm lable (see ds)
* offset
* label - match offset label (see overlay)
* hex (1.6.8)
* on - input character hex (5147504C20202020)
* before
* cc(n) - input ccsid1->ccsid2->ccsid3->ccsid4
* after
* cc(n) - output ccsid1->ccsid2->ccsid3->ccsid4
* trim (1.7.1)
* on - trim character (default)
* off - no trim character
* next (1.9.2)
* label - match next offset label (see overlay)
*
* ---
* pgm parameters/return overlay
* <overlay
* [io='in|out|both'
* offset='n|label'
* top='on|off|n'
* setnext='nextoff'
* ]>(see <ds> and <data>)</overlay>
* ---
* overlay - structure overlay name (tag)
* values - (see ds or data)
* options
* io
* in - input only
* out - output only
* both - input/output only (default)
* offset
* n - overlay bytes offset relative
* label - overlay match bytes offset label (see data)
* setnext (1.9.2)
* label - overlay match next offset label (see data)
* top
* n - overlay parm number (see parm)
* on - overlay parm first (see parm)
* off - overlay parm last seen (see parm)
* ---
* example run a PGM
* <?xml version="1.0"?>
* <xmlservice>
* <cmd>CHGLIBL LIBL(XMLSERVICE) CURLIB(XMLSERVICE)</cmd>
* <pgm name='ZZCALL' lib=''>
* <parm io='both'>
* <data type='1A' var='INCHARA'>a</data>
* </parm>
* <parm io='both'>
* <data type='1A' var='INCHARB'>b</data>
* </parm>
* <parm io='both'>
* <data type='7p4' var='INDEC1'>11.1111</data>
* </parm>
* <parm io='both'>
* <data type='12p2' var='INDEC2'>222.22</data>
* </parm>
* <parm io='both'>
* <ds>
* <data type='1A' var='INDS1.DSCHARA'>x</data>
* <data type='1A' var='INDS1.DSCHARB'>y</data>
* <data type='7p4' var='INDS1.DSDEC1'>66.6666</data>
* <data type='12p2' var='INDS1.DSDEC2'>77777.77</data>
* </ds>
* </parm>
* <return>
* <data type='10i0'>0</data>
* </return>
* </pgm>
* </xmlservice>
* ---
* example run a SRVPGM
* <?xml version="1.0"?>
* <xmlservice>
* <pgm name='ZZSRV' lib='XMLSERVICE' func='ZZARRAY'>
* <parm comment='search this name'>
* <data var='myName' type='10A'>Ranger</data>
* </parm>
* <parm comment='max allowed return'>
* <data var='myMax' type='10i0'>5</data>
* </parm>
* <parm comment='actual count returned'>
* <data var='myCount' type='10i0' enddo='mycount'>0</data>
* </parm>
* <return>
* <ds var='dcRec_t' dim='999' dou='mycount'>
* <data var='dcMyName' type='10A'>na</data>
* <data var='dcMyJob' type='4096A'>na</data>
* <data var='dcMyRank' type='10i0'>0</data>
* <data var='dcMyPay' type='12p2'>0.0</data>
* </ds>
* </return>
* </pgm>
* </xmlservice>
* ---
* example optional ccsid convert name/lib format (1.6.8)
* <?xml version="1.0"?>
* <xmlservice>
* <pgm>
* <name hex='on' before='cc1/cc2/cc3/cc4'>bin2hex('&fredflin')</name>
* <lib hex='on' before='cc1/cc2/cc3/cc4'>bin2hex('omlated')</lib>
* <func hex='on' before='cc1/cc2/cc3/cc4'>bin2hex('me&proc')</func>
* <parm>
* <ds dim='3'>
* <data type='1A'>a</data>
* </ds>
* </parm>
* <return>
* <ds dim='999'>
* <data type='10i0'>0</data>
* </ds>
* </return>
* </pgm>
* </xmlservice>
* ---
* Note:
* - data types (similar RPG):
* ----------------------------------------------------------------------
* int8/byte D myint8 3i 0 <data type='3i0'/>
* int16/short D myint16 5i 0 <data type='5i0'/>
* int32/int D myint32 10i 0 <data type='10i0'/>
* int64/int64 D myint64 20i 0 <data type='20i0'/>
* uint8/ubyte D myint8 3u 0 <data type='3u0'/>
* uint16/ushort D myint16 5u 0 <data type='5u0'/>
* uint32/uint D myint32 10u 0 <data type='10u0'/>
* uint64/uint64 D myint64 20u 0 <data type='20u0'/>
* char D mychar 32a <data type='32a'/>
* varchar D mychar 32a varying <data type='32a' varying='2'/>
* varchar4 D mychar 32a varying(4) <data type='32a' varying='4'/>
* packed D mydec 12p 2 <data type='12p2'/>
* zoned D myzone 12s 2 <data type='12s2'/>
* float D myfloat 4f <data type='4f2'/>
* real/double D myfloat 8f <data type='8f4'/>
* binary D mybin (any) <data type='3b'>F0F1F2</data>
* hole (no out) D myhole (any) <data type='40h'/>
* ------------------------------------------------------------------------
* type='na' [varying='on|off|2|4'] - character (32A)
* <data type='32a'/>
* <data type='32a' varying='on'>ranger</data>
* <data type='32a'><![CDATA[<i am ranger>]]></data>
* <data type='200A' hex='on' before='1208/930' after='930/1208'>
* bin2hex($japan_raw_ascii_data)
* </data>
* type='npn' - packed decimal (12p2)
* <data type='12p2'/>
* <data type='12p2'>30.29</data>
* type='nsn' - zoned decimal (12s2)
* <data type='12s2'/>
* <data type='12s2'>30.29</data>
* type='nin' - signed integer (5i0, 10i0, 20i0)
* <data type='20i0'/>
* <data type='10i0'>-30</data>
* type='nun' - unsigned integer (5u0, 10u0, 20u0)
* <data type='20u0'/>
* <data type='10u0'>30</data>
* type='nfn' - floating point (4f2, 8f4)
* <data type='4f2'/>
* <data type='4f2'>30.34</data>
* <data type='8f4'>30.34</data>
* type='nb' - binary HEX char (2b, 400b)
* <data type='5b'>F0F1F2CDEF</data>
* <data type='2b'>1FBC</data>
* <data type='2b'>0F0F</data>
* - HEX upper case ('1FBC' not '1fbc')
* - high/low bits (HEX='0F0F' not HEX='F0F')
* type='nh' - 'hole' zero in, nothing out (4096h) (1.2.3)
* <data type='400h'/>
* - PGM/SRVPGM calls (<pgm>,<parm>,<data>,<return>) use syntax
* that looks like RPG to describe the data parameters
* (type='4b', type='32a', type='4f', type='10i0', type='12p2',
* etc.).
* - <data dim='n'> - dim='n' is new to 1.2 version and beyond,
* older versions did not include this feature.
* - Parameters using dou='label', enddo='label',
* label must match for this to work,
* then processing will only return records up to enddo limits.
* - Type 'h' for 'hole' is used to input x'00' fill 'hole'
* in the parameter geometry. It can be used to skip over
* a chunk of complex data that you really did not want to
* deal with or see in output XML. It is also very handy to
* use with overlay when output data is variable
* or unpredictable (1.2.3)
* input:
* <ds>
* <data type='40a'>good stuff</data> <---offset 0
* <data type='400h'/> <---400 x'00' input
* <data type='32a'>more good stuff</data><---offset 440
* </ds>
* output:
* <ds>
* <data type='40a'>stuff back</data> <--- offset 0
* <data type='400h'> </data> <--- ignored output
* <data type='32a'>stuff back</data> <--- offset 440
* </ds>
* - Added parm='omit' for RPG OPTIONS(*OMIT) parameter. A
* *NULL will be passed in this location.
* All parm io='omit' will be excluded from XML
* output returned because *NULL parameter has no data (1.2.3).
* <parm comment='my name' io='omit'>
* <data var='myName' type='10A'>Ranger</data> <--ignore *NULL
* </parm>
* RPG procedure (SRVPGM function):
* D zzomit PI 50A varying
* D myName 10A options(*OMIT) <---- optional omitted (*NULL)
* D yourName 10A
* - Added len='label'/setlen='label' to allow for
* automatic length calculation for various system
* APIs that want a %size(thing) parameter.
* This should work across parameters and within
* parameters (any order), but nesting len/setlen is
* not allowed.
* <parm io="both" comment='Error code'>
* <ds comment='Format ERRC0100' len='rec2'>
* <data type='10i0' comment='returned'>0</data>
* <data type='10i0' comment='available' setlen='rec2'>0</data>
* <data type='7A' comment='Exception ID'> </data>
* <data type='1A' comment='Reserved'> </data>
* </ds>
* </parm>
* - Up to four conversions can take place
* for the truly complex ccsid issues (1.6.8)
* <data type='A' hex='on' before='cc1/cc2/cc3/cc4' after='cc4/cc3/cc2/cc1'>
* flow:
* -> PHP client bin2hex('wild_ascii_raw_chars')
* -> xmlservice hex2bin back to 'wild_ascii_raw_chars'
* -> xmlservice convert cc1->cc2->cc3->cc4 (before)
* -> xmlservice make ILE call
* -> xmlservice convert cc4->cc3->cc2->cc1 (after)
* -> xmlservice tohex "xml_hex_back"
* -> PHP client $chars = pack('H*',"xml_hex_back")
* - V5R4 accomidation for OPM programs like CLP (1.6.8)
* - mode='opm' uses non-teraspace memory to build parm lists
* that are used with _CALLPGMV for a "pure" OPM call mode
* - mode='ile' default using teraspace for "mixed" memory
* compatible with PASE calls (IBM i possiblilities)
* - Allow trim control character/binary <data ... trim='on|off'>
* - trim='on' -- right trim (default character type='na')
* - trim='off' -- include all (default binary type='nb')
* - see <overlay> for offset='label'
* <data offset='label'> <-- memory location to pop off a
* variable/changing offset value
* for use in overlay()
* <overlay top='n' offset='label'> <-- top='n' overlay parameter 'n',
* then add offset='label' pop value
* - offset='label' allows label location to pop off a <data> offset value
* at this data location to add position offset <overlay offset='label'>
* - 'label' is NOT a position location for <overlay>, it only holds
* a offset value in this <data> memory location for things like
* system APIs with offset-2-next.
* - data='records' - data follows in record format
* fast "many records" i/o big data (see below) (1.7.5)
* <parm comment='wsopstdcnt'>
* <data type='3s0' enddo='wsopstdcnt'/>
* </parm>
* <parm comment='findMe1'>
* <ds var='findMe1' data='records'>
* <ds var='dcRec1_t' array='on'>
* <ds var='dcRec1_t'>
* <data var='dcMyName1' type='10A'/>
* <ds var='dcRec2_t'>
* <data var='dcMyName2' type='10A'/>
* <ds var='dcRec3_t'>
* <data var='dcMyName3' type='10A'/>
* <ds var='dcRec_t' dim='999' dou='wsopstdcnt'>
* <data var='dcMyName' type='10A'/>
* <data var='dcMyJob' type='4096A'/>
* <data var='dcMyRank' type='10i0'/>
* <data var='dcMyPay' type='12p2'/>
* </ds>
* </ds>
* </ds>
* </ds>
* </ds>
* </ds>
* <records delimit=':'>:Rgr:B:Ok:nd1:nd1:1:1.1:...:</records>
* </parm>
* a) <records delimit=':'> simply match in order input
* of any complex structure. Output matches
* order input (see above)
* b) <records delimit=':'> delimit can be any character
* not in your complex records (see above)
* c) works with any <parm> or <return>
* d) dou/enddo works, but tricky script to design (be careful)
* - setnext='nextoff' / next='nextoff' - see overlay (1.9.2)
* - len/setlen - auto-len calculate ds setlen='here' (1.5.4)
* - error='on,off,fast' (1.7.6)
* on - script stops, full error report (default)
* off - script continues, job error log
* fast - script continues, brief error log
* - pgm parameters/return overlay (custom offset='bytes', input/output):
* <overlay> works "relative" to "previous" <parm> in
* "order of appearance XML"
* or absolute position to (top='n')
* <pgm>
* --->absolute parm <---relative parm
* ---><parm>complex stuff</parm><-------------------
* | <overlay>complex over parm 1 </overlay>____|
* |
* |--><parm>complex stuff</parm><-------------------
* || <overlay>complex over parm 2 </overlay>____|
* || :
* || <parm>complex stuff</parm><-------------------
* || <overlay>complex over last parm</overlay>____|
* || :
* |___<overlay top='on'>over top parm</overlay>
* | :
* |__<overlay top='2'>over parm 2 </overlay>
* </pgm>
* - top='on|n' allow overlay position to parameter n
* ... top='on' absolute parm='1' (1.2.1)
* ... top='n' absolute parm='n' (1.2.2)
* ... offset='n' bytes offset relative
* to top='n' position (parm 1,2,3, etc)
* - Once the top='n' parm location is etablished, offset='n'
* will move overlay to offset within the parameter.
* <data offset='label'> <-- memory location to pop off a
* variable/changing offset value
* for use in overlay()
* <overlay top='n' offset='label'> <-- top='n' overlay parameter 'n',
* then add offset='label' pop value
* - offset='label' allows label location to pop off a <data> offset value
* at this data location to add position offset <overlay offset='label'>
* - 'label' is NOT a position location for <overlay>, it only holds
* a offset value in this <data> memory location for things like
* system APIs with offset-2-next.
* - setnext='nextoff' / next='nextoff' (1.9.2)
* <pgm name='QSZRTVPR'>
* <parm io='both'>
* <ds comment='PRDR0200'>
* :
* <data type='10i0' offset='myOffset'></data>
* :
* </ds>
* </parm>
* :
* <overlay io='out' top='1' offset='myOffset'>
* <ds>
* <data type='10A'></data>
* <data type='2A'></data>
* <data type='10i0' enddo='prim'></data>
* <data type='10i0' offset='myOffset2'></data>
* </ds>
* </overlay>
* <overlay io='out' top='1' offset='myOffset2'
* dim='10' dou='prim' setnext='nextoff'>
* <ds>
* <data type='10i0' next='nextoff'></data>
* <data type='10A'></data>
* <data type='10A'></data>
* <data type='10A'></data>
* <data type='10A'></data>
* <data type='10A'></data>
* <data type='10A'></data>
* <data type='10i0'></data>
* <data type='10A'></data>
* </ds>
* </overlay>
*************************************************************************
Note: Additional XML attributes added for comments, var names, and documentation will simply be returned untouched, so you may build your own label conventions for XML parsing in client code.
Hint: use your own labels and comments XML attributes for easy client XML parsing work (var=’MYVAR’).
Advanced CCSID¶
Using default PHP toolkit DB2 clob interface (iPLUGxxx/iPLUGRxxx), ccsid conversion occurs naturally as DB2 client/server and you will not have to code before/after, but method is available if you have a specific concern or you have scripts returning many different languages.
Theory follows that most of XML document “intent” will remain immutable across db2 clob conversion (keywords, numbers, etc.), but for character data on some occasion there will be mixed/competing client/server ccsid conversion intention (client running 819, but data 1208), or there may be multiple language ccsid in the same XML document request (German, English, French), therefore using a combination of transfer in hex (hex=‘on’) and IBM i server ccsid translation before/after should allow complete control over transforms at user need.
Example:
<data type='200A' hex='on' before='819/424' after='424/819'>bin2hex('Hebrew_ascii_raw_chars')</data>
<data type='200A' hex='on' before='819/1098' after='1098/819'>bin2hex('Farsi_ascii_raw_chars')</data>
<data type='200A' hex='on' before='819/880' after='880/819'>bin2hex('Russia_ascii_raw_chars')</data>
<data type='200A' hex='on' before='819/280' after='280/819'>bin2hex('Italy_ascii_raw_chars')</data>
<data type='200A' hex='on' before='819/273' after='273/819'>bin2hex('Germany_ascii_raw_chars')</data>
<data type='200A' hex='on' before='819/1088' after='1088/819'>bin2hex('Korea_ascii_raw_chars')</data>
<data type='200A' hex='on' before='1208/13488' after='13488/1208'>bin2hex('Japan_ascii_raw_chars')</data>
<data type='200A' hex='on' before='1208/13488' after='13488/1208'>bin2hex('China_ascii_raw_chars')</data>
where:
before - XMLSERVICE convert CCSID before ILE program call
after - XMLSERVICE convert CCSID after ILE program call for client return
bin2hex() - script hex string unaltered ascii image (also returned hex string avoid any conversion)
pack() - script uses pack('H*',"xml_hex_back") function in PHP program for ascii characters
Note:
Up to four conversions can take place for the truly complex ccsid conversion issues
<data type='A' hex='on' before='cc1/cc2/cc3/cc4' after='cc4/cc3/cc2/cc1'>bin2hex('wild_ascii_raw_chars')</data>
flow:
-> PHP client bin2hex('wild_ascii_raw_chars')
-> xmlservice hex2bin back to 'wild_ascii_raw_chars'
-> xmlservice convert cc1->cc2->cc3->cc4 (before)
-> xmlservice make ILE call
-> xmlservice convert cc4->cc3->cc2->cc1 (after)
-> xmlservice tohex "xml_hex_back"
-> PHP client $chars = pack('H*',"xml_hex_back")
XMLSERVICE DB2 SQL¶
DB2 queries using only XML with syntax cut/paste STRSQL (cool, cool, cool version 1.5+).
Note: DB2 SQL XML does not work in-line stateless ($ctl=’*here’), but works fine with normal private connections (ipc=’/tmp/fred’, $ctl=’*sbmjob’).
*****************************************************
* Run XML SQL statements:
* <sql>...</sql> - start/end run XML SQL statements
*
* Example easy way (chglibl, default connection, default statement):
* Input:
* <?xml version='1.0'?>
* <script>
* <cmd>CHGLIBL LIBL(XMLSERVTST QTEMP) CURLIB(XMLSERVTST)</cmd>
* <sql>
* <query>select breed, name from animal</query>
* <fetch block='all' desc='on'/>
* </sql>
* </script>
* Note: You only need chglibl once for all scripts because
* XMLSERVICE jobs remain active until killed (*immed).
* XMLSERVICE default is system naming (not sql naming),
* so library list is significant for unqualified
* statements (see options below).
* Output:
* <?xml version='1.0'?>
* <script>
* <cmd>+++ success CHGLIBL LIBL(XMLSERVTST QTEMP) CURLIB(XMLSERVTST)</cmd>
* <sql>
* <query conn='conn1' stmt='stmt1'>
* +++ success select breed, name from animal</query>
* <fetch block='all' desc='on' stmt='stmt1'>
* <row><data desc='BREED'>cat</data><data desc='NAME'>Pook</data></row>
* <row><data desc='BREED'>dog</data><data desc='NAME'>Peaches</data></row>
* <row><data desc='BREED'>horse</data><data desc='NAME'>Smarty</data></row>
* <row><data desc='BREED'>gold fish</data><data desc='NAME'>Bubbles</data></row>
* <row><data desc='BREED'>budgerigar</data><data desc='NAME'>Gizmo</data></row>
* <row><data desc='BREED'>goat</data><data desc='NAME'>Rickety Ride</data></row>
* <row><data desc='BREED'>llama</data><data desc='NAME'>Sweater</data></row>
* </fetch>
* </sql>
* </script>
*
* Reserved SQL words (syntax):
* <sql>
*
* Connect to DB2 (optional):
* <connect [conn='label' db='x' uid='x' pwd='x' options='label']/>
*
* Options template (optional):
* <options [options='label' error='on|off|fast'
* Environment level (SQLSetEnvAttr) ...
* servermode='on' (default=off)
* Connection level (SQLSetConnAttr) ...
* autocommit='on|off' (default=on)
* commit='none|uncommitted|committed|repeatable|serializable'
* (default=uncommitted)
* naming='system|sql' (default=system)
* sqllib='mylib' (default=na)
* libl='curlib1 mylib2 mylib3' (default=na)
* datefmt='iso|usa|eur|jis|mdy|dmy|ymd|jul|job' (default=na)
* datesep='slash|dash|period|comma|blank|job' (default=na)
* timefmt='iso|usa|eur|jis|hms|job' (default=na)
* timesep='colon|period|comma|blank|job' (default=na)
* decimalsep='period|comma|blank|job' (default=na)
* optimize='first|all' (default=na)
* Statement level (SQLSetStmtAttr) (version 1.5.1+) ...
* scrollable='on|off' (default=off)
* sensitive='unspecified|sensitive|insensitive' (default=unspecified)
* cursor='forward|static|dynamic' (default=forward)
* fetchonly='on|off' (deafult=on)
* fullopen='on|off' (deafult=off)
* ]/>
* These alternate db2 features available ...
* error='on,off,fast' (1.7.6)
* on - script stops, full error report (default)
* off - script continues, job error log
* fast - script continues, brief error log
* Note:
* commit sets transaction-isolation level
* - none - no commit (*NONE)
* - uncommitted - uncommitted read (*CHG)
* - committed - cursor stability (*CS)
* - repeatable - read stability (*RS, *ALL)
* - serializable - repeatable read (*RR, *ALL)
*
* Commit or rollback transaction (optional):
* <commit [conn='label' action='rollback' error='on|off|fast']/>
* These alternate db2 features available ...
* error='on,off,fast' (1.7.6)
* on - script stops, full error report (default)
* off - script continues, job error log
* fast - script continues, brief error log
*
* Execute statement directly (SQLExecDirect):
* <query [conn='label' stmt='label' options='label' error='on|off|fast']>
* call storedproc('fred flinrock',42.42)
* -- or --
* select * from table where abc = 'abc'
* -- or (use CDATA if xml trouble special characters) --
* <![CDATA[select * from animal where ID < 5 and weight > 10.0]]>
* </query>
* These alternate db2 features available ...
* error='on,off,fast' (1.7.6)
* on - script stops, full error report (default)
* off - script continues, job error log
* fast - script continues, brief error log
* Note:
* - options='label' (version 1.5.1+)
*
* Prepare a statement (SQLPrepare/SQLExecute):
* <prepare [conn='label' stmt='label' options='label' error='on|off|fast']>
* call storedproc(?,?)
* -- or --
* select * from table where abc = ?
* -- or (use CDATA if xml trouble special characters) --
* <![CDATA[select * from animal where ID < ? and weight > ?]]>
* </prepare>
* These alternate db2 features available ...
* error='on,off,fast' (1.7.6)
* on - script stops, full error report (default)
* off - script continues, job error log
* fast - script continues, brief error log
* Note:
* - options='label' (version 1.5.1+)
*
* Execute a prepared statement (SQLPrepare/SQLExecute):
* <execute [stmt='label' error='on|off|fast']>
* These alternate db2 features available ...
* error='on,off,fast' (1.7.6)
* on - script stops, full error report (default)
* off - script continues, job error log
* fast - script continues, brief error log
* <parm [io='in|out|both']>my string</parm>
* <parm [io='in|out|both']>42.42</parm>
* </execute>
* Note:
* - substitution parameters '?' are taken in order of
* appearence with data types decided internally in
* XMLSERVICE (SQLNumParams/SQLDescribeParam)
* and bound at call time (SQLBindParameter)
*
* Fetch result(s) of a statement:
* <fetch [stmt='label' block='all|n' rec='n' desc='on|off' error="on|off|fast"/>
* (default=all) (default=on) (default='off')
* These alternate db2 features available ...
* error='on,off,fast' (1.7.6)
* on - script stops, full error report
* off - script continues, job error log (default)
* fast - script continues, brief error log
* Output (column description included via desc='on'):
* <fetch>
* <row><data desc='NAME'>Rip</data><data desc='ID'>9</data></row>
* <row><data desc='NAME'>Bee</data><data desc='ID'>3</data></row>
* </fetch>
* Note:
* - result set column types and descriptions are
* decided internally in XMLSERVICE (SQLNumResultCols/SQLDescribeCol)
* and bound at call time (SQLBindCol)
* - rec='n' (version 1.5.1+)
*
* After query/execute statement:
* Statment rows affected by change (SQLRowCount):
* <rowcount [stmt='label' error='on|off']/>
* (default=off)
* Output:
* <rowcount ...>24</rowcount>
*
* After insert of identity id:
* Statment last identity id (IDENTITY_VAL_LOCAL):
* <identity [conn='label' error='on|off']/>
* (default=off)
* Output:
* <identity ...>23</identity>
*
* Statment describe parms (SQLNumParams/SQLDescribeParam):
* columns (SQLNumResultCols/SQLDescribeCol):
* <describe [stmt='label' desc='col|parm|both' error='on|off']/>
* (default=off)
* Output (parm):
* <describe ...>
* <parm> or <col>
* <name>FRED</name>
* <dbtype>DECIMAL</dbtype>
* <size>12</size>
* <scale>2</scale>
* <nullable>0</nullable>
* </parm> or </col>
* </describe>
*
* Statment count parms (SQLNumParams): (1.7.6)
* columns (SQLNumResultCols):
* <count [stmt='label' desc='col|parm|both' error='on|off']/>
* (default=off)
* Output (parm):
* <count ...>
* <colcount>nbr</colcount>
* <parmcount>nbr</parmcount>
* </count>
*
* Free resources:
* <free [conn='all|label'
* cstmt='label'
* stmt='all|label'
* options='all|label'
* error='on|off|fast']/>
* These alternate db2 features available ...
* error='on,off,fast' (1.7.6)
* on - script stops, full error report
* off - script continues, job error log (default)
* fast - script continues, brief error log
*
* Meta data - tables:
* <tables [conn='label' error='on|off|fast'>
* (default=off)
* <parm>qualifier or catalog<parm>
* <parm>schema name</parm>
* <parm>table name</parm>
* <parm>table type</parm>
* </tables>
* Output (desc varies V5R4 to V6+):
* <tables ...>
* <row>
* <data desc='TABLE_CAT'>LP0164D</data>
* <data desc='TABLE_SCHEM'>XMLSERVTST</data>
* <data desc='TABLE_NAME'>ANIMAL</data>
* <data desc='TABLE_TYPE'>BASE TABLE</data>
* <data desc='REMARKS'></data>
* </row>
* </tables>
*
* Meta data - table privileges:
* <tablepriv [conn='label' error='on|off|fast'>
* (default=off)
* <parm>qualifier or catalog<parm>
* <parm>schema name</parm>
* <parm>table name</parm>
* </tablepriv>
* Output (desc varies V5R4 to V6+):
* <tablepriv ...>
* <row>
* <data desc='TABLE_CAT'>LP0164D</data>
* <data desc='TABLE_SCHEM'>XMLSERVTST</data>
* <data desc='TABLE_NAME'>ANIMAL</data>
* <data desc='GRANTOR'></data>
* <data desc='GRANTEE'>PUBLIC</data>
* <data desc='PRIVILEGE'>SELECT</data>
* <data desc='IS_GRANTABLE'>NO</data>
* <data desc='DBNAME'></data>
* </row>
* </tablepriv>
*
* Meta data - columns:
* <columns [conn='label' error='on|off|fast'>
* (default=off)
* <parm>qualifier or catalog</parm>
* <parm>schema name</parm>
* <parm>table name</parm>
* <parm>column name</parm>
* </columns>
* Output (desc varies V5R4 to V6+):
* <columns ...>
* <row>
* <data desc='TABLE_CAT'>LP0164D</data>
* <data desc='TABLE_SCHEM'>XMLSERVTST</data>
* <data desc='TABLE_NAME'>ANIMAL</data>
* <data desc='COLUMN_NAME'>BREED</data>
* <data desc='DATA_TYPE'>12</data>
* <data desc='TYPE_NAME'>VARCHAR</data>
* <data desc='LENGTH_PRECISION'>0</data>
* <data desc='BUFFER_LENGTH'>34</data>
* <data desc='NUM_SCALE'>0</data>
* <data desc='NUM_PREC_RADIX'>0</data>
* <data desc='NULLABLE'>1</data>
* <data desc='REMARKS'></data>
* <data desc='COLUMN_DEF'>
* </data><data desc='DATETIME_CODE'>0</data>
* <data desc='CHAR_OCTET_LENGTH'>32</data>
* <data desc='ORDINAL_POSITION'>2</data>
* </row>
* </columns>
*
* Meta data - special columns:
* <special [conn='label' error='on|off|fast'>
* (default=off)
* <parm>qualifier or catalog</parm>
* <parm>schema name</parm>
* <parm>table name</parm>
* <parm>row|transaction|session</parm>
* <parm>no|nullable</parm>
* </special>
*
* Meta data - column privileges:
* <columnpriv [conn='label' error='on|off|fast'>
* (default=off)
* <parm>qualifier or catalog</parm>
* <parm>schema name</parm>
* <parm>table name</parm>
* <parm>column name</parm>
* </columnpriv>
* Output (desc varies V5R4 to V6+):
* <columnpriv ...>
* <row>
* <data desc='TABLE_CAT'>LP0164D</data>
* <data desc='TABLE_SCHEM'>XMLSERVTST</data>
* data desc='TABLE_NAME'>ANIMAL</data>
* <data desc='COLUMN_NAME'>BREED</data>
* <data desc='GRANTOR'></data>
* <data desc='GRANTEE'>PUBLIC</data>
* <data desc='PRIVILEGE'>SELECT</data>
* <data desc='IS_GRANTABLE'>NO</data>
* <data desc='DBNAME'></data>
* </row>
* </columnpriv>
*
* Meta data - procedures:
* <procedures [conn='label' error='on|off|fast'>
* (default=off)
* <parm>qualifier or catalog</parm>
* <parm>schema name</parm>
* <parm>procedure name</parm>
* </procedures>
* Output (desc varies V5R4 to V6+):
* <procedures ...>
* <row>
* <data desc='PROCEDURE_CAT'>LP0164D</data>
* <data desc='PROCEDURE_SCHEM'>XMLSERVTST</data>
* <data desc='PROCEDURE_NAME'>MATCH1</data>
* <data desc='NUM_INPUT_PARAMS'>2</data>
* <data desc='NUM_OUTPUT_PARAMS'>1</data>
* <data desc='NUM_RESULT_SETS'>1</data>
* <data desc='REMARKS'></data>
* </row>
*
* Meta data - procedure columns:
* <pcolumns [conn='label' error='on|off|fast'>
* (default=off)
* <parm>qualifier or catalog</parm>
* <parm>schema name</parm>
* <parm>proc name</parm>
* <parm>column name</parm>
* </pcolumns>
* Output (desc varies V5R4 to V6+):
* <pcolumns ...>
* <row>
* <data desc='PROCEDURE_CAT'>LP0164D</data>
* <data desc='PROCEDURE_SCHEM'>XMLSERVTST</data>
* <data desc='PROCEDURE_NAME'>MATCH1</data>
* <data desc='COLUMN_NAME'>FIRST_NAME</data>
* <data desc='COLUMN_TYPE'>1</data>
* <data desc='DATA_TYPE'>12</data>
* <data desc='TYPE_NAME'>CHARACTER VARYING</data>
* <data desc='PRECISION'>0</data>
* <data desc='LENGTH'>128</data>
* <data desc='SCALE'>0</data>
* <data desc='RADIX'>0</data>
* <data desc='NULLABLE'>YES</data>
* <data desc='REMARKS'></data>
* <data desc='COLUMN_DEF'>0</data>
* <data desc='SQL_DATA_TYPE'>12</data>
* <data desc='SQL_DATETIME_SUB'>0</data>
* <data desc='CHAR_OCTET_LENGTH'>128</data>
* <data desc='ORDINAL_POSITION'>1</data>
* <data desc='IS_NULLABLE'>YES</data>
* </row>
* </pcolumns>
*
* Meta data - primary keys:
* <primarykeys [conn='label' error='on|off|fast'>
* (default=off)
* <parm>qualifier or catalog</parm>
* <parm>schema name</parm>
* <parm>table name</parm>
* </primarykeys>
* Output (desc varies V5R4 to V6+):
* <primarykeys ...>
* <row>
* <data desc='TABLE_CAT'>LP0164D</data>
* <data desc='TABLE_SCHEM'>XMLSERVTST</data>
* <data desc='TABLE_NAME'>ANIMAL2</data>
* <data desc='COLUMN_NAME'>NOTEID</data>
* <data desc='KEY_SEQ'>1</data>
* <data desc='PK_NAME'>Q_XMLSERVTST_ANIMAL2_NOTEID_00001</data>
* </row>
* </primarykeys>
*
* Meta data - foreign keys:
* <foreignkeys [conn='label' error='on|off|fast'>
* (default=off)
* <parm>primary qualifier or catalog</parm>
* <parm>primary schema name</parm>
* <parm>primary table name</parm>
* <parm>foreign qualifier or catalog</parm>
* <parm>foreign schema name</parm>
* <parm>foreign table name</parm>
* </foreignkeys>
* Output (desc varies V5R4 to V6+):
* <foreignkeys ...>
* <row>
* <data desc='PKTABLE_CAT'>LP0164D</data>
* <data desc='PKTABLE_SCHEM'>XMLSERVTST</data>
* <data desc='PKTABLE_NAME'>ANIMAL2</data>
* <data desc='PKCOLUMN_NAME'>NOTEID</data>
* <data desc='FKTABLE_CAT'>LP0164D</data>
* <data desc='FKTABLE_SCHEM'>XMLSERVTST</data>
* <data desc='FKTABLE_NAME'>FKEY2</data>
* <data desc='FKCOLUMN_NAME'>IDF</data>
* <data desc='KEY_SEQ'>1</data>
* <data desc='UPDATE_RULE'>3</data>
* <data desc='DELETE_RULE'>3</data>
* <data desc='FK_NAME'>Q_XMLSERVTST_FKEY2_IDF_00001</data>
* <data desc='PK_NAME'>Q_XMLSERVTST_ANIMAL2_NOTEID_00001</data>
* </row>
* </foreignkeys>
*
* Meta data - statistics:
* <statistics [conn='label' error='on|off|fast'>
* (default=off)
* <parm>qualifier or catalog</parm>
* <parm>schema name</parm>
* <parm>table name</parm>
* <parm>all|unique</parm>
* </statistics>
* Output (desc varies V5R4 to V6+):
* <statistics ...>
* <row>
* <data desc='TABLE_CAT'></data>
* <data desc='TABLE_SCHEM'>XMLSERVTST</data>
* <data desc='TABLE_NAME'>ANIMAL2</data>
* <data desc='NON_UNIQUE'>1</data>
* <data desc='00005'>XMLSERVTST</data>
* <data desc='00006'>INDEX2</data>
* <data desc='TYPE'>3</data>
* <data desc='ORDINAL_POSITION'>1</data>
* <data desc='00009'>ANIMALID</data>
* <data desc='COLLATION'>A</data>
* <data desc='CARDINALITY'>0</data>
* <data desc='PAGES'>0</data>
* </row>
* </statistics>
*
* </sql>
*
* NOTES:
*
* The XMLSERVICE sql rules:
* > Connection rules:
* - if connect is omitted, then XMLSERVICE will open
* a default connection under the current profile.
* - if servermode='na' (off default), ONLY ONE connection
* allowed for the XMLSERVICE job. This is a DB2 rule of
* one activation equals one active connection/transaction,
* so you must free/commit a connection/transaction before
* attempting to create a new connection/transaction
* (or connect another profile). This is the correct
* mode for sharing QTEMP with XMLSERVICE called
* PGMs/SRVPGMs, so it is also the XMLSERVICE default.
* - if servermode='on', XMLSERVICE may have multiple
* connection/transactions active, BUT each connection
* will be running in a seperate QSQSRVR job. This means
* QTEMP in XMLSERVICE job will NOT BE USEABLE to communicate
* between XMLSERVICE called PGM/SRVPGM, etc. (QTEMP useless).
* Once an XMLSERVICE job enters servermode='on',
* it will NEVER stop using server mode until the
* process is ended (choose wisely).
* > Commit rules (see options):
* - default is autocommit='on', where all create, insert,
* delete actions will be committed at operation time.
* - if autocommit='off', commit action may be delayed
* across multiple requests to XMLSERVICE and you
* may also rollback the transaction.
* > Statement rules:
* - if stmt='label' is omitted (normal), the first active
* statement available will be used for a sql operation
* such as fetch or describe. Therefore it is not wise
* to mix stmt='label' and omitted in the same XMLSERVICE
* task.
* - if a stmt is left active (no free), and a subsequent
* 'label'/omitted is attempted, the active statement will
* be released along with all result sets, etc. (free),
* and the new statement will become the active statement.
* Therefore if you are attempting to use multiple result
* sets from different queries, you should manually
* specify stmt='label' to avoid fetching from the
* wrong result set.
* > Options rules:
* -Using servermode='on' universally executes statements
* in child process (QSQSRVR jobs), not XMLSERVICE job.
* Once servermode='on', it stays 'on' for all connections
* for the life of the XMLSERVICE job, therefore you cannot
* expect to share QTEMP between PGM calls/DB2 SQL, etc.
* (ie. think very careful before you use this option)
* -Default mode is servermode='na' (off), or 'normal mode',
* which means a single connect/transaction is allowed
* active at any given time. Therefore, you must end
* any transation with <commit> and <free conn='all'>,
* before attempting to switch between connection profiles.
* (normal CLI rules transaction/connect in a single process)
* -System vs. SQL naming libl, where:
* - naming='system' with libl='curlib1 mylib2' (list)
* example: select * from mylib3/table (specific library)
* select * from table (uses library list)
* (system naming is the default naming mode XMLSERVICE)
* - naming='sql' with *sqllib='mylib' (one)
* example: select * from mylib3.table (specific schema)
* select * from table (uses *sqllib='schema')
* Do not try to mix these two modes in the same
* connection as this always leads to program errors
* (ie. make up your mind before you write your scripts).
* > Connection/statements/options label rules (optional labels):
* conn='label' - unique name connection (optional)
* stmt='label' - unique name statement (optional)
* options='label' - unique name options template (optional)
* - a label is ten characters or less
* - a unique 'label' is used as XMLSERVICE/DB2 routing 'key'
* thereby allowing multiple XML <sql> calls to XMLSERVICE
* routing back to open/active DB2 connection(s)/statement(s)
* - If optional conn/stmt 'label' is omitted, XMLSERVICE
* will return a unique 'label' attribute in output XML
* for subsequent sql prepare, execute, fetch, etc.
* - If optional conn/stmt 'label' is omitted, XMLSERVICE
* will attempt to use any active conn/stmt. No 'label(s)'
* works just fine with less XML, but you need to be very careful
* that other scripts do not introduce additional conn/stmt
* 'label(s)' that spoil your generic XML DB2 statements.
* > Connection/statements/options free rules (optional free):
* - Connections/statements remain active/open until released:
* <free/> - release all
* <free options='label'/> - release options template
* <free options='all'/> - release all options template
* <free conn='label'/> - release connection (and statements)
* <free conn='all'/> - release all connections (and statements)
* <free cstmt='label'/> - release all statements 'label' connection
* <free stmt='label'/> - release this statement
* <free stmt='all'/> - release all statements
* - conn='all' : free all connections,
* also frees all statements
* - cstmt='label': free all statements under 'label' connection,
* other connections/statements remain active
* - stmt='all' : free all statements,
* connections remain active
* > These alternate db2 features available ...
* error='on,off,fast' (1.7.6)
* on - script stops, full error report
* off - script continues, job error log
* fast - script continues, brief error log
*
*****************************************************
XMLSERVICE job log¶
Job log info has been added to all XMLSERVICE returned fatal errors. An additonal function was also added to retrieve a job of the XMLSERVICE job or another job (1.5.8+).
* 0) Optional get diagnostics (1.5.8)
* <diag [info='joblog|conf' job='job' user='uid' nbr='nbr']/>
* example run
* <?xml version="1.0"?>
* <diag info='joblog'/>
* Note:
* The current XMLSERVICE job log is assumed if optional
* attributes job='job' user='uid' nbr='nbr' missing.
* if you wish to provide custom diagnostics,
* info='conf' calls optional hook in plugconfx.
One sample by value (xmlservice 1.9.9.3+)¶
This is only about RPG parameters marked ‘const’ or ‘value’. If you do not understand these ‘by value’ RPG parameter types, this will only confuse (ignore).
A fix was added for by value in xmlservice 1.9.9.3. All data types test work except zoned decimal by value (although may work). This will be handled later in next base version of xjservice. Please note by="val"
is an attribute of <parm>
, not <data>
(see below).
BTW – Attribute of <parm by='val'>
is not an error (no debate). That is, by='ref,val'
is indeed attribute of <parm>
, same as io='in,out,both'
, and other exotic parameter attributes like *omit and *nopass. Unfortunately RPG syntax obfuscates spoken words ‘passing parameters’ into minds eye of ‘passing data(s) and structures and arrays of data’ (which make no sense). Aka, <parm by='val' io='in'>
is correct.
dcl-proc GetPacked export;
dcl-pi *N;
i2d char(8) Value;
p1 packed(4:2) Value;
p2 packed(3:2) Value;
p3 packed(12:2) Value;
p4 packed(6:2) Value;
p5 packed(8:2) Value;
p6 packed(24:4) Value;
p7 packed(48:8) Value;
ppd char(15) Value;
zzd char(30);
i2 int(5) value;
i1d char(30);
i4 int(10) value;
i8 int(20) value;
f4 float(4) value;
f4d char(30);
f8 float(8) value;
i4d char(30);
i8d char(30);
f8d char(30);
i1 int(3) value;
end-pi;
p1 += 2.22;
p2 += 2.22;
p3 += 2.22;
p4 += 2.22;
p5 += 2.22;
p6 += 2.22;
p7 += 2.22;
ppd = 'pack man';
zzd = 'zone man';
i1 += 2;
i1d = 'byte man';
i2 += 2;
i2d = 'short man';
i4 += 2;
i4d = 'integer man';
i8 += 2;
i8d = 'longlong man';
f4 += 2.22;
f4d = 'float man';
f8 += 2.22;
f8d = 'double man';
end-proc;
xjInData =
'<?xml version="1.0"?>'
+ '<xmlservice>'
+ '<cmd error="fast" exec="cmd" var="chglibl">'
+ 'CHGLIBL LIBL('+TEST_LIB+')'
+ '</cmd>'
+ '<pgm error="fast" func="GETPACKED" name="TESTZSRV" var="packme">'
+ '<parm io="both" by="val" var="p8">'
+ '<data type="8a" var="i2d">1</data></parm>'
+ '<parm io="both" by="val" var="pp1">'
+ '<data type="4p2" var="pp">1</data></parm>'
+ '<parm io="both" by="val" var="pp2">'
+ '<data type="3p2" var="zz">1</data></parm>'
+ '<parm io="both" by="val" var="pp3">'
+ '<data type="12p2" var="zz">1</data></parm>'
+ '<parm io="both" by="val" var="pp4">'
+ '<data type="6p2" var="zz">1</data></parm>'
+ '<parm io="both" by="val" var="pp5">'
+ '<data type="8p2" var="zz">1</data></parm>'
+ '<parm io="both" by="val" var="pp6">'
+ '<data type="24p4" var="zz">1</data></parm>'
+ '<parm io="both" by="val" var="pp7">'
+ '<data type="48p8" var="zz">1</data></parm>'
+ '<parm io="both" by="val" var="p2">'
+ '<data type="15a" var="ppd">1</data></parm>'
+ '<parm io="both" var="p4">'
+ '<data type="30a" var="zzd">1</data></parm>'
+ '<parm io="both" by="val" var="p7">'
+ '<data type="5i0" var="i2">1</data></parm>'
+ '<parm io="both" var="p6">'
+ '<data type="30a" var="i1d">1</data></parm>'
+ '<parm io="both" by="val" var="p9">'
+ '<data type="10i0" var="i4">1</data></parm>'
+ '<parm io="both" by="val" var="p11">'
+ '<data type="20i0" var="i8">1</data></parm>'
+ '<parm io="both" by="val" var="p13">'
+ '<data type="4f" var="f4">1</data></parm>'
+ '<parm io="both" var="p14">'
+ '<data type="30a" var="f4d">1</data></parm>'
+ '<parm io="both" by="val" var="p15">'
+ '<data type="8f" var="f8">1</data></parm>'
+ '<parm io="both" var="p10">'
+ '<data type="30a" var="i4d">1</data></parm>'
+ '<parm io="both" var="p12">'
+ '<data type="30a" var="i8d">1</data></parm>'
+ '<parm io="both" var="p16">'
+ '<data type="30a" var="f8d">1</data></parm>'
+ '<parm io="both" by="val" var="p5">'
+ '<data type="3i0" var="i1">1</data></parm>'
+ '</pgm>'
+ '</xmlservice>'
+ x'00';
XMLSERVICE Interfaces¶
Who is this page for?¶
Instructions designed for IBM i developer exploring XMLSERVICE …
XMLSERVICE introduction¶
-----------------------------------------
| Browser |
|---------------------------------------|
(1)->| Download RPG (1) | Download PHP (2) |
| 1) XMLSERVICE | a) PHP CW Toolkit |<-(4) cw-tk-php-x.x.x.zip
| HTML/XML/REST | b) New PHP Toolkit |<-(3) cw-tk-php-x.x.x.zip
| no PHP |--------------------|
| (xmlcgi.pgm) | c) PHP “Raw XML” |<-(2) Zend Server for IBM i or Linux or Windows
| (optional) | (ibm_db2, odbc) |
| |--------------------|
| 2) XMLSERVICE DB2 stored procedures |<-(1)
| (iPLUG4K, iPLUG32K, ..., iPLUG15M) |
| 3) XMLSERVICE (xmlservice.pgm) |<-(1) xmlservice-rpg-x.x.x.zip
| call most anything on IBM i ... |
| (PGM, SRVPGM, PASE, DB2, etc.) |
-----------------------------------------
- XMLSERVICE is designed to allow you flexibility to develop from your laptop and run on your IBM i production with no changes. (RPG download)
- PHP DB2 stored procedure interface can be called form your laptop and run on your IBM i production with no changes. (PHP Zend Server)
- Zend New PHP Toolkit is one way to work with XMLSERVICE that ships with Zend Server for IBM i. (PHP download)
- A PHP old toolkit compatibility layer is also available for old PHP Zend toolkit users. (PHP download)
(3-4) You may update IBM i PHP toolkit via Zend PTF or see main page for update download.
Why do you call it XMLSERVICE??¶
- same name as RPG source installation library (XMLSERVICE library)
- name of the job(s)/program(s) seen in wrkactjob (see below)
- services XML input scripts and returns XML output data (a rose by any other name)
wrkactjob
Opt Subsystem/Job User Type CPU % Function Status
QSYSWRK QSYS SBS .0 DEQW
XTOOLKIT QTMHHTP1 BCH .0 PGM-XMLSERVICE SEMW
Note: XTOOLKIT naming 1.5+
Why is XMLSERVICE Open Source?¶
- IBM has many nice folks that believe in Open Source for IBM i, sharing common utilities in IBM i community
- Business friendly BSD license makes it easy to include in any Open Source project (Zend PHP, your applications, etc.)
- XML documents over transport neutral design lends itself to “easy” inclusion is your proprietary/custom applications (XML is just a big string, you can use it anywhere)
- Pure RPG source because many IBM i folks know the language, but not the tricks like using PASE for functions
XMLSERVICE highlights¶
- 100% RPG code BSD licensed to be completely free to use (XMLSERVICE library download)
- allows develop on PC and deploy on either PC (2-tier) or IBM i (1-tier)
- all communication is XML documents
- called RPG programs can maintain state, commit boundaries, etc
- provided IPC mechanism that synchronizes web clients multiple sources
- traditional DB2 connection security 1-2 tier (when using DB2 stored procedures provided)
- conversions of XML string text to/from actual program required parameter geometery is handled (packed decimal 12p2, zoned decimal 7s2, integer 10i0/5i0, float 4f2/8f4, hex binary 400b), and character data converted automatically ASCII/EBCDIC (1024A/10a varying=’on’)
- arrays of data structures are allowed both as paramters and return, including nested data structurers and/or arrays of data structures (try it to believe it)
- Many limits of other toolkit PGM/SRVPGM callers are not an issue with XMLSERVICE (up to 15MB to date), therefore “old school” RPG using occurs can often pass back all data records in one call.
We have been experimenting with the following configurations.
1-tier (running on IBM i):
1) HTML REST : |PC|browser<----------------->|IBM i|<-ApacheCGI--|
2) PHP ibm_db2 : |PC|browser<----------------->|IBM i|<-FastCGI/PHP|
3) PHP pdo_ibm : |PC|browser<----------------->|IBM i|<-FastCGI/PHP|
4) PHP odbc : |PC|browser<----------------->|IBM i|<-FastCGI/PHP|
5) Zend Toolkit: (using one of above transport interfaces) | ...xml ...
->XMLSERVICE
| ...call...
2-tier (Linux/Windows to IBM i): | >PGM
1) PHP REST : |PC|Apache/PHP<-REST--------->|IBM i|<-ApacheCGI--| >SRVPGM
2) PHP ibm_db2 : |PC|Apache/PHP<-DB2 Connect-->|IBM i|<-QRWTSRVR---| >CMD
3) PHP pdo_ibm : |PC|Apache/PHP<-DB2 Connect-->|IBM i|<-QRWTSRVR---| >PASE shell
4) PHP odbc : |PC|Apache/PHP<-CA ODBC------>|IBM i|<-QRWTSRVR---|
5) Zend Toolkit: (using one of above transport interfaces)
Note:
- PHP was used to test, but any DB2/REST capable scripting language will work
- REST interface used for test does not require PHP on IBM i (not fast)
- ibm_db2 has been the best interface for production use (fast)
- 2-tier does not require PHP on IBM i
- odbc and pdo_ibm had trouble with large contiguous data (PDF)
XMLSERVICE syntax (XML in, XML out)¶
-----------------------------------------
| Browser |
|---------------------------------------|
| Download RPG (1) | Download PHP (2) |
| 1) XMLSERVICE | a) PHP CW Toolkit |
| HTML/XML/REST | b) New PHP Toolkit |
| no PHP |--------------------|
| (xmlcgi.pgm) | c) PHP “Raw XML” |
| (optional) | (ibm_db2, odbc) |
| -----------------------------------|
| 2) XMLSERVICE DB2 stored procedures |
| (iPLUG4K, iPLUG32K, ..., iPLUG15M) |
| 3) XMLSERVICE (xmlservice.pgm) |<-(1)
| call most anything on IBM i ... |
| (PGM, SRVPGM, PASE, DB2, etc.) |
-----------------------------------------
- XMLSERVICE parses and executes XML input for IBM i commands (CMD), programs (PGM/SRVPGM), and PASE utilites (ls, system, etc.), data results are then returned in XML output. XMLSERVICE processes XML scripts in “order of appearance”. Every effort is made to make one pass through the XML to keep performance, therefore XML input “order of appearance” is important and left up to the XML script writer (see below).
Order is important:
* <parm> followed by <return>
* <parm> followed by <overlay> (1.2.1 beta)
* <return> followed by <overlay> (1.2.1 beta)
* <ds> followed by <data>
Example:
<script>
<cmd>do something before PGM1</cmd> <--- action 1 call CMD1
<pgm name='PGM1'> <--- action 2 call PGM1
<parm comment='parm 1'><ds><data>stuff</data></ds></parm>
<parm comment='parm 2'><ds><data>stuff</data></ds></parm>
<return comment='complex'><ds><data>stuff</data></ds></return>
</pgm>
<cmd>do something after PGM1</cmd> <--- action 3 call CMD2
<pgm name='PGM2'> <--- action 4 call PGM2
<parm comment='parm 1'><ds><data>stuff</data></ds></parm>
<overlay comment='optional over parm 1'><ds><data>stuff</data></ds></parm>
<parm comment='parm 2'><ds><data>stuff</data></ds></parm>
<return comment='complex'><ds><data>stuff</data></ds></return>
<overlay comment='optional over return'><ds><data>stuff</data></ds></overlay>
</pgm>
<sh>do something PASE after PGM2</sh> <--- action 5 call PASE utility
</script>
- XMLSERVICE setting parameter data
Setting parameter data is a simple task that looks much the same as any language, using XML in this case. As you can see below you must send the entire XML document/script each time, because XMLSERVICE will not remember “everything” about each program (<pgm></pgm>), command (<cmd></cmd>) or PASE utility called (<sh></sh>).
1st XMLSERVICE call ...
<script>
<pgm name='PGM1'>
<parm comment='parm 1'><ds><data>stuff</data></ds></parm>
<parm comment='parm 2'><ds><data>stuff</data></ds></parm>
<return comment='complex'><ds><data>stuff</data></ds></return>
</pgm>
</script>
2nd XMLSERVICE call ...
<script>
<pgm name='PGM1'>
<parm comment='parm 1'><ds><data>different stuff</data></ds></parm>
<parm comment='parm 2'><ds><data>different stuff</data></ds></parm>
<return comment='complex'><ds><data>different stuff</data></ds></return>
</pgm>
</script>
Why send a full XML document description each time you call program/script?¶
Because XMLSERVICE is simply an script interpreter like BASIC or PHP, not a true compiler like RPG where “binary program” lasts forever, therefore you cannot just “pass XML data” without also describing what the data means (type=’12p2’).
If you are writing a language wrapper around this support (like Zend PHPToolkit), you can easily “hide” the XML document specifics of passing a full XML document (the whole idea). However, if you are using the RAW XML interface (this document), you will have to bite your tongue and send the whole document.
Note: However we are working short-cuts like overlay for some XML parameter “customization”, but we intend stopping short of complete “programming by XML” because we do not want to reinvent PHP (or the like).
Full example (2 complete calls):
1) XMLSERVICE call number 1 complete call with data XMLSERVICE will work:
<?xml version='1.0'?>
<script>
<pgm name='ZZCALLII'>
<parm io='both'>
<data type='1A' var='INCHARA'>a</data>
</parm>
<parm io='both'>
<ds>
<data type='1A' var='INDS1.DSCHARA'>x</data>
<data type='1A' var='INDS1.DSCHARB'>y</data>
<data type='7p4' var='INDS1.DSDEC1'>66.6666</data>
<data type='12p2' var='INDS1.DSDEC2'>77777.77</data>
</ds>
</parm>
<return>
<data type='10i0'>0</data>
</return>
</pgm>
</script>
2) XMLSERVICE call number 2 complete call with “different” data XMLSERVICE will work:
<?xml version='1.0'?>
<script>
<pgm name='ZZCALLII'>
<parm io='both'>
<data type='1A' var='INCHARA'>Z</data>
</parm>
<parm io='both'>
<ds>
<data type='1A' var='INDS1.DSCHARA'>F</data>
<data type='1A' var='INDS1.DSCHARB'>T</data>
<data type='7p4' var='INDS1.DSDEC1'>1.1</data>
<data type='12p2' var='INDS1.DSDEC2'>4.4</data>
</ds>
</parm>
<return>
<data type='10i0'>0</data>
</return>
</pgm>
</script>
XMLSERVICE REST interface (HTML/XML, no PHP)¶
-----------------------------------------
| Browser |
|---------------------------------------|
(1)->| Download RPG (1) | Download PHP (2) |
| 1) XMLSERVICE | a) PHP CW Toolkit |
| HTML/XML/REST | b) New PHP Toolkit |
| no PHP |--------------------|
| (xmlcgi.pgm) | c) PHP “Raw XML” |
| (optional) | (ibm_db2, odbc) |
| -----------------------------------|
| 2) XMLSERVICE DB2 stored procedures |
| (iPLUG4K, iPLUG32K, ..., iPLUG15M) |
| 3) XMLSERVICE (xmlservice.pgm) |
| call most anything on IBM i ... |
| (PGM, SRVPGM, PASE, DB2, etc.) |
-----------------------------------------
(1) HTTP REST xmlcgi.pgm (tests, demos):
http://myibmi/cgi-bin/xmlcgi.pgm&db2=x@uid=x@pwd=x@ipc=x@ctl=x@xmlin=x@xmlout=x
A example interface is included that does not require scripting language on the IBM i (no PHP).
The sample program xmlservice/xmlcgi is an RPG program that supports HTTP GET/POST interface.
This is an easy interface to call XMLSRVICE during testing without the bother of writing a script.
If you open up the source code of xmlcgi.pgm you will see that is calls the XMLSERVICE stored procedure interface in the next section. This allows for common access methodology including REST clients (like a browser), because no matter if calling from any application/device choice all security design remains within the consistent DB2 database connection(s). Also, because XMLSERVICE has a built in “one at a time” lock mechanism (semaphore) all/any clients can access the same XMLSERVICE job at the same time and you the script developer don’t have to worry about coordination.
REST parameters (xmlcgi.pgm)
- db2 - what database (*LOCAL tested)
- uid - user profile (*NONE - no uid version 1.5+)
- pwd - profile password (*NONE - no password version 1.5+)
- ipc - IPC key name/security route to XMLSERVICE job (/tmp/fred01, etc.)
- ctl - CTL admin control XMLSERVICE job (see control below)
- xmlin - XML input document (request)
- xmlout - expected size of XML output document (response size in bytes)
Apache rest configuration (CGI interface)
Example: Add the following to /www/zendsvr/conf/httpd.conf
ScriptAlias /cgi-bin/ /QSYS.LIB/XMLSERVICE.LIB/
<Directory /QSYS.LIB/XMLSERVICE.LIB/>
AllowOverride None
order allow,deny
allow from all
SetHandler cgi-script
Options +ExecCGI
</Directory>
Optional for server side includes (very handy for pure html/xml sites):
# server side includes
# AddOutputFilter INCLUDES .htm
# AddOutputFilter INCLUDES .html
Options +ExecCGI +Includes
AddType text/html .shtml
AddOutputFilter INCLUDES .shtml
AddOutputFilter INCLUDES .html
Example (html interface xmlcgi.pgm):
<html>
<body>
<!-- call XMLSERVICE/ZZCALL(a:b:11.1111:222.22:record) (post or get) -->
<form name="input" action="/cgi-bin/xmlcgi.pgm" method="post">
<input type="hidden" name="db2" value="*LOCAL">
<input type="hidden" name="uid" value="*NONE">
<input type="hidden" name="pwd" value="*NONE">
<input type="hidden" name="ipc" value="/tmp/rangerhtml">
<input type="hidden" name="ctl" value="*none">
<input type="hidden" name="xmlin"
value="<?xml version='1.0'?>
<pgm name='ZZCALL' lib='XMLSERVICE'>
<parm><data type='1A'>a</data></parm>
<parm><data type='1A'>b</data></parm>
<parm><data type='7p4'>11.1111</data></parm>
<parm><data type='12p2'>222.22</data></parm>
<parm>
<ds>
<data type='1A'>x</data>
<data type='1A'>y</data>
<data type='7p4'>66.6666</data>
<data type='12p2'>77777.77</data>
</ds>
</parm>
<return><data type='10i0'>0</data></return>
</pgm>">
<input type="hidden" name="xmlout" value="32768">
<input type="submit" value="Submit" />
</form>
</body>
</html>
Output:
<?xml version='1.0'?>
<pgm name='ZZCALL' lib='XMLSERVICE'>
<parm><data>C</data></parm>
<parm><data>D</data></parm>
<parm><data>321.1234</data></parm>
<parm><data>1234567890.12</data></parm>
<parm>
<ds>
<data>E</data>
<data>F</data>
<data>333.3330</data>
<data>4444444444.44</data>
</ds>
</parm>
<return><data>0</data></return>
</pgm>
Note: uid / pwd *NONE available XMLSERVICE version 1.5+.
XMLSERVICE stored procedure interface (production)¶
-----------------------------------------
| Browser |
|---------------------------------------|
| Download RPG (1) | Download PHP (2) |
| 1) XMLSERVICE | a) PHP CW Toolkit |
| HTML/XML/REST | b) New PHP Toolkit |
| no PHP |--------------------|
| (xmlcgi.pgm) | c) PHP “Raw XML” |
| (optional) | (ibm_db2, odbc) |
| -----------------------------------|
| 2) XMLSERVICE DB2 stored procedures |<-(2)
| (iPLUG4K, iPLUG32K, ..., iPLUG15M) |
| 3) XMLSERVICE (xmlservice.pgm) |
| call most anything on IBM i ... |
| (PGM, SRVPGM, PASE, DB2, etc.) |
------------------------------------------
(2) DB2 stored procedures (production):
call iPLUG[R]xxx(ipc, ctl, xmlin, [xmlout])
This DB2 stored procedure interface was designed for production level machines Zend Server on i (1 tier)
and remote machines over DB2 Connect (2 tier).
When you first start using XMLSERVICE generic stored procedures (see below), you may wonder why not just write custom stored procedures for each program i wish to call vs. using XMLSERVICE “dynamic stored procedures”?
- XML documents are very easy to send around over any transport, therefore you can upgrade your application capabilities instantley without all the custom stored procedure programming, In a practical sense this means you can use different scripting languages (php, java, perl, ruby, etc.), with various database connections (db2 connect, odbc, etc.) and/or simply HTTP (seen previously)
- XMLSERVICE supports full state programming via IPC routing (/tmp/fred1, /tmp/sally2, etc.), thereby traditional RPG programming techniques with many open files, transactions, etc., can be employed in called programs/modules lowering the learning curve (and rewrites not needed)
- XMLSERVICE XML documents are very easy to wrapper with any custom scripting interface, such as the new PHP toolkit interface Zend is working on.
DB2 in/out parameters (connections supporting in/out parameters):
... sizes: 4K, 32K, 65K, 512K, 1M, 5M, 10M up to 15M (see crtsql in download) ...
iPLUG4K(IN IPC CHAR(1024), IN CTL CHAR(1024),IN CI CHAR(4064), OUT C0 CHAR(4064))
iPLUG32K(IN IPC CHAR(1024), IN CTL CHAR(1024), IN CI CLOB(32000), OUT CO CLOB(32000))
iPLUG65K(IN IPC CHAR(1024), IN CTL CHAR(1024), IN CI CLOB(65K), OUT CO CLOB(65K))
iPLUG512K(IN IPC CHAR(1024), IN CTL CHAR(1024), IN CI CLOB(512K), OUT CO CLOB(512K))
iPLUG1M(IN IPC CHAR(1024), IN CTL CHAR(1024), IN CI CLOB(1M), OUT CO CLOB(1M))
iPLUG5M(IN IPC CHAR(1024), IN CTL CHAR(1024), IN CI CLOB(5M), OUT CO CLOB(5M))
iPLUG10M(IN IPC CHAR(1024), IN CTL CHAR(1024), IN CI CLOB(10M), OUT CO CLOB(10M))
iPLUG15M(IN IPC CHAR(1024), IN CTL CHAR(1024), IN CI CLOB(15M), OUT CO CLOB(15M))
$stmt = db2_prepare($conn, "call XMLSERVICE.iPLUG4K(?,?,?,?)");
$ret=db2_bind_param($stmt, 1, "ipc", DB2_PARAM_IN);
$ret=db2_bind_param($stmt, 2, "ctl", DB2_PARAM_IN);
$ret=db2_bind_param($stmt, 3, "clobIn", DB2_PARAM_IN);
$ret=db2_bind_param($stmt, 4, "clobOut", DB2_PARAM_OUT);
$ret=db2_execute($stmt);
DB2 Result set returned (connections not supporting in/out parameters):
... sizes: 4K, 32K, 65K, 512K, 1M, 5M, 10M up to 15M (see crtsql in download) ...
CREATE PROCEDURE XMLSERVICE.iPLUGR4K(IN IPC CHAR(1024), IN CTL CHAR(1024), IN CI CHAR(4096))
iPLUGR32K(IN IPC CHAR(1024), IN CTL CHAR(1024), IN CI CHAR(32000))
iPLUGR65K(IN IPC CHAR(1024), IN CTL CHAR(1024), IN CI CLOB(65K))
iPLUGR512K(IN IPC CHAR(1024), IN CTL CHAR(1024), IN CI CLOB(512K))
iPLUGR1M(IN IPC CHAR(1024), IN CTL CHAR(1024), IN CI CLOB(1M))
iPLUGR5M(IN IPC CHAR(1024), IN CTL CHAR(1024), IN CI CLOB(5M))
iPLUGR10M(IN IPC CHAR(1024), IN CTL CHAR(1024), IN CI CLOB(10M))
iPLUGR15M(IN IPC CHAR(1024), IN CTL CHAR(1024), IN CI CLOB(15M))
$stmt = db2_prepare($conn, "call $libxmlservice.iPLUGR4K(?,?,?)");
$ret=db2_execute($stmt,array($ipc,$ctl,$clobIn));
while ($row = db2_fetch_array($stmt)){
$clobOut .= trim($row[0]);
}
Why different sizes??
The idea is simply pick the “best fit” max size of XML documents to/from IBM i per call, so you don’t transport a whole bunch of blanks between you scripting/IBM i applications.
Stored procedure parameters (details follow):
- IN IPC CHAR(1024) - IPC key name/security
- IN CTL CHAR(1024) - CTL admin control XMLSERVICE job
- IN CI CLOB(15M) - XML input document (request)
- OUT CO CLOB(15M) - XML output document (response)
Note: iPLUGRxxx procedures return a result set that is collected by fetch.
1) IN IPC CHAR(1024) - IPC key (InternalKey)
The IPC/InternalKey is used to route XML input/output documents to the correct XMLSERVICE job. The IPC is fully qualified path commonly in the /tmp directory where each unique path is the target of given XMLSERVICE job being used by a given user/password profile (normal profile authority applies).
- InternalKey must be a fully IFS qualified path, if not exist then XMLSERVICE will attempt to create on first use.
- InternalKey object authorization is standard IBM i profile mechanism.
- XMLSERVICE provides a sync IPC lock (/tmp/$anything), so only one user/request is active at a time (like 5250).
Example call PGM ZZCALL, run in Zend subsystem, *debug wait before call (qsysopr msg):
$ipc='/tmp/ranger001';
$ctl = '*sbmjob(ZENDSVR/ZSVR_JOBD) *debug';
$clobIn =
"<?xml version="1.0"?>
<script>
<cmd>ADDLIBLE LIB(XMLSERVICE) POSITION(*FIRST)</cmd>
<pgm name='ZZCALL'>
<parm>
<data type='1A'>a</data>
</parm>
<return>
<data type='10i0'>0</data>
</return>
</pgm>
</script>";
$clobOut = "";
$stmt = db2_prepare($conn, "call XMLSERVICE.iPLUG4K(?,?,?,?)");
$ret=db2_bind_param($stmt, 1, "ipc", DB2_PARAM_IN);
$ret=db2_bind_param($stmt, 2, "ctl", DB2_PARAM_IN);
$ret=db2_bind_param($stmt, 3, "clobIn", DB2_PARAM_IN);
$ret=db2_bind_param($stmt, 4, "clobOut", DB2_PARAM_OUT);
$ret=db2_execute($stmt);
2) IN CTL CHAR(1024) - CTL admin
These control options are used to control the XMLSERVICE jobs.
// *immed - end server immed destroy ipc
// *sbmjob[(lib/jobd)] - sbmjob(PLUGJOBLIB/PLUGJOBD)
// *nostart - disallow spawn
// *here - run stateless in stored proc job
// *session - retrieve session key (IPC)
// *license - license this code
:
Example kill XMLSERVICE for ‘/tmp/rangerusr’:
$ipc='/tmp/rangerusr';
$ctlKill="*immed";
$clobInKill = '<?xml version="1.0"?>';
$sql = "call $libxmlservice.iPLUGR4K('$ipc','$ctlKill','$clobInKill')";
$ret=db2_exec($conn,$sql);
3) IN CI CLOB(4K to 15M) - XML input
This is the input XML request document (see XML syntax next section). The XML input document will run all actions in the XMLSERVICE job and produce an XML output document.
<?xml version="1.0"?>
<script>
<cmd comment='addlible'>ADDLIBLE LIB(DB2) POSITION(*FIRST)</cmd>
<pgm name='ZZSRV' func='ZZARRAY'>
<parm comment='search this name'>
<data var='myName' type='10A'>Ranger</data>
</parm>
<parm comment='max allowed return'>
<data var='myMax' type='10i0'>5</data>
</parm>
<parm comment='actual count returned'>
<data var='myCount' type='10i0' enddo='mycount'>0</data>
</parm>
<return>
<ds var='dcRec_t' dim='10' dou='mycount'>
<data var='dcMyName' type='10A'>na</data>
<data var='dcMyJob' type='4096A'>na</data>
<data var='dcMyRank' type='10i0'>0</data>
<data var='dcMyPay' type='12p2'>0.0</data>
</ds>
</return>
</pgm>
</script>
4) OUT CO CLOB(15M) - XML output
The output XML document will be produced in the same order that the XML input script runs, The output can be collected from the in/out parameter (iPLUGxxx) or from fetch of a result set (iPLUGRxxx).
<?xml version="1.0"?>
<script>
<cmd comment='addlible'>+++ success ADDLIBLE LIB(XMLSERVICE) POSITIO</cmd>
<pgm name='ZZSRV' lib='XMLSERVICE' func='ZZARRAY'>
<parm comment='search this name'>
<data var='myName'>Ranger</data>
</parm>
<parm comment='max allowed return'>
<data var='myMax'>5</data>
</parm>
<parm comment='actual count returned'>
<data var='myCount'>5</data>
</parm>
<return>
<ds var='dcRec_t'>
<data var='dcMyName'>Ranger1</data>
<data var='dcMyJob'>Test 101</data>
<data var='dcMyRank'>11</data>
<data var='dcMyPay'>13.42</data>
</ds>
<ds var='dcRec_t'>
<data var='dcMyName'>Ranger2</data>
<data var='dcMyJob'>Test 102</data>
<data var='dcMyRank'>12</data>
<data var='dcMyPay'>26.84</data>
</ds>
<ds var='dcRec_t'>
<data var='dcMyName'>Ranger3</data>
<data var='dcMyJob'>Test 103</data>
<data var='dcMyRank'>13</data>
<data var='dcMyPay'>40.26</data>
</ds>
<ds var='dcRec_t'>
<data var='dcMyName'>Ranger4</data>
<data var='dcMyJob'>Test 104</data>
<data var='dcMyRank'>14</data>
<data var='dcMyPay'>53.68</data>
</ds>
<ds var='dcRec_t'>
<data var='dcMyName'>Ranger5</data>
<data var='dcMyJob'>Test 105</data>
<data var='dcMyRank'>15</data>
<data var='dcMyPay'>67.10</data>
</ds>
</return>
</pgm>
</script>
Workaround for “bad” drivers leaving junk back of output clob
Alas, the world is not perfect 1-2 tier DB2 drivers IBM i, Windows, Linux, etc., so occasionally a “hack” is handy.
I always “scope” my XML input requests with <script>…</script>, so anything past tailing </script> is ‘junk’ (errors return as <report>…</report>).
The new XMLSERVICE keyword *hack adds </hack> back of every record return result set can be very useful for drivers that do not support stored procedure in/out parameters like PHP odbc.
function driverJunkAway($xml)
{
// trim blanks (opps no)
$clobOut = $xml;
if (! trim($clobOut)) return $clobOut;
// result set has extra data (junk)
$fixme = '</hack>';
$pos = strpos($clobOut,$fixme);
if ($pos > -1) {
$clobOut = substr($clobOut,0,$pos);
}
else {
$fixme = '</script>';
$pos = strpos($clobOut,$fixme);
if ($pos > -1) {
$clobOut = substr($clobOut,0,$pos+strlen($fixme));
}
// maybe error/performance report
else {
$fixme = '</report>';
$pos = strpos($clobOut,$fixme);
if ($pos > -1) {
$clobOut = substr($clobOut,0,$pos+strlen($fixme));
}
}
}
return $clobOut;
}
Example PHP client use driverJunkAway($xml)
function xmlservice_ibm_db2($xml) {
global $fast, $db, $user, $pass, $ipc, $ctl, $conn, $lib, $plug, $plugR;
$xmlIn = $xml;
$xmlOut = '';
if (!$conn) {
if ($fast) $conn = db2_pconnect($db, $user, $pass); // persistent/pooled connection
else $conn = db2_connect($db, $user, $pass); // full open/close connection
}
if (!$conn) die("Bad connect: $db, $user");
$stmt = db2_prepare($conn, "call $lib.$plug(?,?,?,?)"); // Call XMLSERVICE
// stored procedure interface
// in/out parameter (xmlOut)
// sizes: iPLUG4K - iPLUG15M
if (!$stmt) die("Bad prepare: ".db2_stmt_errormsg());
$ret=db2_bind_param($stmt, 1, "ipc", DB2_PARAM_IN); // ? - /tmp/raw_$user (*sbmjob)
$ret=db2_bind_param($stmt, 2, "ctl", DB2_PARAM_IN); // ? - *here or *sbmjob
$ret=db2_bind_param($stmt, 3, "xmlIn", DB2_PARAM_IN); // ? - XML input script
$ret=db2_bind_param($stmt, 4, "xmlOut", DB2_PARAM_OUT); // ? - XML output return
$ret=db2_execute($stmt);
if (!$ret) die("Bad execute: ".db2_stmt_errormsg());
return driverJunkAway($xmlOut); // just in case driver odd
// ... possible junk end record,
// record</script>junk
}
function xmlservice_odbc($xml) {
global $fast, $db, $user, $pass, $ipc, $ctl, $conn, $lib, $plug, $plugR;
$xmlIn = $xml;
$xmlOut = '';
if (!$conn) {
if ($fast) $conn = odbc_pconnect($db, $user, $pass); // persistent/pooled connection
else $conn = odbc_connect($db, $user, $pass); // full open/close connection
}
$stmt = odbc_prepare($conn, "call $lib.$plugR(?,?,?)"); // Call XMLSERVICE
// stored procedure interface
// result set return (fetch)
// sizes: iPLUGR4K - iPLUGR15M
if (!$stmt) die("Bad prepare: ".odbc_errormsg());
$options = array($ipc, // ? - /tmp/raw_$user (*sbmjob)
$ctl, // ?- *here or *sbmjob
$xmlIn); // ?- XML input script
// bad behavior odbc extension
// ... IBM i result set warning???
error_reporting(~E_ALL); // bad behavior odbc extension
// ... IBM i result set warning???
$ret=odbc_execute($stmt,$options);
if (!$ret) die("Bad execute: ".odbc_errormsg());
error_reporting(E_ALL);
while(odbc_fetch_row($stmt)) {
$xmlOut .= driverJunkAway(odbc_result($stmt, 1)); // bad behavior odbc extension
// ... possible junk end record,
// xmlservice provided $ctl='*hack'
// record</hack>junk
}
return $xmlOut;
}
function xmlservice_pdo_ibm($xml) {
global $fast, $db, $user, $pass, $ipc, $ctl, $conn, $lib, $plug, $plugR;
$xmlIn = $xml;
$xmlOut = '';
if (!$conn) {
$database = "ibm:".$db;
try {
if ($fast) $opt = array(PDO::ATTR_PERSISTENT=>true, PDO::ATTR_AUTOCOMMIT=>true); // persistent/pooled connection
else $opt = array(PDO::ATTR_AUTOCOMMIT=>true); // full open/close connection
$conn = new PDO($database, strtoupper($user), strtoupper($pass), $opt);
if (!$conn) throw new Exception("Bad");
} catch( Exception $e ) {
die("Bad connect: $database, $user");
}
}
try {
$stmt = $conn->prepare("call $lib.$plug(?,?,?,?)"); // Call XMLSERVICE
// stored procedure interface
// in/out parameter (xmlOut)
// sizes: iPLUG4K - iPLUG15M
if (!$stmt) throw new Exception('Bad');
} catch( Exception $e ) {
$err = $conn->errorInfo();
$cod = $conn->errorCode();
die("Bad prepare: ".$cod." ".$err[0]." ".$err[1]." ".$err[2]);
}
try {
$r1=$stmt->bindParam(1,$ipc, PDO::PARAM_STR);
$r2=$stmt->bindParam(2,$ctl, PDO::PARAM_STR);
$r3=$stmt->bindParam(3,$xmlIn, PDO::PARAM_STR);
$r4=$stmt->bindParam(4,$xmlOut, PDO::PARAM_STR|PDO::PARAM_INPUT_OUTPUT);
$ret = $stmt->execute();
if (!$ret) throw new Exception('Bad');
} catch( Exception $e ) {
$err = $stmt->errorInfo();
$cod = $stmt->errorCode();
die("Bad execute: ".$cod." ".$err[0]." ".$err[1]." ".$err[2]);
}
return driverJunkAway($xmlOut); // just in case driver odd
// ... possible junk end record,
// record</script>junk
}
XMLSERVICE advanced CCSID¶
Using default PHP toolkit DB2 clob interface (iPLUGxxx/iPLUGRxxx), ccsid conversion occurs naturally as DB2 client/server and you will not have to code before/after, but method is available if you have a specific concern or you have scripts returning many different languages.
Example:
<data type='200A' before='819/424' after='424/819'>bin2hex('Hebrew_ascii_raw_chars')</data>
<data type='200A' before='819/1098' after='1098/819'>bin2hex('Farsi_ascii_raw_chars')</data>
<data type='200A' before='819/880' after='880/819'>bin2hex('Russia_ascii_raw_chars')</data>
<data type='200A' before='819/280' after='280/819'>bin2hex('Italy_ascii_raw_chars')</data>
<data type='200A' before='819/273' after='273/819'>bin2hex('Germany_ascii_raw_chars')</data>
<data type='200A' before='819/1088' after='1088/819'>bin2hex('Korea_ascii_raw_chars')</data>
<data type='200A' before='1208/13488' after='13488/1208'>bin2hex('Japan_ascii_raw_chars')</data>
<data type='200A' before='1208/13488' after='13488/1208'>bin2hex('China_ascii_raw_chars')</data>
where:
before - XMLSERVICE convert CCSID before ILE program call
after - XMLSERVICE convert CCSID after ILE program call for client return
bin2hex() - script hex string unaltered ascii image (also returned hex string avoid any conversion)
pack() - script uses pack('H*',"xml_hex_back") function in PHP program for ascii characters
Note:
Up to four conversions can take place for the truly diabolical ccsid issues
<data type='A' before='cc1/cc2/cc3/cc4' after='cc4/cc3/cc2/cc1'>bin2hex('wild_ascii_raw_chars')</data>
flow:
-> PHP client bin2hex('wild_ascii_raw_chars')
-> xmlservice hex2bin back to 'wild_ascii_raw_chars'
-> xmlservice convert cc1->cc2->cc3->cc4 (before)
-> xmlservice make ILE call
-> xmlservice convert cc4->cc3->cc2->cc1 (after)
-> xmlservice tohex "xml_hex_back"
-> PHP client $chars = pack('H*',"xml_hex_back")
XMLSERVICE Examples¶
The following foils include running examples you can click to help get started as you learn about this Open Source.
Goal of Open Source XMLSERVICE RPG library is flexibility, enabling ANY sort of transport local/remote connection with any language available in your enterprise to call resources on your IBM i machine. XMLSERVICE primary transport interface in production today is DB2 connections using included stored procedures (xmlstoredp.srvpgm iPLUGxxxx). XMLSERVICE secondary transport interface used mostly demos is Apache REST connection (xmlcgi.pgm). However, XMLSERVICE allows writing your own custom transport (anything you imagine). XML Service and Toolkit on-going mission is to add new function, improve performance, expand uses, with goal of never impacting existing customers.
Warning: This is an active IBM i education machine, occasionally examples may not work, try back later.
Part 1 - Protect your investment¶
- XML Service Access Native IBM i Objects from any language using XML
- XML Service completely free/safe commercial use download (Github)
- XML Service examples HTML/XML (no PHP, no RPG)
- XML Service examples RPG (no PHP)
- XML Service vs. DB2 Stored Procedures - Why use XML Service at all?
- XML Service protect your investment summary
XML Service - is free¶
- XML Service is completely free/safe commercial use
- BSD license, download, keep any source copy forever (safe)
- XML Service written in RPG open source (you can change it)
- Techniques used IBM i calls are stable, unlikely to change (ever)
- XML Service is supported Open Source
- XML Service fix/add improvement goal is never impact current customers
XML Service - XML everything¶
- XML input <> XML output
- Any device
- Any language
- Any local/remote connection
- Any IBM i service (PGM, CMD, System API, DB2, PASE)
XML Service - IBM i Native Access through XML¶
- Access Native IBM i Objects from any language using XML
- Local or remote IBM i: IBM i local, IBM i-2-IBM i, Linux/Unix/Windows-2-IBM, etc.
- Any language access: PHP, Ruby, Java, Perl, RPG, no language at all (HTML/XML)
- Many Native Object Types: DB2 SQL and Native, Program call, Procedure call, Data Area, Data Queue, Message Queue, Commands, System values, Spool files, PASE utilities
- Local or remote call interfaces to XMLSERVICE (included RPG download)
- Primary: call DB2 stored procedures local or remote (iPLUG4K - iPLUG15M)
- Secondary: call REST HTTP local or remote (xmlcgi.pgm)
XML Service - Moving Parts¶
- Any language
- Browser HTML/XML
- Script PHP, Ruby, JavaScript, etc.
- Compiled RPG, C, etc.
- Any local/remote connection
- Linux/Unix/Windows/Mac ibm_db2/odbc/REST to IBM i
- Native IBM i ibm_db2/odbc/REST to IBM i
XML Service public “stateless” (*here)¶
- XMLSERVICE public “stateless” (CTL=’*here’, IPC=’*NA’)
- profile FRED (any public QSQ)
- profile SALLY (any public QSQ)
- profile RITA (any public QSQ)
- profile XAVIER (any public QSQ)
- XMLSTOREDP->XMLSERVICE (QSQ)
- QSQ temporary profile use (stateless)
- QSQ return to pool on script end
- XMLSERVICE restart every request (web style)
XML Service private “state full” (*sbmjob)¶
- XMLSERVICE private “state full” (CTL=’*sbmjob’, IPC=’/tmp/xxxx’)
- profile FRED XTOOLKIT myjob1,myjob2 (private)
- profile SALLY XTOOLKIT sallyjob1 (private)
- profile RITA XTOOLKIT nursejob (private)
- profile XAVIER XTOOLKIT xjob1,xjob2,xjob3,xjob4,xjob5 (private)
- XMLSTOREDP (QSQ)
- QSQ temporary profile use (stateless)
- QSQ return to pool on script end
- XMLSERVICE (XTOOLKIT)
- XTOOLKIT owned by profile (private)
- XTOOLKIT job never ends (until killed)
- XTOOLKIT full state programming (5250 style)
XML Service Configuration¶
- Apache REST (xmlcgi.pgm)
- DB2 stored procedures (xmlstoredp.srvpgm iPLUGxx)
XML Service - Example HTML/XML (no PHP, no RPG)¶
XML Service HTTP browser direct (xmlcgi.pgm)
- web enable via httpd.conf
<form method='POST' action='/cgi-bin/xmlcgi.pgm'> ScriptAlias /cgi-bin/ /QSYS.LIB/XMLSERVICE.LIB/ <Directory /QSYS.LIB/XMLSERVICE.LIB/> order allow,deny allow from all SetHandler cgi-script Options +ExecCGI </Directory>
XML Service - Example RPG (no PHP)¶
- XML Service stored procedure interface
- RPG DB2 using Exec Sql (iPLUGxxx)
- RPG DB2 using CLI (iPLUGxxx)
- web enable via httpd.conf
# demo callpase
ScriptAlias /demo/ /QSYS.LIB/CALLPASE.LIB/
<Directory /QSYS.LIB/CALLPASE.LIB/>
order allow,deny
allow from all
SetHandler cgi-script
Options +ExecCGI
</Directory>
Note: Exec Sql RPG CGI uses profile QTMHHTP1, however PHP is usually running QTMHHTTP, so you may fail authorisation sharing same XMLSERVICE job (1). Therefore, i recommend use RPG CLI technique to run any profile, where XMLSERVICE job(s) PHP/RPG is no problem (2).
XML Service vs. DB2 Stored Procedures - Why use XML Service at all?¶
Why not write my own stored procedures? Why use XML Service at all?
XML Service | Stored procedure |
---|---|
Device, protocol, & language portability | |
browser, REST, DB2, RPG… | device/driver/language specific |
Complex Data Structures | |
trivial | near impossible |
Call CMDs and collect data | |
trivial | very difficult |
Call PASE utilities and collect data | |
trivial | very difficult |
Route same job (IPC/internalKey) | |
trivial | near impossible |
30,000 records around 2 seconds | |
fast | faster |
Protect your investment¶
- XML Service protects your wallet 100% free download
- XML Service protects your skills 100% RPG source code
- XML Service protects your project costs with XML based low budget features
- XML Service protects your investment applications functions/performance over time
- XML Service protects your investment across device proliferation
- XML Service protects your investment across script language proliferation
- XML Service protects your investment across any transport driver (XML is a string)
Part 2 - Production use today¶
Topics
- PHP Toolkit included with Zend Server for IBM i
- XML Interface ibm_db2, pdo_ibm, odbc included with Zend Server for IBM i
- XML Interface and PHP Toolkit - Performance
- XML Interface and PHP Toolkit - Debugging
- XML Interface and PHP Toolkit - Active community/support
New PHP Toolkit included with Zend Server for IBM i¶
- Zend Server PHP Toolkit (included)
- PHP CW Layer - old toolkit
- PHP New Toolkit - OO toolkit
XML Interface ibm_db2, pdo_ibm, odbc included with Zend Server for IBM i¶
- XML Service stored procedure interface (included)
- DB2 ibm_db2 param in/out (iPLUG 4k-15m)
- DB2 odbc result set (iPLUGR 4k-15m)
XML Interface and PHP Toolkit - Active community/support¶
You are not alone, seek out help …
Debug technique: It’s as easy as 1-2-3-4-5-6-7-8-9 :)¶
- Run the test script that contains control “*debug” and script will “hang” while it waits on #2
$ctl .= "*debug";
- A MSGW inquiry message in DSPMSG QSYSOPR will be generated by the toolkit. Note the job information (number, name, user) provided in the MSGW.
- STRSRVJOB using that job information as parameters.
- STRDBG with the program and library you wish to debug.
- Answer the MSGW. Any answer will do–“G” is fine.
- The RPG program source will appear in debug mode in your terminal, ready to step through, allowing you to inspect variables, etc.
- When done inspecting and stepping, let the RPG program complete (using function keys indicated on screen).
- ENDDBG
- ENDSRVJOB
Other debug options …
Job1 (threaded) Job 2 Job 3 (DB2 userid/password) Job 4 (optional XTOOLKIT job)
(ctl=*debugcgi) (ctl=*debugproc) (ctl=*debug)
browser -> Apache ->XMLCGI (Apache CGI child) -> QSQSRVR (XMLSERVICE *here)
-> QSQSRVR (XMLSERVICE client) -> XTOOLKIT (XMLSERVICE ipc=/tmp/flinstone)
$ctl .= " *debugcgi"; // Job 2 - debug XMLCGI to see REST/HTTP data passed by client (when using REST only)
$ctl .= " *debugproc"; // Job 3 - debug XMLSERVICE "client" to see DB2 passed data (DB2 interface)
$ctl .= " *debug"; // Job 4 - debug XMLSERVICE "server" to see XMLSERVICE calls (DB2 interface)
// Note: when ctl='*here', both XMLSERVICE "client"/"server"
// are in QSQSRVSR job (NO XTOOLKIT job)
// remote: Attaching with LUW drivers changes QSQSRVR ...
// CLIENT (Client Access drivers) <==> QZDAxxxx
// CLIENT (DB2 Connect drivers) <==> QRWxxxx
XMLSERVICE Layouts¶
This page is for RPG developers that want to customize XMLSERVICE.
XMLSERVICE source files¶
-----------------------------------------
| Browser |
|---------------------------------------|
(1)->| Download RPG (1) | Download PHP (2) |
| 1) XMLSERVICE | a) PHP CW Toolkit |
| HTML/XML/REST | b) New PHP Toolkit |
| no PHP |--------------------|
| (xmlcgi.pgm) | c) PHP “Raw XML” |
| (optional) | (ibm_db2, odbc) |
| -----------------------------------|
| 2) XMLSERVICE DB2 stored procedures |<-(1)
| (iPLUG4K, iPLUG32K, ..., iPLUG15M) |
| 3) XMLSERVICE (xmlservice.pgm) |<-(1) xmlservice-rpg-x.x.x.zip
| call most anything on IBM i ... |
| (PGM, SRVPGM, PASE, DB2, etc.) |
-----------------------------------------
The following is a brief description of the function of the source files included with the XMLSERVICE library Open Source project.
LIB:
XMLSERVICE
QSQLSRC:
crtsql - db2 utility script to create stored procedures
QRPGLESRC:
plugconf_h - compile configuration options
plugmri_h - translateable messages
plugcach_h - interface for all cahces
plugcach - implementation for all caches
plugbug_h - interface debug (*debug)
plugbug - implement debug message qsysopr
plugerr_h - interface errors
plugerr - implement error collection and report
plugile_h - ILE ABI interface
plugile - implement ILE ABI paramter memory layout
plugipc_h -interface IPC (*nostart)
plugipc - implement IPC shared memory and semaphore
pluglic_h - interface license (*license)
pluglic - implement license report
plugpase_h - interface PASE functions
plugpase - implement PASE PGM, SRVPGM, sh
plugperf_h - interface performance (*rpt)
plugperf - implement performance collection and report
plugrun_h - interface run loop (*here *immed *justproc *sbmjob)
plugrun - implement client/server run loop (RPG cycle)
plugxml_h - interface XML
plugxml - implement XML parse and run logic
plugsql_h - interface sql by XML
plugsql - implement sql by XML (1.5)
plugdb2_h - interface sql db2 by XML
plugdb2 - implement sql db2 by XML
plugsig_h.rpgle - interface timeout signal (1.6.6)
plugsig.rpgle - implement timeout signal
plugconv_h.rpgle - interface ccsid conversion (1.6.6)
plugconv.rpgle - implement ccsid conversion
xmlcgi - PGM client interface Apache CGI (HTTP REST)
xmlmain - PGM client interface RPG-2-RPG (experimental)
xmlservice - PGM server deamons (XMLSERVICE jobs)
xmlstoredp - SRVPGM client DB2 stored procedures (DB2 connections)
zzcall - optional PGM for pear tests
zzsrv - optional SRVPGM for pear tests
XMLSERVICE hooks plugconf¶
XMLSERVICE allows for you to customize a few key security and output decisions in the RPG module plugconf or provide your own plugconf(x) version.
XMLSERVICE hooks APIs plugconf_h
The hooks are ONLY available with XMLSERVICE version 1.5.5+.
plugconf module must implement the following interfaces prototyped by plugconf_h … failure to implement these APIs will result in compile failure.
There are key hooks in plugrun / xmlcgi that allow your to decline service after you snoop the input XML. Likewise, there are key hooks that allow you to manipulate the XML output returned to the clients if you wish to “customize” XML (cool), however it should be noted that if you change the output XML you may break other clients (like Zend PHP wrapper).
Running with *NONE from XMLCGI¶
XMLSERVICE via XMLCGI may be too powerful using only HTML/XML with no user/password (*NONE), so it is turned OFF by default always. However, if your machine is behind a firewall and you trust your team you can enable *NONE …
- make your own plugconf(x)
D PLUGNONEOK S 1N inz(*ON)
- recompile and your machine will be wide open …
Note: The other setting PLUGDEMOOK is not used is future consideration for demos not from XMLCGI.
XMLSERVICE XMLCGI (RPG CGI)¶
A sample XMLCGI interface was included to allow REST calls to XMLSERVICE. This was very handy to check out XML to/from XMLSERVICE before ever committing pen to paper on an actual script like PHP. It also seems to very handy fro one off HTML/XML applications that can reside on your laptop (or other device) and use the built in browser to run IBM i.
BIG DESIGN HINT:
Although common practice in IBM i circles, I choose not to use Basic Authentication and/or “switch profile” in RPG CGI programs on the web (httpd.conf ServerId). I much prefer to leave all Apache/CGI related jobs as neutral “low authority” like QTMHHTTP (IBM i) or NOBODY (Linux) for clearly understood security. Also, I prefer keeping any security “switch profile” activity in the database connection (DB2) for portability of design and multiple client support (1-tier/2-tier). An example of this “database security” style of CGI is Apache module XMLCGI included in XMLSERVICE download (main page). XMLCGI implements all “security” preferences and “switch profile” using the DB2 CLI interface in RPG.
Briefly, reasons i like keep the “profile security” in the database … * portable design idea in tradition of millions of PHP/MySql web sites (idea of profile is in database) * easy switch/sharing between 1-tier and 2-tier applications across common database design (DB2) * no “protocol” invention required because most languages support database (including PHP, Java, RPG, Cobol, etc.) * supporting/sharing applications a wide range of device clients such as PDAs, laptops, servers is much easier as most can handle REST and/or DB2 communications (whatever language is available)
Do it yourself RPG:
- If you are a do it yourself RPG programmer, you may want to download and study XMLCGI RPG source file included as this would allow you to write any RPG front-end (just steal the DB2 CLI stuff in XMLCGI if you want a server with profiles).
- RPG program XMLCGI is a traditional “Apache/Unix” style CGI server (ala PHP/MySql), therefore you do not need “switch profile” in the XMLCGI job. All/any “switch profile” is occurring at the DB2 database layer via DB2 CLI server mode not the Apache level (without using profiles in httpd.conf)
- WARNING: Unless https, XMLCGI sends unencrypted password around web and/or leaves them in files (unless using *NONE), which is a bad idea of course, but this is a demonstration module for testing (without PHP, etc.), you as the RPG programmer are intended to customize to your production world (Open Source RPG code).
XMLSERVICE XMLMAIN (unused)¶
Also included, is a simple RPG program XMLMAIN that calls client interface XMLSERVICE in processes inside or outside web context, has not been extensively tested (very little in fact), but should be general idea for a IBM i RPG centric interface (if such a thing is even relevant these Internet days).
If you wish to build a traditional web IBM i “switch profile” CGI web service (httpd.conf ServerId), you would need to combine parts of XMLMAIN (direct call XMLSERVICE) and XMLCGI (excluding DB2 CLI).
XMLSERVICE Connections¶
Sometimes a real conversation helps …¶
Customer wants to know how to remain stateless, but get the benefit of being able to audit the use of RPG subprocedure invocations via XMLToolkit without toggling back and forth (different QSQSRVR jobs).
Stateless may use multiple QSQSRVR jobs (toggling), this is definition of ‘stateless’ in PHP fastcgi environment (working correct).
Will ToolkitService object using persistent db2 connections and “stateless => true” help single QSQ job?
Toolkit “stateless => true” via db2 transport using db2_connect or db2_pconnect makes no difference, fastcgi implies unpredictable QSQSRVR job will be used (command line slightly more predictable over web).
Why ‘unpredictable’ QSQ?
It’s all web PHP fastcgi ‘random’ worker selection, NOTHING to do with ibm_db2 ‘connection’ persistent or non-persistent, toolkit connection is irrelevant. Apache web site using fastcgi routes “randomly” to any available PHP worker job php-cgi, therefore ‘random’ php-cgi worker using db2_(p)connect<>QSQSRVR will also appear ‘random’. To wit, you end up different QSQSRVR jobs (toggle back and forth). Technically, fastcgi protocol, PHP workers poll a single fastcgi socket waiting to take some work, ZS on i at /www/zendsvr6/logs/fcgi-njjjineh.sock (sock name random). As each php worker strips work off fastcgi socket (1st come == 1st do), the worker is busy communicating on ‘private web socket’ running script until finished (no longer waiting). Combination of natural web worker selection (browser +/- KeepAlive), and fastcgi socket poll for work, results in ‘random’ appearance QSQ job toolkit usage, DB2 connection just came along for ride in back seat of the 1950 PHP fastcgi roadster.
Help me be one-and-only-one job?
The only way to assure private connection back to same XTOOLKIT job is ‘internalKey’=>’/tmp/XTOOLKIT_job_1’ (‘ipc’=/tmp/XTOOLKIT_job_1’).
XMLSERVICE connections discussed this page¶
- Stateless – clean running come/go, “full open/close,” start/stop connection to be used by any requester. Must set LIBL during each request
- State full – State retained between requests: LIBL, transactions, etc., “private” connection used by one requester/task for a long period of time (like 5250)
- State full – hybrid “private/persistent” connection shared by many requesters, but keep open PGM, files, etc.
- State full – reservation hybrid “private/persistent” connection exclusively held for a period each requesters, but returned back to pool for re-use (rare use)
The jobs involved in “connections”:
I. Stateless
(job 1) (job 2) (job 3)
Apache FastCGI DB2 (stateless)
------- --------------- ---------------------
browser/client ->thread-->socket->php-cgi
--->$ctl="*here"; -->QSQSRVR+XMLSERVICE
shut down after
each request
("stateless")
II. State full
(job 2) (job 3) (job 4)
FastCGI QSQ (proxy) XMLSERVICE (state full)
--------------- --------------------- ------------------------
-->socket->php-cgi
--->$ctl="*sbmjob";
--->$ipc="/tmp/sally"; -->QSQSRVR+XMLSERVICE -->XMLSERVICE
alive until stopped
("state full")
Jobs originates::
(job 1) Apache picks any thread (1st level routing)
(job 2) FastCGI all "non-busy" worker php-cgi wait on unix domain socket /www/zend2/logs/fcgi-hmjadgek.sock (2nd level routing)
(job 3) php-cgi - database connections odbc, ibm_db2, pdo_ibm (3rd level routing)
db2_pconnect() attach to pooled/persistent QSQ (matching profile) but leaves connection open on exit
db2_connect() acquires a unused pre-start QSQ (or starts one) then attaches to QSQ (profile), and returns to unused pool on exit
(job 3) XMLSERVICE -- Stateless -- run inside QSQ job and clean-up after each request (3rd level routing)
$ctl = "*here";
(job 4) XMLSERVICE -- State full -- run in separate job that any QSQ job can call using IPC (4th level routing)
$ctl = "*sbmjob";
$ipc = "/tmp/sally";
Drivers involved in conection:
400 server start-up Common usage Big picture Comments
STRTCPSVR SERVER(*HTTP)
port 80 or 10088 or … (REST/HTTP interface)
1-tier - XMLCGI CLIENT <==> IBM HTTP Server <==> XMLCGI (CLI server mode) <==> QSQxxxx <==> IBM i Resources XMLCGI + CLI
PASE library (no start needed)
no port
1-tier - PHP ibm_db2+pdo_ibm+odbc (Native PASE CLI libdb400.a driver) CLIENT (PASE libdb400.a driver) <==> QSQxxx <==> IBM i Resources Native PASE CLI libdb400.a driver (IBM Rochester)
STRHOSTSVR SERVER(*DATABASE)
port 8471 (database)
2-tier - PHP odbc interface (IBM i Client Access odbc driver interface) CLIENT (Client Access drivers) <==> QZDAxxxx <==> IBM i Resources Client Access odbc-based drivers (IBM Rochester)
STRTCPSVR SERVER(*DDM)
port 446 (DDM/DRDA)
2-tier - PHP ibm_db2+pdo_ibm (DB2 Connect driver interface) CLIENT (DB2 Connect drivers) <==> QRWxxxx <==> IBM i Resources DB2 CLI DRDA-based DB2 Connect drivers (IBM Toronto)
1) Stateless – no LIBL, come/go¶
These connections are traditional web requests “full open/close” clean running start/stop connection to be used by any requester.
- Stateless
$ctl = "*here";
(1) (2) (3)
Apache FastCGI DB2 (server mode)
------- --------------- ---------------------
browser/client -->thread--socket->php-cgi--->QSQSRVR(profile fred)
XMLSERVICE (fred) <--shut down after each request
--->QSQSRVR(profile sally)
XMLSERVICE (sally) <--shut down after each request
--->QSQSRVR(profile john)
XMLSERVICE (john) <--shut down after each request
Example new Toolkit (stateless):
if ($i5persistentconnect) $conn = db2_pconnect($database,$user,$password);
else $conn = db2_connect($database,$user,$password);
try { $ToolkitServiceObj = ToolkitService::getInstance($conn); }
catch (Exception $e) { die($e->getMessage()); }
$ToolkitServiceObj->CLCommand("CHGLIBL LIBL(FREDFLIN WILMAFLIN) CURLIB(FREDFLIN)");
Stateless: If you choose $ctl=’*here’, you will run in the calling process DB2 connection (QSQSRVR job). When XMLSERVICE completes your XML script it will shut down to nothing, considered stateless and holds zero state on return.
- In general you will run slower in stateless mode (CW default / Toolkit default), because XMLSERVICE has to keep starting things over and over and over again, but perhaps not an issue if you have CPU to burn.*
- The is no semaphore locking or shared memory ipc when running as stateless (*here), because only one sally client/server is a pair, but of course there may be many sally client/server pairs on the same machine.*
- There is no “memory” of the LIBL in stateless, so it must be set EVERY time before use.*
2) State full – LIBL, transactions, etc.¶
These connections are traditional 5250-like “private” connection used by one requester/task for a long period of time.
State full (most RPG programs)¶
$ctl = "*sbmjob";
$ipc = "/tmp/sally";
-- or --
$ipc = "/tmp/john";
(1) (2) (3) (4)
Apache FastCGI DB2 (server mode) XMLSERVICE
------- --------------- --------------------- ----------
-->thread--socket->php-cgi--->QSQSRVR(profile sally)---.->XMLSERVICE (sally) <--alive until stopped (or idle timemout)
--->QSQSRVR(profile john)--. |
-->thread--socket->php-cgi--->QSQSRVR(profile fred) | |
--->QSQSRVR(profile sally)---.
--->QSQSRVR(profile fred) |
--->QSQSRVR(profile john)--.--->XMLSERVICE (john) <--alive until stopped (or idle timemout)
Example new Toolkit (state full):
$internalKey = '/tmp/packers';
if ($i5persistentconnect) $conn = db2_pconnect($database,$user,$password);
else $conn = db2_connect($database,$user,$password);
try { $ToolkitServiceObj = ToolkitService::getInstance($conn); }
catch (Exception $e) { die($e->getMessage()); }
$ToolkitServiceObj->setToolkitServiceParams(array(
'InternalKey'=>$internalKey, // *** RIGHT HERE internalKey/IPC
// *** run state full
// use SBMJOB command run in new job
// PHP can call again, again, again
// with /tmp/packers and get
// same job every time
// same library list (*LIBL)
// same PGMs with open files, etc.
// exactly like 5250 sign-on screen
'plug'=>"iPLUG32K")); // max size data i/o (iPLUG4K,32K,65K.512K,1M,5M,10M,15M)
// state full - MUST do this ONCE ONLY after start/sbmjob of XMLSERVICE job
// then forget about it (unless you choose to change libl)
$ToolkitServiceObj->CLCommand("CHGLIBL LIBL(FREDFLIN WILMAFLIN) CURLIB(FREDFLIN)");
/* Do not use the disconnect() function for "state full" connection */
/* NEVER EVER USE THIS ... $ToolkitServiceObj->disconnect(); */
/* Why? *immed kill of job, not nice or sync, just kill */
/* Use idle timeout for "state full" / "private" connections */
State full: If you choose $ctl="\*sbmjob"
+ $ipc="/tmp/packers"
, you will run in a separate job past
the calling DB2 connection (child job of QSQSRVR). This $ctl/$ipc combination will allow you to return to the
same XMLSERVICE job from any connection to the machine, therefore considered “state full” and any called program
can keep open files, transactions, etc. (just like a real RPG 5250 program does mate).
- $ipc=’/tmp/anything’ can be any unique/accessible directory you want to route you back to same XMLSERVICE job (*sbmjob), but usually anchored in /tmp directory because xmlservice will try to create it if missing.
- Technically $ipc=”/tmp/packers” is a unique IFS machine location in posix function ftok(‘/tmp/packers’) which presents a unique numeric key representing /tmp/packers that is used for XMLSERVICE shared memory and semaphores creation/attach (XMLSERVICE uses shared memory/semaphores for communication).
- Shared memory + semaphore locking is only required for state full connections ($ctl=”*sbmjob” + $ipc=”/tmp/packers”), where each sally XMLSERVICE semaphore “front door lock” will allow only one sally client to chat with a XMLSERVICE job, the other sally requesters will wait until they are invited to chat (just like the dentist office).
- Security is managed through IFS shared memory / semaphores access control just like any other IFS file, so once profile sally owns an active XMLSERVICE ctl+ipc then no other profile can attach to the active XMLSERVICE job … well … except for high authority profiles like *SECOFR (of course).
- With version 1.6.6 state full XMLSERVICE connections are ended via configurable idle timeout $ctl .= ” *idle(3000)”, you may keep the jobs alive forever using $ctl .= ” *idle(0)” to match the original version behavior. There are other options for client wait $clt .= ” *wait(30)” and waiting for called program to return $ctl .= ” *call(200)” and various actions that can be taken for each wait/timer (busy,kill,etc.).
- In this example we have been using one sally client/server $ipc=”/tmp/packers”, you can of course have many different sally client/server ($ipc=”/tmp/packers”, $ipc=”/tmp/vikings”, $ipc=”/tmp/bears”, etc.) and each of these sally ipcs may have many clients chatting with each sally server ipc … sort of a sally work load balancing dream situation where we can clone a new sally ipc server for each task at hand.
- You only need to set the LIBL once in state full (unless you want to change LIBL for some reason).
Toolkit State full (with IPC) - avoid start up/shut down XMLSERVICE (NOT toolkit default)
- avoid using toolkit disconnect ($ctl=”*immed”) to leave XMLSERVICE up and running (will timeout shut down if idle for 1/2 hour)
- choose a the minimum plug size need for the task to avoid send/receive extra blanks
- TURN DEBUG and LOGS off in toolkit to avoid IFS file write (takes forever in computer timings)
- db2_pconnect() - persistent or “shared” connection with toolkit avoids acquire/release QSQ jobs (NOT toolkit default)
XMLSERVICE adopt authority issues¶
When using ctl+ipc “state full” jobs is a generally bad idea to “adopt authority” as originating profile sally will lose all access … and … in fact ipc may become unreachable causing a orphan XMLSERVICE (client is still sally, ipc is still sally’s, but adopt xmlservice server becomes fred).
Two choices:
- If you MUST “adopt authority” do it in a stateless job (*here), where full connection processing may undo “left over switch profile” potential damage on the way out of XMLSERVICE script. This option should always work.
- Be very careful to return back to sally profile EACH TIME leaving xmlservice sending data back to the waiting sally client … sort of good manners talk to sally client as sally server (adopted fred can speak/do only when asked, then go away)
Note: We are thinking about forcing “switch back to originating profile on the way back” within XMLSERVICE code, but have not yet understood what that means to PHP wrappers like CW, so the mission is at the moment in your called program and/or PHP wrapper/user code.
3) State full – hybrid “private/persistent” connection¶
These connections are hybrid “private/persistent” connection shared by many requesters, but keep open PGM, files, etc.
Worried about too many IPC’s/XMLSERVICE jobs??
The following gives you a hybrid “private/persistent” connection
- most all the benefits for called RPG (state full, open files, etc.)
- but only $maxpool XMLSERVICE jobs
Try this simple technique for pooled IPC’s/XMLSERVICE jobs $internalKey = '/tmp/packers'.rand(1,$maxpool)
- IF your application set can tolerate multi-client shared access to a pool of persistent/private/semi-stateless connections the random technique should work well.
- However, if you need your client make a exclusive reservation see the next topic
State full – hybrid “private/persistent” connection
$maxpool = 3;
// -- PHP raw ---
$ctl = "*sbmjob";
$ipc = "/tmp/sally".rand(1,$maxpool);
// -- or PHP toolkit --
$internalKey = '/tmp/sally'.rand(1,$maxpool)
(1) (2) (3) (4)
Apache FastCGI DB2 (server mode) XMLSERVICE
------- --------------- --------------------- ----------
-->thread--socket->php-cgi--->QSQSRVR(profile sally)--.
-->thread--socket->php-cgi--->QSQSRVR(profile sally)--|
: |
-->thread--socket->php-cgi--->QSQSRVR(profile sally)--|->XMLSERVICE (/tmp/sally1) <--alive until stopped (or idle timemout)
-->thread--socket->php-cgi--->QSQSRVR(profile sally)--|->XMLSERVICE (/tmp/sally2) <--alive until stopped (or idle timemout)
-->thread--socket->php-cgi--->QSQSRVR(profile sally)--|->XMLSERVICE (/tmp/sally3) <--alive until stopped (or idle timemout)
: |
-->thread--socket->php-cgi--->QSQSRVR(profile sally)--|
-->thread--socket->php-cgi--->QSQSRVR(profile sally)--.
3 XMSLERVICE jobs handle work for all sally clients using the site
Example new Toolkit (hybrid “private/persistent” connection):
$maxpool = 40; // 40 jobs good enough to handle my machine needs
if ($i5persistentconnect) $conn = db2_pconnect($database,$user,$password);
else $conn = db2_connect($database,$user,$password);
try { $ToolkitServiceObj = ToolkitService::getInstance($conn); }
catch (Exception $e) { die($e->getMessage()); }
$internalKey = '/tmp/packers'.rand(1,$maxpool);
$ToolkitServiceObj->setToolkitServiceParams(array(
'InternalKey'=>$internalKey, // *** RIGHT HERE internalKey/IPC $maxpool jobs for service
'plug'=>"iPLUG32K")); // max size data i/o (iPLUG4K,32K,65K.512K,1M,5M,10M,15M)
/* Do not use the disconnect() function for "state full" connection */
/* NEVER EVER USE THIS ... $ToolkitServiceObj->disconnect(); */
/* Why? *immed kill of job, not nice or sync, just kill */
/* Use idle timeout for "state full" / "private" connections */
So simple, why does it work???
- Works much same as Apache FastCGI PHP jobs (even using random), because $maxpool “child XMLSERVICE workers” can be increased to match machine workload (tinker-trial-error) … just like Apache threads … just like PHP children … all the same
- Most web requests are sub-second, so even on routing collision by random it is a short wait.
Could i dedicate different pools to different tasks ???
- Yes, a bag full of really low effort work
(/tmp/packers1-40, /tmp/vikings1-40)
.
- Yes, a bag full of really low effort work
Could i dedicate different user ids to different pools as well as tasks???
- Yes, a bag full of really low effort work
(/tmp/packers1-40, /tmp/vikings1-40)
.
- Yes, a bag full of really low effort work
Can i idle timeout unused XMLSERVICE jobs ??? + Yes of course, toolkit.ini setting or specify manually. + NEVER EVER USE THIS …
$ToolkitServiceObj->disconnect();
Should i use persistent connections??
- db2_pconnect – Yes of course, it will save the time “attaching” a QSQSRVR job
- db2_connect – However, you can use same technique with full open/close (yes it does work, try it)
Can i prestart jobs?
- Yes, but they will start on web demand which i think is much better (just like Apache)
SBMJOB CMD(CALL PGM(ZENDSVR/XMLSERVICE) PARM('/tmp/packers1')) JOBD(ZENDSVR/ZSVR_JOBD) USER(PACKERS) SBMJOB CMD(CALL PGM(ZENDSVR/XMLSERVICE) PARM('/tmp/vikings1')) JOBD(ZENDSVR/ZSVR_JOBD) USER(VIKINGS)
4) State full – reservation hybrid “private/persistent” connection¶
These connections are hybrid “private/persistent” connection exclusively held for a period of time by each requesters, but returned back to pool for re-use.
If your client needs to start/use/stop a reservation hybrid “private/persistent” connection, use the appropriate keyword in your XML sent to XMLSERVICE to gain exclusive rights to the hybrid “private/persistent” connection.
<start>unique-user-key</start> – acquire exclusive IPC if available
<use>unique-user-key</use> – must appear XML every request job held forever until see <stop>
<stop>unique-user-key</stop> – release IPC for any other use
Errors:
<use>no-match-user-key</use> – non-matching key results in error almost instantly (no wait)
busy response (1301060): <error> <errnoxml>1301060</errnoxml> <xmlerrmsg>IPC owner busy</xmlerrmsg> </error>
thoughtful setting server idle timeout can control unwanted reservation hangs due to careless users or errors ** $ctl .= ” *idle(60)” **
hybrid “private/persistent” connection with reservation
$maxpool = 3;
// -- PHP raw ---
$ctl = "*sbmjob";
$ipc = "/tmp/sally".rand(1,$maxpool);
// -- or PHP toolkit (not available yet -- Alan) --
(1) (2) (3) (4)
Apache FastCGI DB2 (server mode) XMLSERVICE
------- --------------- --------------------- ----------
-->thread--socket->php-cgi--->QSQSRVR(profile sally)--.
-->thread--socket->php-cgi--->QSQSRVR(profile sally)--|
: |
-->thread--socket->php-cgi--->QSQSRVR(profile sally)--|->XMLSERVICE (/tmp/sally1) <--alive until stopped (or idle timemout)
<start>unique-user-key</start> <--exclusive reservation until stopped
<use>unique-user-key</use>
<stop>unique-user-key</stop>
-->thread--socket->php-cgi--->QSQSRVR(profile sally)--|->XMLSERVICE (/tmp/sally2) <--alive until stopped (or idle timemout)
<start>unique-user-key</start> <--exclusive reservation until stopped
<use>unique-user-key</use>
<stop>unique-user-key</stop>
-->thread--socket->php-cgi--->QSQSRVR(profile sally)--|->XMLSERVICE (/tmp/sally3) <--alive until stopped (or idle timemout)
<start>unique-user-key</start> <--exclusive reservation until stopped
<use>unique-user-key</use>
<stop>unique-user-key</stop>
: |
-->thread--socket->php-cgi--->QSQSRVR(profile sally)--|
-->thread--socket->php-cgi--->QSQSRVR(profile sally)--.
3 XMSLERVICE jobs handle work for all sally clients using the site However, reservation locks exclusive use until reservation is stopped.
Example new Toolkit (hybrid “private/persistent” connection with reservation):
--- unfortunately reservation is not available in PHP wrapper yet (Alan) ---
--- raw xml pseudo code version of what happens follows start/use/stop ---
-- no time out --
$ctl .= " *idle(0)"
-- request 1 --
<?xml version="1.0"?>
<script>
<start>unique-user-key</start>
</script>
-- request 2 (two minutes later) --
<?xml version="1.0"?>
<script>
<use>unique-user-key</use>
<cmd exec='rexx'>RTVJOBA USRLIBL(?)</cmd>
</script>
-- request 3 (1/2 hour later) --
<?xml version="1.0"?>
<script>
<use>unique-user-key</use>
<pgm name='ZZCALL'>
<parm>
<data type='1A'>a</data>
</parm>
<return>
<data type='10i0'>0</data>
</return>
</pgm>
</script>
-- request n (2 hours later) --
<?xml version="1.0"?>
<script>
<stop>unique-user-key</stop>
</script>
XMLSERVICE IPC¶
Toolkit internalKey, IPC, toolkit persistent connection …¶
With exception of old CW layer, DB2 connection persistent/full makes NO difference, internalKey (IPC) applies ONLY to XMLSERVICE layer to route back to same xmlservice job every time. This routing technique is referred to as a “state full” XMLSERVICE connection.
The naming of the internalKey (IPC) varies across layers PHP Toolkit, but it means the same thing.
- New PHP Toolkit - refers to internalKey (‘InternalKey’=>”/tmp/packers”)
- Note: works with both persistent and non-persistent connections (db2_pconnect or db2_connect, odbc_pconnect or odbc_connect)
- XMLSERVICE RAW - refers to IPC ($ipc=”/tmp/packers”)
- Note: works with both persistent and non-persistent connections (db2_pconnect or db2_connect, odbc_pconnect or odbc_connect)
- New PHP Toolkit CW Layer - refers to “private connection” and provides APIs to get/set private “key/nbr”
- Note: CW Layer requires “persistent connection” to achieve “private connection”, this old technology is just for compatibly with old toolkit concepts
How IPC/internalKey works?¶
The internalKey (IPC) provided by the user (or PHP wrapper) is simply a unique place in the IFS file system. That is to say that one and only one /tmp/packers lives on the machine (LPAR IBM i instance), therefore it is very handy to hash this location into a key (google ftok), that can be used to create unique purpose semaphores (locks) and shared memory (shared data) on the IBM i.
IFS /tmp/path only provides a unique key (hash key), therefore all manner of IPC-2-xmlservice workload/user balancing could be imagined, in fact you could restrict your site to only the IFS paths pre-created in some lower level directory to avoid any unwanted user ability to start an xmlservice job assuming toolkit wrapper plays along beyond using just /tmp.
When an active xmlservice session is running on /tmp/packers you can see the semaphores and shared memory using the utility ipcs. Please note authorizations ipcs displays are exactly like any other IFS file, including owner access (RW, read/write) and *PUBLIC access (–, none), etc. This is how xmlservice controls authorization front door to a state full XMLSERVICE job allowing only correct/matching profiles to call any specific xmlservice service job (one request at a time).
call qp2term
> ipcs
SHARED MEMORY:
T ID KEY MODE OWNER GROUP
M 2306 0X010404F7 T-RW------- DB2 *NONE <---- /tmp/packers (shared data)
SEMAPHORES:
T ID KEY MODE OWNER GROUP
S 377 0X010404F7 --RW------- DB2 *NONE <---- /tmp/packers (lock one use at time)
PHP program decode hex IPC key¶
Unfortunately ipcs “KEY” column is displayed in hex, so if you want to see what goes with /tmp/packers
you will need to run a little program using $ctl="*session"
.
zzftok2.php:
<?php
require_once('connection.inc');
if ($i5persistentconnect) $conn = db2_pconnect($database,$user,$password);
else $conn = db2_connect($database,$user,$password);
if (!$conn) die("Bad connect: $database,$user");
$stmt = db2_prepare($conn, "call $libxmlservice.iPLUG4K(?,?,?,?)");
if (!$stmt) die("Bad prepare: ".db2_stmt_errormsg());
$ctl = "*session";
$ipc = "/tmp/packers";
$clobIn = "<?xml version='1.0'?>";
$clobOut = "";
$ret=db2_bind_param($stmt, 1, "ipc", DB2_PARAM_IN);
$ret=db2_bind_param($stmt, 2, "ctl", DB2_PARAM_IN);
$ret=db2_bind_param($stmt, 3, "clobIn", DB2_PARAM_IN);
$ret=db2_bind_param($stmt, 4, "clobOut", DB2_PARAM_OUT);
$ret=db2_execute($stmt);
if (!$ret) die("Bad execute: ".db2_stmt_errormsg());
var_dump($clobOut);
?>
> http://myibmi/zzftok2.php
string(70) "<?xml version='1.0'?>
<session key='010404F7'>/tmp/packers</session>
XMLSERVICE/Toolkit idle time out¶
Who is this page for?¶
Instructions designed for IBM i developer learning PHP and XMLSERVICE …
How to make your toolkit jobs time out¶
XMLSERVICE from 1.6.2 onward contains flexible timeout features, including the ability to have a program (RPG, COBOL, CL, whatever) time out if it runs too long. This is useful to end jobs that get hung with a MSGWAIT condition.
The PHP toolkit wrappers currently enable only one of these timeout features: the “idle timeout.” When using a private connection job, also known as jobs that have an IPC or InternalKey, the job can be made to time out when a certain number of seconds of inactivity have elapsed.
Cleanliness vs. performance
Causing jobs to time out quickly will give you a nice, empty, clean-looking ZENDSVR subsystem, but will drag down performance the next time the job is started up. Try to find a balance between cleanliness and performance. If you plan to use the same jobs over and over, you may wish to NOT time out the jobs, either never ending them or ending them *IMMED at night, or some other scheme.
How to choose a timeout value If you have many transient users who will briefly use the site and then not return, you may want a quick timeout (30 seconds?). For users who will return over and over again, you may want a long or nonexistent timeout.
- Idle timeout with the new toolkit API
The “idle timeout” can be set or changed any time in this way:
// let's assume the toolkit connection has been established using getInstance() and is present in the variable $conn:
$idleTimeoutSeconds = 1800; // time out job after 1800 seconds (30 minutes) of inactivity
// a value of 0 means no timeout (infinite wait)
$conn->setOptions(array('idleTimeout' => $idleTimeoutSeconds));
- Idle timeout with the Compatibility Wrapper (CW)
Just as with the old toolkit, when you connect with i5_pconnect(), you can set the timeout interval and the number of seconds to wait. As with the old toolkit, idle timeouts only work with private connections. Use the constant I5_OPTIONS_IDLE_TIMEOUT to provide the number of seconds. Use zero (0) seconds to never time out (the default).
$privateNum = 0; // private conn number of zero causes the CW to generate a number for next time.
$idleTimeoutSeconds = 1800; // time out job after 1800 seconds (30 minutes) of inactivity
// a value of 0 means no timeout (infinite wait)
$options = array(I5_OPTIONS_PRIVATE_CONNECTION => $privateNum,
I5_OPTIONS_IDLE_TIMEOUT => $idleTimeoutSeconds
);
// connect (note: the "p" for persistent is required for CW private connections)
// and specify a private connection and timeout
$conn = i5_pconnect('localhost', 'user', 'pw', $options);
Note: The CW also supports the new API’s “setToolkitServiceParams” technique described above, because the CW uses the new toolkit underneath.
Full support RAW XMLSERVICE Samples¶
Programs with qsysopr message idle timeout may help.
<?php
// see connection.inc param details ...
require_once('connection.inc');
// call IBM i
if ($i5persistentconnect) $conn = db2_pconnect($database,$user,$password);
else $conn = db2_connect($database,$user,$password);
if (!$conn) die("Bad connect: $database,$user");
$stmt = db2_prepare($conn, "call $libxmlservice.iPLUG32K(?,?,?,?)");
if (!$stmt) die("Bad prepare: ".db2_stmt_errormsg());
$ctl .= " *idle(10)";
$clobIn = getxml();
$clobOut = "";
$ret=db2_bind_param($stmt, 1, "ipc", DB2_PARAM_IN);
$ret=db2_bind_param($stmt, 2, "ctl", DB2_PARAM_IN);
$ret=db2_bind_param($stmt, 3, "clobIn", DB2_PARAM_IN);
$ret=db2_bind_param($stmt, 4, "clobOut", DB2_PARAM_OUT);
$ret=db2_execute($stmt);
if (!$ret) die("Bad execute: ".db2_stmt_errormsg());
// -----------------
// output processing
// -----------------
// dump raw XML (easy test debug)
var_dump($clobOut);
// xml check via simplexml vs. expected results
$xmlobj = simplexml_load_string($clobOut);
if (!$xmlobj) die("Bad XML returned");
// good
if (strpos($clobOut,"Pointer not set")<=0) echo "Failure\n";
echo "Success\n";
// *+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
// * zzboom: bad function blow up
// *+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
// P zzboom B export
// D zzboom PI
function getxml() {
$clob = <<<ENDPROC
<?xml version='1.0'?>
<script>
<pgm name='ZZSRV' lib='xyzlibxmlservicexyz' func='ZZBOOM'>
</pgm>
</script>
ENDPROC;
return test_lib_replace($clob);
}
?>
Timeout is a setting control setting with various properties allowed.
*----------------------------------------------------
* -- server time out jobs XMLSERVICE (1.6.2)
* *wait[(seconds[/action])]
* - client side wait for XMLSERVICE call (client side)
* example: $ctl="*wait(10)";
* - default action *wait(60/busy) (see plugconfx)
* *call[(seconds[/action[/duration[/job]]])]
* - client/server side XMLSERVICE call wait (PGM, SRVPGM, PASE, etc)
* example: $ctl="*wait(10) *call(5/busy/client)";
* - default for both client/server is *call(0)
* means wait on call forever (user code flawless),
* but can be overriden client/server/both
* *idle[(seconds[/action[/duration]])]
* - server side XMLSERVICE idle no activity
* example: $ctl="*wait(10/kill) *call(30/kill) *idle(30/perm)";
* - default action *idle(1800/kill) (see plugconfx)
* -- time out parameters
* seconds:
* -1 - current default timer
* 0 - no timer, no timeout, wait forever
* n - idle timer "pop" seconds
* action:
* kill - end job immed
* user - user override signal behaviour (see plugconfx)
* busy - return busy XML (client side)
* busy response (1301050):
* <error>
* <errnoxml>1301050</errnoxml>
* <xmlerrmsg>IPC timeout busy</xmlerrmsg>
* </error>
* duration:
* perm - set and use new defaults all requests
* orig - reset and use original compile defaults (see plugconfx)
* job:
* client - *call action applies client side
* server - *call action applies server side
* -- Notes:
* - default timeout/action provided plugconf.rpgle,
* but each request may override/reset to fit task(s)
* - signal SIGALRM used with this function
* can affect user program calls,
* *call(0) may be used to turn off timer
* during user program calls
* - action 'user' allows for custom signal
* processing in the RPG code (see plugconfx)
* - if duration not specified, attributes
* *wait(),*call(),*idle() are temporary
* for this call only and return to last defaults.
* - if 'job' not specified on *call(),
* attribute settings apply to both sides
* - end job immed kills XMLSERVICE job (server)
* and destroys IPC, so any waiting client is
* released with an IPC missing error.
*----------------------------------------------------
XMLSERVICE/Toolkit debugging and service¶
Who is this page for?¶
Instructions designed for IBM i developer learning PHP and XMLSERVICE …
Debug technique: It’s as easy as 1-2-3-4-5-6-7-8-9 :)¶
- Run the test script that contains control “*debug” and script will “hang” while it waits on #2
$ctl .= " *debug";
- A MSGW inquiry message in DSPMSG QSYSOPR will be generated by the toolkit. Note the job information (number, name, user) provided in the MSGW.
- STRSRVJOB using that job information as parameters.
- STRDBG with the program and library you wish to debug.
- Answer the MSGW. Any answer will do–“G” is fine.
- The RPG program source will appear in debug mode in your terminal, ready to step through, allowing you to inspect variables, etc.
- When done inspecting and stepping, let the RPG program complete (using function keys indicated on screen).
- ENDDBG
- ENDSRVJOB
Other debug options:
Job1 (threaded) Job 2 Job 3 (DB2 userid/password) Job 4 (optional XTOOLKIT job)
(ctl=*debugcgi) (ctl=*debugproc) (ctl=*debug)
browser -> Apache ->XMLCGI (Apache CGI child) -> QSQSRVR (XMLSERVICE *here)
-> QSQSRVR (XMLSERVICE client) -> XTOOLKIT (XMLSERVICE ipc=/tmp/flinstone)
$ctl .= " *debugcgi"; // Job 2 - debug XMLCGI to see REST/HTTP data passed by client (when using REST only)
$ctl .= " *debugproc"; // Job 3 - debug XMLSERVICE "client" to see DB2 passed data (DB2 interface)
$ctl .= " *debug"; // Job 4 - debug XMLSERVICE "server" to see XMLSERVICE calls (DB2 interface)
// Note: when ctl='*here', both XMLSERVICE "client"/"server"
// are in QSQSRVSR job (NO XTOOLKIT job)
// remote: Attaching with LUW drivers changes QSQSRVR ...
// CLIENT (Client Access drivers) <==> QZDAxxxx
// CLIENT (DB2 Connect drivers) <==> QRWxxxx
Working with service provider?¶
Here are a few common things that can provide useful information if working with outside support people.
- easy way 1.7.1 … just send provider XMLSERVLOG/LOG and XMLSERVLOG/DUMP in SAVF
assuming you have a trace of the issue (see xmlservice logging instructions)
- what do you see in error logs for simple tests ???
call qp2term
> tail /usr/local/zendsvr/var/log/php.log
> tail /myasp2/www/zend2/logs/error_log.Q112061800
> tail /myasp2/www/zend2/logs/access_log.Q112061800
- Summarize configuration (below)???
My Example
SYSASP -- ZENDSVR library ... everything as installed SYSASP -- /usr/local/zendsvr ... everything as installed SYSASP -- /tmp ... many Zend "enterprise" components use /tmp MYASP2 -- /myasp2/www/zend2 ... ALL "user data" moved from /www/zendsvr (/conf, /logs, /htdocs) More ... /myasp2/www/zend2 -- NO symbolic links between SYSAPS and MYASP2 for true "independent"ASP. -- config files /myasp2/www/zend2/conf attach: fastcgi.conf attach: httpd.conf
- Around time of simple test failure … do you see job logs?
wrkoutq
If jobs are still active (php-cgi, etc.) ... wrkactjob 5 ZEND2 QTMHHTTP BCI .0 PGM-QZSRHTTP SIGW ZEND2 QTMHHTTP BCI .0 PGM-php-cgi.bi THDW 5 ZEND2 QTMHHTTP BCI .0 PGM-php-cgi.bi TIMW 10. Display job log, if active, on job queue, or pending F10 -- full job log
- Around time of simple test failure … do you see VLOGS???
STRSST 1. Start a service tool 5. Licensed Internal Code log 1. Select entries from the Licensed Internal Code (LIC) log Specify Licensed Internal Code Log Selection Values -- leave as is enter --- 0100A890 i5/OS PASE 4700 0013 06/15/12 09:03:43 7 <--- PASE 0100A891 LIC log interface 0401 0100 06/15/12 09:04:08 1 0100A892 Signals management 4600 0001 06/15/12 09:04:51 255 0100A893 Process management 1300 0001 06/18/12 21:02:06 1Maybe look for PASE, storage management, ASP, so on around “failure time”
Note: You can also dump logs to spool …
When you have no idea (dumping many processes)?¶
Some times you just have no idea what is going on, here is a handy macro to dump a lot of stacks.
STRSST/STRDST 1. Start a service tool 4. Display/Alter/Dump 1. Display/Alter storage ... or option for dump to printer ... 2. Licensed Internal Code (LIC) data 14. Advanced analysis Option Command 1 processinfo In this case dumping all process dealing with keyword "ZEND" appearing in job ... Specify Advanced Analysis Options Output device . . . . . . : Display Type options, press Enter. Command . . . . : PROCESSINFO Options . . . . . -NAMES ZENDNote: Information dumped printer/display is same as paseps macro.
Check active XMLSERVICE job¶
If you are using private connections (InternalKey or $ipc=’/tmp/packers’), the XMLSERVICE job is probably available for examination with wrkactjob.
Work with Active Jobs LP0264D
05/17/12 11:35:12
CPU %: .0 Elapsed time: 00:00:00 Active jobs: 313
Type options, press Enter.
2=Change 3=Hold 4=End 5=Work with 6=Release 7=Display message
8=Work with spooled files 13=Disconnect ...
Current
Opt Subsystem/Job User Type CPU % Function Status
5 XTOOLKIT DB2 BCH .0 PGM-XMLSERVICE SEMW
- Use
option 5=work -> 10. Display job log -> F10=Display detailed messages
to examine joblog on errors …
Display All Messages
System: LP0264D
Job . . : XTOOLKIT User . . : DB2 Number . . . : 435915
>> CALL PGM(XMLSERVICE/XMLSERVICE) PARM('/tmp/packers')
Pointer not set for location referenced.
Application error. MCH3601 unmonitored by ZZSRV at statement 0000000448,
instruction X'0000'.
- Use
option 5=work -> 11. Display call stack -> F5=Refresh
to examine stack during stress tests …
Display Call Stack
System: LP0264D
Job: XTOOLKIT User: DB2 Number: 437582
Thread: 0000000C
Type Program Statement Procedure
1 QCMD QSYS /01C8
XMLSERVICE XMLSERVICE _QRNP_PEP_XMLSERVICE
XMLSERVICE XMLSERVICE 1133 XMLSERVICE
XMLSERVICE XMLSERVICE 4607 RUNSERVER
XMLSERVICE XMLSERVICE 2983 SIGSETTIMEOUT
XMLSERVICE XMLSERVICE 2876 SIGTIMEROFF
QP0SSRV1 QSYS 19 setitimer
QP0SSRV2 QSYS 159 qp0sitimer__F12qp0sitimer_t >
Check the logs¶
Check PHP log for messages¶
On my IBM i machine:
EDTF STMF('/usr/local/zendsvr/var/log/php.log')
-- or --
call qp2term (or ssh myibmi)
> tail /usr/local/zendsvr/var/log/php.log
... stuff
... in /MYASP2/www/zend2/htdocs/hello.php on line 1
On my Linux machine:
$ tail /usr/local/zend/var/log/php.log
[16-May-2012 16:30:12] PHP Warning: db2_close() expects parameter 1 to be resource ...
Check Apache logs for messages¶
error logs for date in question:
EDTF STMF('/myasp2/www/zend2/logs/error_log.Q112051500')
-- or --
call qp2term (or ssh myibmi)
> tail /myasp2/www/zend2/logs/error_log.Q112051500
[Tue May 15 17:10:11 2012] [error] [client 9.5.158.38] CGI PROGRAM /QSYS.LIB/XMLSERVICE.LIB/XMLCGI.PGM RETURNED EXCEPTION ID CEE9901
[Tue May 15 17:10:11 2012] [error] [client 9.5.158.38] SEE JOBLOG FOR JOB 428979/QTMHHTTP /ZEND2
access logs for date in question:
EDTF STMF('/myasp2/www/zend2/logs/access_log.Q112051500')
-- or --
call qp2term (or ssh myibmi)
> tail /myasp2/www/zend2/logs/access_log.Q112051500
9.5.158.38 - - [15/May/2012:17:47:41 -0500] "GET /cgi-bin/xmlcgi.pgm?db2=LP0264D
Check PHP Toolkit logs for messages¶
toolkit.ini logfile
EDTF STMF('/usr/local/zendsvr/share/ToolkitApi/toolkit.log')
-- or --
call qp2term (or ssh myibmi)
> tail /usr/local/zendsvr/share/ToolkitAPI/toolkit.log
15 May 2012 22:53:35.752099 Running stateless; no IPC needed. Service library: ZENDSVR
15 May 2012 22:53:36.588466 i5Error: num=14 cat=9 msg="No more entries." desc="No more entries."
location set in toolkit.ini ...
EDTF STMF('/usr/local/zendsvr/share/ToolkitApi/toolkit.ini')
[log]
; warnings and errors will be written to the logfile.
logfile = "/usr/local/zendsvr/share/ToolkitApi/toolkit.log"
toolkit.ini debugLogFile
EDTF STMF('/usr/local/zendsvr/share/ToolkitApi/debug.log')
-- or --
call qp2term (or ssh myibmi)
> tail /usr/local/zendsvr/share/ToolkitAPI/debug.log
<data type='1A' var='ds1' comment='DSCHARA'><![CDATA[E]]></data>
<data type='1A' var='ds2' comment='DSCHARB'><![CDATA[F]]></data>
location set in toolkit.ini ...
EDTF STMF('/usr/local/zendsvr/share/ToolkitApi/toolkit.ini')
; debug turns PHP toolkit's debug mode on or off (true/false). Default log file: /usr/local/zendsvr/share/ToolkitApi/debug.log
; This log will grow large, so leave this false when you do not need to log everything.
debug = true
debugLogFile = "/usr/local/zendsvr/share/ToolkitApi/debug.log"
PHP and XMLSERVICE bad XML
EDTF STMF('/tmp/bad.xml')
-- or --
> tail /tmp/bad.xml
start
<?xml version="1.0" encoding="ISO-8859-1" ?><script><cmd><success><ܬCDATA�+++ success QSYS/DLTDTAARA DTAARA(XMLSERVICE/BETTYBOOP)||></success></cmd>
Check one level at a time¶
Troubles on your PHP site or installation???
Often times if you take a deep breath, slow down and look at each level of web site components you can find your issue without reaching for the bat phone and calling Zend or IBM. The following set of tests walks up the PHP levels of components to give you confidence you are looking at the correct layer of your issue. Of course after you complete smaller PHP scripts (hello.php, etc.), you can likely use the same step up next level techniques on your sophisticated applications (WorldPeace.php).
Level 0 – PHP Working¶
If you are running on the IBM i machine it is always best to make sure your Apache/PHP setup can do anything.
Note: Your machine may have document root at /www/zendsvr vs. /MYASP2/www/zend2 (out-of-box installed Zend Server).
Test number #0
Install the following simple test in your Document root and see if “Hello World” appears.
/MYASP2/www/zend2/htdocs/hello.php
<?php
echo "Hello world";
?>
Run the test:
call qp2term (or ssh myibmi)
> export PATH=/usr/local/zendsvr/bin:$PATH
> export LIBPATH=/usr/local/zendsvr/lib
> cd /MYASP2/www/zend2/htdocs
> php hello.php
Hello world>
Level 1 – Apache/PHP working¶
If you are running on the IBM i machine it is always best to make sure your Apache/PHP setup can do anything.
Note: Your machine may have document root at /www/zendsvr vs. /MYASP2/www/zend2 (out-of-box installed Zend Server).
Test number #1
Install the following simple test in your Document root and see if “Hello World” appears in your browser.
/MYASP2/www/zend2/htdocs/hello.php
<?php
echo "Hello world";
?>
Ok, next run stress test …
call qp2term
> cd /usr/local/Zend/apache2/bin
> ab -t 25 -c 10 http://myibmi/hello.php
-t 25 -- 25 seconds
-c 10 -- 10 concurrent browsers (simulates ten browsers)
- this test is designed to drive CPU to 100% (a good thing), so don’t panic about CPU
wrkactjob refresh (F10) - if you do not see multiple php-cgi jobs getting CPU re-run tests with more load
call qp2term
> cd /usr/local/Zend/apache2/bin
> ab -t 25 -c 10 http://myibmi/hello.php &; ab -t 25 -c 10 http://myibmi/hello.php &; ab -t 25 -c 10 http://myibmi/hello.php &;
-t 25 -- 25 seconds
-c 10 -- 10 concurrent browsers (simulates ten browsers)
Multiply by 3 Apache ab jobs running background -- 30 concurrent browsers (simulates thirty browsers)
- above using “&;” to fork/exec into background/batch many jobs(s) Apache ab to really hammer your web site
- ab tool is not perfect, so if you start too many of these forked jobs (’&;’) the tool may core dump (die) – i usually can get 3 - 6 jobs working (30-60 browsers)
- if you still do not see a split of CPU between php-cgi jobs you may be missing HTTP PTFS
Level 2 – db2 connection working¶
At this level we want to check our DB2 connections.
Note:
- Your machine may have document root at /www/zendsvr vs. /MYASP2/www/zend2 (out-of-box installed Zend Server).
- This same test can be run 2-tier from Linux/Windows to IBM i using DB2 Connect
Test number #2
Install the following simple test in your Document root and see if “success” appears in your browser.
/MYASP2/www/zend2/htdocs/connection2.inc
<?php
$database = "*LOCAL"; // *LOCAL on IBM i ... LP0264D on Linux
$cwdatabase = "localhost";
$user = "DB2";
$password = "XXXXXXXX";
$libxmlservice = "ZENDSVR"; // ZZCALL (Zend Server)
$i5persistentconnect = false;
?>
/MYASP2/www/zend2/htdocs/xxtoolkit_connect.php
<?php
require_once('connection2.inc');
// flip between persistent and non-persistent connections
for ($i=0;$i<500;$i++) {
for ($i5persistentconnect=1;$i5persistentconnect>-1;$i5persistentconnect--) {
if ($i5persistentconnect) $conn = db2_pconnect($database,$user,$password);
else $conn = db2_connect($database,$user,$password);
if (!$conn) echo "<br>Bad connect: $conn,$database,$user,perm=$i5persistentconnect";
else echo "<br>Good connect: $conn,$database,$user,perm=$i5persistentconnect";
if ($i5persistentconnect) $ok = true;
else $ok = db2_close($conn);
echo ",ok=$ok\n";
}
}
run the test Apache …
Point your browser to php program ...
http://myibmi/xxtoolkit_connect.php
Good connect: Resource id #2,*LOCAL,DB2,perm=1,ok=1
Good connect: Resource id #3,*LOCAL,DB2,perm=0,ok=1
Good connect: Resource id #4,*LOCAL,DB2,perm=1,ok=1
:
run the test 2-tier … I choose to run from command line on my Linux machine, but Linux Apache would also work.
$ which php
/usr/local/zend/bin/php
$ php xxtoolkit_connect.php
<br>Good connect: Resource id #5,LP0264D,DB2,perm=1,ok=1
<br>Good connect: Resource id #6,LP0264D,DB2,perm=0,ok=1
<br>Good connect: Resource id #7,LP0264D,DB2,perm=1,ok=1
:
Level 3 - XMLSERVICE XML interface Working¶
At this level we want to check our PHP raw XML Toolkit built on top of ibm_db2 connections (Level 2).
Note:
- Your machine may have document root at /www/zendsvr vs. /MYASP2/www/zend2 (out-of-box installed Zend Server).
- This same test can be run 2-tier from Linux/Windows to IBM i using DB2 Connect.
- RPG test *PGM ZENDSVR/ZZCALL is included with Zend Server installation, so test will run out-of-box
Test number #3
Install the following simple test in your Document root and see if “success” appears in your browser.
/MYASP2/www/zend2/htdocs/connection2.inc
<?php
$database = "*LOCAL"; // *LOCAL on IBM i ... LP0264D on Linux
$cwdatabase = "localhost";
$user = "DB2";
$password = "XXXXXXXX";
$libxmlservice = "ZENDSVR"; // ZZCALL (Zend Server)
$i5persistentconnect = false;
?>
/MYASP2/www/zend2/htdocs/xxtoolkit_raw.php
<?php
require_once('connection2.inc');
if ($i5persistentconnect) $conn = db2_pconnect($database,$user,$password);
else $conn = db2_connect($database,$user,$password);
if (!$conn) echo "Bad connect: $conn,$database,$user,perm=$i5persistentconnect";
$stmt = db2_prepare($conn, "call XMLSERVICE.iPLUG4K(?,?,?,?)");
$ctl = "*sbmjob"; // *here for no additional private job
$ipc='/tmp/packers';
// $ipc = ""; // *here no need ipc
$clobIn = "<?xml version='1.0'?>
<pgm name='ZZCALL' lib='$libxmlservice'>
<parm io='both'>
<data type='1A'>a</data>
</parm>
<parm io='both'>
<data type='1A'>b</data>
</parm>
<parm io='both'>
<data type='7p4'>11.1111</data>
</parm>
<parm io='both'>
<data type='12p2'>222.22</data>
</parm>
<parm io='both'>
<ds>
<data type='1A'>x</data>
<data type='1A'>y</data>
<data type='7p4'>66.6666</data>
<data type='12p2'>77777.77</data>
</ds>
</parm>
<return>
<data type='10i0'>0</data>
</return>
</pgm>";
$clobOut = "";
$ret=db2_bind_param($stmt, 1, "ipc", DB2_PARAM_IN);
$ret=db2_bind_param($stmt, 2, "ctl", DB2_PARAM_IN);
$ret=db2_bind_param($stmt, 3, "clobIn", DB2_PARAM_IN);
$ret=db2_bind_param($stmt, 4, "clobOut", DB2_PARAM_OUT);
$ret=db2_execute($stmt);
// var_dump($clobOut);
if (strpos($clobOut,"4444444444.44")>0) echo "success";
else echo "fail";
?>
run the test Apache …
Point your browser to php program ...
http://myibmi/xxtoolkit_raw.php
success
run the test 2-tier … I choose to run from command line on my Linux machine, but Linux Apache would also work.
$ which php
/usr/local/zend/bin/php
$ php xxtoolkit_raw.php
success
Ok, next run stress test …
call qp2term
> cd /usr/local/Zend/apache2/bin
> ab -t 25 -c 10 http://lp0264d/xxtoolkit_raw.php
-t 25 -- 25 seconds
-c 10 -- 10 concurrent browsers (simulates ten browsers)
- this test is designed to drive CPU to 100% (a good thing), so don’t panic about CPU
Level 4 – PHP New Toolkit working¶
At this level we want to check our PHP CW Toolkit built on top of raw XML Toolkit (Level 3).
Note:
- Your machine may have document root at /www/zendsvr vs. /MYASP2/www/zend2 (out-of-box installed Zend Server).
- This same test can be run 2-tier from Linux/Windows to IBM i using DB2 Connect.
- RPG test *PGM ZENDSVR/ZZCALL is included with Zend Server installation, so test will run out-of-box
Test number #4
Install the following simple test in your Document root and see if “success” appears in your browser.
/MYASP2/www/zend2/htdocs/connection2.inc
<?php
$database = "*LOCAL"; // *LOCAL on IBM i ... LP0264D on Linux
$cwdatabase = "localhost";
$user = "DB2";
$password = "XXXXXXXX";
$libxmlservice = "ZENDSVR"; // ZZCALL (Zend Server)
$i5persistentconnect = false;
?>
/MYASP2/www/zend2/htdocs/xxtoolkit_new.php
<?php
require_once('connection2.inc');
require_once("ToolkitService.php");
if ($i5persistentconnect) $conn = db2_pconnect($database,$user,$password);
else $conn = db2_connect($database,$user,$password);
if (!$conn) echo "Bad connect: $conn,$database,$user,perm=$i5persistentconnect";
try { $ToolkitServiceObj = ToolkitService::getInstance($conn); }
catch (Exception $e) { die($e->getMessage()); }
$param[] = $ToolkitServiceObj->AddParameterChar ('both', 1, 'INCHARA', 'var1', 'Y');
$param[] = $ToolkitServiceObj->AddParameterChar ('both', 1, 'INCHARB', 'var2', 'Z');
$param[] = $ToolkitServiceObj->AddParameterPackDec('both', 7,4,'INDEC1', 'var3', '001.0001');
$param[] = $ToolkitServiceObj->AddParameterPackDec('both', 12,2,'INDEC2', 'var4', '0000000003.04');
$ds[] = $ToolkitServiceObj->AddParameterChar ('both', 1, 'DSCHARA', 'ds1', 'A');
$ds[] = $ToolkitServiceObj->AddParameterChar ('both', 1, 'DSCHARB', 'ds2', 'B');
$ds[] = $ToolkitServiceObj->AddParameterPackDec('both', 7,4,'DSDEC1', 'ds3', '005.0007');
$ds[] = $ToolkitServiceObj->AddParameterPackDec('both', 12,2,'DSDEC1', 'ds4', '0000000006.08');
$param[] = $ToolkitServiceObj->AddDataStruct($ds);
$clobOut = $ToolkitServiceObj->PgmCall('ZZCALL', $libxmlservice, $param, null, null);
// var_dump($clobOut);
$value = "what is ...".$clobOut["io_param"]["ds4"];
if (strpos($value,"4444444444.44")>-1) echo "success";
else echo "fail";
run the test Apache …
Point your browser to php program ...
http://myibmi/xxtoolkit_new.php
success
run the test 2-tier … I choose to run from command line on my Linux machine, but Linux Apache would also work.
$ which php
/usr/local/zend/bin/php
$ php xxtoolkit_new.php
success
Ok, next run stress test …
call qp2term
> cd /usr/local/Zend/apache2/bin
> ab -t 25 -c 10 http://lp0264d/xxtoolkit_new.php
-t 25 -- 25 seconds
-c 10 -- 10 concurrent browsers (simulates ten browsers)
- this test is designed to drive CPU to 100% (a good thing), so don’t panic about CPU
Level 5 – PHP CW Toolkit working (optional)¶
At this level we want to check our PHP CW Toolkit built on top of new Toolkit (Level 4).
Note:
- Your machine may have document root at /www/zendsvr vs. /MYASP2/www/zend2 (out-of-box installed Zend Server).
- This same test can be run 2-tier from Linux/Windows to IBM i using DB2 Connect.
- RPG test *PGM ZENDSVR/ZZCALL is included with Zend Server installation, so test will run out-of-box
Test number #5
Install the following simple test in your Document root and see if “success” appears in your browser.
/MYASP2/www/zend2/htdocs/connection2.inc
<?php
$database = "*LOCAL"; // *LOCAL on IBM i ... LP0264D on Linux
$cwdatabase = "localhost";
$user = "DB2";
$password = "XXXXXXXX";
$libxmlservice = "ZENDSVR"; // ZZCALL (Zend Server)
$i5persistentconnect = false;
?>
/MYASP2/www/zend2/htdocs/xxtoolkit_cw.php
<?php
require_once('connection2.inc');
require_once('CW/cw.php'); // new toolkit compatibility (Alan)
/* connect */
if ($i5persistentconnect) $conn = i5_pconnect($cwdatabase,$user,$password);
else $conn = i5_connect($cwdatabase,$user,$password);
if (!$conn) echo "Bad connect: $conn,$cwdatabase,$user,perm=$i5persistentconnect";
if (!$conn)
{ $tab = i5_error();
die("fail Connect: ".$tab[2]." "."$tab[3], $tab[0]");
}
/* prepare */
$description =
array
(
// single parms
array
( "Name"=>"INCHARA","IO"=>I5_IN|I5_OUT,"Type"=>I5_TYPE_CHAR,"Length"=>"1"),
array
( "Name"=>"INCHARB","IO"=>I5_IN|I5_OUT,"Type"=>I5_TYPE_CHAR,"Length"=>"1"),
array
( "Name"=>"INDEC1","IO"=>I5_IN|I5_OUT,"Type"=>I5_TYPE_PACKED,"Length"=>"7.4"),
array
( "Name"=>"INDEC2","IO"=>I5_IN|I5_OUT,"Type"=>I5_TYPE_PACKED,"Length"=>"12.2"),
// structure parm
array
( "DSName"=>"INDS1",
"Count"=>1,
"DSParm"=>
array
(
array
( "Name"=>"DSCHARA","IO"=>I5_IN|I5_OUT,"Type"=>I5_TYPE_CHAR,"Length"=>"1"),
array
( "Name"=>"DSCHARB","IO"=>I5_IN|I5_OUT,"Type"=>I5_TYPE_CHAR,"Length"=>"1"),
array
( "Name"=>"DSDEC1","IO"=>I5_IN|I5_OUT,"Type"=>I5_TYPE_PACKED,"Length"=>"7.4"),
array
( "Name"=>"DSDEC2","IO"=>I5_IN|I5_OUT,"Type"=>I5_TYPE_PACKED,"Length"=>"12.2"),
)
)
);
$pgm = i5_program_prepare("$libxmlservice/ZZCALL", $description);
if (!$pgm)
{ $tab = i5_error();
die("fail Prepare: ".$tab[2]." "."$tab[3], $tab[0]");
}
// *** parameter list allocation
$list=
array
(
"DSCHARA"=>"x",
"DSCHARB"=>"y",
"DSDEC1"=>66.6666,
"DSDEC2"=>77777.77,
);
// *** parameter values passed to procedure
$in =
array
(
"INCHARA"=>"a",
"INCHARB"=>"b",
"INDEC1"=>11.1111,
"INDEC2"=>222.22,
"INDS1"=>$list,
);
// *** name of variables created for out parameters
$out =
array
(
"INCHARA"=>"INCHARA",
"INCHARB"=>"INCHARB",
"INDEC1"=>"INDEC1",
"INDEC2"=>"INDEC2",
"INDS1"=>"INDS1",
);
$rc=i5_program_call($pgm, $in, $out);
if ($rc != false)
{
if ($INCHARA != 'C') die("fail C == $INCHARA\n");
if ($INCHARB != 'D') die("fail D == $INCHARB\n");
if ($INDEC1 != 321.1234) die("fail 321.1234 == $INDEC1\n");
if ($INDEC2 != 1234567890.12) die("fail 1234567890.12 = $INDEC2\n");
if ($INDS1["DSCHARA"] != 'E'
|| $INDS1["DSCHARB"] != 'F'
|| $INDS1["DSDEC1"] != 333.333
|| $INDS1["DSDEC2"] != 4444444444.44)
{
var_dump($INDS1);
die("fail DS not correct\n");
}
}
else
{ $tab = i5_error();
die("fail Call: ".$tab[2]." "."$tab[3], $tab[0]");
}
// good
echo "success";
?>
run the test Apache …
Point your browser to php program ...
http://myibmi/xxtoolkit_cw.php
success
run the test 2-tier … I choose to run from command line on my Linux machine, but Linux Apache would also work.
$ which php
/usr/local/zend/bin/php
$ php xxtoolkit_cw.php
success
Ok, next run stress test …
call qp2term
> cd /usr/local/Zend/apache2/bin
> ab -t 25 -c 10 http://lp0264d/xxtoolkit_cw.php
-t 25 -- 25 seconds
-c 10 -- 10 concurrent browsers (simulates ten browsers)
- this test is designed to drive CPU to 100% (a good thing), so don’t panic about CPU
Apache ab sample running¶
Apache ab web site stress tests are performed from the 5250 command line (call qp2term) or ssh myibmi using PASE.
- You can run stress tests from 2-tier Linux/Windows using Apache ab tool, but i am using Apchae ab from PASE.
- Apache ab tool is not perfect, but if you use relatively “sane” number of browsers like -c 10 it will work.
- Apache ab test is designed to drive CPU to 100% (a good thing), so don’t panic about CPU
Example run my machine …
> ab -t 25 -c 10 http://lp0264d/hello.php
This is ApacheBench, Version 2.0.40-dev <$Revision: 1.146 $> apache-2.0
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Copyright 2006 The Apache Software Foundation, http://www.apache.org/
Benchmarking lp0264d (be patient)
Completed 5000 requests
Completed 10000 requests
Completed 15000 requests
Completed 20000 requests
Finished 20325 requests
Server Software: Apache
Server Hostname: lp0264d
Server Port: 80
Document Path: /hello.php
Document Length: 11 bytes
Concurrency Level: 10
Time taken for tests: 25.5119 seconds
Complete requests: 20325
Failed requests: 0
Write errors: 0
Total transferred: 3394275 bytes
HTML transferred: 223575 bytes
Requests per second: 812.83 [#/sec] (mean) <--- 800 hits/second
Time per request: 12.303 [ms] (mean)
Time per request: 1.230 [ms] (mean, across all concurrent requests)
Transfer rate: 132.53 [Kbytes/sec] received
Connection Times (ms)
min mean[+/-sd] median max
Connect: 0 0 1.1 0 30
Processing: 2 11 7.2 10 322
Waiting: 2 10 7.2 10 322
Total: 2 11 7.3 11 322
Percentage of the requests served within a certain time (ms)
50% 11
66% 12
75% 14
80% 15
90% 17
95% 19
98% 21
99% 23
100% 322 (longest request)
>
wrkactjob – during Apache ab test you can use refresh on wrkactjob scree to see the php-cgi jobs working
- you should expect to see multiple php-cgi jobs getting some CPU time as you refresh with F10
Work with Active Jobs LP0264D
05/16/12 13:37:25
CPU %: 100.0 Elapsed time: 00:00:00 Active jobs: 295
Type options, press Enter.
2=Change 3=Hold 4=End 5=Work with 6=Release 7=Display message
8=Work with spooled files 13=Disconnect ...
Current
Opt Subsystem/Job User Type CPU % Function Status
ZEND2 QTMHHTTP BCI .0 PGM-zfcgi SELW
ZEND2 QTMHHTTP BCI .0 PGM-php-cgi.bi THDW
ZEND2 QTMHHTTP BCI .0 PGM-php-cgi.bi THDW
ZEND2 QTMHHTTP BCI 2.8 PGM-php-cgi.bi TIMA
ZEND2 QTMHHTTP BCI 1.8 PGM-php-cgi.bi TIMA
ZEND2 QTMHHTTP BCI 2.3 PGM-php-cgi.bi TIMA
ZEND2 QTMHHTTP BCI 2.8 PGM-php-cgi.bi TIMA
ZEND2 QTMHHTTP BCI 1.8 PGM-php-cgi.bi RUN
ZEND2 QTMHHTTP BCI 1.4 PGM-php-cgi.bi TIMA
Stop XMLSERVICE for debugger attach¶
WRKACTJOB find XMLSERVICE private mode only¶
IF you are running a private connection (ipc=’/tmp/packers’), you can simply use WRKACTJOB and locate the XMLSERVICE job and attach debugger. However the next qsysopr message option is also available.
XMLSERVICE QSYSOPR message stop anytime¶
At times it is useful to stop the XMLSERVICE job to connect a debugger to examine an issue, especially if you are running stateless in the QSQSRVR job.
I often find the simple trick below to be very useful.
The trick …
At this time the PHP wrappers have not implemented a stop for debugger interface (cough … Alan),
but XMLSERVICE has control *debug ability already available. In the following example adding
$ctl .= " *debug";
will stop XMLSERVICE job with a inquire message on qsysopr,
simply attach your debugger to job # in msg, set a breakpoint in your module (or in XMLSERVICE module),
and answer qsysopr message with any character to let XMLSERVICE continue to your breakpoint. That is it …
Hint: PHP wrappers are sending XML just like below, so if you have toolkit.ini debug turned on you can often simply cut/paste your XML in debug log into the variable $clobIn below.
Test #3 as example …
/MYASP2/www/zend2/htdocs/connection2.inc
<?php
$database = "*LOCAL"; // *LOCAL on IBM i ... LP0264D on Linux
$cwdatabase = "localhost";
$user = "DB2";
$password = "XXXXXXXX";
$libxmlservice = "ZENDSVR"; // ZZCALL (Zend Server)
$i5persistentconnect = false;
?>
/MYASP2/www/zend2/htdocs/xxtoolkit_raw.php
<?php
require_once('connection2.inc');
if ($i5persistentconnect) $conn = db2_pconnect($database,$user,$password);
else $conn = db2_connect($database,$user,$password);
if (!$conn) echo "Bad connect: $conn,$database,$user,perm=$i5persistentconnect";
$stmt = db2_prepare($conn, "call XMLSERVICE.iPLUG4K(?,?,?,?)");
$ctl = "*sbmjob"; // *here for no additional private job
$ctl .= " *debug"; // THIS WILL STOP XMLSERVICE JOB MSG TO QSYSOPR (server side)
// $ctl .= " *debugproc"; // THIS WILL STOP XMLSERVICE/QSQSRVR JOB MSG TO QSYSOPR (client side)
// if running *here either *debug/*debugproc will work
$ipc='/tmp/packers';
// $ipc = ""; // *here no need ipc
$clobIn = "<?xml version='1.0'?>
<pgm name='ZZCALL' lib='$libxmlservice'>
<parm io='both'>
<data type='1A'>a</data>
</parm>
<parm io='both'>
<data type='1A'>b</data>
</parm>
<parm io='both'>
<data type='7p4'>11.1111</data>
</parm>
<parm io='both'>
<data type='12p2'>222.22</data>
</parm>
<parm io='both'>
<ds>
<data type='1A'>x</data>
<data type='1A'>y</data>
<data type='7p4'>66.6666</data>
<data type='12p2'>77777.77</data>
</ds>
</parm>
<return>
<data type='10i0'>0</data>
</return>
</pgm>";
$clobOut = "";
$ret=db2_bind_param($stmt, 1, "ipc", DB2_PARAM_IN);
$ret=db2_bind_param($stmt, 2, "ctl", DB2_PARAM_IN);
$ret=db2_bind_param($stmt, 3, "clobIn", DB2_PARAM_IN);
$ret=db2_bind_param($stmt, 4, "clobOut", DB2_PARAM_OUT);
$ret=db2_execute($stmt);
// var_dump($clobOut);
if (strpos($clobOut,"4444444444.44")>0) echo "success";
else echo "fail";
?>
Hint: If $clobIn PHP XML single quote / double quote issues are driving you nuts try the following technique.
BTW – For the enterprising do-it-yourself PHP builder you can see how very easy it would be to make a custom
call *PGM API with parameter substitution using str_replace() function similar to test_lib_replace(),
something like function ZZCALL($INCHARA,$INCHARB,$INDEC1,$INDEC2,$INDS1)
$xml = <<<ENDPROC
<?xml version='1.0'?>
<script>
<pgm name='ZZCALL' lib='xyzlibxmlservicexyz'>
<parm io='both'>
<data type='1A' var='INCHARA'>a</data>
</parm>
<parm io='both'>
<data type='1A' var='INCHARB'>b</data>
</parm>
<parm io='both'>
<data type='7p4' var='INDEC1'>11.1111</data>
</parm>
<parm io='both'>
<data type='12p2' var='INDEC2'>222.22</data>
</parm>
<parm io='both'>
<ds>
<data type='1A' var='INDS1.DSCHARA'>x</data>
<data type='1A' var='INDS1.DSCHARB'>y</data>
<data type='7p4' var='INDS1.DSDEC1'>66.6666</data>
<data type='12p2' var='INDS1.DSDEC2'>77777.77</data>
</ds>
</parm>
<return>
<data type='10i0'>0</data>
</return>
</pgm>
</script>
ENDPROC;
$clobIn = test_lib_replace($xml);
// xml common text replacement
function test_lib_replace($xml) {
global $libxmlservice, $iOPM;
if (!$iOPM) {
$was = array("xyzlibxmlservicexyz");
$now = array("$libxmlservice");
}
else {
$was = array("xyzlibxmlservicexyz","<pgm");
$now = array("$libxmlservice","<pgm mode='opm'");
}
$out = str_replace($was,$now,$xml);
return $out;
}
XMLSERVICE can be stopped …¶
Debug technique:¶
It’s as easy as 1-2-3-4-5-6-7-8-9-10 :)
- Add the following line to your PHP script before the program call to be debugged. $toolkitConn should be your toolkit connection object.
``$toolkitConn->setOptions(array('customControl'=>'*debug')).``
Run your script.
The script will "hang" while it waits on #2 below...
(move to green screen 5250 for steps 2-10)
- A MSGW inquiry message in DSPMSG QSYSOPR will be generated by the toolkit.
- Note the job information (number, name, user) provided in the MSGW.
- STRSRVJOB using that job information as parameters.
- STRDBG with the program and library you wish to debug.
- Answer the MSGW. Any answer will do–“G” is fine.
- The RPG program source will appear in debug mode in your terminal, ready to step through, allowing you to inspect variables, etc.
- When done inspecting and stepping, let the RPG program complete (using function keys indicated on screen).
- ENDDBG
- ENDSRVJOB
XMLSERVICE/Toolkit Date, Time, Timestamp¶
Date, Time, Timestamp - what do?¶
Time and date are easy, correct size character buffer and pass any format you want. Essentially xmlservice/toolkit works without formal knowledge of date, time, timestamp formats, simply passing another set of characters in memory for a parameter (pass-by-reference mostly), called program most likely throws exception when format incompatible (see with any MI debugger).
1) New PHP Toolkit Samples¶
ZZDATE (D datfmt(*iso))¶
<?php
require_once('connection.inc');
// assume /usr/local/zendsvr/share/ToolkitAPI
require_once("ToolkitService.php");
// new toolkit
try { $ToolkitServiceObj = ToolkitService::getInstance($database, $user, $password); }
catch (Exception $e) { die($e->getMessage()); }
$ToolkitServiceObj->setToolkitServiceParams(
array('InternalKey'=>$internalKey, // route to same XMLSERVICE job /tmp/myjob1
'subsystem'=>"QGPL/QDFTJOBD", // subsystem/jobd to start XMLSERVICE (if not running)
'plug'=>"iPLUG32K")); // max size data i/o (iPLUG4K,32K,65K.512K,1M,5M,10M,15M)
// *+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
// * zzdate: check date parm
// *+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
// P zzdate B export
// D zzdate PI D
// D myDate D datfmt(*iso)
// * vars
// D retDate s D datfmt(*iso)
// /free
// retDate=myDate;
// myDate=d'2007-09-30';
// return retDate;
// /end-free
// P E
$param[] = $ToolkitServiceObj->AddParameterChar ('both', 10, 'ZZDATE', 'myDate', '2009-05-11');
$retrn[] = $ToolkitServiceObj->AddParameterChar ('both', 10, 'ZZDATE', 'retDate', '2002-02-02');
$result = $ToolkitServiceObj->PgmCall('ZZSRV', $libxmlservice, $param, $retrn, array('func'=>'ZZDATE'));
// var_dump($result);
/* in/out param myDate */
$myDate = "XMLSERVICE i/o param myDate: ".$result["io_param"]["myDate"];
echo "$myDate\n";
$expect = '2007-09-30';
if (strpos($myDate,$expect)<1) die("Fail missing $expect\n");
/* return value retDate */
$retDate = "XMLSERVICE return retDate: ".$result["retvals"]["retDate"];
echo "$retDate\n";
$expect = '2009-05-11';
if (strpos($retDate,$expect)<1) die("Fail missing $expect\n");
/* all good */
echo "Success\n";
?>
ZZDATEUSA (D datfmt(*USA))¶
<?php
require_once('connection.inc');
// assume /usr/local/zendsvr/share/ToolkitAPI
require_once("ToolkitService.php");
// new toolkit
try { $ToolkitServiceObj = ToolkitService::getInstance($database, $user, $password); }
catch (Exception $e) { die($e->getMessage()); }
$ToolkitServiceObj->setToolkitServiceParams(
array('InternalKey'=>$internalKey, // route to same XMLSERVICE job /tmp/myjob1
'subsystem'=>"QGPL/QDFTJOBD", // subsystem/jobd to start XMLSERVICE (if not running)
'plug'=>"iPLUG32K")); // max size data i/o (iPLUG4K,32K,65K.512K,1M,5M,10M,15M)
// *+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
// * zzdateUSA: check date parm
// *+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
// P zzdateUSA B export
// D zzdateUSA PI D datfmt(*USA)
// D myDate D datfmt(*USA)
// * vars
// D retDate s D datfmt(*USA)
// /free
// retDate=myDate;
// myDate=d'2007-09-30';
// return retDate;
// /end-free
// P E
$param[] = $ToolkitServiceObj->AddParameterChar ('both', 10, 'ZZDATEUSA', 'myDate', '05/11/2009');
$retrn[] = $ToolkitServiceObj->AddParameterChar ('both', 10, 'ZZDATEUSA', 'retDate', '2002-02-02');
$result = $ToolkitServiceObj->PgmCall('ZZSRV', $libxmlservice, $param, $retrn, array('func'=>'ZZDATEUSA'));
// var_dump($result);
/* in/out param myDate */
$myDate = "XMLSERVICE i/o param myDate: ".$result["io_param"]["myDate"];
echo "$myDate\n";
$expect = '09/30/2007';
if (strpos($myDate,$expect)<1) die("Fail missing $expect\n");
/* return value retDate */
$retDate = "XMLSERVICE return retDate: ".$result["retvals"]["retDate"];
echo "$retDate\n";
$expect = '05/11/2009';
if (strpos($retDate,$expect)<1) die("Fail missing $expect\n");
/* all good */
echo "Success\n";
?>
ZZTIME (T timfmt(*iso))¶
<?php
require_once('connection.inc');
// assume /usr/local/zendsvr/share/ToolkitAPI
require_once("ToolkitService.php");
// new toolkit
try { $ToolkitServiceObj = ToolkitService::getInstance($database, $user, $password); }
catch (Exception $e) { die($e->getMessage()); }
$ToolkitServiceObj->setToolkitServiceParams(
array('InternalKey'=>$internalKey, // route to same XMLSERVICE job /tmp/myjob1
'subsystem'=>"QGPL/QDFTJOBD", // subsystem/jobd to start XMLSERVICE (if not running)
'plug'=>"iPLUG32K")); // max size data i/o (iPLUG4K,32K,65K.512K,1M,5M,10M,15M)
// *+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
// * zztime: check time parm
// *+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
// P zztime B export
// D zztime PI T
// D myTime T timfmt(*iso)
// * vars
// D retTime s T timfmt(*iso)
// /free
// retTime=myTime;
// myTime=t'12.34.56';
// return retTime;
// /end-free
// P E
$param[] = $ToolkitServiceObj->AddParameterChar ('both', 8, 'ZZTIME', 'myTime', '09.45.29');
$retrn[] = $ToolkitServiceObj->AddParameterChar ('both', 8, 'ZZTIME', 'retTime', '02.02.02');
$result = $ToolkitServiceObj->PgmCall('ZZSRV', $libxmlservice, $param, $retrn, array('func'=>'ZZTIME'));
// var_dump($result);
/* in/out param myDate */
$myTime = "XMLSERVICE i/o param myTime: ".$result["io_param"]["myTime"];
echo "$myTime\n";
$expect = '12.34.56';
if (strpos($myTime,$expect)<1) die("Fail missing $expect\n");
/* return value retTime */
$retTime = "XMLSERVICE return retTime: ".$result["retvals"]["retTime"];
echo "$retTime\n";
$expect = '09.45.29';
if (strpos($retTime,$expect)<1) die("Fail missing $expect\n");
/* all good */
echo "Success\n";
?>
ZZTIME (T timfmt(*USA))¶
<?php
require_once('connection.inc');
// assume /usr/local/zendsvr/share/ToolkitAPI
require_once("ToolkitService.php");
// new toolkit
try { $ToolkitServiceObj = ToolkitService::getInstance($database, $user, $password); }
catch (Exception $e) { die($e->getMessage()); }
$ToolkitServiceObj->setToolkitServiceParams(
array('InternalKey'=>$internalKey, // route to same XMLSERVICE job /tmp/myjob1
'subsystem'=>"QGPL/QDFTJOBD", // subsystem/jobd to start XMLSERVICE (if not running)
'plug'=>"iPLUG32K")); // max size data i/o (iPLUG4K,32K,65K.512K,1M,5M,10M,15M)
// *+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
// * zztimeUSA: check time parm
// *+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
// P zztimeUSA B export
// D zztimeUSA PI T timfmt(*USA)
// D myTime T timfmt(*USA)
// * vars
// D retTime s T timfmt(*USA)
// /free
// retTime=myTime;
// myTime=t'12.34.00';
// return retTime;
// /end-free
// P E
$param[] = $ToolkitServiceObj->AddParameterChar ('both', 8, 'ZZTIMEUSA', 'myTime', '09:45 AM');
$retrn[] = $ToolkitServiceObj->AddParameterChar ('both', 8, 'ZZTIMEUSA', 'retTime', '02:02 PM');
$result = $ToolkitServiceObj->PgmCall('ZZSRV', $libxmlservice, $param, $retrn, array('func'=>'ZZTIMEUSA'));
// var_dump($result);
/* in/out param myDate */
$myTime = "XMLSERVICE i/o param myTime: ".$result["io_param"]["myTime"];
echo "$myTime\n";
$expect = '12:34 PM';
if (strpos($myTime,$expect)<1) die("Fail missing $expect\n");
/* return value retTime */
$retTime = "XMLSERVICE return retTime: ".$result["retvals"]["retTime"];
echo "$retTime\n";
$expect = '09:45 AM';
if (strpos($retTime,$expect)<1) die("Fail missing $expect\n");
/* all good */
echo "Success\n";
?>
ZZSTAMP (Z)¶
<?php
require_once('connection.inc');
// assume /usr/local/zendsvr/share/ToolkitAPI
require_once("ToolkitService.php");
// new toolkit
try { $ToolkitServiceObj = ToolkitService::getInstance($database, $user, $password); }
catch (Exception $e) { die($e->getMessage()); }
$ToolkitServiceObj->setToolkitServiceParams(
array('InternalKey'=>$internalKey, // route to same XMLSERVICE job /tmp/myjob1
'subsystem'=>"QGPL/QDFTJOBD", // subsystem/jobd to start XMLSERVICE (if not running)
'plug'=>"iPLUG32K")); // max size data i/o (iPLUG4K,32K,65K.512K,1M,5M,10M,15M)
// *+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
// * zzstamp: check timestamp parm
// *+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
// P zzstamp B export
// D zzstamp PI Z
// D myStamp Z
// * vars
// D retStamp s Z
// /free
// retStamp=myStamp;
// myStamp=z'1960-12-31-12.32.23.000000';
// return retStamp;
// /end-free
// P E
$param[] = $ToolkitServiceObj->AddParameterChar ('both', 26, 'ZZSTAMP', 'myStamp', '2011-12-29-12.45.29.000000');
$retrn[] = $ToolkitServiceObj->AddParameterChar ('both', 26, 'ZZSTAMP', 'retStamp', '2002-02-02-02.02.02.000000');
$result = $ToolkitServiceObj->PgmCall('ZZSRV', $libxmlservice, $param, $retrn, array('func'=>'ZZSTAMP'));
// var_dump($result);
/* in/out param myDate */
$myStamp = "XMLSERVICE i/o param myStamp: ".$result["io_param"]["myStamp"];
echo "$myStamp\n";
$expect = '1960-12-31-12.32.23.000000';
if (strpos($myStamp,$expect)<1) die("Fail missing $expect\n");
/* return value retStamp */
$retStamp = "XMLSERVICE return retStamp: ".$result["retvals"]["retStamp"];
echo "$retStamp\n";
$expect = '2011-12-29-12.45.29.000000';
if (strpos($retStamp,$expect)<1) die("Fail missing $expect\n");
/* all good */
echo "Success\n";
?>
2) XMLSERVICE Raw XML Samples¶
ZZDATE (D datfmt(*iso))¶
<?php
// see connection.inc param details ...
require_once('connection.inc');
// call IBM i
if ($i5persistentconnect) $conn = db2_pconnect($database,$user,$password);
else $conn = db2_connect($database,$user,$password);
if (!$conn) die("Bad connect: $database,$user");
$stmt = db2_prepare($conn, "call $libxmlservice.iPLUG4K(?,?,?,?)");
if (!$stmt) die("Bad prepare: ".db2_stmt_errormsg());
$clobIn = getxml();
$clobOut = "";
$ret=db2_bind_param($stmt, 1, "ipc", DB2_PARAM_IN);
$ret=db2_bind_param($stmt, 2, "ctl", DB2_PARAM_IN);
$ret=db2_bind_param($stmt, 3, "clobIn", DB2_PARAM_IN);
$ret=db2_bind_param($stmt, 4, "clobOut", DB2_PARAM_OUT);
$ret=db2_execute($stmt);
if (!$ret) die("Bad execute: ".db2_stmt_errormsg());
// -----------------
// output processing
// -----------------
// dump raw XML (easy test debug)
var_dump($clobOut);
// xml check via simplexml vs. expected results
$xmlobj = simplexml_load_string($clobOut);
if (!$xmlobj) die("Bad XML returned");
$allpgms = $xmlobj->xpath('/script/pgm');
if (!$allpgms) die("Missing XML pgm info");
// -----------------
// output pgm call
// -----------------
// only one program this XML script
$pgm = $allpgms[0];
$name = $pgm->attributes()->name;
$lib = $pgm->attributes()->lib;
$func = $pgm->attributes()->func;
// pgm parms
$parm = $pgm->xpath('parm');
if (!$parm) die("Fail XML pgm parms missing ($lib/$name.$func)\n");
$var = $parm[0]->data->attributes()->var;
$actual = (string)$parm[0]->data;
$expect='2007-09-30';
if ($actual != $expect) die("parm: $var ($actual not $expect) ($lib/$name.$func)\n");
// pgm data returned
$retn = $pgm->xpath('return');
if (!$retn) die("Fail XML pgm return missing ($lib/$name.$func)\n");
$var = $retn[0]->data->attributes()->var;
$actual = (string)$retn[0]->data;
$expect='2009-05-11';
if ($actual != $expect) die("return: $var ($actual not $expect) ($lib/$name.$func)\n");
// good
echo "Success ($lib/$name.$func)\n";
// *+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
// * zzdate: check date parm
// *+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
// P zzdate B export
// D zzdate PI D
// D myDate D datfmt(*iso)
function getxml() {
$clob = <<<ENDPROC
<?xml version='1.0'?>
<script>
<pgm name='ZZSRV' lib='xyzlibxmlservicexyz' func='ZZDATE'>
<parm io='both'>
<data var='myDate' type='10A'>2009-05-11</data>
</parm>
<return>
<data var='myDateRet' type='10A'>nada</data>
</return>
</pgm>
</script>
ENDPROC;
return test_lib_replace($clob);
}
?>
ZZDATEUSA (D datfmt(*USA))¶
<?php
// see connection.inc param details ...
require_once('connection.inc');
// call IBM i
if ($i5persistentconnect) $conn = db2_pconnect($database,$user,$password);
else $conn = db2_connect($database,$user,$password);
if (!$conn) die("Bad connect: $database,$user");
$stmt = db2_prepare($conn, "call $libxmlservice.iPLUG4K(?,?,?,?)");
if (!$stmt) die("Bad prepare: ".db2_stmt_errormsg());
$clobIn = getxml();
$clobOut = "";
$ret=db2_bind_param($stmt, 1, "ipc", DB2_PARAM_IN);
$ret=db2_bind_param($stmt, 2, "ctl", DB2_PARAM_IN);
$ret=db2_bind_param($stmt, 3, "clobIn", DB2_PARAM_IN);
$ret=db2_bind_param($stmt, 4, "clobOut", DB2_PARAM_OUT);
$ret=db2_execute($stmt);
if (!$ret) die("Bad execute: ".db2_stmt_errormsg());
// -----------------
// output processing
// -----------------
// dump raw XML (easy test debug)
var_dump($clobOut);
// xml check via simplexml vs. expected results
$xmlobj = simplexml_load_string($clobOut);
if (!$xmlobj) die("Bad XML returned");
$allpgms = $xmlobj->xpath('/script/pgm');
if (!$allpgms) die("Missing XML pgm info");
// -----------------
// output pgm call
// -----------------
// only one program this XML script
$pgm = $allpgms[0];
$name = $pgm->attributes()->name;
$lib = $pgm->attributes()->lib;
$func = $pgm->attributes()->func;
// pgm parms
$parm = $pgm->xpath('parm');
if (!$parm) die("Fail XML pgm parms missing ($lib/$name.$func)\n");
$var = $parm[0]->data->attributes()->var;
$actual = (string)$parm[0]->data;
$expect='09/30/2007';
if ($actual != $expect) die("parm: $var ($actual not $expect) ($lib/$name.$func)\n");
// pgm data returned
$retn = $pgm->xpath('return');
if (!$retn) die("Fail XML pgm return missing ($lib/$name.$func)\n");
$var = $retn[0]->data->attributes()->var;
$actual = (string)$retn[0]->data;
$expect='05/11/2009';
if ($actual != $expect) die("return: $var ($actual not $expect) ($lib/$name.$func)\n");
// good
echo "Success ($lib/$name.$func)\n";
// *+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
// * zzdateUSA: check date parm
// *+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
// P zzdateUSA B export
// D zzdateUSA PI D datfmt(*USA)
// D myDate D datfmt(*USA)
function getxml() {
$clob = <<<ENDPROC
<?xml version='1.0'?>
<script>
<pgm name='ZZSRV' lib='xyzlibxmlservicexyz' func='ZZDATEUSA'>
<parm io='both'>
<data var='myDate' type='10A'>05/11/2009</data>
</parm>
<return>
<data var='myDateRet' type='10A'>nada</data>
</return>
</pgm>
</script>
ENDPROC;
return test_lib_replace($clob);
}
?>
ZZTIME (T timfmt(*iso))¶
<?php
// see connection.inc param details ...
require_once('connection.inc');
// call IBM i
if ($i5persistentconnect) $conn = db2_pconnect($database,$user,$password);
else $conn = db2_connect($database,$user,$password);
if (!$conn) die("Bad connect: $database,$user");
$stmt = db2_prepare($conn, "call $libxmlservice.iPLUG4K(?,?,?,?)");
if (!$stmt) die("Bad prepare: ".db2_stmt_errormsg());
$clobIn = getxml();
$clobOut = "";
$ret=db2_bind_param($stmt, 1, "ipc", DB2_PARAM_IN);
$ret=db2_bind_param($stmt, 2, "ctl", DB2_PARAM_IN);
$ret=db2_bind_param($stmt, 3, "clobIn", DB2_PARAM_IN);
$ret=db2_bind_param($stmt, 4, "clobOut", DB2_PARAM_OUT);
$ret=db2_execute($stmt);
if (!$ret) die("Bad execute: ".db2_stmt_errormsg());
// -----------------
// output processing
// -----------------
// dump raw XML (easy test debug)
var_dump($clobOut);
// xml check via simplexml vs. expected results
$xmlobj = simplexml_load_string($clobOut);
if (!$xmlobj) die("Bad XML returned");
$allpgms = $xmlobj->xpath('/script/pgm');
if (!$allpgms) die("Missing XML pgm info");
// -----------------
// output pgm call
// -----------------
// only one program this XML script
$pgm = $allpgms[0];
$name = $pgm->attributes()->name;
$lib = $pgm->attributes()->lib;
$func = $pgm->attributes()->func;
// pgm parms
$parm = $pgm->xpath('parm');
if (!$parm) die("Fail XML pgm parms missing ($lib/$name.$func)\n");
$var = $parm[0]->data->attributes()->var;
$actual = (string)$parm[0]->data;
$expect='12.34.56';
if ($actual != $expect) die("parm: $var ($actual not $expect) ($lib/$name.$func)\n");
// pgm data returned
$retn = $pgm->xpath('return');
if (!$retn) die("Fail XML pgm return missing ($lib/$name.$func)\n");
$var = $retn[0]->data->attributes()->var;
$actual = (string)$retn[0]->data;
$expect='09.45.29';
if ($actual != $expect) die("return: $var ($actual not $expect) ($lib/$name.$func)\n");
// good
echo "Success ($lib/$name.$func)\n";
// *+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
// * zztime: check time parm
// *+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
// P zztime B export
// D zztime PI T
// D myTime T timfmt(*iso)
function getxml() {
$clob = <<<ENDPROC
<?xml version='1.0'?>
<script>
<pgm name='ZZSRV' lib='xyzlibxmlservicexyz' func='ZZTIME'>
<parm io='both'>
<data var='myTime' type='8A'>09.45.29</data>
</parm>
<return>
<data var='myTimeRet' type='8A'>nada</data>
</return>
</pgm>
</script>
ENDPROC;
return test_lib_replace($clob);
}
?>
ZZTIMEUSA (T timfmt(*USA))¶
<?php
// see connection.inc param details ...
require_once('connection.inc');
// call IBM i
if ($i5persistentconnect) $conn = db2_pconnect($database,$user,$password);
else $conn = db2_connect($database,$user,$password);
if (!$conn) die("Bad connect: $database,$user");
$stmt = db2_prepare($conn, "call $libxmlservice.iPLUG4K(?,?,?,?)");
if (!$stmt) die("Bad prepare: ".db2_stmt_errormsg());
$clobIn = getxml();
$clobOut = "";
$ret=db2_bind_param($stmt, 1, "ipc", DB2_PARAM_IN);
$ret=db2_bind_param($stmt, 2, "ctl", DB2_PARAM_IN);
$ret=db2_bind_param($stmt, 3, "clobIn", DB2_PARAM_IN);
$ret=db2_bind_param($stmt, 4, "clobOut", DB2_PARAM_OUT);
$ret=db2_execute($stmt);
if (!$ret) die("Bad execute: ".db2_stmt_errormsg());
// -----------------
// output processing
// -----------------
// dump raw XML (easy test debug)
var_dump($clobOut);
// xml check via simplexml vs. expected results
$xmlobj = simplexml_load_string($clobOut);
if (!$xmlobj) die("Bad XML returned");
$allpgms = $xmlobj->xpath('/script/pgm');
if (!$allpgms) die("Missing XML pgm info");
// -----------------
// output pgm call
// -----------------
// only one program this XML script
$pgm = $allpgms[0];
$name = $pgm->attributes()->name;
$lib = $pgm->attributes()->lib;
$func = $pgm->attributes()->func;
// pgm parms
$parm = $pgm->xpath('parm');
if (!$parm) die("Fail XML pgm parms missing ($lib/$name.$func)\n");
$var = $parm[0]->data->attributes()->var;
$actual = (string)$parm[0]->data;
$expect='12:34 PM';
if ($actual != $expect) die("parm: $var ($actual not $expect) ($lib/$name.$func)\n");
// pgm data returned
$retn = $pgm->xpath('return');
if (!$retn) die("Fail XML pgm return missing ($lib/$name.$func)\n");
$var = $retn[0]->data->attributes()->var;
$actual = (string)$retn[0]->data;
$expect='09:45 AM';
if ($actual != $expect) die("return: $var ($actual not $expect) ($lib/$name.$func)\n");
// good
echo "Success ($lib/$name.$func)\n";
// *+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
// * zztimeUSA: check time parm
// *+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
// P zztimeUSA B export
// D zztimeUSA PI T timfmt(*USA)
// D myTime T timfmt(*USA)
function getxml() {
$clob = <<<ENDPROC
<?xml version='1.0'?>
<script>
<pgm name='ZZSRV' lib='xyzlibxmlservicexyz' func='ZZTIMEUSA'>
<parm io='both'>
<data type='8A'>09:45 AM</data>
</parm>
<return>
<data type='8A'>nada</data>
</return>
</pgm>
</script>
ENDPROC;
return test_lib_replace($clob);
}
?>
ZZSTAMP (Z)¶
<?php
// see connection.inc param details ...
require_once('connection.inc');
// call IBM i
if ($i5persistentconnect) $conn = db2_pconnect($database,$user,$password);
else $conn = db2_connect($database,$user,$password);
if (!$conn) die("Bad connect: $database,$user");
$stmt = db2_prepare($conn, "call $libxmlservice.iPLUG4K(?,?,?,?)");
if (!$stmt) die("Bad prepare: ".db2_stmt_errormsg());
$clobIn = getxml();
$clobOut = "";
$ret=db2_bind_param($stmt, 1, "ipc", DB2_PARAM_IN);
$ret=db2_bind_param($stmt, 2, "ctl", DB2_PARAM_IN);
$ret=db2_bind_param($stmt, 3, "clobIn", DB2_PARAM_IN);
$ret=db2_bind_param($stmt, 4, "clobOut", DB2_PARAM_OUT);
$ret=db2_execute($stmt);
if (!$ret) die("Bad execute: ".db2_stmt_errormsg());
// -----------------
// output processing
// -----------------
// dump raw XML (easy test debug)
var_dump($clobOut);
// xml check via simplexml vs. expected results
$xmlobj = simplexml_load_string($clobOut);
if (!$xmlobj) die("Bad XML returned");
$allpgms = $xmlobj->xpath('/script/pgm');
if (!$allpgms) die("Missing XML pgm info");
// -----------------
// output pgm call
// -----------------
// only one program this XML script
$pgm = $allpgms[0];
$name = $pgm->attributes()->name;
$lib = $pgm->attributes()->lib;
$func = $pgm->attributes()->func;
// pgm parms
$parm = $pgm->xpath('parm');
if (!$parm) die("Fail XML pgm parms missing ($lib/$name.$func)\n");
$var = $parm[0]->data->attributes()->var;
$actual = (string)$parm[0]->data;
$expect='1960-12-31-12.32.23.000000';
if ($actual != $expect) die("parm: $var ($actual not $expect) ($lib/$name.$func)\n");
// pgm data returned
$retn = $pgm->xpath('return');
if (!$retn) die("Fail XML pgm return missing ($lib/$name.$func)\n");
$var = $retn[0]->data->attributes()->var;
$actual = (string)$retn[0]->data;
$expect='2011-12-29-12.45.29.000000';
if ($actual != $expect) die("return: $var ($actual not $expect) ($lib/$name.$func)\n");
// good
echo "Success ($lib/$name.$func)\n";
// *+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
// * zzstamp: check timestamp parm
// *+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
// P zzstamp B export
// D zzstamp PI Z
// D myStamp Z
function getxml() {
$clob = <<<ENDPROC
<?xml version='1.0'?>
<script>
<pgm name='ZZSRV' lib='xyzlibxmlservicexyz' func='ZZSTAMP'>
<parm io='both'>
<data var='myStamp' type='26A'>2011-12-29-12.45.29.000000</data>
</parm>
<return>
<data var='myStampRet' type='26A'>nada</data>
</return>
</pgm>
</script>
ENDPROC;
return test_lib_replace($clob);
}
?>
XMLSERVICE/Toolkit *LIBL¶
change XMLSERVICE 1.7.3+¶
The default setting for SBMJOB of all XMLSERVICE jobs was changed to INLLIBL(*CURRENT) better follow the user profile of QSQSRVR job. If you are having difficulty with private connections libl you may want to try this version.
setting *LIBL - what do i do?¶
Few other topics dealing with web programs cause more frustrations then setting IBM i library list (*LIBL), but if you follow a few rules it all works.
*LIBL conflict between web scripts and IBM i PGMs¶
Any good conflict needs a basic difference in philosophy causing all the trouble.
- ‘’’Stateless’’’ - web scripts typically wish to run stateless (PHP), with no environmental settings (such as *LIBL) set on the server
- ‘’’State Full’’’ - IBM PGMs generally run state full (RPG/Cobol/CLP), meaning need *LIBL to run at all
Rule of toolkit web *LIBL …¶
The following rules apply to all toolkit DB2 connections persistent (db2_pconnect, odbc_pconnect) and non-persistent (db2_connect, odbc_connect).
- ‘’’Stateless’’’ - PHP scripts using Toolkit without internalKey (IPC) MUST set *LIBL on every request before calling PGMs (see specific examples)
- ‘’’State Full’’’ - PHP scripts using Toolkit with internalKey (IPC) set *LIBL only once for life of job before calling PGMs (see specific examples)
1) Specific examples for New PHP Toolkit¶
- ‘’’Stateless’’’ - set *LIBL every single request
// *** stateless occurs when no internalKey (e.g. '/tmp/packers') was specified ***
// *** also when ->setToolkitServiceParams('stateless'=>true)) is called
try { $ToolkitServiceObj = ToolkitService::getInstance($database, $user, $password); }
catch (Exception $e) { die($e->getMessage()); }
$ToolkitServiceObj->setToolkitServiceParams(array(
'plug'=>"iPLUG32K")); // max size data i/o (iPLUG4K,32K,65K.512K,1M,5M,10M,15M)
// stateless - MUST do this every single script after connect/getInstance()
// even if using persistent connections (db2_pconnect, odbc_pconnect)
$ToolkitServiceObj->CLCommand("CHGLIBL LIBL(FREDFLIN WILMAFLIN) CURLIB(FREDFLIN)");
// another option might be to call a setup program that sets *LIBL for you.
- ‘’’State Full’’’ - set *LIBL once and forget it
$internalKey = '/tmp/packers';
try { $ToolkitServiceObj = ToolkitService::getInstance($database, $user, $password); }
catch (Exception $e) { die($e->getMessage()); }
$ToolkitServiceObj->setToolkitServiceParams(array(
'InternalKey'=>$internalKey, // *** RIGHT HERE internalKey/IPC
// *** run state full ...
// use SBMJOB command run in new job
// PHP can call again, again, again
// with /tmp/packers and get ...
// same job every time
// same library list (*LIBL)
// same PGMs with open files, etc.
// ... exactly like 5250 sign-on screen
'plug'=>"iPLUG32K")); // max size data i/o (iPLUG4K,32K,65K.512K,1M,5M,10M,15M)
// state full - MUST do this ONCE ONLY after start/sbmjob of XMLSERVICE job
// then forget about it (unless you choose to change libl) ...
$ToolkitServiceObj->CLCommand("CHGLIBL LIBL(FREDFLIN WILMAFLIN) CURLIB(FREDFLIN)");
- ‘’’persistent connections’’’ - later releases PHP Toolkit allow reused/shared connections with other work, including persistent connections, but internalKey (IPC) rules remain the same
if ($i5persistentconnect) $conn = db2_pconnect($database,$user,$password);
else $conn = db2_connect($database,$user,$password);
try { $ToolkitServiceObj = ToolkitService::getInstance($conn); }
catch (Exception $e) { die($e->getMessage()); }
$internalKey = '/tmp/packers';
$ToolkitServiceObj->setToolkitServiceParams(array(
'InternalKey'=>$internalKey, // *** RIGHT HERE internalKey/IPC
// *** run state full ...
// use SBMJOB command run in new job
// PHP can call again, again, again
// with /tmp/packers and get ...
// same job every time
// same library list (*LIBL)
// same PGMs with open files, etc.
// ... exactly like 5250 sign-on screen
'plug'=>"iPLUG32K")); // max size data i/o (iPLUG4K,32K,65K.512K,1M,5M,10M,15M)
- ‘’’plug size’’’ - later releases PHP Toolkit 1.2.4+ calculates the correct plug name via ‘plugSize’, taking db type (ODBC or DB2) into account.
if ($userPickFast) $conn = db2_pconnect($database,$user,$password);
else $conn = odbc_pconnect($database,$user,$password);
try { $ToolkitServiceObj = ToolkitService::getInstance($conn); }
catch (Exception $e) { die($e->getMessage()); }
$internalKey = '/tmp/packers';
$ToolkitServiceObj->setToolkitServiceParams(array(
'InternalKey'=>$internalKey, // *** RIGHT HERE internalKey/IPC
// *** run state full ...
'plugSize'=>"32K")); // max size data i/o (iPLUG4K,32K,65K.512K,1M,5M,10M,15M)
2) Specific examples for XMLSERVICE¶
- ‘’’Stateless’’’ - set *LIBL every single request
if ($i5persistentconnect) $conn = db2_pconnect($database,$user,$password);
else $conn = db2_connect($database,$user,$password);
if (!$conn) die("Bad connect: $database,$user");
$stmt = db2_prepare($conn, "call $libxmlservice.iPLUG4K(?,?,?,?)");
if (!$stmt) die("Bad prepare: ".db2_stmt_errormsg());
$ipc = ""; // *** RIGHT HERE MISSING internalKey/IPC
$ctl = "*here"; // *** run stateless ...
// here in any available database job
// must set *LIBL evey time
// stateless - MUST do this every single script after connect/getInstance()
// even if using persistent connections (db2_pconnect, odbc_pconnect)
$clobIn =
"<?xml version='1.0'?>
<script>
<cmd>CHGLIBL LIBL(FREDFLIN WILMAFLIN) CURLIB(FREDFLIN)</cmd>
</script>";
$clobOut = "";
$ret=db2_bind_param($stmt, 1, "ipc", DB2_PARAM_IN);
$ret=db2_bind_param($stmt, 2, "ctl", DB2_PARAM_IN);
$ret=db2_bind_param($stmt, 3, "clobIn", DB2_PARAM_IN);
$ret=db2_bind_param($stmt, 4, "clobOut", DB2_PARAM_OUT);
$ret=db2_execute($stmt);
- ‘’’State Full’’’ - set *LIBL once and forget it
if ($i5persistentconnect) $conn = db2_pconnect($database,$user,$password);
else $conn = db2_connect($database,$user,$password);
if (!$conn) die("Bad connect: $database,$user");
$stmt = db2_prepare($conn, "call $libxmlservice.iPLUG4K(?,?,?,?)");
if (!$stmt) die("Bad prepare: ".db2_stmt_errormsg());
$ipc = "/tmp/packers"; // *** RIGHT HERE internalKey/IPC
$ctl = "*sbmjob"; // *** run state full ...
// use SBMJOB command run in new job
// PHP can call again, again, again
// with /tmp/packers and get ...
// same job every time
// same library list (*LIBL)
// same PGMs with open files, etc.
// ... exactly like 5250 sign-on screen
// state full - MUST do this ONCE ONLY after start/sbmjob of XMLSERVICE job
// then forget about it (unless you choose to change libl) ...
$clobIn =
"<?xml version='1.0'?>
<script>
<cmd>CHGLIBL LIBL(FREDFLIN WILMAFLIN) CURLIB(FREDFLIN)</cmd>
</script>";
$clobOut = "";
$ret=db2_bind_param($stmt, 1, "ipc", DB2_PARAM_IN);
$ret=db2_bind_param($stmt, 2, "ctl", DB2_PARAM_IN);
$ret=db2_bind_param($stmt, 3, "clobIn", DB2_PARAM_IN);
$ret=db2_bind_param($stmt, 4, "clobOut", DB2_PARAM_OUT);
$ret=db2_execute($stmt);
3) Specific examples for New PHP Toolkit CW layer¶
- ‘’’Stateless’’’ - set *LIBL every single request, but for CW, specify libraries in reverse order (via ADDLIBLE)
$options = array(I5_OPTIONS_INITLIBL => 'WILMAFLIN FREDFLIN' );
$conn = i5_connect($host, $user, $password, $options);
// or persistent (which is also stateless): still must specify library each time
$conn = i5_pconnect($host, $user, $password, $options);
- ‘’’State Full’’’ - set *LIBL once and forget it
session_start();
// if we previously saved a connection number in PHP session, use it.
// otherwise, use 0 (which means create a new connection)
$conNum = (isset($_SESSION['conectionNum']) ? $_SESSION['conectionNum'] : 0;
// I5_OPTIONS_PRIVATE_CONNECTION: connection is private for the session
// I5_OPTIONS_IDLE_TIMEOUT: After a delay of the specified number of seconds with no activity, the job will end.
$options = array(I5_OPTIONS_PRIVATE_CONNECTION => $conNum,
I5_OPTIONS_IDLE_TIMEOUT => "60");
// connect as a private connection, starting with a persistent conn
$conn = i5_pconnect ($host, $user, $password, $options);
if (!$conn) {
echo "Something went wrong: <PRE>" . print_r(i5_error(), true) . "</PRE>";
$_SESSION['conectionNum'] = 0; // reset number
} else {
// connected successfully
// if original conNum was 0, let's retrieve the new number.
if ($conNum == 0) {
// Session variable was 0: Get connection ID and store it in session variable.
$ret = i5_get_property(I5_PRIVATE_CONNECTION, $conn);
if (!$ret) {
echo "Something went wrong: <PRE>" . print_r(i5_error(), true) . "</PRE>";
} else {
// We have a good new private connection. Store connection ID in session variable
$_SESSION['conectionNum'] = $ret;
// and set library list, too.
i5_command("CHGLIBL LIBL(FREDFLIN WILMAFLIN) CURLIB(FREDFLIN)");
}
}
}
XMLSERVICE/Toolkit Sharing QTEMP¶
Sharing QTEMP - what do?¶
Sharing QTEMP ibm_db2/odbc/pdo_ibm/RPG/xmlservice has a few quirks, but can be “mostly” done if you understand the jobs in play.
The big picture …¶
xmlservice |--LUW/PASE------|-----------------------IBM i----------------------
run type |job 1 (1-2 tier)| job 2 (stateless) job 3 (IPC/private)
-------|----------------|------------------------|-------------------------
stateless | php-client-----|-->QSQSRVR |
| | ->ibm_db2,pdo_ibm,odbc |
| | *iPLUGxxxx-> |
| | ->XMLSERVICE<>RPG |
| | ->XMLSERVICE<>DB2 (xml)|
| | ->QTEMP (yes share) |
IPC/private| php-client-----|--->QSQSRVR |
| | ->ibm_db2,pdo_ibm,odbc|
| | *iPLUGxxxx----------|-->XMLSERVICE<>RPG
| | ->QTEMP (no share) | ->XMLSERVICE<>DB2 (xml)
| | | ->QTEMP (yes share)
Note: iPLUGxxx stored procedures are included with xmlservice download
php-client (job 1) - can be 1-tier (PASE) / 2-tier (Linux/Unix/Windows) and PHP using extension ibm_db2,pdo_ibm,odbc.
ibm_db2,pdo_ibm,odbc (job 2 ONLY) - can ONLY “share” QTEMP when running stateless XMLSERVICE<>RPG mode, therefore no IPC/private.
PHP ibm_db2,pdo_ibm,odbc are shown in stateless column (job 2), so that everyone is visually reminded that IBM i DB2 actions do not really occur in php-client (job 1), but actually run in an “acquired” DB2 pre-started job (ie. 1-tier/2-tier makes no difference).
2-tier - “true” IBM i connection/job maybe QSQ (PASE), QWI (LUW), etc., but picture above remains consistent behavior for “sharing” QTEMP (job 2).
persistent connect - persistent connection (db2_pconnect/odbc_pconnect) or full connection (db2_connect/odbc_connect) makes no difference, big picture remains exactly the same for both connections. Of course you will run faster with persistent connections (up to 30-40%), due to QSQ server job already up and waiting, but you cannot rely on persistent connections to reach same QSQ job each script start / browser click.
- exception - PASE ibm_db2 provides an obscure “in-line” mode only a few people understand where ibm_db2.ini file set to ibm_db2.i5_ignore_userid=1 will run all DB2 work under the default web profile and stateless in PASE php-client (job 1) … but only if pure PASE ibm_db2 site (no PASE odbc/pdo_ibm) … and … well gee … you are an expert if you understand i5_ignore_userid mode and you probably don’t need this documentation.
- Note to future self ibm_db2, etc. … just like xmlservice has idle timeout today (1/2 hour), we really should have a timeout idle for persistent connections across db2.
XMLSERVICE<>RPG (job 2 or job 3) - is toolkit call to RPG program wishing to “share” QTEMP.
Stateless (job 2) - Generally run slower with more CPU challenges, re-started xmlservice running in “acquired” QSQ job under stored procedure call iPLUGxxx, but on return iPLUGxxx/xmlservice need to close up shop and lose all caches (shut down, wake up, shut down, wake up, …).
- YES stateless - ibm_db2/odbc/pdo_ibm (job 2) CAN share QTEMP with XMLSERVICE<>RPG/XMLSERVICE<>DB2 (also job 2).
IPC/private (job 3) - Generally run faster with less CPU challenges, because xmlservice stays running until explicitly killed or idle timeout (1/2 hour default / user controlled).
- NO IPC/private - ibm_db2/odbc/pdo_ibm (job 2) can NOT share QTEMP with XMLSERVICE<>RPG/XMLSERVICE<>DB2 (job 3).
XMLSERVICE<>DB2 (job 2 or job 3) - is NOT yet available in toolkit wrapper (Alan), but RAW xmlservice interface allows xml based DB2 queries and therefore can “share” QTEMP (available today in RAW xmlservice mode).
<?xml version='1.0'?>
<script>
<sql>
<query>
DECLARE GLOBAL TEMPORARY TABLE animalq(
id integer, breed varchar(32), name char(16),
weight decimal(7,2), height numeric(9,2))
ON COMMIT PRESERVE ROWS
</query>
<prepare>
insert into
animalq (id, breed, name, weight, height)
values (?,?,?,?,?)
</prepare>
<execute>
<parm io='in'>1</parm>
<parm io='in'>cat</parm>
<parm io='in'>Pook</parm>
<parm io='in'>3.2</parm>
<parm io='in'>9.56</parm>
</execute>
<execute>
<parm io='in'>2</parm>
<parm io='in'>dog</parm>
<parm io='in'>Peaches</parm>
<parm io='in'>12.3</parm>
<parm io='in'>22.65</parm>
</execute>
<prepare>select * from animalq where WEIGHT > 1.0</prepare>
<execute/>
<describe desc='col'/>
<fetch block='all' desc='on'/>
</sql>
</script>
1) Specific examples for New PHP Toolkit¶
-----------------------------------------
| Browser |
|---------------------------------------|
| Download RPG (1) | Download PHP (2) |
| 1) XMLSERVICE | a) PHP CW Toolkit |
| HTML/XML/REST | b) New PHP Toolkit |<- cw-tk-php-x.x.x.zip (*)
| no PHP |--------------------|
| (xmlcgi.pgm) | c) PHP “Raw XML” |
| (optional) | (ibm_db2, odbc) |
| -----------------------------------|
| 2) XMLSERVICE DB2 stored procedures |
| (iPLUG4K, iPLUG32K, ..., iPLUG15M) |
| 3) XMLSERVICE (xmlservice.pgm) |
| call most anything on IBM i ... |
| (PGM, SRVPGM, PASE, DB2, etc.) |
------------------------------------------
- Stateless - connection (job 1 / job 2)
// *** stateless occurs when no internalKey (e.g. '/tmp/packers') was specified ***
// *** also when ->setToolkitServiceParams('stateless'=>true)) is called
try { $ToolkitServiceObj = ToolkitService::getInstance($database, $user, $password); }
catch (Exception $e) { die($e->getMessage()); }
$ToolkitServiceObj->setToolkitServiceParams(array(
'plug'=>"iPLUG32K")); // max size data i/o (iPLUG4K,32K,65K.512K,1M,5M,10M,15M)
- State Full - connection (job 1 / job 2 / job 3)
$internalKey = '/tmp/packers';
try { $ToolkitServiceObj = ToolkitService::getInstance($database, $user, $password); }
catch (Exception $e) { die($e->getMessage()); }
$ToolkitServiceObj->setToolkitServiceParams(array(
'InternalKey'=>$internalKey, // *** RIGHT HERE internalKey/IPC
// *** run state full ...
// use SBMJOB command run in new job
// PHP can call again, again, again
// with /tmp/packers and get ...
// same job every time
// same library list (*LIBL)
// same PGMs with open files, etc.
// ... exactly like 5250 sign-on screen
'plug'=>"iPLUG32K")); // max size data i/o (iPLUG4K,32K,65K.512K,1M,5M,10M,15M)
- persistent connections - later releases PHP Toolkit allow reused/shared connections with other work, including persistent connections, but internalKey (IPC) rules remain the same (above)
if ($i5persistentconnect) $conn = db2_pconnect($database,$user,$password);
else $conn = db2_connect($database,$user,$password);
try { $ToolkitServiceObj = ToolkitService::getInstance($conn); }
catch (Exception $e) { die($e->getMessage()); }
2) Specific examples for XMLSERVICE¶
-----------------------------------------
| Browser |
|---------------------------------------|
| Download RPG (1) | Download PHP (2) |
| 1) XMLSERVICE | a) PHP CW Toolkit |
| HTML/XML/REST | b) New PHP Toolkit |
| no PHP |--------------------|
| (xmlcgi.pgm) | c) PHP “Raw XML” |<- Zend Server for IBM i or Linux or Windows (*)
| (optional) | (ibm_db2, odbc) |
| -----------------------------------|
| 2) XMLSERVICE DB2 stored procedures |
| (iPLUG4K, iPLUG32K, ..., iPLUG15M) |
| 3) XMLSERVICE (xmlservice.pgm) |
| call most anything on IBM i ... |
| (PGM, SRVPGM, PASE, DB2, etc.) |
------------------------------------------
- Stateless - (job 1 / job 2)
if ($i5persistentconnect) $conn = db2_pconnect($database,$user,$password);
else $conn = db2_connect($database,$user,$password);
if (!$conn) die("Bad connect: $database,$user");
$stmt = db2_prepare($conn, "call $libxmlservice.iPLUG4K(?,?,?,?)");
if (!$stmt) die("Bad prepare: ".db2_stmt_errormsg());
$ipc = ""; // *** RIGHT HERE MISSING internalKey/IPC
$ctl = "*here"; // *** run stateless ...
// here in any available database job
// must set *LIBL evey time
// stateless - MUST do this every single script after connect/getInstance()
// even if using persistent connections (db2_pconnect, odbc_pconnect)
$clobIn =
"<?xml version='1.0'?>
<script>
<cmd>CHGLIBL LIBL(FREDFLIN WILMAFLIN) CURLIB(FREDFLIN)</cmd>
</script>";
$clobOut = "";
$ret=db2_bind_param($stmt, 1, "ipc", DB2_PARAM_IN);
$ret=db2_bind_param($stmt, 2, "ctl", DB2_PARAM_IN);
$ret=db2_bind_param($stmt, 3, "clobIn", DB2_PARAM_IN);
$ret=db2_bind_param($stmt, 4, "clobOut", DB2_PARAM_OUT);
$ret=db2_execute($stmt);
- State Full - (job 1 / job 2 / job 3)
if ($i5persistentconnect) $conn = db2_pconnect($database,$user,$password);
else $conn = db2_connect($database,$user,$password);
if (!$conn) die("Bad connect: $database,$user");
$stmt = db2_prepare($conn, "call $libxmlservice.iPLUG4K(?,?,?,?)");
if (!$stmt) die("Bad prepare: ".db2_stmt_errormsg());
$ipc = "/tmp/packers"; // *** RIGHT HERE internalKey/IPC
$ctl = "*sbmjob"; // *** run state full ...
// use SBMJOB command run in new job
// PHP can call again, again, again
// with /tmp/packers and get ...
// same job every time
// same library list (*LIBL)
// same PGMs with open files, etc.
// ... exactly like 5250 sign-on screen
// state full - MUST do this ONCE ONLY after start/sbmjob of XMLSERVICE job
// then forget about it (unless you choose to change libl) ...
$clobIn =
"<?xml version='1.0'?>
<script>
<cmd>CHGLIBL LIBL(FREDFLIN WILMAFLIN) CURLIB(FREDFLIN)</cmd>
</script>";
$clobOut = "";
$ret=db2_bind_param($stmt, 1, "ipc", DB2_PARAM_IN);
$ret=db2_bind_param($stmt, 2, "ctl", DB2_PARAM_IN);
$ret=db2_bind_param($stmt, 3, "clobIn", DB2_PARAM_IN);
$ret=db2_bind_param($stmt, 4, "clobOut", DB2_PARAM_OUT);
$ret=db2_execute($stmt);
XMLSERVICE/Toolkit Character Varying¶
Character varying - what do?¶
Character varying is easy, just add character varying attribute. Varying character works for parameters, return and data structures (DS).
1) New PHP Toolkit¶
-----------------------------------------
| Browser |
|---------------------------------------|
| Download RPG (1) | Download PHP (2) |
| 1) XMLSERVICE | a) PHP CW Toolkit |
| HTML/XML/REST | b) New PHP Toolkit |<- cw-tk-php-x.x.x.zip (*)
| no PHP |--------------------|
| (xmlcgi.pgm) | c) PHP “Raw XML” |
| (optional) | (ibm_db2, odbc) |
| -----------------------------------|
| 2) XMLSERVICE DB2 stored procedures |
| (iPLUG4K, iPLUG32K, ..., iPLUG15M) |
| 3) XMLSERVICE (xmlservice.pgm) |
| call most anything on IBM i ... |
| (PGM, SRVPGM, PASE, DB2, etc.) |
------------------------------------------
Character varying (S 20A varying)¶
<?php
require_once('connection.inc');
// assume /usr/local/zendsvr/share/ToolkitAPI
require_once("ToolkitService.php");
// new toolkit
try { $ToolkitServiceObj = ToolkitService::getInstance($database, $user, $password); }
catch (Exception $e) { die($e->getMessage()); }
$ToolkitServiceObj->setToolkitServiceParams(
array('InternalKey'=>$internalKey, // route to same XMLSERVICE job /tmp/myjob1
'subsystem'=>"QGPL/QDFTJOBD", // subsystem/jobd to start XMLSERVICE (if not running)
'plug'=>"iPLUG32K")); // max size data i/o (iPLUG4K,32K,65K.512K,1M,5M,10M,15M)
// *+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
// * zzvary: check return varying
// *+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
// P zzvary B export
// D zzvary PI 20A varying
// D myName 10A varying
// * vars
// D tmp S 20A varying
// /free
// tmp = 'my name is ';
// tmp = tmp + myName;
// return tmp;
// /end-free
// P E
$param[] = $ToolkitServiceObj->AddParameterChar('both', 10, 'ZZVARY', 'myVary', 'Ranger', 'on'); // 6th parameter--'on'--is for varying
$retrn[] = $ToolkitServiceObj->AddParameterChar('both', 20, 'ZZVARY', 'retVary', 'Mud', 'on'); // 6th parameter--'on'--is for varying
$result = $ToolkitServiceObj->PgmCall('ZZSRV', $libxmlservice, $param, $retrn, array('func'=>'ZZVARY'));
// var_dump($result);
/* in/out param myDate */
$myVary = "XMLSERVICE i/o param myVary: ".$result["io_param"]["myVary"];
echo "$myVary\n";
$expect = 'Ranger';
if (strpos($myVary,$expect)<1) die("Fail missing $expect\n");
/* return value retVary */
$retVary = "XMLSERVICE return retVary: ".$result["retvals"]["retVary"];
echo "$retVary\n";
$expect = 'my name is Ranger';
if (strpos($retVary,$expect)<1) die("Fail missing $expect\n");
/* all good */
echo "Success\n";
?>
2) XMLSERVICE Raw XML¶
-----------------------------------------
| Browser |
|---------------------------------------|
| Download RPG (1) | Download PHP (2) |
| 1) XMLSERVICE | a) PHP CW Toolkit |
| HTML/XML/REST | b) New PHP Toolkit |
| no PHP |--------------------|
| (xmlcgi.pgm) | c) PHP “Raw XML” |<- Zend Server for IBM i or Linux or Windows (*)
| (optional) | (ibm_db2, odbc) |
| -----------------------------------|
| 2) XMLSERVICE DB2 stored procedures |
| (iPLUG4K, iPLUG32K, ..., iPLUG15M) |
| 3) XMLSERVICE (xmlservice.pgm) |
| call most anything on IBM i ... |
| (PGM, SRVPGM, PASE, DB2, etc.) |
------------------------------------------
Character varying (S 20A varying)¶
<?php
// see connection.inc param details ...
require_once('connection.inc');
// call IBM i
if ($i5persistentconnect) $conn = db2_pconnect($database,$user,$password);
else $conn = db2_connect($database,$user,$password);
if (!$conn) die("Bad connect: $database,$user");
$stmt = db2_prepare($conn, "call $libxmlservice.iPLUG32K(?,?,?,?)");
if (!$stmt) die("Bad prepare: ".db2_stmt_errormsg());
$clobIn = getxml();
$clobOut = "";
$ret=db2_bind_param($stmt, 1, "ipc", DB2_PARAM_IN);
$ret=db2_bind_param($stmt, 2, "ctl", DB2_PARAM_IN);
$ret=db2_bind_param($stmt, 3, "clobIn", DB2_PARAM_IN);
$ret=db2_bind_param($stmt, 4, "clobOut", DB2_PARAM_OUT);
$ret=db2_execute($stmt);
if (!$ret) die("Bad execute: ".db2_stmt_errormsg());
// -----------------
// output processing
// -----------------
// dump raw XML (easy test debug)
var_dump($clobOut);
// xml check via simplexml vs. expected results
$xmlobj = simplexml_load_string($clobOut);
if (!$xmlobj) die("Bad XML returned");
$allpgms = $xmlobj->xpath('/script/pgm');
if (!$allpgms) die("Missing XML pgm info");
// -----------------
// output pgm call
// -----------------
// only one program this XML script
$pgm = $allpgms[0];
$name = $pgm->attributes()->name;
$lib = $pgm->attributes()->lib;
$func = $pgm->attributes()->func;
// pgm parms
$parm = $pgm->xpath('parm');
if ($parm) die("Unexpected XML pgm parms io='in' ($lib/$name.$func)\n");
// pgm data returned
$retn = $pgm->xpath('return');
if (!$retn) die("Fail XML pgm return missing ($lib/$name.$func)\n");
$var = $retn[0]->data->attributes()->var;
$actual = (string)$retn[0]->data;
$expect = 'my name is Ranger';
if ($actual != $expect) die("$var ($actual not $expect) ($lib/$name.$func)\n");
// good
echo "Success ($lib/$name.$func)\n";
// *+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
// * zzvary: check return varying
// *+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
// P zzvary B export
// D zzvary PI 20A varying
// D myName 10A varying
// * vars
// D tmp S 20A varying
// /free
// tmp = 'my name is ';
// tmp = tmp + myName;
// return tmp;
// /end-free
// P E
function getxml() {
$clob = <<<ENDPROC
<?xml version='1.0'?>
<script>
<pgm name='ZZSRV' lib='xyzlibxmlservicexyz' func='ZZVARY'>
<parm comment='search this name' io='in'>
<data var='myName' type='10A' varying='on'>Ranger</data>
</parm>
<return>
<data var='myReturnName' type='20A' varying='on'>Mud</data>
</return>
</pgm>
</script>
ENDPROC;
return test_lib_replace($clob);
}
?>
XMLSERVICE/Toolkit CCSID¶
XMLSERVICE should just work¶
In general if you are using DB2 connections 1/2 tier and settings for your Apache or user profile are valid (not 65535), you will not need any additional CCSID manipulation/configuration.
CCSID - what do?¶
Few other topics dealing with web programs cause more frustrations over IBM i CCSID settings.
CCSID conflict between web scripts (clients) and IBM i PGMs (server)¶
Any good conflict needs a basic difference in philosophy causing all the trouble.
- Client (ASCII) - web scripts typically wish to run ASCII 819/1208 (PHP), code set(s) laptop
- Server (EBCDIC) - IBM i PGMs generally run EBCDIC (RPG/Cobol/CLP), code set(s) main frame
CCSID rule – always a conversion client/server¶
The basic operational code set differences between client (ASCII) / server (EBCDIC) required a conversion for all character data moving between client/server to be of value on either side. Your CCSID setting options are many, experimentation is usually required, but perhaps this web page helps avoid hours of frustrating tinkering attempting to adjust multitude of software “products” where CCSID conversions is possible.
Happens 99 times out of 100 when not working (CCSID 65535)¶
You cannot run IBM i PHP and interact with IBM i DB2, PGM, SRVPGM (xmlservice), when your IBM i machine is default ship setting of 65535. Change the default ship Zend Server setting and you will likely be up and running.
I run 65535 xmlservice on my V6R1 machine (among other machines)
DSPSYSVAL SYSVAL(QCCSID)
Coded character set
identifier . . . . . : 65535 1-65535
Change these files and restart everything (web, db2, xmlservice, tec.)
I. Apache: /www/zendsvr/conf/httpd.conf <– (ILE Apache side)
DefaultFsCCSID 37 ... or 280 (Italian) ... or so on ...
CGIJobCCSID 37 ... or 280 (Italian) ... or so on ...
This often fixes majority of web script issue
II. FastCGI: /www/zendsvr/conf/fastcgi.conf <– THIS file (PASE side)
CCSID=819 and LANG=C,
which works nearly anywhere (UNIX default).
Note:
- This file must be ASCII 819, so careful with editor or will not work (EDTF especially).
- CCSID=1208 can be a problem for PHP extensions, so most configurations use CCSID=819 and take specific script action to encode/decode 1208 data (see xmlservice hex/before/after)
III. User profile – DB2 connections
CHGUSRPRF USRPRF(IAM37) CCSID(37)
This fixes most DB2 CCSID client/server problems both 1-tier and 2-tier
– or (skip I. - III.)–
IV. change system ccsid for everything on the machine
CHGSYSVAL SYSVAL(QCCSID) VALUE(37)
Note:
- some legacy applications may not work, should work but…, so check things out
- 65535 means HEX (or binary), which also means no conversion and 65535 is essentially doomsday for most any application trying to work between PASE and DB2 (most any client and DB2).
client (ASCII 819) + server (EBCDIC 65535) = junk-you-can't-read-or-use = xmlservice fails
Now the rest of the story 65535 problem is common, so IBM i DB2 folks force a reasonable job CCSID for SOME remote connections (based on primary language generally), but NOT all connections (PASE and others), therefore observationally you may see some applications work (client access products) and others will not (PHP).
- Specific examples for XMLSERVICE
- Command user ccsid override
if ($i5persistentconnect) $conn = db2_pconnect($database,$user,$password); else $conn = db2_connect($database,$user,$password); if (!$conn) die("Bad connect: $database,$user"); $stmt = db2_prepare($conn, "call $libxmlservice.iPLUG4K(?,?,?,?)"); if (!$stmt) die("Bad prepare: ".db2_stmt_errormsg()); $ipc = "/tmp/packers"; // *** RIGHT HERE internalKey/IPC $ctl = "*sbmjob"; // *** run state full $clob = "<?xml version='1.0'?>\n"; $clob .= "<script>\n"; $clob .= "<cmd exec='rexx' hex='on' before='819/37' after='37/819'>"; $clob .= bin2hex("RTVJOBA USRLIBL(?) SYSLIBL(?)"); $clob .= "</cmd>\n"; $clob .= "</script>\n"; $clobIn = $clob; $clobOut = ""; $ret=db2_bind_param($stmt, 1, "ipc", DB2_PARAM_IN); $ret=db2_bind_param($stmt, 2, "ctl", DB2_PARAM_IN); $ret=db2_bind_param($stmt, 3, "clobIn", DB2_PARAM_IN); $ret=db2_bind_param($stmt, 4, "clobOut", DB2_PARAM_OUT); $ret=db2_execute($stmt); var_dump($clobOut); $xmlobj = simplexml_load_string($clobOut); if (!$xmlobj) die("Bad XML output"); $clobOut = pack("H*",(string)$xmlobj->cmd->hex); var_dump($clobOut); OUTPUT: > php zzhexccsidcmd.php string(527) "<?xml version='1.0'?> <script> <cmd exec='rexx' hex='on' before='819/37' after='37/819'><success>+++ success RTVJOBA USRLIBL(?) SYSLIBL(?)</success><hex>3C726F773E0A3C6461746120646573633D275553524C49424C273E5147504C202020202020205154454D5020202020202051444556454C4F5020202051424C445359532020202051424C44535953523C2F646174613E0A3C2F726F773E0A3C726F773E0A3C6461746120646573633D275359534C49424C273E5153595320202020202020515359533220202020202051484C5053595320202020515553525359533C2F646174613E0A3C2F726F773E0A</hex></cmd> </script>" string(176) "<row> <data desc='USRLIBL'>QGPL QTEMP QDEVELOP QBLDSYS QBLDSYSR</data> </row> <row> <data desc='SYSLIBL'>QSYS QSYS2 QHLPSYS QUSRSYS</data> </row>
- Program ccsid override
if ($i5persistentconnect) $conn = db2_pconnect($database,$user,$password); else $conn = db2_connect($database,$user,$password); if (!$conn) die("Bad connect: $database,$user"); $stmt = db2_prepare($conn, "call $libxmlservice.iPLUG512K(?,?,?,?)"); if (!$stmt) die("Bad prepare: ".db2_stmt_errormsg()); $ipc = "/tmp/packers"; // *** RIGHT HERE internalKey/IPC $ctl = "*sbmjob"; // *** run state full $clob = "<?xml version='1.0'?>\n"; $clob .= "<script>\n"; $clob .= "<pgm name='ZZSRV' lib='XMLSERVICE' func='ZZ200'>\n"; $clob .= "<parm io='both'>\n"; $clob .= "<data type='200A' hex='on' before='819/37' after='37/819'>"; $clob .= bin2hex("Hi there i am ok on return from xmlservice."); $clob .= "</data>\n"; $clob .= "</parm>\n"; $clob .= "</pgm>\n"; $clob .= "</script>\n"; $clobIn = $clob; $clobOut = ""; $ret=db2_bind_param($stmt, 1, "ipc", DB2_PARAM_IN); $ret=db2_bind_param($stmt, 2, "ctl", DB2_PARAM_IN); $ret=db2_bind_param($stmt, 3, "clobIn", DB2_PARAM_IN); $ret=db2_bind_param($stmt, 4, "clobOut", DB2_PARAM_OUT); $ret=db2_execute($stmt); var_dump($clobOut); $xmlobj = simplexml_load_string($clobOut); if (!$xmlobj) die("Bad XML output"); $clobOut = pack("H*",(string)$xmlobj->pgm->parm->data); var_dump($clobOut); OUTPUT: > php zzhexccsidpgm.php string(273) "<?xml version='1.0'?> <script> <pgm name='ZZSRV' lib='XMLSERVICE' func='ZZ200'> <parm io='both'> <data type='200A' hex='on' before='819/37' after='37/819'>4869207468657265206920616D206F6B206F6E2072657475726E2066726F6D20786D6C736572766963652E</data> </parm> </pgm> </script>" string(43) "Hi there i am ok on return from xmlservice."
- Specific examples for New PHP Toolkit
- CCSID override - PHP Toolkit/CW
Simple CCSIDs only require setting the CCSID via QCCSID or in Apache. The overrides directly below are intended for for languages with more complex needs such as Hebrew or Japanese, or when individual pieces of data are encoded differently (such as a combination of 819 and 1208).
The easiest way to try these CCSID settings is with three new settings in toolkit.ini:
advanced CCSID options. Use all three options together. ccsidBefore = '819/37' ccsidAfter = '37/819' useHex = trueUncomment the three settings and then adjust the ccsidBefore and ccsidAfter values according to your needs.
Another way to set these global CCSID settings is with the method setToolkitServiceParams(). In your code, after connecting with $conn::getInstance(* etc.), set the parameters with this statement:
$conn->setToolkitServiceParams(array('ccsidBefore'=>'819/37', 'ccsidAfter'=>'37/819', 'useHex'=>true));This technique works identically to changing INI values, except that this coding technique can be re-done over and over with different settings before each program/command call.
These “global” CCSID techniques work with both the new API and the CW, and will convert not only data/commands and command output, but the names of programs, libraries, and functions. You may notice that your data will be converted to hex inside the toolkit and then converted back to readable text by the toolkit.
For more fine-grained control over parameter data–that is, the ability to use a different CCSID conversion for each parameter, if desired–chain several new methods to AddParameterChar() like so: (new API only–not in CW):
$param[] = $conn->AddParameterChar('both', 10,'CODE', 'CODE', $code) ->setParamCcsidBefore('819/37') ->setParamCcsidAfter('37/819') ->setParamUseHex(true);These parameters can also be passed as AddParameterChar() function parameters directly but it’s easier to use the setParam… methods above.
Note:
These advanced CCSID settings do not affect some of the handmade API calls in the CW such as getting object lists. Helping those may be a future enhancement.
If you wish to see how XMLSERVICE implements these overrides, see the following URL, under the heading: “CCSID user override - xmlservice options (hex/before/after)”.
Toolkit go faster¶
Before you start …
This page is about running faster using persistent and private pooled connections workload balancing techniques. This is not a discussion about using QTEMP or *LDA in called RPG programs across browser clicks, that is a different topic entirely.
New version Zend Server include ZRAY¶
Server set up asks if Developer or Production. If Developer, Z-Ray is on by default, but easy to turn off. If Production, Z-Ray is off by default, but easy to turn on. Z-Ray is recommended to be off in Production for performance and security reasons. It can be set up in secured mode in production to only be used on pages deliberately accessed by a developer.
Toolkit connections performance¶
There are many ways to workload balance PHP Toolkit connections:
- slower … public “stateless” job - no ‘InternalKey’ (default)
$ToolkitServiceObj->setToolkitServiceParams( array('stateless'=>true));
- faster … private “state full ” single job - $user=”SALLY” connected to single xmlservice job
// php:db2_connect <- XML IN/OUT -> QSQSRVR:XMLSERVICE <- "/tmp/$user" -> XTOOLKIT:XMLSERVICE
$ToolkitServiceObj->setToolkitServiceParams(array('InternalKey'=>"/tmp/$user"))
- fastest … private “state full” pool jobs - $user=”SALLY”.rand(1,10) connected to 10 random xmlservice jobs
// php:db2_connect :<- XML IN/OUT -> QSQSRVR:XMLSERVICE <- "/tmp/$user1" -> XTOOLKIT:XMLSERVICE(1)
// rand pick server:
// :<- XML IN/OUT -> QSQSRVR:XMLSERVICE <- "/tmp/$user10" -> XTOOLKIT:XMLSERVICE(10)
$ToolkitServiceObj->setToolkitServiceParams(array('InternalKey'=>"/tmp/$user".rand(1,10))
The following is a relative performance guideline:
- slower … public “stateless” job … stateless connection (safe default)
<?php
// job 1 (client) job 2 (server)
// any php-cgi job attach QSQSRVR call XMSLERVICE
// ------------------ ------------------------------
// php:db2_(p)connect <- XML IN/OUT -> QSQSRVR:XMLSERVICE
$extension='ibm_db2';
try { $ToolkitServiceObj = ToolkitService::getInstance($db, $user, $pass, $extension); }
catch (Exception $e) { echo $e->getMessage(), "\n"; exit(); }
$options = array('stateless'=>true,'plugSize'=>'4K');
$ToolkitServiceObj->setToolkitServiceParams($options);
$ToolkitServiceObj->disconnect();
slower … uses db2_connect / odbc_connect (full open/close of QSQSRVR job)
slower … starts/stops xmlservice within QSQSRVR job each script request
What to watch for …
not much, this is a simple full start/stop model (slow, but safe mostly)
faster … private pool jobs … state full connection (private)
<?php
// job 1 (client) job 2 (proxy) job 3..13 (10 servers)
// any php-cgi job QSQSRVR passthru XTOOLKIT(s) ready (always)
// ------------------ ------------------ --------------------------
// php:db2_(p)connect:<- XML IN/OUT -> QSQSRVR:XMLSERVICE <- "/tmp/$user1" -> XTOOLKIT:XMLSERVICE(1)
// :<- XML IN/OUT -> QSQSRVR:XMLSERVICE <- "/tmp/$user2" -> XTOOLKIT:XMLSERVICE(2)
// rand pick a server:
// :<- XML IN/OUT -> QSQSRVR:XMLSERVICE <- "/tmp/$user10" -> XTOOLKIT:XMLSERVICE(10)
$extension='ibm_db2';
try { $ToolkitServiceObj = ToolkitService::getInstance($db, $user, $pass, $extension); }
catch (Exception $e) { echo $e->getMessage(), "\n"; exit(); }
$maxpool = 10; // 10 jobs good enough to handle my machine needs
$internalKey = '/tmp/packers'.rand(1,$maxpool);
$ToolkitServiceObj->setToolkitServiceParams(array('InternalKey'=>$internalKey));
/* Do not use the disconnect() function for "state full" connection */
/* NEVER EVER USE THIS ... $ToolkitServiceObj->disconnect(); */
/* Why? *immed kill of job, not nice or sync, just kill */
slower … uses db2_connect / odbc_connect (full open/close of QSQSRVR job)
fastest … starts another xmlservice /tmp/packers1-to-10 beyond QSQSRVR job (XTOOLKIT PGM-XMLSERVICE), and jobs 1-10 use over and over and over … until killed by $ToolkitServiceObj->disconnect() or by IBM i operator
What to watch for …
Security 1: profile FRED can not attach to profile SALLY XMLSERVICE jobs (SALLY owns /tmp/packers1-10, FRED will have to make his own jobs /tmp/bears1-10)
Co-operate with web site: develop procedures start/stop xmlservice when doing system maintenance (kill xmlservice jobs, etc.)
A live job: QTEMP/*LDA are re-used, therefore your called applications must be ready to handle/clear
fastest … private pool jobs … add persistent db2 connections (db2_pconnect)
<?php
// job 1 (client) job 2 (proxy) job 3..13 (10 servers)
// any php-cgi job QSQSRVR passthru XTOOLKIT(s) ready (always)
// ------------------ ------------------ --------------------------
// php:db2_(p)connect:<- XML IN/OUT -> QSQSRVR:XMLSERVICE <- "/tmp/$user1" -> XTOOLKIT:XMLSERVICE(1)
// :<- XML IN/OUT -> QSQSRVR:XMLSERVICE <- "/tmp/$user2" -> XTOOLKIT:XMLSERVICE(2)
// rand pick a server:
// :<- XML IN/OUT -> QSQSRVR:XMLSERVICE <- "/tmp/$user10" -> XTOOLKIT:XMLSERVICE(10)
require_once("ToolkitService.php");
$i5persistentconnect = true;
if ($i5persistentconnect) $conn = db2_pconnect($database,$user,$password);
else $conn = db2_connect($database,$user,$password);
if (!$conn) echo "Bad connect: $conn,$database,$user,perm=$i5persistentconnect";
try { $ToolkitServiceObj = ToolkitService::getInstance($conn); }
catch (Exception $e) { die($e->getMessage()); }
$maxpool = 10; // 10 jobs good enough to handle my machine needs
$ToolkitServiceObj->setToolkitServiceParams(array('InternalKey'=>'/tmp/packers'.rand(1,$maxpool),'plug'=>'iPLUG32K'));
fastest … uses db2_pconnect / odbc_pconnect (persistent QSQSRVR job stays alive “forever”)
fastest … starts another xmlservice /tmp/packers1-to-10 beyond QSQSRVR job (XTOOLKIT PGM-XMLSERVICE), and jobs 1-10 use over and over and over … until killed by $ToolkitServiceObj->disconnect() or by IBM i operator
What to watch for …
Security 1: profile FRED can not attach to profile SALLY XMLSERVICE jobs (SALLY owns /tmp/packers1-10, FRED will have to make his own jobs /tmp/bears1-10)
Security 2: profile FRED owns a db2_pconnect(tion), and SALLY owns a db2_pconnect(ion), XMLSERVICE connect InternalKey profile must match (db2_pconnect(“SALLY”) owns /tmp/packers1-10, db2_pconnect(“FRED”) owns /tmp/bears1-10)
Co-operate with web site: develop procedures start/stop xmlservice when doing system maintenance (kill xmlservice jobs, etc.)
A live job: QTEMP/*LDA are re-used, therefore your called applications must be ready to handle/clear
Toolkit operations performance¶
Always use PgmCall API for speed including data area, job attributes, etc. (V6+ also call CL and OPM *PGM with PgmCall), most command functions will run significantly slower.
- slower … PASE sh utilities (system wrkactjob, ls, ps, etc.)
$ToolkitServiceObj->CLInteractiveCommand
- slightly faster … CMDS that return data (RTVJOBA, etc.)
$ToolkitServiceObj->CLCommandWithOutput
- faster … CMDS that do not return data (ADDLIBLE, etc.)
$ToolkitServiceObj->CLCommand
- fastest … calling PGMs/SRVPGMs (RPG, CLP, Cobol, System API, etc.)
$ToolkitServiceObj->PgmCall
Toolkit plug size performance¶
Setting plug size to match your data size can offer increased performance.
- slower … 15 MB plug size (max)
$ToolkitServiceObj->setToolkitServiceParams(array('InternalKey'=>'/tmp/packers'.rand(1,$maxpool),'plugSize' => '15M'));
- faster … 512K plug size (default)
$ToolkitServiceObj->setToolkitServiceParams(array('InternalKey'=>'/tmp/packers'.rand(1,$maxpool),'plugSize'=>'512K'));
- fastest … 4K plug size (min)
$ToolkitServiceObj->setToolkitServiceParams(array('InternalKey'=>'/tmp/packers'.rand(1,$maxpool),'plugSize'=>'4K'));
Why a plug size at all?¶
DB2 connections are safe reliable transport for XML documents between client (PHP) and server (XMLSERVICE), but DB2 forces you to declare IN/OUT parameter size of any call procedure, XMLSERVICE download includes a few different stored procedure sizes (iPLUG4k .. iPLUG15M), so your script needs to choose the IN/OUT size that fits your data.
XMLSERVICE Performance¶
Performance at a glance¶
The following tests are full browser simulations (Apache ab tool), not PHP loop tests
- Apache ab web site stress tests are performed from the 5250 command line (call qp2term) or ssh myibmi using PASE (see Apache ab link install instructions)
> ab -t 15 -c 10 http://lp0264d/level/xxtoolkit_new.php
- You can run stress tests from 2-tier Linux/Windows using Apache ab tool, but i am using Apchae ab from PASE.
- Apache ab tool is not perfect, but if you use relatively “sane” number of browsers like -c 10 it will work.
- Apache ab test is designed to drive CPU to 100% (a good thing), so don’t panic about CPU
Connection | Sample (ab tool) |
---|---|
Apache | <H1>Hello world</H1> …………………………… Requests per second: 767.87 |
PHP | <?php echo "Hello world"; ?> ……………………. Requests per second: 560.05 |
ibm_db2 |
$ipc = "";
$ctl = "*justproc"; // *justproc (no progam call)
$clobIn = "<?xml version='1.0'?>";
$clobOut = "";
$stmt = db2_prepare($conn, "call XMLSERVICE.iPLUG4K(?,?,?,?)");
$ret = db2_bind_param($stmt, 1, "ipc", DB2_PARAM_IN);
$ret = db2_bind_param($stmt, 2, "ctl", DB2_PARAM_IN);
$ret = db2_bind_param($stmt, 3, "clobIn", DB2_PARAM_IN);
$ret = db2_bind_param($stmt, 4, "clobOut", DB2_PARAM_OUT);
$ret = db2_execute($stmt);
echo "success";
|
XMLSERVICE |
<?php
require_once('connection2.inc');
if ($i5persistentconnect) $conn = db2_pconnect($database,$user,$password);
else $conn = db2_connect($database,$user,$password);
if (!$conn) echo "Bad connect: $conn,$database,$user,perm=$i5persistentconnect";
$ipc = '/tmp/packers'; // *here no need ipc
$ctl = "*sbmjob"; // *here for no additional private job
// $ipc = '';
// $ctl = '*here';
$clobIn = "<?xml version='1.0'?>
<pgm name='ZZCALL' lib='$libxmlservice'>
<parm io='both'>
<data type='1A'>a</data>
</parm>
<parm io='both'>
<data type='1A'>b</data>
</parm>
<parm io='both'>
<data type='7p4'>11.1111</data>
</parm>
<parm io='both'>
<data type='12p2'>222.22</data>
</parm>
<parm io='both'>
<ds>
<data type='1A'>x</data>
<data type='1A'>y</data>
<data type='7p4'>66.6666</data>
<data type='12p2'>77777.77</data>
</ds>
</parm>
<return>
<data type='10i0'>0</data>
</return>
</pgm>";
$clobOut = "";
$stmt = db2_prepare($conn, "call XMLSERVICE.iPLUG4K(?,?,?,?)");
$ret=db2_bind_param($stmt, 1, "ipc", DB2_PARAM_IN);
$ret=db2_bind_param($stmt, 2, "ctl", DB2_PARAM_IN);
$ret=db2_bind_param($stmt, 3, "clobIn", DB2_PARAM_IN);
$ret=db2_bind_param($stmt, 4, "clobOut", DB2_PARAM_OUT);
$ret=db2_execute($stmt);
// var_dump($clobOut);
if (strpos($clobOut,"4444444444.44")>0) echo "success";
else echo "fail";
?>
|
Toolkit |
<?php
require_once('connection2.inc');
require_once("ToolkitService.php");
if ($i5persistentconnect) $conn = db2_pconnect($database,$user,$password);
else $conn = db2_connect($database,$user,$password);
if (!$conn) echo "Bad connect: $conn,$database,$user,perm=$i5persistentconnect";
try { $ToolkitServiceObj = ToolkitService::getInstance($conn); }
catch (Exception $e) { die($e->getMessage()); }
// $ToolkitServiceObj->setToolkitServiceParams(array('stateless'=>true,
'plug'=>'iPLUG32K'));
ToolkitServiceObj->setToolkitServiceParams(array('InternalKey'=>'/tmp/packers',
'plug'=>'iPLUG32K'));
$param[] = $ToolkitServiceObj->AddParameterChar ('both', 1, 'INCHARA', 'var1', 'Y');
$param[] = $ToolkitServiceObj->AddParameterChar ('both', 1, 'INCHARB', 'var2', 'Z');
$param[] = $ToolkitServiceObj->AddParameterPackDec('both', 7,4,'INDEC1', 'var3',
'001.0001');
$param[] = $ToolkitServiceObj->AddParameterPackDec('both', 12,2,'INDEC2', 'var4',
'0000000003.04');
$ds[] = $ToolkitServiceObj->AddParameterChar ('both', 1, 'DSCHARA', 'ds1', 'A');
$ds[] = $ToolkitServiceObj->AddParameterChar ('both', 1, 'DSCHARB', 'ds2', 'B');
$ds[] = $ToolkitServiceObj->AddParameterPackDec('both', 7,4,'DSDEC1', 'ds3',
'005.0007');
$ds[] = $ToolkitServiceObj->AddParameterPackDec('both', 12,2,'DSDEC1', 'ds4',
'0000000006.08');
$param[] = $ToolkitServiceObj->AddDataStruct($ds);
$clobOut = $ToolkitServiceObj->PgmCall('ZZCALL', $libxmlservice, $param, null, null);
// var_dump($clobOut);
$value = "what is ...".$clobOut["io_param"]["ds4"];
if (strpos($value,"4444444444.44")>-1) echo "success";
else echo "fail";
?>
|
How many users?¶
Apache ab tool is designed to “exercise” CPU/Apache engine as fast as possible, therefore ‘ab tool’ requests/second multiply by a factor of 5-10x to estimate ‘user capacity’ assuming humans actually read/scan each browser page (~ 5-10 seconds per page). So, on this machine using just ibm_db2 without toolkit, the following is about as fast as any given connection can run using only ONE job.
Drive the CPU to 100% capacity, then estimate actual users supported to drive the same CPU 100%.
>> ab -t 15 -c 15 http://lp0264d/ibm_db2_fetch.php?pool
483 requests/second..............483 users/second (varies widely per application base line)
28,980 requests/minute..............600 users/minute (relatively constant user capacity ibm_db2 scripts persistent connections)
1,738,800 requests/hour.............36,000 users/hour (relatively constant user capacity ibm_db2 scripts persistent connections)
41,731,200 requests/day (24 hours)..864,000 users/day (relatively constant user capacity ibm_db2 scripts persistent connections)
Things to consider:
- Who am i testing with ab tool?
- lighting fast ab tool “no delay” push CPU 100% (superman users faster than a speeding bullet … a machine)
- normal thinking users “10 second delay user actions” to push CPU 100% (average Joe … a human)
- your application complexity may go far beyond simple ibm_db2_fetch.php above, but most any web ‘load runner’ tool (like ‘ab tool’) or worse php script ‘loop call test’ requires human action calculation to understand user capacity, so don’t believe blog people until you actually run your application specific performance test.
If you want to run Apache ab tool on PASE (IBM i) …
- ab — Apache ab tool from old Zend Core (simulate browsers)
XMLSERVICE still evolving¶
Toolkit/XMLSERVICE performance will evolve over time, we understand performance is important and we intend push data through faster (perhaps much faster).
- What runs fast?
- calling PGMs/SRVPGMs (RPG, CLP, Cobol, System API, etc.)
- loading your program is relatively slow so keep process alive (see below)
- use persistent connections (db2_pconnect/i5_pconnect)
- use IPC state full XMLSERVICE (‘InternalKey’=>’/tmp/packers’)
- turn off PHP debug, logs, traces toolkit all ini files
- calling PGMs/SRVPGMs (RPG, CLP, Cobol, System API, etc.)
- What runs slower?
- CMDs, especially CMDS that return data (RTVJOBA, etc.)
- CMDs return data use REXX, which is slow first time
- CMDs that do NOT return data, will run fairly quickly
- call CLP using the program interface (not command interface)
- use IPC state full XMLSERVICE (‘InternalKey’=>’/tmp/packers’)
- turn off PHP debug, logs, traces toolkit all ini files
- CMDs, especially CMDS that return data (RTVJOBA, etc.)
- What runs slowest?
- PASE sh utilities (system wrkactjob, ls, ps, etc.)
- not much can be done because most time due to fork of another job (Unix/PASE style)
- what little can be done is mostly same all call types
- use IPC state full XMLSERVICE (‘InternalKey’=>’/tmp/packers’)
- turn off PHP debug, logs, traces toolkit all ini files
- PASE sh utilities (system wrkactjob, ls, ps, etc.)
XMLSERVICE Errors¶
Common errors¶
<errnoile>3401</errnoile>
<errnoilemsg><![CDATA[Permission denied.]]></errnoilemsg>
<errnoxml>1301011</errnoxml>
<xmlerrmsg><![CDATA[IPC shmat fail 1]]></xmlerrmsg>
<xmlhint><![CDATA[/tmp/packers3]]></xmlhint>
</error>
errnoile 3401 – usually means another process with a different user profile is using IPC (/tmp/packers3)
<errnoile>3021</errnoile>
<errnoxml>1301009</errnoxml>
<xmlerrmsg>IPC getshm fail</xmlerrmsg>
<xmlhint><![CDATA[/tmp/ ]]></xmlhint>
‘Hung’ semaphores/shared memory associated with a user never suppose to happen, but have seen in rare occasion. The following commands can be used to remove “hung” semaphores/shared memory associated with a user assuming you have appropriate authority to run like SECOFR, etc. (Ranger welcomes you to Unix geek-ville).
Example:
grep -i qtm means ipcrm for (QTM)HHTTP ...
endTCPSVR SERVER(*HTTP) INSTANCE(ZENDSVR) -- suggest end web server
call qp2term
> ipcs | grep -i qtm | awk '{print "ipcrm -" tolower($1) " "$2}' -- show action, but NOT do action
> ipcs | grep -i qtm | awk '{print "ipcrm -" tolower($1) " "$2}' | sh -- remove semaphores/shared memory
strTCPSVR SERVER(*HTTP) INSTANCE(ZENDSVR) -- suggest start web server
<errnoxml>1000005</errnoxml>
<xmlerrmsg>PASE resolve failed</xmlerrmsg>
<xmlhint><![CDATA[MYPGM]]></xmlhint>
The program you tried to call, shown here as “MYPGM” (in the CDATA tag), was not found. Make sure you specified the library and program correctly, including upper or lower case (usually upper case).
<errnoxml>1480002</errnoxml>
<xmlerrmsg>XMLCGI invalid</xmlerrmsg>
<xmlhint>*NONE</xmlhint>
*NONE requires a special compile of the RPG source and is NOT enabled in production versions of the toolkit by default. It is most useful for demos with custom security like this site, if you try on your machine you will likely get 1480002 error XMLCGI. You can find details in plugerr_h.
ILE errno¶
Errno Values for UNIX-Type Functions¶
Programs using the UNIX(R)-type functions may receive error information as errno values. The possible values returned are listed here in ascending errno value sequence.
Name Value Text
EDOM 3001 A domain error occurred in a math function.
ERANGE 3002 A range error occurred.
ETRUNC 3003 Data was truncated on an input, output, or update operation.
ENOTOPEN 3004 File is not open.
ENOTREAD 3005 File is not opened for read operations.
EIO 3006 Input/output error.
ENODEV 3007 No such device.
ERECIO 3008 Cannot get single character for files opened for record I/O.
ENOTWRITE 3009 File is not opened for write operations.
ESTDIN 3010 The stdin stream cannot be opened.
ESTDOUT 3011 The stdout stream cannot be opened.
ESTDERR 3012 The stderr stream cannot be opened.
EBADSEEK 3013 The positioning parameter in fseek is not correct.
EBADNAME 3014 The object name specified is not correct.
EBADMODE 3015 The type variable specified on the open function is not correct.
EBADPOS 3017 The position specifier is not correct.
ENOPOS 3018 There is no record at the specified position.
ENUMMBRS 3019 Attempted to use ftell on multiple members.
ENUMRECS 3020 The current record position is too long for ftell.
EINVAL 3021 The value specified for the argument is not correct.
EBADFUNC 3022 Function parameter in the signal function is not set.
ENOENT 3025 No such path or directory.
ENOREC 3026 Record is not found.
EPERM 3027 The operation is not permitted.
EBADDATA 3028 Message data is not valid.
EBUSY 3029 Resource busy.
EBADOPT 3040 Option specified is not valid.
ENOTUPD 3041 File is not opened for update operations.
ENOTDLT 3042 File is not opened for delete operations.
EPAD 3043 The number of characters written is shorter than the expected record length.
EBADKEYLN 3044 A length that was not valid was specified for the key.
EPUTANDGET 3080 A read operation should not immediately follow a write operation.
EGETANDPUT 3081 A write operation should not immediately follow a read operation.
EIOERROR 3101 A nonrecoverable I/O error occurred.
EIORECERR 3102 A recoverable I/O error occurred.
EACCES 3401 Permission denied.
ENOTDIR 3403 Not a directory.
ENOSPC 3404 No space is available.
EXDEV 3405 Improper link.
EAGAIN 3406 Operation would have caused the process to be suspended.
EWOULDBLOCK 3406 Operation would have caused the process to be suspended.
EINTR 3407 Interrupted function call.
EFAULT 3408 The address used for an argument was not correct.
ETIME 3409 Operation timed out.
ENXIO 3415 No such device or address.
EAPAR 3418 Possible APAR condition or hardware failure.
ERECURSE 3419 Recursive attempt rejected.
EADDRINUSE 3420 Address already in use.
EADDRNOTAVAIL 3421 Address is not available.
EAFNOSUPPORT 3422 The type of socket is not supported in this protocol family.
EALREADY 3423 Operation is already in progress.
ECONNABORTED 3424 Connection ended abnormally.
ECONNREFUSED 3425 A remote host refused an attempted connect operation.
ECONNRESET 3426 A connection with a remote socket was reset by that socket.
EDESTADDRREQ 3427 Operation requires destination address.
EHOSTDOWN 3428 A remote host is not available.
EHOSTUNREACH 3429 A route to the remote host is not available.
EINPROGRESS 3430 Operation in progress.
EISCONN 3431 A connection has already been established.
EMSGSIZE 3432 Message size is out of range.
ENETDOWN 3433 The network currently is not available.
ENETRESET 3434 A socket is connected to a host that is no longer available.
ENETUNREACH 3435 Cannot reach the destination network.
ENOBUFS 3436 There is not enough buffer space for the requested operation.
ENOPROTOOPT 3437 The protocol does not support the specified option.
ENOTCONN 3438 Requested operation requires a connection.
ENOTSOCK 3439 The specified descriptor does not reference a socket.
ENOTSUP 3440 Operation is not supported.
EOPNOTSUPP 3440 Operation is not supported.
EPFNOSUPPORT 3441 The socket protocol family is not supported.
EPROTONOSUPPORT 3442 No protocol of the specified type and domain exists.
EPROTOTYPE 3443 The socket type or protocols are not compatible.
ERCVDERR 3444 An error indication was sent by the peer program.
ESHUTDOWN 3445 Cannot send data after a shutdown.
ESOCKTNOSUPPORT 3446 The specified socket type is not supported.
ETIMEDOUT 3447 A remote host did not respond within the timeout period.
EUNATCH 3448 The protocol required to support the specified address family is not available at this time.
EBADF 3450 Descriptor is not valid.
EMFILE 3452 Too many open files for this process.
ENFILE 3453 Too many open files in the system.
EPIPE 3455 Broken pipe.
ECANCEL 3456 Operation cancelled.
EEXIST 3457 File exists.
EDEADLK 3459 Resource deadlock avoided.
ENOMEM 3460 Storage allocation request failed.
EOWNERTERM 3462 The synchronization object no longer exists because the owner is no longer running.
EDESTROYED 3463 The synchronization object was destroyed, or the object no longer exists.
ETERM 3464 Operation was terminated.
ENOENT1 3465 No such file or directory.
ENOEQFLOG 3466 Object is already linked to a dead directory.
EEMPTYDIR 3467 Directory is empty.
EMLINK 3468 Maximum link count for a file was exceeded.
ESPIPE 3469 Seek request is not supported for object.
ENOSYS 3470 Function not implemented.
EISDIR 3471 Specified target is a directory.
EROFS 3472 Read-only file system.
EUNKNOWN 3474 Unknown system state.
EITERBAD 3475 Iterator is not valid.
EITERSTE 3476 Iterator is in wrong state for operation.
EHRICLSBAD 3477 HRI class is not valid.
EHRICLBAD 3478 HRI subclass is not valid.
EHRITYPBAD 3479 HRI type is not valid.
ENOTAPPL 3480 Data requested is not applicable.
EHRIREQTYP 3481 HRI request type is not valid.
EHRINAMEBAD 3482 HRI resource name is not valid.
EDAMAGE 3484 A damaged object was encountered.
ELOOP 3485 A loop exists in the symbolic links.
ENAMETOOLONG 3486 A path name is too long.
ENOLCK 3487 No locks are available.
ENOTEMPTY 3488 Directory is not empty.
ENOSYSRSC 3489 System resources are not available.
ECONVERT 3490 Conversion error.
E2BIG 3491 Argument list is too long.
EILSEQ 3492 Conversion stopped due to input character that does not belong to the input codeset.
ETYPE 3493 Object type mismatch.
EBADDIR 3494 Attempted to reference a directory that was not found or was destroyed.
EBADOBJ 3495 Attempted to reference an object that was not found, was destroyed, or was damaged.
EIDXINVAL 3496 Data space index used as a directory is not valid.
ESOFTDAMAGE 3497 Object has soft damage.
ENOTENROLL 3498 User is not enrolled in system distribution directory.
EOFFLINE 3499 Object is suspended.
EROOBJ 3500 Object is a read-only object.
EEAHDDSI 3501 Hard damage on extended attribute data space index.
EEASDDSI 3502 Soft damage on extended attribute data space index.
EEAHDDS 3503 Hard damage on extended attribute data space.
EEASDDS 3504 Soft damage on extended attribute data space.
EEADUPRC 3505 Duplicate extended attribute record.
ELOCKED 3506 Area being read from or written to is locked.
EFBIG 3507 Object too large.
EIDRM 3509 The semaphore, shared memory, or message queue identifier is removed from the system.
ENOMSG 3510 The queue does not contain a message of the desired type and (msgflg logically ANDed with IPC_NOWAIT).
EFILECVT 3511 File ID conversion of a directory failed.
EBADFID 3512 A file ID could not be assigned when linking an object to a directory.
ESTALE 3513 File handle was rejected by server.
ESRCH 3515 No such process.
ENOTSIGINIT 3516 Process is not enabled for signals.
ECHILD 3517 No child process.
EBADH 3520 Handle is not valid.
ETOOMANYREFS 3523 The operation would have exceeded the maximum number of references allowed for a descriptor.
ENOTSAFE 3524 Function is not allowed.
EOVERFLOW 3525 Object is too large to process.
EJRNDAMAGE 3526 Journal is damaged.
EJRNINACTIVE 3527 Journal is inactive.
EJRNRCVSPC 3528 Journal space or system storage error.
EJRNRMT 3529 Journal is remote.
ENEWJRNRCV 3530 New journal receiver is needed.
ENEWJRN 3531 New journal is needed.
EJOURNALED 3532 Object already journaled.
EJRNENTTOOLONG 3533 Entry is too large to send.
EDATALINK 3534 Object is a datalink object.
ENOTAVAIL 3535 IASP is not available.
ENOTTY 3536 I/O control operation is not appropriate.
EFBIG2 3540 Attempt to write or truncate file past its sort file size limit.
ETXTBSY 3543 Text file busy.
EASPGRPNOTSET 3544 ASP group not set for thread.
ERESTART 3545 A system call was interrupted and may be restarted.
ESCANFAILURE 3546 An object has been marked as a scan failure due to processing by an exit program associated with the scan-related integrated file system exit points.
XMLSERVICE FAQ¶
Tip¶
Some webserver products take issue with <script>
in XML documents. If you find chunks missing in your XML, try <myscript>
<script> change <myscript>
: to :
</script> this </myscript>
XMLSERVICE is hanging right out of box (because CCSID 65535)¶
Q: XMLSERVICE is returning junk, how can i fix?
Q: XMLSERVICE is not working at all and IPC /tmp/xxx dir not created, how can i fix?
Q: XMLSERVICE is hanging?
A: You need to change Apache settings.
The following steps lead to a successful test::
end current running jobs:
ENDTCPSVR SERVER(*HTTP) HTTPSVR(ZENDSVR)
ENDPJ SBS(QSYSWRK) PGM(QSQSRVR) OPTION(*IMMED)
check the system CCSID value is 65535:
DSPSYSVAL SYSVAL(QCCSID)
Coded character set
identifier . . . . . : 65535 1-65535
edit configuration:
/www/zendsvr/conf/fastcgi.conf (optional from editor):
SetEnv="LANG=C"
/www/zendsvr/conf/httpd.conf (web admin GUI port 2001 - ZENDSVR):
DefaultFsCCSID 37 ... or 208 (Italian) ... or so on ...
CGIJobCCSID 37 ... or 208 (Italian) ... or so on ...
restart jobs:
STRPJ SBS(QSYSWRK) PGM(QSQSRVR)
STRTCPSVR SERVER(*HTTP) HTTPSVR(ZENDSVR)
wrkactjob
If you see ANY XMLSERVICE jobs, kill them before retry.
This will allow XMLSERVICE to work (IPC was junk not /tmp/whatever).
This should fix "junk" from ibm_db2 on i (odbc, pdo_ibm, etc.).
php-cli command line issues (CCSID 65535) …¶
The easy way to fix this is to simply CHGUSRPRF and specify a valid CCSID like 37, then as you sign-on (ssh, call qp2term) you will not be stuck with nasty 65535 problems.
And now i can run my test cases (ssh->tcsh)
>setenv PATH /usr/local/zendsvr/bin:$PATH
>setenv LIBPATH /usr/local/zendsvr/lib
>cd /mytests
>pear run-tests *.phpt
>php test.php
XMLSERVICE DBCS (CCSID 5035)¶
Q: Did anyone try DBCS with XMLSERVICE?
A: Yes. Japanese 5035 appears to work.
The following steps lead to a successful test:
end current running jobs:
ENDTCPSVR SERVER(*HTTP) HTTPSVR(ZENDSVR)
ENDPJ SBS(QSYSWRK) PGM(QSQSRVR) OPTION(*IMMED)
change the system CCSID value:
CHGSYSVAL SYSVAL(QCCSID) VALUE(5035)
edit configuration:
/www/zendsvr/conf/fastcgi.conf (editor:
SetEnv="CCSID=1208" SetEnv="LANG=C"
/www/zendsvr/conf/httpd.conf (web admin GUI port 2001 - ZENDSVR):
DefaultFsCCSID 5035
CGIJobCCSID 5035
restart jobs:
STRPJ SBS(QSYSWRK) PGM(QSQSRVR)
STRTCPSVR SERVER(*HTTP) HTTPSVR(ZENDSVR)
Note: If using ODBC will likely have to start/stop prestart jobs as well.
XMLSERVICE multi-CCSID or problems?¶
Using default PHP toolkit DB2 clob interface (iPLUGxxx/iPLUGRxxx), ccsid conversion occurs naturally as DB2 client/server and you will not have to code before/after, but method is available if you have a specific concern or you have scripts returning many different languages.
Example:
<data type='200A' before='819/424' after='424/819'>bin2hex('Hebrew_ascii_raw_chars')</data>
<data type='200A' before='819/1098' after='1098/819'>bin2hex('Farsi_ascii_raw_chars')</data>
<data type='200A' before='819/880' after='880/819'>bin2hex('Russia_ascii_raw_chars')</data>
<data type='200A' before='819/280' after='280/819'>bin2hex('Italy_ascii_raw_chars')</data>
<data type='200A' before='819/273' after='273/819'>bin2hex('Germany_ascii_raw_chars')</data>
<data type='200A' before='819/1088' after='1088/819'>bin2hex('Korea_ascii_raw_chars')</data>
<data type='200A' before='1208/13488' after='13488/1208'>bin2hex('Japan_ascii_raw_chars')</data>
<data type='200A' before='1208/13488' after='13488/1208'>bin2hex('China_ascii_raw_chars')</data>
where:
before - XMLSERVICE convert CCSID before ILE program call
after - XMLSERVICE convert CCSID after ILE program call for client return
bin2hex() - script hex string unaltered ascii image (also returned hex string avoid any conversion)
pack() - script uses pack('H*',"xml_hex_back") function in PHP program for ascii characters
Note:
Up to four conversions can take place for the truly diabolical ccsid issues
<data type='A' before='cc1/cc2/cc3/cc4' after='cc4/cc3/cc2/cc1'>bin2hex('wild_ascii_raw_chars')</data>
flow:
-> PHP client bin2hex('wild_ascii_raw_chars')
-> xmlservice hex2bin back to 'wild_ascii_raw_chars'
-> xmlservice convert cc1->cc2->cc3->cc4 (before)
-> xmlservice make ILE call
-> xmlservice convert cc4->cc3->cc2->cc1 (after)
-> xmlservice tohex "xml_hex_back"
-> PHP client $chars = pack('H*',"xml_hex_back")
Can i use CDATA for XML special characters?¶
Many common XMLSERVICE tags already support a form of CDATA
<data><![CDATA[<i am tony>]]></data>
<query><![CDATA[select * from animal where ID < 5 and weight > 10.0]]></query>
<prepare><![CDATA[select * from animal where ID < ? and weight > ?]]></prepare>
BUT there are restrictions for speed of parsing (i think reasonable)
- not allowed to put reserved words in cdata - NO
<query><![CDATA[</query>]]></query>
- no binary data (character data only) -
<query><![CDATA[binary stuff]]></query>
- there may be other restrictions because i don’t know everything about CDATA “abuse” in XML.
How do i kill/end XMLSERVICE jobs?¶
XMLSERVICE jobs stay active until killed with CTL option *immed. You need only know the ipc name (/tmp/fred01, /tmp/sally43, etc.), and have appropriate profile abilities to issue the XML/CTL kill to a running XMLSERVICE job.
Example kill XMLSERVICE for ‘/tmp/rangerusr’:
$ipc='/tmp/rangerusr';
$ctlKill="*immed";
$clobInKill = '<?xml version="1.0"?>';
$sql = "call $libxmlservice.iPLUGR4K('$ipc','$ctlKill','$clobInKill')";
$ret=db2_exec($conn,$sql);
Can i run “stateless” XMLSERVICE jobs (*here)?¶
XMLSERVICE can also run in the stored procedure (or web) job by using the ctl=’*here’ option. When you run in *here mode you do NOT need an IPC (ipc is ignored). XMLSERVICE is just another program in the job in *here mode, so when the job ends so does XMLSERVICE.
Example XMLSERVICE running in stored procedure job:
$ipc='not need ipc it is ignored';
$ctl="*here";
$clobIn = '<?xml version="1.0"?>
<script>do stuff</script>';
$sql = "call $libxmlservice.iPLUGR512K('$ipc','$ctl','$clobIn')";
$ret=db2_exec($conn,$sql);
Note: Performance is generally slower when running in this mode, because XMLSERVICE has to start from scratch each time. There are varying degrees of overall XMLSERVICE performance using *here (“stateless”) depending on application interface supporting persistent/non-persistent/private connections (ie., db2_connect vs. db2_pconnect).
Warning: It should be noted XMLSERVICE “private” connection with ipc (ipc=/tmp/fred01, ipc=/tmp/fred02, ipc=/tmp/sally43) is most often the best high performance fit for calling “real” RPG programs because most RPG programs actually do something other than “hello world” with multiple files open and lot’s of state you do not want to restart each time called (much better to dedicate a XMLSERVICE job to the task). So, while *here is an easy answer for operator style process management (older toolkit options), and works ok for small “calculate this” SRVPGMs, you will likely find *here does not work in a practical world where business tasks are much more complex (ie. exactly why XMLSERVICE is optimized for using a IPC connection).
What is IPC??¶
IPC is traditional computer science abbreviation for InterProcess Communication (IPC), which in the case of XMLSERVICE is simply a unique directory name /tmp/mydir01 used for machine-wide unique ‘key/token’ to route XML script documents between various clients calling XMLSERVICE job(s)(/tmp/fred01, /tmp/sally02, etc.).
Behind the scenes of XMLSERVICE the unique IPC provided by any client interface (/tmp/fred01, /tmp/sally02, etc.), establishes a shared memory area for XML information/control passing and a semaphore to provide one-at-time processing in XMLSERVICE job(s). All XMLSERVICE InterProcess Communication activities are keyed and routed to appropriate XMLSERVICE jobs by the IPC name in the /tmp directory (/tmp/fred01, /tmp/sally02, etc.).
As far as security is concerned, all normal IBM i IFS authority rules apply to the IPC access (/tmp/fred01, /tmp/sally02, etc.), therefore assuming no group profile override precedence, only profile fred will be able to access IPC /tmp/fred01 XMLSERVICE job, and only sally profile will be able to access /tmp/sally XMLSERVICE job, and so on for all profiles used. Of course, high authority profiles like *SECOFR have more abilities over *USER profiles, but general rule is the profile you see in wrkactjob “owns/uses” the XMLSERVICE job.
deployment centric, not development centric (caches used XMLSERVICE)¶
XMLSERVICE is a deployment/production centric service, not development centric, therefore XMLSERVICE internal caches are used to speed up next call processing. XMLSERVICE job(s) caching allows production sites to simply ignore costly operations like XMLSERVICE find/load called modules (your RPG programs), under the assumption that production machine is stable (no recompiles going on).
However, if you are doing development work on your machine (recompiles CL/RPG), you will have to end active/running XMLSERVICE job(s) and restart to call your new program.
Example (actual email): My coworker has told me on more than one occasion that I’m calling his “old” CL or RPG programs even though he recompiled them. I resolve this by restarting the correct XMLSERVICE jobs, allowing me to call the current versions of CL/RPG programs.
Why not use pcml?¶
pcml falls short in terms of supported RPG types and common attribute conventions (varying) and simply cannot do what this XMLSERVICE custom XML interface is capable of doing. pcml falls even shorter when trying to return complex structures as <return> elements, which is very popular for modern RPG SRVPGMs (XMLSERVICE supports of course). Last, pcml is especially short of the “complete scripting” mark when calling PASE shells, or CMDs (XMLSERVICE supports).
I leave it to the entrepreneurial user to XSLT map PCML to XMLSERVICE (hint).
DB2 XML SQL not work ctl=’*here’?¶
DB2 SQL XML does not work in-line stateless ($ctl='*here'
), but works fine with normal private connections
(ipc='/tmp/fred', $ctl='*sbmjob'
).
Workaround for “bad” drivers leaving junk back of output clob¶
The world is not perfect 1-2 tier DB2 drivers IBM i, Windows, Linux, etc., so occasionally a “hack” is handy.
I always “scope” my XML input requests with <script>...</script>
, so anything past tailing </script>
is ‘junk’ (errors return as <report>...</report>
).
The new XMLSERVICE keyword *hack adds </hack>
back of every record return result set can be very useful for drivers that do not support stored procedure
in/out parameters like PHP odbc.
function driverJunkAway($xml)
{
$clobOut = $xml;
if (! trim($clobOut)) return $clobOut;
// result set has extra data (junk)
$fixme = '</hack>';
$pos = strpos($clobOut,$fixme);
if ($pos > -1) {
$clobOut = substr($clobOut,0,$pos);
}
else {
$fixme = '</script>';
$pos = strpos($clobOut,$fixme);
if ($pos > -1) {
$clobOut = substr($clobOut,0,$pos+strlen($fixme));
}
// maybe error/performance report
else {
$fixme = '</report>';
$pos = strpos($clobOut,$fixme);
if ($pos > -1) {
$clobOut = substr($clobOut,0,$pos+strlen($fixme));
}
}
}
return $clobOut;
}
Can i use curl to test XMLSERVICE?¶
This works from my Linux machine to IBM i.
curl http://myibmi/cgi-bin/xmlcgi.pgm --data-urlencode db2=*LOCAL --data-urlencode uid=*NONE
--data-urlencode pwd=*NONE --data-urlencode ipc=/tmp/rangerhtmlonly --data-urlencode ctl=*sbmjob
--data-urlencode xmlin="<?xml version='1.0'?><script><pgm name='ZZCALL' lib='XMLSERVICE'>
<parm><data type='1A'>a</data></parm><parm><data type='1A'>b</data></parm><parm>
<data type='7p4'>11.1111</data></parm><parm><data type='12p2'>222.22</data></parm><parm><ds>
<data type='1A'>x</data><data type='1A'>y</data><data type='7p4'>66.6666</data>
<data type='12p2'>77777.77</data></ds></parm><return><data type='10i0'>0</data>
</return></pgm></script>" --data-urlencode xmlout=32768
I need to start over kill everything¶
… doing a lot of machine “updating” test cw/xmlservice versions … many install errors … state unknown … i recommend following actions assuming only Zend Server running this system (be kind to others) …
1) end zendsrvr http
ENDTCPSVR SERVER(*HTTP) INSTANCE(ZENDSVR)
wrkactjob wait all php-cgi to go down, kill php-cgi *immed by hand if won’t die.
2) clear left over IPC attributes for cw/xmlservice test profiles
call qp2term
> ipcs | grep -i qtm | awk '{print "ipcrm -" tolower($1) " "$2}' | sh
> ipcs | grep -i myid1 | awk '{print "ipcrm -" tolower($1) " "$2}' | sh
> ipcs | grep -i myid1 | awk '{print "ipcrm -" tolower($1) " "$2}' | sh
Where:
> qtm - clear IPCs for QTMHHTTP
> myid1 - clear IPCs for profile MYID1
> myid2 - clear IPCs for profile MYID2
3) carefully reset /tmp
call qp2term
> cd /tmp
> pwd
/tmp --- if you do not see /tmp, stop now rm -R may kill whole system
> rm -R *
4) recycle DB2 connections (optional)
ENDPJ SBS(QSYSWRK) PGM(QSQSRVR) OPTION(*IMMED)
STRPJ SBS(QSYSWRK) PGM(QSQSRVR)
- re-check configuration
Change these files and restart everything (web, db2, xmlservice, tec.)
I. Apache: /www/zendsvr/conf/httpd.conf <-- (ILE Apache side)
DefaultFsCCSID 37 ... or 280 (Italian) ... or so on ...
CGIJobCCSID 37 ... or 280 (Italian) ... or so on ...
6) restart http
STRTCPSVR SERVER(*HTTP) HTTPSVR(ZENDSVR)
PASE php-cgi out of memory (SetEnv=”LDR_CNTRL=MAXDATA=0x80000000”)¶
This is a very rare condition, but if you find your huge size PHP script runs out of PASE memory …
You can force most any 32-bit PASE program to leave more heap space (including php-cgi), just specify environment variable up to LDR_CNTRL=MAXDATA=0x80000000 (8 * 256MB). However, please be aware while you are increasing the heap, you are also limiting the number of 256MB segments available for shared memory (not commonly used by PHP programs anyway).
- [[http://www.ibm.com/developerworks/aix/library/j-nativememory-aix/index.html]] - Thanks for the memory article applies to PASE as well (therefore also PHP)*
In FastCGI php-cgi just add directive to fastcgi.config.
/www/zendsvr/conf/fastcgi.conf
Server type="application/x-httpd-php" ... normal stuff ... SetEnv="LDR_CNTRL=MAXDATA=0x80000000"
Graphically memory of PASE 32 bit LDR_CNTRL=MAXDATA=0x80000000. Variations of LDR_CNTRL allow you to rearrange even reserved segments and shared libraries, but these uses of LDR_CNTRL are infrequently deployed (except for the most gruesome memory hog Java programs MAXDATA=0xD0000000@DSA).
>export LDR_CNTRL=MAXDATA=0x80000000
>php -v
memory of PASE program PHP
00000000 - 0FFFFFFF - PASE kernel heap (mostly read or no access to user programs)
10000000 - 1FFFFFFF - PASE main program text/code (php)
20000000 - 2FFFFFFF - PASE stack (programs usually share stack/heap in this one segment 256MB)
30000000 - 3FFFFFFF ------
40000000 - 4FFFFFFF |
50000000 - 5FFFFFFF |
60000000 - 6FFFFFFF |--- PASE heap (LDR_CNTRL=MAXDATA=0x80000000)
70000000 - 7FFFFFFF | used for new/delete/malloc/free allocations PHP to run scripts
80000000 - 8FFFFFFF |
90000000 - 9FFFFFFF | *Note: no more segments avail for shared memory
A0000000 - AFFFFFFF ------
B0000000 - BFFFFFFF - reserved
C0000000 - CFFFFFFF - reserved
D0000000 - DFFFFFFF - machine wide shared library text/code (libc.a, etc.)
E0000000 - EFFFFFFF - shared memory
F0000000 - FFFFFFFF - machine wide shared library data