-
-
Notifications
You must be signed in to change notification settings - Fork 103
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
Add Router events #49
Conversation
@hacksparrow this is nice. I guess "layer" would be the event I'm looking for, but where can I associate that to the request itself here? For example, being able to do the below, would be nice, but needs req to be injected: app.router.on('layer', function (layer, req) {
req.matchedLayers = req.matchedLayers ? req.matchedLayers.concat([layer]) : [layer]
}) |
The end goal being that I can have a middleware like so: function routeLogger (req, res, next) {
res.on('finish', function () {
// write to analytics, or log route
console.log(req.matchedLayers); // Maybe do some formatting here to get the actual paths joined
});
} |
We will also need to discuss how the events are bubbled (or not). For example, would a user be required to attach an event listener on every single router (and of course, understand the list of all their routers) or should this use a form of event bubbling between routers, similar to the DOM? |
@evanshortiss great suggestion. "handlestart" and "handleend" would need the request context too. @dougwilson excellent feedback. I will implement a bubbling version as well for further thoughts on the feature. |
5756cca
to
585eeb0
Compare
@evanshortiss the event handlers have been updated to the following: app.router.on('handlestart', function (req, router) {
})
app.router.on('layer', function (layer, req, router) {
})
app.router.on('handleend', function (req, router) {
}) |
That looks solid to me. Should provide plenty of context for introspection. Nice work! 👍 |
@hacksparrow why add the |
@dougwilson err yes! I will remove it from the params. Thanks. |
585eeb0
to
40c292f
Compare
So, just for the record, the signature now is: app.router.on('handlestart', function (req) {
})
app.router.on('layer', function (layer, req) {
})
app.router.on('handleend', function (req) {
}) |
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.
Additional to the other comments, any thoughts about the DOM-like event bubbling?
index.js
Outdated
@@ -186,6 +192,9 @@ Router.prototype.handle = function handle(req, res, callback) { | |||
req.baseUrl = parentUrl | |||
req.originalUrl = req.originalUrl || req.url | |||
|
|||
// trigger the "beginning of route handling" event | |||
if (events && routerEvents[0] in events) self.emit('handlestart', req) |
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.
Is routerEvents[0] in events
actually saving any time? I'm not sure if accessing _events
and using in
on it is really a good long term API to use with event emitters.
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.
Agreed. I decided on this to keep the naming convention "flexible". Will come up with a better alternative.
index.js
Outdated
@@ -269,6 +278,9 @@ Router.prototype.handle = function handle(req, res, callback) { | |||
return done(layerError) | |||
} | |||
|
|||
// trigger the "layer found" event | |||
if (events && routerEvents[1] in events) self.emit(routerEvents[1], layer, req) |
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.
If we swap req
and layer
, all three events will conveniently start with req
as the first argument.
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.
👍
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 is a good suggestion 👍
test/router.js
Outdated
var server = createServer(router) | ||
|
||
router.on('handlestart', function (req) { | ||
assert(IncomingMessage.prototype.isPrototypeOf(req)) |
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.
I would just check for certain properties, perhaps add a request header and try to read it here, rather than checking the prototype specifically. I know there are a bunch of people interested in better browser support, so if there is no reason to hurt the support, I would err on the side of that.
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.
👍
test/router.js
Outdated
var counter = 0 | ||
router.on('layer', function (layer, req) { | ||
counter++ | ||
if (handlers.length === counter) done() |
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.
I'd recommend using the after
function for this type of tracking, which will throw when invoked too much.
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.
👍
Does anyone have any thoughts on the event names? I'm neutral on them :) |
Please add some docs for this to the README. |
40c292f
to
f95996f
Compare
The following events were added: 1. `handlestart` - emitted when the router starts to handle the router stack. 2. `layer` - emitted when a layer is macthed by the router. It returns the matched layer. 3. `handleend` - emitted when the router has completed handling the router stack. I am sure very sure about the name of the events, feedback are most welcome.
f95996f
to
79f8afa
Compare
@dougwilson @crandmck I have updated the PR according to your suggestions. For the record, the signature of the
|
Thanks, docs LGTM. |
Anything blocking this? I would love to replace what I have been doing in middleware with these new events. |
There are still issues I wrote above unaddressed, with the biggest that it is reaching into the internals of Node.js events which is unlikely to keep working in the future. |
Ahh, sorry @dougwilson I didn't open the "outdated diffs". I completely agree with the comments, and am anxiously awaiting this merge :) Thanks for the good work @hacksparrow!! |
|
||
// trigger the "end of route handling" event | ||
if (events && 'handleend' in events) { | ||
res.once('finish', function () { |
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.
What is the purpose of adding the finish event listener? Especially because there are no tests around all the edge cases this will cause (for example, are we saying that if the response is already finished before the handleend, our events is not supposed to fire?)
@@ -164,6 +168,7 @@ Router.prototype.handle = function handle(req, res, callback) { | |||
var self = this | |||
var slashAdded = false | |||
var paramcalled = {} | |||
var events = self._events |
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.
Let's not peak into the internals of Node.js if it's not required, and I see no reason this is required. If there is, please enlighten me :)
@@ -20,6 +20,7 @@ var mixin = require('utils-merge') | |||
var parseUrl = require('parseurl') | |||
var Route = require('./lib/route') | |||
var setPrototypeOf = require('setprototypeof') | |||
var EventEmitter = require('events').EventEmitter |
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 should probably be alphabetical like the rest.
|
||
### 2. `layer` | ||
|
||
This event is emitted when a route layer is matched. |
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.
Can you define "route layer" in the docs?
}) | ||
``` | ||
|
||
Here is a complete example of using router events in an Express 5 app. |
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 should not be part of the handleend section.
Here is a complete example of using router events in an Express 5 app. | ||
|
||
```js | ||
var express = require('express') |
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.
Can the example be a real example instead of just a bunch of console log lines? I.e. demonstrate the use case people will use this for. People love to copy paste, and this is not something they would do that on.
@@ -983,8 +984,153 @@ describe('Router', function () { | |||
.expect(200, 'saw GET /bar', done) | |||
}) | |||
}) | |||
|
|||
describe('events', function () { | |||
|
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.
These tests have a very inconsistent style from all other tests if you can clean them up at some point :)
Also, what ever happened with the whole event bubbling discussion? I feel like I've mentioned it before, and @hacksparrow said he would look into it, but never really anything seemed to happen there. So what is the thoughts on that? Does that make it easier to accomplish whatever the goals of this PR are or not? |
@dougwilson I haven't made significant progress on event bubbling yet. Will update once done. In the mean time, it'd be great to hear the community's opinion about a bubbling and non-bubbling version. |
@dougwilson @hacksparrow just to be clear, by bubbling you mean bubble to an upper "app" (express) instance? So for submounted apps, the event will bubble to the parent app and so on? |
@evanshortiss bubbling within the routers (independent or the one that comes with Express) hierarchy. We were thinking along lines of the browser DOM events. |
TBH I think you have to do bubbling for
|
To address the cases that I would use this for right away, here are two I can think of:
So for number one, having multiple start events for one "run" of the router stack, would mean I would have to track it myself. Possible, obviously, but it would be nicer if the design compensated for this and gave me a simple way. For number two, each "layer", IMO should include all sub routers. Thus requiring bubbling. |
So I am circling back on this, and after re-thinking it I do not think anything other than the
That all being said, the one thing we cannot do with ease is track stats and metrics on individual layers. Which is why I propose that this change just to:
So I created #56 to illustrate and keep the discussion going. |
Hi guys! so is this still happening? :) |
If it is, as I said in my last comment, I would hope we just go with the layer events. That being said, I am not sure even this is the best way forward, and I have an idea which has been bubbling about adding a decorator mechanism which would allow for much more flexibility and also superseded the need to add these events at all. I have left open my other PR since it might be good, but I think this PR should be closed. If no one disagrees (@dougwilson or @hacksparrow) then I will close this in a week (reminder already set to revisit). |
Ok, no comments. But @dougwilson it looks like I am not an owner on this org so cannot close this. 🤷♂️ |
@dougwilson It looks like just adding me to the tc group is not enough to be able to close issues. Seems like there must be some settings to make that work, but I am not sure. |
Sorry @wesleytodd about that. I will check it out and make sure your permissions are in order. You should have the ability do to this, so I will check why here shortly 👍 |
7e90c9e
to
8643ec6
Compare
Finally coming back around here. This is superseded by #56 IIRC. Closing this, and assessing if that could possibly land for |
The following events were added:
handlestart
- emitted when the router starts to handle the router stack.layer
- emitted when a layer is matched by the router. It returns the matched layer.handleend
- emitted when the router has completed handling the router stack.This feature should help developers handle requirements as described in expressjs/express#2501, expressjs/express#3100, and similar.
Example of usage with Express:
@dougwilson @danieljuhl @evanshortiss \o/