Testare API SOAP senza un web server

Se hai mai avuto bisogno di utilizzare delle API su web, è molto probabile che oltre a REST tu abbia avuto a che fare con SOAP.

SOAP è un protocollo per effettuare Remote-Procedure Calls (RPC), può utilizzare differenti meccanismi di comunicazione tra server e client (HTTP, SMTP, AMQP, e altri) ed effettua chiamate inviando e restituendo XML.

La forza di SOAP è data dalle seguenti caratteristiche:

  • astrae il protocollo per la comunicazione con servizi remoti dalla sua implementazione reale,
  • ha uno standard per descrivere quali sono gli input e gli output che richiede e fornisce,
  • molti linguaggi hanno un modo nativo per istanziare dei client e dei server SOAP,
  • molti linguaggi forniscono classi per la creazione, la lettura e la modifica di file XML.

Durante il nostro progetto per Skebby, abbiamo sviluppato delle API REST e delle API SOAP. Avendo la necessità di verificare il loro funzionamento abbiamo sviluppato dei test per entrambe.

Per le API REST abbiamo sfruttato l’architettura MVC di Zend Framework 2, l’implementazione di un front controller e abbiamo creato un client che ci permettesse di verificare l’applicazione senza la presenza di un web server.

Per le chiamate alle API SOAP abbiamo iniziato scrivendo dei test che facessero chiamate attraverso il web server sfruttando la classe SoapClient di PHP.

L’architettura delle nostre API

Oltre ad un oggetto che rappresenta il server SOAP, sono necessari almeno due controller: uno per gestire le richieste SOAP e uno per restituire il file WSDL.

Il primo controller istanzia il SOAP server e si occupa di rispondere alle chiamate.

Il secondo genera un file WSDL che descrive quali sono le operazioni disponibili, i parametri di input per ogni operazione e cosa viene restituito.

Per quel che riguarda i test, è necessario creare una istanza del SOAPClient di PHP. Questo fa chiamate reali sia verso il file WSDL durante la sua costruzione, sia verso il server durante i test.

Questo approccio ci ha permesso di testare le API dal punto di vista funzionale come una blackbox sfruttando il web server come mezzo di comunicazione con il client. Tuttavia ci siamo subito resi conto che in questo modo i test erano lenti.

Test con SOAP senza web server

Cercando di ottenere un ciclo di feedback breve, l’attesa di quasi 6 minuti solo per i test che coinvolgevano le API SOAP non era accettabile.

Per questo abbiamo deciso di guardarci intorno per capire se era possibile eliminare la necessità di un web server per testare delle API SOAP. Sul blog di Benjamin Eberlei abbiamo trovato l’articolo “SOAP and PHP in 2014” da cui abbiamo preso l’idea per risolvere il nostro problema.

Sempre all’interno di questo articolo c’è il riferimento ad un SOAPClient di ZF2 che accetta un server come parametro nel costruttore permettendo al client di chiamare direttamente il server e di manipolare la sua risposta.

Sfortunatamente, dovendo eseguire una autenticazione prima di accedere alle API SOAP ed essendo questa autenticazione esterna al server SOAP, il caso d’uso descritto non corrispondeva a quello che ci trovavamo ad affrontare. Ma l’idea di incapsulare il server era esattamente quella che utilizzavamo per il client delle API REST e così abbiamo deciso di proseguire cambiando un pò le carte in tavola.

 SOAP Client personalizzato

Il primo passo che abbiamo fatto è stato quello di creare un client personalizzato che al posto del server accettasse un oggetto ZendMvcApplication  in modo da poter sfruttare l’intero stack della nostra applicazione e non solo il server SOAP.

Nella classe SoapTestCase – che abbiamo sviluppato per i test delle API SOAP – abbiamo quindi aggiornato il metodo  initClient

Il client SOAP che abbiamo implementato estende quello di ZF2 ma ne modifica alcuni metodi: aggiunge un setApplication  e modifica il metodo _doRequest  che crea una richiesta con autorizzazione e restituisce la risposta del server.

Questo secondo punto è quello più complesso per quanto riguarda il client. Vanno specificati tutti gli header HTTP e in particolare è stato necessario crearne uno nuovo per la gestione dell’header “Soapaction” che contiene la url delle api e il nome del metodo chiamato, divisi da un “#”.

Ok. Lato client siamo a posto, lato server dovrebbe già essere tutto pronto… quasi!

Vincoli del SOAPServer di PHP

C’erano ancora due cose che ci impedivano di eseguire i test:

  • un comportamento particolare del SOAPServer di PHP,
  • un workaround nel nostro EndpointController.

SOAPServer di PHP

In caso si verifichino delle eccezioni e sia necessario restituire un errore, la classe SOAPServer di PHP blocca il processo PHP e restituisce l’errore nello standard output. Il comportamento è simile all’utilizzo di una exit all’interno di uno script php.

EndpointController

Nell’ Endpointcontroller viene istanziato il SOAP sever e subito dopo aver chiamato il metodo handle  che gestisce la richiesta è presente una exit; . Questa istruzione si è resa necessaria per ovviare ad un warning utilizzando le API:

Per aggirare questi due ostacoli abbiamo esteso la classe SOAPServer così da modificare la gestione delle eccezioni ed evitare il blocco del processo PHP.

Abbiamo quindi modificato il SOAPServer dell’applicazione MySoapServer  per utilizzare MySoapSoapServer  invece del SoapServer di PHP.

Infine abbiamo modificato l’ EndpointController .

Il soap server di ZF2 accetta come parametro opzionale per il metodo handle un oggetto che rappresenta una richiesta. Nel nostro caso questa richiesta è quella configurata nel client creato per i test.

Oltre alla richiesta dobbiamo fornire anche la url del file WSDL e una volta salvato su filesystem possiamo sfruttare file://  anziché http:// .

Con queste ultime modifiche siamo riusciti ad ottenere quello che volevamo: i test delle API SOAP vengono eseguiti senza un web server.

Che cosa ne abbiamo guadagnato?

Inizialmente i test che riguardavano soap impiegavano quasi 6 minuti

Mentre ora, a dispetto anche di un piccolo aumento del numero dei test (+4), girano in poco più di un minuto e mezzo.

Cosa si può ancora migliorare

Eliminare la exit  dall’EndpointController

Sfruttando la classe ZendSoapServer  si potrebbe utilizzare il metodo setReturnResponse  per eliminare la necessità dalla exit;  [1, 2]

Generazione file WSDL su filesystem

Una delle modifiche necessarie per evitare chiamate al web server è quella di salvare il file WSDL su filesystem. Al momento questa operazione è fatta all’inizio di ogni build. Per il singolo test, in caso si modificassero le API SOAP, va lanciato il comando manualmente, pena un errore SOAP anche se la chiamata è corretta. L’ideale sarebbe generare in automatico il file in base ai cambiamenti alle API.