4
\$\begingroup\$

This question is related to Express under Node.js.

Background

With Express, you stack up several "middleware" modules to handle a specific HTTP request.

app.use(express.logger());
app.use(express.favicon()));
app.use(express.cookieParser());
app.use(express.session());
app.use(app.router);

When a request comes in, Express goes through all of the functions in the stack until a module handles the request. Specifically, each middleware module is a function (req, res, next), and that function will call next() to call the next module in the stack. Functions can modify data in the req or res objects if they choose.

The Problem

I was interested in making custom log middleware modules for my Express application, and I needed to use more than one module at a time. The data I need to log cannot be logged until the response has been sent to the client. (I am tracking bytes sent, and the amount of time a connection was established.) Unfortunately, Node.js's HTTP response object does not raise an end event. Presumably, this is because it is up to the application to do any "end" work prior to calling .end() on the response object in the first place. Express does not expose a hook prior to .end() being called.

Express/Connect's built-in logger middleware is able to track how long a request was running, so I decided to see what method they use. They actually replace the res.end function with their own. When their own res.end function is called, it swaps the old res.end back in place, finishes logging, then calls res.end with the parameters that their own res.end was called with. Their method works, but can be problematic when a handful of modules try to do this. (The order in which res.end function replacement occurs becomes critical.)

My Solution

I have used their method to patch res to fire end events so that any module in the chain can subscribe to that event in the usual way.

function (req, res, next) {
    var end = res.end;

    res.end = function () {
        res.end = end;
        res.emit('end');
        res.end.apply(this, arguments);
    }

    next();
}

Then, all my logger module has to do is:

res.on('end', function () { /* log here */ });

Is there any problem with what I have done? Is there a better way to do it?

\$\endgroup\$

2 Answers 2

3
\$\begingroup\$

I did the following for something similar. This uses the closure to create a chain-of-command pattern.

function (req, res, next) {
  var realEnd = res.end;
  res.end = function () {
    var realEndArgs = arguments;
    req.log.info ("HTTP RESPONSE CODE " + res.statusCode);
    realEnd.apply (res, realEndArguments);
  }
  next();
}

You could instead of logging the status code, do res.emit('end') as you described, or anything else, before returning control to the original res.end (which may itself be a replacement). Or, you could do directly in this function whatever you want to do in response to the end event.

Note: I explicitly store res.end's arguments in realEndArguments in case I later add anything asynchronous, because arguments would then refer to the asynchronous action's callback's arguments instead of res.end's arguments. A subtle bug I ran into at first.

\$\endgroup\$
0
\$\begingroup\$

I had similar problem and published the package express-end to solve this limitation. The code is quite similar to Brad's solution with additional check.

var endMw = function(req, res, next) {

  if (!res._orig_end_handler) {

    res._orig_end_handler = res.end;

    res.end = function () {
      res.end = res._orig_end_handler;
      res.emit('end');
      res.end.apply(this, arguments);
    };

  } else {
    debug('Warning: res.end() function is already overridden');
  }

  next();

};

Hope, the package will save some time to somebody else.

Upd: As this code is utility, to be used in the package and may be reused by other packages, I assume the check is needed to prevent double patching of res.end() if the middleware is applied twice. In order to do that, I store original handler in property of res object, not in the closure.

\$\endgroup\$
1
  • 1
    \$\begingroup\$ Please add some explanation to explain how your code is an improvement over the original solution. \$\endgroup\$
    – Quill
    Commented Mar 10, 2016 at 9:13

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.