Consommer des Services WEB



 


 Tout cela étant normalisé aujourd'hui sous le vocable générique de "Web services"

Définitions

Présentation de l'architecture web-services


   

Précisions sur SOAP

Précisions sur WSDL

 


Sur nos systèmes IBM i et AS400 ?

L'OS propose aujourd'hui un nouveau serveur d'application intégré, permettant de déclarer extrèment facilement un programme RPG en tant que service WEB.

 

et le contraire ?


En V6R1, et de manière intégrée au système d'exploitation, nous avons des mécanismes pour accèder à un service web depuis le langage RPG (entre autre).

on dit "consommer un service web"...

 

L'exemple fourni par IBM est basé sur le service ConvertTemp automatiquement créé lors de la création du serveur d'application

 

Allez sur la page

et récupérez le fichier de définition (WSDL) en cliquant sur View definition
(sinon, pour celui-ci uniquement , il est déja présent dans /QIBM/ProdData/OS/WebServices/V1/client)

ensuite, il faut générer le "stub" soit le programme "proxy"

pour cela nous utiliserons l'outil WSDL2WS.SH sous shell (STRQSH)

> /QIBM/ProdData/OS/WebServices/V1/Client/bin/wsdl2ws.sh
   -lc -o/myconverttemp
   /QIBM/ProdData/OS/WebServices/V1/Client/samples/ConvertTemp/ConvertTemp.wsdl


   Code generation completed.
$

 

Si vous passez la commande ls, vous devez voir :

$
> ls /myconverttemp
ConvertTempPortType.c CONVERTTEMPInput.c CONVERTTEMPResult.c
ConvertTempPortType.h CONVERTTEMPInput.h CONVERTTEMPResult.h
$

Détails du fichier wsdl

Extrait de la définition du service
---------------------------------------
<wsdl:portType name="ConvertTempPortType">
  <wsdl:operation name="converttemp_XML">
    <wsdl:input message="axis2:converttemp_XMLRequest" wsaw:Action="urn:converttemp_XML"/>
    <wsdl:output message="axis2:converttemp_XMLResponse" wsaw:Action="urn:converttemp_XMLResponse"/>
  </wsdl:operation>
  <wsdl:operation name="converttemp">
    <wsdl:input message="axis2:converttempRequest" wsaw:Action="urn:converttemp"/>
    <wsdl:output message="axis2:converttempResponse" wsaw:Action="urn:converttempResponse"/>
  </wsdl:operation>
</wsdl:portType>

<wsdl:binding name="ConvertTempSOAP11Binding" type="axis2:ConvertTempPortType">

.../...
</wsdl:binding>

< wsdl:service name="ConvertTemp">
  <wsdl:port name="ConvertTempSOAP11port_http" binding="axis2:ConvertTempSOAP11Binding">
   <soap:address location="http://localhost:10022/web/services/ConvertTemp"/>
  </wsdl:port>
</wsdl:service>

les fonctions C importantes et utilisables en RPG, sont liées au type de port (proche de la notion d'interface en java)

l'outil a généré les fonctions de gestion du "stub" :

puis les fonctions liées aux opérations il faut passer en paramètre le pointeur recu lors de l'initialisation (de type AXISCHANDLE)

La dernière fonction recoit un pointeur sur une structure de type CONVERTTEMPResult

  typedef struct CONVERTTEMPResultTag {
       xsdc__string _TEMPOUT;
  } CONVERTTEMPResult;


dont la mémoire est allouée dynamiquement, il faut donc la libérer par Axis_Delete_CONVERTTEMPResult (voir CONVERTEMPResult.h)

En résumé :

Dans un programme RPG ou nous allons déclarer les fonctions en prototype, puis linker tout ce petit monde...

     h DFTNAME(CNVRTTEMP) 
*********************************************************************
* *
* IBM Web Services Client for ILE *
* *
* FILE NAME: ConvertTempClient.RPGLE *
* *
* DESCRIPTION: Source for ConvertTemp Web service client *
* *
*--------------------------------------------------------------------
* Prototypes for the service interface and operations
* obtained from ConvertTempPortType.h file.
*--------------------------------------------------------------------
DgetStub PR * ExtProc('get_ConvertTempPortType_stub')
D pEndpoint * Value
*
DConvertTemp PR * ExtProc('converttemp')
D pWsStub * Value
D pTempInF * Value
*
DdestroyStub PR ExtProc('destroy_ConvertTempPortType _stub')
D pWsStub * Value
*--------------------------------------------------------------------
* Prototype for function to free dynamic storage obtained from
* CONVERTTEMPResult.h file.
*--------------------------------------------------------------------
DdestroyResult PR ExtProc('Axis_Delete_CONVERTTEMPResult')
D pResult * Value
D size 9B 0 Value
*--------------------------------------------------------------------
* Input data structure used by the converttemp operation. The structure
* layout is obtained from CONVERTTEMPInput.h file.
*--------------------------------------------------------------------
D Input DS
D pTempInF *
*--------------------------------------------------------------------
* Result returned by converttemp operation. The structure layout
* is obtained from CONVERTTEMPResult.h file.
*--------------------------------------------------------------------
D Result DS BASED(ResultP)
D pTempOutC *
*--------------------------------------------------------------------
* Miscellaneous declarations.
*--------------------------------------------------------------------
D WsStubP S *
D Endpoint S 100A
D TempInF S 10A
D ResultP S *
D OutputText S 50A
*--------------------------------------------------------------------
* Program entry point. The input parameter is a character field
* representing the temperature in Fahrenheit.
*--------------------------------------------------------------------
C *ENTRY PLIST
C PARM TEMPIN 32
*--------------------------------------------------------------------
* Web service logic. The code will attempt to invoke a Web
* service in order to convert temperature in Fahrenheit to Celsius
* and then display the results.
*--------------------------------------------------------------------
/FREE
// Get a Web service stub. The host and port for the endpoint may need
// to be changed to match host and port of Web service. You can pass
// *NULL to getStub() if the endpoint in the WS DL file is correct.
Endpoint = 'http://localhost:10022/web/services/ConvertTemp' + X'00';
WsStubP = getStub(%Addr(Endpoint));

// Initialize input values.
TempInF = %trim(TEMPIN) + X'00';
pTempInF = %addr(TempInF);

// Invoke the ConvertTemp Web service operation.
ResultP = ConvertTemp(WsStubP:%Addr(Input));

// Prepare output string depending on what was returned.
if (ResultP <> *NULL);
OutputText = %str(pTempInF) + ' Fahrenheit is '
+ %str(pTempOutC) + ' Celsius.';
else;
OutputText = 'No results returned...';
endif;

// Display results.
dsply OutputText;

// Clean up storage for the result and the Web service stub.
destroyResult(ResultP:0);
destroyStub(WsStubP);
/END-FREE
*--------------------------------------------------------------------
* Done.
*--------------------------------------------------------------------
C seton lr

Compilation

Placez vous dans votre répertoire (/myConvertTemp) par CD

Copier le fichier ConvertTempClient.RPGLE dans votre répertoire
et pensez à modifier (en rouge) l'URL permettant d'accèder au service web

CRTRPGMOD QTEMP/CVTTEMPCL SRCSTMF(ConvertTempClient.rpgle)
  (remarquez la nouveauté V6 permettant de compiler un fichier de l'IFS)

CRTCMOD QTEMP/CVTTEMPIN SRCSTMF(ConvertTempInput.c)
 INCDIR('/qibm/proddata/os/webservices/v1/client/include') ENUM(*int)
CRTCMOD QTEMP/CVTTEMPRES SRCSTMF(ConvertTempResult.c)
 INCDIR('/qibm/proddata/os/webservices/v1/client/include') ENUM(*int)
CRTCMOD QTEMP/CVTTEMPPT SRCSTMF(ConvertTempPortType.c)
 INCDIR('/qibm/proddata/os/webservices/v1/client/include') ENUM(*int)

puis

CRTPGM PGM(MABIB/CVTTEMP) MODULE(QTEMP/CVTTEMPCL +
 QTEMP/CVTTEMPIN QTEMP/CVTTEMPRES QTEMP/CVTTEMPPT°
 BNDSRVPGM(QSYSDIR/QAXIS10CC)

test

CALL PGM(CVTTEMP) PARM('100')
DSPLY 100 Fahrenheit is 37.77 Celsius.



Ca marche !

 

En début 2011 les PTF suivantes SI42234 (V5R40) , SI42236 (V6R10) , SI42235 (v7) apportent un nouvel utilitaire wsdl2rpg

comme le précédent, il se tourve dans QIBM/ProdData/OS/WebServices/V1/Client/bin/

> /QIBM/ProdData/OS/WebServices/V1/Client/bin/wsdl2rpg.sh
   -o/myconvertRPG
   -s/QSYS.LIB/MABIB.LIB/CONVERT.SRVPGM
   /QIBM/ProdData/OS/WebServices/V1/Client/samples/ConvertTemp/ConvertTemp.wsdl


Code generation completed. Generated files in directory
'/myconvertRPG'.

Attempting to create service program...

Service program created. Service program is
'/QSYS.LIB/MABIB.LIB/CONVERT.SRVPGM'.

$

L'option -T demande la création d'un programme de service dans la bibliothèque MABIB.

Cette fois il génère aussi du RPG, soit 6 fichiers en plus, portant le nom du type de port (ConvertTempPortType dans nos exemples)

ConvertTempPortType.CL le CL de création du *SRVPGM
ConvertTempPortType.rpgleinc fichier à inclure (dans votre programme)
ConvertTempPortType.rpgle le code d'invoquation du web service (*SRVPGM)
ConvertTempPortType_util.rpgleinc fichier à inclure (dans le source suivant)
ConvertTempPortType_util.rpgle divers utilitaires
ConvertTempPortType__xsdtypes.rpgleinc différents types de données standards

le fichier de référence est le ConvertTempPortType.rpgleinc qui contient les définitions dont vous avez besoin, et qu'il faut inclure dans votre code :


Compilez ce source par CRTRPGMOD.

Attention, pour des problèmes de code page, vous devrez peut-être modifier légèrement le fichier à inclure
et remplacer les @ de fin par à, sur les trois fonctions vues plus haut ou bien recompiler le *SRVPGM.

Normalement, les dernière PTF corrigent ce bug, l'opération n'est plus utile.

 

Si vous devez travailler en HTTPS (avec un certificat)

 

  1. Allez chercher le certificat sur le serveur concerné
  2. Installez le certficat sous Digital Certificat Manager
  3. Dans votre code, utilisez l'API axiscStubSetSecure(), après avoir ajouté le /copy
  /copy /QIBM/ProdData/OS/WebServices/V1/client/include/Axis.rpgleinc
* qui contient
D* axiscStubSetSecure...
D* PR EXTPROC('axiscStubSetSecure')
D* pStub * Value
D* pKeyRingFile * Value OPTIONS(*STRING)
D* pKeyRingSorP * Value OPTIONS(*STRING : *NOPASS)
D* pKeyRingLabel * Value OPTIONS(*STRING : *NOPASS)
D* pV2Cipher * Value OPTIONS(*STRING : *NOPASS)
D* pV3Cipher * Value OPTIONS(*STRING : *NOPASS)
D* pTLSCipher * Value OPTIONS(*STRING : *NOPASS)

/free

if stub_create_convertTempPortType;
axiscStubSetSecure(WsStub.handle:
'/QIBM/USERDATA/ICSS/CERT/SERVER/DEFAULT.KDB':
'motdepasse': 'label':'NONE':'05':'NONE');

Si vous recevez HTTPTransportException: HTTPS transport error.GSKit Error is 428 - No certificate is available for SSL processing.
  ne mettez rien 3eme paramètre (remplacez motdepasse par une chaine vide)

 

Si vous rencontrez des problèmes, lancez une trace par axiscAxisStartTrace(’/tmp/axis.log’:*NULL) en début de pgm.

 

tous les détails sur ces API : http://www-03.ibm.com/systems/resources/systems_i_software_iws_pdf_WebServicesClient_new.pdf



Dernier point, vous pouvez linker tout ce petit monde, par :

 

Essayons .

 

Cette nouvelle version, permet un code RPG nettement simplifié, tant mieux !

 

Attention.

avec le niveau 9 de SF99713 (printemps 2015) les fonctions sont renommées :



cette version introduit un bug (en Français) : un paramètre est nommé #_return -> voir l'APAR SE62384

La PTF SI57437 corrige cela.

 

Remarquez dans la fenêtre structure, le prototype pour l'opération (suite au /copy)



les paramètres sont en fait des DS possédant deux sous-zones significatives

Par exemple celui-ci attend des paramètres plus simples (voyez le .wsdl)



ce qui génère (par wsdl2rpg)


le code suivant est cohérent (et fonctionne) :


Enfin, si vous devez travailler en HTTPS (avec un certificat)

  1. Allez chercher le certificat sur le serveur concerné
  2. Installez le certificat sous Digital Certificat Manager
  3. Dans votre code, utilisez l'API axiscStubSetSecure(), après avoir ajouté le /copy
  /copy /QIBM/ProdData/OS/WebServices/V1/client/include/Axis.rpgleinc
* qui contient
D* axiscStubSetSecure...
D* PR EXTPROC('axiscStubSetSecure')
D* pStub * Value
D* pKeyRingFile * Value OPTIONS(*STRING)
D* pKeyRingSorP * Value OPTIONS(*STRING : *NOPASS)
D* pKeyRingLabel * Value OPTIONS(*STRING : *NOPASS)
D* pV2Cipher * Value OPTIONS(*STRING : *NOPASS)
D* pV3Cipher * Value OPTIONS(*STRING : *NOPASS)
D* pTLSv1Cipher * Value OPTIONS(*STRING : *NOPASS)
D* pTKSv11Cipher * Value OPTIONS(*STRING : *NOPASS)
D* pTKSv12Cipher * Value OPTIONS(*STRING : *NOPASS)
* ce dernier paramètre permet une tolérance (date dépassée par ex.)
D* pTolerate * Value OPTIONS(*STRING : *NOPASS)


/free

NONE = 'NONE' + x'00;
TRUE = 'true' + x'00';

if stub_create_convertTempServices;
axiscStubSetSecure(WsStub.handle:
'/QIBM/USERDATA/ICSS/CERT/SERVER/DEFAULT.KDB':
'motdepasse': 'label':NONE:NONE:NONE:NONE:x'00':TRUE);
// activation TLSv12 + tolérance

Si vous recevez HTTPTransportException: HTTPS transport error.GSKit Error is 428 - No certificate is available for SSL processing.
  ne mettez rien en 3eme paramètre (remplacez motdepasse par une chaine vide)


Si vous avez des temps de réponses particulièrement longs, utilisez axiscStubSetTransportConnectTimeout(handle, nb-de-secondes)

Si vous rencontrez des problèmes, lancez une trace par axiscAxisStartTrace(’/tmp/axis.log’:*NULL) en début de pgm.
( axiscAxisStopTrace permet d'arrêter la trace)


Pour vous sortir à l'aide d'un proxy

  /copy /QIBM/ProdData/OS/WebServices/V1/client/include/Axis.rpgleinc

if stub_create_convertTempServices;
axiscStubSetProxy(WsStub.handle: 'adresse-proxy' : 'port');
axiscStubSetProxyUserName(WsStub.handle: 'proxy-user');
axiscStubSetProxyPassword(WsStub.handle: 'mot de passe');


Pour un accès avec authentification

  /copy /QIBM/ProdData/OS/WebServices/V1/client/include/Axis.rpgleinc

if stub_create_convertTempServices;
axiscStubSetUserName(WsStub.handle: 'HTTP-user');
axiscStubSetPassword(WsStub.handle: 'mot de passe');

tous les détails sur ces API : http://www-03.ibm.com/systems/resources/systems_i_software_iws_pdf_WebServicesClient_new.pdf

 

Enfin, depuis SF99368 Level 52, SF99713 Level 26, SF99722 Level 13 (Décembre 2017), nos pouvons récupérer les erreurs SOAP générées par le serveur

 

Exemple

 SoapFault = axiscStubGetSOAPFault(WsStub.handle) ; 
if SoapFault <> *NULL;
ErrCode = %str(axiscSOAPFaultGetFaultCode(SoapFault));
ErrChaine = %str(axiscSOAPFaultGetFaultString(SoapFault));
ErrActeur = %str(axiscSOAPFaultGetFaultActor(SoapFault));
ErrDetail = %str(axiscSOAPFaultGetSimpleFaultDetail(SoapFault));
endif;


En Mai 2016, IBM propose aussi d'utiliser directement les API Axis pour consommer des web services REST

il vous faut :

7.3: SI60805, SI60808
7.2: SI60806, SI60809
7.1: SI60807, SI60810

Avec,

7.3 : SI63730, SI63759
7.2 : SI63729, SI63760
7.1 : SI63727, SI63761

Exemple

**free
Ctl-Opt ;
// pour compiler CRTRPGMOD puis CRTPGM BNDSRVPGM((QSYSDIR/QAXIS10CC))
//puis
// ADDLNK OBJ('/qsys.lib/af4test.lib/datanantes.pgm') NEWLNK('/home/CM/datanantes')
// pour tester, sous QSH-> datanantes xml
// ********************************************************************
dcl-pi *N;
wformat char(4);
END-PI;
dcl-s format char(4);
/COPY /QIBM/ProdData/OS/WebServices/V1/client/include/Axis.rpgleinc
DCL-S rc         INT(10);
DCL-S tHandle POINTER;
DCL-S uri        CHAR(200);
DCL-S response CHAR(32768);
DCL-S request CHAR(32768);
DCL-S propBuf CHAR(100);
// --------------------------------------------------------------------
// Web service REST : liste des aires de co-voiturage à Nantes.
// --------------------------------------------------------------------

// choix du format (xml,json,csv, ...)
if %parms() <1;
format = 'xml';
else;
format = wformat;
ENDIF;

// récupération des data (liste des aires de co-voiturage à Nantes)
uri =
'http://data.nantes.fr/api/publication/24440040400129_NM_NM_00003/LOC_AIRES_COV_NM_STBL/content/?format=' + format;
PRINT ('==Retourne les aires de co-voiturage sur ');
PRINT (uri);

// Connexion.
tHandle = axiscTransportCreate(uri:AXISC_PROTOCOL_HTTP11);
if (tHandle = *NULL);
PRINT ('TransportCreate() failed');
return;
endif;

// méthode GET
propBuf = 'GET' + X'00';
axiscTransportSetProperty(tHandle: AXISC_PROPERTY_HTTP_METHOD: %addr(propBuf));

// Exécution
flushAndReceiveData();
// Déconnexion.
axiscTransportDestroy(tHandle);
*INLR=*ON;
// code des procédures extrait de developerWorks
// =============================================
// Print to standard out (under QSH)
// =============================================
DCL-PROC PRINT ;
dcl-pi *n;
msg varchar(32767) const;
end-pi;
  dcl-pr printf extproc(*dclcase);
template pointer value options(*string);
dummy int(10) value options(*nopass);
end-pr;
  dcl-c NEWLINE CONST(x'15');
  printf(%TRIM(msg) + NEWLINE);
END-PROC;
// =========================================
// Handle error
// =========================================
DCL-PROC checkError ;
dcl-pi *n;
msg varchar(5000) const;
end-pi;
 DCL-S axisCode   INT(10);
DCL-S statusCode POINTER;
DCL-S rc INT(10);
 axisCode = axiscTransportGetLastErrorCode(tHandle);
PRINT (msg + ' call failed: ' +
%CHAR(axisCode) + ':' +
%STR(axiscTransportGetLastError(tHandle)));
 if (axisCode = EXC_TRANSPORT_HTTP_EXCEPTION);
rc = axiscTransportGetProperty(tHandle:
AXISC_PROPERTY_HTTP_STATUS_CODE: %ADDR(statusCode));
PRINT ('HTTP Status code: ' + %STR(statusCode));
endif;
END-PROC;
// =========================================
// Flush and Receive data
// =========================================
DCL-PROC flushAndReceiveData;
dcl-pi *n;
end-pi;
 DCL-S header     POINTER;
DCL-S property CHAR(100);
DCL-S bytesRead INT(10) inz(0);
 clear response;
clear header;
 // Flush data so request is sent
rc = axiscTransportFlush(tHandle);
if (rc = -1);
checkError ('TransportFlush()');
return;
endif;
 // Receive data and print out data and response to stdout
rc = axiscTransportReceive(tHandle: %ADDR(response): %SIZE(response): 0);
if (rc = 0);
PRINT ('No data to read');
else;
dow rc > 0 AND bytesRead < %SIZE(response);
bytesRead = bytesRead + rc;
rc = axiscTransportReceive(tHandle:
%ADDR(response)+bytesRead:
%SIZE(response)-bytesRead:
0);
enddo;
endif;
 if (rc = -1);
checkError ('TransportReceive()');
elseif (bytesRead > 0);
PRINT ('Bytes read: ' + %CHAR(bytesRead));
PRINT ('Data: ' + response);
endif;
 if (rc > -1);
rc = axiscTransportGetProperty(tHandle:
AXISC_PROPERTY_HTTP_STATUS_CODE:
%addr(header));
if (rc = -1);
checkError ('TransportGetProperty()');
else;
PRINT ('HTTP status code: ' + %str(header));
endif;
endif;
END-PROC;

Testons

 

Copyright © 1995,2015 VOLUBIS