Conversation
…a dictionary of GET arguments passed. IOW: the part of the URL behind the "?" is now parsed by choreographer. This simplify's using GET parameters greatly.
|
Node already has an excellent query string parser, but I see no reason why Choreographer would do that for you, since it's completely orthogonal to Choreographer's core routing functionality. I would consider setting |
|
-1 on query parsing. Agreed that it's not the router's job. +1 on exposing the parsed URL at req.parsedUrl since it has to be parsed already. |
|
Choreographer has a code-wise logical position to provide for this parsing. Choreographer calls a part of personal code with the arguments passed in-URI. It does this already! We've already have a hacked in argument passing via URL ('/*/') instead of respecting standard and using GET arguments. Since GET arguments are orderless and not intended for it they shouldn't be used for routing. I see no reason we should support a hacky way and not the standard way of passing arguments. I'm using this for my site right now and it makes a lot of sense, it's easy, quick and simple. If you don't like it you'll never notice it's there. It's a 4 line addition that'll likely save you lines (it sure did to me). We could use the node-standard parser but even in the example non-standard processing is demo'd (supplying the same argument twice should overwrite, the latter argument being the actual value) and depending upon that code's consistency isn't wise when this suffices. |
|
I won't comment on whether the arguments Choreographer passes in are "hacky", but there is precedent, Choreographer's routing API is based on Sinatra's. Your GET arguments code does make a lot sense, and it does appear to be easy, quick, and simple. Its functionality is also completely orthogonal to Choreographer's. In my humble opinion, a piece of 4-line middleware in a stage after Choreographer would save you those same lines while loosening coupling and increasing cohesion over having both pieces of unrelated functionality being in your router. |
There was a problem hiding this comment.
I think this might be a global leak?
There was a problem hiding this comment.
Can you explain what you mean by "global leak"? It is intended to parse all GET arguments that are present in the url.query string and push them on the back of the existing arguments.
There was a problem hiding this comment.
well, are you familiar with the JavaScript var statement?
There was a problem hiding this comment.
I wasn't when I wrote this. Pretty sure it's a global. It stays within the module though, so it doesn't affect code now. It'd be neater to add a var statement. The "pairsplit" a few lines below this has the same problem :(.
There was a problem hiding this comment.
Learn something new every day, and it's a quick fix. :)
|
Something that would help with such middleware and I think make sense as part of Choreographer would be a Sinatra-style router.beforeGet('/register', function(callback, args, req, res) {
//parse the GET query
var url = req.parsedUrl, query = {};
url.query && url.query.split('&').forEach(function(pair) {
var pairsplit = pair.split('=');
query[pairsplit[0]] = pairsplit[1];
});
//and append that to the callback's arguments
args.push(query);
callback.apply(this, args);
});
router.get('/register/*', function(req, res, lobby, args) {
res.end('Entered lobby ' + lobby + 'arguments: ' + JSON.stringify(args));
});I haven't fully thought this through, what do you guys think? |
|
It depends on how I specify when to use the middleware. In the shape you presented I think it'd not be usefull for me. If I could just wildcard all GET requests it'd be fine. I still disagree on it's function being orthogonal. Sure it's not routing but already Choreographer does more than routing. Really Choreographer brings a request to my code, so I can just write the handling code. The work it does for me is handeling (routing) requests. This is IMHO still very much calling my code when it should, in a convenient manner. It's specific to GET requests though, that might be grounds for exclusion I could agree with. Before filters would likely not be worth the lines and complexity required to implement them properly. Maybe if you can do a filter for a type of request (beforeGet('*', function())) optionally limiting the URL to have to match. You can then provide my code as an example of a filter in the readme, it's a pretty useful snippet. |
|
I don't like the look of beforeGet. Either include middleware in the server stack before passing control to your routes, affecting every request (but only acting on GETs, etc., as you wish), or include it in the route before the rest of the code there. |
|
Also, regarding this query-parsing snippet, it lacks support for param arrays, alternative separator and assignment characters, and a maximum keys guard, all of which we can get in one line with the querystring module: router.get('/register', function(req, res) {
var query = querystring.parse(req.parsedUrl.query, ';', ':', {maxKeys: 100});
// do things with your query object here
});If we don't have req.parsedUrl, and you're fine with the defaults, the url library can do the job: router.get('/register', function(req, res) {
var query = url.parse(req.url, true).query;
// do things with your query object here
});Either of these approaches could be wrapped in a middleware function and applied as necessary. For example: function parseQuery(req, res) {
req.query = url.parse(req.url, true).query;
}
http.createServer(function(req, res) {
parseQuery(req, res);
// routes now have a query object at req.query
router(req, res);
}).listen(5000);I've been enjoying using https://github.com/creationix/stack, in which case things could look like this: function parseQuery(req, res, next) {
req.query = url.parse(req.url, true).query;
next();
}
router.get('/register', stack(
// apply parseQuery at the route level
parseQuery,
function(req, res, next) {
// do things with req.query
}
));
http.createServer(stack(
// or apply parseQuery at the server level
parseQuery,
router
)).listen(5000); |
|
And (sorry for the barrage) parseQuery could of course only act on GETs: function parseQuery(req, res, next) {
if (req.method == 'GET') {
req.query = url.parse(req.url, true).query;
}
// work with or without a next callback
if (next) next();
} |
|
@lodewijkadlp I was definitely thinking the route in the |
|
@gsf If you want to intercept all requests of a certain type, or to only do something for a particular route, then certainly a
both of which involve smelly duplication. In this situation, what I think you really want is for the router to handle routing requests through your middleware, hierarchically, before it reaches your handlers. What I'm unsure about is whether this situation actually comes up; @lodewijkadlp seems to be saying they want this on all (GET) requests, so your solution would work fine. Side note: I'm toying with |
|
Support for hierarchical path namespacing reminds me of https://github.com/cloudhead/journey#paths -- are you thinking something along those lines? You could also achieve something like this with express-style route control passing: http://expressjs.com/guide.html#passing-route%20control. |
|
Let's take this one step further! All functions called are processing steps, right? Be they the one calling res.end or some in-between steps they're all just functions being processed. Let's make the filter ('/', '/function//*', etc.) just that. And let's add a parameter, called the precedence integer. Let's add another, special, parameter that accesses a request-specific object (but remains between functions). Now, in order of ascending precedence integers, process every function who's filter checks out as true. If I say This can be used to plug an authentication middleware to "/" or to "/auth/**" or the like without affecting other steps. It's also easily stackable (you want another preprocessing step? Sure no problem), it explicitly includes ordering of steps, and it's fairly easy. We can make the precedence variable default to 1, taking precedence as "first added, first processed". I think this'll make the interface consistent and a lot more functional. |
|
Precedence on "first added, first processed" I could see being useful. I'm not sure I understand the precedence integer, and I get the sense that there would be a number of edge cases around it. |
|
As with the z-index on elements a lower to 0 precedence integer causes priority. IOW: processing steps are ordered lowest precedence integer first. 0 before 1 before 2 before 3 before 4 before 5 etc. Because things can have the same integer we should sub-order on first added first processed. This will define clear order of processing. |
|
Can they be negative? Do they have to be integers? I don't really see the advantage of specifying precedence over just filtering in the order the filters are defined. Rather than taking a step further, I'd like to step back as far as possible, and keep Choreographer minimalist. Can we simplify the |
|
I think simpler and general (more powerful) is better. Side effects are not I'd argue it's already the simplification. It DOES unify the two.
|
|
I think the unification is a great idea, and I'm trying to incorporate that. However, I think specifying the order your code runs in by a runtime integer rather than by ordering your code is a recipe for spaghetti code. The problem with side effects is spooky action at a distance, stuff like "over in this code I have these 3 pieces of middleware running one after another, and it looks all fine and dandy, and over in this completely separate code I added a piece of middleware that looks innocuous enough, but gave it a precedence integer that got it inserted in between the middleware way over there and now sometimes this piece does something and those 3 over there stop working". If you think specifying precedence with an integer is simpler and more powerful than just ordering your code, you could use a generalization that lets you order all of your code by precedence, like: order(1, function() {
router.get('/', function(req, res, obj) { obj.sample = 'samp'; });
});
order(10, function() {
router.get('/', function(req, res, obj) { res.end(obj.sample); });
}); |
|
Very well. It's just that typical code without side-effects doesn't mind
|
I recently did a pull request that would've broken everything. Turns out I mixed old and new code. This is the code as I use it myself now. I tried catching GET parameters by ending with "**" but that didn't catch behind the "?", now choreographer parses the GET parameters (when present) and adds them as a last parameter.
Eg:
or
or
As you can see it's fairly elegant. Someone who doesn't know it's there doesn't notice it. It allows slightly more "standard compliant" behavior of putting arguments behind the "?".