Eccomi qui con un nuovo posto..
Ultimamente sto tenendo una bella media, chissà se riuscirò a tenere questa costanza.. :)
Bene, oggi volevo scrivere un post sull'invio e la ricezione di file verso e da un web service, visto che è una cosa che mi sono ritrovato a fare solo recentemente.
In questo post sia i servizi1 che i client saranno scritti in Java. Il web service engine che utilizzato è
Apache Axis (installato su Tomcat 7) ed il protocollo di comunicazione è SOAP.
Per la generazione del pacchetto .aar contenente le classi per "deployare" il servizio in Axis è stato utilizzato il plugin per Eclipse "Service Archiver", mentre per creare il file WSDL dal codice del servizio e generare il client dal WSDL creato sono stati utilizzati Java2WSDL e WSDL2Java, entrambi contenuti nel plugin "Code Generator". Questi tool sono disponibili all'indirizzo:
http://axis.apache.org/axis2/java/core/tools/index.html.
Prenderemo in considerazione un servizio,
FileService che mette a disposizione 2 operazioni:
UploadFile e
GetFile.
La cosa importante affinchè le cose funzionino è definire la firma dei metodi corrispondenti alle operazioni affinchè il Code Generator possa generare un file WSDL con il giusto mapping tra i tipi.
Una prima prova potrebbe essere fatta specificando come parametro dell'operazione
UploadFile e come tipo restituito dall'operazione
GetFile semplicemente
java.io.File.
Purtroppo questa non è una buona soluzione. Se si prova ad utilizzare Java2WSDL con i metodi così definiti, verrà generato un correttamente il file WSDL desiderato. Il problema, però, nasce quando si utilizza WSDL2Java per generare il client. Dalle classi create si vede che il mapping dei tipi è abbastanza sballato, infatti ci sono delle classi come
java.io.xsd.File che per essere utilizzate come minimo dovrebbero essere incluse in un package diverso, in quanto l'uso del nome
java.io per un package è ovviamente proibito.
Non so se sia possibile utilizzare comunque
java.io.File per il trasferimento dei dati, probabilmente definendo a mano i tipi complessi nel WSDL e costruento i messaggi SOAP tramite codice è possibile, ma se uno non ha tempo, o non ha voglia, o una combinazione delle 2, allora sarebbe preferibile percorrere un'altra strada :)
Bene, dopo aver visto questa non-soluzione passiamo alle 2 che ho sperimentato:
- trasferire dati tramite array di byte e DataHandler
- trasferire dati tramite DataHandler utilizzando MTOM per gli allegati SOAP
Array di Byte
Questo metodo è abbastanza intuitivo, per fare l'upload di un file si passa un'array di byte che rappresenta il contenuto del file e il web service che lo riceve lo scrive su file. Lato client, però, non si può passare semplicemente un array di byte, ma dev'essere passato un
DataHandler.
Il servizio può essere per esempio il seguente:
public class ImportService {
public String importSingleFile(String name, byte[] content) {
try {
File f = new File(name);
BufferedWriter out = new BufferedWriter(new FileWriter(f));
out.write(s);
out.close();
return "Import Successful!";
}
catch(IOException e) {
e.printStackTrace();
}
}
}
Con Java2WSDL si può creare il file che descrive il servizio e con WSDL2Java si può generare il client che lo interroga. Il client sarà composto da una serie di classi,tra cui
ImportStub e
ImportSingleFile che corrispondono rispettivamente al servizio ed all'operazione da richiamare.
Il client, per poter trasferire il file al servizio, dovrà:
- aprire il file che vuole trasferire
- associare il file ad un DataSource (presente in javax.activation)
- associare il DataSource ad un DataHandler (sempre in javax.activation)
- richiamare il servizio
Ecco il codice:
public class Client {
public static void main(String args[]) {
try {
ImportStub stub = new ImportStub();
ImportSingleFile single = new ImportSingleFile();
File f = new File("upload.txt");
FileDataSource ds = new FileDataSource(f);
DataHandler dh = new DataHandler(ds);
single.setName(f.getName());
single.setContent(dh);
ImportSingleFileResponse res = stub.importSingleFile(single);
System.out.println(res.get_return());
}
catch(IOException | AxisFault e) {
e.printStackTrace();
}
}
}
E questo è quanto.
Il problema con questa soluzione è che i dati binari vengono codificati come testo per poi venire spediti in messaggi SOAP. Per ovviare a questo problema è possibile utilizzare MTOM.
MTOM e DataHandlers
MTOM (Message Transmission Optimization Mechanism) è una raccomandazione W3C per inviare e ricevere dati binari da web services. Altri meccanismi possono essere utilizzati per lo stesso scopo, come
SwA (SOAP with Attachments) e i DIME Attachments, ma a MTOM sembra sia migliore di questi ultimi.
Per poterlo utilizzare in Axis è necessario abilitarlo: bisogna editare il file di configurazione
axis/WEB-INF/conf/axis2.xml e settare a true il parametro
enableMTOM.
Fatto ciò si può scrivere il servizio. In realtà cambia molto poco: invece di un array di byte come parametro prende un
DataHandler. Ecco il codice:
public class ImportService {
public String importSingleFile(String name, DataHandler dh) {
try {
File f = new File(name);
FileOutputStream fos =
new FileOutputStream(f)
;
dh.writeTo(fos);
fos.close();
return "Import Successful!";
}
catch(IOException e) {
e.printStackTrace();
}
}
}
Il client invece resta esattamente uguale.
E così si conclude questo post..spero di scrivere qualche altra cosa presto! :)