Siddharth Trikha Siddharth Trikha - 4 months ago 44
Java Question

Handle asynchronous http request using JAX-RS?

I am implementing CRUD operations for a resource using JAX-RS. During my first use case it a blocking (synchronous) request i.e in a single threaded execution the client get the response back.

Use case 1:

@Path("/resourceService")
public class CRUDService {

@GET
@Path("/{param}")
public Response getResource(@PathParam("param") String id) {

Resource res = SomeBean().getResource(id);
return Response.status(200).entity(res).build();

}

}


Use case 2:

Now I have to implement the same request but it is a non-blocking (asynchronous) request such that as soon as the request lands on the server I have to return a Response to the server as an ACKNOWLEDGEMENT i.e a
Response
object with a
ACCEPTED (202)
status code and after that resume the initial request processing (get the resource by id in this case).

The response of this request (resource retrieved) is sent to the client by sending a new
POST
request whose content is the resource retrieved and then the client will return the response of this
POST
request with success status code.
(NOTE: Here client does not bean browser, both client and server are machines as in a REST Api being called).

@Path("/resourceService")
public class CRUDService {

@GET
@Path("/{param}")
public Response getResource(@PathParam("param") String id) {

// ACKNOWLEDGEMENT response built and sent back to clinet
return Response.status(202).build();

// Do the request handling i.e resource retrieval
Resource res = SomeBean().getResource(id);
Resonse response = Response.status(200).entity(res).build();

// Create a HTTP POST Request and set the body as response object

HttpClient client = HttpClientBuilder.create().build();
HttpPost post = new HttpPost(url);
post.setEntity(response);
HttpResponse response = client.execute(post);


}

}


I read about
Asynchronous JAX-RS
and
java.util.concurrent
package.

If I do it asynchronously like launch a new thread to do the request handling, how to send a first time response of ACK back. Ex:

@POST
@Consumes("application/json")
@Produces("application/json")
public void getResource(@PathParam("param") String id,
final @Suspended AsyncResponse response) {
new Thread() {
public void run() {
Resource res = SomeBean().getResource(id);
response.resume(resource);
}
}.start();
}
}


How can this call flow be achieved using JAX-RS ??

EDIT:

Server log:

2016-07-25 12:22:01,521 ERROR [io.undertow.request] (default task-1) UT005023: Exception handling request to /R82: org.jboss.resteasy.spi.UnhandledException: java.lang.NoSuchMethodError: cdot.onem2m.comm.http.RestHttpServlet$1.<init>(Lcdot/onem2m/comm/http/RestHttpServlet;)V
at org.jboss.resteasy.core.ExceptionHandler.handleApplicationException(ExceptionHandler.java:76) [resteasy-jaxrs-3.0.6.Final.jar:]
at org.jboss.resteasy.core.ExceptionHandler.handleException(ExceptionHandler.java:212) [resteasy-jaxrs-3.0.6.Final.jar:]
at org.jboss.resteasy.core.SynchronousDispatcher.writeException(SynchronousDispatcher.java:149) [resteasy-jaxrs-3.0.6.Final.jar:]
at org.jboss.resteasy.core.SynchronousDispatcher.invoke(SynchronousDispatcher.java:372) [resteasy-jaxrs-3.0.6.Final.jar:]
at org.jboss.resteasy.core.SynchronousDispatcher.invoke(SynchronousDispatcher.java:179) [resteasy-jaxrs-3.0.6.Final.jar:]
at org.jboss.resteasy.plugins.server.servlet.ServletContainerDispatcher.service(ServletContainerDispatcher.java:220) [resteasy-jaxrs-3.0.6.Final.jar:]
at org.jboss.resteasy.plugins.server.servlet.HttpServletDispatcher.service(HttpServletDispatcher.java:56) [resteasy-jaxrs-3.0.6.Final.jar:]
at org.jboss.resteasy.plugins.server.servlet.HttpServletDispatcher.service(HttpServletDispatcher.java:51) [resteasy-jaxrs-3.0.6.Final.jar:]
at javax.servlet.http.HttpServlet.service(HttpServlet.java:790) [jboss-servlet-api_3.1_spec-1.0.0.Final.jar:1.0.0.Final]
at io.undertow.servlet.handlers.ServletHandler.handleRequest(ServletHandler.java:85) [undertow-servlet-1.0.0.Final.jar:1.0.0.Final]
at io.undertow.servlet.handlers.security.ServletSecurityRoleHandler.handleRequest(ServletSecurityRoleHandler.java:61) [undertow-servlet-1.0.0.Final.jar:1.0.0.Final]
at io.undertow.servlet.handlers.ServletDispatchingHandler.handleRequest(ServletDispatchingHandler.java:36) [undertow-servlet-1.0.0.Final.jar:1.0.0.Final]
at org.wildfly.extension.undertow.security.SecurityContextAssociationHandler.handleRequest(SecurityContextAssociationHandler.java:78)
at io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:25) [undertow-core-1.0.0.Final.jar:1.0.0.Final]
at io.undertow.servlet.handlers.security.SSLInformationAssociationHandler.handleRequest(SSLInformationAssociationHandler.java:113) [undertow-servlet-1.0.0.Final.jar:1.0.0.Final]
at io.undertow.security.handlers.AuthenticationCallHandler.handleRequest(AuthenticationCallHandler.java:52) [undertow-core-1.0.0.Final.jar:1.0.0.Final]
at io.undertow.security.handlers.AbstractConfidentialityHandler.handleRequest(AbstractConfidentialityHandler.java:45) [undertow-core-1.0.0.Final.jar:1.0.0.Final]
at io.undertow.servlet.handlers.security.ServletConfidentialityConstraintHandler.handleRequest(ServletConfidentialityConstraintHandler.java:61) [undertow-servlet-1.0.0.Final.jar:1.0.0.Final]
at io.undertow.servlet.handlers.security.CachedAuthenticatedSessionHandler.handleRequest(CachedAuthenticatedSessionHandler.java:70) [undertow-servlet-1.0.0.Final.jar:1.0.0.Final]
at io.undertow.security.handlers.SecurityInitialHandler.handleRequest(SecurityInitialHandler.java:76) [undertow-core-1.0.0.Final.jar:1.0.0.Final]
at io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:25) [undertow-core-1.0.0.Final.jar:1.0.0.Final]
at org.wildfly.extension.undertow.security.jacc.JACCContextIdHandler.handleRequest(JACCContextIdHandler.java:61)
at io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:25) [undertow-core-1.0.0.Final.jar:1.0.0.Final]
at io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:25) [undertow-core-1.0.0.Final.jar:1.0.0.Final]
at io.undertow.servlet.handlers.ServletInitialHandler.handleFirstRequest(ServletInitialHandler.java:240) [undertow-servlet-1.0.0.Final.jar:1.0.0.Final]
at io.undertow.servlet.handlers.ServletInitialHandler.dispatchRequest(ServletInitialHandler.java:227) [undertow-servlet-1.0.0.Final.jar:1.0.0.Final]
at io.undertow.servlet.handlers.ServletInitialHandler.access$000(ServletInitialHandler.java:73) [undertow-servlet-1.0.0.Final.jar:1.0.0.Final]
at io.undertow.servlet.handlers.ServletInitialHandler$1.handleRequest(ServletInitialHandler.java:146) [undertow-servlet-1.0.0.Final.jar:1.0.0.Final]
at io.undertow.server.Connectors.executeRootHandler(Connectors.java:168) [undertow-core-1.0.0.Final.jar:1.0.0.Final]
at io.undertow.server.HttpServerExchange$1.run(HttpServerExchange.java:687) [undertow-core-1.0.0.Final.jar:1.0.0.Final]
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145) [rt.jar:1.7.0_40]
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615) [rt.jar:1.7.0_40]
at java.lang.Thread.run(Thread.java:724) [rt.jar:1.7.0_40]
Caused by: java.lang.NoSuchMethodError: cdot.onem2m.comm.http.RestHttpServlet$1.<init>(Lcdot/onem2m/comm/http/RestHttpServlet;)V
at cdot.onem2m.comm.http.RestHttpServlet.handlePutRequest(RestHttpServlet.java:78) [classes:]
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) [rt.jar:1.7.0_40]
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57) [rt.jar:1.7.0_40]
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) [rt.jar:1.7.0_40]
at java.lang.reflect.Method.invoke(Method.java:606) [rt.jar:1.7.0_40]
at org.jboss.resteasy.core.MethodInjectorImpl.invoke(MethodInjectorImpl.java:137) [resteasy-jaxrs-3.0.6.Final.jar:]
at org.jboss.resteasy.core.ResourceMethodInvoker.invokeOnTarget(ResourceMethodInvoker.java:280) [resteasy-jaxrs-3.0.6.Final.jar:]
at org.jboss.resteasy.core.ResourceMethodInvoker.invoke(ResourceMethodInvoker.java:234) [resteasy-jaxrs-3.0.6.Final.jar:]
at org.jboss.resteasy.core.ResourceMethodInvoker.invoke(ResourceMethodInvoker.java:221) [resteasy-jaxrs-3.0.6.Final.jar:]
at org.jboss.resteasy.core.SynchronousDispatcher.invoke(SynchronousDispatcher.java:356) [resteasy-jaxrs-3.0.6.Final.jar:]
... 29 more

Answer

In the case that both client and server are actually servers, you can create a resource method with some argument that would get the uri to send the Resource to, for instance by @HeaderParam, if initial request needs to be GET :

@GET
@Path("/{param}")
public Response getResource(@PathParam("param") String id, @HeaderParam(
"SendBackUri") String uri) {
    new Thread() {
       public void run() {
          Resource res = SomeBean().getResource(id);
          ClientBuilder.newClient().target(uri).request().buildPost(Entity.entity(res, MediaType.WILDCARD_TYPE)).invoke();
       }
    }.start();
    return Response.status(202).build();
}

EDIT: You can use ExecutorService:

@GET
@Path("/{param}")
public Response getResource(@PathParam("param") String id, @HeaderParam(
"SendBackUri") String uri) {
    executorService.execute(new Runnable() {
       public void run() {
          Resource res = SomeBean().getResource(id);
          Response response = ClientBuilder.newClient().target(uri).request().buildPost(Entity.entity(res, MediaType.WILDCARD_TYPE)).invoke();
          //deal with response
       }
    });
    return Response.status(202).build();
}

Whatever is in the runmethod of either Runnable or Thread is executed in a separate thread. Of course, in this thread, you can deal with Response from the POST request and you can react somehow based on returned status code if needed.


Your case of having two servers communicating with each other is possible, though not very common. If the client side is just a client, you can do asynchronous client request, such as:

Future<Response> f = ClientBuilder.newClient().target(uri).request().async().get();
//do some client specific stuff which you probably do when receive 202;
Response r = f.get(); //wait for response
//do whatever you would do with the resource

For server side, you can use your asynchronous method

public void getResource(@PathParam("param") String id,
                  final @Suspended AsyncResponse response)

as you have it.

Comments