Значения глобальных переменных Node.js отсутствуют в App Engine

#node.js #google-app-engine #global #dialogflow-es-fulfillment

#node.js #google-app-engine #глобальные #dialogflow-es-выполнение

Вопрос:

У меня есть Node.js служба, развернутая в App Engine, которая использует библиотеку выполнения Dialogflow. Сценарий таков: у меня есть асинхронная функция, которая извлекает учетные данные с помощью Secret manager и, используя эту информацию, вызывает 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(...)

Проблема здесь в том, что при получении запроса POST, поступающего из Dialogflow, мне нужно вызвать другой 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 каждый раз, когда поступает запрос, чтобы гарантировать, что она всегда будет существовать, но производительность была бы потеряна, особенно при работе с чат-ботами, которые должны быть быстрыми.

Я также попробовал подход с прогревом, при котором теоретически он вызывался бы при развертывании и изменении экземпляра (но его нельзя было вызвать, как в документах):

 app.get('/_ah/warmup', (req, res) => {
   credentials();
});
  

Как я мог бы подойти к этому? Я довольно новичок в Node.js и в мире серверов.

Спасибо

Ответ №1:

credentials(); само по себе. нет необходимости делать это в express. Проблема, с которой я столкнулся бы, заключается в состоянии гонки для общих учетных данных.

грубый пример, предполагающий, что цикл событий имеет в очереди только эти скрипты: допустим, у вас есть 2 одновременных пользователя A и B. Запрос и обнаружение истечения срока действия учетных данных, которые, в свою очередь, запрашивают новые учетные данные. Запрос B перед возвратом учетных данных из запроса, который, в свою очередь, запрашивает другие учетные данные. На основе node 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.
   

Улучшением этого было бы использование event to вместо использования timeout.

Я бы сам предпочел работать с базой данных, а также, возможно, вы захотите также зарегистрировать генерацию учетных данных. Большинство баз данных обещают определенный вид транзакции или блокировки. (так безопаснее)