当CompleteableFuture引发异常时返回500内部错误

时间:2018-07-18 10:32:31

标签: java dropwizard

我有一个具有端点的rest控制器:

@GET
@Path("/reindex-record")
public String reindexRecord(@QueryParam("id") String id) {
    if (StringUtils.isEmpty(id)) {
        CompletableFuture.runAsync(
            () -> runWithException(Reindexer::reindexAll));
    } else {
        CompletableFuture.runAsync(() -> runWithException(
            () -> Reindexer.reindexOne(id)));
    }

    // return "ok" or throw WebApplciationException from runWithException method below
}

这是我的包装器方法-两种方法-reindexAll和reindexOne都抛出检查异常,因此决定使用包装器方法和接口:

public interface RunnableWithException {
    void run() throws Exception; 
}

private void runWithException(RunnableWithException task) {
    try {
        task.run();
    } catch (Exception e) {
        log.error("Error occured during async task execution", e);
        throw new WebApplicationException(
            Response.status(Response.Status.INTERNAL_SERVER_ERROR)
                .entity("Internal error occurred").build());
    }
}

问题是我想使用CompleteableFuture不同步地运行此任务,并且仅在完成给定任务后或在发生错误时抛出WebApplicationException且状态为INTERNAL_SERVER_ERROR的情况下给出响应。

您如何在if / else的用例中实现它?

编辑: 到目前为止,我有这种方法:

@GET
@Path("/reindex-record")
public String reindexRecord(@QueryParam("id") String id) throws ExecutionException,
    InterruptedException {
    CompletableFuture<Void> task;
    if (StringUtils.isEmpty(id)) {
        task = CompletableFuture.runAsync(
            () -> runWithException(Reindexer::reindexAll));
    } else {
        task = CompletableFuture.runAsync(() -> runWithException(
            () -> Reindexer.reindexOne(id)));
    }
    return task.thenApply(x -> "ok")
        .exceptionally(throwable -> {
            log.error("Error occured during async task execution", throwable);
            throw new WebApplicationException(Response.status(Response.Status.SERVICE_UNAVAILABLE)
                                                  .entity("Internal error occurred. Try again later")
                                                  .build());

        }).get();

}

但是,如果任何Reindexer方法引发错误,我仍然会获得状态500的数据:

{
"code": 500,
"message": "There was an error processing your request. It has been logged (ID 03f09a62b62b1649)."

}

而不是在我的exceptionally块中定义的503。 如果重要的话,将dropwizard与JAX-RS一起使用。

2 个答案:

答案 0 :(得分:1)

您可以将方法的主体更改为此:

@GET
@Path("/reindex-record")
public String reindexRecord(@QueryParam("id") String id) {
    final CompletableFuture<Void> future;
    if (StringUtils.isEmpty(id)) {
        future = CompletableFuture.runAsync(
            () -> runWithException(Reindexer::reindexAll));
    } else {
        future = CompletableFuture.runAsync(
            () -> runWithException(() -> Reindexer.reindexOne(id)));
    }

    // this will block
    future.get();

    return "ok";
}

通过存储未来,您可以在其上调用get()方法,该方法将阻塞直到未来完成。

来自CompletableFuture.get()的javadoc:

  

等待将来完成,然后返回结果。

答案 1 :(得分:0)

问题是您正在使用exceptionally()进行某些非预期的操作。 CompletableFuture旨在用于CompletableFuture的链中,其中一个的输出馈入下一个。如果CompletableFuture之一抛出异常怎么办?您可以使用exceptionally来捕获该错误,并返回新的后备ComletableFuture供链的下一步使用。

您没有这样做,您只抛出了WebApplicationException。 CompleteableFuture将其视为链中的故障,并将您的WebApplicationException包装在ExecutionException中。 Dropwizard仅看到ExecutionException(它不检查任何包装的异常),并引发通用500响应。

答案只是执行@Lino答案中的future.get();,但包裹在try ... catch块中以获取ExecutionException,然后从捕获中抛出WebApplicationException。

try {
    // this will block
    future.get();
} catch (ExecutioException) {
    throw new WebApplicationException(
        Response.status(Response.Status.INTERNAL_SERVER_ERROR)
            .entity("Internal error occurred").build());
}

return "ok";

您也许可以将整个throw new WebApplicationException(...缩短为简单的throw new InternalServerErrorException()