I have a Spring MVC application that receives an HTTP request from an external system in the form of a JSON string, and its response is returned similarly as 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 works as expected.
However, when I went to test my application against the actual external system that will be using it, I discovered that the incoming requests to not specify a content-type! This completely confuses Spring and results in 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 force Spring to route such a request via the MappingJacksonHttpMessageConverter
, either by somehow forcing Spring to use a custom handler chain or modifying the incoming request to explicitly set a content-type?
I've tried a few things:
MappingJacksonHttpMessageConverter
so that its canRead()
and canWrite()
methods always return true. Unfortunately, Spring doesn't even get to the point of looking at message converters before bailing out due to the lack of content type.Any ideas are appreciated.
To address the comments below, my @RequestMapping
looks like:
@RequestMapping(value="/{service}" )
public @ResponseBody MyResponseObject( @PathVariable String service, @RequestBody MyRequestObject request) {
So there's nothing here that specifies JSON, but without a content type Spring doesn't appear to even take a stab at building my request object from the incoming request (which makes sense, as it does not have enough information to determine how to do so).
And as for @geoand's comment asking "why can you not add the content-type http header in a Servlet Filter or Spring Interceptor", the answer is "because I'm dumb and forgot how servlet filters work". That is the approach that I ultimately used to solve the problem, which I will be adding as an answer imminently.
I was being a bit dumb when I asked this question because I was looking for a way in Spring to directly manipulate the incoming request or otherwise explicitly tell the handler chain that I wanted the request to always be treated as JSON. Once I thought about it for 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'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 );
}
}
I then put this wrapper to use in a Filter like so:
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
}
}
It's not exactly bullet-proof, but it correctly handles request from the one source that this application actually cares about.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With