parallel requests to node.js, connect-mongo, sessi

2020-07-14 10:15发布

问题:

In a current project (kind of a shop system), I use node.js with expressJS and connect-mongo as session store. On client side, I use a single request at startup to create a new session and send multiple parallel requests to the node.js server afterwards. Because these parallel requests change the session, those changes seem to overwrite each other, although they change different objects of the session, of course.

Example (all 3 requests start at the same time):

  • Request A pushes some products to the array req.session.productHist['abc']
  • Request B pushes products to req.session.productHist['def']
  • Request C takes some time, but doesn't change the session

Because request C finishes after request A and B, but starts before they finished, it seems to overwrite session.productHist with the value it held when request C started (null).

How can I fix this?

Update:

Some example code with console output:

var url = require('url'),
    express = require('express'),
    MongoStore = require('connect-mongo');

var aDay = 24*60*60*1000;

var app = express.createServer();

app.configure(function(){
  app.use(express.cookieParser());
  app.use(express.session({
    secret: "secret",
    store: new MongoStore({ db: 'lmsCache' }),
    maxAge: aDay
    })
  );
  app.use(express.methodOverride());    app.use(express.bodyParser());
  app.use(express.errorHandler({ dumpExceptions: true, showStack: true }));
  app.use(app.router);
  app.use(express.logger());
});


function sendStringified(req, res, data) {
    data = JSON.stringify(data);
    if (req.url_query.callback) { data = req.url_query.callback + "(" + data + ");"; }
    res.send(data);
}

function parseParams(req,res,next) {
  req.url_query = url.parse(req.url,true).query;
  next();
}

function doExpensiveStuff(req,res,next) {
  console.log("######################### init start");
  [...]
}


app.get('/init', parseParams, doExpensiveStuff, function(req,res) {
  console.log("init: session.productHist: " + JSON.stringify(req.session.productHist));
  console.log("######################### init end");
  sendStringified(req,res,null);
});


app.get('/products', parseParams, function(req,res) {

  console.log("######################### products "+req.url_query.category+" start");

  if(!req.session.productHist[req.url_query.category])
    req.session.productHist[req.url_query.category] = [];

  for(var i=0;i<2;i++) {
      req.session.productHist[req.url_query.category].push({ "id": new Date().toGMTString() }); 
  } 

  console.log("products: session.productHist: " + JSON.stringify(req.session.productHist));
  console.log("######################### products "+req.url_query.category+" end");
  sendStringified(req,res,[]);
});

app.get('/newSession', parseParams, function(req,res) {
  console.log("######################### newSession");
  req.session.productHist = {};
  sendStringified(req,res,true);
});  

app.listen(8080);  

time = new Date().toGMTString();  

console.log('Server starting at: ' + time);  

Console log:

Server starting at: Thu, 15 Dec 2011 15:50:37 GMT

################### newSession
################### init start
################### products -1 start

products: session.productHist: {"-1":[{"id":"Thu, 15 Dec 2011 15:50:40 GMT"},{"id":"Thu, 15 Dec 2011 15:50:40 GMT"}]}

################### products -1 end


init: session.productHist: {}

################### init end


[...]

################### products -1 start

products: session.productHist: {"-1":[{"id":"Thu, 15 Dec 2011 15:50:53 GMT"},{"id":"Thu, 15 Dec 2011 15:50:53 GMT"}]}

################### products -1 end

回答1:

I think I have found the answer to this tricky problem.

From the Express.js documentation:

Properties on req.session are automatically saved on a response

Short version

When you set a session variable (req.session.my_var = value), it's not actually saved then (at that exact moment), but later (when you send the response, in your case it's when you do res.send). That caused your problem.

Long version

So what does that mean exactly?

  1. You make a request, then get the session variable (which is in state A) and after that do something with it (that requires some time)
  2. You make another request and get the session variable (which is still in state A because the response hasn't been sent yet) and change some stuff to it
  3. Now the session processing etc has been done so you send the response from 1), thus modifying the session variable and bringing it into state B
  4. Here comes the 'fun' part, after you sent the response from 2). Now you aren't actually modifying the current session (which has been updated into state B), because the response was delayed, so what you're actually changing is the session from state A, bringing it into state C => which means all the modifications from state B were lost!