Soit une liste des cours AF400 , en HTML :
<h3>Liste des cours AF400 (© Volubis)</h3> |
ce qui s'affiche :
(voir le fichier complet, le source du pgm ayant réalisé cela)
Voici la manière dont RDP, montre ce fichier :
Les règles du jeu XML
Elles sont extrêmement simples. Les informations doivent être :
- soit encadrées par des balises ouvrantes(ex. <LIVRE>) et fermantes (ex. </LIVRE>) (contrairement à HTML où ses ces dernières n'étaient pas toujours obligatoires). On parle alors d'éléments. Les éléments doivent s'imbriquer proprement les uns dans les autres : aucun chevauchement n'est autorisé. Les éléments vides sont permis, selon le format <ELEMENTVIDE/>.
- soit incluses à l'intérieur même des balises : on parle alors d'attributs. Exemple : <LIVRE SUJET="XML">. Ici l'attribut SUJET de l'élément LIVRE a la valeur "XML" . En XML, contrairement à HTML, les valeurs des entités doivent toujours être encadrées par des guillemets (simples ou doubles).
- Soit encore définies sous forme d'entités. Les entités sont des abréviations. Par ex; si "Extensible Markup Language" est déclaré comme une entité associée à la notation "xml"; cette chaîne de caractères pourra être abrégée en "&xml;" dans tout le fichier XML. Une entité peut aussi représenter un fichier XML externe tout entier. (inclusion de fichier XML)
Les caractères < , > , & et " doivent être remplacés par les entités < , > , & et "e;dans les valeurs de type texte.
- le document peut enfin contenir :
- des commentaires sous la forme <!-- le commentaire -->
- des processing instructions (PI) c'est à dire des instructions destinées à une application particulière sous la forme <?nom-application instructions-spécifiques?>
par exemple, une "processing instruction" destinées à XML lui même commence souvent les documents XML.
- <?xml version="1.0" encoding="ISO-8859-1" ?> (version de XML et jeu de caractères des données, ici "latin-1", c.a.d le nôtre)
- La structure arborescente du document XML (intitulé des balises, imbrications des balises, caractère obligatoire ou facultatif des balises et de leur ordre de succession) peut être déclarée formellement dans le corps du document XML ou dans un fichier séparé.
Cette déclaration s'appelle une Définition de Type de Document (DTD) ou un schéma XML (XSD)un schéma est lui même un fichier XML qui en décrit un autre.
commençons par une partie d'entête :
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<xsd:annotation>
<xsd:documentation xlm:lang="fr">
XML Schema pour la lsite des cours AF400.
</xsd:documentation>
</xsd:annotation>puis la partie descriptive en elle même:
<xsd:element name="AF400" type="AF400Type"/>
<xsd:complexType name="AF400Type">
<xsd:sequence>
<xsd:element name="COURS" type="CoursType" minOccurs="1"/>
</xsd:sequence>
</xsd:complexType><xsd:complexType name="CoursType">
<xsd:sequence>
<xsd:element name="TEXTE" type="xsd:string"/>
<xsd:element name="TYPE" type="xsd:string"/>
<xsd:element name="SRCFIL" type="xsd:string"/>
<xsd:element name="SRCLIB" type="xsd:string"/>
<xsd:element name="SRCMBR" type="xsd:string"/>
<xsd:element name="CHEMIN" type="xsd:string"/>
<xsd:element name="SUJET" type="xsd:string"/>
<xsd:element name="MOT-DIRECTEUR type="MotType"/>
<xsd:element name="DATE" type="xsd:string"/>
</xsd:sequence>
< /xsd:complexType><xsd:complexType name="MotType">
<xsd:sequence>
<xsd:element name="MOTCLE1 type="xsd:string" minOccurs="1"/>
<xsd:element name="MOTCLE2 type="xsd:string"/>
<xsd:element name="MOTCLE3 type="xsd:string"/>
<xsd:element name="MOTCLE4 type="xsd:string"/>
<xsd:element name="MOTCLE5 type="xsd:string"/>
</xsd:sequence>
< /xsd:complexType>On peut définir un élément (xsd:element) ou un attribut (xsd:attribut), chacun pouvant faire référence à un type
(voyez ici la liste des types admis dans un schéma XML) comme xsd:nonNegativeNumber
ou bien faire référence à un type définit dans le scéma.
Les types définis peuvent être :
- xsd:complexType (structure)
- xsd:simpleType (donnée élémentaire, mais subissant, par exemple, des contrôles particuliers)
Lorsqu'un document XML possède une DTD ou un Schéma associé et la/le respecte, on dit qu'il est valide.
Lorsqu'il respecte seulement les règles de la grammaire XML (balises fermées, correctement imbriquées) on dit qu'il est bien formé.
Aujourd'hui JSON est de plus en plus utilisé.
JSON (JavaScript Object Notation) est un format de données textuelles dérivé de la notation des objets du langage JavaScript (Wikipedia)
ce qui s'écrit comme cela en XML s'écrit comme cela en JSON <producteurs>
<producteur>
<numero>45</numero>
<commune>Reims</commune>
<appellation>13</appellation>
</producteur>
</producteurs>-> {
"producteurs": {
"producteur": {
"numero":45,
"commune":"Reims",
"appellation":13
}
}
}
D'ailleurs vous trouverez ici deux fonctions bien pratiques XML2JSON et JSON2XML écrites en Java.
Un élément peut contenir un objet, une valeur ou un tableau de valeurs, marqué alors par [ et ]
<PO> <id>103</id>
<orderDate>2014-06-20</orderDate>
<customer>
<cid>888</cid>
</customer> <items>
<item>
<partNum>872-AA</partNum>
<shipDate>2014-06-21</shipDate>
<productName>Lawnmower</productName>
<USPrice>749.99</USPrice>
<quantity>1</quantity>
</item>
<item>
<partNum>837-CM</partNum>
<productName>Digital Camera</productName>
<USPrice>199.99</USPrice>
<quantity>2</quantity>
<comment>2014-06-22</comment>
</item>
</items>
</PO>devient (remarquez le tableau de "item")
{ "PO":{ "id": 103, "orderDate": "2014-06-20", "customer": {"@cid": 888}, "items": { "item": [ { "partNum": "872-AA", "productName": "Lawnmower", "quantity": 1, "USPrice": 749.99, "shipDate": "2014-06-21" }, { "partNum": "837-CM", "productName": "Digital Camera", "quantity": 2, "USPrice": 199.99, "comment": "2014-06-22" } ] } } }
L'état de l'art est aujourd'hui de travailler dans une architecture dite 3 tiers, c'est à dire en découplant le serveur de traitement (les programmes souvent placés avec le serveur WEB) des données (pouvant être situées sur un serveur éloigné).
Cette technique est implémentée avec les serveurs
d'application ou serveurs de servlet.
Websphere Application Server(WAS) ou TOMCAT par
exemple, mais aussi ".NET" de Microsoft.
Il s'agit d'écrire des programmes JAVA (ou C# ou PHP)
s'exécutant sur le serveur et non sur le poste client , le
serveur d'application assurant le lien entre le serveur WEB et la JVM
(machine virtuelle java).Ces programmes java pouvant être des
classes autonomes (servlet) générant du HTML :
ou contenus dans des pages JSP : pages HTML faisant
références à des objets externes [des beans].
Les pages JSP permettant d'intégrer du HTML (conçu par
un graphiste) et du code JAVA (écrit par un développeur)
dans un même fichier.
Aujourd'hui, pour une application WEB, l'architecture logique doit
être :
Les web services sont des briques logicielles "en ligne" et permettent de créer des applications distribuées et accessibles depuis n'importe quel navigateur xml. Ces briques sont référencés dans l'annuaire UDDI, décrite selon la norme WSDL (dérivée de XML) et opérent avec d'autres briques selon le protocole SOAP. |
Un web-service : c'est un logiciel qui interagis avec d'autres au moyen de protocoles universels (http, xml...)
<?xml version="1.0"?> |
<?xml version="1.0" encoding="UTF-8"?> |
<?xml version="1.0"?> |
<pcml
version="1.0"> <!-- Create a Data Structure --> <struct name ="custinfo" > < data name ="Number" type="char" length= " 7" usage="inputoutput" init="0014400"> </data> < data name ="Name" type="char" length= " 40" usage="inputoutput" init=" "> </data> </struct> <!-- Program getcust --> <program name="getcust" path="/QSYS.lib/MABIB.lib/GETCUST.pgm" > < data name ="gotback" type=" struct" usage="inputoutput" struct="custinfo"> </data> </program > </pcml> |
public static void
main(String[] argv) { AS400 as400System = new AS400(); ProgramCallDocument pcml = null; String msgId, msgText; Object value = null; try { System.out. println ("Creating ProgramCallDocument for GetCust pgm."); pcml = new ProgramCallDocument(as400System, "GETCUST"); boolean ok = pcml.callProgram("getcust"); System.out.println(" rc is---> " + rc); if (!ok) { /* Retrieve list of AS/400 messages & display them */ } else { value = pcml.getValue("getcust.gotback.Name"); System.out.println("Customer name: " + value); } } catch (PcmlException exc) { System.out.println("*** Call to getcust failed. ***"); Sy ste m. exit (0); } System.exit(0 ); } // end main method |
WDS Client V7et RDI for SOA contiennent les mêmes fonctionnalités (mais avec un
nouvel assistant pour RDI)
Il faut cliquez droit à partir d'un source RPG4 (ILE uniquement)
Indiquez que vous souhaitez générer un client de test.
Cliquez sur parcourir pour modifier les caractéristiques du service
1/ le choix *SRVPGM ou *PGM
2/ par le bouton Modifier, la connexion :
Vous devrez indiquer aussi, le(les) type(s) de service(s) à générer
il vous faudra démarrer le serveur WAS de test sur votre poste.
Confirmer le lancement du test
la page de test est générée puis lancée
Enfin, l'intégration de
code-opération pour manipuler le XML et l'arrivée
d'outils pour travailler sur les sockets (outils IBM ou projets
indépendants comme iSockets ou HTTPApi
de Scott Klement ) permettent aujourd'hui de consommer des services WEB
depuis un RPG dès la version 5.
Il s'agit du nouveau serveur d'application intégré à l'OS pour distribuer les applications Java., remplaçant Tomcat, qui n'est plus fourni avec la V6 et en V5R40, via PTF
Ce serveur support JSF, JSP, servlets et services web, et implique peu de ressources et d'administration, c'est le même que celui utilisé par DB2 WebQUERY, il est basé sur OSGI, puis une version de WAS nommée Liberty profile
Lancez le serveur d'administration HTTP, si ce n'est déjà fait
par STRTCPSVR *HTTP HTTPSVR(*ADMIN)
Puis loggez vous sur http://<votreas400>:2001/HTTPAdmin/
Suivant les versions, vous allez déployer votre premier service web en même temps que la création du serveur d'application ou dans un deuxième temps
Ici un programme W_RECAP de BDVIN0, attendant une zone PR_CODE, et retournant
une DS nommée INFOCENTRE,
ce pgm RPG a été compilé avec PGMINFO(*PCML:*MODULE)
, en COBOL ->PROCESS OPTIONS PGMINFO(PCML MODULE)
en V5R40 il faut SI27065 pour
activer cette option.
SI55531 (7.2) et SI55340 (7.1) apportent l'option *DCLCASE permettant de demander le respect de la casse quant aux noms dans le PCML
Si vos programmes sont dans un ASP indépendant, Utilisez plutôt l'option Browse...
Et indiquez /iASP/QSYS.LIB/mabib.LIB/monpgm.PGM ou monsrvpgm.SRVPGM
ou /iASP est le point de montage de votre ASP indépendant
Indiquez ensuite, le nom public du service et un texte explicatif
puis, précisez le sens d'utilisation des paramètres (automatiquement découverts par l'assistant grâce à PGMINFO)
Si vous avez oublié l'option PGMINFO ou si vous appelez un CL (CLLE et non CLP), il faudra faire référence à un fichier PCML, pour faire apparaitre les paramètres SI vous voulez lancer un COBOL (non ILE) ou un RPG 3, faite un CLLE qui appelle pour vous. un fichier PCML devant être structuré comme suit :
Dans un ASP indépendant
avec une structure :
|
voici les types reconnus par PCML : char | int | packed | zoned | float | byte | struct
Pour infos, voici un extrait du programme déployé ici, en ce qui concerne les paramètres
:
H PGMINFO(*PCML : *MODULE) DPR_code S 6 0 DINFOCENTRE E DS qualified
C *entry plist
C parm pr_code
C parm infocentre
En free
Ctl-opt PGMINFO(*PCML : *MODULE); Dcl-DS INFOCENTRE_T EXT EXTNAME('INFOCENTRE') Qualified Template; DCL-PI *n;
pr_code packed(6:0);
infocentre likeds(infocentre_T);
End-PI;
Les groupes PTF de 2009, apportent la possibilité de gérer en variable (integer uniquement) le nombre d'occurrences d'une structure
Enfin en Juin 2015, SI56823(7.2) propose de rechercher automatiquement le nombre d'occurrences dans une variable placée avant la DS et nommée comme la DS suivit de _LENGTH
si vous décochez l'option, vous retrouvez l'affichage du dessus permettant de saisir le nbr d'occurrences
Cela permet de gérer les DS imbriquées :
Indiquez ensuite le profil utilisateur qui lancera le programme
indiquez aussi, la liste des bibliothèques à utiliser
Si vous travaillez avec un ASP indépendant, vous saisirez /IASP/QSYS.LIB/AUTREBIB.LIB
et voilà, vérifiez les informations récapitulatives (regardez les ports IP attribués automatiquement)
liste des services (l'assistant créé un exemple nommé ConvertTemp)
Les opérations admises
soit les informations que l'on peut demander :
w_recap, les paramètres retour transmis dans l'enveloppe SOAP
w_recap_XML, les données transmises dans un flux XML
Le serveur se créé
Puis, vous basculez automatiquement sur la gestion du serveur d'application de type web services V1.3, cette fois.
Remarquez l'ajout automatique du service exemple convertTemp
Vous pouvez, ici, revenir gérer les services déployés, ou bien, en déployer de nouveaux. (Manage…).
(properties, permet de changer le profil ou d'éditer le fichier WSDL)
le WSDL pouvant être produit dynamiquement ou de manière statique (nouveauté) et donc être édité par vos soins
passez la paramètre "Web service endpoint generation" à Static, appliquez, puis éditer le wsdl.
Important si vous masquez votre Serveur IBM i derrière un nom de domaine public.
Il faudra alors remplacer (par exemple)
<soap:address location="http://as400.volubis.intra:10030/web/services/W_RECAP"/>
par
<soap:address location="http://www.volubis.fr:10030/web/services/W_RECAP"/>
Mais surtout, vous pourrez tester votre service web, par le bouton "Test Service"
Choisissez l'opération (w_recap ou w_recap_XML), ici w_recap
Ajoutez un paramètre en entrée, PR _CODE est automatiquement proposé et vous ne pourrez l'ajouter qu'une seule fois
puis ajoutez une valeur (là aussi, une seule occurrence, dans ce cas) et cliquez sur GO
la réponse vous est affichée dans la fenêtre Status
Ci dessous, le résultat de w_recap_XML
Vous pouvez tester aussi avec des produits standard comme soapUI
Installez et lancez le produit.
Créez un nouveau projet en indiquant les coordonnées pour acquérir
le fichier WSDL sur le system i.
renseignez les valeurs envoyées en remplacant les ? par vos données
(ici le producteur 45)
lancez (flèche verte en haut à gauche de la fenêtre Request1)
Si vous rencontrez des problèmes, voyez
Octobre 2008, une PTF PTF SI32432 apporte les scripts suivants qui à ce jour ne sont donc pas documentés, dans /Qibm/ProdData/os/webservices/V1/server/bin
pour le passage en production d'un service WEB, il faut passer en production l'objet *PGM (l'exécutable) et l'enregistrer ensuite par le script installWebService.shcreateWebServicesServer.sh
deleteWebServicesServer.sh
getWebServiceProperties.sh
getWebServicesServerProperties.sh
installWebService.sh
listWebServices.sh
listWebServicesServers.sh
setWebServiceProperties.sh
setWebServicesServerProperties.sh
startWebService.sh
startWebServicesServer.sh
stopWebService.sh
stopWebServicesServer.sh
uninstallWebService.sh
les derniers correctifs (2015) proposent un nouveau paramètre -parameterUsage
permettant d'indiquer le sens d'utilisation des paramètres i (input) , o (output) , io (input/output)
-parameterUsage GetClientDEp:i,o:UpdateClientCA:i,i,io
pour vous familiariser avec les scripts, passez la commande QSH (qui est un émulateur de shell Unix sur IBM i) sur une ligne de commande OS/400
puis CD /qibm/proddata/os/webservices/v1/server/bin
ls vous affiche le contenu du répertoire, pwd vous affiche le répertoire
en cours, etc...
installWebservice.sh -help Command usage:
installWebService.sh -server 'server-name' -programObject 'program-object'
[-service 'service-name'] [-pcml 'pcml-file'] [-userid 'userid']
[-detectFieldLengths] [-serviceType '*SOAP11|*SOAP12|*REST']
[-targetNamespace 'target-namespace']
[-parameterUsage 'parameter-list'] [-propertiesFile 'property-file']
[-libraryList 'library-list'] [-libraryListPosition '*FIRST|*LAST']
[-disableNillableWSDLElements] [-disableOptionalWSDLElements]
[-addUnderscoreToWSDLElementNames] [-printErrorDetails] [-help]
Quelques Exemples :
installWebService.sh -server 'WFORMATION' -programObject '/QSYS.LIB/FORMATIONX.LIB/GETVINS.PGM' -service 'GETVINJ' -userid 'CM' -detectFieldLengths -serviceType '*REST' -propertiesFile '/home/CM/getvins.properties' -libraryList 'FORMATIONX;BDVIN1' -libraryListPosition '*LAST'
IWS00102I - Command completed successfully. |
|
getWebServiceProperties.sh -server WFORMATION -service GETVINJ Name: GETVINJ |
|
stopWebservicesServer.sh -server WFORMATION IWS00102I - Command completed successfully. $ startWebservicesServer.sh -server WFORMATION IWS00102I - Command completed successfully. $ |
|
saveWebServices.sh -server WFORMATION -saveFile /QSYS.LIB/LIBSAVF.LIB/WFORMATION.FILE -serviceList *ALL IWS00102I - Command completed successfully. $ |
pour passer la commande directement depuis un CL
QSH CMD('/qibm/proddata/os/webservices/V1/server/bin/<votrescript.sh> paramètre1
paramètre2 ...')
en 2010, un groupe PTF permet de déléguer des droits via l'administration graphique.
Sans cette délégation, il faut être *ALLOBJ et *IOSYSCFG pour administrer les serveurs mais aussi déployer les web services.
Ajoutons un utilisateur à la liste des utilisateurs autorisés :
Il faut ensuite spécifier les droits (globalement par type de serveur ou serveur par serveur)
Enfin, les Groupes PTF de 2012 (SF99115 level 23 et SF99368 level 11) améliorent le serveur de Web service pour le faire utiliser Axis 1.5 (à la place de 1.3)
(voyez ce Wiki pour accéder aux dernières nouveautés)
Une fois installées ces correctifs vous verrez apparaître une option de migration
Vous aurez aussi d'autres options :
QtmhGetEnv
+ char(?) variable pour recevoir le paramètre à lire
+ int(10) lg de la variable (ce que je transmet)
+ int(10) lg de la valeur retour (ce qu'il fallait)
+ Char(?) nom de la variable d'environnement
+ int(10) lg du nom
+ char(?) structure std de gestion des erreurs
la V7R2 amenant une nouvelle version de PCML (6) supportant les dates, les restrictions concernant les paramètres s'estompent
• Déclarons un programme ayant des dates en paramètre
le service est déployé , testons
Il faut saisir la date au format xs:dateTime du XML, soit
sans paramétrage particulier, vous recevez une erreur IWAB0383E
avec la propriété Java com.ibm.xml.xlxp.jaxb.opti.level=0, cela fonctionne
Serveur/properties/JVM options -> bouton EDIT
Enfin, le Groupes PTF de 2014 (SF99713 level 5) propose des nouveautés importantes :
pour les nouveaux services, les variables date sont mieux gérées et doivent être saisie au format xs:date
Résultat ->
le format xs:dateTime est toujours vrai pour les timestamp
l'option java com.ibm.xml.xlxp.jaxb.opti.level=0 est toujours obligatoire à ce jour.
•Autres nouveautés (intégrées également dans SF99368 level 31, de la version 7.1) :
- nouveaux scripts savewebserviceServeur.sh et restorewebserviceServeur.sh
- nouveaux scripts savewebservices.sh et restorewebservices.sh
- ce dernier peut être utilisé pour migrer un web service du serveur LWI (1.5) à celui basé sur liberty profile (2.6)
- Enfin, avec ces correctifs, le serveur permet de créer des web services REST
Choisissez le pgm ou le pgm de service (comme avant)
Indiquez l'URL permettant de le reconnaître
- /chemin/{variable} dans le cas d'une transmission de paramètre dans le PATH (PATH_PARAM)
(vous pouvez alors précisez ":" et une expression régulière devant être vraie, ici que des chiffres)
- /chemin, dans tous les autres cas
ici l'URL sera /web/services/W_RECAP/recap/prod/1 pour le producteur 1
Les paramètres sont reconnus grâce à PCML, indiquez le sens d'utilisation
Ensuite précisez
- la méthode (GET | POST | PUT |DELETE))
- la manière de renseigner les paramètres en entrée
![]()
- *QUERY_PARAM
les paramètres sont transmis dans l'URL sous la forme ?param1=valeur1¶m2=valeur2
- *PATH_PARAM
les paramètres sont transmis dans l'URL sous la forme /chemin/valeur, l'URL vue plus haut -> /chemin/{param1} définit le nom du paramètre
- *FORM_PARAM
les paramètres sont transmis dans un formulaire contenant une zone de formulaire nommé param1 <input name="param1" type="text">
- *COOKIE_PARAM
les paramètres sont transmis sous forme de cookies (param1=valeur1)
- *HEADER_PARAM
les paramètres sont transmis dans l'entête HTTP sous la forme param1=valeur1
- *MATRIX_PARAM
les paramètres sont transmis dans l'URL sous la forme ;param1=valeur1;param2=valeur2
- *NONE
les paramètres sont transmis en tant que structure (XML ou JSON) dans le corps (Body)
<param1>
<zone1>valeur</zone1>
<zone2>valeur</zone2>
</param1>
un seul paramètre peut être transmis via cette méthode
- dans tous les cas, Indiquez à quel paramètre RPG correspond param1 (ici noprod)
- Notez que :
- L'un des paramètres du pgm peut servir à retourner le statut HTTP (404 ou 500, par exemple) dans HTTP response code...
- L'un des paramètres du pgm peut servir à retourner l'entête HTTP (LOCATION: url-de-redirection, par ex.) dans HTTP header ...
Avec SI56883, vous pouvez préciser plusieurs types de média en retour et le type est libre (text/html, par ex.)
![]()
Il faut alors fixer la variable d'environnement CONTENT_TYPE pour préciser dynamiquement le type retourné à chaque appel.Ensuite, Indiquez le profil utilisateur à utiliser
la liste de bibliothèques
![]()
les informations d'entête HTTP à transporter, puis vous arrivez sur l'écran final
![]()
Quelques tests
*PATH_PARAM (celui déployé ci-dessous)
![]()
*QUERY_PARAM
nouveau service
![]()
paramètre en entrée
Format de sortie JSON
![]()
Format de sortie HTML
Exemple
Avec les tables résultat de CALL CREATE_SQL_SAMPLE
Résultat
![]()
Deuxième Exemple avec une image
Nous utiliserons ici
- une "ruse" du Html permettant de charger une image inline avec <img src="data:image/gif,base64, chaine-image-enBase64">
En effet le serveur de web service, ne sait pas, à aujourd'hui retourner de la donnée binaire
- l'API de Sott KLEMENT pour convertir en base64
Exemple
Avec les tables résultat de CALL CREATE_SQL_SAMPLE
Cette fois nous renseignerons
- le code status obligatoirement sans une variable intéger 10 chiffres
- l'entête HTTP obligatoirement dans un tableau
Ces nouveaux Web services peuvent être testés par les dernières versions de SoapUI
Indiquez l'URL
SoapUI va découvrir les paramètres (ici {prod}) , assignez des valeurs et exécutez :
ou par POSTMAN, plus simple pour tester les web servies RESTici la création d'un client par POST
Un seul paramètre peut être indiqué Input Source *NONE
Le pgm attend une DS ClientRecu composée des champs indiqués en haut (id, reference,...)
Il retourne plusieurs paramètres :
Les champs sont transmis en minuscules, car le programme utilise PGMINFO(*PCML:*MODULE : *DCLCASE)
- Depuis Octobre 2016, une documentation complète est disponible : http://www-03.ibm.com/systems/resources/systems_i_software_iws_pdf_WebServicesServer_new.pdf
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 (SOAP) fourni par IBM est basé sur le service ConvertTemp automatiquement créé lors de la création du serveur d'application
Allez sur la page
Si vous le testez, vous voyez (remarquez le paramètre param0, qui est une structure composée d'une seule zone _TEMPIN) :
Utilisation en PHP
Débug :
<?php $client = new SoapClient("http://AS400S:10030/web/services/ConvertTemp?wsdl"); |
Résultat :
>
Pour accéder à la valeur
<?php $client = new SoapClient("http://AS400S:10030/web/services/ConvertTemp?wsdl"); |
ATTENTION, sur les dernières versions du serveur de web services les noms des variables ne sont plus précédés de "_", sauf à indiquer enabled ici : (en cas de doute, vérifiez le fichier .wsdl) |
Si vous retournez un DS à occurrences -> DIM(xx) , tenez en compte dans votre code
Démo avec notre exemple retournant une DS INFOCENTRE à NBPROD occurrences
affiche
en .NET
Après avoir ajouté une référence web dont l'URL est "http://AS400:10030/web/services/ConvertTemp?wsdl"
Dim wsTemp As New AS400_Temperature.ConvertTemp |
Par exemple sur l'événement "click" d'un bouton cmdOK (ici libellé Go)
Protected Sub cmdOK_Click(ByVal sender As Object, ByVal e As System.EventArgs)
Dim wsTemp As New AS400_Temperature.ConvertTemp
Dim input As New AS400_Temperature.CONVERTTEMPInput
If txtF.text <> "" Then
input._TEMPIN = txtF.text
lblC.Text = wsTemp.converttemp_XML(input)
End If
End Sub
Résultat
Enfin, en RPG :
1/ 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)
2 / il faut générer le "stub" soit le programme "proxy" faisant l'interface entre Axis et notre RPG.
pour cela nous utiliserons l'outil WSDL2WS.SH sous shell (STRQSH)
> /QIBM/ProdData/OS/WebServices/V1/Client/bin/wsdl2ws.sh |
Si vous passez la commande ls, vous devez voir :
$ |
L'utilisation de ces sources C en RPG était assez complexe.
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 trouve dans QIBM/ProdData/OS/WebServices/V1/Client/bin/
> /QIBM/ProdData/OS/WebServices/V1/Client/bin/wsdl2rpg.sh |
L'option -s 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 xxxxxxxx.PortType.rpgleinc (ConvertTempPortType.rpgleinc)
où xxxxxxxx représentant le nom du service tel que défini dans "http://AS400S:10030/web/services/xxxxxxxx?wsdl"
qui contient les définitions dont vous avez besoin, et qu'il faut inclure dans votre code :
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. |
Pour faire en RPG l'équivalent du test suivant (sur ConvertTemp) :
Ecrivez
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
Dernier point, vous devez linker tout ce petit monde, par :
.Essayons le pgm :
Cette version de l'utilitaire, permet un code RPG relativement simple plus lisible que le source généré en C par wsdl2ws.
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)
/copy /QIBM/ProdData/OS/WebServices/V1/client/include/Axis.rpgleinc * qui contient D* axiscStubSetSecure... |
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.
tous les détails sur ces API : http://www-03.ibm.com/systems/resources/systems_i_software_iws_pdf_WebServicesClient_new.pdf
Si vous préférez utiliser SQL, la PTF SF99701 level 23 propose une consommation de web services (Rest) via des fonctions dans SYSTOOLS
httpGetBlobhttpGetClobhttpPutBlobhttpPutClobhttpPostBlobhttpPostClobhttpDeleteBlobhttpDeleteClobhttpBlobhttpClobhttpHead- UrlEncode
UrlDecode
base64Encode
base64Decode
le but de ces fonctions est de consommer des services web plutôt orientés REST
Regardons à travers des exemplesValues SYSTOOLS.HTTPGETCLOB('http://www.volubis.fr' ,'') ;
Récupère dans une variable le contenu de notre page d'accueil
![]()
Pour les fonctions HTTPPOSTBLOB|CLOB et HTTPPUTBLOB|CLOB, il y un troisième paramètre: les données à transmettre
(avec GET les paramètres sont dans l'URL)Pour une page web "classique" , par formulaire
->
Voyez cet extrait de code
(pensez simplement à indiquer le type de contenu formulaire : "x-www-form-urlencoded" pour simuler un formulaire)
-- Appel d'une page PHP (normalement un formulaire avec "prenom") Values SYSTOOLS.HTTPPOSTCLOB('http://as400/php/exemples/tp/tp0.php' , CAST ('<httpHeader> <header name="Content-Type" value="application/x-www-form-urlencoded"/> </httpHeader>' AS CLOB(1K)), CAST('prenom=christian' AS CLOB(10K)) )
Si le site retourne du XML, l'utilisation de la fonction XMLTABLE permettra de ne recevoir que les données utiles
Ici, le site www.redbooks.ibm.com propose des flux RSS donnant les publications récentes
![]()
le codeSELECT * FROM XMLTABLE('$result/rss/channel/item' PASSING XMLPARSE( DOCUMENT SYSTOOLS.HTTPGETBLOB('http://www.redbooks.ibm.com/rss/iseries.xml','') ) as "result" COLUMNS title VARCHAR(128) PATH 'title', description VARCHAR(1024) PATH 'description', link VARCHAR(255) PATH 'link', pubDate VARCHAR(20) PATH 'substring(pubDate, 1, 16)' ) AS RESULT;permet de lire les données de manière structurée :
![]()
Ce code permet de lire le fichier cours.xml , s'il est disponible via HTTP
SELECT cours, texte, motcle1, monthname(modif) concat '-' concat year(modif) FROM XMLTABLE('$result/AF400/COURS' PASSING XMLPARSE( DOCUMENT SYSTOOLS.HTTPGETBLOB('http://as400.volubis.intra/af4dir/courshtm/XML/cours.xml','') ) as "result" COLUMNS cours CHAR(10) PATH 'AF4MBR', texte CHAR(50) PATH 'AF4TXT', motcle1 VARCHAR(20) PATH 'MOTCL1', MODIF DATE PATH 'DATOUT' ) AS TABLEXML;
Ce code permet de le lire via HTTP, la page étant protégée par un mot de passe
-- LECTURE fichier XML via INTERNET avec authentification
SELECT cours, texte, motcle1, monthname(modif) concat '-' concat year(modif)
FROM XMLTABLE('$result/AF400/COURS'
PASSING XMLPARSE(
DOCUMENT
SYSTOOLS.HTTPGETBLOB('http://af400:motdepasse@as400.volubis.fr/af4dir/courshtm/XML/cours.xml','')
) as "result"
COLUMNS
cours CHAR(10) PATH 'AF4MBR',
texte CHAR(50) PATH 'AF4TXT',
motcle1 VARCHAR(20) PATH 'MOTCL1',
MODIF DATE PATH 'DATOUT'
) AS TABLEXML;
Sur la base d'un service ayant ce fichier WSDL : http://www.webservicex.net/stockquote.asmx?WSDL
et qui génère sous SoapUI cette enveloppe SOAP en entrée
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:web="http://www.webserviceX.NET/">
<soapenv:Header/>
<soapenv:Body>
<web:GetQuote>
<web:symbol>IBM</web:symbol>
</web:GetQuote>
</soapenv:Body>
</soapenv:Envelope>
Appel d'un Web service, utilisation du deuxième paramètre permettant de fournir les entêtes http,
le troisième contenant la demande(body), soit l'enveloppe SOAP. (ici, nous demandons la valeur de l'action IBM à aujourd'hui dans une enveloppe SOAP)
-- Appel d'un web service , récupération de l'enveloppe SOAP réponse dans une variable
VALUES SYSTOOLS.HTTPPOSTCLOB('http://www.webservicex.net//stockquote.asmx',
CAST ('<httpHeader>
<header name="Content-Type" value="text/xml;charset=utf-8"/>
<header name="SOAPAction" value=""http://www.webserviceX.NET/GetQuote""/>
</httpHeader>' AS CLOB(1K)),
CAST('<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<GetQuote xmlns="http://www.webserviceX.NET/">
<symbol>IBM</symbol>
</GetQuote>
</soap:Body>
</soap:Envelope>' AS CLOB(10K)) ) ;![]()
SoapUI nous montre l'enveloppe Soap en sortie, comme ceci :
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<soap:Body>
<GetQuoteResponse xmlns="http://www.webserviceX.NET/">
<GetQuoteResult><![CDATA[
<StockQuotes><Stock> <Symbol>IBM</Symbol><Last>182.46</Last><Date>11/14/2013</Date>
<Time>12:08pm</Time><Change>-1.09</Change><Open>180.63</Open><High>182.90</High><Low>179.66</Low>
<Volume>3474990</Volume><MktCap>198.1B</MktCap><PreviousClose>183.55</PreviousClose>
<PercentageChange>-0.59%</PercentageChange><AnnRange>172.57 - 215.90</AnnRange><Earns>14.439</Earns>
<P-E>12.71</P-E><Name>International Bus</Name></Stock></StockQuotes>
]]>
</GetQuoteResult>
</GetQuoteResponse>
</soap:Body>
</soap:Envelope>-- Appel d'un web service , récupération de l'enveloppe SOAP, "parsée" -- il y a des espaces de nommage (xmlns) SOAP et d'autres propres au web service, d'où -> *:
-- les données extraites sont elle-même au format XML (on voit que l'action est à 210 $ 55 ) SELECT* FROM XMLTABLE('$result/*:Envelope/*:Body/*:GetQuoteResponse' PASSING XMLPARSE( DOCUMENT SYSTOOLS.HTTPPOSTCLOB('http://www.webservicex.net//stockquote.asmx', CAST ('<httpHeader> <header name="Content-Type" value="text/xml;charset=utf-8"/> <header name="SOAPAction" value=""http://www.webserviceX.NET/GetQuote""/> </httpHeader>' AS CLOB(1K)), CAST('<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"> <soap:Body> <GetQuote xmlns="http://www.webserviceX.NET/"> <symbol>IBM</symbol> </GetQuote> </soap:Body> </soap:Envelope>' AS CLOB(10K)) ) ) as "result" COLUMNS resultat VARCHAR(2000) PATH '*:GetQuoteResult' ) AS TABLEXML;-- Appel d'un web service , récupération de l'enveloppe SOAP, "parsée" pour lire le montant de l'action -- puis découpage du XML extrait (présent dans CDATA[ ] ) à nouveau par la fonction XMLTABLE
<![CDATA[ <StockQuotes> <Stock><Symbol>IBM</Symbol><Last>182.46</Last><Date>11/14/2013</Date> <Time>12:08pm</Time><Change>-1.09</Change><Open>180.63</Open><High>182.90</High><Low>179.66</Low> <Volume>3474990</Volume><MktCap>198.1B</MktCap><PreviousClose>183.55</PreviousClose> <PercentageChange>-0.59%</PercentageChange><AnnRange>172.57 - 215.90</AnnRange><Earns>14.439</Earns> <P-E>12.71</P-E><Name>International Bus</Name></Stock>
</StockQuotes> ]]>WITH temp as (SELECT resultat FROM XMLTABLE('$result/*:Envelope/*:Body/*:GetQuoteResponse' PASSING XMLPARSE( DOCUMENT SYSTOOLS.HTTPPOSTCLOB('http://www.webservicex.net//stockquote.asmx', CAST ('<httpHeader> <header name="Content-Type" value="text/xml;charset=utf-8"/> <header name="SOAPAction" value=""http://www.webserviceX.NET/GetQuote""/> </httpHeader>' AS CLOB(1K)), CAST('<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"> <soap:Body> <GetQuote xmlns="http://www.webserviceX.NET/"> <symbol>IBM</symbol> </GetQuote> </soap:Body> </soap:Envelope>' AS CLOB(10K)) ) ) as "result" COLUMNS resultat VARCHAR(2000) PATH '*:GetQuoteResult' ) AS TABLEXML )select * from temp, XMLTABLE ('$c/StockQuotes/Stock' passing XMLPARSE(DOCUMENT RESULTAT) as "c" COLUMNS symbol CHAR(30) PATH 'Symbol', prix dec(11, 2 ) PATH 'Last' ) AS X ;![]()
Pour un service web REST retournant du XML, c'est plus simple
conjuguons à nouveau HTTPGETBLOB et XMLTABLE
SELECT* FROM XMLTABLE('$result/lstprodResult/RETOUR' PASSING XMLPARSE( DOCUMENT SYSTOOLS.HTTPGETBLOB('http://as400:10042/web/services/LSTPRODR/suivant/25', '') ) as "result"COLUMNS nom VARCHAR(50) PATH 'PR_NOM' , tel varchar(20) PATH 'PR_TEL', nbvins dec(3, 0) PATH'NBVIN', cepage char(20) PATH 'CEPAGE' ) AS TABLEXML;Si le service retourne du JSON
EN PHP
![]()
Résultat
![]()
AVANT TR11(7.2)/TR3(7.3), voyez l'utilitaire XML2JSON / JSON2XML
(http://www.mcpressonline.com/programming/techtip-json-and-xml-conversion-in-db2-for-i.html)
![]()
Depuis TR1 (7.3) TR5(7.2) une fonction JSON_TABLE est intégrée à SQL
(le deuxième caractère, optionnel est un code page)
httpGetBlobVerbosehttpGetClobVerbosehttpPutBlobVerbosehttpPutClobVerbosehttpPostBlobVerbosehttpPostClobVerbosehttpDeleteBlobVerbosehttpDeleteClobVerbosehttpBlobVerbosehttpClobVerbosehttpHeadVerbose
qui s'utilisent comme çawith temp as ( select responsemsg as msg , responsehttpheader as header
from table( systools.httpgetclobverbose('http://www.ecb.europa.eu/stats/eurofxref/eurofxref-daily.xml' ,'')) as E)select code, monnaie, taux from temp CROSS JOIN
xmltable('$r/httpHeader' passing xmlparse(DOCUMENT header) as "r" COLUMNS code char(25) PATH '@responseCode', message char(25) PATH 'responseMessage' ) as x CROSS JOIN
xmltable('$m/*:Envelope/*:Cube/*:Cube/*:Cube' passing xmlparse(DOCUMENT msg) as "m" COLUMNS monnaie char(3) PATH '@currency', taux dec(11, 7) PATH '@rate' ) as y
- Pour les fonctions tables (verbeuses)
- Si la réponse du serveur n'est pas success vous recevez SQLSTATE 01H52 (Warning)
- Si le serveur ne peut être contacté SQLSTATE 38000
- Pour les fonctions scalaires (non verbeuses, vous recevez simplement SQLSTATE 38000 en cas d'erreur
Vous pouvez mettre des options à java, en créant un fichier d'options
-Xmx Mémoire pour java (par exemple -Xmx2g -> 2 Go) http.proxyHost Coordonnées du Proxy http.proxyPort port du proxy (80 par défaut) http.proxyUser Utilisateur pour proxy http.proxyPassword Mot de passe pour proxy http.nonProxyHosts Liste des serveurs pour lesquels ne pas utiliser le proxy javax.net.ssl.trustStore emplacement du fichier contenant la liste des certificats des sites de confiance javax.net.ssl.trustStorePassword mot de passe pour ouvrir le fichier trustStore javax.net.ssl.keyStore emplacement du fichier contenant la liste des certificats et des clés privées (keystore) javax.net.ssl.keyStorePassword mot de passe pour ouvrir le fichier keyStore pour ces dernières options, concernant l'accès HTTPS, voyez notre cours SSL
Le fichier de propriétés java peut être indiqué comme suit :
- en créant une variable d'environnement indiquant sa localisation
ADDENVVAR ENVVAR(QIBM_JAVA_PROPERTIES_FILE)
VALUE(/QIBM/userdata/java400/mySystem.properties)- en créant un fichier SystemDefault.properties dans la home directory de l'utilisateur
- en créant un fichier SystemDefault.properties dans /QIBM/userdata/java400/
voir https://www-304.ibm.com/partnerworld/wps/servlet/ContentHandler/stg_ast_sys_wp_access_web_service_db2_i_udf
Attention
Toutes les fonctions HTTPxxxx sont livrées dans SYSTOOLS, donc "as is' et ne peuvent faire l'objet d'une demande de support
A noter, en Mai 2016, IBM propose aussi d'utiliser directement les API Axis pour consommer des web services REST (alternative aux fonctions httpgetblob/clob)
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 général
**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. // --------------------------------------------------------------------
// Déconnexion. axiscTransportDestroy(tHandle); *INLR=*ON; // code des procédures extrait de developerWorks 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; |
Voyez nos différentes solutions pour lire du JSON : http://www.volubis.fr/freeware/READJSON.html
Copyright © 2016 VOLUBIS