2.3. JAX-RS 请求处理
2.3.1. 异步 HTTP 请求处理
异步请求处理允许您使用非阻塞输入和输出来处理单个 HTTP 请求,如果需要,也可以在单独的线程中处理。
考虑 AJAX 聊天客户端,您要在其中从客户端和服务器推送和拉取。此场景使客户端在服务器的套接字上长时间阻止,等待新的消息。如果同步 HTTP 处理(其中服务器在传入和传出输入和输出上阻塞),则每个客户端连接使用一个单独的线程。这种请求处理模式消耗了大量内存和宝贵的线程资源。
异步处理分隔连接接受和请求处理操作。它分配两个不同的线程:一个用于接受客户端连接;另一个用于处理大量耗时的操作。在这个模型中,容器的工作方式如下:
- 它分配线程接受客户端连接,这是接收器。
- 然后,它将请求交给处理线程,即工作程序。
- 最后,它会释放接收器线程。
结果由 worker 线程发送回客户端。因此,客户端的连接保持开放,从而提高服务器的吞吐量和可扩展性。
2.3.1.1. 异步 NIO 请求处理
RESTEasy 的默认异步引擎实施类是 ApacheHttpAsyncClient4Engine
。它在 Apache Http
上构建,后者使用非阻塞 IO 模型内部分配请求。
Components 的 Http
AsyncClient
您可以通过调用 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)); }