在 WebFlux WebClient 中测试状态码时如何获取响应正文?
Posted
技术标签:
【中文标题】在 WebFlux WebClient 中测试状态码时如何获取响应正文?【英文标题】:How to get response body when testing the status code in WebFlux WebClient? 【发布时间】:2018-03-27 08:13:03 【问题描述】:在尝试根据返回的状态码抛出异常时如何检索响应正文?例如,假设我想抛出异常并拒绝 HTTP 201。
client.post().exchange().doOnSuccess(response ->
if (response.statusCode().value() == 201)
throw new RuntimeException();
如何使用响应的正文填充异常,以便我可以抛出详细的WebClientResponseException
?
我应该使用不同的方法来测试响应状态代码吗?
编辑:我正在尝试复制以下功能,同时改用exchange()
。
client.get()
.retrieve()
.onStatus(s -> !HttpStatus.CREATED.equals(s),
MyClass::createResponseException);
//MyClass
public static Mono<WebClientResponseException> createResponseException(ClientResponse response)
return response.body(BodyExtractors.toDataBuffers())
.reduce(DataBuffer::write)
.map(dataBuffer ->
byte[] bytes = new byte[dataBuffer.readableByteCount()];
dataBuffer.read(bytes);
DataBufferUtils.release(dataBuffer);
return bytes;
)
.defaultIfEmpty(new byte[0])
.map(bodyBytes ->
String msg = String.format("ClientResponse has erroneous status code: %d %s", response.statusCode().value(),
response.statusCode().getReasonPhrase());
Charset charset = response.headers().contentType()
.map(MimeType::getCharset)
.orElse(StandardCharsets.ISO_8859_1);
return new WebClientResponseException(msg,
response.statusCode().value(),
response.statusCode().getReasonPhrase(),
response.headers().asHttpHeaders(),
bodyBytes,
charset
);
);
【问题讨论】:
【参考方案1】:您可以通过自定义 ExchangeFilterFunction 并在构建 WebClient 之前将其与 WebClient.Builder 挂钩来实现此目的。
public static ExchangeFilterFunction errorHandlingFilter()
return ExchangeFilterFunction.ofResponseProcessor(clientResponse ->
if(clientResponse.statusCode()!=null && (clientResponse.statusCode().is5xxServerError() || clientResponse.statusCode().is4xxClientError()) )
return clientResponse.bodyToMono(String.class)
.flatMap(errorBody ->
return Mono.error(new CustomWebClientResponseException(errorBody,clientResponse.statusCode()));
);
else
return Mono.just(clientResponse);
);
你可以像这样使用上面的:
WebClient.builder()
.clientConnector(new ReactorClientHttpConnector(clientOptions))
.defaultHeader(HttpHeaders.USER_AGENT, "Application")
.filter(WebClientUtil.errorHandlingFilter())
.baseUrl("https://httpbin.org/")
.build()
.post()
.uri("/post")
.body(BodyInserters.fromObject(customObjectReference) )
.exchange()
.flatMap(response -> response.toEntity(String.class) );
因此,任何 4XX 或 5XX HttpResponse 实际上都会抛出 CustomWebClientResponseException ,您可以配置一些全局异常处理程序并使用它做您喜欢的事情。至少使用 ExchangeFilterFunction,您可以拥有全局位置来处理此类事情或添加自定义标头和内容。
【讨论】:
这个解决方案是最好的!如果可以的话,我会投票 10 次。 :) 感谢您提供的出色解决方案。如果我有机会,我会给予 100 多个 ups。 感谢您的解决方案。我建议您可以简化if
为:clientResponse.statusCode().isError()
【参考方案2】:
经过反复试验,我得到了以下似乎可以解决问题的方法。
Mono<ClientResponse> mono = client.get().exchange()
.flatMap(response ->
if (HttpStatus.CREATED.equals(response.statusCode()))
return Mono.just(response);
else
return response.body(BodyExtractors.toDataBuffers())
.reduce(DataBuffer::write)
.map(dataBuffer ->
byte[] bytes = new byte[dataBuffer.readableByteCount()];
dataBuffer.read(bytes);
DataBufferUtils.release(dataBuffer);
return bytes;
)
.defaultIfEmpty(new byte[0])
.flatMap(bodyBytes ->
String msg = String.format("ClientResponse has erroneous status code: %d %s", response.statusCode().value(),
response.statusCode().getReasonPhrase());
Charset charset = response.headers().contentType()
.map(MimeType::getCharset)
.orElse(StandardCharsets.ISO_8859_1);
return Mono.error(new WebClientResponseException(msg,
response.statusCode().value(),
response.statusCode().getReasonPhrase(),
response.headers().asHttpHeaders(),
bodyBytes,
charset
));
);
)
.retry(3);
final CompletableFuture<ClientResponse> future = mono.toFuture();
【讨论】:
谢谢!对于调试,这很好。注意databuffer读取后需要释放,所以响应体被消耗,bodyToMono()
会返回null。【参考方案3】:
doOn**
操作符是副作用操作符,例如应该用于记录目的。
在这里,您希望在管道级别实现该行为,因此onStatus
更适合这里:
Mono<ClientHttpResponse> clientResponse = client.post().uri("/resource")
.retrieve()
.onStatus(httpStatus -> HttpStatus.CREATED.equals(httpStatus),
response -> response.bodyToMono(String.class).map(body -> new MyException(body)))
bodyToXYZ(...);
或者
Mono<ResponseEntity<String>> result = client.post().uri("/resource")
.exchange()
.flatMap(response -> response.toEntity(String.class))
.flatMap(entity ->
// return Mono.just(entity) or Mono.error() depending on the response
);
请注意,如果您期望大型响应主体,则获取整个响应主体可能不是一个好主意;在这种情况下,您将在内存中缓冲大量数据。
【讨论】:
抱歉,我实际上想使用exchange()
,以便我可以访问ClientResponse
。由于该方法不可用,您能否以其他方式执行与 onStatus()
相同的功能?
添加了一个可能适合您的用例的替代解决方案,尽管我不知道您为什么特别喜欢访问 ClientResponse。您想在您的客户端中将其设置为 ExchangeFilterFunction
吗?
我已经编辑了这个问题,希望能更清楚地了解我所追求的。
@BrianClozel 使用大型响应机构操作的最佳做法是什么?
@BrianClozel 我按照您处理 onStatus 的方法进行操作,但我认为它被吞没了....也许您有一些想法?我可以把我的代码发给你吗?以上是关于在 WebFlux WebClient 中测试状态码时如何获取响应正文?的主要内容,如果未能解决你的问题,请参考以下文章
Spring WebFlux WebClient 弹性和性能
Srping 响应式框架 WebFlux 的性能小测试_WebClient连接池
Spring WebFlux Webclient 接收应用程序/八位字节流文件作为 Mono