spring-webflux - 在 401 响应的情况下,带有 oauth 令牌的 Spring 响应式 Web 客户端 REST 请求
问题描述
我想尝试使用 Spring 响应式 Web 客户端和一个实际上很简单的示例:请求 REST 资源,并在 401 响应的情况下获取新的 OAuth 访问令牌。
第一部分似乎很简单:
return webClientBuilder
.baseUrl(targetInstance.getBaseUrl())
.build()
.get().uri(targetInstance.getItemEndpointUrl())
.retrieve()
.bodyToMono(ItemResponse.class)
....
但是这里的混乱已经开始了。我尝试了类似的东西
.onStatus(HttpStatus::is4xxClientError, (response) -> {
if(response.rawStatusCode() == 401) {
oAuthClient.initToken()
然后,我的令牌应保存在实例 JPA 实体中。但是我想我在这里缺乏概念上的理解。当 OAuth 客户端收到 OAuth 响应时,我需要先将其提取以将其(作为嵌入对象)保存在我的实例实体中。因此我需要阻止它,对吗?
.exchangeToMono(response -> {
if (response.statusCode().equals(HttpStatus.OK)) {
OAuthResponse oauthResponse = response.bodyToMono(OAuthResponse.class).block();
}
根据 OAuth 客户端的响应结果,我需要某种 Mono 来告诉实际的 REST 客户端,然后它是否应该开始重试?在 .retrieve() 或 .exchangeToMono() 上应该首选哪种方式?因此,如果我走在正确的道路上,或者应该使用经典的 RestTemplate 更好地完成类似的事情,我会在这里有点迷失吗?但我也读过 RestTemplate 没有被弃用......
感谢您与我分享一些想法。
解决方案
好的,与此同时,我找到了一种非阻塞方式。也许不是最好的,但对我来说效果很好。
客户端:
class ApiClient {
public Mono<MyResponse> getResponse(Tenant tenant) {
return webClientBuilder
.baseUrl(tenant.getUrl())
.clientConnector(getClientConnector())
.build()
.get().uri("/api/my-content-entpoint")
.exchangeToMono(response -> {
if (response.statusCode().equals(HttpStatus.OK)) {
return response.bodyToMono(MyResponse.class);
} else if(response.statusCode().equals(HttpStatus.FORBIDDEN)) {
return Mono.error(new MyOAuthExcpetion());
} else {
return Mono.empty();
}
});
}
}
服务:
@Service
public class MyService {
private final ApiClient apiClient;
private final RetryStrategy retryStrategy;
private final TenantService tenantService;
public Mono<MyResponse> getResponse(String tenantId){
return tenantService.getTenant(tenantId)
.flatMap(tenant-> apiClient.getResponse(instance))
.retryWhen(Retry.from(signals -> signals
.flatMap(retrySignal -> retryStrategy.reconnect(retrySignal, tenantId))));
}
}
和重试策略
@Component
public class RetryStrategy {
private final TenantService tenantService;
public Publisher<? extends Long> reconnect(RetrySignal retrySignal, String tenantId) {
long count = retrySignal.totalRetriesInARow();
Throwable failure = retrySignal.failure();
if(count > 0) {
return Mono.error(new UnsupportedOperationException("Retry failed", failure));
}
Mono<Tenant> updatedTenant = null;
if(failure instanceof MyOAuthExcpetion) {
updatedTenant = tenantService.getTenant(tenantId)
.flatMap(tenant -> tenantService.refreshOAuth(tenant));
}
if(updatedTenant == null) {
return Mono.error(new UnsupportedOperationException("Retry failed", failure));
}
return updatedTenant.then(Mono.delay(Duration.ofSeconds(1)));
}
}
很高兴有任何反馈或改进。
推荐阅读
- swift - 带有来自数组的文本标签的 UICollectionView
- javascript - StripeJS - 在输入中获取 Exp 月份和年份
- batch-file - 如何制作脚本来检查计算机中的许可证?
- javascript - 使用 isInteger 检查字符(作为字符串)是否为数字不起作用
- vb.net - 如何在 VB.Net 的 ListView 中显示图标
- android - Flutter(稳定)运行 Android throwToolExit(包:flutter_tools/src/base/common.dart:14:3)
- mongodb - 流星连接到远程mongodb错误自签名证书
- php - PHP 没有执行 INSERT INTO
- r - 为什么 For 循环在 R 和循环索引中无效
- php - PHP preg_match 语言相关解释错误