web-dev-qa-db-fra.com

Spring WebFlux, comment puis-je déboguer mon WebClient POST échange?

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_;
}
9
nograde

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);
7
Brian Clozel

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. 

1