Serveis web RESTful amb Java EE7. Consumint serveis web
Explicarem, mitjançant exemples, com podem consumir serveis web RESTful remots amb Java.
Un cop codificat el servei web RESTful, el següent que ens cal fer és accedir-hi, i per fer-ho l’únic que ens cal és fer les diferents peticions HTTP a l’URI del servidor que tingui els recursos als quals volem accedir.
Es poden provar els serveis RESTful amb qualsevol eina que permeti fer peticions HTTP.
La primera eina en la qual pensaríem tots és un navegador web (Microsoft Internet Explorer, Google Chrome, Mozilla Firefox, etc.). El problema és que els navegadors, per defecte, tan sols poden fer peticions GET
i peticions POST
. Si voleu fer peticions d’un altre tipus (PUT
, DELETE
, HEAD
) us caldrà instal·lar algun plugin al navegador que us ho permeti (Postman és un possible plugin per a Google Chrome, però n’hi ha molts).
Una altra opció és utilitzar la utilitat cURL que us permet fer tot tipus de peticions HTTP per línia de comandes.
Si el que volem és consumir serveis web RESTful des de Java, l’única opció que hi havia abans de JAX-RS 2.0 era fer servir l’API de baix nivell java.net.HttpURLConnection
o alguna llibreria propietària que us fes la vida una mica més fàcil.
JAX-RS 2.0 proporciona una API client estàndard que permet fer tota mena de peticions HTTP als serveis web RESTful remots de forma fàcil.
L’API client de JAX-RS és molt senzilla d’utilitzar; moltes vegades tan sols us caldrà utilitzar tres classes: Client
, WebTarget
i Response
.
Amb SOAP, l’API JAX-WS forma part del mateix JDK, mentre que amb JAX-RS cal que incloguem JAX-RS al classpath del client. JAX-WS generava un conjunt d’artefactes automàticament per poder connectar amb els serveis web, mentre que JAX-RS no en genera cap; tan sols cal tenir el .jar de la implementació de l’API al classpath.
Un client per al servei web RESTful que contesta "Hola"
Veurem com consumir serveis web RESTful des d’una aplicació Java stand-alone utilitzant l’API que proporciona JAX-RS.
Creació i configuració inicial del projecte
Descarregueu el codi del projecte “Resthelloioc” en l’estat inicial d’aquest apartat des de l’enllaç que trobareu als annexos de la unitat, i importeu-lo a NetBeans.
Tot i que també podeu descarregar-vos el projecte en l’estat final des del annexos de la unitat, sempre és millor que aneu fent vosaltres tots els passos partint del projecte en l’estat inicial de l’apartat.
El projecte és un senzill servei web RESTful que respon a peticions GET
a l’URI localhost:8080/resthelloioc/rest/hello amb la salutació “Hello World!!!”.
Creació del client Java 'stand-alone'
Quan NetBeans us demani quins imports voleu afegir especifiqueu els del paquet javax.ws.rs
.
Creeu la classe Java que farà de client al paquet cat.xtec.ioc.resthelloioc.client
i l’anomeneu HelloWorldClient
amb el següent codi:
public class HelloWorldClient { Client client = ClientBuilder.newClient(); WebTarget target = client.target("http://localhost:8080/resthelloioc/rest/hello"); Invocation invocation = target.request(MediaType.TEXT_HTML).buildGet(); Response res = invocation.invoke(); } }
Fem servir la interfície Client
per construir l’objecte WebTarget
.
Client client = ClientBuilder.newClient();
L’objecte WebTarget
representa l’URI on enviarem les peticions; en el nostre cas, a localhost:8080/resthelloioc/rest/hello.
WebTarget target = client.target("http://localhost:8080/resthelloioc/rest/hello");
Un cop tenim l’URI on enviarem les peticions ens cal construir la petició HTTP. Ho fem amb la interfície Invocation
, que permet, entre moltes altres coses, especificar el tipus MIME de la petició:
Invocation invocation = target.request(MediaType.TEXT_HTML).buildGet();
En aquest punt, quan construïm la petició, podrem especificar paràmetres, especificar el path, els objectes i el format d’aquests per a les peticions POST
i PUT
, etc. Tot això ho podrem fer mitjançant els mètodes de la interfície Invocation
.
La creació de l’objecte Invocation
no executa encara la petició, i per fer-ho cal que executeu el mètode invoke
:
Response res = invocation.invoke()
Aquesta crida, en el nostre cas, fa una petició GET
a un servei web RESTful que hi ha a localhost:8080/resthelloioc/rest/hello i ens torna el resultat en text/HTML.
API fluent
Una API fluent és un patró de disseny que permet encadenar les crides als mètodes d’un objecte amb l’objectiu de fer un codi més elegant, concís i comprensible.
Noteu que, pel fet que l’API client de JAX-RS és una API fluent, podríem escriure tot el codi anterior d’una forma molt més concisa:
Response response = ClientBuilder.newClient() .target("http://localhost:8080/resthelloioc/rest/hello") .request(MediaType.TEXT_HTML).get();
L’objecte Response
representa la resposta del servei web i conté, a part del “Hello World!!!”, informació de la resposta HTTP rebuda del servei web. Amb Response
podreu verificar els codis HTTP de retorn, accedir a les capçaleres, a les cookies i al valor de retorn.
Accedirem al valor retornat pel servei web amb el mètode readEntity
; a aquest mètode li heu de passar un objecte que permeti fer la transformació entre la representació del recurs que envia el servidor (en el nostre cas, un senzill String, però poden ser tipus complexes) i el tipus de dades que volem. JAX-RS s’encarregarà de fer aquestes transformacions.
Ara ja tan sols ens queda desplegar el servei web i executar el client per veure si es comporta com volem.
Desplegament del servei web i prova amb el client Java
Desplegueu el servei web com a part de l’aplicació Java EE que el conté fent Clean and Build i després Run a NetBeans.
Comproveu que el servei web està desplegat correctament accedint a l’URL localhost:8080/resthelloioc/rest/hello amb un navegador. El servei web us ha de tornar la salutació “Hello World!!!”.
Si tot és correcte ja podeu executar el client; per fer-ho, poseu-vos damunt de la classe HelloWorldClient
i feu Run File a NetBeans (vegeu la figura).
I veureu la salutació ”Hello World!!!” a la consola de sortida de NetBeans:
Hello World!!! ------------------------------------------------------------------------ BUILD SUCCESS ------------------------------------------------------------------------
El servei web de dades de llibres. Consum i testeig
Veurem com consumir i fer un test d’integració d’un servei web que permet als clients fer operacions sobre un catàleg de llibres des d’un conjunt de test d’integració amb JUnit i l’API client que proporciona JAX-RS.
El primer que fareu serà desplegar a Glassfish el servei web REST de gestió del catàleg de llibres i després creareu el conjunt de tests d’integració que faran peticions HTTP al servei web amb l’API client de JAX-RS.
Els tests d’integració difereixen dels tests unitaris en el fet que no testegen el codi de forma aïllada, sinó que requereixen que el codi a testejar estigui desplegat al servidor d’aplicacions per funcionar.
Creació i configuració inicial del projecte
Descarregueu el codi del projecte “Restbooksioc” des de l’enllaç disponible als annexos de la unitat i importeu-lo a NetBeans.
Tot i que podeu descarregar-vos el projecte en l’estat final d’aquest apartat en l’altre enllaç disponible, sempre és millor que aneu fent vosaltres tots els passos partint del projecte en l’estat inicial de l’apartat. Recordeu que podeu utilitzar la funció d’importar per carregar els projectes a NetBeans.
Aquest projecte correspon al servei web RESTful de gestió d’un catàleg de llibres i ja té codificades les següents operacions:
- Llistar tots els llibres del catàleg.
- Consulta d’un llibre mitjançant l’ISBN.
- Creació d’un llibre al catàleg.
- Actualització de les dades d’un llibre.
- Esborrar un llibre del catàleg.
- Cercar llibres per títol.
Crearem els tests d’integració dins el mateix projecte que conté el codi del servei web que volem provar. Una altra opció, igualment vàlida, seria crear un nou projecte i posar-hi només el test.
JUnit és un framework de test que utilitza anotacions per identificar els mètodes que especifiquen un test. A JUnit, un test, ja sigui unitari o d’integració, és un mètode que s’especifica en una classe que només s’utilitza per al test. Això s’anomena classe de test. Un mètode de test amb JUnit 4 es defineix amb l’anotació @org.junit.Test
. En aquest mètode s’utilitza un mètode d’asserció en el qual es comprova el resultat esperat de l’execució de codi en comparació del resultat real.
Per tal de fer els tests d’integració farem servir JUnit 4; per fer-ho ens cal tenir la dependència a JUnit al pom.xml del projecte. Al projecte de partida ja hi teniu aquesta dependència afegida.
Tot i que el resultat de l’execució dels tests es pot veure a la finestra de sortida de NetBeans, és molt més còmode visualitzar-ho a la finestra de resultats de test que proporciona també NetBeans. Per mostrar aquesta finestra aneu al menú Window / IDE Tools / Test Results (vegeu la figura).
A Netbeans, els tests es poden executar de forma individual, és a dir, classe per classe o tots els del projecte. Si voleu executar tots els tests d’un projecte primer heu de designar com a projecte principal el projecte “Restbooksioc”, tal com podeu veure en la figura.
Un cop fet això, desplegueu el servei web com a part de l’aplicació Java EE que el conté fent Clean and Build i després Run a NetBeans.
Comproveu que el servei web s’ha desplegat correctament accedint a l’URL localhost:8080/restbooksioc/rest/books amb un navegador. El servei web us ha de tornar el llistat de llibres que té el catàleg en format JSON:
[{"isbn":"9788425343537","author":"Ildefonso Falcones","title":"La catedral del mar"}, {"isbn":"9788467009477","author":"Jose Maria Peridis Perez","title":"La luz y el misterio de las catedrales"}]
Creació i execució dels tests d’integració
Quan NetBeans us demani quins imports voleu afegir, especifiqueu els del paquet javax.ws.rs
.
Ara toca començar a crear els tests d’integració per provar el servei web RESTful de gestió del catàleg.
Per fer-ho, creeu un nou paquet dins de Test Packages
anomenat, per exemple, cat.xtec.ioc.test, i creeu-hi una classe Java anomenada BooksRestServiceTest
amb el següent codi:
package cat.xtec.ioc.test; import javax.ws.rs.client.Client; import javax.ws.rs.client.ClientBuilder; public class BooksRestServiceTest { private static final Client client = ClientBuilder.newClient(); }
Estructura dels tests
Un dels patrons més utilitzats a l’hora d’estructurar el codi d’un test és l’anomenat AAA (de l’anglès Arrange-Act-Assert); amb aquest, els tests sempre tindran una fase de preparació (Arrange), una d’execució (Act) i una de verificació de resultats (Assert).
El primer test que fareu serà un test que comprovi que la consulta de tots els llibres del catàleg us torna la llista sencera de llibres; per fer-ho, creeu un mètode anomenat shouldReturnAllBooks
dins la classe BooksRestServiceTest
amb el següent codi:
@Test public void shouldReturnAllBooks() { // Arrange URI uri = UriBuilder.fromUri("http://localhost/restbooksioc/rest/books").port(8080).build(); WebTarget target = client.target(uri); Invocation invocation = target.request(MediaType.APPLICATION_JSON).buildGet(); // Act Response res = invocation.invoke(); List<Book> returnedBooks = res.readEntity(new GenericType<List<Book>>() {}); // Assert assertTrue(returnedBooks.contains(first)); assertTrue(returnedBooks.contains(second)); }
Si analitzeu el codi veureu diverses coses importants: la primera és que heu anotat el mètode shouldReturnAllBooks
amb l’anotació @Test
per indicar que es tracta d’un mètode de test.
A la fase de preparació feu servir la interfície Client
per construir l’objecte WebTarget
, que representa l’URI on enviareu les peticions; en el vostre cas, a localhost:8080/restbooksioc/rest/books.
URI uri = UriBuilder.fromUri("http://localhost/restbooksioc/rest/books").port(8080).build(); WebTarget target = client.target(uri);
Un cop teniu l’URI on enviareu les peticions cal construir la petició HTTP. Ho feu amb la interfície Invocation
, que permet, entre moltes altres coses, especificar el tipus MIME de la petició:
Invocation invocation = target.request(MediaType.APPLICATION_JSON).buildGet();
Tot l’anterior ha servit per preparar la petició que voleu enviar al servei web; el següent que fareu serà l’execució, cridant el mètode invoke
:
Response res = invocation.invoke();
Aquesta crida, en el vostre cas, fa una petició GET
a un servei web RESTful que hi ha a localhost:8080/restbooksioc/rest/books i torna el resultat en format application/json
.
L’objecte Response
representa la resposta del servei web que us permetrà verificar que el resultat és correcte. Per fer-ho, accedireu al valor retornat pel servei web amb el mètode readEntity
; a aquest mètode li heu de passar un objecte que permeti fer la transformació entre la representació del recurs que envia el servidor (en el vostre cas, la llista de llibres en format JSON) i el tipus de dades que voleu. JAX-RS s’encarregarà de fer aquestes transformacions.
List<Book> returnedBooks = res.readEntity(new GenericType<List<Book>>() {});
En la fase de verificació comproveu, per exemple, que el títol dels dos llibres coincideix amb l’esperat:
assertTrue(returnedBooks.contains(first)); assertTrue(returnedBooks.contains(second));
Ja podeu executar el test que heu creat fent Test File (vegeu la figura) al menú contextual de la classe de Test
.
Si tot ha anat bé veureu el resultat a la finestra de Test
(vegeu la figura).
El segon test que fareu serà un test que comprovarà que si consulteu un llibre per ISBN que no existeix al catàleg de llibres el servei web us torna el codi HTTP 404 – Not Found.
Per fer-ho, creeu un mètode anomenat nonExistentBookShouldReturn404
dins la classe BooksRestServiceTest
amb el següent codi:
@Test public void nonExistentBookShouldReturn404() { // Arrange URI uri = UriBuilder.fromUri("http://localhost/restbooksioc/rest/books").port(8080).build(); WebTarget target = client.target(uri).path("unknownISBN"); Invocation invocation = target.request(MediaType.APPLICATION_JSON).buildGet(); // Act Response res = invocation.invoke(); // Assert assertEquals(Response.Status.NOT_FOUND.toString(), res.getStatusInfo().toString()); }
Fixeu-vos que ara l’URI on enviareu les peticions GET
és localhost:8080/restbooksioc/rest/books, i que li afegiu el path /unknownISBN. Aquesta crida correspon al mètode find
del servei web que té el següent codi:
@GET @Path("{isbn}") @Produces(MediaType.APPLICATION_JSON) return this.bookRepository.get(isbn); }
A la fase d’execució feu el mateix que per cercar tots els llibres del catàleg, i a la verificació ara comprovareu el codi HTTP retornat per la crida:
assertEquals(Response.Status.NOT_FOUND.toString(), res.getStatusInfo().toString());
L’objecte Response
representa la resposta del servei web i conté, a part del contingut, molta informació de la resposta HTTP rebuda. Amb Response
podeu verificar els codis HTTP de retorn i accedir a les capçaleres, a les cookies i al valor de retorn.
Si executeu els tests veureu que el test que comprova que el servei web torni un 404 – Not Found si consulteu un llibre per ISBN que no existeix falla perquè torna un 204 – No content enlloc de l’esperat 404 – Not Found (vegeu la figura).
El problema és la codificació del mètode que fa la cerca per ISBN al servei web; us tocarà modificar-lo si voleu que torni un error 404 enlloc d’un 204. Per fer-ho obriu la classe BooksRestService
del paquet cat.xtec.ioc.service
i canvieu el codi del mètode find
pel següent:
@GET @Path("{isbn}") @Produces(MediaType.APPLICATION_JSON) if(book == null) { throw new NotFoundException(); } return Response.ok(book).build(); }
Hem canviat el tipus de retorn a Response
, i en aquest objecte hi afegim el llibre en cas que el trobem. En cas que no trobem el llibre llencem una excepció de tipus NotFoundException
que el runtime de JAX-RS s’encarregarà de transformar en un error HTTP 404 – Not Found.
Desplegueu el servei web fent Run a NetBeans i torneu a executar el test. Si tot ha anat bé veureu que s’han executat correctament els dos tests (vegeu la figura).
El tercer test que mostrarem és un test que comprovarà que no es puguin afegir llibres amb contingut nul. El que volem és que si la informació del llibre que es vol afegir és nul·la, el servei web ens torni el codi HTTP 400 – Bad Request.
Per fer-ho, creeu un mètode anomenat attemptsToCreateNullBooksShouldReturn400
dins la classe BooksRestServiceTest
amb el següent codi:
@Test public void attemptsToCreateNullBooksShouldReturn400() { // Arrange URI uri = UriBuilder.fromUri("http://localhost/restbooksioc/rest/books").port(8080).build(); WebTarget target = client.target(uri); Invocation invocation = target.request(MediaType.APPLICATION_JSON).buildPost(null); // Act Response res = invocation.invoke(); // Assert assertEquals(Response.Status.BAD_REQUEST.toString(), res.getStatusInfo().toString()); }
Fixeu-vos que ara enviarem peticions POST
a l’URI localhost:8080/restbooksioc/rest/books especificant un objecte nul. Aquesta crida correspon al mètode create
del servei web:
@POST @Consumes(MediaType.APPLICATION_JSON) this.bookRepository.add(book); }
A la fase d’execució feu el mateix que per cercar tots els llibres del catàleg i a la verificació tornareu a comprovar el codi HTTP retornat per la crida:
// Assert assertEquals(Response.Status.BAD_REQUEST.toString(), res.getStatusInfo().toString());
Si executeu els tests veureu que el test que comprova que no es puguin afegir llibres amb contingut nul falla perquè torna un 500 – Internal Server Error enlloc del 400 – Bad Request(vegeu la figura).
El problema és la codificació del mètode que fa la creació de llibres al servei web; us tocarà modificar-lo si voleu que torni un error 400 enlloc d’un 500. Per fer-ho, obriu la classe BooksRestService
del paquet cat.xtec.ioc.service
i canvieu el codi del mètode create
pel següent:
@POST @Consumes(MediaType.APPLICATION_JSON) if(book == null) { throw new BadRequestException(); } this.bookRepository.add(book); return Response.ok(book).build(); }
Heu canviat el tipus de retorn a Response
, i si us passen un llibre nul llenceu una excepció de tipus BadRequestException
que el runtime de JAX-RS s’encarregarà de transformar en un error HTTP 400 – Bad Request.
Desplegueu el servei web fent Run a NetBeans i torneu a executar el test. Si tot ha anat bé veureu que s’han executat correctament els tres tests (vegeu la figura).
El quart i últim test que mostrarem és un test que comprovarà que la creació d’un llibre ens torni l’URL amb la qual es podrà consultar el llibre. Per fer-ho, creeu un mètode anomenat createBookShouldReturnTheURLToGetTheBook
dins la classe BooksRestServiceTest
amb el següent codi:
@Test public void createBookShouldReturnTheURLToGetTheBook() { // Arrange URI uri = UriBuilder.fromUri("http://localhost/restbooksioc/rest/books").port(8080).build(); WebTarget target = client.target(uri); Invocation invocation = target.request(MediaType.APPLICATION_JSON).buildPost(Entity.entity(book, MediaType.APPLICATION_JSON)); // Act Response res = invocation.invoke(); URI returnedUri = res.readEntity(URI.class); // Assert URI expectedUri = UriBuilder.fromUri("http://localhost/restbooksioc/rest/books").port(8080).path("9788423342518").build(); assertEquals(expectedUri, returnedUri); }
El codi del test és el mateix que el codi del test que comprovava que no es poguessin afegir llibres nul; tan sols canvia la comprovació final i el llibre a afegir. Evidentment, si executeu aquest test fallarà, ja que ara el mètode create
del servei web està tornant la representació del llibre en format JSON i no l’URL on es pot consultar el llibre.
Per fer que torni l’URL cal que modifiqueu un altre cop el codi del servei web. Per fer-ho, obriu la classe BooksRestService
del paquet cat.xtec.ioc.service
i canvieu el codi del mètode create
pel següent:
@POST @Consumes(MediaType.APPLICATION_JSON) if(book == null) { throw new BadRequestException(); } this.bookRepository.add(book); URI bookUri = uriInfo.getAbsolutePathBuilder().path(book.getIsbn()).build(); return Response.ok(bookUri).build(); }
També cal que afegiu el següent atribut a la classe:
L’objecte UriInfo
us permet accedir a informació sobre la petició, i amb això podreu construir l’URL amb la qual es podrà consultar el llibre creat i tornar-la a l’objecte Response
:
URI bookUri = uriInfo.getAbsolutePathBuilder().path(book.getIsbn()).build();
Desplegueu el servei web fent Run a NetBeans i torneu a executar el test. Si tot ha anat bé veureu que s’han executat correctament els quatre tests (vegeu la figura).
Aquests quatre exemples us donen la base per tal que pugueu fer tots els tests d’integració que se us acudeixin i així tenir el servei web de gestió del catàleg de llibres totalment provat!
Què s'ha après?
En aquest apartat heu vist les bases per al desenvolupament de clients Java que accedeixin a serveis web RESTful amb Java EE 7 i els heu treballat de manera pràctica mitjançant exemples.
Concretament, heu après:
- Les bases de l’API Client de JAX-RS.
- A desenvolupar i provar un client Java stand-alone que consulti un servei web RESTful mitjançant l’API Client de JAX-RS.
- A fer tests d’integració que us permeten provar els serveis web RESTful.
Per aprofundir en algun d’aquests conceptes i veure com podeu treballar de forma asíncrona amb els serveis web RESTful us recomanem la realització de l’activitat associada a aquest apartat.