简体   繁体   English

添加重试 WebClient 的所有请求

[英]Adding a retry all requests of WebClient

we have a server to retrieve a OAUTH token, and the oauth token is added to each request via WebClient.filter method eg我们有一个服务器来检索 OAUTH 令牌,并且 oauth 令牌通过 WebClient.filter 方法添加到每个请求中,例如

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)

I want to have a retry functionality that on 401 error, will invalidate the token and try the request again I have this working like so我想有一个重试功能,在 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());
    }

is there a way to move this up to the webClient.mutate().....build() function?有没有办法将其移动到 webClient.mutate().....build() function? so that all requests will have this retry facility?以便所有请求都具有此重试功能?

I tried adding as a filter but it didn't seem to work eg我尝试添加为过滤器,但它似乎不起作用,例如

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

any suggestions of the best way to approach this?任何解决这个问题的最佳方法的建议? Regards问候

I figured this out, which was apparent after seeing retry only works on exceptions, webClient doesn't throw the exception, since the clientResponse object just holds the response, only when bodyTo is called is the exception thrown on http status, so to fix this, one can mimic this behaviour我发现了这一点,在看到重试仅适用于异常后很明显,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();
    }

EDIT one of the features with repeat/retry is that, it doesn't change the original request, in my case I needed to retrieve a new OAuth token, but the above sent the same (expired) token.编辑重复/重试的功能之一是,它不会更改原始请求,在我的情况下,我需要检索新的 OAuth 令牌,但上面发送了相同的(过期)令牌。 I did figure a way to do this using exchange filter, once OAuth password-flow is in spring-security-2.0 I should be able to have this integrated with AccessTokens etc, but in the mean time我确实想出了一种使用交换过滤器来做到这一点的方法,一旦 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);
                    }
                });
    }

Suppose a scenario I have subscribed the SSE and the sudden server goes down and the client application closes the flux. 假设我已订阅了SSE,并且突然的服务器关闭并且客户端应用程序关闭了流量。

But after a couple of minutes server got revived but the client application didn't pick up the refreshed streaming. 但是过了几分钟,服务器恢复了,但客户端应用程序却没有收到刷新的流。

How can I can handle this scenario? 我该如何处理这种情况? Does it resilient enough to understand the outage in a client-server connection manner? 它是否具有足够的弹性以客户端-服务器连接的方式了解中断?

A generalized approach for common needs:针对共同需求的通用方法:

@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"));
}

I was able to accomplish this completely through an ExchangeFilterFunction , without having to throw exceptions or similar operations.我能够通过ExchangeFilterFunction完全完成此操作,而不必抛出异常或类似操作。

The thing that tripped me up originally was expecting the response (Mono, Flux, etc) to behave the same as the response you get from the resulting WebClient call.最初让我感到困惑的是期望响应(Mono、Flux 等)的行为与您从WebClient调用中获得的响应相同。 When you use the WebClient , the Mono is an "error" if an unauthorized is received, and you can handle it via something like onErrorResume .当您使用WebClient时,如果收到未经授权的消息,则 Mono 是一个“错误”,您可以通过类似onErrorResume的方式处理它。 However, within the ExchangeFilterFunction , if you call next.exchange(ClientRequest) , the Mono returned is just a regular success value of type ClientResponse , even if an unauthorized is returned.但是,在ExchangeFilterFunction中,如果您调用next.exchange(ClientRequest) ,返回的 Mono 只是ClientResponse类型的常规成功值,即使返回未授权也是如此。

So to handle it, you can use code like the following (where token service is substituted for your specific token handling code):因此,要处理它,您可以使用如下代码(其中令牌服务替换了您的特定令牌处理代码):

public class OneRetryAuthExchangeFilterFunction implements ExchangeFilterFunction {

    private final ITokenService tokenService;

    @Override
    public Mono<ClientResponse> filter(ClientRequest request, ExchangeFunction next) {
        ClientRequest authenticatedRequest = applyAuthentication(request);

        return next.exchange(authenticatedRequest)
                .flatMap(response -> {
                    if (HttpStatus.UNAUTHORIZED.equals(response.statusCode())) {
                        tokenService.forceRefreshToken();

                        ClientRequest refreshedAuthenticatedTokenRequest = applyAuthentication(request);

                        return next.exchange(refreshedAuthenticatedTokenRequest);
                    }

                    return Mono.just(response);
                });
    }

    private ClientRequest applyAuthentication(ClientRequest request) {
        String authenticationToken = tokenService.getToken();

        return ClientRequest.from(request)
                .headers(headers -> headers.setBearerAuth(authenticationToken))
                .build();
    }
}

You would then configure your WebClient via something like:然后,您可以通过以下方式配置您的WebClient

WebClient.builder()
        .filter(new OneRetryAuthExchangeFilterFunction(tokenService))
        .build();

and all users of that WebClient would have authentication with a single retry on an unauthorized response并且该 WebClient 的所有用户都将通过对未经授权的响应进行一次重试来进行身份验证

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM