Лучший способ ограничить время выполнения в @RestController - PullRequest
0 голосов
/ 30 августа 2018

Учитывая следующий код:

@RestController
@RequestMapping("/timeout")
public class TestController {

    @Autowired
    private TestService service;

    @GetMapping("/max10secs")
    public String max10secs() {
        //In some cases it can take more than 10 seconds
        return service.call();
    }
}

@Service
public class TestService {

    public String call() {
        //some business logic here
        return response;
    }
}

Что я хочу сделать, так это то, что если метод call из TestService занимает более 10 секунд, я хочу отменить его и сгенерировать ответ с кодом HttpStatus.REQUEST_TIMEOUT.

Ответы [ 2 ]

0 голосов
/ 30 августа 2018

Существует другое решение через DeferredResult .

TestController.java

@RestController
@RequestMapping("/timeout")
public class TestController
{

    @Autowired
    private TestService service;


    @GetMapping("/max10secs")
    public DeferredResult<String> max10secs()
    {
        //In some cases it can take more than 10 seconds
        return service.call();
    }
}

TestService.java

@Service
public class TestService
{

    public DeferredResult<String> call()
    {
        DeferredResult<String> result = new DeferredResult(10000L);
        //some business logic here
        result.onTimeout(()->{
           // do whatever you want there
        });
        result.setResult("test");
        return result;
    }
}

Таким образом, контроллер будет возвращать фактический результат только при вызове result.setResult ("test"); .

Как видите, в случае тайм-аута (значение для тайм-аута определяется в конструкторе объекта DeferredResult в миллисекундах) будет выполнен обратный вызов, в котором вы можете вызвать любое исключение или вернуть другой объект ( HttpStatus.REQUEST_TIMEOUT в вашем случае).

Вы можете прочитать о DeferredResult весной здесь .

0 голосов
/ 30 августа 2018

То, что мне удалось сделать, но я не знаю, есть ли какие-то концептуальные или практические недостатки, это то, что следует ...

Во-первых, конфигурация пружинно-асинхронная

@Configuration
@EnableAsync
public class AsyncConfig implements AsyncConfigurer {

    @Bean(name = "threadPoolTaskExecutor")
    public Executor threadPoolTaskExecutor() {

        ThreadPoolTaskExecutor pool = new ThreadPoolTaskExecutor();
        pool.setCorePoolSize(10);
        pool.setMaxPoolSize(10);
        pool.setWaitForTasksToCompleteOnShutdown(true);
        return pool;
    }

    @Override
    public Executor getAsyncExecutor() {
        return new SimpleAsyncTaskExecutor();
    }
}

И далее, модификации контроллера и сервиса:

@RestController
@RequestMapping("/timeout")
public class TestController {

    @Autowired
    private TestService service;

    @GetMapping("/max10secs")
    public String max10secs() throws InterruptedException, ExecutionException {
        Future<String> futureResponse = service.call();
        try {
            //gives 10 seconds to finish the methods execution
            return futureResponse.get(10, TimeUnit.SECONDS);
        } catch (TimeoutException te) {
            //in case it takes longer we cancel the request and check if the method is not done
            if (futureResponse.cancel(true) || !futureResponse.isDone())
                throw new TestTimeoutException();
            else {
                return futureResponse.get();
            }
        }
    }
}

@Service
public class TestService {

    @Async("threadPoolTaskExecutor")
    public Future<String> call() {
        try{
            //some business logic here
            return new AsyncResult<>(response);
        } catch (Exception e) {
            //some cancel/rollback logic when the request is cancelled
            return null;
        }
    }
}

И, наконец, сгенерировать исключение TestTimeoutException:

@ResponseStatus(value = HttpStatus.REQUEST_TIMEOUT, reason = "too much time")
public class TestTimeoutException extends RuntimeException{ }
...