Spring WebFlux,如何调试我的 WebClient POST 交换?
Spring WebFlux, how can I debug my 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。我有一个实用程序 class,它将使用 Dyn 的电子邮件 api 发送电子邮件。我想使用新的 WebClient class 来完成这个 (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 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_;
}
您目前正在尝试序列化请求正文 "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);
问题是关于调试 WebClient POST。我在 callicoder.com.
中找到了很大的帮助
关键是在WebClient中添加一个过滤器。过滤器允许轻松访问请求和响应。对于请求和响应,您可以访问方法、URL、headers 和其他内容。但是,您无法访问 body。我希望我是错的,但实际上,只有一个 body() 方法来设置 body。
这里不得不吐槽一下WebClient的怪异行为POST。有时,它不会立即获得 4XX 响应,而是永远阻塞。有时,它会给出 501 响应。我的建议是尝试使用 LinkedMultiValueMap 来携带 body,避免使用纯字符串或 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.过滤器的顺序很重要。交换这 2 个过滤器,身份验证 header 将不包括在内。
2. 过滤器实际上适用于通过此 WebClient 实例的所有请求。
https://www.callicoder.com/spring-5-reactive-webclient-webtestclient-examples/ 很有帮助,也许您应该阅读并下载他的示例代码。
我无法理解我在构建 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。我有一个实用程序 class,它将使用 Dyn 的电子邮件 api 发送电子邮件。我想使用新的 WebClient class 来完成这个 (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 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_;
}
您目前正在尝试序列化请求正文 "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);
问题是关于调试 WebClient POST。我在 callicoder.com.
中找到了很大的帮助关键是在WebClient中添加一个过滤器。过滤器允许轻松访问请求和响应。对于请求和响应,您可以访问方法、URL、headers 和其他内容。但是,您无法访问 body。我希望我是错的,但实际上,只有一个 body() 方法来设置 body。
这里不得不吐槽一下WebClient的怪异行为POST。有时,它不会立即获得 4XX 响应,而是永远阻塞。有时,它会给出 501 响应。我的建议是尝试使用 LinkedMultiValueMap 来携带 body,避免使用纯字符串或 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.过滤器的顺序很重要。交换这 2 个过滤器,身份验证 header 将不包括在内。
2. 过滤器实际上适用于通过此 WebClient 实例的所有请求。
https://www.callicoder.com/spring-5-reactive-webclient-webtestclient-examples/ 很有帮助,也许您应该阅读并下载他的示例代码。