spring-boot - Spring WebClient - 如果在 doOnError 中引发异常,则停止重试
问题描述
我有以下代码来发出将被重试最大次数的请求。此请求需要一个授权标头,我正在缓存此信息以防止此方法每次都调用该方法来检索此信息。
我想做的是:
- 调用 myMethod 时,我首先检索正在调用的服务的登录信息,在大多数情况下,这些信息将来自调用 getAuthorizationHeaderValue 方法时的缓存。
- 在 Web 客户端中,如果发送此请求的响应返回 4xx 响应,我需要在重试请求之前再次登录到我正在调用的服务。为此,我调用 tryToLoginAgain 方法再次设置标头的值。
- 之后,请求的重试应该可以工作了,因为已经设置了标头。
- 如果再次登录调用失败,我需要停止重试,因为重试请求没有用。
public <T> T myMethod(...) {
...
try {
AtomicReference<String> headerValue = new AtomicReference<>(loginService.getAuthorizationHeaderValue());
Mono<T> monoResult = webclient.get()
.uri(uri)
.accept(MediaType.APPLICATION_JSON)
.header(HttpHeaders.AUTHORIZATION, headerValue.get())
.retrieve()
.onStatus(HttpStatus::is4xxClientError, response -> throwHttpClientLoginException())
.bodyToMono(type)
.doOnError(HttpClientLoginException.class, e -> tryToLoginAgain(headerValue))
.retryWhen(Retry.backoff(MAX_NUMBER_RETRIES, Duration.ofSeconds(5)));
result = monoResult.block();
} catch(Exception e) {
throw new HttpClientException("There was an error while sending the request");
}
return result;
}
...
private Mono<Throwable> throwHttpClientLoginException() {
return Mono.error(new HttpClientLoginException("Existing Authorization failed"));
}
private void tryToLoginAgain(AtomicReference<String> headerValue) {
loginService.removeAccessTokenFromCache();
headerValue.set(loginService.getAuthorizationHeaderValue());
}
我有一些单元测试并且快乐路径工作正常(第一次未经授权,尝试再次登录并再次发送请求)但是登录根本不起作用的场景不起作用。
我认为如果 tryToLoginAgain 方法抛出一个异常,该异常将被我在 myMethod 中的 catch 捕获但它永远不会到达那里,它只是再次重试请求。有什么办法可以做我想做的事吗?
解决方案
所以最后我找到了一种做我想做的事情的方法,现在代码看起来像这样:
public <T> T myMethod() {
try {
HttpHeaders headers = new HttpHeaders();
headers.setBearerAuth(getAuthorizationHeaderValue());
final RetryBackoffSpec retrySpec = Retry.backoff(MAX_NUMBER_RETRIES, Duration.ofSeconds(5))
.doBeforeRetry(retrySignal -> {
//When retrying, if this was a login error, try to login again
if (retrySignal.failure() instanceof HttpClientLoginException) {
tryToLoginAgain(headers);
}
});
Mono<T> monoResult = Mono.defer(() ->
getRequestFromMethod(httpMethod, uri, body, headers)
.retrieve()
.onStatus(HttpStatus::is4xxClientError, response -> throwHttpClientLoginException())
.bodyToMono(type)
)
.retryWhen(retrySpec);
result = monoResult.block();
} catch (Exception e) {
String requestUri = uri != null ?
uri.toString() :
endpoint;
log.error("There was an error while sending the request [{}] [{}]", httpMethod.name(), requestUri);
throw new HttpClientException("There was an error while sending the request [" + httpMethod.name() +
"] [" + requestUri + "]");
}
return result;
}
private void tryToLoginAgain(HttpHeaders httpHeaders) {
//If there was an 4xx error, let's evict the cache to remove the existing access_token (if it exists)
loginService.removeAccessTokenFromCache();
//And let's try to login again
httpHeaders.setBearerAuth(getAuthorizationHeaderValue());
}
private Mono<Throwable> throwHttpClientLoginException() {
return Mono.error(new HttpClientLoginException("Existing Authorization failed"));
}
private WebClient.RequestHeadersSpec getRequestFromMethod(HttpMethod httpMethod, URI uri, Object body, HttpHeaders headers) {
switch (httpMethod) {
case GET:
return webClient.get()
.uri(uri)
.headers(httpHeaders -> httpHeaders.addAll(headers))
.accept(MediaType.APPLICATION_JSON);
case POST:
return body == null ?
webClient.post()
.uri(uri)
.headers(httpHeaders -> httpHeaders.addAll(headers))
.accept(MediaType.APPLICATION_JSON) :
webClient.post()
.uri(uri)
.headers(httpHeaders -> httpHeaders.addAll(headers))
.accept(MediaType.APPLICATION_JSON)
.contentType(MediaType.APPLICATION_JSON)
.bodyValue(body);
case PUT:
return body == null ?
webClient.put()
.uri(uri)
.headers(httpHeaders -> httpHeaders.addAll(headers))
.accept(MediaType.APPLICATION_JSON) :
webClient.put()
.uri(uri)
.headers(httpHeaders -> httpHeaders.addAll(headers))
.accept(MediaType.APPLICATION_JSON)
.contentType(MediaType.APPLICATION_JSON)
.bodyValue(body);
case DELETE:
return webClient.delete()
.uri(uri)
.headers(httpHeaders -> httpHeaders.addAll(headers))
.accept(MediaType.APPLICATION_JSON);
default:
log.error("Method [{}] is not supported", httpMethod.name());
throw new HttpClientException("Method [" + httpMethod.name() + "] is not supported");
}
}
private String getAuthorizationHeaderValue() {
return loginService.retrieveAccessToken();
}
通过使用Mono.defer()
,我可以重试该 Mono 并确保更改将与 WebClient 一起使用的标头。重试规范将检查异常是否属于HttpClientLoginException
在请求获得 4xx 状态码时抛出的异常类型,在这种情况下,它将尝试再次登录并设置下次重试的标头。如果状态码不同,它将使用相同的授权重试。
此外,如果我们再次尝试登录时出现错误,那将被 catch 捕获并且不再重试。
推荐阅读
- python - 将元数据添加到 tensorflow 冻结图 pb
- android - 当我的应用程序被销毁时,firebase 不会存储我的用户并且它会跳过登录页面
- openscenegraph - 如何在 Android 中创建 osg::Image 对象?
- r - 估算常数并创建缺失假人
- jmeter - 如何从 JMeter 中的 AMQP 请求中提取数据
- javascript - 嵌套标签时查找元素包含文本
- java - Socket IO事件多次触发NodeJS
- google-data-studio - 找不到组件 ID:gs://
- c# - 如何从 UTC 偏移量获取 DateTime
- regex - 重型 3GB csv 数据库的 sed/awk 处理问题