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:

  1. public class HelloWorldClient {
  2.  
  3. public static void main(String[] args) {
  4. Client client = ClientBuilder.newClient();
  5. WebTarget target = client.target("http://localhost:8080/resthelloioc/rest/hello");
  6. Invocation invocation = target.request(MediaType.TEXT_HTML).buildGet();
  7. Response res = invocation.invoke();
  8. System.out.println(res.readEntity(String.class));
  9. }
  10. }

Fem servir la interfície Client per construir l’objecte WebTarget.

  1. Client client = ClientBuilder.newClient();

L’objecte WebTarget representa l’URI on enviarem les peticions; en el nostre cas, a localhost:8080/resthelloioc/rest/hello.

  1. 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ó:

  1. 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:

  1. 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:

  1. Response response = ClientBuilder.newClient()
  2. .target("http://localhost:8080/resthelloioc/rest/hello")
  3. .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.

  1. res.readEntity(String.class)

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).

Figura Execució d’una classe Java a NetBeans

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).

Figura Finestra de resultats dels tests

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.

Figura Assignació del projecte com a projecte principal

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:

  1. package cat.xtec.ioc.test;
  2.  
  3. import javax.ws.rs.client.Client;
  4. import javax.ws.rs.client.ClientBuilder;
  5.  
  6. public class BooksRestServiceTest {
  7.  
  8. private static final Client client = ClientBuilder.newClient();
  9.  
  10. }

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:

  1. @Test
  2. public void shouldReturnAllBooks() {
  3. // Arrange
  4. URI uri = UriBuilder.fromUri("http://localhost/restbooksioc/rest/books").port(8080).build();
  5. WebTarget target = client.target(uri);
  6. Invocation invocation = target.request(MediaType.APPLICATION_JSON).buildGet();
  7. Book first = new Book("9788425343537", "Ildefonso Falcones", "La catedral del mar");
  8. Book second = new Book("9788467009477", "Jose Maria Peridis Perez", "La luz y el misterio de las catedrales");
  9.  
  10. // Act
  11. Response res = invocation.invoke();
  12. List<Book> returnedBooks = res.readEntity(new GenericType<List<Book>>() {});
  13.  
  14. // Assert
  15. assertTrue(returnedBooks.contains(first));
  16. assertTrue(returnedBooks.contains(second));
  17. }

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.

  1. URI uri = UriBuilder.fromUri("http://localhost/restbooksioc/rest/books").port(8080).build();
  2. 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ó:

  1. 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:

  1. 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.

  1. 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:

  1. assertTrue(returnedBooks.contains(first));
  2. 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.

Figura Execució del test

Si tot ha anat bé veureu el resultat a la finestra de Test (vegeu la figura).

Figura Resultat de l’execució del test

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:

  1. @Test
  2. public void nonExistentBookShouldReturn404() {
  3. // Arrange
  4. URI uri = UriBuilder.fromUri("http://localhost/restbooksioc/rest/books").port(8080).build();
  5. WebTarget target = client.target(uri).path("unknownISBN");
  6. Invocation invocation = target.request(MediaType.APPLICATION_JSON).buildGet();
  7.  
  8. // Act
  9. Response res = invocation.invoke();
  10.  
  11. // Assert
  12. assertEquals(Response.Status.NOT_FOUND.toString(), res.getStatusInfo().toString());
  13. }

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:

  1. @GET
  2. @Path("{isbn}")
  3. @Produces(MediaType.APPLICATION_JSON)
  4. public Book find(@PathParam("isbn") String isbn) {
  5. return this.bookRepository.get(isbn);
  6. }

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:

  1. 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).

Figura Resultat erroni de l’execució del test

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:

  1. @GET
  2. @Path("{isbn}")
  3. @Produces(MediaType.APPLICATION_JSON)
  4. public Response find(@PathParam("isbn") String isbn) {
  5. Book book = this.bookRepository.get(isbn);
  6. if(book == null) {
  7. throw new NotFoundException();
  8. }
  9. return Response.ok(book).build();
  10. }

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).

Figura Resultat correcte de l’execució dels tests

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:

  1. @Test
  2. public void attemptsToCreateNullBooksShouldReturn400() {
  3. // Arrange
  4. URI uri = UriBuilder.fromUri("http://localhost/restbooksioc/rest/books").port(8080).build();
  5. WebTarget target = client.target(uri);
  6. Invocation invocation = target.request(MediaType.APPLICATION_JSON).buildPost(null);
  7.  
  8. // Act
  9. Response res = invocation.invoke();
  10.  
  11. // Assert
  12. assertEquals(Response.Status.BAD_REQUEST.toString(), res.getStatusInfo().toString());
  13. }

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:

  1. @POST
  2. @Consumes(MediaType.APPLICATION_JSON)
  3. public void create(Book book) {
  4. this.bookRepository.add(book);
  5. }

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:

  1. // Assert
  2. 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).

Figura Resultat erroni de l’execució del test

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:

  1. @POST
  2. @Consumes(MediaType.APPLICATION_JSON)
  3. public Response create(Book book) {
  4. if(book == null) {
  5. throw new BadRequestException();
  6. }
  7. this.bookRepository.add(book);
  8.  
  9. return Response.ok(book).build();
  10. }

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).

Figura Resultat correcte de l’execució dels tests

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:

  1. @Test
  2. public void createBookShouldReturnTheURLToGetTheBook() {
  3. // Arrange
  4. Book book = new Book("9788423342518", "Clara Sanchez", "Lo que esconde tu nombre");
  5. URI uri = UriBuilder.fromUri("http://localhost/restbooksioc/rest/books").port(8080).build();
  6. WebTarget target = client.target(uri);
  7. Invocation invocation = target.request(MediaType.APPLICATION_JSON).buildPost(Entity.entity(book, MediaType.APPLICATION_JSON));
  8.  
  9. // Act
  10. Response res = invocation.invoke();
  11. URI returnedUri = res.readEntity(URI.class);
  12.  
  13. // Assert
  14. URI expectedUri = UriBuilder.fromUri("http://localhost/restbooksioc/rest/books").port(8080).path("9788423342518").build();
  15. assertEquals(expectedUri, returnedUri);
  16. }

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:

  1. @POST
  2. @Consumes(MediaType.APPLICATION_JSON)
  3. public Response create(Book book) {
  4. if(book == null) {
  5. throw new BadRequestException();
  6. }
  7. this.bookRepository.add(book);
  8.  
  9. URI bookUri = uriInfo.getAbsolutePathBuilder().path(book.getIsbn()).build();
  10. return Response.ok(bookUri).build();
  11. }

També cal que afegiu el següent atribut a la classe:

  1. @Context private UriInfo uriInfo;

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:

  1. 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).

Figura Resultat correcte de l’execució dels tests

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.

Anar a la pàgina anterior:
Annexos
Anar a la pàgina següent:
Activitats