Не удалось записать запрос: не найден подходящий HttpMessageConverter для типа запроса [org.json.JSONObject] и типа контента [application/json]
Я пытаюсь отправить запрос POST с полезной нагрузкой JSON на удаленный сервер.
Эта команда GET curl работает нормально:
curl -H "Accept:application/json" --user aaa@aaa.com:aaa "http://www.aaa.com:8080/aaa-project-rest/api/users/1" -i
И этот POST один тоже отлично работает:
curl -H "Accept:application/json" -H "Content-Type: application/json" "http://www.aaa.com:8080/aaa-project-rest/api/users/login" -X POST -d "{ \"email\" : \"aaa@aaa.com\", \"password\" : \"aaa\" }" -i
И поэтому я пытаюсь имитировать это в моем приложении для Android.
Приложение отлично работает при первом GET-запросе, но выдает 400 Bad Request во втором POST-запросе.
Вот код, который работает для запроса GET:
RestTemplate restTemplate = new RestTemplate();
restTemplate.getMessageConverters().add(new MappingJackson2HttpMessageConverter());
HttpHeaders httpHeaders = Common.createAuthenticationHeaders("aaa@aaa.com" + ":" + "aaa");
User user = null;
ResponseEntity<User> responseEntity = restTemplate.exchange("http://" + REST_HOST + ":8080/aaa-project-rest/api/users/" + 1L, HttpMethod.GET, new HttpEntity<Object>(httpHeaders), User.class);
Вот исходный код для запроса POST:
RestTemplate restTemplate = new RestTemplate();
restTemplate.getMessageConverters().add(new MappingJackson2HttpMessageConverter());
User user = null;
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
httpHeaders.setAccept(Arrays.asList(MediaType.APPLICATION_JSON));
JSONObject jsonCredentials = new JSONObject();
jsonCredentials.put("email", REST_LOGIN);
jsonCredentials.put("password", REST_PASSWORD);
ResponseEntity<User> responseEntity = restTemplate.exchange("http://" + REST_HOST + ":" + REST_PORT + "/" + REST_APP + "/api/users/login",
HttpMethod.POST, new HttpEntity<Object>(jsonCredentials, httpHeaders), User.class);
Но это дает сообщение:
Could not write request: no suitable HttpMessageConverter found for request type [org.json.JSONObject] and content type [application/json]
Вот контроллер Spring REST:
@RequestMapping(value = RESTConstants.SLASH + RESTConstants.LOGIN, method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE)
@ResponseBody
public ResponseEntity<UserResource> login(@Valid @RequestBody CredentialsResource credentialsResource, UriComponentsBuilder builder) {
HttpHeaders responseHeaders = new HttpHeaders();
User user = credentialsService.checkPassword(credentialsResource);
userService.clearReadablePassword(user);
if (user == null) {
return new ResponseEntity<UserResource>(responseHeaders, HttpStatus.NOT_FOUND);
} else {
tokenAuthenticationService.addTokenToResponseHeader(responseHeaders, credentialsResource.getEmail());
responseHeaders.setLocation(builder.path(RESTConstants.SLASH + RESTConstants.USERS + RESTConstants.SLASH + "{id}").buildAndExpand(user.getId()).toUri());
UserResource createdUserResource = userResourceAssembler.toResource(user);
ResponseEntity<UserResource> responseEntity = new ResponseEntity<UserResource>(createdUserResource, responseHeaders, HttpStatus.CREATED);
return responseEntity;
}
}
@RequestMapping(value = RESTConstants.SLASH + "{id}", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
@ResponseBody
public ResponseEntity<UserResource> findById(@PathVariable Long id, UriComponentsBuilder builder) {
HttpHeaders responseHeaders = new HttpHeaders();
User user = userService.findById(id);
if (user == null) {
return new ResponseEntity<UserResource>(responseHeaders, HttpStatus.NOT_FOUND);
} else {
UserResource userResource = userResourceAssembler.toResource(user);
responseHeaders.setLocation(builder.path(RESTConstants.SLASH + RESTConstants.USERS + RESTConstants.SLASH + "{id}").buildAndExpand(user.getId()).toUri());
ResponseEntity<UserResource> responseEntity = new ResponseEntity<UserResource>(userResource, responseHeaders, HttpStatus.OK);
return responseEntity;
}
}
Код класса CredentialsResource:
public class CredentialsResource extends ResourceSupport {
@NotEmpty
@Email
private String email;
@NotEmpty
private String password;
public CredentialsResource() {
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
4 ответа
Довольно поздно, чтобы ответить, хотя я только что столкнулся с той же проблемой и потратил некоторое время на ее решение. Так что, думаю, мне лучше поделиться этим и отследить свое решение.
На самом деле, выброшенное исключение полностью вводит в заблуждение. Оказалось, проблема не в том, что MappingJackson2HttpMessageConverter
не знал, как маршалировать мой объект - который звучит странно, будучи JSON, - но конфигурация базового ObjectMapper
,
Что я сделал, чтобы отключить свойство SerializationFeature.FAIL_ON_EMPTY_BEANS
как это
restTemplate = new RestTemplate();
MappingJackson2HttpMessageConverter jsonHttpMessageConverter = new MappingJackson2HttpMessageConverter();
jsonHttpMessageConverter.getObjectMapper().configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
restTemplate.getMessageConverters().add(jsonHttpMessageConverter);
и все начало работать как положено.
Я должен был сделать несколько вещей, чтобы заставить это работать.
Сначала я должен был преобразовать JSONObject в строку, как в:
HttpEntity<String> entityCredentials = new HttpEntity<String>(jsonCredentials.toString(), httpHeaders);
Причина в том, что для класса JSONObject нет конвертера сообщений сопоставления, а для класса String - конвертера.
Во-вторых, мне нужно было передать истинное значение конструктору RestTemplate. В противном случае я получил бы 400 плохих запросов.
RestTemplate restTemplate = new RestTemplate(true);
Истинное значение указывает остальному шаблону использовать конвертеры по умолчанию. Если кто-то знает, почему это так, я был бы рад узнать больше о.
В-третьих, я удалил ненужный конвертер Джексона:
restTemplate.getMessageConverters().add(new MappingJackson2HttpMessageConverter());
С этими вещами, запрос работает просто отлично.
Вот полный код:
RestTemplate restTemplate = new RestTemplate(true);
User user = null;
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.setContentType(MediaType.APPLICATION_JSON);
httpHeaders.setAccept(Arrays.asList(MediaType.APPLICATION_JSON));
try {
JSONObject jsonCredentials = new JSONObject();
jsonCredentials.put("email", REST_LOGIN);
jsonCredentials.put("password", REST_PASSWORD);
Log.e(Constants.APP_NAME, ">>>>>>>>>>>>>>>> JSON credentials " + jsonCredentials.toString());
HttpEntity<String> entityCredentials = new HttpEntity<String>(jsonCredentials.toString(), httpHeaders);
ResponseEntity<User> responseEntity = restTemplate.exchange("http://" + REST_HOST + ":" + REST_PORT + "/" + REST_APP + "/api/users/login",
HttpMethod.POST, entityCredentials, User.class);
if (responseEntity != null) {
user = responseEntity.getBody();
}
return user;
} catch (Exception e) {
Log.e(Constants.APP_NAME, ">>>>>>>>>>>>>>>> " + e.getLocalizedMessage());
}
return null;
Я подозреваю, что может быть способ явно использовать конвертер Джексона и пропустить истинное значение в конструкторе остальных шаблонов, но это всего лишь предположение.
Выдается исключение "не найден подходящий HTTPMessageConverter", если в пути к классам присутствует реализация JSON. Взгляните на исходный код RestTemplate:
public RestTemplate() {
this.messageConverters.add(new ByteArrayHttpMessageConverter());
this.messageConverters.add(new StringHttpMessageConverter());
this.messageConverters.add(new ResourceHttpMessageConverter());
this.messageConverters.add(new SourceHttpMessageConverter<Source>());
this.messageConverters.add(new AllEncompassingFormHttpMessageConverter());
if (romePresent) {
this.messageConverters.add(new AtomFeedHttpMessageConverter());
this.messageConverters.add(new RssChannelHttpMessageConverter());
}
if (jackson2XmlPresent) {
this.messageConverters.add(new MappingJackson2XmlHttpMessageConverter());
}
else if (jaxb2Present) {
this.messageConverters.add(new Jaxb2RootElementHttpMessageConverter());
}
if (jackson2Present) {
this.messageConverters.add(new MappingJackson2HttpMessageConverter());
}
else if (gsonPresent) {
this.messageConverters.add(new GsonHttpMessageConverter());
}
}
Добавьте реализацию JSON в ваш путь к классу. Е. г.:
implementation "com.fasterxml.jackson.core:jackson-databind:2.9.0"
Я знаю... слишком поздно. Но я здесь.
Я исправил это, посылая объект Java вместо JSONObject, что-то вроде этого:
JSONObject jsonCredentials = new JSONObject();
jsonCredentials.put("email", REST_LOGIN);
jsonCredentials.put("password", REST_PASSWORD);
// Generic Object. It might be a DTO.
Object jsonCredentialsObj = JsonUtils.stringToObject(jsonCredentials.toString(), Object.class);
ResponseEntity<User> responseEntity = restTemplate.exchange("http://" + REST_HOST + ":" + REST_PORT + "/" + REST_APP + "/api/users/login",
HttpMethod.POST, new HttpEntity<Object>(jsonCredentialsObj, httpHeaders), User.class);
Где JsonUtils.stringToObject - моя собственная утилита конвертации.
Spring RestTemplate не знает, как сериализовать класс Android org.json.JSONObject. (org.json.JSONObject недоступен в "обычной"/ настольной Java)
RestTemplate для Android поддерживает
Jackson JSON Processor, Jackson 2.x и Google Gson.
за: https://docs.spring.io/autorepo/docs/spring-android/1.0.1.RELEASE/reference/html/rest-template.html
С Джексоном в пути к классам RestTemplate должен понимать, как сериализовать компоненты Java.
CredentialsResource cr = new CredentialsResource();
cr.setEmail(...);
cr.setPassword(...);
HttpEntity<CredentialsResource> entityCredentials = new HttpEntity<CredentialsResource>(cr, httpHeaders);
PS Было бы довольно легко написать свой собственный JSONObjectHttpMessageConverter, если бы вы хотели продолжить использование JSONObject, ваш конвертер должен просто вызвать.toString()