J'ai de la difficulté à comprendre ce que j'ai fait de mal en construisant ma demande WebClient. J'aimerais comprendre à quoi ressemble la demande HTTP réelle. (par exemple, vider la demande brute sur la console)
POST /rest/json/send HTTP/1.1
Host: emailapi.dynect.net
Cache-Control: no-cache
Postman-Token: 93e70432-2566-7627-6e08-e2bcf8d1ffcd
Content-Type: application/x-www-form-urlencoded
apikey=ABC123XYZ&from=example%40example.com&to=customer1%40domain.com&to=customer2%40domain.com&to=customer3%40domain.com&subject=New+Sale+Coming+Friday&bodytext=You+will+love+this+sale.
J'utilise les outils réactifs de Spring5 pour construire une API. J'ai une classe utilitaire qui enverra un email en utilisant l'API email de Dyn. Je voudrais utiliser la nouvelle classe WebClient pour y parvenir (org.springframework.web.reactive.function.client.WebClient)
La commande suivante est extraite de: https://help.dyn.com/email-rest-methods-api/sending-api/#postsend
curl --request POST "https://emailapi.dynect.net/rest/json/send" --data "apikey=ABC123XYZ&[email protected]&[email protected]&[email protected]&[email protected]&subject=New Sale Coming Friday&bodytext=You will love this sale."
Lorsque je passe l'appel en boucle avec de vraies valeurs, l'e-mail est envoyé correctement. J'ai donc l'impression de ne pas générer correctement ma demande.
Ma commande d'envoi
public Mono<String> send( DynEmailOptions options )
{
WebClient webClient = WebClient.create();
HttpHeaders headers = new HttpHeaders();
// this line causes unsupported content type exception :(
// headers.setContentType( MediaType.APPLICATION_FORM_URLENCODED );
Mono<String> result = webClient.post()
.uri( "https://emailapi.dynect.net/rest/json/send" )
.headers( headers )
.accept( MediaType.APPLICATION_JSON )
.body( BodyInserters.fromObject( options ) )
.exchange()
.flatMap( clientResponse -> clientResponse.bodyToMono( String.class ) );
return result;
}
Ma classe DynEmailOptions
import Java.util.Collections;
import Java.util.Set;
public class DynEmailOptions
{
public String getApikey()
{
return apiKey_;
}
public Set<String> getTo()
{
return Collections.unmodifiableSet( to_ );
}
public String getFrom()
{
return from_;
}
public String getSubject()
{
return subject_;
}
public String getBodytext()
{
return bodytext_;
}
protected DynEmailOptions(
String apiKey,
Set<String> to,
String from,
String subject,
String bodytext
)
{
apiKey_ = apiKey;
to_ = to;
from_ = from;
subject_ = subject;
bodytext_ = bodytext;
}
private Set<String> to_;
private String from_;
private String subject_;
private String bodytext_;
private String apiKey_;
}
Vous essayez actuellement de sérialiser le corps de la requête "tel quel", sans utiliser la variable correcte BodyInserter
.
Dans ce cas, je pense que vous devriez transformer votre objet DynEmailOptions
en un MultiValueMap<String, String>
puis:
MultiValueMap<String, String> formData = ...
Mono<String> result = webClient.post()
.uri( "https://emailapi.dynect.net/rest/json/send" )
.contentType(MediaType.APPLICATION_FORM_URLENCODED)
.accept( MediaType.APPLICATION_JSON )
.body( BodyInserters.fromFormData(formData))
.retrieve().bodyToMono(String.class);
La question concerne le débogage de WebClient POST. J'ai trouvé une aide précieuse dans callicoder.com .
La clé est d'ajouter un filtre dans le Web Client. Le filtre permet un accès facile aux demandes et aux réponses. Pour les demandes et les réponses, vous pouvez accéder à la méthode, à l'URL, aux en-têtes et à d'autres éléments. Cependant, vous ne pouvez pas accéder au corps. J'espère que je me trompe, mais vraiment, il n'y a qu'une méthode body () pour définir le corps.
Ici, je dois me plaindre du comportement étrange de WebClient POST. Parfois, au lieu d’obtenir une réponse 4XX immédiatement, elle bloque pour toujours. Parfois, il donne une réponse 501. Mon conseil est que d'essayer d'utiliser un LinkedMultiValueMap pour transporter le corps, évitez d'utiliser plain String ou Java.util.Map.
Voici mon exemple de code, utilisant l'API GitHub V3 comme exemple:
@Bean
public WebClient client() {
return WebClient.builder()
.baseUrl("https://api.github.com")
.defaultHeader("User-Agent", "Spring-boot WebClient")
.filter(ExchangeFilterFunctions.basicAuthentication("YOUR_GITHUB_USERNAME", "YOUR_GITHUB_TOKEN"))
.filter(printlnFilter).build();
}
ExchangeFilterFunction printlnFilter= new ExchangeFilterFunction() {
@Override
public Mono<ClientResponse> filter(ClientRequest request, ExchangeFunction next) {
System.out.println("\n\n" + request.method().toString().toUpperCase() + ":\n\nURL:"
+ request.url().toString() + ":\n\nHeaders:" + request.headers().toString() + "\n\nAttributes:"
+ request.attributes() + "\n\n");
return next.exchange(request);
}
};
//In some method:
String returnedJSON = client.post().uri(builder->builder.path("/user/repos").build())
.contentType(MediaType.APPLICATION_JSON)
.syncBody(new LinkedMultiValueMap<String, String>(){{
put("name", "tett");
}})
.retrieve()
.bodyToMono(String.class)
.block(Duration.ofSeconds(3))
Vous verrez des choses comme:
2018-04-07 12:15:57.823 INFO 15448 --- [ main] o.s.b.web.embedded.netty.NettyWebServer : Netty started on port(s): 8084
2018-04-07 12:15:57.828 INFO 15448 --- [ main] c.e.w.WebclientDemoApplication : Started WebclientDemoApplication in 3.892 seconds (JVM running for 8.426)
POST:
URL:https://api.github.com/user/repos:
Headers:{Content-Type=[application/json], User-Agent=[Spring-boot WebClient], Authorization=[Basic XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX]}
Attributes:{}
Il y a 2 choses à l'esprit à propos de: 1. La séquence de filtres est importante. Echangez ces 2 filtres et l'en-tête d'authentification ne sera pas inclus.
2. Les filtres s'appliquent en réalité à toutes les demandes via cette instance WebClient.
https://www.callicoder.com/spring-5-reactive-webclient-webtestclient-examples/ est utile, vous devriez peut-être le lire et télécharger son exemple de code.