背景:我有一个 React Native 应用程序,它使用基于 Microsoft 令牌的身份验证与 .NET 框架后端(Web API)进行通信。令牌将在 30 分钟后过期,然后当进行下一次调用时,服务器会响应 401,然后调用刷新令牌 API,然后创建新的刷新令牌并应用程序使用它。当我们等待刷新令牌时,其他调用在客户端上排队,一旦令牌到达,就会使用新令牌调用它们。
问题(神秘):问题是有时服务器的状态为0。我们试图找出原因,但没有成功。我们还看到的模式是,当调用刷新令牌时,后端会删除旧令牌并生成新令牌。但是,当应用程序调用新的 api 时,它仍然使用相同的旧令牌,而不是新令牌。这种情况并不总是发生。难道是排队的呼叫没有被排队吗?那里出了什么问题?
工具:我们使用四台运行 IIS 10 的专用服务器。我们有一个负载均衡器来平衡负载。在前端,我们使用 React Native 0.64.2。我们正在使用react-native-ssl-pinning来进行调用。
代码:所以这是代码
async request(route: string,
action: string,
params: any = null,
isJson: boolean = true,
) {
var options = await this.handleOption(action, params, isJson)
route = Config.API_BASE_URI + route
return new Promise(async resolve => {
try {
var response = await fetchSSLPinning(route, options)
return resolve(response)
} catch (e) {
// // console.log(e)
var originalRequest = {
route,
options,
}
var p = this.handleError(e, originalRequest)
return resolve(p)
}
})
},
处理错误的代码
async handleError(error: any, originalRequest: any): Promise<any> {
const errorResponse = error
if (this.isTokenExpiredError(errorResponse)) {
return this.resetTokenAndReattemptRequest(error, originalRequest)
}
this.dispatchError(true, errorResponse.status)
return Promise.reject(error)
},
获取新刷新令牌的代码
async resetTokenAndReattemptRequest(error: any, originalRequest: any) {
try {
const resetToken = await TokenStorage.getToken()
if (!resetToken) {
return Promise.reject(error)
}
const retryOriginalRequest = new Promise(resolve => {
this.addSubscriber((accessToken: string) => {
var options = {
...originalRequest.options,
headers: {
...originalRequest.options.headers,
authorization: 'Bearer ' + accessToken,
},
}
resolve(fetchSSLPinning(originalRequest.route, options))
})
})
if (!this.isAlreadyFetchingAccessToken) {
this.isAlreadyFetchingAccessToken = true
const refreshToken = await TokenStorage.getRefreshToken()
const userName = await TokenStorage.getUsername()
const response = await AuthenticationService.refreshToken(
refreshToken,
userName,
)
if (!response) {
this.isAlreadyFetchingAccessToken = false
this.dispatchError(true, '404')
return Promise.reject(error)
}
await TokenStorage.storeTokenInfo(
response.access_token,
response.refresh_token,
response.userName,
) // save the newly refreshed token for other requests to use
const newToken = response.access_token
this.isAlreadyFetchingAccessToken = false
this.onAccessTokenFetched(newToken)
}
return retryOriginalRequest
} catch (err) {
this.isAlreadyFetchingAccessToken = false
this.dispatchError(true, err.status)
return Promise.reject(err)
}
},
在订阅者中运行所有代码
onAccessTokenFetched(accessToken: string) {
// When the refresh is successful, we start retrying the requests one by one and empty the queue
this.subscribers.forEach((callback: any) => {
callback(accessToken)
})
this.subscribers = []
},
我确实知道很难找到解决方案,但还是把它放在那里,因为我们已经被这个问题困扰了很长时间。
听起来像是竞争条件(有时命令序列按预期进行,有时不是,意思是 - 有些命令在其他命令开始之前没有完全执行)
在对您的代码进行简短扫描后,我想我已经得到了一些东西
函数
fetchSSLPinning
是一个 async
函数(感谢下面的代码示例)
var response = await fetchSSLPinning(route, options)
但是,当从另一个函数(在承诺内)解析时,它的使用方式如下:
resolve(fetchSSLPinning(originalRequest.route, options))
让我们来分解一下:
const result = fetchSSLPinning(originalRequest.route, options)
resolve(result)
很容易看出
result
是一个Promise
,这不是解决promise的好方法。如果您打算做出一系列承诺,则首选使用 await
关键字(作为时间的函数更具可读性和可维护性)
我的猜测是你想等待结果,使用:
const result = await fetchSSLPinning(originalRequest.route, options)
resolve(result)
或者
fetchSSLPinning(originalRequest.route, options).then(resolve)
这可能是用例(并且可能 - 您的代码中还有更多这样的用例)
并且,是的 - 如果您在项目中使用
async
/ await
关键字,请尝试对齐其余部分,以保持一致性
希望这些信息有帮助