web-dev-qa-db-ja.com

Spring WebFlux、WebClient POST exchange?

WebClientリクエストを作成する際に間違ったことを理解するのに苦労しています。実際のHTTP要求がどのように見えるかを理解したいと思います。 (例えば、生のリクエストをコンソールにダンプする)

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.

Spring5のリアクティブツールを使用してAPIを構築しています。 DynのメールAPIを使用してメールを送信するユーティリティクラスがあります。これを達成するために新しいWebClientクラスを使用したいと思います(org.springframework.web.reactive.function.client.WebClient

次のコマンドは以下から取得されています。 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."

実際の値を使用してcurlで呼び出しを行うと、電子メールが正しく送信されるため、要求を誤って生成しているように感じます。

私の送信コマンド

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;
}

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

現在、正しいBodyInserterを使用せずに、リクエスト本文を「現状のまま」シリアル化しようとしています。

この場合、DynEmailOptionsオブジェクトをMultiValueMap<String, String>に変換してから、次のようにする必要があります。

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

問題は、WebClient POSTのデバッグについてです。 callicoder.com で大きな助けを見つけました。

重要なのは、WebClientにフィルターを追加することです。このフィルターにより、要求と応答の両方に簡単にアクセスできます。要求と応答の両方について、メソッド、URL、ヘッダーなどにアクセスできます。ただし、本文にアクセスすることはできません。私が間違っていることを願っていますが、実際には、bodyを設定するbody()メソッドしかありません。

ここで、WebClient POSTの奇妙な動作について不満を言う必要があります。時々、すぐに4XX応答を受け取る代わりに、永久にブロックします。時には、501応答を返します。私のアドバイスは、LinkedMultiValueMapを使用して本文を保持し、プレーンなStringやJava.util.Mapを使用しないようにすることです。

GitHub V3 APIを例として使用したサンプルコードを次に示します。

@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))

次のようなものが表示されます。

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:{}

次の2つの点に注意してください。1.フィルターのシーケンスが重要です。これらの2つのフィルターを交換すると、認証ヘッダーは含まれません。
2。実際には、このWebClientインスタンスを介したすべてのリクエストにフィルターが適用されます。

https://www.callicoder.com/spring-5-reactive-webclient-webtestclient-examples/ はすべて役に立ちます。おそらく読んで彼のサンプルコードをダウンロードしてください。