Setting up request cleanup middleware in expressjs
Asked Answered
B

1

7

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?

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.

Bustos answered 13/9, 2013 at 9:56 Comment(1)
This one is a real bummer for me. Anyone?Bustos
I
15

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...
  });
  ...
}
Inset answered 19/9, 2013 at 13:52 Comment(1)
Looks good. There is only one nuance - when using server sent events the response survives multiple requests and the connection has to be returned to pool explicitly, because the 'finish' event is irrelevant. Other than that, this is exactly what I was looking for.Bustos

© 2022 - 2024 — McMap. All rights reserved.