cote icon indicating copy to clipboard operation
cote copied to clipboard

Implement middleware for responders / subscribers

Open dashersw opened this issue 8 years ago • 9 comments

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
});

dashersw avatar May 23 '16 11:05 dashersw

Hi. Can we use promise-based middleware? and what is the right way to implement it? thanks

geka-evk avatar Sep 29 '17 08:09 geka-evk

// do something if middleware allows not sure i understand this. what signature are we expecting the middleware function to have?

orrgal1 avatar Jun 26 '18 11:06 orrgal1

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.

dashersw avatar Jun 26 '18 12:06 dashersw

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?

orrgal1 avatar Jun 26 '18 15:06 orrgal1

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.

QQping avatar Dec 18 '18 13:12 QQping

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 avatar Apr 23 '19 16:04 tericnexus

@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 avatar Apr 23 '19 19:04 dashersw

@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. ;)

akbaruddink avatar May 05 '19 22:05 akbaruddink

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();
        });
      }

Msiavashi avatar Jan 07 '20 09:01 Msiavashi