2.3. JAX-RS 请求处理

2.3.1. 异步 HTTP 请求处理

异步请求处理允许您使用非阻塞输入和输出来处理单个 HTTP 请求,如果需要,也可以在单独的线程中处理。

考虑 AJAX 聊天客户端,您要在其中从客户端和服务器推送和拉取。此场景使客户端在服务器的套接字上长时间阻止,等待新的消息。如果同步 HTTP 处理(其中服务器在传入和传出输入和输出上阻塞),则每个客户端连接使用一个单独的线程。这种请求处理模式消耗了大量内存和宝贵的线程资源。

异步处理分隔连接接受和请求处理操作。它分配两个不同的线程:一个用于接受客户端连接;另一个用于处理大量耗时的操作。在这个模型中,容器的工作方式如下:

  1. 它分配线程接受客户端连接,这是接收器。
  2. 然后,它将请求交给处理线程,即工作程序。
  3. 最后,它会释放接收器线程。

结果由 worker 线程发送回客户端。因此,客户端的连接保持开放,从而提高服务器的吞吐量和可扩展性。

2.3.1.1. 异步 NIO 请求处理

RESTEasy 的默认异步引擎实施类是 ApacheHttpAsyncClient4Engine。它在 Apache Http Components 的 Http AsyncClient 上构建,后者使用非阻塞 IO 模型内部分配请求。

您可以通过调用 ResteasyClientBuilder 类中的 useAsyncHttpEngine 方法将异步引擎设置为活跃引擎:

Client asyncClient = new ResteasyClientBuilder().useAsyncHttpEngine()
                     .build();
Future<Response> future = asyncClient
                          .target("http://locahost:8080/test").request()
                          .async().get();
Response res = future.get();
Assert.assertEquals(HttpResponseCodes.SC_OK, res.getStatus());
String entity = res.readEntity(String.class);

2.3.1.2. 服务器异步响应处理

在服务器端,异步处理涉及暂停原始请求线程,并在不同的线程中启动请求处理,后者释放原始服务器端线程,以接受其他传入的请求。

2.3.1.2.1. AsyncResponse API

JAX-RS 2.0 规范通过两个类添加了异步 HTTP 支持: @Suspended 注释和 AsyncResponse 接口。

AsyncResponse 作为参数注入到您的 JAX-RS 方法,可提示 RESTEasy 分离当前正在执行的线程的 HTTP 请求和响应。这样可确保当前线程不会尝试自动处理响应。

AsyncResponse 是回调对象。调用其中一个 restore ()方法 的操作会导致响应发回到客户端,并且终止 HTTP 请求。以下是异步处理的示例:

import javax.ws.rs.container.Suspended;
import javax.ws.rs.container.AsyncResponse;

@Path("/")
public class SimpleResource {
   @GET
   @Path("basic")
   @Produces("text/plain")
   public void getBasic(@Suspended final AsyncResponse response) throws Exception {
      Thread t = new Thread() {
         @Override
         public void run() {
            try {
               Response jaxrs = Response.ok("basic").type(MediaType.TEXT_PLAIN).build();
               response.resume(jaxrs);
            }
            catch (Exception e) {
               e.printStackTrace();
            }
         }
      };
      t.start();
   }
}

2.3.1.3. AsyncInvoker Client API

同样,在客户端上,异步处理可防止阻止请求线程,因为不需要花时间等待来自服务器的响应。例如,发出请求的线程也可以更新用户界面组件。如果线程被阻止等待响应,用户感知的应用性能将会受到影响。

2.3.1.3.1. 使用将来

在下面的代码片段中,get() 方法在 async() 方法上调用,而不是请求。这会将调用机制从同步更改为异步。async() 方法不同步响应,而是返回一个 将来 的对象。当您 调用 get() 方法时,调用会被阻止,直到响应就绪。当响应就绪时,会返回 if .get() 方法。

import java.util.concurrent.Future;
import javax.ws.rs.client.Client;
...

@Test
public void AsyncGetTest() throws Exception {
    Client client = ClientBuilder.newClient();
    Future<String> future = client.target(generateURL("/test")).request().async().get(String.class);
    String entity = future.get();
    Assert.assertEquals("get", entity);
}
2.3.1.3.2. 使用 InvocationCallback

通过 AsyncInvoker 接口,您可以在异步调用准备好处理时注册回调用的对象。InvocationCallback 接口提供了两种方法: completed()failed()。当处理成功完成并且收到响应时,会调用 completed() 方法。相反,每当请求处理不成功时,会调用 failed() 方法。

import javax.ws.rs.client.InvocationCallback;
...

@Test
public void AsyncCallbackGetTest() throws Exception {
    Client client = ClientBuilder.newClient();
	final CountDownLatch latch = new CountDownLatch(1);
        Future<Response> future = client.target(generateURL("/test")).request().async().get(new InvocationCallback<Response>() {
         	@Override
                public void completed(Response response) {
                    String entity = response.readEntity(String.class);
                    Assert.assertEquals("get", entity);
                    latch.countDown();
                }

                @Override
                public void failed(Throwable error) {
                }
            });
            Response res = future.get();
            Assert.assertEquals(HttpResponseCodes.SC_OK, res.getStatus());
            Assert.assertTrue("Asynchronous invocation didn't use custom implemented Invocation callback", latch.await(5, imeUnit.SECONDS));
}