Curtis
Curtis

Reputation: 2704

stop variables being overwritten in NodeJS & Express

Okay so currently I've got a ExpressJS API that I've wrote that works alongside Puppeteer however if I send concurrent requests then if request 1 updates device_imei then request 2 will get that value instead of it's own state?

My script looks like so:

let proxy;
let recaptcha_solution;
let page;
let browser = false;
let device_imei;
let domains;
let token;
let userProfile;
let tokenRefId;
let requestNo;
let success = true;
let errorCode;
let emails;
let errorMessage;
let requestId;
let bitmap;
let validationId;

const handle_response = async (response) => {
    const url = response.url();
    try {
        const req = response.request();
        const orig = req.url();
        let status;
        let text;
        if(response.status) {
            status = response.status();
        }
        if(
            status
            && !(status > 299 && status < 400)
            && !(status === 204)
            && (req.resourceType() === 'xhr')
        ) {
            text = await response.text();
            if(text.includes('Access Denied')) {
                success = false;
                return;
            }
            const json = JSON.parse(text);
            if(json['reCaptchaDetail']) {
                if(json['reCaptchaDetail']['token'] && json['reCaptchaDetail']['tokenRefId']) {
                    token = json['reCaptchaDetail']['token'];
                    tokenRefId = json['reCaptchaDetail']['tokenRefId'];
                    await client.set('captcha_solution', JSON.stringify(json['reCaptchaDetail']), 'EX', 3600);
                }
            } else if(json['error']) {
                success = false;
                if(json['error']['errorId'].startsWith('UNLOCK')) {
                    errorCode = json['error']['details'][0]['code'];
                    errorMessage = json['error']['details'][0]['message'];
                }
            }
        }
    } catch (err) {
        console.error(`Failed getting data from: ${url}`);
        console.error(err);
    }
};

const createOrder = async (req, res) => {
    let params = await parser.parseStringPromise(req.body.parameters);

    device_imei = params.PARAMETERS.IMEI;

    //config
    axios.defaults.withCredentials = true;
    axios.defaults.timeout = 15000;
    userProfile = faker.entity.user();

    page = await browser.newPage();

    page.on('response', handle_response);

    //a lot more stuff that accesses/updates all the let vars and working with page var.
}

app.post('/api/index.php', async function (req, res) {
    res.setHeader('Content-Type', 'application/json; charset=utf-8');
    res.setHeader('X-Powered-By', 'DHRU-FUSION');
    res.setHeader('dhru-fusion-api-version', '6.1');
    res.removeHeader('pragma');
    res.removeHeader('server');
    res.removeHeader('transfer-encoding');
    res.removeHeader('cache-control');
    res.removeHeader('expires');

    if (Object.keys(req.body).length === 0) {
        return res.send(error_response('Invalid Request'));
    }

    if (typeof req.body.apiaccesskey === undefined || req.body.apiaccesskey !== process.env.SITE_API_KEY) {
        return res.send(error_response('Invalid API Key'));
    }

    switch(req.body.action) {
        case "placeimeiorder":
            createOrder(req, res).catch(function ignore() {});
            return res.send(success_response({
                'MESSAGE': 'Order Received!'
            }));
        default:
            return res.send(error_response('Invalid Action'));
    }
});

app.listen(3000);

Would I have to implement mutli-threading so my variables can maintain state? or is their a much easier solution?

Upvotes: 0

Views: 752

Answers (1)

Klaycon
Klaycon

Reputation: 11070

You're having issues due to making all of your variables top-level, as if they're static or global. But really the variables each depend on a specific execution of your handler function, so they need to be declared in the function! If you're struggling with passing the variables to your handle_response function, I recommend containing them all into an object and passing that on. Something like this:

const handle_response = async (response, state) => {
    const url = response.url();
    try {
        const req = response.request();
        const orig = req.url();
        let status;
        let text;
        if(response.status) {
            status = response.status();
        }
        if(
            status
            && !(status > 299 && status < 400)
            && !(status === 204)
            && (req.resourceType() === 'xhr')
        ) {
            text = await response.text();
            if(text.includes('Access Denied')) {
                state.success = false;
                return;
            }
            const json = JSON.parse(text);
            if(json['reCaptchaDetail']) {
                if(json['reCaptchaDetail']['token'] && json['reCaptchaDetail']['tokenRefId']) {
                    state.token = json['reCaptchaDetail']['token'];
                    state.tokenRefId = json['reCaptchaDetail']['tokenRefId'];
                    await client.set('captcha_solution', JSON.stringify(json['reCaptchaDetail']), 'EX', 3600);
                }
            } else if(json['error']) {
                state.success = false;
                if(json['error']['errorId'].startsWith('UNLOCK')) {
                    state.errorCode = json['error']['details'][0]['code'];
                    state.errorMessage = json['error']['details'][0]['message'];
                }
            }
        }
    } catch (err) {
        console.error(`Failed getting data from: ${url}`);
        console.error(err);
    }
};

const createOrder = async (req, res) => {
    let params = await parser.parseStringPromise(req.body.parameters);
    let state = {}; //put all your variables as properties of this object    

    state.device_imei = params.PARAMETERS.IMEI;

    //config
    axios.defaults.withCredentials = true;
    axios.defaults.timeout = 15000;
    state.userProfile = faker.entity.user();

    let page = await browser.newPage(); //make things local if they dont need to be passed

    page.on('response', response => handle_response(response, state));

    //a lot more stuff that accesses/updates all the object properties and working with page var.
}

To reiterate: When building any asynchronous application, absolutely do not reuse the same global variables in your functions... as you've seen, it just leads to their values being overwritten by a concurrent request. Keep all your variables locally scoped to the scope of the request, and pass them along to any functions that need them.

Upvotes: 1

Related Questions