【问题标题】:Adding a retry all requests of WebClient添加重试WebClient的所有请求
【发布时间】:2025-11-25 05:30:01
【问题描述】:

我们有一个服务器来检索 OAUTH 令牌,并通过 WebClient.filter 方法将 oauth 令牌添加到每个请求中 例如

webClient
                .mutate()
                .filter((request, next) -> tokenProvider.getBearerToken()
                        .map(token -> ClientRequest.from(request)
                                .headers(httpHeaders -> httpHeaders.set("Bearer", token))
                                .build()).flatMap(next::exchange))
                .build();
TokenProvider.getBearerToken returns Mono<String> since it is a webclient request (this is cached)

我想要一个重试功能,在 401 错误时,将使令牌无效并再次尝试请求 我有这样的工作

webClient.post()
            .uri(properties.getServiceRequestUrl())
            .contentType(MediaType.APPLICATION_JSON)
            .body(fromObject(createRequest))
            .retrieve()
            .bodyToMono(MyResponseObject.class)
            .retryWhen(retryOnceOn401(provider))

private Retry<Object> retryOnceOn401(TokenProvider tokenProvider) {
        return Retry.onlyIf(context -> context.exception() instanceof WebClientResponseException && ((WebClientResponseException) context.exception()).getStatusCode() == HttpStatus.UNAUTHORIZED)
                .doOnRetry(objectRetryContext -> tokenProvider.invalidate());
    }

有没有办法将其移至 webClient.mutate().....build() 函数? 以便所有请求都具有此重试功能?

我尝试添加为过滤器,但它似乎不起作用,例如

.filter(((request, next) -> next.exchange(request).retryWhen(retryOnceOn401(tokenProvider))))

对解决此问题的最佳方法有什么建议吗? 问候

【问题讨论】:

  • 请提供有关“它不起作用”的更多信息 - 您是否遇到异常?您的重试函数是否被调用?令牌没有失效吗?能否提供log() 运算符的日志输出?
  • 嗨,布赖恩,我想我明白了。 webClient 不会在 401 上引发异常,因为这些仅在我调用 bodyToMono 后才会引发,因为这会检查 ClientResponse 的状态并在出现错误时引发 WebClientResponseException。所以在构建器上,retryWhen 永远不会被实际调用,因为没有抛出异常,我可以通过检查响应是 401 并抛出异常然后重试函数启动来完成这项工作。
  • 很高兴听到!请回答您的问题,我相信这会对其他人有所帮助。

标签: spring-webflux project-reactor


【解决方案1】:

我想通了,这在看到 retry 仅适用于异常后很明显,webClient 不会抛出异常,因为 clientResponse 对象只是保存响应,只有在调用 bodyTo 时才会在 http 状态下抛出异常,所以为了解决这个问题,人们可以模仿这种行为

@Bean(name = "retryWebClient")
    public WebClient retryWebClient(WebClient.Builder builder, TokenProvider tokenProvider) {
        return builder.baseUrl("http://localhost:8080")
                .filter((request, next) ->
                        next.exchange(request)
                            .doOnNext(clientResponse -> {
                                    if (clientResponse.statusCode() == HttpStatus.UNAUTHORIZED) {
                                        throw new RuntimeException();
                                    }
                            }).retryWhen(Retry.anyOf(RuntimeException.class)
                                .doOnRetry(objectRetryContext -> tokenProvider.expire())
                                .retryOnce())

                ).build();
    }

编辑重复/重试的功能之一是,它不会更改原始请求,在我的情况下,我需要检索一个新的 OAuth 令牌,但上面发送了相同的(过期)令牌。 我确实想出了一种使用交换过滤器的方法,一旦 OAuth 密码流在 spring-security-2.0 中,我应该能够将它与 AccessTokens 等集成,但与此同时

ExchangeFilterFunction retryOn401Function(TokenProvider tokenProvider) {
        return (request, next) -> next.exchange(request)
                .flatMap((Function<ClientResponse, Mono<ClientResponse>>) clientResponse -> {
                    if (clientResponse.statusCode().value() == 401) {
                        ClientRequest retryRequest = ClientRequest.from(request).header("Authorization", "Bearer " + tokenProvider.getNewToken().toString()).build();
                        return next.exchange(retryRequest);
                    } else {
                        return Mono.just(clientResponse);
                    }
                });
    }

【讨论】:

  • 小心重试和过滤,因为你可以创建一个无限循环。
【解决方案2】:

满足共同需求的通用方法:

@Configuration
public class WebConfiguration {

@Bean
@Primary
public WebClient webClient(ObjectMapper mapper) {

WebClient httpClient =
    WebClient.builder()
        .filter(retryFilter())
        .build();

  return httpClient;
}

private ExchangeFilterFunction retryFilter() {
return (request, next) ->
    next.exchange(request)
        .retryWhen(
            Retry.fixedDelay(3, Duration.ofSeconds(30))
              .doAfterRetry(retrySignal -> log.warn("Retrying"));
}

【讨论】:

    最近更新 更多