cote
cote copied to clipboard
Implement middleware for responders / subscribers
Certain functions such as auth or request decoration would make use of middlewares, as in, virtually everywhere (Express & co).
The following code...
responder.on('request', function(req, cb) {
// do something;
});
should still work with a middleware:
responder.on('request', middleware, function(req, cb) {
// do something if middleware allows
});
Hi. Can we use promise-based middleware? and what is the right way to implement it? thanks
// do something if middleware allows not sure i understand this. what signature are we expecting the middleware function to have?
It should be the same as the actual handler, and it should have a means to stop execution, either through a next()
function or throwing inside a promise.
ah ok. so would this work?
responder.use('somerequest',(req,cb,next) => {
// do something
});
responder.use('somerequest',(req,cb,next) => {
// do something else
});
responder.on('somerequest',(req,cb) => {
// ...
});
what would happen with the callback in a middleware? what if it gets called? can it be called again later down the line? what effect would that have?
Just from personal interest, here is a little function which enables a certain set of express-like features.
- middleware functions without the need for the next() call at all
- if middleware returns false, break chain of functions
- if some middleware throws something, break chain and return message to requester
- first argument is an object, not just the event type!
- first argument can be expanded with e.g. userdata from auth middleware
src/components/responder.js
on() {
var args = Array.from(arguments),
type = args.shift();
super.on(type, async (type, callback) => {
var error,
request;
if (typeof type === 'object') {
request = type;
} else {
request = {
type: type
};
}
let index = 0;
const next = () => {
const layer = args[index++];
try {
// Execute the layer
var result = layer(request);
if (result === false) {
// break the next() chain
callback();
}
if (args.length === index) {
// last function called. time to return data
callback(result);
}
if (result === undefined) {
// no break, still not last function?
// let's execute the next()
next();
}
} catch(error) {
callback(error);
}
};
next();
});
}
the requester example:
spotlightRequester.send({ type: 'latest', error: true }, (res) => {
console.log(res);
});
spotlightRequester.send({ type: 'latest', intercept: true }, (res) => {
console.log(res);
});
spotlightRequester.send({ type: 'latest' }, (res) => {
console.log(res);
});
the responder with middleware:
spotlightResponder.on('latest', function (request) {
if (request.error) {
throw "something";
}
if (request.intercept) {
return false;
}
} , function(request) {
console.log('result func ' + JSON.stringify(request));
return { test: 1 };
});
and the output from the requester
>something
>null
>{ test:1 }
It is by far just a proof of concept, not clean and only tested on my side. It is just an idea how to solve the implementation of middleware and I am aware of the fact, that this implementation restructures the responder process.
Edit: merged actual request object from requester with the request object for the middleware.
Hey, I implemented a promise based middleware just like app.use() in express. At any level in the middleware, we can do either res.resolve or res.reject and the requester will trigger .then() and .catch() accordingly.
@tericnexus cool! do you have it on a fork? does it work with callbacks as well? (because cote has to support callbacks for now.)
@dashersw I've come up with a pretty clear idea of middleware based on express which will require no breaking changes and still support callbacks.
src/components/responder.js
on(type, ...listeners) {
super.on(type, (...args) => {
let index = 0;
const next = () => {
const rv = listeners[index++](...args, next);
if (rv && typeof rv.then == 'function') {
const cb = args.pop();
rv.then((val) => cb(null, val)).catch(cb);
}
};
next();
});
}
Examples -
Requester
requester.send({
type: 'ping',
},
(err, str) => {
console.log(str);
}
);
Responder
responder.on(
'ping',
(req, cb, next) => {
console.log('responder middleware 1 - doing auth stuff');
next();
},
(req, cb, next) => {
console.log('responder middleware 2 - rate limiter');
next();
},
(req, cb, next) => {
console.log('responder middleware 3 - the result');
cb(null, 'pong');
}
);
Output
> responder middleware 1 - doing auth stuff
> responder middleware 2 - rate limiter
> responder middleware 3 - the result
> pong
I'll ready a PR if you find this helpful and after testing it a bit more. ;)
requester.send({ type: 'ping', }, (err, str) => { console.log(str); } );
Greate solution. I've been using it for a week now, but recently I noticed that the next function becomes undefined when there is an await in the function.
responder.on(
'ping',
async (req, cb, next) => {
const test = await Test();
next();
},
async (req, cb, next) => {
// next becomes undefined here
console.log('responder middleware 2 - rate limiter');
}
)
Here is the fixed code for the problem:
on(type, ...listeners) {
super.on(type, (...args) => {
let index = 0;
const next = async () => {
const rv = await listeners[index++](...args, next);
if (rv && typeof rv.then == 'function') {
const cb = args.pop();
rv.then((val) => cb(null, val)).catch(cb);
}
};
next();
});
}