【问题标题】:Spring Boot MVC to allow any kind of content-type in controllerSpring Boot MVC 允许控制器中的任何类型的内容类型
【发布时间】:2019-04-02 19:55:57
【问题描述】:

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

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

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

@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 到达控制器操作为空。

【问题讨论】:

    标签: spring-boot spring-mybatis media-type


    【解决方案1】:

    最好的情况是从RequestMapping 构造函数中删除consumes 参数。在你添加它的那一刻,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 已经读取了该流。因此,要访问输入内容,请使用 spring 的 ContentCachingRequestWrapper 注入和 Filter,然后您可以稍后重复读取缓存的内容,而不是从原始流中读取。

    我在这里包含了一些代码 sn-p 以供参考,但是要查看可执行示例,您可以参考我的 github repo。它是一个带有 maven 的 spring-boot 项目,一旦你启动它,你可以将你的 post 请求发送到http://localhost:3007/badmedia,它会在回复request content-type &amp; 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);
        }
    }
    

    【讨论】:

    • 这不起作用。我原本没有消耗,然后添加以查看是否有帮助。
    • 同一个。 415. 如果我省略了@RequestBody,那么我就不会收到这个错误。但我无法获取请求有效负载,因为如果 HttpServletRequest 有一个 null inputStream
    • 是的,你不能有 @RequestBody 注释,因为 spring 将再次尝试解析内容类型以正确解析正文。以及您收到带有注入 HttpServletRequest 的 null inputStream 的原因,因为 Spring 已经读取了正文,因此流是空的。我已经更新了我的解决方案,以便通过示例代码为您提供更多帮助。
    • 更新的解决方案对您有用吗?为您提供所需的内容。
    • 嗨,Amith,我一回到办公室就会对其进行测试并进行更新。它看起来很有希望,我真的很感激这个答案。它还没有解决,所以我们将在星期一尝试第一件事
    猜你喜欢
    • 2022-12-21
    • 1970-01-01
    • 2011-05-23
    • 2014-04-06
    • 1970-01-01
    • 1970-01-01
    • 2018-07-17
    • 2018-08-05
    • 1970-01-01
    相关资源
    最近更新 更多