Spring Boot MVC允许在控制器中使用任何类型的内容类型

问题描述 投票:1回答:1

我有一个RestController,多个合作伙伴用来发送XML请求。然而,这是一个遗留系统,它被传递给我,原始实现是在PHP中以非常松散的方式完成的。

这允许客户,现在他们拒绝改变,发送不同的content-typesapplication/xmltext/xmlapplication/x-www-form-urlencoded),它让我需要支持许多MediaTypes,以避免返回415 MediaType Not Supported Errors。

我在配置类中使用了以下代码来允许许多媒体类型。

@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)

由于charset被发送的方式,Spring Boot拒绝返回除415之外的任何东西。甚至MediaType.ALL似乎都没有任何效果。

有没有办法让Spring允许我忽略内容类型?创建过滤器并更改内容类型是不可行的,因为HttpServletRequest不允许改变内容类型。我没有想法但我真的认为必须有一种方法来允许自定义内容类型。

UPDATE

如果我删除@RequestBody然后我没有得到错误415但我无法获得请求正文,因为HttpServletRequest到达Controller操作为空。

spring-boot spring-mybatis media-type
1个回答
1
投票

最好的情况是从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(),你会看到null,因为Spring已经读过了这个流。因此,要访问输入内容,请使用ContentCachingRequestWrapper使用spring的Filter注入,然后您可以稍后重复读取内容,因为它已缓存而不是从原始流中读取。

我在这里包含一些代码片段以供参考,但是要查看可执行示例,您可以参考我的github repo。它是一个带有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);
    }
}
© www.soinside.com 2019 - 2024. All rights reserved.