2014-04-17 8 views
6

Ho un'applicazione Spring MVC che riceve una richiesta HTTP da un sistema esterno sotto forma di una stringa JSON e la sua risposta viene restituita allo stesso modo di una stringa JSON. Il mio controller è annotato correttamente con @RequestBody e @ResponseBody e ho test di integrazione che effettivamente inviano richieste per verificare che tutto funzioni come previsto.Spring MVC: @RequestBody quando non è specificato alcun tipo di contenuto

Tuttavia, quando sono andato a testare la mia applicazione contro il sistema esterno effettivo che lo utilizzerà, ho scoperto che le richieste in arrivo non specificavano un tipo di contenuto! Questo confonde completamente primavera e risultati nei seguenti tipi di errori:

DEBUG [] 2014-04-17 13:33:13,471 AbstractHandlerExceptionResolver.java:132 resolveException - Resolving exception from handler [[email protected]]: org.springframework.web.HttpMediaTypeNotSupportedException: Cannot extract parameter (ValidationRequest request): no Content-Type found 

Quindi, c'è un modo per forzare primavera per instradare tale richiesta tramite il MappingJacksonHttpMessageConverter, sia in qualche modo costringendo Primavera di utilizzare una catena gestore personalizzato o modificando la richiesta in arrivo per impostare esplicitamente un tipo di contenuto?

Ho provato un paio di cose:

  • Estendere MappingJacksonHttpMessageConverter modo che i suoi canRead() e canWrite() metodi restituiscono sempre vero. Sfortunatamente, Spring non arriva nemmeno al punto di guardare i convertitori di messaggi prima del salvataggio a causa della mancanza di tipo di contenuto.
  • Utilizzo di intercettatori o filtri Servlet per impostare manualmente il tipo di contenuto. Sfortunatamente, non riesco a vedere un modo per entrambi questi meccanismi di apportare effettivamente una modifica alla richiesta in arrivo oltre a impostare nuovi attributi.

Tutte le idee sono apprezzate.


per affrontare i commenti qui sotto, il mio @RequestMapping assomiglia:

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

Quindi qui non c'è niente che specifica JSON, ma senza un tipo di contenuto primavera non sembra prendere anche una pugnalata a edificio la mia richiesta oggetto dalla richiesta in entrata (che ha senso, in quanto non ha abbastanza informazioni per determinare come farlo).

E come per il commento di @ geoand che chiede "perché non si può aggiungere l'intestazione http del tipo di contenuto in un filtro servlet o Spring Interceptor", la risposta è "perché sono stupido e ho dimenticato come funzionano i filtri servlet". Questo è l'approccio che alla fine ho usato per risolvere il problema, che aggiungerò imminentemente come risposta.

+0

Se i normali filtri servlet non funzionano, hai provato il RequestInterceptorFilter di Spring? Dovrai intercettare la richiesta e scrivere una nuova risposta che verrà inoltrata alla primavera. –

+0

Aggiungi il tuo @RequestMapping Code –

+0

Hai esplicitamente dichiarato nella richiesta mapping che puoi accettare solo json? Perché non è possibile aggiungere l'intestazione http del tipo di contenuto in un filtro servlet o in Spring Interceptor? – geoand

risposta

4

Ero un po 'stupido quando ho posto questa domanda perché stavo cercando un modo in Spring per manipolare direttamente la richiesta in arrivo o altrimenti dire esplicitamente alla catena del gestore che volevo che la richiesta venisse sempre trattata come JSON. Una volta pensato per un po ', ho capito che questo è esattamente ciò che i filtri Servlet sono per.

In primo luogo, ho creato un nuovo HttpServletRequestWrapper che assomiglia a questo:

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

} 

Ho poi messo questo wrapper da utilizzare in un filtro in questo modo:

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 
    } 

} 

Non è esattamente a prova di proiettile, ma gestisce correttamente la richiesta dall'unica fonte a cui questa applicazione interessa realmente.

Problemi correlati