Jersey:异步请求后立即响应

时间:2014-07-31 15:16:54

标签: java multithreading rest asynchronous jersey

我尝试了解异步响应与Jersey一起工作的方式。我阅读了Jersey文档的第10章(https://jersey.java.net/documentation/latest/async.html),但它对我的问题没有帮助。此处关于stackoverflow的研究也没有得到令人满意的答案(我能理解)。

我要做的是类似于这篇文章中的一个问题(Use http status 202 for asynchronous operations)。我想使用HTML表单文档将大文件上传到服务器。请求发送到服务器后,Web服务应立即响应状态202和URI,在请求完成后可以找到该文件。

阅读帖子后,似乎有可能但遗憾的是没有提示如何在给定的情况下实现这样的行为。

我写了一个小型Web服务来测试功能:

    @Path("/test/async/")
    public class TestAsyncResponse {

        @GET
        @Path("get")
        public Response asyncGet(@Suspended final AsyncResponse response) {

            new Thread(new Runnable() {

                @Override
                public void run() {

                    DateFormat df = new SimpleDateFormat("dd/MM/yy HH:mm:ss");

                    System.out.println("#### thread started: " 
                                       + df.format(new Date()) + " ####");
                    String result = veryExpensiveOperation();
                    System.out.println("#### thread finished: " 
                                        + df.format(new Date()) + " ####");

                    response.resume(result);
                }

                private String veryExpensiveOperation() {

                    try {

                        Thread.sleep(10000);
                    } 
                    catch (InterruptedException e) {

                        e.printStackTrace();
                    }

                    return "Woke up!";
                }
            }).start();

            return Response.status(202).entity("Request accepted. " + 
                                        "Long running operation started")
                                        .build();
        }
    }

该服务有效但作为回应我得到了“醒来!” 10秒等待后的消息而不是202响应似乎是合乎逻辑的,因为AsyncResponse是处理响应的响应(据我所知)。

在阅读文档后,我得到的印象是,假设这是因为所有Jersey都使用异步服务器响应,将线程从响应线程池外包给另一个,以便释放处理时间以获得对服务的更多响应。

所以我的两个问题是:我的理解是否正确,我可以使用异步服务器响应来获得所需的行为吗?

我试图在没有AsyncResponse的情况下启动一个新线程,我得到一个NullPointerException因为Jersey已经关闭了响应,因此关闭了包含文件数据的InputStream。这是预期的行为吗?这篇文章(https://stackoverflow.com/a/17559684/1416602)似乎表明它可能会奏效。

非常感谢任何回应。

问候

2 个答案:

答案 0 :(得分:4)

你的问题是混合两个主题。

从HTTP的角度来看,202在技术上是一个已完成的请求。并且请求的结果是202,服务器告诉你它将在侧面执行。您必须发出另一个HTTP请求才能获得更新状态。

从应用程序的角度来看,异步意味着您将以单独的线程(或其他异步方式)执行请求。但是,这意味着你不会返回结果,甚至不会返回202,直到另一个结果非常昂贵的操作"饰面。跳过这个环的重点是释放调用线程。您的网络服务器数量有限,例如20,如果你的每个请求花了很长时间,那么所有20个请求都会挂起。使用@Suspended,您可以将执行从Web服务器线程转移到其他方式(在您的情况下为另一个线程)。这真的只是第一步。异步服务器背后的想法是,即使是非常昂贵的操作也是以某种异步方式实现的,这样等待数据库或文件就不会占用整个线程。

答案 1 :(得分:0)

我最近经历过同样的痛苦。泽西岛一直声称它支持异步REST调用,但我认为它是不诚实的。 事实上,一旦我开始研究出正确的方法,泽西岛实际上就挡住了。

private static ExecutorService executorService = Executors.newFixedThreadPool( Integer.valueOf( numberOfThreads ) );

@POST
@Path("async")
@Consumes(MediaType.MULTIPART_FORM_DATA)
public Response async( @FormDataParam("file") InputStream inputStream,
        @FormDataParam("file") FormDataContentDisposition des ) throws Throwable {

    String uniqueID = UUID.randomUUID().toString();
    executorService.execute( new Runnable() {

        @Override
        public void run() {
            try {
               // do long performing action
            } catch (Exception ex) {
            }
        }            

   } );
   return Response.accepted().location( getResultsURI( uniqueID ) ).build();
}

@GET
@Path("results/{uniqueID}")
@Produces("application/zip")
public Response results( @PathParam(value = "uniqueID ") String uniqueID ) {

   // Check status of job
   // If not finished...
   if (notFinished) {
   return Response.status( 202 ).location( getResultsURI( uniqueID ) )
                .entity( status ).build();
   }

   return Response.ok( FileUtils.readFileToByteArray( zip.toFile() ) ).type( "application/zip" )
            .header( "Content-Disposition", "attachment; filename=\"filename.zip\"" ).build();
  }

protected URI getResultsURI( String uniqueID ) throws URISyntaxException {
    return new URI( Constants.WS_VERSION + "/results/" + uniqueID );
}

最大的痛苦是当您设置Response.location()时,即使您将其设置为“./results”或“/ results”,Jersey也会将其扩展为完整的URL。哪个会好的,除了它忽略任何类级别的@Path:

@Path(Constants.WS_VERSION)
public class MyEndpoint {

因此,我使用上面的代码来代替对抗它,至少使其正确。理想情况下,我希望泽西岛单独留下“位置”标题。

无论如何 - 上面的代码是我使用的(不包括业务逻辑位;))