web-dev-qa-db-fra.com

Publication de JSON dans REST

Je crée une REST API qui acceptera les requêtes JSON.

Je le teste en utilisant CURL:

curl -i -POST -H 'Accept: application/json' -d '{"id":1,"pan":11111}' http://localhost:8080/PurchaseAPIServer/api/purchase


Mais en obtenant l'erreur suivante:

HTTP/1.1 415 Unsupported Media Type
Server: Apache-Coyote/1.1
Content-Type: text/html;charset=utf-8
Content-Length: 1051
Date: Wed, 25 Apr 2012 21:36:14 GMT

The server refused this request because the request entity is in a format not supported by the requested resource for the requested method ().



Lors du débogage, il n'entre même jamais dans mon action de création dans le contrôleur.

import Java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;

import com.app.model.Purchase;
import com.app.service.IPurchaseService;

@Controller
public class PurchaseController {

    @Autowired
    private IPurchaseService purchaseService;

    @RequestMapping(value = "purchase", method = RequestMethod.GET)
    @ResponseBody
    public final List<Purchase> getAll() {
        return purchaseService.getAll();
    }

    @RequestMapping(value = "purchase", method = RequestMethod.POST)
    @ResponseStatus( HttpStatus.CREATED )
    public void create(@RequestBody final Purchase entity) {
        purchaseService.addPurchase(entity);
    }
}



MISE À JOUR

J'ai ajouté Jackson config à AppConfig.Java:

@Configuration
@ComponentScan(basePackages = "com.app")
public class AppConfig {

    @Bean
    public AnnotationMethodHandlerAdapter annotationMethodHandlerAdapter()
    {
        final AnnotationMethodHandlerAdapter annotationMethodHandlerAdapter = new AnnotationMethodHandlerAdapter();
        final MappingJacksonHttpMessageConverter mappingJacksonHttpMessageConverter = new MappingJacksonHttpMessageConverter();

        HttpMessageConverter<?>[] httpMessageConverter = { mappingJacksonHttpMessageConverter };

        String[] supportedHttpMethods = { "POST", "GET", "HEAD" };

        annotationMethodHandlerAdapter.setMessageConverters(httpMessageConverter);
        annotationMethodHandlerAdapter.setSupportedMethods(supportedHttpMethods);

        return annotationMethodHandlerAdapter;
    }
}



Mes GET fonctionnent correctement maintenant:

curl -i -H "Content-Type:application/json" -H "Accept:application/json" http://localhost:8080/PurchaseAPIServer/api/purchase

HTTP/1.1 200 OK
Server: Apache-Coyote/1.1
Content-Type: application/json
Transfer-Encoding: chunked
Date: Thu, 26 Apr 2012 21:19:55 GMT

[{"id":1,"pan":111}]



Mais j'obtiens ce qui suit lors d'une tentative de POST:

curl -i -X POST -H "Content-Type:application/json" -H "Accept:application/json" http://localhost:8080/PurchaseAPIServer/api/purchaseMe -d "{"id":2,"pan":122}"

HTTP/1.1 400 Bad Request
Server: Apache-Coyote/1.1
Content-Type: text/html;charset=utf-8
Content-Length: 971
Date: Thu, 26 Apr 2012 21:29:56 GMT
Connection: close

The request sent by the client was syntactically incorrect ().



Mon modele:

@Entity
@XmlRootElement
public class Purchase implements Serializable {

    /**
     * 
     */
    private static final long serialVersionUID = 6603477834338392140L;

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    private Long pan;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public Long getPan() {
        return pan;
    }

    public void setPan(Long pan) {
        this.pan = pan;
    }

}



Des idées sur lesquelles je me trompe?

Merci

16
Thomas Buckley

Comme sdouglass l'a suggéré, Spring MVC détecte automatiquement Jackson et configure un MappingJacksonHttpMessageConverter pour gérer la conversion vers/depuis JSON. Mais j'avais besoin d'explicitement configurer le convertisseur pour le faire fonctionner comme il l'a également souligné.

J'ai ajouté ce qui suit et mes demandes CURL GET fonctionnaient ... Hourra.

AppConfig.Java

@Configuration
@ComponentScan(basePackages = "com.app")
public class AppConfig {

    @Bean
    public AnnotationMethodHandlerAdapter annotationMethodHandlerAdapter()
    {
        final AnnotationMethodHandlerAdapter annotationMethodHandlerAdapter = new AnnotationMethodHandlerAdapter();
        final MappingJacksonHttpMessageConverter mappingJacksonHttpMessageConverter = new MappingJacksonHttpMessageConverter();

        HttpMessageConverter<?>[] httpMessageConverter = { mappingJacksonHttpMessageConverter };

        String[] supportedHttpMethods = { "POST", "GET", "HEAD" };

        annotationMethodHandlerAdapter.setMessageConverters(httpMessageConverter);
        annotationMethodHandlerAdapter.setSupportedMethods(supportedHttpMethods);

        return annotationMethodHandlerAdapter;
    }
}


curl -i -H "Content-Type:application/json" -H "Accept:application/json" http://localhost:8080/PurchaseAPIServer/api/purchase

HTTP/1.1 200 OK
Server: Apache-Coyote/1.1
Content-Type: application/json
Transfer-Encoding: chunked
Date: Thu, 26 Apr 2012 21:19:55 GMT

[{"id":1,"pan":111}]



Mais le CURL suivant POST ne fonctionnait toujours pas (Ne jamais toucher l'action du contrôleur et ne donner aucune information de débogage de la console.

curl -i -X POST -H "Content-Type:application/json"  http://localhost:8080/PurchaseAPIServer/api/purchaseMe -d "{"id":2,"pan":122}"

HTTP/1.1 400 Bad Request
Server: Apache-Coyote/1.1
Content-Type: text/html;charset=utf-8
Content-Length: 971
Date: Thu, 26 Apr 2012 21:29:56 GMT
Connection: close

The request sent by the client was syntactically incorrect ().



J'ai donc ajouté Logback pour démarrer un débogage détaillé.

<configuration>

    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n
            </pattern>
        </encoder>
    </appender>

    <appender name="FILE" class="ch.qos.logback.core.FileAppender">
        <file>/home/thomas/springApps/purchaseapi.log</file>
        <encoder>
            <pattern>%date %level [%thread] %logger{10} [%file:%line] %msg%n
            </pattern>
        </encoder>
    </appender>

    <logger name="org.hibernate" level="DEBUG" />

    <logger name="org.springframework" level="TRACE" />
    <logger name="org.springframework.transaction" level="INFO" />
    <logger name="org.springframework.security" level="INFO" /> <!-- to debug security related issues (DEBUG) -->
    <logger name="org.springframework.web.servlet.mvc" level="TRACE" /> <!-- some serialization issues are at trace level here: org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod -->

    <!-- our service -->
    <logger name="com.app" level="DEBUG" />
    <!-- <logger name="com.app" level="INFO" /> --><!-- to follow if setup is being executed -->

    <root level="INFO">
        <appender-ref ref="FILE" />
    </root>

</configuration>



L'ajout du débogage de niveau TRACE à org.springframework.web.servlet.mvc m'a donné la réponse au problème.

2012-04-28 14:17:44,579 DEBUG [http-bio-8080-exec-3] o.s.w.s.m.m.a.RequestResponseBodyMethodProcessor [AbstractMessageConverterMethodArgumentResolver.Java:117] Reading [com.app.model.Purchase] as "application/json" using [org.springframework.http.converter.json.MappingJacksonHttpMessageConverter@74a14fed]
2012-04-28 14:17:44,604 TRACE [http-bio-8080-exec-3] o.s.w.s.m.m.a.ServletInvocableHandlerMethod [InvocableHandlerMethod.Java:159] Error resolving argument [0] [type=com.app.model.Purchase]
HandlerMethod details: 
Controller [com.app.controller.PurchaseController]
Method [public void com.app.controller.PurchaseController.create(com.app.model.Purchase)]

org.springframework.http.converter.HttpMessageNotReadableException: Could not read JSON: Unexpected character ('p' (code 112)): was expecting double-quote to start field name



J'ai changé mes CURL POST pour les suivants et tout a fonctionné:

curl -i -X POST -H "Content-Type:application/json" http://localhost:8080/PurchaseAPIServer/api/purchase -d '{"pan":11111}'
HTTP/1.1 201 Created
Server: Apache-Coyote/1.1
Content-Length: 0
Date: Sat, 28 Apr 2012 13:19:40 GMT

J'espère que quelqu'un trouvera cela utile.

16
Thomas Buckley

Si je me souviens bien, les docs de Spring disent que Spring MVC détectera automatiquement Jackson sur le chemin de classe et configurera un MappingJacksonHttpMessageConverter pour gérer la conversion vers/depuis JSON, mais je pense avoir rencontré des situations où j'ai dû configurer manuellement/explicitement ce convertisseur pour obtenir les choses à travailler. Vous pouvez essayer d'ajouter ceci à votre XML de configuration MVC:

<bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter">
    <property name="messageConverters">
        <list>
            <bean class="org.springframework.http.converter.json.MappingJacksonHttpMessageConverter">
        </list>
    </property>
</bean>

[~ # ~] mise à jour [~ # ~] : c'était cela en plus de formater correctement le JSON en cours de publication, voir https: // stackoverflow.com/a/10363876/433789

6
sdouglass

Son 2014 et je voulais ajouter quelques mises à jour à cette question qui m'a aidé à résoudre le même problème.

  1. Mise à jour de code pour remplacer AnnotationMethodHandlerAdapter obsolète dans Spring 3.2

     @Configuration 
     Classe publique AppConfig {
     
    
        @Bean
        public RequestMappingHandlerAdapter  annotationMethodHandlerAdapter()
        {
            final RequestMappingHandlerAdapter annotationMethodHandlerAdapter = new RequestMappingHandlerAdapter();
            final MappingJackson2HttpMessageConverter mappingJacksonHttpMessageConverter = new MappingJackson2HttpMessageConverter();
    
            List<HttpMessageConverter<?>> httpMessageConverter = new ArrayList<HttpMessageConverter<?>>();
            httpMessageConverter.add(mappingJacksonHttpMessageConverter);
    
            String[] supportedHttpMethods = { "POST", "GET", "HEAD" };
    
            annotationMethodHandlerAdapter.setMessageConverters(httpMessageConverter);
            annotationMethodHandlerAdapter.setSupportedMethods(supportedHttpMethods);
    
            return annotationMethodHandlerAdapter;
        }
    }
    
  2. HTTP/1.1 415 Erreur de type de support non pris en charge

Après avoir passé de nombreuses heures à essayer de comprendre pourquoi j'obtiens toujours une erreur 415 même après avoir ajouté la configuration JSON correcte, j'ai finalement réalisé que le problème n'était pas du côté serveur mais du côté client. Pour que Spring accepte votre JSON, vous DEVEZ vous assurer que vous envoyez à la fois "Content-Type: application/json" et "Accept: application/json" dans le cadre de vos en-têtes http. pour moi en particulier, c'était une Android Android HttpUrlConnection que je devais définir comme:

    public static String doPost(final String urlString,final String requestBodyString) throws IOException {
        final URL url = new URL(urlString);

        final HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
        try {
          urlConnection.setReadTimeout(10000 /* milliseconds */);
          urlConnection.setConnectTimeout(15000 /* milliseconds */);
          urlConnection.setRequestProperty("Content-Type", "application/json");
          urlConnection.setRequestProperty("Accept", "application/json");
          urlConnection.setDoOutput(true);
          urlConnection.setRequestMethod("POST");
          urlConnection.setChunkedStreamingMode(0);

          urlConnection.connect();

          final PrintWriter out = new PrintWriter(urlConnection.getOutputStream());
          out.print(requestBodyString);
          out.close();

          final InputStream in = new BufferedInputStream(urlConnection.getInputStream());
          final String response =  readIt(in);

          in.close(); //important to close the stream

          return response;

        } finally {
          urlConnection.disconnect();
        }
    }
5
yoram givon

Essayez d'ajouter un descripteur de ce qui se trouve dans votre POST. Autrement dit, ajoutez à curl l'en-tête:

Content-Type: application/json

Si vous ne l'ajoutez pas, curl utilisera la valeur par défaut text/html Indépendamment de ce que vous envoyez réellement.

De plus, dans PurchaseController.create() vous devez ajouter que le type accepté est application/json.

2
Diego Sevilla

Voici une solution de test unitaire similaire à la réponse de Yoram Givon - https://stackoverflow.com/a/22516235/1019307 .

public class JSONFormatTest
{
    MockMvc mockMvc;

    // The controller used doesn't seem to be important though YMMV
    @InjectMocks
    ActivityController controller;  

    @Before
    public void setup()
    {
        MockitoAnnotations.initMocks(this);

        this.mockMvc = standaloneSetup(controller).setMessageConverters(new MappingJackson2HttpMessageConverter())
                .build();
    }

    @Test
    public void thatSaveNewDataCollectionUsesHttpCreated() throws Exception
    {
        String jsonContent = getHereJSON02();
        this.mockMvc
                .perform(
                     post("/data_collections").content(jsonContent).contentType(MediaType.APPLICATION_JSON)
                                .accept(MediaType.APPLICATION_JSON)).andDo(print()).andExpect(status().isCreated());
    }

    private String getHereJSON01()
    {
        return "{\"dataCollectionId\":0,\"name\":\"Sat_016\",\"type\":\"httpUploadedFiles\"," ...
    }
}

Exécutez le test unitaire et la print() devrait imprimer le MockHttpServletRequest, y compris l'exception.

Dans Eclipse (vous ne savez pas comment procéder dans d'autres IDE), cliquez sur le lien Exception et une boîte de dialogue des propriétés de cette exception devrait s'ouvrir. Cochez la case "activé" pour rompre cette exception.

Déboguez le test unitaire et Eclipse se cassera sur l'exception. L'inspecter devrait révéler le problème. Dans mon cas, c'était parce que j'avais deux membres de la même entité dans mon JSON.

2
HankCa

J'ai eu le même problème, qui a été résolu par deux changements dans mon code:

  1. @PathVariable manquant dans mon argument de méthode, ma méthode n'en avait pas
  2. La méthode suivante dans ma classe SpringConfig depuis celle que j'avais avec l'intercepteur de gestionnaire était obsolète et posait un problème:

    public RequestMappingHandlerAdapter RequestMappingHandlerAdapter()
    {
        final RequestMappingHandlerAdapter requestMappingHandlerAdapter = new RequestMappingHandlerAdapter();
        final MappingJacksonHttpMessageConverter mappingJacksonHttpMessageConverter = new MappingJacksonHttpMessageConverter();
        final String[] supportedHttpMethods = { "POST", "GET", "HEAD" };
    
        requestMappingHandlerAdapter.getMessageConverters().add(0, mappingJacksonHttpMessageConverter);
        requestMappingHandlerAdapter.setSupportedMethods(supportedHttpMethods);
    
        return requestMappingHandlerAdapter;
    }
    
2
Nisarg Panchal

J'ai eu le même problème et je l'ai résolu.

1 ajoutez MappingJackson2HttpMessageConverter comme décrit dans ce fil (voir également la section 4 http://www.baeldung.com/spring-httpmessageconverter-rest )

2 utilisez la commande correcte (avec les symboles d'échappement):

curl -i -X POST -H "Content-Type:application/json" -d "{\"id\":\"id1\",\"password\":\"password1\"}" http://localhost:8080/user    
0
Alex Alekhin

Je l'ai expérimenté une fois et l'ai finalement résolu en ajoutant le fichier jar jackson-mapper-asl.jar. Allez vérifier si vous avez inclus toutes ces dépendances bien que l'exception elle-même ne vous le dise pas.

Et vous n'avez vraiment pas besoin de configurer explicitement le bean, et vous n'avez pas besoin de mettre "consomme" dans l'instruction @RequestMapping. J'utilise Spring 3.1 btw.

contentType: "application/json" est le seul que vous devez configurer. oui, côté client.

0
tonywu

Essayez d'ajouter le code suivant dans la configuration de votre application

<mvc:annotation-driven>
  <mvc:message-converters>
      <bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
          <property name="objectMapper" ref="jacksonObjectMapper" />
      </bean>
  </mvc:message-converters>
0
Roberto