Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Setting up request cleanup middleware in expressjs

I am trying to write an expressjs server utilizing the postgresql as the backend. Each request starts by calling pg.connect to get a pooled connection (client) as well as the method to return it to the pool once the connection is no longer needed (done). For example:

  function dbConnect(req, res, next) {
    if (res.locals.pgCtx) {
      next();
      return;
    }

    pg.connect(dbConn, function (err, client, done) {
      if (err) {
        res.send(500, err.message);
      } else {
        app.locals.pgCtx = res.locals.pgCtx = {
          client: client,
          done: done
        };
        next();
      }
    });
  }

  app.use(allowCrossDomain);
  app.use(express.methodOverride());
  app.use(express.compress());
  app.use(express.bodyParser());
  app.use(express.logger());
  app.use(passport.initialize());
  app.use(express["static"](webRootDir));
  app.use(dbConnect);                       // <--------------
  app.use(authenticate);
  app.use(app.router);
  app.use(express.errorHandler({ dumpExceptions: true, showStack: true }));
  app.set('view engine', 'jade');
  app.set('views', webRootDir);
  app.engine("jade", jade.__express);

  indexHandlers = [fetchConstData, function (req, res) {
    res.render(templatesDir + 'index', {
      constData: app.locals.constData,
      env: app.get('env'),
      user: req.user,
      admin: isAdmin(req.user.role),
      host: req.host
    });
  }];
  app.get('/', indexHandlers);
  app.get('/index', indexHandlers);
  app.get('/index.html', indexHandlers);

My problem is that while I can insert dbConnect as the global middleware to be run before any other middleware for a request I also need to be able to cleanup after all the middleware is run in order to return the connection back to the pool.

Ideally, there should be a way to specify a global middleware to be run after all the request specific ones are run regardless of how the request ends - be it by:

  • res.send(...)
  • throwing an exception
  • passing err object to next()

Note, that any request specific middleware can terminate the chain in this way.

Right now I can see only this approach:

  1. Hook into the negative outcome by registering a custom global error handler middleware instead of express.errorHandler.
  2. Replace the res.send method in the res object by a custom version that returns the connection back to pool first and then proceeds to the original res.send implementation.

All of this has a strong smell of a hack. I would like to do it right and so I am asking is there a way to register something like a request cleanup middleware?

EDIT

The static content handler must be moved above the dbConnect middleware, otherwise we leak db connections until no more connections are available and the server is unable to serve anything because dbConnect never returns waiting for a connection to be released.

like image 951
mark Avatar asked Sep 13 '13 09:09

mark


1 Answers

There's a finish event that you can listen for on the response object and which will be emitted when the response has finished:

function dbConnect(req, res, next) {
  res.on('finish', function() {
    // perform your cleanups...
  });
  ...
}
like image 188
robertklep Avatar answered Oct 15 '22 22:10

robertklep