Spring MVC: @RequestBody if no content type specified

I have a Spring MVC application that receives an HTTP request from an external system as a JSON string, and its response is returned similarly to a JSON string. My controller is correctly annotated with @RequestBody and @ResponseBody , and I have integration tests that actually send requests to verify that everything is working as expected.

However, when I went to check my application for the actual external system that will use it, I found that the incoming requests did not indicate the type of content! This completely confuses Spring and leads to the following types of errors:

 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 

So, is there a way to get Spring to forward such a request through the MappingJacksonHttpMessageConverter , either somehow forcing Spring to use a custom handler chain, or modifying the incoming request to explicitly set the content type

I have tried several things:

  • The MappingJacksonHttpMessageConverter extension MappingJacksonHttpMessageConverter that its canRead() and canWrite() methods always return true. Unfortunately, Spring doesn't even get to look at message converters before releasing it due to lack of content type.
  • Use interceptors or servlet filters to manually set the type of content. Unfortunately, I do not see a way for any of these mechanisms to make changes to the incoming request, besides setting new attributes.

Any ideas are welcome.


To answer the comments below, my @RequestMapping looks like this:

 @RequestMapping(value="/{service}" ) public @ResponseBody MyResponseObject( @PathVariable String service, @RequestBody MyRequestObject request) { 

So there is nothing here that JSON indicates, but without the content type, Spring doesn't seem to even strike at creating my request object from an incoming request (which makes sense since it does not have enough information to determine how to do this).

As for the @geoand comment, asking β€œwhy can't you add the header of the http type of content to the servlet filter or the <w760 interceptor>, the answer isβ€œ because I am dumb and forgot how the servlet filters work. ”This is an approach that I ultimately used to solve the problem, which I will add as an answer soon.

+6
source share
1 answer

I was a bit dumb when I asked this question because I was looking for a way in Spring to directly manipulate an incoming request or otherwise explicitly tell the chain of handlers that I wanted the request to always be treated as JSON. Once I thought about this a bit, I realized that this is exactly what servlet filters are for.

First I created a new HttpServletRequestWrapper that looks like this:

 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 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 ); } } 

Then I put this wrapper in the filter as follows:

 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 } } 

This is not entirely bulletproof, but it correctly processes the request from a single source, which this application really refers to.

+4
source

All Articles