2010-12-02 22 views
35

extrait de code:Http Poster avec la forme de type de contenu de la demande ne fonctionne pas dans Spring MVC 3

@RequestMapping(method = RequestMethod.POST)//, headers = "content-type=application/x-www-form-urlencoded") 
public ModelAndView create(@RequestBody UserAccountBean account) { 
    try{ 
     accounts.put(account.assignId(), account); 
    }catch(RuntimeException ex) 
    { 
     return new ModelAndView("account/registerError"); 
    } 
    return new ModelAndView("account/userVerification"); 
} 

Après réception de la demande, ce que je suis est Http Code d'état 415: Le serveur a refusé cette demande parce que l'entité de demande est dans un format non pris en charge par la ressource demandée pour la méthode demandée().

Si je change le code à ceci:

extrait de code:

@RequestMapping(method = RequestMethod.POST,headers = "content-type=application/x-www-form-urlencoded") 
public ModelAndView create(@RequestBody UserAccountBean account) { 
    try{ 
     accounts.put(account.assignId(), account); 
    }catch(RuntimeException ex) 
    { 
     return new ModelAndView("account/registerError"); 
    } 
    return new ModelAndView("account/userVerification"); 
} 

Je vais obtenir 405 Méthode non autorisée. Une chose amusante est dans l'en-tête allow de la réponse, elle liste GET et POST comme méthodes autorisées.

J'ai une classe qui fait la cartographie JOSN:

@Component 
public class JacksonConversionServiceConfigurer implements BeanPostProcessor { 

private final ConversionService conversionService; 

@Autowired 
public JacksonConversionServiceConfigurer(ConversionService conversionService) { 
    this.conversionService = conversionService; 
} 

public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { 
    return bean; 
} 

public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { 
    if (bean instanceof AnnotationMethodHandlerAdapter) { 
     AnnotationMethodHandlerAdapter adapter = (AnnotationMethodHandlerAdapter) bean; 
     HttpMessageConverter<?>[] converters = adapter.getMessageConverters(); 
     for (HttpMessageConverter<?> converter : converters) { 
      if (converter instanceof MappingJacksonHttpMessageConverter) { 
       MappingJacksonHttpMessageConverter jsonConverter = (MappingJacksonHttpMessageConverter) converter; 
       jsonConverter.setObjectMapper(new ConversionServiceAwareObjectMapper(this.conversionService)); 
      }    
     } 
    } 
    return bean; 
} 

} 

Copié à partir des exemples de printemps. fonctionne très bien avec le type de contenu JSON.

Une question plus générale est de savoir comment faire en sorte que les gestionnaires de requêtes printaniers mvc fonctionnent avec différents types de contenu de requête. Tout conseil serait grandement apprécié.

Répondre

53

Malheureusement FormHttpMessageConverter (qui est utilisé pour @RequestBody paramètres -annotated lorsque le type de contenu est application/x-www-form-urlencoded) ne peut pas lier les classes cibles (comme @ModelAttribute peut). Par conséquent, vous avez besoin de @ModelAttribute au lieu de @RequestBody. Si vous n'avez pas besoin de passer différents types de contenu à cette méthode, vous pouvez simplement remplacer l'annotation:

@RequestMapping(method = RequestMethod.POST) 
public ModelAndView create(@ModelAttribute UserAccountBean account) { ... } 

Sinon, je suppose que vous pouvez créer des données de formulaire de traitement des formulaires de méthode distincte avec l'headers attribut approprié:

@RequestMapping(method = RequestMethod.POST, 
    headers = "content-type=application/x-www-form-urlencoded") 
public ModelAndView createFromForm(@ModelAttribute UserAccountBean account) { ... } 

EDIT: Une autre option possible est de mettre en œuvre votre propre HttpMessageConverter en combinant FormHttpMessageConverter (pour convertir un message d'entrée à la carte des paramètres) et WebDataBinder (pour convertir carte des paramètres à l'obje cible ct).

+0

BTW, où avez-vous découvert cela? de tous les documents de référence (si oui, pouvez-vous partager s'il vous plaît?) ou par essai et erreur? – Bobo

+0

@Bobo: Le fait que 'FormHttpMessageConverter' produise' MultiValueMap' peut être trouvé dans son javadoc: http://static.springsource.org/spring/docs/3.0.x/javadoc-api/org/springframework/http/converter /FormHttpMessageConverter.html – axtavt

+0

Oops. Je n'ai pas vérifié le javadoc. Une autre chose que je ne comprends pas est pourquoi Spring framework ne détectera pas cela et me lancer une erreur msg au moment du déploiement. – Bobo

2

L'utilisation de @ModelAttribute est en effet la méthode préférée pour traiter les paramètres de formulaire.

22

J'avais code de réponse HTTP 415

Mes problèmes ont été résolus quand j'ai ajouté le contenu pour demander la tête

par exemple

"Content-Type: application/json"

0

Utiliser JSON a fonctionné pour moi aussi, je suppose que cela fait que l'interpréteur JSON récupère les données du corps. J'essayais d'utiliser PUT, ce qui est un peu plus difficile. Vous pouvez lire mon article à ce sujet here.

0

Ci-dessous travaillé pour moi

côté serveur:

@RequestMapping(value = "test", method = RequestMethod.POST, consumes = {"application/xml", "application/json"}) 
     @ResponseStatus(HttpStatus.OK) 
     public @ResponseBody 
     String methodName(@RequestBody EntityClassName entity) { 

côté client:

String json = new JSONStringer().object() 
         .key("key").value("value") 
         .endObject() 
         .toString(); 
StringEntity se = new StringEntity(json); 
se.setContentType(new BasicHeader(HTTP.CONTENT_TYPE, "application/json")); 
request.setEntity(se); 
HttpResponse response = client.execute(request); 
3

Au cœur du problème, nous voulons accepter à la fois l'application/JSON et application/x-www-form-urlencoded Content-types avec le même gestionnaire de requêtes. Pour ce faire, j'utilise @RequestBody, qui fonctionnait déjà pour application/json pour moi (et généralement d'autres pour les threads que j'ai trouvés, mais il y a du travail supplémentaire donc application/x-www-form- urlencoded peut être utilisé avec @RequestBody

D'abord, créez un nouveau HttpMessageConverter capable de modifier l'entrée de la requête sur un objet, en réutilisant le FormHttpMessageConverter, qui est déjà capable de changer l'entrée en MultiValueMap. changez le MultiValueMap à une carte normale, et utilisez Jackson pour tourner la carte à l'objet désiré

Voici le code pour le HttpMessageConverter:

import com.fasterxml.jackson.databind.ObjectMapper; 
import org.springframework.http.HttpInputMessage; 
import org.springframework.http.HttpOutputMessage; 
import org.springframework.http.MediaType; 
import org.springframework.http.converter.FormHttpMessageConverter; 
import org.springframework.http.converter.HttpMessageConverter; 
import org.springframework.http.converter.HttpMessageNotReadableException; 
import org.springframework.util.LinkedMultiValueMap; 
import org.springframework.util.MultiValueMap; 

import java.io.IOException; 
import java.util.List; 
import java.util.Map; 

/** 
* <p>Converts HTTP requests with bodies that are application/x-www-form-urlencoded or multipart/form-data to an Object 
* annotated with {@link org.springframework.web.bind.annotation.RequestBody} in the the handler method. 
* 
* @author Jesse Swidler 
*/ 
public class ObjectHttpMessageConverter implements HttpMessageConverter<Object> { 

    private final FormHttpMessageConverter formHttpMessageConverter = new FormHttpMessageConverter(); 
    private final ObjectMapper objectMapper = new ObjectMapper(); 

    private static final LinkedMultiValueMap<String, String> LINKED_MULTI_VALUE_MAP = new LinkedMultiValueMap<>(); 
    private static final Class<? extends MultiValueMap<String, ?>> LINKED_MULTI_VALUE_MAP_CLASS 
      = (Class<? extends MultiValueMap<String, ?>>) LINKED_MULTI_VALUE_MAP.getClass(); 

    @Override 
    public boolean canRead(Class clazz, MediaType mediaType) { 
     return objectMapper.canSerialize(clazz) && formHttpMessageConverter.canRead(MultiValueMap.class, mediaType); 
    } 

    @Override 
    public boolean canWrite(Class clazz, MediaType mediaType) { 
     return false; 
    } 

    @Override 
    public List<MediaType> getSupportedMediaTypes() { 
     return formHttpMessageConverter.getSupportedMediaTypes(); 
    } 

    @Override 
    public Object read(Class clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException { 
     Map<String, String> input = formHttpMessageConverter.read(LINKED_MULTI_VALUE_MAP_CLASS, inputMessage).toSingleValueMap(); 
     return objectMapper.convertValue(input, clazz); 
    } 

    @Override 
    public void write(Object o, MediaType contentType, HttpOutputMessage outputMessage) throws UnsupportedOperationException { 
     throw new UnsupportedOperationException(""); 
    } 
} 

Il existe de nombreuses façons qu'une application Spring peut prendre ce convertisseur de message. Pour moi, il a été accompli dans un fichier XML:

<mvc:annotation-driven> 
    <mvc:message-converters> 
     <bean class="com.terminal.core.services.config.ObjectHttpMessageConverter"/> 
    </mvc:message-converters> 
</mvc:annotation-driven> 
0

J'utilise ce code pour convertir la forme html en json.

function ConvertFormToJSON(form) { 
         var array = $(form).serializeArray(); 
         var json = {}; 

         $.each(array, function() { 
          json[this.name] = this.value || ''; 
         }); 

         return json; 
        } 

et d'utiliser des guillemets simples était faux. J'ai changé '' à "" et le problème a été résolu.