我有一个Spring MVC应用程序,它以JSON字符串的形式从外部系统接收HTTP请求,其响应与JSON字符串类似地返回.我的控制器使用@RequestBody和@ResponseBody正确注释,我有集成测试,实际上发送请求以验证一切是否按预期工作.
但是,当我针对将要使用它的实际外部系统测试我的应用程序时,我发现传入的请求没有指定内容类型!这完全混淆了Spring并导致以下类型的错误:
DEBUG [] 2014-04-17 13:33:13,471 AbstractHandlerExceptionResolver.java:132 resolveException - Resolving exception from handler [com.example.controller.MyController@1d04f0a]: org.springframework.web.HttpMediaTypeNotSupportedException: Cannot extract parameter (ValidationRequest request): no Content-Type found
那么,是否有办法强制Spring通过MappingJacksonHttpMessageConverter路由此类请求,或者通过某种方式强制Spring使用自定义处理程序链或修改传入请求以显式设置内容类型?
我尝试过几件事:
>扩展MappingJacksonHttpMessageConverter,使其canRead()和canWrite()方法始终返回true.不幸的是,由于缺乏内容类型,Spring甚至无法在淘汰之前查看消息转换器.
>使用拦截器或Servlet过滤器手动设置内容类型.不幸的是,除了设置新属性之外,我无法看到这些机制中的任何一种实际上对传入请求进行更改的方法.
任何想法都表示赞赏.
为了解决以下评论,我的@RequestMapping看起来像:
@RequestMapping(value="/{service}" )
public @ResponseBody MyResponSEObject( @PathVariable String service,@RequestBody MyRequestObject request) {
所以这里没有任何东西可以指定JSON,但是没有内容类型Spring似乎甚至没有从传入请求中构建我的请求对象(这是有道理的,因为它没有足够的信息来确定如何这样做).
至于@ geoand的评论,询问“为什么你不能在Servlet过滤器或Spring Interceptor中添加内容类型的http标头”,答案是“因为我愚蠢而且忘记了servlet过滤器是如何工作的”.这是我最终用来解决问题的方法,我将立即作为答案添加.
首先,我创建了一个新的HttpServletRequestWrapper,如下所示:
public class ForcedContentTypeHttpServletRequestWrapper extends HttpServletRequestWrapper {
private static final Logger log = Logger.getLogger( ForcedContentTypeHttpServletRequestWrapper.class );
// this is the header to watch out for and what we should make sure it always resolves to.
private static final String CONTENT_TYPE_HEADER = "content-type";
private static final String CONTENT_TYPE = "application/json";
public ForcedContentTypeHttpServletRequestWrapper( HttpServletRequest request ) {
super( request );
}
/**
* If content type is explicitly queried,return our hardcoded value
*/
@Override
public String getContentType() {
log.debug( "Overriding request's content type of " + super.getContentType() );
return CONTENT_TYPE;
}
/**
* If we are being asked for the content-type header,always return JSON
*/
@Override
public String getHeader( String name ) {
if ( StringUtils.equalsIgnoreCase( name,CONTENT_TYPE_HEADER ) ) {
if ( super.getHeader( name ) == null ) {
log.debug( "Content type was not originally included in request" );
}
else {
log.debug( "Overriding original content type from request: " + super.getHeader( name ) );
}
log.debug( "Returning hard-coded content type of " + CONTENT_TYPE );
return CONTENT_TYPE;
}
return super.getHeader( name );
}
/**
* When asked for the names of headers in the request,make sure "content-type" is always
* supplied.
*/
@SuppressWarnings( { "unchecked","rawtypes" } )
@Override
public Enumeration getHeaderNames() {
ArrayList headerNames = Collections.list( super.getHeaderNames() );
if ( headerNames.contains( CONTENT_TYPE_HEADER ) ) {
log.debug( "content type already specified in request. Returning original request headers" );
return super.getHeaderNames();
}
log.debug( "Request did not specify content type. Adding it to the list of headers" );
headerNames.add( CONTENT_TYPE_HEADER );
return Collections.enumeration( headerNames );
}
/**
* If we are being asked for the content-type header,always return JSON
*/
@SuppressWarnings( { "rawtypes","unchecked" } )
@Override
public Enumeration getHeaders( String name ) {
if ( StringUtils.equalsIgnoreCase( CONTENT_TYPE_HEADER,name ) ) {
if ( super.getHeaders( name ) == null ) {
log.debug( "Content type was not originally included in request" );
}
else {
log.debug( "Overriding original content type from request: " + Collections.list( super.getHeaders( name ) ) );
}
log.debug( "Returning hard-coded content type of " + CONTENT_TYPE );
return Collections.enumeration( Arrays.asList( CONTENT_TYPE ) );
}
return super.getHeaders( name );
}
}
然后我把这个包装器用在Filter中,如下所示:
public class ContentTypeFilter implements Filter {
/**
* @see Filter#destroy()
*/
@Override
public void destroy() {
// do nothing
}
/**
* @see Filter#doFilter(ServletRequest,ServletResponse,FilterChain)
*/
@Override
public void doFilter( ServletRequest request,ServletResponse response,FilterChain chain ) throws IOException,ServletException {
ForcedContentTypeHttpServletRequestWrapper requestWrapper = new ForcedContentTypeHttpServletRequestWrapper( (HttpServletRequest) request );
chain.doFilter( requestWrapper,response );
}
/**
* @see Filter#init(FilterConfig)
*/
@Override
public void init( FilterConfig fConfig ) throws ServletException {
// do nothing
}
}
它并不完全是防弹,但它正确地处理了该应用程序实际关心的一个来源的请求.