Java Spring WebFlux,如何调试WebClient POST exchange?

Java Spring WebFlux,如何调试WebClient POST exchange?,java,spring,project-reactor,spring-webflux,Java,Spring,Project Reactor,Spring Webflux,我很难理解我在构建WebClient请求时做错了什么。我想了解实际的HTTP请求是什么样子的。(例如,将原始请求转储到控制台) 我正在使用Spring5的反应式工具构建API。我有一个实用类,将发送电子邮件使用Dyn的电子邮件api。我想使用新的WebClient类来实现这一点(org.springframework.web.reactive.function.client.WebClient) 以下命令取自: 当我在curl中调用实际值时,电子邮件发送正确,因此我感觉生成的请求不正确 我的发送

我很难理解我在构建WebClient请求时做错了什么。我想了解实际的HTTP请求是什么样子的。(例如,将原始请求转储到控制台)

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

以下命令取自:

当我在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;
}
公共单声道发送(双声道选项)
{
WebClient WebClient=WebClient.create();
HttpHeaders=新的HttpHeaders();
//此行导致不支持的内容类型异常:(
//headers.setContentType(MediaType.APPLICATION\u FORM\u URLENCODED);
Mono result=webClient.post()
.uri(“https://emailapi.dynect.net/rest/json/send" )
.标题(标题)
.accept(MediaType.APPLICATION_JSON)
.body(BodyInserters.fromObject(选项))
.exchange()
.flatMap(clientResponse->clientResponse.bodytomino(String.class));
返回结果;
}
我的动态选项课

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_;
}
import java.util.Collections;
导入java.util.Set;
公共类动态选择
{
公共字符串getApikey()
{
返回apiKey;
}
公共集getTo()
{
返回集合。不可修改集(到);
}
公共字符串getFrom()
{
从中国返回;
}
公共字符串getSubject()
{
返回主题;
}
公共字符串getBodytext()
{
返回bodytext;
}
受保护的动态选择(
字符串apiKey,
设置为,
从,
字符串主题,
字符串正文
)
{
apiKey=apiKey;
to=to;
from=from;
主语=主语;
bodytext=bodytext;
}
私密设置为",;
来自(u)的私有字符串;
私有字符串主语;
私有字符串bodytext;
私有字符串apiKey;
}

您当前正在尝试“按原样”序列化请求正文,而没有使用正确的
正文插入器

在这种情况下,我认为应该将
DynEmailOptions
对象转换为
多值映射
,然后:

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);
多值映射formData=。。。
Mono result=webClient.post()
.uri(“https://emailapi.dynect.net/rest/json/send" )
.contentType(MediaType.APPLICATION\u FORM\u URLENCODED)
.accept(MediaType.APPLICATION_JSON)
.body(BodyInserters.fromFormData(formData))
.retrieve().BodyToNo(String.class);

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

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

在这里,我不得不抱怨WebClient POST的奇怪行为。有时,它不会立即得到4XX响应,而是永远阻塞。有时,它会给出501响应。我的建议是,尝试使用LinkedMultiValueMap来承载正文,避免使用纯字符串或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))
有两件事需要注意: 1.过滤器的顺序很重要。交换这两个过滤器和身份验证标头将不包括在内。
2.过滤器实际上适用于通过此WebClient实例的所有请求


非常有用,也许你应该阅读它并下载他的示例代码。

嗨,Brian,我对问题的调试部分很感兴趣,与apaches HttpClient类似,能够转储传递的原始数据非常方便。spring的WebClient中是否有类似的功能?您好,我也很好奇是否有一个可以转储正在传递的原始数据(请求主体,如果存在的话)。我认为logging.level.reactor=debug应该可以做到这一点-那么至少您可以看到请求后问题是如何打印请求主体,因为
BodyInserter
似乎不需要toString()
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);
@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:{}