Я использую Java 1.8, dropwizard 1.3.5 и swagger Inflection 1.0.13 для своего API.
У меня есть метод, который принимает HTTP-запрос, задерживает 20 секунд, а затем возвращает ответ с кодом состояния 200:
public ResponseContext delayBy20Seconds(RequestContext context) {
ResponseContext response = new ResponseContext().contentType(MediaType.APPLICATION_JSON_TYPE);
Thread.sleep(20000);
response.status(Response.Status.OK);
return response;
}
Скажем, я хочу вернуть код состояния 400, если операция (которая в данном случае занимает 20 секунд) занимает более 15 секунд. Как бы я этого добился?
Вы можете использовать что-то вроде TimeLimiter из библиотеки Google Guava. Это позволяет вам обернуть вызываемый объект в операцию, которую вы можете вызвать с помощью Timeout. Если вызываемый объект не завершит операцию вовремя, он выдаст TimeoutException
, который вы можете поймать и вернуть ответ 400.
Например:
TimeLimiter timeLimiter = new SimpleTimeLimiter();
try {
String result = timeLimiter.callWithTimeout(
() -> doSomeHeavyWeightOperation(), 15, TimeUnit.SECONDS);
} catch (TimeoutException e) {
// return 400
}
Один из способов сделать это без дополнительных библиотек — использовать пакет java.util.concurrent
. Самый надежный способ отменить такую длительную задачу — запустить ее в отдельном потоке.
import java.util.concurrent.*;
...
private ExecutorService exec = Executors.newSingleThreadExecutor();
public ResponseContext delayBy20Seconds(RequestContext context) {
Callable<ResponseContext> task = new Callable<ResponseContext>() {
@Override
public ResponseContext call() throws Exception {
Thread.sleep(20000);
return new ResponseContext().contentType(MediaType.APPLICATION_JSON_TYPE);
}
};
List<Callable<ResponseContext>> tasks = new ArrayList<>();
tasks.add(task);
List<Future<ResponseContext>> done = exec.invokeAll(tasks, 15, TimeUnit.SECONDS);
Future<ResponseContext> task1 = done.get(0);
if (task1.isCancelled()) {
return some Error Response;
}
return task1.get();
}
Ваш ExecutorService
не должен быть статичным, потому что вы не хотите делиться им между потоками для этого конкретного использования.
Реализация Callable<ResponseContext>
— это место, где выполняется работа над долговременной задачей. И, как должно быть очевидно, в вызове exec.invokeAll
мы сообщаем ему, сколько мы готовы ждать. Список возвращаемых фьючерсов всегда будет содержать столько же элементов, сколько и список задач, поэтому нет необходимости проверять его на пустоту. Нам просто нужно проверить, выполнена ли задача или нет.