HttpMediaTypeNotAcceptableException: не удалось найти приемлемое представление — MediaType дает разные результаты

Используя Spring MVC, у меня есть контроллер с конечной точкой, возвращающей SseEmitter.

@GetMapping(path = "/accept/{amount}", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
    @ApiResponses({
            @ApiResponse(responseCode = "200", description = "OK"),
            @ApiResponse(responseCode = "409", description = "BUSY", content = @Content(schema = @Schema(implementation = MyErrorClass.class))),
            @ApiResponse(responseCode = "500", description = "UNEXPECTED_ERROR", content = @Content(schema = @Schema(implementation = MyErrorClass.class)))
    })
    public SseEmitter accept(@Parameter(description = "Amount to accept") @PathVariable double amount) throws MyException {
        ExecutorService service = Executors.newCachedThreadPool();
        SseEmitter emitter = new SseEmitter();
        myService.accept(amount);
        service.execute(() -> {
            while(myService.inAcceptanceState()) {
                try {
                    emitter.send(myService.getCurrentAmount());
                    Thread.sleep(1000);
                } catch (InterruptedException | IOException ex) {
                    emitter.completeWithError(ex);
                }
            }
            try {
                emitter.send(myService.getCurrentAmount());
                emitter.complete();
            } catch (IOException e) {
                emitter.completeWithError(e);
            }
        });
        service.shutdown();
        return emitter;
    }

В случае приведенного выше кода ожидается, что метод accept() выдаст ошибку 400, когда он вызывается во второй раз до завершения первого вызова. На данный момент я получаю следующее:

2020-09-03 15:22:14.105  WARN 480 --- [nio-8080-exec-1] .m.m.a.ExceptionHandlerExceptionResolver : Failure in @ExceptionHandler com.mydomain.common.ExceptionController#handleMyException(Exception)

org.springframework.web.HttpMediaTypeNotAcceptableException: Could not find acceptable representation

Это не приводит к сбою моей службы Springboot, но вызов не завершается, поэтому вызывает очевидную проблему. Я заметил, что это прекрасно работает, если я перехожу на produces = MediaType.APPLICATION_JSON_STREAM_VALUE. Он возвращает исключение и сообщение об ошибке, связанное с этим исключением, как я и ожидал, но нам нужно использовать TEXT_STREAM для SseEmitter, и наши потребители ожидают возврата такого типа. По моему (ограниченному) пониманию, в Spring есть какая-то магия HttpConversion, происходящая с телами ответа, так что мне нужно сделать, чтобы вернуть исключение, используя produces = MediaType.APPLICATION_JSON_STREAM_VALUE? Нужно ли мне писать свой собственный преобразователь ответов, как бы это ни выглядело...?


person TheDubiousDubber    schedule 04.09.2020    source источник
comment
Кажется, эта проблема похожа на ту, с которой я столкнулся, но решения нет: github .com/spring-projects/spring-framework/issues/23821 Я думаю, что @ExceptionHandler можно использовать, но не уверен, что это так просто   -  person TheDubiousDubber    schedule 08.09.2020


Ответы (1)


Вы не завершаете своего исполнителя должным образом. Вызов shutdown не делает того, что вы могли бы ожидать. Пример из javadoc ExecutorService.

 void shutdownAndAwaitTermination(ExecutorService pool) {
   pool.shutdown(); // Disable new tasks from being submitted
   try {
     // Wait a while for existing tasks to terminate
     if (!pool.awaitTermination(60, TimeUnit.SECONDS)) {
       pool.shutdownNow(); // Cancel currently executing tasks
       // Wait a while for tasks to respond to being cancelled
       if (!pool.awaitTermination(60, TimeUnit.SECONDS))
           System.err.println("Pool did not terminate");
     }
   } catch (InterruptedException ie) {
     // (Re-)Cancel if current thread also interrupted
     pool.shutdownNow();
     // Preserve interrupt status
     Thread.currentThread().interrupt();
   }
 }

Как правило, пул cachedThread должен быть общим ресурсом и не должен инициализироваться в конечной точке. Смысл cachedThreadPool состоит в том, чтобы воспользоваться преимуществами повторного использования потоков и избежать неконтролируемого создания потоков. Если вам действительно нужен один поток, используйте newSingleThreadExecutor

Я на самом деле не понимаю, почему вы вообще используете исполнителя здесь. Не похоже, что это добавляет какую-либо ценность. Вы просто блокируете свой поток обработчика запросов для выполнения кода в другом потоке, когда он мог бы только что выполниться в исходном потоке.

person Deadron    schedule 04.09.2020
comment
Спасибо за указание на это. Я думаю, что часть этого была плохим случаем копи-пасты, хотя и не ответом на мою настоящую проблему. - person TheDubiousDubber; 08.09.2020