-
-
Notifications
You must be signed in to change notification settings - Fork 243
Adding callback to res.write and res.end for streaming support #80
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 16 commits
ed51c3a
f9c95f9
c4ba4af
bd60b96
5cd1fe2
2b85837
31792c1
bda469c
5f122e8
eb1bad8
ac8914b
866775d
c1f3aab
b0055d2
3ab2f0d
79c6b75
0f617c7
4b50f90
2e995d0
74fdf7b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -60,10 +60,11 @@ function compression (options) { | |
var length | ||
var listeners = [] | ||
var stream | ||
var noop = function () {} | ||
|
||
var _end = res.end | ||
res._end = res.end | ||
res._write = res.write | ||
var _on = res.on | ||
var _write = res.write | ||
|
||
// flush | ||
res.flush = function flush () { | ||
|
@@ -74,25 +75,29 @@ function compression (options) { | |
|
||
// proxy | ||
|
||
res.write = function write (chunk, encoding) { | ||
res.write = function write (chunk, encoding, cb) { | ||
if (ended) { | ||
return false | ||
} | ||
|
||
cb = (res._write.length === 3) ? cb : null | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think this is going yo be robust, for example, I see the following pretty frequently:
This is a very common pattern to wrap some function, but the length of the function ends up being zero, which means this module won't send the callback (and the wrapping may only be for certain requests, complicating debugging, which would point to "when I use compression, my callbacks sometimes do not work. Removing compression works fine"). Thoughts? |
||
|
||
if (!this._header) { | ||
this._implicitHeader() | ||
} | ||
|
||
return stream | ||
? stream.write(new Buffer(chunk, encoding)) | ||
: _write.call(this, chunk, encoding) | ||
? stream.write(new Buffer(chunk, encoding), cb) | ||
: res._write.call(this, chunk, encoding, cb) | ||
} | ||
|
||
res.end = function end (chunk, encoding) { | ||
res.end = function end (chunk, encoding, cb) { | ||
if (ended) { | ||
return false | ||
} | ||
|
||
cb = (res._end.length === 3) ? cb : null | ||
|
||
if (!this._header) { | ||
// estimate the length | ||
if (!this.getHeader('Content-Length')) { | ||
|
@@ -103,16 +108,16 @@ function compression (options) { | |
} | ||
|
||
if (!stream) { | ||
return _end.call(this, chunk, encoding) | ||
return res._end.call(this, chunk, encoding, cb) | ||
} | ||
|
||
// mark ended | ||
ended = true | ||
|
||
// write Buffer for Node.js 0.8 | ||
return chunk | ||
? stream.end(new Buffer(chunk, encoding)) | ||
: stream.end() | ||
? stream.end(new Buffer(chunk, encoding), null, cb) | ||
: stream.end(null, null, cb) | ||
} | ||
|
||
res.on = function on (type, listener) { | ||
|
@@ -202,13 +207,13 @@ function compression (options) { | |
|
||
// compression | ||
stream.on('data', function onStreamData (chunk) { | ||
if (_write.call(res, chunk) === false) { | ||
if (res._write.call(res, chunk) === false) { | ||
stream.pause() | ||
} | ||
}) | ||
|
||
stream.on('end', function onStreamEnd () { | ||
_end.call(res) | ||
res._end.call(res) | ||
}) | ||
|
||
_on.call(res, 'drain', function onResponseDrain () { | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -676,6 +676,76 @@ describe('compression()', function () { | |
.end() | ||
}) | ||
}) | ||
|
||
describe('when callbacks are used', function () { | ||
it('should call the passed callbacks in the order passed when compressing', function (done) { | ||
var hasCallbacks = false | ||
var callbackOutput = [] | ||
var server = createServer(null, function (req, res) { | ||
hasCallbacks = (res._write.length === 3 && res._end.length === 3) | ||
res.setHeader('Content-Type', 'text/plain') | ||
res.write('Hello', null, function () { | ||
callbackOutput.push(0) | ||
}) | ||
res.write(' World', null, function () { | ||
callbackOutput.push(1) | ||
}) | ||
res.end(null, null, function () { | ||
callbackOutput.push(2) | ||
}) | ||
}) | ||
|
||
request(server) | ||
.get('/') | ||
.set('Accept-Encoding', 'gzip') | ||
.expect('Content-Encoding', 'gzip') | ||
.end(function (err) { | ||
if (err) { | ||
throw new Error(err) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You don't want to wrap an error ina new error, because it looses the original stack trace. It is good practice to test your error handling code to see that it works, just like the normal code. This should simply be passing err to the done function. |
||
} | ||
if (hasCallbacks) { | ||
assert.equal(callbackOutput.length, 3) | ||
assert.deepEqual(callbackOutput, [0, 1, 2]) | ||
} | ||
done() | ||
}) | ||
}) | ||
|
||
it('should call the passed callbacks in the order passed when not compressing', function (done) { | ||
var hasCallbacks = false | ||
var callbackOutput = [] | ||
var server = createServer(null, function (req, res) { | ||
hasCallbacks = (res._write.length === 3 && res._end.length === 3) | ||
res.setHeader('Cache-Control', 'no-transform') | ||
res.setHeader('Content-Type', 'text/plain') | ||
res.write('hello,', null, function () { | ||
callbackOutput.push(0) | ||
}) | ||
res.write(' world', null, function () { | ||
callbackOutput.push(1) | ||
}) | ||
res.end(null, null, function () { | ||
callbackOutput.push(2) | ||
}) | ||
}) | ||
|
||
request(server) | ||
.get('/') | ||
.set('Accept-Encoding', 'gzip') | ||
.expect('Cache-Control', 'no-transform') | ||
.expect(shouldNotHaveHeader('Content-Encoding')) | ||
.end(function (err) { | ||
if (err) { | ||
throw new Error(err) | ||
} | ||
if (hasCallbacks) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The test when hasCallbacks is false is flawed. Instead of verifying that no callbacks were called, it does not checks at all, basically it's as if the test didn't even run. You need to add tests for that condition as well. Probably assert that callbackOutput is empty, as expected. |
||
assert.equal(callbackOutput.length, 3) | ||
assert.deepEqual(callbackOutput, [0, 1, 2]) | ||
} | ||
done() | ||
}) | ||
}) | ||
}) | ||
}) | ||
|
||
function createServer (opts, fn) { | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You cannot store the references as res._end and _write. There is no code where that check nothing is being overwritten, for example. Using this module twice will no longer function, etc.
Doing this causes too many bugs to be able to accept. Why did you have to change this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You're right that doing this isn't going to work, but I documented why I did it in the comment just above this one.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, sorry, I missed that. Unfortunately that is not an acceptable solution, due to the bugs it is causing from actual real-world apps I dropped your PR into in the last few hours. There is other code from npm that has written to those variables and responses are hanging or causing a stack overflow now.
You will need you use a different method for that check in the tests, not compromise the self-contained nature of this module. For example, add a middleware prior to this on in the tests and check against that.
In fact, the necessity to do this seems like a critical flaw to this functionality, for example, another module overwrites the moths with the three argument form, and just passes them upstream. This will now erroneously think callbacks are supported and we end up in the same buggy situation we set out to solve. It seems the method of using function length is just too fragile and I encourage you to come up with a different solution.