问题描述
我在App Engine上部署了一个Node.js服务,该服务使用Dialogflow履行库。情况是这样的:我有一个异步函数,该函数使用Secret Manager检索凭据,并使用该信息调用调用API的API,该URL实例和令牌返回。这是服务器到服务器的身份验证(OAuth),因此所有访问它的用户都相同。我在全局变量中设置这些值,如下所示:
let globalUser = "";
let globalPass = "";
...
async function credentials() {
const credentials = await secretsInstance.getCredentials();
const parsedCredentials = JSON.parse(credentials);
const user = parsedCredentials.user;
const pass = parsedCredentials.pass;
//setting the values to the global variables
globalUser = user;
globalPass = pass;
//call the authentication API - in the callback I set other global variables
await authApiInstance.authenticate(user,pass,callback);
}
调用回调函数后,我将实例url和令牌设置为全局变量。
令牌每20分钟过期一次,因此我需要对其进行更新。为此,我调用了setInterval函数,在其中我调用了authApiInstance.authenticate(...)
这里的问题是,当接收到来自Dialogflow的POST请求时,我需要调用另一个需要该URL的API,该URL在此阶段首次为空,因此会抛出ECONNREFUSED。然后,如果我其他时间呼叫服务器,则会设置该变量。
GCP中的日志如下:
2020-08-14 23:29:49.078 BRT
"Calling the loadQuestions API
2020-08-14 23:29:49.078 BRT
"The url is: /services/…
2020-08-14 23:29:49.091 BRT
"CATCH: Error: connect ECONNREFUSED 127.0.0.1:80"
2020-08-14 23:29:49.268 BRT
dialogflowGatewayProdxjmztxaet4d8Function execution took 764 ms,finished with status code:
200
2020-08-14 23:29:49.278 BRT
{ message_id: '39045207393',status: 200 }
2020-08-14 23:29:49.289 BRT
"Credentials ok"
2020-08-14 23:29:49.976 BRT
"Url set"
可以看出,凭据和URL是在调用API后设置的,因此它没有可成功进行调用的URL。
我可以在POST中调用该函数,每次有一个保证它永远存在的请求时,但是性能会丢失,尤其是处理必须快速的聊天机器人。
我还尝试了预热方法,从理论上讲,该方法将在部署和更改实例时调用(但不能像docs那样调用它):
app.get('/_ah/warmup',(req,res) => {
credentials();
});
我该如何处理?我对Node.js和服务器世界还很陌生。
谢谢
解决方法
credentials();
本身。无需做快递。我的问题是共享凭据上的竞赛条件。
一个粗略的例子,假设事件循环在队列中只有这些脚本: 假设您有2个并发用户A和B。一个请求并发现凭据到期,这又请求新的凭据。从A请求返回凭据之前的B请求,后者又请求另一个凭据。基于节点eventloop,A然后获取credential_A,B将获取凭据B。如果您的第三方仅允许单个凭据,则A会从api调用中获取错误。
因此,方法是将与凭证相关的任务转发到一个管理凭证的模块。后台任务或请求(获取令牌将在请求时过期)将面临相同的比赛问题。由于节点没有线程上下文,因此很简单。
let credential = {}
let isUpdating = false;
const _updateCrediental = (newCrediential){
//map here
}
const _getCredential = async()=> {
try{
if(!updating){
updating = true;
const newCrediential = await apiCall();
updateCrediential(newCrediential);
updating = false;
return credential;
}else{
return false;
}
}catch(err){
throw err;
}
}
export.getCredential = ()=>{
if(credentialIsValid()){
return credential;
}
return __getCredential();
}
/// check the return if it promise type then waaait for it if its false then wait for certain time and check again.
对此的一种改进是使用事件代替超时。
我本人更喜欢使用数据库,并且您可能还希望记录凭证生成。大多数数据库承诺进行某种类型的事务处理或锁定。 (感觉更安全)