Introduction
Here I am going to give an example on how Asynchronous REST webservice works. This is a simple asynchronous REST service using Jersey API.
The most important concept in REST is resources, which are identified by global IDs — typically using URIs. Client applications use HTTP methods (GET/ POST/ PUT/ DELETE) to manipulate the resource or collection of resources. A RESTful Web service is implemented using HTTP and the principles of REST. Typically, a RESTful Web service should define the following aspects:
- The base/root URI for the Web service such as http://<host>/<appcontext/contextpath>/<url pattern>/<resources>.
- The MIME type of the response data supported, which are JSON/XML/TEXT/HTML etc.
- The set of operations supported by the service. (for example, POST, GET, PUT or DELETE).
HTTP Methods
HTTP methods are mapped to CRUD (create, read, update and delete) actions for a resource. Although you can make slight modifications such as making the PUT method to be create or update, the basic patterns are listed as follows.
- HTTP GET: Get/List/Retrieve an individual resource or a collection of resources.
- HTTP POST: Create a new resource or resources.
- HTTP PUT: Update an existing resource or collection of resources.
- HTTP DELETE: Delete a resource or collection of resources.
Asynchronous Server API
By default client connection of a request is processed in a synchronous mode in server in a single I/O container thread. After processing the client request, the thread returns to the I/O container and I/O container safely assumes that the request process is finished. Thus all resources associated with the client connection are released. This kind of synchronous mechanism is sufficient for requests which take relatively short execution time. But for requests which take relatively longer execution time, the association between a request processing thread and client connection is broken. Thus server-side asynchronous processing model should be used to facilitate explicitly suspend, resume and close the client connections.
I will use grizlly web server in Junit class for running the service.
Prerequisites
Java 1.8+, Jersey 2.6/3.x, Maven 3.6.0/3.8.5
Project Setup
Create maven based webapp in your favorite IDE or tool with the below information.
Modify the pom.xml file to add required dependencies. I am using Jersey version 2.6/3.1.0. I am going to use Grizlly server to test our Asynchronous REST service. So, I have added the required dependencies for this server.
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.roytuts</groupId>
<artifactId>asynchronous-rest</artifactId>
<packaging>war</packaging>
<version>0.0.1-SNAPSHOT</version>
<url>http://maven.apache.org</url>
<properties>
<jersey.version>2.6</jersey.version>
</properties>
<dependencies>
<dependency>
<groupId>javax.ws.rs</groupId>
<artifactId>javax.ws.rs-api</artifactId>
<version>2.0</version>
</dependency>
<dependency>
<groupId>org.glassfish.grizzly</groupId>
<artifactId>grizzly-http-server</artifactId>
<version>2.3.11</version>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.containers</groupId>
<artifactId>jersey-container-servlet</artifactId>
<version>${jersey.version}</version>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.bundles.repackaged</groupId>
<artifactId>jersey-guava</artifactId>
<version>${jersey.version}</version>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.containers</groupId>
<artifactId>jersey-container-grizzly2-http</artifactId>
<version>${jersey.version}</version>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.test-framework.providers</groupId>
<artifactId>jersey-test-framework-provider-inmemory</artifactId>
<version>${jersey.version}</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.1.1</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<finalName>asynchronous-rest</finalName>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.6.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
</plugins>
</build>
</project>
For Jersey version 3.x, use the following pom.xml file:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.roytuts</groupId>
<artifactId>asynchronous-rest</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>war</packaging>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>4.0.1</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>jakarta.ws.rs</groupId>
<artifactId>jakarta.ws.rs-api</artifactId>
<version>3.1.0</version>
</dependency>
<dependency>
<groupId>org.glassfish.grizzly</groupId>
<artifactId>grizzly-http-server</artifactId>
<version>3.0.1</version>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.containers</groupId>
<artifactId>jersey-container-servlet</artifactId>
<version>3.1.0-M2</version>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.bundles.repackaged</groupId>
<artifactId>jersey-guava</artifactId>
<version>2.26-b03</version>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.containers</groupId>
<artifactId>jersey-container-grizzly2-http</artifactId>
<version>3.1.0-M2</version>
</dependency>
<!-- jersey-hk2 is required for jersey 3.1.0-M2 -->
<dependency>
<groupId>org.glassfish.jersey.inject</groupId>
<artifactId>jersey-hk2</artifactId>
<version>3.1.0-M2</version>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.test-framework.providers</groupId>
<artifactId>jersey-test-framework-provider-inmemory</artifactId>
<version>3.1.0-M2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.2</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<finalName>rest-service-authentication</finalName>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
</plugin>
<plugin>
<artifactId>maven-war-plugin</artifactId>
<version>3.2.2</version>
</plugin>
</plugins>
</build>
</project>
Deployment Descriptor – web.xml
Modify web.xml file to use the jersey servlet. I also specify the REST service package location to let the server know about the REST services.
<?xml version="1.0" encoding="UTF-8"?>
<!-- <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://java.sun.com/xml/ns/javaee"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
id="WebApp_ID" version="3.0"> -->
<web-app version="4.0"
xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd">
<display-name>Asynchronous REST using Jersey</display-name>
<servlet>
<servlet-name>REST</servlet-name>
<servlet-class>org.glassfish.jersey.servlet.ServletContainer</servlet-class>
<init-param>
<param-name>jersey.config.server.provider.packages</param-name>
<param-value>com.roytuts.rest.resources</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<!-- Map /rest/* to Jersey -->
<servlet-mapping>
<servlet-name>REST</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>
</web-app>
REST Service
Create a REST resource class as shown below using Jersey API.
package com.roytuts.rest.resources;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.container.AsyncResponse;
import javax.ws.rs.container.Suspended;
@Path("/resource")
public class AsyncResource {
@GET
@Path("/simpleAsync")
public void asyncGet(@Suspended final AsyncResponse asyncResponse) {
new Thread(new Runnable() {
@Override
public void run() {
String result = veryExpensiveOperation();
asyncResponse.resume(result);
}
private String veryExpensiveOperation() {
return "Very Expensive Operation";
}
}).start();
}
}
For Jersey version 3.x, use the following Java code:
package com.roytuts.rest.resources;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.container.AsyncResponse;
import jakarta.ws.rs.container.Suspended;
@Path("/resource")
public class AsyncResource {
@GET
@Path("/simpleAsync")
public void asyncGet(@Suspended final AsyncResponse asyncResponse) {
new Thread(new Runnable() {
@Override
public void run() {
String result = veryExpensiveOperation();
asyncResponse.resume(result);
}
private String veryExpensiveOperation() {
return "Very Expensive Operation";
}
}).start();
}
}
Junit Class
Create JUnit test class for testing the service.
package com.roytuts.rest.resources;
import java.net.URI;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import javax.ws.rs.client.AsyncInvoker;
import javax.ws.rs.client.Client;
import javax.ws.rs.client.ClientBuilder;
import javax.ws.rs.client.WebTarget;
import javax.ws.rs.core.Response;
import org.glassfish.grizzly.http.server.HttpServer;
import org.glassfish.jersey.grizzly2.httpserver.GrizzlyHttpServerFactory;
import org.glassfish.jersey.server.ResourceConfig;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import com.roytuts.rest.reources.AsyncResource;
public class AsyncResourceTest {
private HttpServer httpServer;
private WebTarget webTarget;
private static final URI baseUri = URI.create("http://localhost:9090/rest/");
@Before
public void setup() throws Exception {
// create ResourceConfig from Resource class
ResourceConfig rc = new ResourceConfig(AsyncResource.class);
// create the Grizzly server instance
httpServer = GrizzlyHttpServerFactory.createHttpServer(baseUri, rc);
// start the server
httpServer.start();
// configure client with the base URI path
Client client = ClientBuilder.newClient();
webTarget = client.target(baseUri);
}
@After
public void tearDown() throws Exception {
// if you want to stop the server from the input through keyboard then uncomment
// below two lines
// System.out.println(String.format("Application started.%nHit enter to stop it..."));
// System.in.read();
// stop the server
httpServer.shutdown();
}
@Test
public void testAsyncGet() throws InterruptedException, ExecutionException {
final AsyncInvoker asyncInvoker = webTarget.path("resource/simpleAsync").request().async();
final Future<Response> responseFuture = asyncInvoker.get();
System.out.println("Request is being processed asynchronously.");
final Response response = responseFuture.get();
// get() waits for the response to be ready
System.out.println("Response received : " + response);
System.out.println("Response from GET method : " + response.readEntity(String.class));
}
}
For jersey version 3.x, use the following Junit class:
package com.roytuts.rest.resources;
import java.net.URI;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import org.glassfish.grizzly.http.server.HttpServer;
import org.glassfish.jersey.grizzly2.httpserver.GrizzlyHttpServerFactory;
import org.glassfish.jersey.server.ResourceConfig;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import jakarta.ws.rs.client.AsyncInvoker;
import jakarta.ws.rs.client.Client;
import jakarta.ws.rs.client.ClientBuilder;
import jakarta.ws.rs.client.WebTarget;
import jakarta.ws.rs.core.Response;
public class AsyncResourceTest {
private HttpServer httpServer;
private WebTarget webTarget;
private static final URI baseUri = URI.create("http://localhost:9090/rest/");
@Before
public void setup() throws Exception {
// create ResourceConfig from Resource class
ResourceConfig rc = new ResourceConfig(AsyncResource.class);
// create the Grizzly server instance
httpServer = GrizzlyHttpServerFactory.createHttpServer(baseUri, rc);
// start the server
httpServer.start();
// configure client with the base URI path
Client client = ClientBuilder.newClient();
webTarget = client.target(baseUri);
}
@After
public void tearDown() throws Exception {
// if you want to stop the server from the input through keyboard then uncomment
// below two lines
// System.out.println(String.format("Application started.%nHit enter to stop
// it..."));
// System.in.read();
// stop the server
httpServer.shutdown();
}
@Test
public void testAsyncGet() throws InterruptedException, ExecutionException {
final AsyncInvoker asyncInvoker = webTarget.path("resource/simpleAsync").request().async();
final Future<Response> responseFuture = asyncInvoker.get();
System.out.println("Request is being processed asynchronously.");
final Response response = responseFuture.get();
// get() waits for the response to be ready
System.out.println("Response received : " + response);
System.out.println("Response from GET method : " + response.readEntity(String.class));
}
}
Testing Asynchronous REST Service
Running the above Junit class will produce the following output:
Request is being processed asynchronously.
Response received : InboundJaxrsResponse{context=ClientResponse{method=GET, uri=http://localhost:9090/rest/resource/simpleAsync, status=200, reason=OK}}
Response from GET method : Very Expensive Operation