java Spring WebFlux,如何调试我的 WebClient POST 交换?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/45986417/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-11-03 09:00:46  来源:igfitidea点击:

Spring WebFlux, how can I debug my WebClient POST exchange?

javaspringproject-reactorspring-webflux

提问by nograde

I am having trouble understanding what I've done wrong in constructing my WebClient request. I would like to understand what the actual HTTP request looks like. (e.g., dumping the raw request to console)

我无法理解在构建 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.

I am using Spring5's reactive tools to build an API. I have a utility class that will send an email using Dyn's email api. I would like to use The new WebClient class to accomplish this (org.springframework.web.reactive.function.client.WebClient)

我正在使用 Spring5 的响应式工具来构建 API。我有一个实用程序类,它将使用 Dyn 的电子邮件 API 发送电子邮件。我想使用新的 WebClient 类来完成这个(org.springframework.web.reactive.function.client.WebClient

The following command has been taken from : https://help.dyn.com/email-rest-methods-api/sending-api/#postsend

以下命令取自: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."

When I make the call in curl with real values, the email sends correctly, so I feel like I am generating my request incorrectly.

当我使用真实值在 curl 中进行调用时,电子邮件会正确发送,所以我觉得我生成的请求不正确。

My Send Command

我的发送命令

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 Class

我的 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_;
}

采纳答案by Brian Clozel

You're currently trying to serialize the request body "as is", without using the right BodyInserter.

您当前正在尝试“按原样”序列化请求正文,而不使用正确的BodyInserter.

In this case, I think you should turn your DynEmailOptionsobject into a MultiValueMap<String, String>and then:

在这种情况下,我认为你应该把你的DynEmailOptions对象变成 aMultiValueMap<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);

回答by Peter YuChen waNgamer

The question is about debugging WebClient POST. I've found great help in callicoder.com.

问题是关于调试 WebClient POST。我在callicoder.com 上找到了很大的帮助。

The key is to add a filter in the WebClient. The filter allows easy access to both requests and responses. For both requests and responses, you can access method, URL, headers and other things. However, you can't access the body. I hope I'm wrong, but really, there is only a body() method to SET the body.

关键是在WebClient中添加过滤器。过滤器允许轻松访问请求和响应。对于请求和响应,您都可以访问方法、URL、标题和其他内容。但是,您无法访问主体。我希望我是错的,但实际上,只有一个 body() 方法来设置主体。

Here I have to complain about the weird behavior of WebClient POST. Sometimes, instead of getting a 4XX response immediately, it blocks forever. Sometimes, it gives a 501 response. My advice is that try to use a LinkedMultiValueMap to carry the body, avoid using plain String or java.util.Map.

在这里我不得不抱怨 WebClient POST 的奇怪行为。有时,它不会立即获得 4XX 响应,而是永远阻塞。有时,它会给出 501 响应。我的建议是尽量使用 LinkedMultiValueMap 来承载主体,避免使用纯 String 或 java.util.Map。

Here is my sample code, using GitHub V3 API as example:

这是我的示例代码,以 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))

You will see things like:

你会看到类似的东西:

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

There are 2 things to mind about: 1. The sequence of filters matters. Swap these 2 filters and Authentication header will not be included.
2. The filters actually applies to all requests through this WebClient instance.

有两件事需要注意: 1. 过滤器的顺序很重要。交换这 2 个过滤器,将不包含 Authentication 标头。
2. 过滤器实际上适用于通过此 WebClient 实例的所有请求。

https://www.callicoder.com/spring-5-reactive-webclient-webtestclient-examples/is every helpful, maybe you should read it and download his sample code.

https://www.callider.com/spring-5-reactive-webclient-webtestclient-examples/很有帮助,也许你应该阅读它并下载他的示例代码。