Nodejs гарантирует, что вход в API происходит только один раз - PullRequest
0 голосов
/ 27 июня 2019

Я написал следующий код для Node.js для подключения к API моей CMS.API должен войти в систему один раз с именем пользователя и паролем, а затем приступить к выполнению запросов с токеном доступа.Однако токен доступа имеет ограниченное время, в течение которого он действителен - после этого API должен снова войти в систему и получить новый токен.

Когда я выполняю запросы к API, я всегда пытаюсь использовать текущий токен- если он больше не действителен, я получу 403 и инициирую новый вход в систему, после чего начальный запрос будет выполнен снова.

Проблема здесь в том, если несколько запросов одновременно пытаются получить доступ кAPI, все они инициируют вход в систему - что приводит к нескольким сеансам API в CMS, что плохо.Я думал, что этот случай был обработан с помощью Promises, но, видимо, мой код не работает так, как я себе представлял.

var api = {
    url: 'servername',
    user: 'user',
    password: 'password',
    token: null
};
var login_in_progress = true;
var loginPromise;
function loginApi() {
    login_in_progress = true;
    return new Promise(function (resolve, reject) {
        request({
            uri: api.url + api.user + ',' + api.password,
            method: 'GET',
            json: true
        }, function (err, res, data) {
            if (!err && res.statusCode === 200) {
                api.token = data.token;
                login_in_progress = false;
                resolve(data);
            } else {
                login_in_progress = false;
                reject(err);
            }
        });
    });
}

var getContent = function (query) {
    // Currently a login is running - wait till it's finished and then execute get
    // at least that was the idea - but does not seem to work
    if (login_in_progress && loginPromise) {
        return new Promise(function (resolve, reject) {
            loginPromise
                .then(function () {
                    getContent(query)
                        .then(function (data) {
                            resolve(data);
                        })
                        .catch(function (err) {
                            reject(err);
                        });
                })
                .catch(function (err) {
                    reject(err);
                });
        });
    } else {
        // Do the actual request
        // case 403 Api is logged out => reLogin
        loginPromise = loginApi();
        loginPromise
            .then(function () {
                getContent(query)
                    .then(function (data) {
                        resolve(data);
                    })
                    .catch(function (err) {
                        reject(err);
                    });
            })
            .catch(function (err) {
                reject(err)
            });
    }
}

Очевидно, проверка того, что вход в систему в данный момент не выполняется, не происходит, и функция getContent всегданаталкивается на утверждение else.Я даже не уверен, сработает ли моя идея проверить работающий запрос на вход в систему.

Ответы [ 3 ]

1 голос
/ 28 июня 2019

Было несколько вещей, которые можно исправить или упростить в вашем коде.Здесь я смоделировал вход в систему и выход из нее с помощью переменной testlogged, которая заменяет ответ 403 в исходном коде, и задержку входа в систему с тайм-аутом, просто чтобы продемонстрировать, как вы можете сделать это с минимальным кодом.

ПРИМЕЧАНИЯ:

  • Кажется, вам не нужны эти вложенные обещания в getContent.Не совсем понятно, использует ли эта функция другую функцию для получения содержимого, которое возвращает обещание, или же оно должно само возвращать обещание.Это не одно и то же.Для второго случая вы можете увидеть пример 2.
  • Вы должны быть последовательны в отношении того, что возвращает getContent, знать, что вы хотите быть обещанием.Либо вы передаете обратный вызов getContent (пример 1), либо используете для него then (пример 2).
  • В примере 1 довольно просто вызвать getContent внутри себя, но если он возвращаетобещание, которое я бы предпочел не делать, потому что оно приносит дополнительную сложность, заставляя использовать then внутри него самой функции.
  • Например, 2 гораздо понятнее, если getContent возвращает новое обещание с самого начала,который охватывает весь код

ПРИМЕР 1

var api = {
    url: 'servername',
    user: 'user',
    password: 'password',
    token: null
};
var testlogged = false;
var login_in_progress = true;
var loginPromise;
function loginApi() {
    login_in_progress = true;
    return new Promise(function (resolve, reject) {
        setTimeout(function(){
            console.log('login OK');
            login_in_progress = false;
            testlogged = true;
            resolve();
        }, 10);
    });
}

function getContent(query, callback) {
    // Currently a login is running - wait till it's finished and then execute get
    if (login_in_progress && loginPromise) {
        console.log('login in progress, waiting before getting content.. query: ' + query);
        loginPromise
            .then(function () {
                callback('got content OK (1) query: ' + query);
            })
            .catch(function (err) {
                console.error('error: ', err);
            });
    } else {
        // Do the actual request
        if (!testlogged) {
            // case 403 Api is logged out => reLogin
            console.log('starting login.. query: ' + query);
            loginPromise = loginApi();
            //restarting function to put query in queue, saves code, but you could also do directly loginPromise.then(function () {...
            getContent(query, callback);
        } else {
            console.log('already logged');
            callback('got content OK (2) query: ' + query);
        }
    }
}

var theCallback = function(data){
    console.log(data);
};

//doing first attempt while not logged
getContent('test', theCallback);
//trying with a concurrent attempt while still logging
getContent('test2', theCallback);
//simulating attempt while still being logged then after logout (waiting first that the 2 precendent attempts are finished)
setTimeout(function(){
    getContent('test3', theCallback);
}, 100);
setTimeout(function(){
    console.log('simulate log out');
    testlogged = false;
    getContent('test4', theCallback);
}, 100);

ПРИМЕР 2

var api = {
    url: 'servername',
    user: 'user',
    password: 'password',
    token: null
};
var testlogged = false;
var login_in_progress = true;
var loginPromise;
function loginApi() {
    login_in_progress = true;
    return new Promise(function (resolve, reject) {
        setTimeout(function(){
            console.log('login OK');
            login_in_progress = false;
            testlogged = true;
            resolve();
        }, 10);
    });
}

var getContent = function (query) {
    return new Promise(function (resolve, reject) {
        // Currently a login is running - wait till it's finished and then execute get
        if (login_in_progress && loginPromise) {
            console.log('login in progress, waiting before getting content.. query: ' + query);
            loginPromise
                .then(function () {
                    resolve('got content OK (1) query: ' + query);
                })
                .catch(function (err) {
                    reject(err);
                });
        } else {
            // Do the actual request
            if (!testlogged) {
                // case 403 Api is logged out => reLogin
                console.log('starting login.. query: ' + query);
                loginPromise = loginApi();
                loginPromise
                    .then(function () {
                        resolve('got content OK (1) query: ' + query);
                    })
                    .catch(function (err) {
                        reject(err);
                    });
            } else {
                console.log('already logged');
                resolve('got content OK (2) query: ' + query);
            }
        }
    });
}

var theCallback = function(data){
    console.log(data);
};

//doing first attempt while not logged
getContent('test').then(theCallback);
//trying with a concurrent attempt while still logging
getContent('test2').then(theCallback);
//simulating attempt while still being logged then after logout (waiting first that the 2 precendent attempts are finished)
setTimeout(function(){
    getContent('test3').then(theCallback);
}, 100);
setTimeout(function(){
    console.log('simulate log out');
    testlogged = false;
    getContent('test4').then(theCallback);
}, 150);
1 голос
/ 27 июня 2019

Я полагаю, что что-то подобное может сработать (примечание: это действительно "псевдокод", а не исполняемый код):

let authPromise = undefined;

function getContent(query) {
    if (!authPromise) {
        // no login pending - start one, then restart the whole function
        authPromise = login();
        return authPromise.then(() => getContent(query));
    } else {
        // login pending - wait for it, then make the real call
        // if the login promise already resolved, it won't wait
        return authPromise.then(() => makeAjaxRequest(query)).then((response) => {
            if (response.status === 403) {
                // session expired - remove the state, and restart the whole function
                authPromise = undefined;
                return getContent(query);
            } else {
                // session still valid, return the API response
                return response;
            }
        });
    }
}

с единственным состоянием, состоящим в наличии Promise.

0 голосов
/ 28 июня 2019

Итак, в то время как ответы помогли мне больше думать о проблеме, реальная проблема была другой: Я проверял только login_inprogress, когда getContent был запущен, а не когда 403 вернулся. Это вызвало несколько входов API, когда запросы запускались одновременно.

Решение на самом деле было довольно простым:

// case 403 Api is logged out => reLogin
// check again if login is running NOW, only if not, start one
if(!login_in_progress){
    loginPromise = loginApi();
}
// Always attach to the loginPromise to redo the initial getContent
loginPromise
    .then(function () {
        getContent(query)
            .then(function (data) {
                resolve(data);
            })
            .catch(function (err) {
                reject(err);
            });
    })
    .catch(function (err) {
        reject(err)
    });
...