我有一个RestController
,多个合作伙伴用来发送XML请求。然而,这是一个遗留系统,它被传递给我,原始实现是在PHP中以非常松散的方式完成的。
这允许客户,现在他们拒绝改变,发送不同的content-types
(application/xml
,text/xml
,application/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操作为空。
最好的情况是从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);
}
}