首页 文章

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

提问于
浏览
8

我无法理解在构建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 's reactive tools to build an API. I have a utility class that will send an email using 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&from=example@example.com&to=customer1@domain.com&to=customer2@domain.com&to=customer3@domain.com&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;
}

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

2 回答

  • 6

    您当前正在尝试序列化请求正文"as is",而不使用正确的 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);
    
  • 0

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

    关键是在WebClient中添加过滤器 . 过滤器可以轻松访问请求和响应 . 对于请求和响应,您可以访问方法,URL, Headers 和其他内容 . 但是,您无法访问正文 . 我希望我错了,但实际上,只有一个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:{}
    

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

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

相关问题