Отправка JSON в REST API

Я создаю REST API, который будет принимать запросы JSON.

Я тестирую это с помощью CURL:

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


Но получаю следующую ошибку:

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 ().



При отладке он даже не попадает в мое действие create в контроллере.

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



ОБНОВИТЬ

Я добавил конфигурацию Джексона в 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;
    }
}



Мои GET теперь работают правильно:

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}]



Но при попытке 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 ().



Моя модель:

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

}



Есть идеи, где я ошибаюсь?

Спасибо


person Thomas Buckley    schedule 25.04.2012    source источник
comment
Я думаю, что это как-то связано с вашими заголовками. Вы принимаете application/json с помощью curl, но он распознается как тип mimetype text/html.   -  person wachpwnski    schedule 26.04.2012


Ответы (9)


Как было предложено sdouglass, Spring MVC автоматически обнаруживает Джексона и настраивает MappingJacksonHttpMessageConverter для обработки преобразования в/из JSON. Но мне нужно было явно настроить конвертер, чтобы заставить его работать, как он также указал.

Я добавил следующее, и мои запросы CURL GET работали. Ура.

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}]



Но следующий CURL POST по-прежнему не работал (никогда не нажимал на действие контроллера и не давал информации об отладке консоли.

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 ().



Поэтому я добавил Logback, чтобы начать детальную отладку.

<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>



Добавление отладки на уровне TRACE в org.springframework.web.servlet.mvc помогло мне решить проблему.

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



Я изменил свои сообщения CURL на следующие, и все заработало:

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

Надеюсь, кто-то найдет это полезным.

person Thomas Buckley    schedule 28.04.2012
comment
Вам не нужно рассказывать нам историю вашей отладки, это затрудняет поиск фактического ответа - person Louis Hong; 10.07.2014
comment
Спасибо за предложение Logger - это тоже сработало. Вопрос в том, почему этот код Spring маскирует исключение и выдает 400 без каких-либо объяснений? - person HankCa; 22.10.2014

Если я правильно помню, в документах Spring говорится, что Spring MVC автоматически обнаружит Джексона в пути к классам и настроит MappingJacksonHttpMessageConverter для обработки преобразования в/из JSON, но я думаю, что сталкивался с ситуациями, когда мне приходилось вручную/явно настраивать этот преобразователь, чтобы получить вещи для работы. Вы можете попробовать добавить это в свой XML-файл конфигурации 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>

ОБНОВЛЕНИЕ: это плюс правильное форматирование публикуемого JSON, см. https://stackoverflow.com/a/10363876/433789

person sdouglass    schedule 25.04.2012
comment
Спасибо sdouglass, это немного помогло. См. Мое обновление в основном вопросе о проблемах с обработкой POST. - person Thomas Buckley; 27.04.2012

Это 2014 год, и я хотел добавить несколько обновлений к этому вопросу, которые помогли мне решить ту же проблему.

  1. Обновление кода для замены устаревшего AnnotationMethodHandlerAdapter в Spring 3.2

        @Configuration
        public class 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 Ошибка неподдерживаемого типа носителя

Потратив много часов, пытаясь понять, почему я ВСЕ ЕЩЕ ПОЛУЧАЮ ошибку 415 даже после добавления правильной конфигурации JSON, я наконец понял, что проблема была НЕ на стороне сервера, а на стороне клиента. Чтобы Spring принял ваш JSON, вы ДОЛЖНЫ убедиться, что вы отправляете как «Content-Type: application/json», так и «Accept: application/json» как часть ваших заголовков http. конкретно для меня это было приложение для Android HttpUrlConnection, которое я должен был установить как:

    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();
        }
    }
person yoram givon    schedule 19.03.2014
comment
Я думаю, что это должно работать, хотя я не проверял это. Я отправляю вариант этого в качестве ответа, где MappingJackson2HttpMessageConverter() используется в модульном тесте. - person HankCa; 22.10.2014

Попробуйте добавить дескриптор того, что находится в вашем запросе POST. То есть добавить в curl заголовок:

Content-Type: application/json

Если вы не добавите его, curl будет использовать text/html по умолчанию независимо от того, что вы на самом деле отправляете.

Кроме того, в PurchaseController.create() вы должны добавить, что допустимым типом является application/json.

person Diego Sevilla    schedule 25.04.2012
comment
Все еще не повезло, Диего, та же ошибка. Смотрите мой исходный вопрос с обновленным curl, но он все еще отображается как text/html? - person Thomas Buckley; 26.04.2012
comment
См. мою правку в спецификации типа носителя, принятого PurchaseController.create(). - person Diego Sevilla; 26.04.2012
comment
Спасибо за советы, Диего, но добавление принятых типов мультимедиа в контроллеры не обязательно, так как это обрабатывается Spring, когда используется jackson. - person Thomas Buckley; 27.04.2012

У меня была та же проблема, которая была решена двумя изменениями в моем коде:

  1. Отсутствует @PathVariable в аргументе моего метода, мой метод не имеет
  2. Следующий метод в моем классе SpringConfig, поскольку тот, который у меня был с перехватчиком обработчика, устарел и вызвал некоторые проблемы:

    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;
    }
    
person Nisarg Panchal    schedule 11.12.2013

Вот решение для модульного тестирования, похожее на ответ Йорама Гивона - 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\"," ...
    }
}

Запустите модульный тест, и print() должен распечатать MockHttpServletRequest, включая исключение.

В Eclipse (не знаю, как это сделать в других IDE) щелкните ссылку «Исключение», и должно открыться диалоговое окно свойств для этого исключения. Отметьте поле «включено», чтобы отключить это исключение.

Отладьте модульный тест, и Eclipse сломается при исключении. Проверка должна выявить проблему. В моем случае это произошло потому, что в моем JSON было два одинаковых объекта.

person HankCa    schedule 22.10.2014

Однажды я испытал это и, наконец, решил это, добавив файл jar jackson-mapper-asl.jar. Проверьте, включили ли вы все эти зависимости, хотя само исключение не говорит вам об этом.

И вам действительно не нужно явно настраивать bean-компонент, и вам не нужно помещать «consumes» в инструкцию @RequestMapping. Я использую Spring 3.1, кстати.

contentType : «application/json» — это единственное, что вам нужно настроить. да, на стороне клиента.

person tonywu    schedule 16.12.2012

Попробуйте добавить следующий код в конфигурацию вашего приложения

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

person Roberto    schedule 23.02.2015
comment
Для меня проблема начинается после добавления этого, 400 неверный запрос, иначе он работает нормально, но мне нужно отформатировать дату в ответ, поэтому я должен добавить это свойство - person Aadam; 17.10.2016

У меня была такая же проблема, и я решил ее.

1 добавьте MappingJackson2HttpMessageConverter, как описано в этой ветке (см. также раздел 4 http://www.baeldung.com/spring-httpmessageconverter-rest)

2 используйте правильную команду (с escape-символами):

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