-
Notifications
You must be signed in to change notification settings - Fork 1.4k
Propagate "peek" and "finish" errors as error response #4173
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
Conversation
kanongil
left a comment
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.
Looks good. Just needs a few tweaks.
|
I don't think the failing tests are related to the changes of this PR. |
kanongil
left a comment
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.
LGTM.
|
CI errors are due to hapijs/lab#997. |
|
Any idea on when this could land, @kanongil ? |
|
As workaround, one can currently do: request.raw.req.emit('error', unauthorized('Corrupted payload'))as in: server.ext('onRequest', (request, h) => {
const hash = createHash('sha512')
request.events.on('peek', (chunk, encoding) => {
hash.update(chunk, encoding)
})
request.events.on('finish', () => {
const payloadDigest = hash.digest('base64')
if (payloadDigest !== getDigestHeader(request, 'sha512')) {
/**
* @hack Until we can throw from the 'finish' listener
* @see {@link https://github.com/hapijs/hapi/pull/4173}
*/
request.raw.req.emit('error', Boom.unauthorized('Corrupted payload'))
// throw unauthorized('Corrupted payload')
}
})
}); |
|
This is nice, I think I do like the behavior. Thanks for the contribution. Two notes here:
|
|
I started writing some tests but encountered the following issue with |
kanongil
left a comment
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.
Given the performance considerations that @devinivy brought up, I am no longer certain that this should be patched. I might defer to my original stance, which was that it is a fatal programming error, if an event handler throws.
Looking at podium itself, and the usage across the hapijs org, it is apparent that handling thrown errors is mostly a non-consideration. The two other modules that use it, catbox and beam, both call the emitter without handling the returned promise, resulting in an unhandledRejection emit on process.
For hapi itself, the emit() promise is only handled for the server start and stop events. Every other usage is presently unhandled. I would in fact consider any await of the returned promise to be a bug, since the application flow should not delay to handle any fallout of an event. emit() is by its nature a fire and forget thing.
All-in-all, the conclusion seems to be that hapi should stop awaiting start and stop events (breaking change), and podium should have an optimization pass to not return a promise (breaking change), and that this PR is unfortunately not appropriate.
|
Regarding the performance issues, you know that node's stream heavily rely on micro tasks internally, right? And that this change has indeed the potential to slow down request processing, but only if the dev is not careful during the peek & finally. It is kinda sad that there is no clean way to fix #4172. (Ie. Preventing any processing of the request payload, in an auth scheme). Also, what is the point of having a podium at all (instead of node's event emitter) if not for being able to await listeners execution? I use hapi quite a lot and love how it is so easily extendable and plug & play. I really fell like this decision is a move backwards. For example, being able to execute all async setup tasks during start is so elegant and efficient (all tasks run in parallel). I don't really understand how a PR adding extendability made you rethink everything in the opposite direction |
Podium contains channels and tags, that allow filtering and more efficient processing. The registration step prevents emitting mistyped event names. It also ensures that all emitters are called on an
It is still possible to stall a start() / stop(), you just need to use the exts FYI, your contribution has helped us gain insight into a part that is not clearly handled. A clarification can go either way, and in this case it seems more consistent to do something other than you proposed. |
|
FYI, if podium is changed to not emit async, you will likely be able to solve your #4172 issue with: request.events.once('finish', () => {
if (digest !== hash.digest('base64')) {
request.raw.req.destroy(throw new Boom.badData('nice error message'));
}
}); |
|
I did some benchmarking to see the impact of microtasks. Here are my conclusions: ComputermacOS Catalina 10.15.7 (19H15)
|
|
In my mind, this is no longer a performance issue, but an conceptual The provided benchmark is not really appropriate, as there is not apparent what is really benchmarked here. It just shows that the difference is too small to detect in relation to the request processing itself (which does not surprise me). Given that this will waste much time waiting for IO, there is no way to determine the actual CPU load of the change. |
Fixes #4172