mcavage/node-restify — pull request #122 by trentm (2012-04-17 04:48:57+00:00)
Title: `Response._writeHead` saving can be overwritten -> infinite loop

Say I have a 'restbug' project:

```
[trentm@banana:~/tmp/restbug]$ ls
node_modules package.json restbug.js
[trentm@banana:~/tmp/restbug]$ cat package.json | json
{
"name": "restbug",
"version": "1.0.0",
"dependencies": {
"restify": "1.4"
}
}
[trentm@banana:~/tmp/restbug]$ cat restbug.js
var restify = require('restify');
//var a = require('a'); // We'll come to this later.
var server = restify.createServer({name: 'restbug'});
server.get('/', function (req, res, next) {
res.send('hi')
next();
});
server.listen(8080, function () {
console.log('listening')
});
```

Let's also apply this patch to "restify/lib/response.js" whereever that file
appears:

```
Index: /Users/trentm/tmp/restbug/node_modules/restify/lib/response.js
index d1881ff..27d5fce 100644
--- a/lib/response.js
+++ b/lib/response.js
@@ -218,8 +218,10 @@ module.exports = {
]

};
+console.log('%s: set _writeHead to writeHead: %s\n------------------', __filename, Response.prototype.writeHead);
Response.prototype._writeHead = Response.prototype.writeHead;
Response.prototype.writeHead = function restifyWriteHead() {
+ console.log('%s: here in writeHead', __filename)
if (this.code !== undefined)
this.statusCode = this.code;
```

Running this works as expected:

```
[trentm@banana:~/tmp/restbug]$ node restbug.js
/Users/trentm/tmp/restbug/node_modules/restify/lib/response.js: set _writeHead to writeHead: function (statusCode) {
var reasonPhrase, headers, headerIndex;

if (typeof arguments[1] == 'string') {
reasonPhrase = arguments[1];
...
}
------------------
listening
```

And responds to requests as expected:

```
$ curl -Ss http://localhost:8080/
"hi"
```

Now say that the "restbug" project has another dependency "a", which itself
also depends on restify and importantly **has its own copy of the restify
module** (whether by accident of npm install order with dep changes,
explicitly, slight version dep differences or whatever):

```
[trentm@banana:~/tmp/restbug/node_modules/a]$ cat package.json | json
{
"name": "a",
"version": "1.0.0",
"dependencies": {
"restify": "1.4"
}
}
[trentm@banana:~/tmp/restbug/node_modules/a]$ ls node_modules/
restify
[trentm@banana:~/tmp/restbug/node_modules/a]$ cat index.js
console.log("%s: load", __filename);
var restify = require('restify');
[trentm@banana:~/tmp/restbug/node_modules/a]$ cd ../..
[trentm@banana:~/tmp/restbug]$ cat restbug.js
var restify = require('restify');
var a = require('a'); // <---- this is new
var server = restify.createServer({name: 'restbug'});
server.get('/', function (req, res, next) {
res.send('hi')
next();
});
server.listen(8080, function () {
console.log('listening')
});
```

Now when we run it **http.ServerResponse._writeHead set twice**, first with the intended function then with the **first loaded restify replacement**:

```
[trentm@banana:~/tmp/restbug]$ node restbug.js
/Users/trentm/tmp/restbug/node_modules/restify/lib/response.js: set _writeHead to writeHead: function (statusCode) {
var reasonPhrase, headers, headerIndex;

if (typeof arguments[1] == 'string') {
reasonPhrase = arguments[1];
...
this._storeHeader(statusLine, headers);
}
------------------
/Users/trentm/tmp/restbug/node_modules/a/index.js: load
/Users/trentm/tmp/restbug/node_modules/a/node_modules/restify/lib/response.js: set _writeHead to writeHead: function restifyWriteHead() {
console.log('%s: here in writeHead', __filename)
if (this.code !== undefined)
this.statusCode = this.code;

if (this.statusCode === 204 || this.statusCode === 304) {
...
this._writeHead.apply(this, arguments);
this.emit('header');
}
------------------
listening
```

... resulting in an infinite loop on handling a request:

```
/Users/trentm/tmp/restbug/node_modules/a/node_modules/restify/lib/response.js: here in writeHead
/Users/trentm/tmp/restbug/node_modules/restify/lib/response.js: here in writeHead
/Users/trentm/tmp/restbug/node_modules/restify/lib/response.js: here in writeHead
/Users/trentm/tmp/restbug/node_modules/restify/lib/response.js: here in writeHead
...
/Users/trentm/tmp/restbug/node_modules/restify/lib/response.js: here in writeHead
/Users/trentm/tmp/restbug/node_modules/restify/lib/response.js: here in writeHead
/Users/trentm/tmp/restbug/node_modules/restify/lib/response.js: here in writeHead

node.js:520
NativeModule.getCached = function(id) {
^
RangeError: Maximum call stack size exceeded
```