web-dev-qa-db-fra.com

La publication Http avec le formulaire de type de contenu de demande ne fonctionne pas dans Spring MVC 3

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

Après avoir reçu la demande, ce que j'ai obtenu est le code d'état Http 415: le serveur a refusé cette demande car 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 en 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");
}

J'obtiendrai la méthode 405 non autorisée. Le plus drôle est dans l'en-tête allow de réponse, il répertorie GET et POST comme méthodes autorisées.

J'ai une classe qui fait le mappage 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 d'exemples Spring. fonctionne très bien avec le type de contenu JSON.

Une question plus générale est de savoir comment faire fonctionner les gestionnaires de requêtes mvc Spring avec différents types de contenu de requête. Tout avis serait grandement apprécié.

35
Bobo

Malheureusement FormHttpMessageConverter (qui est utilisé pour @RequestBody- paramètres annotés lorsque le type de contenu est application/x-www-form-urlencoded) ne peut pas lier les classes cibles (comme @ModelAttribute pouvez).

Vous avez donc 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 une méthode de traitement des données de formulaire distincte avec l'attribut headers approprié:

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

EDIT: Une autre option possible consiste à implémenter votre propre HttpMessageConverter en combinant FormHttpMessageConverter (pour convertir le message d'entrée en carte de paramètres) et WebDataBinder ( pour convertir la carte des paramètres en objet cible).

55
axtavt

J'avais un code de réponse HTTP de 415

Mes problèmes ont été résolus lorsque j'ai ajouté le type de contenu à l'en-tête de la demande

par exemple

"Content-Type: application/json"

22
user1306828

Au cœur du problème, nous souhaitons accepter les types de contenu application/json et application/x-www-form-urlencoded 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 à partir des threads que j'ai trouvés, mais il y a du travail supplémentaire afin que application/x-www-form-urlencoded puisse être utilisé avec @RequestBody.

Tout d'abord, créez un nouveau HttpMessageConverter capable de changer l'entrée de demande en un objet. Je le fais en réutilisant le FormHttpMessageConverter, qui est déjà capable de changer l'entrée en MultiValueMap. Je change ensuite le MultiValueMap en une carte normale et j'utilise Jackson pour transformer la carte en l'objet souhaité.

Voici le code du 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 différentes pour une application Spring de récupérer ce convertisseur de messages. Pour moi, cela a été réalisé dans un fichier XML:

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

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

2
Rossen

L'utilisation de JSON a également fonctionné pour moi, je suppose que cela permet à l'interpréteur JSON d'obtenir les données du corps. J'essayais cependant d'utiliser PUT, ce qui est un peu plus difficile. Vous pouvez lire mon article à ce sujet ici .

0
TheZuck

J'utilise ce code pour convertir le formulaire html en json.

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

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

                        return json;
                    }

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

0
Amin Arab

Ci-dessous a 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);
0
Deepti Kohli