XMLRPC Kontó

Dr. Vermes Mátyás1

2001. július

1.  Áttekintés
2.  Elosztó szerver (wrapper)
3.  Jogosultság kezelés
    3.1.  RPCUSER
    3.2.  RPCAUTH
    3.3.  Engedélyezés
4.  Session manager
5.  Indítóscriptek
6.  Szerverek programozása
    6.1.  xmlrpcServer osztály
    6.2.  Példa
7.  CCC kliensek programozása
    7.1.  xmlrpcClient osztály
    7.2.  Példa
8.  CORBA vs. XMLRPC
9.  XMLRPC és CCC típusok megfeleltetése
    9.1.  NIL
    9.2.  String
    9.3.  Szám
    9.4.  Logikai érték
    9.5.  Dátum
    9.6.  Block
    9.7.  Base64
    9.8.  Array
    9.9.  Objektum

1.  Áttekintés

Szeretnénk felállítani egy olyan Kontó kiszolgálót, ami XMLRPC protokollon keresztül szolgáltat adatokat a Kontóból, illetve segítségével műveleteket végezhetünk a Kontóban, pl. lekérhetjük egy ügyfél forgalmát, kivonatát, felvihetünk egy ügyfelet, nyithatunk számlát, rögzíthetünk egy könyvelési tételt. Egy ilyen kiszolgáló rögzíti az üzleti logikát, és elrejti az adattárolás módját. A kliensekbe csak a megjelenítés kerül, ami a bankok igényei szerint többféle lehet anélkül, hogy a Kontó üzleti logikáját meg kellene változtatni, vagy többször implementálni. A megoldás arra is lehetőséget ad, hogy külső programok, mondjuk egy home banking rendszer dokumentált és ellenőrzött módon kapcsolódjon a Kontóhoz.

Az alábbi rétegek egymásra épülését tételezem fel:

1 Jáva Terminál
2 SSL
3 CCC frontend programok
4 XMLPRC (HTTP)
5 Wrapper (elosztó)
6 Speciális CCC szerverek
7 Adatbázis

Ez a táblázat interaktív kliens esetét mutatja. Nem interaktív kliensek-pl. a szerveren batch feldolgozást végző programok-a 3. szinten kapcsolódnak a rendszerbe. A 6. szinten működő szerverek egymás szolgáltatásait (a wrapperen keresztül) korlátozás nélkül igénybe vehetik.

Az XMLRPC protokoll részleteivel nem foglalkozunk, a ccc_socket és a ccc_xmlrpc könyvtár szolgáltatásai minden feladatra elegendőnek látszanak (mindig a legfrissebb változatra van szükség).

2.  Elosztó szerver (wrapper)

A wrapper nem xmlrpc szerver, hanem socket szinten dolgozik, és az üzeneteket mechanikusan közvetíti a felek között. A program egyszerűsíti a szervezést, ui. a résztvevőknek elég csak a wrapper hálózati címét ismerni.

A fentiekből adódóan a program az üzenetközvetítést elég hatékonyan csinálja. Bizonyos xmlrpc requestekre a wrapper közvetlenül válaszol:

A wrapper a system hívásokat sosem adja tovább, és a fenti két metódustól különböző hívások esetén "service not available" kivétel keletkezik. Ugyanezért a wrapperen keresztül nem érhetők el a szerverek által esetlegesen implementált szabványos "system" metódusok.

3.  Jogosultság kezelés

3.1.  RPCUSER

Az xmlrpc-s Kontó felhasználókat külön adatbázisban fogjuk nyilvántartani, így véletlenül sem keveredik össze a kétféle felhasználási mód.A felhasználók az rpcuser állományban vannak felsorolva, ennek szerkezete:

rekordszám   :          4
fejléc hossz :        258
rekord hossz :        193
mezők száma  :          7
UID        C         16          0          2
TID        C         16          0         18
GID        C         64          0         34
NAME       C         64          0         98
PASSWORD   C         16          0        162
STARTDATE  D          8          0        178
ENDDATE    D          8          0        186

A mezők jelentése: UID felhasználói név, TID felhasználó típus, GID csoport azonosító, NAME teljes felhasználói név, PASSWORD jelszó, STARTDATE érvényesség kezdete, ENDDATE érvényesség vége.

A korábbi rendszerhez képest a tid és gid megkülönböztetése a leglényegesebb újdonság. A tid értékei ilyenek lehetnek:

3.2.  RPCAUTH

A egyes felhasználói típusok (tid) számára engedélyezett metódusokat (plusz az engedély módját) tartalmazza rpcauth.

rekordszám   :          8
fejléc hossz :        130
rekord hossz :         50
mezők száma  :          3
TID        C         16          0          2
METHOD     C         32          0         18
PERMISSION C          1          0         50

Itt egy rekord jelentése a következő: A TID típusú felhasználónak a METHOD funkcióhoz PERMISSION engedélye van. A PERMISSION mező értékei lehetnek:

3.3.  Engedélyezés

A csoport azonosító (gid) alapján döntik el a szerverek, hogy az adatbázis egy eleméhez van-e hozzáférése a kliensnek. A csoport azonosító tartalma szerverenként más és más lehet. Az LTP szerver példáján mutatjuk be a gid használatát.

Internetes (lakossági) LTP ügyfél
Az ügyfél csak a saját szerződéséhez férhet hozzá, ezért a customer típusba soroljuk, és gid-nek választjuk a szerződésazonosítót.

LTP ügyintéző (ügynök)
Az ügynök az általa hozott szerződésekhez férhet hozzá, a user típusba soroljuk, és az ügynök azonosítót írjuk a gid-be.

Emelt jogú ügyintéző
Minden adathoz hozzáférhet, ezért a poweruser típusba soroljuk, a gid-et üresen hagyjuk.

Megjegyzem, hogy a METHOD mező nem feltétlenül egy ténylegesen létező megírt metódus neve, hanem egy olyan azonosító, amire a szerverek hivatkozhatnak. Pl. valójában nincs "ltp.read" nevű metódus, az rpcltp szerver mégis ezt ellenőrzi, mielőtt adatot szolgáltatna ki a kliensnek.

4.  Session manager

A Kontó felhasználók beléptetésével, az engedélyek nyilvántartasával foglalkozik az rpcsession szerver.

function main(port)
local server
    set printer to log-rpcsession additive
    set printer on
    set console off
    alertblock({|t,a|xmlrpc_alert(t,a)})
    server:=xmlrpcserverNew(port) 
    server:keepalive:=.t.
    //server:debug:=.t. 
    //server:recover:=.f.
 
    server:addmethod("session.getversion",{|sid|getversion(sid)})
    server:addmethod("session.login",{|u,p|login(u,p)})
    server:addmethod("session.logout",{|sid|logout(sid)})
    server:addmethod("session.validate",{|sid,prolong|validate(sid,prolong)})
    server:addmethod("session.validatex",{|sid,prolong|validatex(sid,prolong)})
    server:addmethod("session.who",{|sid|who(sid)})
    server:addmethod("session.permission",{|sid,module|permission(sid,module)})
    server:addmethod("session.groupid",{|sid|groupid(sid)})
    server:addmethod("session.userid",{|sid|userid(sid)})
    server:addmethod("session.username",{|sid|username(sid)})
    server:addmethod("session.usertype",{|sid|usertype(sid)})
    server:loopfreq:=5000
    server:loopblock:={||fflush()}
    server:closeblock:={|s,r|xmlrpc_verifyconnection(s,r)}
    xmlrpc_register(server,"session",VERSION)
    server:loop
    return NIL

A session szerver jelenleg elég egyszerű, az alábbi néhány funkcióval rendelkezik:

session.login(string user,string passw) string sid
Bejelentkezik a Kontóba. Ha a bejelentkezés sikeres, akkor visszaad egy string (nem base64!) típusú értéket, a sid-et, amivel a kliens a továbbiakban igazolja magát. A sid base64 kódolású, ezért a HTTP üzenetek fejlécében könnyen átküldhető, cookie-ként tárolható. Ha a kliens szünetelteti a tevékenységét, a sid XMLRPC_TIMEOUT idő alatt elévül. Ha a bejelentkezés sikertelen xmlrpc kivétel keletkezik.2

session.logout(string sid) void
A szerver törli a nyilvántartásából a sid-et.

session.getversion(string sid) string version3
Visszaadja a szerver verziószámát.

session.validate(string sid, boolean prolong) boolean success
Megmondja, hogy érvényes-e a sid. Ha prolong true, akkor az érvényes sid timeout mérése újraindul.

session.validatex(string sid, boolean prolong) rpckontosession s
Ellenőrzi, hogy érvényes-e a sid, ha nem, akkor xmlrpc kivétel keletkezik. Ha a sid érvényes, akkor visszaad egy rpckontosession objektumot. Ha prolong true, akkor a timeout mérése újraindul.

session.who(string sid) array list_of_sessions
Ad egy listát a bejelentkezett felhasználókról.

session.permission(string sid, string module) permissions
Ha module egy xmlrpc szervert azonosító string pont nélkül (pl. "ltp"), akkor az eredmény egy array, ami a sid-del meghatározott felhasználó jogait tartalmazza a megadott szerver összes metódusára: {{ltp.method1,p1}, ... }, ahol pi="u", "g", vagy "o". Ha module egy komplett metódusnév ponttal (pl. "ltp.write"), akkor az eredmény egy string: "u", "g", vagy "o".

session.groupid(string sid) string groupid
A visszakapott groupid alapján az alkalmazási program eldöntheti, hogy az aktuális felhasználó jogosult-e az adatbázis egy bizonyos eleméhez hozzáférni. Tartalma éppen ezért alkalmazásfüggő. Például egy interneten át jövő lakossági felhasználó (customer) groupid-je lehet a saját szerződésazonosítója, ami alapján a rendszer ellenőrzi, hogy a customer csak a saját adataihoz férhessen hozzá.

session.userid(string sid) string userid
A felhasználó bejelentkezési nevét adja.

session.username(string sid) string username
A felhasználó teljes nevét adja.

session.usertype(string sid) string usertype
A felhasználó típusát adja, a támogatott típusok: master, poweruser, user, program, customer.

A szerver kiszórja azokat a session-okat, amik az XMLRPC_TIMEOUT környezeti változóban megadott időn keresztül nem mutatnak aktivitást (default 300 sec).

A session szerver egyszerre legfeljebb XMLRPC_MAXSESSION darabszámú session-t engedélyez (default 128).

A session szerver egy felhasználótól maximum XMLRPC_MAXSAMEUID egyidejű bejelentkezést fogad el (default 4).

5.  Indítóscriptek

A szervereket az alábbihoz hasonló scripttel indíthatjuk:

#!/bin/bash
export XMLRPC_WRAPPER=foton,45000
rpcwrapper.exe  45000  &
rpcsession.exe  45001  &
rpcteszt.exe    45002  &

Az XMLRPC_WRAPPER változóban minden szerverrel tudatjuk, hogy hol van a wrapper. Először a wrappert indítjuk el természetesen azon a gépen és porton, amit az előbbi változóban megadtunk.

Ezután elindítjuk a többi szervert. Ha a szervereknek nem adnánk meg explicit portszámot, akkor azok automatikusan választanának maguknak egy szabad portot, ekkor azonban ugyanaz a szerver egyidejűleg több porton is futhat, ami esetleg nem kívánatos. A port explicit megadása esetén, a szerver kilép, ha a megadott port nem szabad.

A szervereknek nem kell ugyanazon a gépen lenniük. A szerverek bármikor utólag is elindíthatók, ez alól csak a wrapper kivétel. Ha ez kilép, akkor a többi szerver is automatikusan kilép (így vannak megírva), és az egész rendszert újra kell indítani.

A klienseket az alábbi módon indítjuk:

#!/bin/bash
export CCC_TERMINAL=dummy
client1.exe  &
client1.exe  &
client1.exe  &
client1.exe  &
client1.exe  &
client1.exe  &
client1.exe  &
client1.exe  &
client1.exe  &
client1.exe  &

A CCC klienseknek általában két paramétert fogunk megadni, a wrapper címét (host nevét) és portszámát. Ha ezek hiányoznak (mint a jelen esetben), akkor a {localhost,45000} default címen próbálkozik. A script a szerver nyúzása céljából egyszerre elindít 10 nem interaktív klienst. Az egész rendszer 50-60 folyamatosan kérdező klienssel még símán működik. Nyilván bír többet is, különösen, ha a klienseket nem kell lokálisan futtatni.

6.  Szerverek programozása

6.1.  xmlrpcServer osztály

A ccc_xmlrpc könyvtárban van definiálva az alábbi xmlrpcServer osztály. Az ccc_xmlrpc könyvtárat a jelen alkalmazás igényei szerint állandóan frissítem, ezért fontos, hogy mindig a legutolsó változattal linkeljünk. Ugyanez áll a ccc_socket könyvtárra is.

function xmlrpcserverClass() 
static clid

    if( clid==NIL )
        clid:=classRegister("xmlrpcserver",{objectClass()})
        classMethod(clid,"initialize",{|this,p|xmlrpcserverIni(this,p)})
        classMethod(clid,"response",{|this,s|xmlrpcserverResponse(this,s)})
        classMethod(clid,"addmethod",{|this,m,b,h,s|xmlrpcserverAddmethod(this,m,b,h,s)})
        classMethod(clid,"loop",{|this|xmlrpcserverLoop(this)})
 
        classMethod(clid,"methodidx",{|this,m|xmlrpcserverMethodIdx(this,m)})
        classMethod(clid,"methodblk",{|this,m|xmlrpcserverMethodBlock(this,m)})
        classMethod(clid,"methodhlp",{|this,m|xmlrpcserverMethodHelp(this,m)})
        classMethod(clid,"methodsig",{|this,m|xmlrpcserverMethodSignature(this,m)})
        classMethod(clid,"methodlst",{|this|xmlrpcserverListMethods(this)})
 
        classAttrib(clid,"port")        //ezen a porton hallgatózik
        classAttrib(clid,"methods")     //metódusok: {{m,b,h,s},...} 
        classAttrib(clid,"keepalive")   //tartja-e a kapcsolatot
        classAttrib(clid,"debug")       //printeli-e a debug infót
        classAttrib(clid,"recover")     //elkapja-e a hibákat
        classAttrib(clid,"server")      //szerver név (HTTP header)
        classAttrib(clid,"evalarray")   //kibontva adja-e át a <params> tagot
        classAttrib(clid,"loopfreq")    //a select timeout-ja (ezred sec-ben)
        classAttrib(clid,"loopblock")   //a select lejártakor végrehajtódik 
        classAttrib(clid,"closeblock")  //minden socket lezárásakor végrehajtódik 
        classAttrib(clid,"socketlist")  //az összes élő socket
        classAttrib(clid,"scklisten")   //ezen a socketen hallgatózik
    end
    return clid

6.2.  Példa

//XMLRPC teszt szerver

static wrapper
 
*****************************************************************************
function main(port)

local server

    set printer to log-rpcteszt additive
    set printer on
    alertblock({|t,a|xmlrpc_alert(t,a)})


Megnyitjuk a logfilét, kikapcsoljuk az alertet.
    
    server:=xmlrpcserverNew(port)
    server:keepalive:=.t.
    //server:debug:=.t.
    //server:recover:=.f.


Létrehozzuk a szerver objektumot, beállítjuk néhány jellemzőjét: a kliensekkel tartjuk a kapcsolatot, debug infót nem nyomtatunk, a hibákat elkapjuk, így hiba esetén automatikusan xmlrpc exception-t kap a kliens.
 
    server:addmethod("teszt.hello",{|sid|hello(sid)})
    server:addmethod("teszt.gettime",{|sid|gettime(sid)})
    server:addmethod("teszt.echo",{|sid,p1,p2,p3,p4,p5,p6|echo(sid,p1,p2,p3,p4,p5,p6)})


Feltöltjük a szervert a metódusokkal. Minden metódushoz tartozik egy kódblokk, ami végre fog hajtódni, ha a kliens meghívja. A szerver automatikusan csinál magának "system.listMethods", "system.methodHelp", "system.methodSignature" metódusokat (xmlrpc ajánlás), bár ezeket a wrapperen keresztül jelenleg nem lehet elérni.
 
    xmlrpc_register(server,"teszt")
    wrapper:=xmlrpc_client()
    server:closeblock:={|s,r|verify_connection(s,r)}


A szerver regisztrálja magát a wrappernél. Létrehoz egy xmlrpcclient objektumot, amit akkor használ, ha kliensként igénybe akarja venni a többi rpc szerver szolgáltatását. Beállítja a szerver closeblock-ját, ez automatikusan végre fog hajtódni, amikor egy socket lezáródik. Ez alkalmat ad a szervernek arra, hogy észrevegye, ha a wrapper kilépett, ilyenkor a program befejeződik.

    server:loop


Elindítjuk a szerver főciklusát, amiben a requestek kiszolgálása történik. A program kilépéséig a loop-ban marad a vezérlés.
 
    return NIL

*****************************************************************************
static function verify_connection(server,r)
local e
    if( server:socketlist[1]==r )
        e:=errorNew()
        e:operation:="verify_connection"
        e:description:="wrapper died"
        eval(errorblock(),e)
    end
    return NIL


A wrapper kilépésének észlelése azon alapszik, hogy a socketlist első eleme mindig a wrapperhez kapcsolódik (xmlrpc_register teszi oda). Alább a metódusok implementációja következik.
 
*****************************************************************************
static function hello(sid)
local uid
    validate_session_id(sid)
    sid:=_chr2arr(base64_decode(sid))
    uid:=sid[1][2]
    return "Hello '"+upper(uid)+"'!"


*****************************************************************************
static function gettime(sid)
    validate_session_id(sid)
    return time()


*****************************************************************************
static function echo(sid,p1,p2,p3,p4,p5,p6)
    validate_session_id(sid)
    return {p1,p2,p3,p4,p5,p6}
 

*****************************************************************************
static function  validate_session_id(sid)
local e
    if( !wrapper:call("session.validate",sid) )
        e:=errorNew()
        e:description:="invalid sid"
        eval(errorblock(),e)
    end
    return NIL

*****************************************************************************

Figyeljük meg, hogy bármi gond van (pl. érvénytelen a sid), egyszerűen el kell szállítani a programot, ezt a szerver loop metódusa el fogja kapni (ha recover==.t.), és automatikusan xmlrpc kivétellé transzformálja, amit elküld a kliensnek. Az egyszerű programhibák miatti elszállásokkal is ez történik, ami megnehezíti a tesztelést, ezért tesztelés céljára a normál hibakezelés visszaállítható (recover==.f.).

7.  CCC kliensek programozása

7.1.  xmlrpcClient osztály

A ccc_xmlrpc könyvtárban van definiálva az alábbi xmlrpcClient osztály. Az ccc_xmlrpc könyvtárat a jelen alkalmazás igényei szerint állandóan frissítem, ezért fontos, hogy mindig a legutolsó változattal linkeljünk. Ugyanez áll a ccc_socket könyvtárra is.

function xmlrpcclientClass() 
static clid

    if( clid==NIL )
        clid:=classRegister("xmlrpcclient",{objectClass()})
        classMethod(clid,"initialize",{|this,host,port|xmlrpcclientIni(this,host,port)})

        classMethod(clid,"call",{|this,method,params|xmlrpcclientCall(this,method,params)})
        classMethod(clid,"close",{|this|xmlrpcclientClose(this)})
        classMethod(clid,"connect",{|this|xmlrpcclientConnect(this)})
        classMethod(clid,"write",{|this,r|xmlrpcclientWrite(this,r)})
        classMethod(clid,"read",{|this|xmlrpcclientRead(this)})
 
        classAttrib(clid,"useragent")  //kliens id (HTTP header)
        classAttrib(clid,"hostname")   //szerver neve/ip címe
        classAttrib(clid,"host")       //szerver ip címe
        classAttrib(clid,"port")       //szerver portszám
        classAttrib(clid,"socket")     //socket (file descriptor)
        classAttrib(clid,"keepalive")  //tartja-e a kapcsolatot
        classAttrib(clid,"debug")      //printeli-e a debug infót
        classAttrib(clid,"URI")        //HTTP header (általában /RPC2)
        classAttrib(clid,"timeout")    //ennyit vár a válaszra (ezred sec)
    end
    return clid

7.2.  Példa

Az alábbi program célja a szerverek nyúzása, nem kell benne különösebb értelmet keresni.

function main(ipaddr,port)

local client, sid, n, cnt:=0

    set printer to ("log-client"+alltrim(str(getpid())))
    set printer on

    if(ipaddr==NIL)
        ipaddr:="localhost"
    end

    if(port==NIL)
        port:=45000
    end
    
    client:=xmlrpcclientNew(ipaddr,port)
    client:keepalive:=.t.
    //client:debug:=.t.


Megvan az új kliens objektum, néhány tulajdonság beállítva.
 
    while( .t. )

        client:call("system.printstate")     
 
        ?? sid:=client:call("session.login",{"vermes","hopp"}); fflush()
        
        for n:=1 to 1024
            cnt++; client:call("session.validate",sid)     
            cnt++; client:call("session.who",sid)     
            cnt++; client:call("teszt.hello",sid)     
            cnt++; client:call("teszt.gettime",sid)     
            cnt++; client:call("teszt.echo",{ sid,1,"A",.t.,{}, date() })     

            ?? cnt; fflush()
        next

        client:call("session.logout",sid)     
        sleep(1000)
    end
    
    return NIL

Az xmlrpc hívás egyszerűen név szerinti függvényhívásnak tekinthető, ahol

    funcname(par1,par2,...)

helyett ezt írjuk:
    client:call("funcname",{par1,par2,...})

Egyetlen paraméter esetén az array-be csomagolás nem kötelező, azt az interfész program automatikusan megteszi. Ha a szerver xmlrpc kivételt ad, akkor a call metódus nem tér vissza, hanem elszáll a program (kiértékelődik az errorblock), ami a normál eszközökkel kezelendő.

8.  CORBA vs. XMLRPC

Szebb a CORBA

A CCC-CORBA objektumos megvalósítása elegánsabb, mint az xmlrpc. A szerver oldalon implementálni kell egy olyan osztályt, ami az IDL-ben megadott minden metódust tartalmaz. Ez a megszokott programozási módszerrel történik. A kliens oldalon használt metódushívás szintaktika egyszerűbb és szebb, mint a név szerinti függvényhívás, ráadásul a kliens oldali (proxy) objektumot előállító kód teljes egészében automatikusan generálódik az IDL-ből.

Praktikusabb az XMLRPC

Az xmlrpc egyszerű komponensekből (HTTP üzenetek plusz szövegfeldolgozást jelentő XML) épül fel, ezért könnyen implementálható mindenféle nyelveken. A körítés (pl. a kapcsolatfelvétel) sincs úgy misztifikálva, mint a CORBA-nál, ezért a külső kliensprogram írók könnyebben elboldogulnak vele. Az is fontos szempont, hogy a saját rendszerünket nem terheljük olyan nehézsúlyú idegen komponenssel, mint egy CORBA könyvtár.

9.  XMLRPC és CCC típusok megfeleltetése

9.1.  NIL

A CCC a NIL értéket az alábbi formában küldi:

<value></value>

Ha közvetlenül az xmlrpc üzenet törzsét vizsgálnánk, akkor megállapítható volna, hogy a CCC program NIL-t, vagy üres stringet (<value><string></string></value>) küldött-e. Mivel azonban a szabvány szerint explicit típusmegjelölés hiányában az adat típusa string, azt mondhatjuk, hogy a NIL érték az xmlrpc üres stringjére képződik.

9.2.  String

Példa string adatra:

<value><string>ez egy string  </string></value>

A szabvány szerint a stringek tetszőleges (akár bináris) adatokat is tartalmazhatnak, kivéve a < és & karaktereket, amiket &lt és &amp formában kell küldeni. Ezért a CCC is csak az előbbi transzformációt végzi a stringeken, de nem trimel, nem végez ékezetes karakter konverziót, stb. Érthetetlen és bosszantó, hogy a PHP xmlrpc interfész okosabb akar lenni a szabványnál, és további karaktereket is kódol, nevezetesen a > karaktert &gt-re és a " karaktert &quot-ra, amivel óhatatlanul zavarokat fog előidézni.

9.3.  Szám

A CCC xmlrpc interfész minden számtípust (i4, int, double) az egyetlen CCC számtípusra konvertál, és küldeni mindig double típust küld, például:

<value><double>100</double></value>

9.4.  Logikai érték

A CCC logikai típus egyértelműen leképezhető xmlrpc-re,

<value><boolean>1</boolean></value>

jelenti a logikai true értéket,

<value><boolean>0</boolean></value>

pedig a logikai false értéket. Megjegyzem, hogy a szabvány szerint az 1 és 0 érték nem helyettesíthető mással, pl. 1 helyett nem felel meg egy nemnulla szám, vagy a true string. A PHP xmlrpc interfész itt is bosszantóan eltér a szabványtól.

9.5.  Dátum

Mivel a Clipperben csak egyszerű date típus van, nem pedig összetett date-time, ezért az xmlrpc dateTime.iso8601 típusának idő részét nem használjuk. Példa:

<value><dateTime.iso8601>20011005T00:00:00</dateTime.iso8601></value>

9.6.  Block

A CCC kódblokk típust az xmlrpc interfész a következő módon küldi: Először a block kiértékelődik, a kiértékelésnek egy szintaktikailag helyes <value> tagot tartalmazó stringet kell eredményeznie. Az interfész ezt a <value>-t fogja küldeni.

9.7.  Base64

A base64 típusról: Három byte-ban összesen 24 bit van. Ha ezt a 24 bitet szétosztjuk négy byte-ra, akkor mindegyikre csak 6 bit jut. A base64 kódolás tehát minden 3 (tetszőleges adatot tartalmazó) byte-ból 4 db 6 bites byte-ot készít, ahol a kimenet csak betű és számkaraktereket tartalmaz, és ezért biztonságosan továbbítható a hálózaton.

A CCC xmlrpc interfész a fogadott base64 típust automatikusan stringre konvertálja (dekódolja). A base64 típus küldéséhez a blockok küldésekor történő automatikus kiértékelést használjuk.

function xmlrpcbase64(x)
    return {||"<base64>"+base64_encode(x)+"</base64>"}

A fenti segédfüggvénnyel tudunk CCC-ből <base64> típust küldeni. A base64_encode(x) és base64_decode(x) segédfüggvények végzik egy string base64 kódolását és visszaalakítását.

9.8.  Array

A CCC array és az xmlrpc array típusok egyértelműen megfeleltethetők egymásnak. Az array tartalmazhatja az előbbi skalár típusokat, ezenkívül tartalmazhat array-t és objektumot, amik tetszőleges mélységben egymásba ágyazhatók. Az üres array (Clipperben {}) így néz ki:

<value><array><data></data></array></value>

9.9.  Objektum

Amikor a CCC egy objektumot küld át paraméterként, vagy visszatérési értékként, akkor az objektum xmlrpc struktúrára konvertálódik. A konverzió az attrvals metódussal veszi elő az objektum név-érték párjait, és minden attribútumból egy xmlrpc membert készít, a member name tagja az attribútum nevét, a value tagja az attribútum értékét fogja tartalmazni. Az alábbi segédosztályban a standard attrvals metódust (trükkösen) felüldefiniáljuk közvetlenül megadható attribútumra, így külön osztálydefiníció nélkül (röptében) is küldhetó struct típus.

 
function xmlrpcstructClass() 
static clid
    if( clid==NIL )
        clid:=classRegister("xmlrpcstruct",{objectClass()})
        classMethod(clid,"initialize",{|this,av|xmlrpcstructIni(this,av)})
        classAttrib(clid,"attrvals") //felüldefiniálás: method -> attr
    end
    return clid

function xmlrpcstructNew(av) 
local clid:=xmlrpcstructClass()
    return objectNew(clid):initialize(av)

function xmlrpcstructIni(this,av) 
    objectIni(this)
    this:attrvals:=av
    return this

Amikor a CCC átvesz egy struktúrát, akkor azt array-re konvertálja {{name1,value1},{name2,value2}, ... } formában, ahol a külső array minden eleme az eredeti struktúra egy member-ének felel meg.


Jegyzetek:

1ComFirm BT.

2 A timeout-ot nem célszerű egy-két percnél hosszabbra állítani. A hosszú timeout eredményeképpen a szerverekben felhalmozódnak a magukra hagyott sessionok arra várva, hogy egyszer talán majd újra jelentkezik a kliens, és folytatja a munkát. Csakhogy az illető kliensprogram esetleg hibás, minek folytán gyakran elszáll, és már sokadszorra indítják újra. Ezért a kliensprogram írók állandó sirámai ellenére rövid timeout-ot kell beállítani, a kliensprogramokat pedig úgy kell megírni, hogy a timeout lejártával képesek legyenek automatikusan újra bejelentkezni.

3 A továbbiakban a sid paraméter egységesen mindenhol a session id-t jelöli.