Ecrire un client WebService avec CXF

Cet article, proposé par Henri Darmet, présente une méthode presque générique pour écrire la partie cliente d’un Web Service en utilisant CXF.

On peut légitimement se demander en quoi cet article est légitime, vu qu’il existe de multiples tutoriels proposant un exercice de ce genre. Certes, mais tous se concentrent sur les premiers pas pour newbies. Et aucun ne vous propose la démarche complète qui intègre la transmission de pièces jointes au format binaire (en utilisant MTOM) et la sécurisation via WS-Security. C’est donc d’un client « professionnel » qu’il est question ici.

Je laisse à d’autres (ou à un autre article) le soin d’expliquer comment implémenter le service lui-même. Il s’avère que ce second exercice est en général bien mieux maîtrisé et mieux documenté.

Première étape : écrire un client simple

La première idée, celle qui nous vient tout de suite à l’esprit est de faire générer ce client par Eclipse. Mauvaise idée : celle-ci se basera sur le WSDL de votre Web Service, et l’exploitera ensuite dynamiquement. Conséquence : des appels frénétiques pour lire et exploiter ce WSDL, d’où

–          une grande difficulté pour les tester,

–          des performances dégradées,

–          pas de souplesse d’emploi.

En particulier, il est très difficile de préciser dynamiquement, sans venir altérer le code généré, l’URL  permettant d’atteindre le Web Service. Si bien que lorsqu’on doit changer d’environnement, le Web Service lui-même (pour passer d’une machine de développement pour aller en recette puis en production), il faut modifier le WSDL et régénérer le client : absurde.

Seconde solution : utiliser l’API CXF et là tout devient facile, ou presque. A titre d’exemple, je propose ici un Web Service d’envoi de mail (pour une application qui, par exemple, permet de s’adresser au SMTP de l’entreprise afin d’envoyer un mail au nom de l’entreprise). L’intérêt de cet exemple sera, bien sûr, qu’on peut y ajouter des pièces jointes et qu’il faudra le sécuriser (afin que n’importe qui, sur internet ne puisse spammer au nom de votre entreprise).

Voici le premier jet de l’interface de notre service :

package com.objetdirect.mailer;

import javax.jws.WebService;

@WebService

public interface Mailer {

 void send(Mail mail);

}

L’objet Mail contient toutes les informations qui doivent être contenues dans le mail, à savoir : une adresse d’origine (from), une série de déstinataires (to), un sujet (topic) et un contenu (message). Je vous propose l’implémentation triviale suivante :

package com.objetdirect.mailer;

 

import javax.xml.bind.annotation.XmlAccessType;

import javax.xml.bind.annotation.XmlAccessorType;

import javax.xml.bind.annotation.XmlRootElement;

 

@XmlRootElement

@XmlAccessorType(XmlAccessType.FIELD)

publicclass Mail {

String from;

String to;

String topic;

String message;

public Mail(String from, String to, String topic, String message) {

super();

this.from = from;

this.to = to;

this.topic = topic;

this.message = message;

}

public String getFrom() {

return from;

}

public String getTo() {

return to;

}

public String getTopic() {

return topic;

}

public String getMessage() {

returnmessage;

}

}

Passons à ce qui est le vrai sujet de cet exercice, l’écriture du bout de code qui nous permet d’accéder à un client de ce service :

package com.objetdirect.mailer;

import org.apache.cxf.jaxws.JaxWsProxyFactoryBean;

import org.apache.cxf.ws.security.wss4j.WSS4JInInterceptor;

import org.apache.cxf.ws.security.wss4j.WSS4JOutInterceptor;

 

publicclass MailerClient {

 publicstatic Mailer getClient(String url) {

JaxWsProxyFactoryBean factory = new JaxWsProxyFactoryBean();

factory.getInInterceptors().add(new LoggingInInterceptor());

factory.getOutInterceptors().add(new LoggingOutInterceptor());

factory.setServiceClass(Mailer.class);

factory.setAddress(url);

Mailer client = (Mailer) factory.create();

return client;

}

}

Eh, oui, c’est tout. Pas besoin d’une phase de génération. Notez, qu’à part la mention de son interface (la classe d’interface elle-même), il n’y a aucune adhérence entre cette méthode et le code du service. Il est donc possible de réécrire cette méthode pour qu’elle soit entièrement générique. Je vous laisse le soin de tenter l’exercice, cela vous permettra de vous dégourdir les jambes dans l’utilisation des generics J.

La seule donnée passée en paramètre est l’URL du WebService qui est donc bien définie dynamiquement. Il devient alors possible (et facile) de changer d’environnement.

L’implémentation proposée ici incluse l’appel d’intercepteur de log afin de journaliser, à toute fins utiles, les appels et les réponses. On aurait pu, à la rigueur la simplifier donc de deux lignes. Obtenir un client nécessite donc un minimum de quatre lignes…

Pour l’appeler, c’est encore plus simple :

publicstaticvoid main(String[] argv) {

Mailer mailer = MailerClient.getClient(

« http://www.acme.com/ws/mailer »

mailer.send(new Mail(

« noanswer@acme.com »,

« bigboss@goyoo.com »,

« New Webservice »,

« The new Web Service is ready ! »));

}

Comme on peut le constater, l’aide d’éclipse – vu la complexité très, très relative de l’exercice – ne s’impose en aucune manière.

Dans le prochain article, nous verrons comment sécuriser l’accès avec WS-Security.

soapUI : tests de charge de Web Services

Je continue mon tutorial sur soapUI. Voyons cette fois-ci comment utiliser soapUI pour réaliser des tests de charge.

Il faut ajouter un nouveau test de charge sur le TestCase voulu.
Note: il faut savoir que le test de charge va lancer tous les tests en parallèle, donc il ne faut pas qu’il y a de dépendance entre les services du TestCase, car ceci pourrait fausser vos résultats.

clip_image002

Une fenêtre s’ouvre, il est possible de choisir la stratégie de test et de la configurer.
A savoir : la durée du test, le nombre de threads à utiliser, le délai (aléatoire ou non) entre chaque nouvelle requête, etc.

clip_image004

Sur cette image on peut se rendre compte qu’un service qui renvoie une stacktrace d’exception est couteuse en temps et en données.


Le tableau de résultat affiche : le temps de réponse min max moyen, le nombre de requêtes totales lancées lors du test, le total de données transférées, le nombre d’erreur.

De plus, la console de log en dessous permet d’avoir des détails sur les tests déroulés, comme l’heure de début et de fin du test, les éventuelles erreurs, etc …

soapUI : pré-remplir les champs d’une requête

Suite de mon tutorial sur soapUI, voyons cette fois-ci comment pré-remplir certains champs d’une requête. Par exemple, un login et un password nécessaire pour se connecter.

Si vous avez une série de requêtes dans votre Test case, il est préférable de créer un script qui se chargera de vous pré-remplir ces champs plutôt que de le faire à la main 😉

Il faut créer un nouveau « step » nommé ‘Groovy Script’.

clip_image002

Voici un exemple de code qui pré-rempli les champs ‘login’ et ‘password’. Ces valeurs doivent être déclarées et initialisées dans l’onglet « Custom Properties » du projet (il s’affiche en cliquant sur le nom du projet).

image

Entrez ensuite le code du script :

image

Un petit Copier-(Réfléchir*)-Coller du code suivant simplifiera le travail !             (* © F.D.)

def groovyUtils = new com.eviware.soapui.support.GroovyUtils( context )
//variables definitions
def login = '${#Project#login}'
def password = '${#Project#password}'

//Retrieves all TestSteps of the current testCase
log.info("* " + testRunner.testCase.name )
def testStepList = testRunner.testCase.getTestStepList()

testStepList.each{currentTestStep->
    if (currentTestStep.name != "Inits fields") { //ici liste des steps que le script ne doit pas modifier
        log.info(" \\-- " + currentTestStep.name )
        def holder = groovyUtils.getXmlHolder( currentTestStep.name+"#Request" )
        log.info(" " + 'holder' )
        holder.namespaces["ns"] = "http://schemas.xmlsoap.org/soap/envelope/"

        //Inits values of the current testStep
        log.info(" " + 'login' )
        holder.setNodeValue("//ns:Body/*/login", login)
        log.info(" " + 'password' )
        holder.setNodeValue("//ns:Body/*/password", password)

        //Updates the change
        holder.updateProperty()
    }
}
'Script OK'

Voici la requête une fois le script exécuté :

image

Note : pour éviter de faire un copier-coller du code à chaque Test case, il est possible de cloner ce « step » et le déplacer dans un autre Test case 🙂

soapUI : tester l’enchainement de Web Services

Dans un précédent article, je vous ai montré comment tester unitairement un Web Service. Cette fois-ci, nous allons aller un peu plus loin en testant l’enchainement de plusieurs Web Services.

Comme nous l’avons vu chacun de nos tests unitaires se trouve dans un arbre. Il est alors possible de lancer l’ensemble des tests de la suite, ou toutes les étapes d’un scénario, simplement en double cliquant sur le nœud correspondant de l’arbre. (Nœuds : TestSuite ou TestCase)

Une fenêtre s’ouvre et permet de voir les résultats

clip_image002[10]

A peine plus compliqué, il est aussi possible de récupérer une valeur dans une réponse et de l’injecter dans le test suivant.

Exemple d’utilisation : Dans nos jeux de tests, on veut réaliser l’enchainement suivant :

  • Créer une commande (createOrder)
  • Annuler la commande qui vient d’être créée (cancelRequestNumber)

sachant que ce dernier service prend en paramètre un « request number » qui est renvoyé dans la réponse du « createOrder ».

Pour récupérer cette valeur et remplir notre requête « cancelRequestNumber », on utilise la fonctionnalité offerte par soapUI : le « Property Transfer ».

La valeur à récupérer (la « Source ») :

image

Le champ à remplacer (la « Target ») :

image

On ajoute un « step » ‘Property Transfer’ à notre Test Case « PropTransfer » :

clip_image006

On donne un nom au remplacement automatique qui sera effectué, à chaque exécution des tests :

clip_image008

On configure les chemins vers les champs qui nous intéressent :

image
Les chemins représentent les nœuds XML, de la requête et de la réponse SOAP, à parcourir pour atteindre les champs voulus

On place maintenant le « step » ‘Property Transfer’ entre les 2 tests (createOrder et cancelRequestNumber), de façon à obtenir un enchainement logique dans notre TestCase ‘PropTransfer’ :

clip_image012

On peut maintenant dérouler l’ensemble du Test Case 🙂

soapUI et les tests de Web Services

SoapUI est un outil de test de Web Services développé par Eviware. Il existe en 2 versions dont une entièrement gratuite. Je le trouve très utile pour le debug et les tests unitaires de web services, mais aussi pour les tests automatisés et les tests de charge.

Je vous propose de découvrir dans cet article comment utiliser SoapUI pour réaliser des tests unitaires.

Lire la suite