HttpMediaTypeNotAcceptableException: не удалось найти приемлемое представление в обработчике исключений

У меня есть следующий способ загрузки изображений в моем контроллере (Spring 4.1):

@RequestMapping(value = "/get/image/{id}/{fileName}", method=RequestMethod.GET)
public @ResponseBody byte[] showImageOnId(@PathVariable("id") String id, @PathVariable("fileName") String fileName) {
    setContentType(fileName); //sets contenttype based on extention of file
    return getImage(id, fileName);
}

Следующий метод ControllerAdvice должен обрабатывать несуществующий файл и возвращать ответ json-error:

@ExceptionHandler(ResourceNotFoundException.class)
@ResponseStatus(HttpStatus.NOT_FOUND)
public @ResponseBody Map<String, String> handleResourceNotFoundException(ResourceNotFoundException e) {
    Map<String, String> errorMap = new HashMap<String, String>();
    errorMap.put("error", e.getMessage());
    return errorMap;
}

Мой тест JUnit работает безупречно

( EDIT, это из-за расширения .bla: это также работает на appserver):

@Test
public void testResourceNotFound() throws Exception {
    String fileName = "bla.bla";
      mvc.perform(MockMvcRequestBuilders.get("/get/image/bla/" + fileName)
            .with(httpBasic("test", "test")))
            .andDo(print())
            .andExpect(jsonPath("$error").value(Matchers.startsWith("Resource not found")))
            .andExpect(status().is(404));
}

и дает следующий результат:

MockHttpServletResponse:
          Status = 404
   Error message = null
         Headers = {X-Content-Type-Options=[nosniff], X-XSS-Protection=[1; mode=block], Cache-Control=[no-cache, no-store, max-age=0, must-revalidate], Pragma=[no-cache], Expires=[0], X-Frame-Options=[DENY], Content-Type=[application/json]}
    Content type = application/json
            Body = {"error":"Resource not found: bla/bla.bla"}
   Forwarded URL = null
  Redirected URL = null
         Cookies = []

Однако на моем сервере приложений я получаю следующее сообщение об ошибке при попытке загрузить не существующее изображение:

( РЕДАКТИРОВАТЬ, это из-за расширения .jpg: это также терпит неудачу в тесте JUnit с расширением .jpg):

ERROR org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver - Failed to invoke @ExceptionHandler method: public java.util.Map<java.lang.String, java.lang.String> nl.krocket.ocr.web.controller.ExceptionController.handleResourceNotFoundException(nl.krocket.ocr.web.backing.ResourceNotFoundException) org.springframework.web.HttpMediaTypeNotAcceptableException: Could not find acceptable representation

Я сконфигурировал messageconverters в моей конфигурации mvc следующим образом:

@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
    converters.add(mappingJackson2HttpMessageConverter());
    converters.add(byteArrayHttpMessageConverter());
}

@Bean
public MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter() {
    ObjectMapper objectMapper = new ObjectMapper();
    objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
    objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
    //objectMapper.registerModule(new JSR310Module());
    MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
    converter.setObjectMapper(objectMapper);
    converter.setSupportedMediaTypes(getJsonMediaTypes());
    return converter;
}

@Bean
public ByteArrayHttpMessageConverter byteArrayHttpMessageConverter() {
    ByteArrayHttpMessageConverter arrayHttpMessageConverter = new ByteArrayHttpMessageConverter();
    arrayHttpMessageConverter.setSupportedMediaTypes(getImageMediaTypes());
    return arrayHttpMessageConverter;
}

Что мне не хватает? И почему работает JUnit-тест?

Ответ 1

Вам необходимо решить, как тип носителя ответа должен быть определен Spring. Это можно сделать несколькими способами:

  • расширение пути (например,/image.jpg)
  • Параметр URL (например,? Format = jpg)
  • Заголовок HTTP Accept (например, Accept: image/jpg)

По умолчанию Spring смотрит на расширение, а не на заголовок Accept. Это поведение может быть изменено, если вы реализуете класс @Configuration который расширяет WebMvcConfigurerAdapter (или, поскольку Spring 5.0 просто реализует WebMvcConfigurer. Там вы можете переопределить configureContentNegotiation(ContentNegotiationConfigurer configurer) и настроить ContentNegotiationConfigurer в соответствии с вашими потребностями, например, путем вызова

ContentNegotiationConfigurer#favorParameter
ContentNegotiationConfigurer#favorPathExtension

Если вы установите оба в false, Spring будет смотреть на заголовок Accept. Так как ваш клиент может сказать Accept: image/*,application/json и обрабатывать оба, Spring должен иметь возможность вернуть либо изображение, либо ошибку JSON.

См. Этот учебник Spring по согласованию контента для получения дополнительной информации и примеров.

Ответ 2

Обратите внимание на заголовок HTTP Accept. Например, если ваш контроллер создает "application/octet-stream" (в ответ), ваш заголовок Accept не должен быть "application/json" (в запросе):

@GetMapping(value = "/download", produces = MediaType.APPLICATION_OCTET_STREAM_VALUE)
public void download(HttpServletResponse response) {}