Spring Boot MVC, чтобы разрешить любой тип содержимого в контроллере - PullRequest
2 голосов
/ 02 апреля 2019

У меня есть RestController, который несколько партнеров используют для отправки запросов XML.Однако это устаревшая система, которую мне передали, и оригинальная реализация была сделана очень свободно в PHP.

Это позволило клиентам, которые теперь отказываются меняться, отправлять различные content-types (application/xml, text/xml, application/x-www-form-urlencoded), и это оставило мне необходимость поддержки многих MediaTypes, чтобы избежать возврата 415 MediaType Неподдерживаемые ошибки.

Я использовал следующий код в классе конфигурации, чтобы разрешить много типов носителей.

@Bean
public MarshallingHttpMessageConverter marshallingMessageConverter() {
    MarshallingHttpMessageConverter converter = new MarshallingHttpMessageConverter();
    converter.setMarshaller(jaxbMarshaller());
    converter.setUnmarshaller(jaxbMarshaller());
    converter.setSupportedMediaTypes(Arrays.asList(MediaType.APPLICATION_OCTET_STREAM, MediaType.APPLICATION_XML,
            MediaType.TEXT_XML, MediaType.TEXT_PLAIN, MediaType.APPLICATION_FORM_URLENCODED, MediaType.ALL));
    return converter;
}

@Bean
public Jaxb2Marshaller jaxbMarshaller() {
    Jaxb2Marshaller marshaller = new Jaxb2Marshaller();
    marshaller.setClassesToBeBound(CouponIssuedStatusDTO.class, CouponIssuedFailedDTO.class,
            CouponIssuedSuccessDTO.class, RedemptionSuccessResultDTO.class, RedemptionResultHeaderDTO.class,
            RedemptionFailResultDTO.class, RedemptionResultBodyDTO.class, RedemptionDTO.class, Param.class,
            ChannelDTO.class, RedeemRequest.class);
    Map<String, Object> props = new HashMap<>();
    props.put(javax.xml.bind.Marshaller.JAXB_FORMATTED_OUTPUT, true);
    marshaller.setMarshallerProperties(props);
    return marshaller;
}

Метод контроллера таков:

@PostMapping(value = "/request", produces = { "application/xml;charset=UTF-8" }, consumes = MediaType.ALL_VALUE)
public ResponseEntity<RedemptionResultDTO> request(
        @RequestHeader(name = "Content-Type", required = false) String contentType,
        @RequestBody String redeemRequest) {
    return requestCustom(contentType, redeemRequest);

}

Эта конечная точка пораженавсеми клиентами.Это только один последний клиент, доставляющий мне неприятности.Они отправляют content-type = application/x-www-form-urlencoded; charset=65001 (UTF-8)": 65001 (UTF-8)

Из-за способа отправки кодировки Spring Boot отказывается возвращать что-либо, кроме 415 .Даже MediaType.ALL, кажется, не имеет никакого эффекта.

Есть ли способ заставить Spring позволить мне достичь этого, игнорируя тип содержимого?Создание фильтра и изменение типа контента было невозможно, так как HttpServletRequest не позволяет изменять тип контента.У меня нет идей, но я действительно думаю, что должен быть способ разрешить создание пользовательских типов контента.

ОБНОВЛЕНИЕ

Если я удалю @RequestBody, то яне получаю ошибку 415, но у меня нет способа получить тело запроса, так как HttpServletRequest достигает действия Controller пустым.

1 Ответ

1 голос
/ 03 апреля 2019

В лучшем случае вы должны удалить аргумент consumes из конструктора RequestMapping. В тот момент, когда вы добавили его, Spring попытается разобрать его в известный тип MediaType.parseMediaType(request.getContentType()), который пытается создать new MimeType(type, subtype, parameters) и, таким образом, выдает исключение из-за передачи неверного формата кодировки.

Однако, если вы удалите consumes и захотите проверить / ограничить входящий Content-Type определенным типом, вы можете добавить HttpServletRequest в свой метод в качестве параметра, а затем проверить значение request.getHeader(HttpHeaders.CONTENT_TYPE).

Вы также должны удалить аннотацию @RequestBody, чтобы Spring не пытался анализировать тип содержимого при попытке демонтировать тело. Если вы прямо попытаетесь прочитать здесь request.getInputStream() или request.getReader(), вы увидите нулевое значение, поскольку поток уже был прочитан Spring. Таким образом, чтобы получить доступ к входному контенту, используйте ContentCachingRequestWrapper инъекцию Spring, используя Filter, а затем вы можете впоследствии повторно читать контент, так как он кэшируется и не читает из исходного потока.

Я включил здесь фрагмент кода для справки, однако, чтобы увидеть исполняемый пример, вы можете обратиться к моему репозиторию github . Это весенний загрузочный проект с maven. После запуска вы можете отправить свой запрос на сообщение по номеру http://localhost:3007/badmedia, и он ответит вам в ответ request content-type & body. Надеюсь, это поможет.

@RestController
public class BadMediaController {

        @PostMapping("/badmedia")
        @ResponseBody
        public Object reflect(HttpServletRequest request) throws IOException {
            ObjectMapper mapper = new ObjectMapper();
            JsonNode rootNode = mapper.createObjectNode();
            ((ObjectNode) rootNode).put("contentType", request.getHeader(HttpHeaders.CONTENT_TYPE));
            String body = new String(((ContentCachingRequestWrapper) request).getContentAsByteArray(), StandardCharsets.UTF_8);
            body = URLDecoder.decode(body, StandardCharsets.UTF_8.name());
            ((ObjectNode) rootNode).put("body", body);
            return mapper.writerWithDefaultPrettyPrinter().writeValueAsString(rootNode);
        }
    }


@Component
public class CacheRequestFilter extends GenericFilterBean {

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain)
            throws IOException, ServletException {
        HttpServletRequest cachedRequest
                = new ContentCachingRequestWrapper((HttpServletRequest) servletRequest);
        //invoke caching
        cachedRequest.getParameterMap();
        chain.doFilter(cachedRequest, servletResponse);
    }
}
...