Activitats

Creació d'un servei web RESTful que processi les peticions asíncronament

L’objectiu d’aquesta activitat és veure les capacitats que proporciona l’API JAX-RS 2.0 tant per processar les peticions de forma asíncrona (sense bloqueig) al costat del servidor com per fer clients que facin les peticions de forma asíncrona.

Creeu un servei web RESTful amb Java EE 7 que processi les peticions de forma asíncrona; el servei web serà molt senzill, tan sols caldrà que torni la salutació “Hello World!!!”. Per veure que realment processa les peticions asíncronament poseu-hi una espera activa de 5 segons.

Un cop fet el servei web asíncron proveu-lo primer amb un client que el consumeixi síncronament i després feu un altre client que el consumeixi asíncronament.

Com que es tracta d’un exemple molt senzill no ens cal cap projecte de partida, simplement creeu a NetBeans un nou projecte Maven de tipus Web Application. Per fer-ho, feu File / New Project i us apareixerà l’assistent de creació de projectes. A l’assistent, seleccioneu Maven i Web Application, tal com es pot veure en la figura.

Figura Creació de projectes a NetBeans

A la següent pantalla (vegeu la figura) triareu el nom del projecte; el podeu anomenar “resthelloasyncioc”, i el paquet per defecte on anirà el codi font, per exemple cat.xtec.ioc.resthelloasyncioc.

Figura Nom del nou projecte

A la següent pantalla de l’assistent deixeu el valors per defecte i cliqueu Finish.

Un cop creat el projecte cal que modifiqueu el pom.xml per afegir-hi les següents dependències:

  1. <dependency>
  2. <groupId>org.glassfish.main.extras</groupId>
  3. <artifactId>glassfish-embedded-all</artifactId>
  4. <version>4.0</version>
  5. <scope>provided</scope>
  6. <type>jar</type>
  7. </dependency>
  8. <dependency>
  9. <groupId>org.glassfish.jersey.core</groupId>
  10. <artifactId>jersey-server</artifactId>
  11. <version>2.22.1</version>
  12. </dependency>
  13. <dependency>
  14. <groupId>com.sun.jersey</groupId>
  15. <artifactId>jersey-json</artifactId>
  16. <version>1.19</version>
  17. </dependency>
  18. <dependency>
  19. <groupId>org.glassfish.jersey.media</groupId>
  20. <artifactId>jersey-media-json-jackson</artifactId>
  21. <version>2.22.1</version>
  22. </dependency>

Recarregueu el pom.xml fent clic amb el botó dret damunt el nom del projecte, i prement l’opció Reload POM del menú contextual ja tindreu el projecte configurat i llest per començar.

Amb el model de petició/resposta síncron la petició del client és acceptada i processada en un únic fil (thread) del servidor. Normalment tenim un pool finit de threads al servidor per acceptar i servir peticions; amb aquest model el thread que accepta la petició també s’encarrega de servir-la i es bloqueja fins que pot tornar la petició al client.

Aquest model funciona molt bé si les peticions no triguen massa temps a ser processades i servides, però quan el nombre de peticions s’incrementa i aquestes peticions triguen força temps, el pool de threads acaba saturat i ja no accepta més peticions.

El processament asíncron intenta mitigar aquesta problemàtica oferint un model que separa l’acceptació de les peticions del processament de les mateixes. Bàsicament ho fem tenint un conjunt de threads que tan sols es dediquen a acceptar les peticions (acceptors) i un altre conjunt de threads diferents que les processen i envien el resultat al client (workers). El processament asíncron impacta molt en el throughput i en l’escalabilitat del servidor!

Tingueu en compte, però, que el processament asíncron afegeix complexitat el desenvolupament dels serveis web; cal analitzar molt bé la tipologia de l’aplicació per decidir si és millor un model síncron o un model asíncron.

Un cop vista la justificació del model asíncron i creat el projecte que ens servirà de base, ara cal que codifiqueu el servei web RESTful, que ens ha de tornar la salutació “Hello World!!!” de forma asíncrona. El servei web respondrà a peticions GET a la URI /helloasync. Per tant, a les peticions d’aquest tipus:

GET http://localhost:8080/resthelloasyncioc/helloasync

Ha de respondre amb:

Hello World!!!

Creeu la classe de configuració anomenada ApplicationConfig al paquet cat.xtec.ioc.resthelloasyncioc.service amb el següent codi:

  1. package cat.xtec.ioc.resthelloasyncioc.service;
  2. import javax.ws.rs.core.Application;
  3. @javax.ws.rs.ApplicationPath("rest")
  4. public class ApplicationConfig extends Application {
  5. }

Un cop decidit el funcionament del nostre servei, el següent que us cal fer és codificar una classe Java que implementi la funcionalitat demanada. Per fer-ho creeu una classe Java, anomeneu-la HelloWorldAsyncService i poseu-la al paquet cat.xtec.ioc.resthelloasyncioc.service.

Anoteu la classe amb l’anotació @Path(“/helloasync”) i hi creeu un mètode, anomenat, per exemple, sayHelloAsync, i l’anoteu amb @GET i @Produces(“text/html”).

  1. @Stateless
  2. @Path("/helloasync")
  3. public class HelloWorldAsyncService {
  4. @Resource
  5. ManagedExecutorService mes;
  6.  
  7. @GET
  8. @Produces("text/html")
  9. public void sayHelloAsync(@Suspended final AsyncResponse ar) {
  10. final String initialThread = Thread.currentThread().getName();
  11. System.out.println("Thread: "+ initialThread + " in action...");
  12.  
  13. mes.execute(new Runnable() {
  14. @Override
  15. public void run() {
  16. try {
  17. String processingThread = Thread.currentThread().getName();
  18. System.out.println("Processing thread: " + processingThread);
  19.  
  20. Thread.sleep(5000);
  21. String respBody = "Hello World!!! - Process initiated in " + initialThread + " and finished in " + processingThread;
  22. ar.resume(Response.ok(respBody).build());
  23.  
  24. } catch (InterruptedException ex) {
  25. Logger.getLogger(HelloWorldAsyncService.class.getName()).log(Level.SEVERE, null, ex);
  26. }
  27. }
  28. });
  29. }
  30. }

Hi ha diverses coses a comentar d’aquest codi:

  • Afegim una espera activa de 5 segons abans de tornar la salutació.
  • La salutació mostrarà quin thread accepta la petició (acceptor) i quin fa l’espera activa (worker).
  • El mètode sayHelloAsync injecta una instància de AsyncResponse amb l’anotació @Suspended per indicar al runtime de JAX-RS que la petició es resoldrà de forma asíncrona.
  • Just després de crear el worker, el thread que ha recollit la petició queda alliberat i torna a estar disponible per acceptar noves peticions.
  • No és gaire rellevant per a l’exemple, però el worker thread s’obté del ManagedExecutorService que proporciona el servidor Java EE 7 (Glassfish).
  • El tipus de retorn de sayHelloAsync és void, ja que aquest thread tan sols recull la petició i la passa al worker, que és l’encarregat de tornar la resposta al client.
  • El worker torna la resposta al client amb el mètode resume.

Ja teniu un servei web RESTful que us torna la salutació “Hello World!!!” de forma asíncrona. Si el deplegueu al servidor fent Run a NetBeans i el proveu accedint a l’URL localhost:8080/resthelloasyncioc/rest/helloasync veureu que el servei web triga 5 segons a tornar-vos la salutació i que teniu dos threads involucrats en resolució de la petició (vegeu la figura).

Figura Sortida del servei web

El fet que trigui 5 segons a contestar és normal, ja que l’asincronia l’hem creat al servidor però el client (en aquest cas el navegador) encara espera els 5 segons a rebre la resposta. Al final de l’exemple veureu com fer el client també asíncron.

El codi anterior funciona perfectament, però com podem assegurar que els client no es quedin indefinidament esperant la resposta del servidor? Ens cal alguna mena de timeout després del qual el client rebi un error HTTP 503 – Service Unavailable.

Per fer-ho, simplement especifiqueu el timeout a la instància AsyncResponse, que rep el mètode sayHelloAsync, just abans de fer l’execute:

  1. ar.setTimeout(3, TimeUnit.SECONDS);

Si ho feu, desplegueu el projecte fent Run a NetBeans i ho proveu, per exemple, amb cUrl i l’opció -v per tal de veure la petició i la resposta; veurem que obtenim el 503 esperat (vegeu la figura).

Figura Error HTTP 503 – Service Unavailable

Tragueu la línia anterior i desplegueu el projecte fent Run a NetBeans per tal de seguir provant el servei web asíncron sense que us doni timeout.

Ara que ja teniu el servei web implementat asíncronament a la part del servidor fareu un client Java stand-alone que faci servir les capacitats asíncrones de l’API client de JAX-RS per tal que el client no es quedi bloquejat esperant els 5 segons.

Creeu la classe Java que farà de client al paquet cat.xtec.ioc.resthelloasyncioc.client i l’anomeneu HelloWorldAsyncClient amb el següent codi:

  1. public class HelloWorldAsyncClient {
  2.  
  3. public static void main(String[] args) throws InterruptedException, ExecutionException {
  4. Client client = ClientBuilder.newClient();
  5. WebTarget target = client.target("http://localhost:8080/resthelloasyncioc/rest/helloasync");
  6. Invocation.Builder reqBuilder = target.request();
  7. AsyncInvoker asyncInvoker = reqBuilder.async();
  8. Future<Response> futureResp = asyncInvoker.get(new InvocationCallback<Response>() {
  9. @Override
  10. public void completed(Response response) {
  11. String responseBody = response.readEntity(String.class);
  12. System.out.println(responseBody);
  13. }
  14.  
  15. @Override
  16. public void failed(Throwable throwable) {
  17. System.out.println("Failed");
  18. }
  19.  
  20. });
  21.  
  22. while (!futureResp.isDone()) {
  23. System.out.println("I'm not locked waiting for the service response ;-)");
  24. }
  25. }
  26. }

Hi ha diverses coses a comentar d’aquest codi:

  • Utilitza la versió asíncrona del Invoker anomenada AsyncInvoker cridant el mètode async del Invocation.Builder.
  • Aquesta crida torna un objecte del tipus Future i hi registrem dos mètodes de callback que es cridaran quan el servei ens torni la resposta.
  • El mètode de callback completed es cridarà si no hi ha cap error i el mètode failed si hi ha algun error.
  • En aquest punt, el codi del client NO està bloquejat!!!. Per demostrar-ho posem un bucle que va mostrant per la sortida el missatge “I’m not locked waiting for the service response ;-)” fins que el servei hagi respost.
  • Passats els 5 segons s’executa el mètode de callback completed i es mostra per consola la resposta del servei web.

Per provar-ho poseu-vos damunt de la classe HelloWorldAsyncClient i feu Run File i veureu la sortida a la consola de NetBeans:

I'm not locked waiting for the service response ;-)
I'm not locked waiting for the service response ;-)
I'm not locked waiting for the service response ;-)
I'm not locked waiting for the service response ;-)
I'm not locked waiting for the service response ;-)
I'm not locked waiting for the service response ;-)
I'm not locked waiting for the service response ;-)
Hello World!!! - Process initiated in http-listener-1(4) and finished in concurrent/__defaultManagedExecutorService-managedThreadFactory-Thread-11
------------------------------------------------------------------------
BUILD SUCCESS
------------------------------------------------------------------------

Cal remarcar que aquest mateix codi us serveix tant per cridar de forma asíncrona serveis web implementats asíncronament a la part servidora com per cridar asíncronament serveis web implementats síncronament a la part servidora.

El codi de la solució d’aquesta activitat el podeu descarregar del següent

enllaç ( 6.3 KB )
.

Anar a la pàgina següent:
Exercicis d'autoavaluació