Still working out in my head how this is going to work, but I'd like to have an official place to share and bounce ideas off everybody, rather than keep it cramped and musty in my own attic.
We want some kind of prioritized, registered chain of callbacks for handling messages in the router, which will open up the door for future extensions such as persistent stream support, applying onion routing to arbitrary frames, etc. The final steps will be the ones that are primary now -
send to a client if available, and
send to a jack if available.
Probably best to wait until both jack and frame registration are implemented, but that doesn't mean we have to wait to cook up ideas.
Each callback is registered with a name, and a function.
The name follows a format of “7 Route into Hyperboria”, consisting of a number, a space, and a short descriptive title of what the function does. Prioritization occurs by string-sorting these keys. All the names starting with “1” will be checked before those that start with “2”, and “404” will happen before “45”. We can organically extend specificity as far as we want, and numbers can collide as long as the rest of the name doesn't (which is great for programmatically generated rerouting callbacks).
We should probably define some basic categories for the single-digit numbers. 9 should definitely be reserved for jack and client dispatch. The rest, I dunno. We certainly don't have to define them all yet.
A callback function takes arguments
(router, frame, resend). It should use a docstring to provide a longer description of its purpose and inner workings.
resend(frame) is a function that sends a frame through another pass of the callback list. Callbacks are encouraged to use this function, rather than
router.recv(frame), because it internally imposes a recursion limit. It is defined on the fly at the top of router.recv.
There may be efficiency concerns to this approach, so I'm open to an integer-based alternative to the same problem.
A function signals if a message should be passed to the next callback by the return value. True if the callback has handled things and no further processing is necessary, False to let the next callback in the chain take a crack at it. If the callback raises an exception, the frame is passed to the next callback as if the function had returned False, and the traceback is logged as an error.
Largely depends how we implement registration elsewhere. I don't think we want global registration, since callbacks are basically the configuration of routers, and while most applications will only have use for one router, there are use cases for separate and differently configured routers within the same Python environment.
That does bring up the question of how to register default callbacks. I propose doing a call to a
setup_default_callbacks or similar function in
router.__init__, which registers a small set of basic callbacks, probably just jack/client dispatch. Leave it up to the application to build up from that minimal base as the application needs, rather than bloating the default configuration.