【问题标题】:Handle asynchronous http request using JAX-RS?使用 JAX-RS 处理异步 http 请求?
【发布时间】:2016-07-20 16:24:13
【问题描述】:

我正在使用 JAX-RS 为资源实现 CRUD 操作。在我的第一个用例中,它是一个 阻塞(同步) 请求,即在单线程执行中,客户端会返回响应。

用例 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();

    }

}

用例 2:

现在我必须实现相同的请求,但它是一个 非阻塞(异步) 请求,因此一旦请求到达服务器,我必须向服务器返回一个响应ACKNOWLEDGEMENT 即带有ACCEPTED (202) 状态码的Response 对象,然后恢复初始请求处理(在这种情况下通过id 获取资源)。

通过发送一个新的POST 请求将这个请求的响应(检索到的资源)发送给客户端,该请求的内容是检索到的资源,然后客户端将返回这个POST 请求的响应以及成功状态代码。 (注意:这里的客户端不是 bean 浏览器,客户端和服务器都是机器,就像在被调用的 REST Api 中一样)。

@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); 


    }

}

我读到了 Asynchronous JAX-RSjava.util.concurrent 包。

如果我像启动一个新线程来执行请求处理一样异步执行此操作,如何发送第一次ACK 响应。例如:

@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();
   }
}

如何使用 JAX-RS 实现此调用流程?

编辑: 我按照您的回答中所述进行了尝试,但是出现了异常:

服务器日志:

2016 - 07 - 25 12: 22: 01, 521 ERROR[io.undertow.request](
 default task - 1) UT005023: Exception handling request to / resourceService: org.jboss.resteasy.spi.UnhandledException: java.lang.NoSuchMethodError: my.ow.dummy.package.CRUDService$1. < init > (Lmy / own / dummy / package / CRUDService;) 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: my.ow.dummy.package.CRUDService$1. < init > (Lmy / own / dummy / package / CRUDService;) V at my.ow.dummy.package.CRUDService.handlePutRequest(CRUDService.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

这个异常没有提前出现,它是在我添加新的 Thread() 部分之后开始的。有什么想法吗?

【问题讨论】:

  • 好吧,首先,return语句后面不能有任何代码,所以第二块代码无效
  • 您不能将 POST 请求从服务器发送到客户端,除非客户端和服务器实际上都是服务器。或者您可以使用 websocket,两个端点都可以发送多条消息而无需请求/响应
  • @jan.supol:是的,两者都是服务器。
  • @cricket_007:我知道。但这就是我写的解释我想要异步实现的调用流程的虚拟代码。可以使用后台线程来完成。

标签: java multithreading http asynchronous jax-rs


【解决方案1】:

在客户端和服务器实际上都是服务器的情况下,您可以创建一个带有一些参数的资源方法,如果初始请求需要@ 987654322@:

@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();
}

编辑:您可以使用 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();
}

RunnableThreadrun 方法中的任何内容都在单独的线程中执行。当然,在这个线程中,您可以处理来自 POST 请求的Response,如果需要,您可以根据返回的状态码做出某种反应。


您有两个服务器相互通信的情况是可能的,尽管不是很常见。如果客户端只是客户端,可以做异步客户端请求,如:

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

对于服务器端,你可以使用你的异步方法

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

如你所愿。

【讨论】:

  • 那么发送的POST请求的响应也可以在同一个线程中处理?新的POST 请求将启动一个新线程?我不确定这里将如何管理线程。
  • 另外,我们应该使用ExecutorService 线程而不是直接启动线程吗?
  • 添加到答案中
  • 我的 server1 将收到ACK 形式的响应。现在,POST 请求必须登陆它,其中包含初始请求的响应。如何在 server1 上实现该呼叫流程? POST 请求应该落在单独的 Servlet 上还是在发送请求的同一类上
  • 我不确定我是否完全理解。发布请求需要以 server1 上某些资源的 uri 为目标,在我们的示例中由 @HeaderParam 给出。发布请求需要同时包含数据(实体)和 id,以路径参数(再次)或其他参数的形式,以便 server1 知道发布的实体指的是什么。
【解决方案2】:

如果你想向客户端返回 ACK,然后想进一步处理你的请求,下面的代码 sn-p 应该可以工作。

public static void main(String args[]) {
    Runnable thread = () -> {
        // you code goes here
        // which you want to execute in a new thread
    };
    // return response code / ACK
}

在这里,新创建的线程将开始执行,主函数将继续执行,无需等待线程完成执行。

您可以在 JAX-RS 实现中使用类似的实现。

【讨论】:

  • 您能否详细说明为什么我们不能通过另一个请求返回响应?类似@jan.supol 的回答发送POST 请求?
  • 实际上,如您的方案所述,当另一端有服务器机器时,您可以借助新请求。因此,按照我的回答,您可以使用 Runnable 启动一个新线程,并在您返回 ACK 时让该线程处理其工作,并且该线程稍后可以在准备好后发布数据。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-08-10
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多