Web Services for dummies: creare un servizio web SOAP per Tomcat – il Producer

Si sente molto spesso parlare di web services e di architetture orientate ai servizi (SOA) applicate a diversi ambiti, soprattutto Enterprise. Con l’esplosione del mobile, negli ultimi tempi si è visto come sfruttare questi tipo di architettura per rendere fruibili contenuti sulle nuove piattaforme senza creare servizi nuovi, ma esponendo quelli esistenti sotto forma si servizi web SOAP o REST (soprattutto questi ultimi). In giro si trovano tantissime documentazioni teoriche su cosa sono i web services, su cos’è il protocollo SOAP e come com’è strutturato, ma è difficile trovare una documentazione banale che risponda a questa domanda: ho una classe scritta con Eclipse e la voglio deployare su Tomcat, punto. Come diavolo faccio ad esporla come servizio web senza diventare scemo? Vediamo se riusciamo a rispondere a questa domanda.

Prerequisiti

E’ vero che, soprattutto nel mondo mobile, la tendenza è quella di sviluppare servizi REST con payload in JSON per vari motivi:

  • sfrutta appieno il protocollo HTTP;
  • messaggi poco verbosi, meno occupazione di banda;
  • ogni risorsa/servizio è referenziale in modo univoco tramite un URI.

Nonostante questo, è importante valutare quanto tempo impieghiamo a scrivere un servizio: con Eclipse per esempio, scrivere un servizio SOAP per Tomcat richiede 5 minuti, senza aggiungere un jar al nostro progetto (in realtà se ne occuperà Eclipse…). In questo tutorial abbiamo usato Eclipse Indigo e Tomcat 7.

Se volete potete essere completamente agnostici del servizio e passare al capitolo successivo, ma vale la pena sapere qualche nozione base. In generale, sia i servizi SOAP che REST espongono una interfaccia (anche se gli ultimi ne possono fare a meno), un contratto che descrive le funzionalità del server (chiamato producer del servizio) e alla quale il client (consumer del servizio) fa affidamento per “consumare” il servizio. Questa interfaccia, nota in SOAP come Web Service Definition Language (WSDL) e Web Application Description Language (WADL) in REST, è in XML e descrive i metodi per accedere ai servizi e i tipi di dati scambiati. Essendo in XML è chiaramente cross-platform: basta analizzare il WSDL/WADL per poter scrivere un producer o un consumer del servizio.

KISS: Keep It Simple, Stupid

Eclipse ha un ottimo generatore di codice per servizi SOAP. Non complichiamoci quindi la vita! Escludiamo subito l’eventualità folle si scrivere il WSDL a mano e seguiamo il cosiddetto approccio “bottom-up“: implementiamo cioè la nostra classe che fa quelle cose che vogliamo trasformare in un servizio, come le seguenti:

public class MyService {

   public String echo(String message) {
      return "Echo " + message;
   }
   
   public Output sort(Input input) {
      Arrays.sort(input.getVector());
      return new Output(input.getVector());
   }
}

Dove Input e Output sono semplici POJO:

public class Input {

   private int[] vector;

   public Input() {
   }
   
   public Input(int... numbers) {
      this.vector = numbers;
   }

//Getter & Setter

}

public class Output {

   private int[] sortedVector;
   
   public Output() {
   }
   
   public Output(int... numbers) {
      this.sortedVector = numbers;
   }

//Getter & Setter

}

Da notare i costruttori: secondo la specifica Java Bean, oltre ai getter & setter devo avere almeno un costruttore senza argomenti.

Ovviamente li avremo creati in un progetto web, che tutti sapete (File->New->Dynamic Web Project) adesso come fare ;).

Producer

Adesso comincia la parte più facile: lasciamo fare tutto ad Eclipse!

  1. Click destro sulla classe del nostro servizio, poi New->Other… e selezionare “Web Service” come in figura:
    Seleziona "Web Service"
  2. Verrà avviato un wizard di configurazione: nella prima schermata compatirà in alto il nome della classe e la strategia di creazione del servizio (“Bottom-up”). Portiamo la levetta verticale sulla sinistra tutta in basso al livello “Test Service” come in figura:
    Seleziona il servizio e il livello di deploy

    In questo modo il servizio verrà generato, verrà automaticamente deplorato su Tomcat e si aprirà una pagina di test del servizio che vedremo al termine. Prima di proseguire selezionare anche “Monitor the Web Service” in basso in modo da monitorare i messaggi SOAP da dentro Eclipse.

  3. Successivamente selezioniamo il nome del file WSDL che verrà creato e i metodi della classe che vogliamo esporre come servizio:
    WSDL e metodi da esporre
  4. Gli ultimi due step sono molto semplici: verrà chiesto di avviare il Tomcat se non lo è già:
    Avvia Tomcat

    Successivamente lanciamo l’applicazione di test fornita da Eclipse:

    Avvio dell'applicazione di test
  5. Al termine si aprirà il browser mostrando una pagina di questo tipo:

    Applicazione di test: selezionare un metodo

    Per il momento lasciamola perdere e guardiamo cos’è successo al nostro progetto, in particolare nella cartella WebContent:

    Cartella WebContent

    E’ apparsa la nuova cartella wsdl contenente il descrittore del nostro servizio. Dentro WEB-INF troviamo invece un’altra nuova cartella e un file di configurazione del servizio che sembra scritto in aramaico… ma non ci interessa molto, l’importante è che tutto funzioni come verificheremo subito dalla pagina del browser che si è aperta precedentemente. Selezionando per esempio il metodo echo e inserendo una stringa, verrà visualizzato immediatamente il risultato restituito dal server. Tornando in Eclipse, possiamo verificare i messaggi scambiati dal tab “TCP/IP Monitor”:

    Monitor traffico SOAP in ingresso e in uscita

    Un’ultima curiosità: guardando il web.xml si scopre che è pieno di cose mai viste: basta poco per rendersi conto di cosa è successo. Eclipse ha aggiunto due nuova servlet: AxisServlet e AdminServlet con diverse tipologie di mapping: saranno loro a ricevere le chiamate e a tradurle per il nostro servizio in Java. Axis non è altro che una implementazione open source del protocollo SOAP per Java: ogni volta che avete a che fare con Axis quindi potete scoprire tutti i servizi deplorati sul server semplicemente richiamando un URI di questo tipo:

    http://localhost:8080/WebServiceProducerTest/services

    che mostrerà gli URI dei WSDL, come quello appena creato:

    http://localhost:8080/WebServiceProducerTest/services/MyService?wsdl

    Abbiamo quindi creato il nostro primo servizio web in pochi minuti, facile no? Nel prossimo post vedremo come sarà altrettanto facile realizzare un consumer del servizio.

Andrea Como

Sono un software engineer focalizzato nella progettazione e sviluppo di applicazioni web in Java. Presso OmniaGroup ricopro il ruolo di Tech Leader sulle tecnologie legate alla piattaforma Java EE 5 (come WebSphere 7.0, EJB3, JPA 1 (EclipseLink), JSF 1.2 (Mojarra) e RichFaces 3) e Java EE 6 con JBoss AS 7, in particolare di CDI, JAX-RS, nonché di EJB 3.1, JPA2, JSF2 e RichFaces 4. Al momento mi occupo di ECM, in particolar modo sulla customizzazione di Alfresco 4 e sulla sua installazione con tecnologie da devops come Vagrant e Chef. In passato ho lavorato con la piattaforma alternativa alla enterprise per lo sviluppo web: Java SE 6, Tomcat 6, Hibernate 3 e Spring 2.5. Nei ritagli di tempo sviluppo siti web in PHP e ASP. Per maggiori informazioni consulta il mio curriculum pubblico. Follow me on Twitter - LinkedIn profile - Google+

  • Pingback: Web Services for dummies: creare un servizio web SOAP per Tomcat – il Consumer | Cose Non Javiste()

  • Pingback: JBoss e Web Services SOAP? Ci pensa JBossWS! | Cose Non Javiste()

  • carmine

    come faccio a lanciare l’applicazione test web service da java?? grazie mille

  • Ciao, se vuoi testare il servizio devi creare i proxy client come spiegato in questo post: http://www.cosenonjaviste.it/web-services-for-dummies-creare-un-servizio-web-soap-per-tomcat-–-il-consumer/
    che spiega come creare un client a partire dal WSDL e come testarlo con jUnit. Spero di aver risposto alla tua domanda.

  • Simone

    ciao CoseNonJaviste,
    il tuo tutorial semplifica molto la realizzazione dei WS. Ma una volta definiti ed implementati, come posso pubblicarli su Tomcat ? E’ necessario effettuare il WAR e via all’opera?

    • cosenonjaviste

      Ciao! Nel post viene menzionato che si lavora con un Dynamic Web Project di Eclipse… che viene automaticamente deployato proprio come un WAR 😉 Grazie comunque della precisazione

      • Simone

        grazie a voi per il tutorial!

      • schiappolo

        ciao, Questo tutorial mi è stato molto utile e ti ringrazio. Ho implementato anche il client e funziona bene. Lanciando tutto da eclipse va tutto benissimo, ma facendo il deploy su tomcat il server mi carica una pagina di errore(eppure il deploy è andato a buon fine). Probabilmente mi perdo in un bicchiere d’acqua. Puoi aiutarmi?

        • cosenonjaviste

          Ciao. Di che errore si tratta? Su una pagina specifica? All’avvio del server? Se mi dai qualche dettaglio in più cerco di aiutarti 😉

          • schiappolo

            Ciao, scusa il ritardo.

            Nei giorni scorsi mi sono accorto che funziona è solo l”indirizzo che non corrisponde. Nella pagina /manager su tomcat, dopo aver fatto il deploy (del war esportato da eclipse), mi appare il servizio nella lista dei servizi solo che mi visualizza come Path “/BankWebService/” (e cliccandoci sopra mi da errore 404), quando invece quello corretto è “/BankWebService/services/BankService”. A questo punto vorrei chiederti se è normale così o se andrebbe modificato il path in qualche file di configurazione prima di esportare il war da eclipse oppure da tomcat dopo il deploy? Sono alle prime armi coi web service, non è ancora prioritario per me conoscere la materia ma a breve lo diventerà.
            Ti ringrazio in anticipo

          • cosenonjaviste

            Il percorso del servizio è composto da tre parti: la prima è il context path dell’applicazione, configurabile da eclipse; la seconda (/services) è la mappatura della servlet di axis che puoi modificare dal web.xml; la terza invece è il nome del servizio vero e proprio che di default prende il nome della classe. Quest’ultimo si dovrebbe poter modificare dal file di deployment di axis che genera automaticamente eclipse.

          • schiappolo

            grazie!

  • Eliana Monticone

    ciao,
    devo implementare un web service, si puo creare in un progetto gia ‘avviato’ che gira sotto web logic?
    EM

    • cosenonjaviste

      Ciao,
      non ho mai lavorato con web logic, ma cercando un po’ in rete ho trovato un sacco di archetipi maven per questo application server… non con riferimenti diretti ai web services ma è sempre un punto di partenza. Se usi una delle ultime versioni di web logic non credo che l’implementazione di servizi rest o soap sia molto diversa da quelle riferite a jboss che trovi in questo blog.

  • Alessandro Gelormini

    ciao,molto interessante la guida…io dovrei realizzare un web service con autenticazione ovvero https con certificati da entrambe le parti. come si può fare?

    • cosenonjaviste

      Ciao,

      per dialogare in https con doppio certificato hai bisogno del keytool (incluso nella jvm) per creare i certificati lato server e lato client. A questo punto devi configurare il server.xml del Tomcat per indicare il percorso fisico su cui si trova il certificato. Lato client, devi fare la stessa cosa impostando la proprietà System.setProperty(“javax.net.ssl.trustStore”,”C:\path\to\client.keystore”) e altre che trovi più in dettaglio in questa giuda (in inglese)
      http://eduunix.ccut.edu.cn/index2/pdf/FAQ_Tomcat_SOAP_SSL.pdf

  • Alessio

    Ciao, complimenti per il tutorial davvero chiaro e ben fatto.
    Ho installato eclipse ee (nel computer ho anche la versione “normale” nel quale uso Android) per avere il wtp e la possibilità di creare progetti “Dynamic Web Project”,

    arrivato al passo in cui si definisce il wsdl e premo su next mi da il seguente messaggio di errore:IWAB0014E Unexpected exception occurred.

    e su dettagli:

    IWAB0014E Unexpected exception occurred.

    java.lang.IllegalArgumentException
    at org.eclipse.wst.server.core.internal.ResourceManager.getServer(ResourceManager.java:814)

    at org.eclipse.wst.server.core.ServerCore.findServer(ServerCore.java:303)

    at org.eclipse.jst.ws.internal.consumption.command.common.CreateMonitorCommand.execute(CreateMonitorCommand.java:50)

    at org.eclipse.wst.command.internal.env.core.fragment.CommandFragmentEngine.runCommand(CommandFragmentEngine.java:419)

    at org.eclipse.wst.command.internal.env.core.fragment.CommandFragmentEngine.visitTop(CommandFragmentEngine.java:359)

    at org.eclipse.wst.command.internal.env.core.fragment.CommandFragmentEngine.moveForwardToNextStop(CommandFragmentEngine.java:254)

    at org.eclipse.wst.command.internal.env.ui.widgets.SimpleCommandEngineManager$6.run(SimpleCommandEngineManager.java:294)

    at org.eclipse.jface.operation.ModalContext.runInCurrentThread(ModalContext.java:464)

    at org.eclipse.jface.operation.ModalContext.run(ModalContext.java:372)

    at org.eclipse.jface.wizard.WizardDialog.run(WizardDialog.java:1028)

    at org.eclipse.wst.command.internal.env.ui.widgets.SimpleCommandEngineManager.runForwardToNextStop(SimpleCommandEngineManager.java:264)

    at org.eclipse.wst.command.internal.env.ui.widgets.WizardPageManager.runForwardToNextStop(WizardPageManager.java:91)

    at org.eclipse.wst.command.internal.env.ui.widgets.WizardPageManager.getNextPage(WizardPageManager.java:154)

    at org.eclipse.wst.command.internal.env.ui.widgets.SimpleWizardPage.getNextPage(SimpleWizardPage.java:136)

    at org.eclipse.jface.wizard.WizardDialog.nextPressed(WizardDialog.java:908)

    at org.eclipse.jface.wizard.WizardDialog.buttonPressed(WizardDialog.java:428)

    at org.eclipse.jface.dialogs.Dialog$2.widgetSelected(Dialog.java:628)

    at org.eclipse.swt.widgets.TypedListener.handleEvent(TypedListener.java:248)

    at org.eclipse.swt.widgets.EventTable.sendEvent(EventTable.java:84)

    at org.eclipse.swt.widgets.Widget.sendEvent(Widget.java:1057)

    at org.eclipse.swt.widgets.Display.runDeferredEvents(Display.java:4170)

    at org.eclipse.swt.widgets.Display.readAndDispatch(Display.java:3759)

    at org.eclipse.jface.window.Window.runEventLoop(Window.java:826)

    at org.eclipse.jface.window.Window.open(Window.java:802)

    at org.eclipse.ui.internal.handlers.WizardHandler$New.executeHandler(WizardHandler.java:259)

    at org.eclipse.ui.internal.handlers.WizardHandler.execute(WizardHandler.java:279)

    at org.eclipse.ui.internal.handlers.HandlerProxy.execute(HandlerProxy.java:290)

    at org.eclipse.ui.internal.handlers.E4HandlerProxy.execute(E4HandlerProxy.java:90)

    at sun.reflect.GeneratedMethodAccessor26.invoke(Unknown Source)

    at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)

    at java.lang.reflect.Method.invoke(Unknown Source)

    at org.eclipse.e4.core.internal.di.MethodRequestor.execute(MethodRequestor.java:56)

    at org.eclipse.e4.core.internal.di.InjectorImpl.invokeUsingClass(InjectorImpl.java:243)

    at org.eclipse.e4.core.internal.di.InjectorImpl.invoke(InjectorImpl.java:224)

    at org.eclipse.e4.core.contexts.ContextInjectionFactory.invoke(ContextInjectionFactory.java:132)

    at org.eclipse.e4.core.commands.internal.HandlerServiceHandler.execute(HandlerServiceHandler.java:167)

    at org.eclipse.core.commands.Command.executeWithChecks(Command.java:499)

    at org.eclipse.core.commands.ParameterizedCommand.executeWithChecks(ParameterizedCommand.java:508)

    at org.eclipse.e4.core.commands.internal.HandlerServiceImpl.executeHandler(HandlerServiceImpl.java:213)

    at org.eclipse.ui.internal.handlers.LegacyHandlerService.executeCommand(LegacyHandlerService.java:420)

    at org.eclipse.ui.internal.actions.CommandAction.runWithEvent(CommandAction.java:157)

    at org.eclipse.jface.action.ActionContributionItem.handleWidgetSelection(ActionContributionItem.java:584)

    at org.eclipse.jface.action.ActionContributionItem.access$2(ActionContributionItem.java:501)

    at org.eclipse.jface.action.ActionContributionItem$5.handleEvent(ActionContributionItem.java:411)

    at org.eclipse.swt.widgets.EventTable.sendEvent(EventTable.java:84)

    at org.eclipse.swt.widgets.Widget.sendEvent(Widget.java:1057)

    at org.eclipse.swt.widgets.Display.runDeferredEvents(Display.java:4170)

    at org.eclipse.swt.widgets.Display.readAndDispatch(Display.java:3759)

    at org.eclipse.e4.ui.internal.workbench.swt.PartRenderingEngine$9.run(PartRenderingEngine.java:1113)

    at org.eclipse.core.databinding.observable.Realm.runWithDefault(Realm.java:332)

    at org.eclipse.e4.ui.internal.workbench.swt.PartRenderingEngine.run(PartRenderingEngine.java:997)

    at org.eclipse.e4.ui.internal.workbench.E4Workbench.createAndRunUI(E4Workbench.java:138)

    at org.eclipse.ui.internal.Workbench$5.run(Workbench.java:610)

    at org.eclipse.core.databinding.observable.Realm.runWithDefault(Realm.java:332)

    at org.eclipse.ui.internal.Workbench.createAndRunWorkbench(Workbench.java:567)

    at org.eclipse.ui.PlatformUI.createAndRunWorkbench(PlatformUI.java:150)

    at org.eclipse.ui.internal.ide.application.IDEApplication.start(IDEApplication.java:124)

    at org.eclipse.equinox.internal.app.EclipseAppHandle.run(EclipseAppHandle.java:196)

    at org.eclipse.core.runtime.internal.adaptor.EclipseAppLauncher.runApplication(EclipseAppLauncher.java:110)

    at org.eclipse.core.runtime.internal.adaptor.EclipseAppLauncher.start(EclipseAppLauncher.java:79)

    at org.eclipse.core.runtime.adaptor.EclipseStarter.run(EclipseStarter.java:354)

    at org.eclipse.core.runtime.adaptor.EclipseStarter.run(EclipseStarter.java:181)

    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)

    at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)

    at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)

    at java.lang.reflect.Method.invoke(Unknown Source)

    at org.eclipse.equinox.launcher.Main.invokeFramework(Main.java:636)

    at org.eclipse.equinox.launcher.Main.basicRun(Main.java:591)

    at org.eclipse.equinox.launcher.Main.run(Main.java:1450)

    at org.eclipse.equinox.launcher.Main.main(Main.java:1426)

    sai dirmi come risolvere?

    • Alessio

      Il problema si “risolve” non selezionando “Monitor the Web Service”, ma dal punto 4 in poi a me non compare niente di quello scritto su;
      al punto 2 quando seleziono MyService in ServiceImplementation mi da il warning “No server has been selected. The Web service will not be deployed, installed, or run.”, e al posto del punto 4 una finestra con due checkbox relativi a elementi UDDI,

      come posso fare?

      • cosenonjaviste

        Ciao. Forse nel post ho dato per scontato che Tomcat sia già stato aggiunto tra i server gestiti da Eclipse. Controlla se nel tab “Servers” hai Tomcat.