XMLSERVICE Interfaces

(click to open)

Quick Page Table of Contents

Scanning…

XMLSERVICE XML Interfaces (PHP examples)

Goto Main Page
Goto Documents

The basic idea of XMLSERVICE is to allow any scripting language to use XML input/output documents to drive XMLSERVICE job(s) calling PGMs, SRVPGMS, CMDs, PASE shells. Therefore using 1 tier and/or 2 tier connections already available such as stored procedures and/or REST interfaces offers a wide range of ways to get the XML data to/from IBM i XMLSERVICE jobs (XML documents are just strings after all, so nearly any “driver” can transport strings to/from clients/IBM i). The following examples feature PHP, but almost all scripting languages support REST and/or DB2, therefore PHP examples need only be ‘copied” into matching syntax for your language of choice.

REST interface (download included xmlcgi.pgm)

The REST-GET/REST-POST interfaces below required optional XMLSERVICE XMLCGI installed and configured. The RPG CGI XMLCGI sample module is included in the XMLSERVICE download.

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>

IMPORTANT: If you are running a machine with CCSID 65535 (and nothing works), 
please read and follow the documentation (main XMLSERVICE page), 
setting valid CCSID like 37 for Apache (web) 
and/or command line tests (pear tests).

DefaultFsCCSID 37  ... or 280 (Italian) ... or so on ...
CGIJobCCSID 37     ... or 280 (Italian) ... or so on ...

REST examples use XMLCGI: REST parameters (xmlcgi.pgm):
http://myibmi/cgi-bin/?db2=*LOCAL&uid=MYID&pwd=MYPWD&ipc=/tmp/MYID&ctl=*here&xmlin=<?xml >&xmlout=32768

  • db2 - what database (*LOCAL tested)
  • uid - user profile
  • pwd - profile password
  • ipc - IPC key name/security route to XMLSERVICE job
  • clt - CTL admin control XMLSERVICE job
  • xmlin - XML input document (request)
  • xmlout - expected size of XML output document (response size in bytes)
  • Optional: persis — 10 char label persistent connection (experimental not commonly used)

For detail all ctl keywords see main page {XML Reserved Words}.

DB2 stored procedure interface (download included crtsql)

The DB2 interfaces below are included in XMLSERVICE download and created via CRTXML CLP (or other library CLP CRT). Multiple sized stored procedures are provided with XMLSERVICE download, because DB2 CLI / SQL requires a length on stored procedure I/O parameters. However, multiple DB2 stored procedure options are available, including lob in/out parameters, lob input parameters with result set fetch output, chunk varchar input VARCHAR with result set fetch output (1.9.0), goal, hopefully, covering all the possible capabilities various client DB2 drivers (or lack of DB2 driver capabilities perhaps more precisely said). i suspect best performing is single call via IN/OUT parameters over result fetch option, but your DB2 driver rules the method selected, xmlservice just wants to play ball.

Recommended stored procedure IN/OUT parameters (details follow):
iPLUG512K(IN IPC CHAR(1024), IN CTL CHAR(1024), IN CI CLOB(512K), OUT CO CLOB(512K))

  • 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)

For detail all ctl keywords see main page {XML Reserved Words}.

Alternative stored procedure parameters with result set (details follow):
iPLUGR5M(IN IPC CHAR(1024), IN CTL CHAR(1024), IN CI CLOB(5M))

  • 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)

Input XML is same as IN/OUT above, but you must fetch result set OUT in a loop (3000 characters per fetch). Why 3000? We attempted sending back max size fetch, but various DB2 language drivers failed to collect that much data in one fetch.

NEW: Enable DB2 drivers with no LOB support (version 1.9.0+)
iPLUGRC32K(IN IPC CHAR(1024), IN CTL CHAR(1024), IN CI VARCHAR(32700), IN CNT INTEGER)

  • 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)
  • IN CNT INTEGER - 0=run XML request; >0=accumulate input xml

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. Again, you must fetch result set OUT in a loop (3000 characters per fetch).

For detail all ctl keywords see main page {XML Reserved Words}.

XMLSERVICE two basic connections

Independent of the driver (REST or DB2), XMLSERVICE provides two modes of operation after the data arrives in XMLSERVICE:

  • stateless or public connection
    • ctl=“*here *cdata”
  • state full or private connection (XTOOLKIT job)
    • ctl=“*sbmjob *cdata”
    • ipc=“/tmp/my_unique_path”

For detail all ctl keywords see main page {XML Reserved Words}.

XMLSERVICE public connection
Briefly, public connections (ctl=“*here”), are traditional web connections, whereby resources such as DB2 connections, XMLSERVICE activation, caleld PGM, etc., are used for a single script request. When script completes, all resources are returned to IBM i pools for use by other applications (other users, other profiles, other applications, etc.).

XMLSERVICE public connections do NOT involved XTOOLKIT job(s),instead public connections run in a DB2 connect job (QSQSRVR job). This allows main web server to process scripts of any language in low capability authority profile (QTMHHTTP), while connected profile XMLSERVICE relies on tried and true DB2 security to run your requests in a associated QSQSRVR pool job.

XMLSERVICE private connection (XTOOLKIT job)
Private connections (ctl=“*sbmjob”; ipc=“/tmp/my_unique_path”), are traditional 5250-like connections made available to web applications, whereby a single profile owns a spawned toolkit job (like sign-on 5250), and all resources such as DB2 files, XMLSERVICE activation, call PGM, SRVPM activation, CMD, etc., remain active for life of process (forever, until killed like 5250).

If you check job log of XTOOLKIT private connection, you will see the start command issues by the first web request using the private connection.

  • CALL PGM(XMLSERVICE/XMLSERVICE) PARM(‘/tmp/ipc_cw_DB2_42′)

Remember XTOOLKIT job is running as requested profile, therefore, like 5250, you can only connect to a running XTOOLKIT job using same profile. To wit, web requests must continue to use same user profile with same routing information (‘/tmp/ipc_cw_DB2_42′), for all requests to go back to this XTOOLKIT job.

XMLSERVICE private connection provides a semaphore lock on requester use, thereby only-one-at-a-time will be using resources in XTOOLKIT job. Why only one at a time? Well, kindly, 98% of traditional business RPG programs are NOT threaded RPG programs. XMLSERVICE knows this, so queues each requester behind a semaphore to force one at a time calling your RPG programs to avoid absolute chaos (you are welcome).

Examples PHP, but same any language

ibm_db2 in/out parms (interface)

<?php
// -----------------
// make the call
// -----------------
// 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("Fail connect: $database,$user");
$stmt = db2_prepare($conn, "call $libxmlservice.iPLUG65K(?,?,?,?)");
if (!$stmt) die("Fail 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("Fail execute: ".db2_stmt_errormsg());
// -----------------
// output processing
// -----------------

ibm_db2 result set (interface)

<?php
// -----------------
// make the call
// -----------------
// 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.iPLUGR512K(?,?,?)");
if (!$stmt) die("Bad prepare: ".db2_stmt_errormsg());
$clobIn = getxml();
$clobOut = "";
$ret=db2_execute($stmt,array($ipc,$ctl,$clobIn));
if (!$ret) die("Bad execute: ".db2_stmt_errormsg());
while ($row = db2_fetch_array($stmt)){
  $clobOut .= $row[0];
}
$clobOut = trim($clobOut);
// -----------------
// output processing
// -----------------

odbc result set (interface)

<?php
// see connection.inc param details ...
require_once('connection.inc');
// call IBM i
if ($i5persistentconnect) $conn = odbc_pconnect($database,$user,$password);
else $conn = odbc_connect($database,$user,$password);
if (!$conn) die("Bad connect: $database,$user");
$stmt = odbc_prepare($conn, "call $libxmlservice.iPLUGR32K(?,?,?)");
if (!$stmt) die("Bad prepare: ".odbc_errormsg());
$clobIn = getxml();
$clobOut = "";
// bad behavior odbc extension ... 
// why IBM i result set warning???
error_reporting(~E_ALL); 
$ret=odbc_execute($stmt,array($ipc,$ctl,$clobIn));
if (!$ret) die("Bad execute: ".odbc_errormsg());
error_reporting(E_ALL); 
while(odbc_fetch_row($stmt)) {
  $row = odbc_result($stmt, 1);
  $clobOut .= $row;
}
// -----------------
// output processing
// -----------------

pdo_ibm in/out parms (interface)

<?php
// see connection.inc param details ...
require_once('connection.inc');
$database = "ibm:".$database;
try {
  $db = new PDO($database, 
                strtoupper($user), 
                strtoupper($password), 
                array(PDO::ATTR_AUTOCOMMIT=>true));
  if (!$db) throw new Exception('foo');
} catch( Exception $e ) { 
  die("Bad connect: $database,$user"); 
}
try {
  $stmt = $db->prepare("call $libxmlservice.iPLUG65K(?,?,?,?)");
  if (!$stmt) throw new Exception('bar');
} catch( Exception $e ) { 
  $err = $db->errorInfo();
  $cod = $db->errorCode();
  die("Bad prepare: ".$cod." ".$err[0]." ".$err[1]." ".$err[2]);
}
try {
  $clobIn = getxml();
  $clobOut = "";
  $r1=$stmt->bindParam(1,$ipc, PDO::PARAM_STR);
  $r2=$stmt->bindParam(2,$ctl, PDO::PARAM_STR);
  $r3=$stmt->bindParam(3,$clobIn, PDO::PARAM_LOB);
  $r4=$stmt->bindParam(4,$clobOut, PDO::PARAM_LOB|PDO::PARAM_INPUT_OUTPUT);
  $ret = $stmt->execute();
  if (!$ret) throw new Exception('yoyo');
  // result set has extra data (junk)
  // XMLSERVICE workaround sends ...
  // <myscript>data</myscript>NULL<eof>eof</eof>LFNULL
  $fixme = '<eof>eof</eof>';
  $clobOut = trim($clobOut);
  $pos = strpos($clobOut,$fixme);
  if ($pos > -1) {
    $clobOut = substr($clobOut,0,$pos-1);
  }
} catch( Exception $e ) {
  $err = $stmt->errorInfo();
  $cod = $stmt->errorCode();
  die("Bad execute: ".$cod." ".$err[0]." ".$err[1]." ".$err[2]);
}
// -----------------
// output processing
// -----------------

pdo_ibm result set (interface)

<?php
// see connection.inc param details ...
require_once('connection.inc');
$database = "ibm:".$database;
try {
  $db = new PDO($database, 
                strtoupper($user), 
                strtoupper($password), 
                array(PDO::ATTR_AUTOCOMMIT=>true));
  if (!$db) throw new Exception('foo');
} catch( Exception $e ) { 
  die("Bad connect: $database,$user"); 
}
try {
  $stmt = $db->prepare("call $libxmlservice.iPLUGR4K(?,?,?)");
  if (!$stmt) throw new Exception('bar');
} catch( Exception $e ) { 
  $err = $db->errorInfo();
  $cod = $db->errorCode();
  die("Bad prepare: ".$cod." ".$err[0]." ".$err[1]." ".$err[2]);
}
try {
  $clobIn = getxml();
  $clobOut = "";
  $ret = $stmt->execute(array($ipc,$ctl,$clobIn));
  if (!$ret) throw new Exception('yoyo');
  while( $row = $stmt->fetch(PDO::FETCH_NUM,PDO::FETCH_ORI_NEXT) ) {
    $clobOut .= $row[0];
    // occasional bad behavior extension ... extra data (junk)
    // XMLSERVICE workaround sends ...
    // <myscript>data</myscript>NULL<eof>eof</eof>LFNULL
    $fixme = '<eof>eof</eof>';
    // $clobOut = trim($clobOut);
    $pos = strpos($clobOut,$fixme);
    if ($pos > -1) {
      $clobOut = substr($clobOut,0,$pos-1);
      break;
    }
  }
} catch( Exception $e ) {
  $err = $stmt->errorInfo();
  $cod = $stmt->errorCode();
  die("Bad execute: ".$cod." ".$err[0]." ".$err[1]." ".$err[2]);
}
// -----------------
// output processing
// -----------------

ibm_db2 example for drivers without LOB support (1.9.0+)

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.

    $stmt = db2_prepare($conn, "call $procLib.iPLUGRC32K(?,?,?,?)");
    if (!$stmt) die("Bad prepare: ".db2_stmt_errormsg());
    $clobIn = getxml_part1();
    $count = 1; // 0=run; 1=accumulate input xml
    $clobOut = "";
    $ret=db2_execute($stmt,array($ipc,$ctl,$clobIn,$count));
    if (!$ret) die("Bad execute: ".db2_stmt_errormsg());
    $clobIn = getxml_part2();
    $count = 0; // 0=run; >0=accumulate input xml
    $clobOut = "";
    $ret=db2_execute($stmt,array($ipc,$ctl,$clobIn,$count));
    if (!$ret) die("Bad execute: ".db2_stmt_errormsg());
    while ($row = db2_fetch_array($stmt)){
      $clobOut .= $row[0];
    }
    $clobOut = trim($clobOut);

REST GET (interface)

require_once('connection.inc');

// http GET parms
$clobIn = getxml();
$clobOut = "";
$parm  = "?db2=$i5restdatabase";
$parm .= "&uid=$user";
$parm .= "&pwd=$password";
$parm .= "&ipc=$ipc";
$parm .= "&ctl=$ctl";
$parm .= "&xmlin=".urlencode($clobIn);
$parm .= "&xmlout=32768";  // size expected XML output
// execute
$linkall = "$i5rest".htmlentities($parm);
$getOut = simplexml_load_file($linkall);
// result
if ($getOut) $clobOut = $getOut->asXML();
else $clobOut = "";
// -----------------
// output processing
// -----------------

REST POST (interface)

<?php
// see connection.inc param details ...
require_once('connection.inc');

// http POST parms
$clobIn = getxml();
$clobOut = "";
$postdata = http_build_query(
   array(
     'db2' => "*LOCAL",
     'uid' => $user,
     'pwd' => $password,
     'ipc' => $ipc,
     'ctl' => $ctl,
     'xmlin' => $clobIn,
     'xmlout' => 4096    // 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);
// execute
$linkall = $i5rest;
$result = file_get_contents($linkall, false, $context);
// result
if ($result) {
  $getOut = simplexml_load_string($result);
  $clobOut = $getOut->asXML();
}
else $clobOut = "";
// -----------------
// output processing
// -----------------

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 (Goto Reserved Words).

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;
}

Author(s)

Tony “Ranger” Cairns - IBM i PHP / PASE