Проводка API JSON для REST
Я создаю 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);
}
}
UPDATE
Я добавил конфигурацию Jackson в 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;
}
}
Любые идеи, в которых я ошибаюсь?
Спасибо
Ответы
Ответ 1
Как 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.springf[email protected]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 POSTs на следующие, все это сработало:
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
Надеюсь, кто-то найдет это полезным.
Ответ 2
Если я правильно помню, что Spring docs говорят, что Spring MVC автоматически обнаружит Jackson в пути к классам и настроит 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, см. fooobar.com/questions/4707/...
Ответ 3
В 2014 году я хотел добавить несколько обновлений к этому вопросу, которые помогли мне решить ту же проблему.
-
Обновление кода для замены устаревшего 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;
}
}
-
HTTP/1.1 415 Ошибка неподдерживаемого типа носителя
Проведя много часов, пытаясь понять, почему я все время получаю ошибку 415 даже после добавления правильной конфигурации JSON, я наконец понял, что проблема была НЕ с серверной, а с клиентской стороной. Чтобы Spring принял ваш JSON, вы ДОЛЖНЫ убедиться, что вы отправляете как "Content-Type: application/json", так и "Accept: application/json" как часть ваших заголовков http. для меня конкретно это было андроидное приложение 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();
}
}
Ответ 4
Попробуйте добавить дескриптор того, что в вашем запросе POST. То есть, добавьте в curl
заголовок:
Content-Type: application/json
Если вы не добавите его, curl
будет использовать по умолчанию text/html
независимо от того, что вы действительно отправляете.
Кроме того, в PurchaseController.create()
вы должны добавить, что принятый тип application/json
.
Ответ 5
У меня была та же проблема, которая была решена двумя изменениями в моем коде:
- Отсутствует @PathVariable в аргументе метода, у моего метода не было никаких
-
Следующий метод в моем классе 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;
}
Ответ 6
Вот решение unit test, похожее на ответ yoram givon - fooobar.com/questions/4707/....
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\"," ...
}
}
Запустите unit test, а print()
должен распечатать MockHttpServletRequest, включая Исключение.
В Eclipse (не уверен, как это сделать в других IDE) нажмите ссылку "Исключение", и откроется диалоговое окно свойств для этого исключения. Отметьте поле "включено", чтобы разбить это исключение.
Отладка unit test, а Eclipse будет разбита на исключение. Обследование должно выявить проблему. В моем случае это было потому, что у меня было два одинаковых объекта в моем JSON.
Ответ 7
Я испытал один раз и, наконец, решил его, добавив файл jar файла jackson-mapper-asl.jar. Проверьте, включили ли вы все эти зависимости, хотя само исключение не говорит об этом.
И вам действительно не нужно явно настраивать bean, и вам не нужно помещать "потребляет" в оператор @RequestMapping. Я использую Spring 3.1 битв.
contentType: "application/json" - это единственное, что вам нужно настроить. да, на стороне клиента.
Ответ 8
Попробуйте добавить следующий код в конфигурацию вашего приложения
<mvc:annotation-driven>
<mvc:message-converters>
<bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
<property name="objectMapper" ref="jacksonObjectMapper" />
</bean>
</mvc:message-converters>
Ответ 9
У меня была та же проблема, и я разрешил ее.
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