Asynchronous processing

JAX-RS 2.0 has added support for asynchronous processing in both the client API and the server API. By default, when a client sends a request to the server, it suspends all other processing till the response is received. With asynchronous processing, the client suspends connection with the server and continues to process while a server response is being generated and sent back to the client. When the response is delivered to the client, the client re-establishes a connection with the server and accepts the response. The client-server model in synchronous and asynchronous request/response is illustrated in the following diagram:

Asynchronous processing

Similarly, by default a server thread blocks all other incoming client requests while waiting for an external process to complete one client request. With asynchronous processing, the server suspends connection with the client so that it may accept other client requests. When a response is available for a client request, the server re-establishes a connection with the client and sends the request. In this section, we will discuss asynchronous processing with an example. Create Java classes AsyncResource (for a root resource class), AsyncClient(for a client), and AsyncTimeoutHandler (for a timeout handler). The directory structure of the async classes is shown in Project Explorer as follows:

Asynchronous processing

The server API has added the javax.ws.rs.container.AsyncResponse interface to represent an asynchronous response for server-side processing of an asynchronous response. The javax.ws.rs.container.Suspended interface is provided to inject a suspended AsyncResponse instance into a resource method parameter. The AsyncResponse instance is bound to an active client request and can be used to provide a response asynchronously when a response is available. When a response is to be sent to the client, the AsyncResponse instance resumes the suspended request.

Suspended response

A resource or subresource method that injects a suspended AsyncResponse using the @Suspended annotation must declare the return type as void. If the injected AsyncResponse instance does not cancel or resume a suspended asynchronous response, the response is suspended indefinitely. In the AsyncResource root resource class, add a resource method (called timeout for example), which has a suspended AsyncResponse instance injected into a resource method parameter using the @Suspended annotation, as shown in the following listing. A template parameter {timeout} is included in the path URI for the resource method:

package org.jboss.resteasy.rest.async;

import javax.ws.rs.GET;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.Path;
import javax.ws.rs.container.AsyncResponse;
import javax.ws.rs.container.Suspended;
 

@Path("/helloworld")
public class AsyncResource {

  @GET
  @Path("/timeout/{timeout}")
  Produces("text/plain")
  public void timeout(@PathParam("timeout") String timeoutStr,@Suspended AsyncResponse ar) {}
}

In the AsyncClient, class includes a value of 60 for the {timeout} template parameter in the request URI, as shown in the following listing:

package org.jboss.resteasy.rest.async;

import javax.ws.rs.client.Client;
import javax.ws.rs.client.ClientBuilder;
import javax.ws.rs.client.WebTarget;
import javax.ws.rs.core.*;

public class AsyncClient {
  
  public static void main(String[] args) {
    
    Client client =	ClientBuilder.newClient();
    
    WebTarget target = client.target("http://localhost:8080/jboss-resteasy/rest/helloworld/timeout/60");
    
    String response = target.request("text/plain").get(String.class);
    System.out.println("Text response: " + response);
    
  }
  
}

Run the pom.xml file to deploy the jboss-resteasy application. When the AsyncClient application is run, the server does not return a response as the asynchronous response is suspended with the following exception being returned:

Suspended response

Resuming request processing

The suspended AsyncResponse may choose to resume the request processing, usually when a response is available, using the resume(Object) method. Build the response using the ResponseBuilder object, which may be obtained for the Response static method ok(Object). Set the media type for the response using the ResponseBuilder method type(MediaType) and create a Response object using the build() method. Resume the suspended request processing using the resume(Object) method to send the Response object. The AsyncResource root resource class is listed as follows:

package org.jboss.resteasy.rest.async;

import javax.ws.rs.GET;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.Path;
import javax.ws.rs.container.AsyncResponse;
import javax.ws.rs.container.Suspended;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;

@Path("/helloworld")
public class AsyncResource {
  
  @GET
  @Path("/timeout/{timeout}")
  @Produces("text/plain")
  public void timeout(@PathParam("timeout") String timeoutStr,
  @Suspended AsyncResponse ar) {
    
    try {
      Response hello = Response.ok("Hello John Smith").type(MediaType.TEXT_PLAIN).build();
      ar.resume(hello);
      
    } catch (Exception e) {
      System.out.println(e.getMessage());
    }
  }
}

The client class is the same as listed in the Suspended response section of this chapter. To compile and package the jboss-resteasy application, right-click on pom.xml and select Run As | Maven Install. Start the WildFly 8.1 server to deploy the application, and after the application has deployed, run the client class AsyncClient. Right-click on AsyncClient.java in Package Explorer and select Run As | Java Application. The client runs to produce the output, which is shown as follows:

Resuming request processing

The Response object to be sent may be a String literal. If a String literal is used in the resume(Object) as shown here, a Hello after a timeout message gets generated:

ar.resume("Hello after a timeout");

Resuming a request with a suspend timeout handler

The AsyncResponse instance may choose to update the suspended set data to set a new suspend time-out. A new suspend time-out is set as follows using the setTimeout(long time, TimeUnit unit) method:

ar.setTimeout(timeout, TimeUnit.SECONDS);

The ar variable is the AsyncResponse object. The new suspended timeout value overrides the previous timeout value. At the first invocation of setTimeout, the suspend timeout has gone from being suspended indefinitely to being suspended for the specified timeout value. The javax.ws.rs.container.TimeoutHandler interface is used to provide custom resolution of timeout events. The default resolution of a timeout event is for the JAX-RS 2.0 runtime to generate a Service unavailable exception. Set a suspend timeout handler using the setTimeoutHandler(TimeoutHandler handler) method:

ar.setTimeoutHandler(new AsyncTimeoutHandler("Timeouted after " + timeout + " seconds"));

The AsyncResource class to set a suspend timeout handler is listed as follows:

package org.jboss.resteasy.rest.async;

import java.util.concurrent.TimeUnit;

import javax.ws.rs.GET;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.Path;
import javax.ws.rs.container.AsyncResponse;
import javax.ws.rs.container.Suspended;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;

@Path("/helloworld")
public class AsyncResource {
  
  @GET
  @Path("/timeout/{timeout}")
  @Produces("text/plain")
  public void timeout(@PathParam("timeout") String timeoutStr,
  @Suspended AsyncResponse ar) {
    
    try {
      long timeout = Long.parseLong(timeoutStr);
      System.out.println("timeout - enter with timeout=" + timeoutStr
      + "s");
      ar.setTimeoutHandler(new AsyncTimeoutHandler("Timeouted after "
      + timeout + " seconds"));
      ar.setTimeout(timeout, TimeUnit.SECONDS);
    } catch (Exception e) {
      System.out.println(e.getMessage());
    }
  }
}

Make the AsyncTimeoutHandler timeout handler class implement the TimeoutHandler interface. In the AsyncTimeoutHandler, implement the handleTimeout(AsyncResponse asyncResponse) method in which the suspended timeout can be handled with one of the following methods:

  • The asynchronous response can be resumed using the resume(Object) method
  • The response can be resumed using the resume(Throwable) method to throw an exception
  • The response can be cancelled using the cancel() method
  • The suspend timeout can be extended using another invocation of the setTimeout(long time, TimeUnit unit) method

In the AsyncTimeoutHandler class, resume the asynchronous response using the resume(Object) method to return a response to the client. The AsyncTimeoutHandler class is listed as follows:

package org.jboss.resteasy.rest.async;
import java.util.concurrent.TimeUnit;
import javax.ws.rs.container.AsyncResponse;
import javax.ws.rs.container.TimeoutHandler;
public class AsyncTimeoutHandler implements TimeoutHandler {
  private String _message;
  boolean keepSuspended = false;
  //boolean cancel = true;
  boolean cancel = false;
  int retryPeriod = 10;
  AsyncTimeoutHandler(String message) {
    _message = message;
  }
  @Override
  public void handleTimeout(AsyncResponse ar) {
    System.out.println("handleTimeout - enter");
    if (keepSuspended) {
      ar.setTimeout(10, TimeUnit.SECONDS);
    } else if (cancel) {
      ar.cancel(retryPeriod);
    } else {
      ar.resume(_message);
    }
    /*Response hello = Response.ok("Hello after a timeout").type(MediaType.TEXT_PLAIN).build();
    ar.resume(hello);*/
  }
}

Redeploy the application with Maven and rerun the AsyncClient class. The new suspended timeout gets applied and the response gets suspended for 60 seconds as indicated by the message timeout- enter with timeout=60s, which is shown as follows:

Resuming a request with a suspend timeout handler

When the request processing is resumed, the following response, as shown in the following screenshot, is sent to the client and output from the client class AsyncClient.java:

Resuming a request with a suspend timeout handler

In the previous example, we resumed the request in the timeout handler. A request can be resumed in a resource method in which the new suspended timeout and the timeout handler are set before the new suspend timeout has run as shown in the resource method in the following listing:

@GET
@Path("/timeout/{timeout}")
@Produces("text/plain")
public void timeout(@PathParam("timeout") String timeoutStr, @Suspended AsyncResponse ar) {
  try {
    long timeout = Long.parseLong(timeoutStr);
    System.out.println("timeout - enter with timeout=" + timeoutStr + "s");
    ar.setTimeoutHandler(new AsyncTimeoutHandler("Timeouted after " + timeout + " seconds"));
    ar.setTimeout(timeout, TimeUnit.SECONDS);
    Response hello = Response.ok("Hello before the suspend timeout of 60 seconds has run").type(MediaType.TEXT_PLAIN).build();
    ar.resume(hello);
  } catch (Exception e) {
    System.out.println(e.getMessage());
  }
}

For a new suspend timeout to be applied, the request must be resumed in the timeout handler. If a new suspended timeout and a timeout handler are set and the suspended timeout handler does not take any action, the default resolution is for the request processing to be resumed with a ServiceUnAvailableException exception. To resume the request to send a response, the request has to be resumed explicitly using the resume(Object) method.

Cancelling a request

The AsyncResponse instance can cancel the response in the suspended timeout handler or the resource method using the overloaded cancel() method:

boolean cancel = true;
int retryPeriod = 10;
if (cancel) {
  System.out.println("Cancel the suspeneded request processing");
  ar.cancel(retryPeriod);
}

The client gets the following exception when a response is cancelled, which is shown as follows:

Cancelling a request
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset