-
-
Notifications
You must be signed in to change notification settings - Fork 241
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
Do not swallow calls to res.end()
#188
base: master
Are you sure you want to change the base?
Do not swallow calls to res.end()
#188
Conversation
if (onFinished.isFinished(this)) { | ||
return endOnce.call(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.
This addition takes care of situations where a route handler calls res.end()
after the response is considered "finished". This is often due to the client having moved on before the response is ready for them.
onFinished(res, function onFinished () { | ||
if (ended) { | ||
endOnce.call(res) | ||
} | ||
}) |
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.
This addition takes care of situations where a response becomes "finished" soon after res.end()
gets called by a route handler. Without this code, if the events occur close enough to one another, the stream may (at least in principle) get stuck in a "paused" state, never emitting the end
event that would normally invoke the original res.end
function.
setTimeout(function () { | ||
server.close(function () { | ||
if (originalResEndCalledTimes === 1) { | ||
done() | ||
} else { | ||
done(new Error('The original res.end() was called ' + originalResEndCalledTimes + ' times')) | ||
} | ||
}) | ||
}, 5) |
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.
Waiting for the condition to assert could be extracted into a generic helper, and it could also poll a couple of times if that is desired. I left this code in its naked form for now, and it is also duplicated in the other added test cases. I'm happy to improve this further as needed.
Background
In our application we tend to rely on
res.end()
calls to perform cleanup. Specifically, we intercept those calls, similarly to how the same is done in thecompression
middleware, and take custom actions before calling the originalres.end
function.Recently we found out that depending on middleware order, our cleanup code either gets called reliably or it does not. Additional investigation revealed that it is the
compression
middleware that sometimes "swallows" calls tores.end
, which is to say that even though a route handler callsres.end
, the middleware never calls the originalres.end
function. In particular, this happens when the client cuts the connection before consuming the compressed response because the following happens:res.end
, thecompression
middleware does not call the originalres.end
function and instead callsstream.end
.end
event does not get emitted and the handler attached to it never calls the originalres.end
function.We considered using the
finish
orprefinish
events instead of wrappingres.end
calls, but these do not get emitted either, likely becauseOutgoingMessage.end
never gets called either.Change
res.end
function does not get called with the code onmaster
on-finished
to detect client disconnect and call the originalres.end
function - though this is only done if the replacementres.end
wrapper had already been called, or if it is currently being calledres.end
function, the changed code also ensures that we never call the function more than onceOverall, the change should ensure that
res.end
calls do not get swallowed by thecompression
middleware.Acknowledgement
Lots of the ideation around this change as well as the test code originate from @papandreou - many thanks for all the help! 🙇