Uh oh!
There was an error while loading. Please reload this page.
RFC: phased router [do not merge] - #757
Conversation
Use `PhasedRouter` as the default application router.
Add `app.phase` delegating to `router.phase`, this allows developers
to register middleware with arbitrary phase:
app.phase('init').use(compress());2ad29a1 to
bc2fa66CompareThere was a problem hiding this comment.
Is this an optimized implementation of util.inherits()?
There was a problem hiding this comment.
Not really.
AFAIK
util.inheritsworks for objects created vianew. The router instances are functions, they use different mechanism.With
util.inherits, if you want to override a method and call the base class from your impl, you have to useapplyorcall, because you need to specify a differentthisobject:Child.prototype.method=function(){Base.prototype.method.apply(this,arguments);}
DecoratedRouterallows you to call methods without bind, becausethis._superandthisare sharing the same set of properties.Decorated.prototype.method=function(){this._super.method();}
However, there is one thing that does not work:
Base.foo=function(){this.bar();}Decorated.foo=function(){// do somethingthis._super.foo();}vard=newDecorated();d.bar();// calls Base.foo, not Decorated.fooI'll give this some more though tomorrow to see if I am able to come up with a better solution.
ritch
commented
Nov 6, 2014
We need to move this outside the LoopBack repo... this is too much to land in the core. My initial impression is that this reinvents or adds to phases. I'd rather implement those features in the phase module. Originally I didn't think we needed to build a custom router... but seeing this it makes sense. Although some things are not clear: varphase=app.phase('init');This should return a phase. AFAIK I didn't implement the following for phases: phase.use(myMiddleware);Since the |
ritch
commented
Nov 6, 2014
We can probably test the number of arguments to tell if the function is a middleware or a phase handler. |
bajtos
commented
Nov 6, 2014
Agreed, I never intended to land this in loopback. It was just faster to implement the prototype in this repo, since I have everything in one place.
No, that's my extension. I'd like to make
Adding exceptions and special rules like that is IMO a road to hell. I'd rather make the phases more generic, so that it's easy to build any solution based on them. |
ritch
commented
Nov 7, 2014
Phases are already very generic. The following API makes it possible to build on top of phases without having to extend them for the middleware use case: app.use('init',myMiddleware(options));Psuedo implementation: app.use=functino(phaseOrRoute,middleware){varroute=getRoute(phaseOrRoute);varphase=getPhase(phaseOrRoute);if(route){originalUse(route,middleware);}elseif(phase){phase.use(function(ctx,next){middleware(ctx.req,ctx.rex,next);});}// ...}This leaves phases untouched and unaware of middleware. Creating a However it doesn't support the same API as outlined at the top of the thread. It won't let you do |
ritch
commented
Nov 7, 2014
To rephrase my point above... I don't really mind what the implementation is as long as I am able to do the following: // in a loopback componentapp.phase('auth').use(function(ctx,cb){getTheCurrentUser(ctx.req,function(err,user){if(err)returncb(err);ctx.set('user',user);cb();});});// in my appapp.phase('auth').after(function(ctx,cb){console.log(ctx.get('user').email);// => foo@bar.comcb();}); |
raymondfeng
commented
Nov 7, 2014
Would use/before/after return the Phase object so that we can do the chaining of methods? I think as long as we agree on the apis, @bajtos can choose however he implements them. |
- remove DecoratedRouter - improve `app.lazyrouter` so that it does not copy express impl
bajtos
commented
Nov 7, 2014
Here are the most important API decision we need to make:
// scoped middlewareapp.phase('init').use('/subpath',handler);// routes - they always terminate the chain AFAIKapp.phase('init').post('/subpath',routeHandler);// request parametersapp.phase('init').param('user_id',function(req,res,next,id){User.find(id,function(err,user){if(err)returnnext(err);req.user=user;next();});});
// manually run the phaseapp.phase('init').run(ctx,next);// a silly exampleapp.phase('init').toString();// what if this is added in the future?app.phase('init').clear();Considering the main use case we have for middleware phases,
// express compatibleapp.phase('init').use(function(req,res,next){ ... });app.phase('error').use(function(err,req,res,next){ ... });// phase/hook basedapp.phase('init').use(function(ctx,next){// ctx.req, ctx.res });app.phase('error').use(function(ctx,next){// ctx.err, ctx.req, ctx.res });I am personally in favour of staying compatible with express middleware.
For the initial version, I am proposing to add a single new method to app API: app.middleware(phaseName,handlerFn);This name is preserving consistency with other loopback-provided Usage: app.middleware('init',compression());// phases.find('init').use(compression())app.middleware('before:init',serveFavicon());// phases.find('init').before(serveFavicon(...))app.middleware('after:files',loopback.urlNotFound());// phases.find('files').after(loopback.urlNotFound());Encoding before/after into the phase name keeps the API concise and A larger example: // example by @ritch// in a loopback componentapp.middleware('auth',function(req,res,next){getTheCurrentUser(req,function(err,user){if(err)returnnext(err);req.user=user;loopback.getCurrentContext().set('user',user);next();});});// in my appapp.middleware('after:auth',function(req,res,next){console.log(req.user.email);// => foo@bar.comnext();}); |
ritch
commented
Nov 7, 2014
I'm fine with app.middleware. |
bajtos
commented
Nov 7, 2014
Closing in favour of #767. |
A prototype implementation of of a phased router - see #739.
Usage
Todo
errorphase toendorfinallib/application.jsto a standalone fileapp.lazyrouterfunction: Support custom router expressjs/express#2431undefined phaseinPhaseList.run- see PhaseList: fixrun()and unit-tests loopback-phase#2caseSensitiveare correctly forwarded byDecoratedRouterPhaseimplementations (any object withidandruncan be used as a phase)PhaseList.runPhaseList.get(name)that throws a helpful error when the phase was not found/to @raymondfeng@ritch please review, ideally by EOD. Tomorrow my time, I'll start moving the code to a new module
strong-routerand work on the missing items outlined above.