Je cherche moi-même à envoyer une demande POST avec une charge JSON à un serveur distant.
Cette commande GET curl fonctionne bien:
curl -H "Accept:application/json" --user [email protected]:aaa "http://www.aaa.com:8080/aaa-project-rest/api/users/1" -i
Et ce POST on fonctionne bien aussi:
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\" : \"[email protected]\", \"password\" : \"aaa\" }" -i
Et j'essaie de l'imiter dans mon application Android.
L'application fonctionne correctement sur la première demande GET mais donne 400 demandes incorrectes sur la deuxième POST.
Voici le code qui fonctionne pour la requête GET:
RestTemplate restTemplate = new RestTemplate();
restTemplate.getMessageConverters().add(new MappingJackson2HttpMessageConverter());
HttpHeaders httpHeaders = Common.createAuthenticationHeaders("[email protected]" + ":" + "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);
Voici le code source de la requête 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);
Mais cela donne le message:
Could not write request: no suitable HttpMessageConverter found for request type [org.json.JSONObject] and content type [application/json]
Voici le contrôleur 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;
}
}
Le code de la classe 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;
}
}
Assez tard pour répondre, même si je viens juste de résoudre le même problème et de prendre le temps de le résoudre. Donc, je pense que je ferais mieux de le partager et de garder une trace de ma solution.
En réalité, l'exception levée est totalement trompeuse. En fait, le problème n’est pas que le MappingJackson2HttpMessageConverter
ne sache pas rassembler mon objet (ce qui me paraissait étrange, étant JSON), mais une configuration de la variable ObjectMapper
sous-jacente.
Ce que j’ai fait est de désactiver la propriété SerializationFeature.FAIL_ON_EMPTY_BEANS
comme ça
restTemplate = new RestTemplate();
MappingJackson2HttpMessageConverter jsonHttpMessageConverter = new MappingJackson2HttpMessageConverter();
jsonHttpMessageConverter.getObjectMapper().configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
restTemplate.getMessageConverters().add(jsonHttpMessageConverter);
et tout a commencé à fonctionner comme prévu.
Je devais faire quelques choses pour que cela fonctionne.
J'ai d'abord dû convertir le JSONObject en une chaîne, comme dans:
HttpEntity<String> entityCredentials = new HttpEntity<String>(jsonCredentials.toString(), httpHeaders);
La raison en est qu'il n'y a pas de convertisseur de message de mappage pour la classe JSONObject alors qu'il en existe un pour la classe String.
Deuxièmement, je devais passer une vraie valeur au constructeur RestTemplate. Faute de quoi, je recevrais 400 Bad Request.
RestTemplate restTemplate = new RestTemplate(true);
La valeur true indique au gabarit de repos d'utiliser les convertisseurs par défaut. Si quelqu'un sait pourquoi il en est ainsi, je serais heureux d'en savoir plus.
Troisièmement, j'ai supprimé le convertisseur de Jackson inutile:
restTemplate.getMessageConverters().add(new MappingJackson2HttpMessageConverter());
Avec ces choses faites, la demande fonctionne très bien.
Voici le code complet:
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;
Je soupçonne qu'il pourrait y avoir un moyen d'utiliser explicitement un convertisseur de Jackson et d'ignorer la valeur vraie dans le constructeur de gabarit de repos, mais ceci n'est qu'une supposition.
L'exception "aucun HTTPMessageConverter approprié trouvé" est levée, sinon l'implémentation JSON est présente dans le chemin de classe. Regardez le code source de 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());
}
}
Ajoutez une implémentation JSON à votre chemin de classe. Par exemple.:
implementation "com.fasterxml.jackson.core:jackson-databind:2.9.0"
Je sais ... c'est trop tard. Mais me voilà.
J'ai corrigé l'envoi d'un objet Java à la place d'un objet JSON, comme ceci:
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);
Où JsonUtils.stringToObject est mon propre utilitaire de conversion.