diff --git a/docs/index.rst b/docs/index.rst index 0a21d58..6218b05 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -252,11 +252,12 @@ instance includes versions of several of the methods in the :class:`socketio.Server` class that default to the proper namespace when the ``namespace`` argument is not given. -Event handler middlewares -------------------------- +Event handler interceptors +-------------------------- -Event handler middlewares are objects with the following methods or a -subset of them: +Event handler interceptors should be objects that inherit from +``socketio.Interceptor`` and can have the following methods or a subset +of them: * ``before_event(*args)`` is called before the event handler is executed with the event name, the namespace and the list of arguments the event @@ -269,21 +270,21 @@ subset of them: event handling process with the event name, namespace and the exception object as its arguments. The exception could have been raised by either the event handler itself, or the ``before_event`` and ``after_event`` - methods of another middleware that comes later in the middleware chain + methods of another interceptor that comes later in the interceptor chain (one that is applied more-specifically). -* ``ignore_for`` is called before the middleware is applied to an +* ``ignore_for`` is called before the interceptor is applied to an event handler with the event name and namespace as arguments. If its - return value resolves to ``True`` the middleware is ignored and not + return value resolves to ``True`` the interceptor is ignored and not applied to that particular event handler. -Event handler middlewares can be chained. The ``before_event`` methods -will be executed in the same order the middlewares were added, the +Event handler interceptors can be chained. The ``before_event`` methods +will be executed in the same order the interceptors were added, the ``after_event`` methods are called in reversed order. If the ``before_event`` method returns something else than ``None``, -execution jumps to ``after_event`` of this middleware, passing the +execution jumps to ``after_event`` of this interceptor, passing the arguments the method returned to it and continues the regular processing -from there by rippling up until the least specific middleware is reached. +from there by rippling up until the least specific interceptor is reached. If an ``after_event`` method returns something else than ``None``, execution is stopped at that point and the returned value is used as @@ -292,23 +293,25 @@ the result of event handling. ``handle_exception``, if present, must either handle the given exception and return something that will be treated as the event handling result and eventually be passed to the ``after_event`` method of less-specific -middlewares or re-raise the exception (or another one, +interceptors or re-raise the exception (or another one, at their choice). Note that you can also use classes (objects of type ``type``) instead -of instances as event handler middlewares. If you do so, they are -instantiated with no arguments every time they are used to process -an event. +of instances as event handler interceptors. If you do so, they are +instantiated with the ``socketio.Server`` object every time they are +used to process an event. If you use ``socketio.Interceptor`` as base +class for your interceptors, they'll have the ``socketio.Server`` object +in use available as their ``server`` attribute. Example: :: - from socketio import server + from socketio import interceptor, server - class MyMiddleware(object): + class MyInterceptor(interceptor.Interceptor): def ignore_for(self, event, namespace): - # This middleware won't be applied to connect and disconnect + # This interceptor won't be applied to connect and disconnect # event handlers. return event in ("connect", "disconnect") @@ -330,19 +333,19 @@ Example: print("foo executed") return "foo executed" - sio.middlewares.append(MyMiddleware()) + sio.interceptors.append(MyInterceptor(s)) -In this example, the middleware would be applied to every event handler +In this example, the interceptor would be applied to every event handler executed by ``sio``, except for the ``'connect'`` and ``'disconnect'`` handlers. -Middlewares can be added to a ``Namespace`` object as well by inserting -them into its ``middlewares`` ``list`` attribute. They are applied -after the server-wide middlewares to every event handler defined in that +Interceptors can be added to a ``Namespace`` object as well by inserting +them into its ``interceptors`` ``list`` attribute. They are applied +after the server-wide interceptors to every event handler defined in that ``Namespace`` object. -There is also a decorator available to add a middleware to a specific -handler only. Given the middleware class from above, it would be used +There is also a decorator available to add a interceptor to a specific +handler only. Given the interceptor class from above, it would be used as follows: :: @@ -352,20 +355,20 @@ as follows: sio = socketio.server.Server() @sio.on("/foo") - @util.apply_middleware(MyMiddleware) + @util.apply_interceptor(MyInterceptor) def foo(sid, data): print("foo executed") return "foo executed" Middlewares added by the decorator are treated as if they were added -*after* the server-wide and namespace-wide middlewares. Naturally, +*after* the server-wide and namespace-wide interceptors. Naturally, decorators are applied from bottom to top. Hence the following will first add ``MW1`` and then ``MW2``. :: - @util.apply_middleware(MW2) - @util.apply_middleware(MW1) + @util.apply_interceptor(MW2) + @util.apply_interceptor(MW1) def foo(sid): # ... diff --git a/socketio/__init__.py b/socketio/__init__.py index 514583f..540de25 100644 --- a/socketio/__init__.py +++ b/socketio/__init__.py @@ -1,11 +1,12 @@ from .middleware import Middleware +from .interceptor import Interceptor from .namespace import Namespace from .base_manager import BaseManager from .pubsub_manager import PubSubManager from .kombu_manager import KombuManager from .redis_manager import RedisManager from .server import Server -from .util import apply_middleware +from .util import apply_interceptor -__all__ = [Middleware, Namespace, Server, BaseManager, PubSubManager, - KombuManager, RedisManager, apply_middleware] +__all__ = [Interceptor, Middleware, Namespace, Server, BaseManager, + PubSubManager, KombuManager, RedisManager, apply_interceptor] diff --git a/socketio/interceptor.py b/socketio/interceptor.py new file mode 100644 index 0000000..5acaf34 --- /dev/null +++ b/socketio/interceptor.py @@ -0,0 +1,17 @@ +class Interceptor(object): + """Base class for event handler interceptors.""" + + def __init__(self, server): + self.server = server + + def ignore_for(self, event, namespace): + return False + + def before_event(self, event, namespace, args): + pass + + def after_event(self, event, namespace, args): + pass + + def handle_exception(self, event, namespace, exc): + raise exc diff --git a/socketio/namespace.py b/socketio/namespace.py index 982cf22..3df6029 100644 --- a/socketio/namespace.py +++ b/socketio/namespace.py @@ -13,7 +13,7 @@ class Namespace(object): def __init__(self, namespace=None): self.name = namespace or '/' self.server = None - self.middlewares = [] + self.interceptors = [] def _set_server(self, server): self.server = server diff --git a/socketio/server.py b/socketio/server.py index ef7d634..37a2754 100644 --- a/socketio/server.py +++ b/socketio/server.py @@ -80,7 +80,7 @@ class Server(object): self.environ = {} self.handlers = {} self.namespace_handlers = {} - self.middlewares = [] + self.interceptors = [] self._binary_packet = [] @@ -446,52 +446,49 @@ class Server(object): def _trigger_event(self, event, namespace, *args): """Invoke an application event handler.""" handler = None - middlewares = list(self.middlewares) + interceptors = list(self.interceptors) # first see if we have an explicit handler for the event if namespace in self.handlers and event in self.handlers[namespace]: handler = self.handlers[namespace][event] elif namespace in self.namespace_handlers: ns = self.namespace_handlers[namespace] - middlewares.extend(ns.middlewares) + interceptors.extend(ns.interceptors) handler = ns._get_event_handler(event) if handler is not None: - middlewares.extend(getattr(handler, '_sio_middlewares', [])) - handler = self._apply_middlewares(middlewares, event, namespace, - handler) + interceptors.extend(getattr(handler, '_sio_interceptors', [])) + handler = self._apply_interceptors(interceptors, event, namespace, + handler) return handler(*args) - @staticmethod - def _apply_middlewares(middlewares, event, namespace, handler): - """Wraps the given handler with a wrapper that executes middlewares + def _apply_interceptors(self, interceptors, event, namespace, handler): + """Wraps the given handler with a wrapper that executes interceptors before and after the real event handler.""" - _middlewares = [] - for middleware in middlewares: - if isinstance(middleware, type): - middleware = middleware() - if not hasattr(middleware, 'ignore_for') or \ - not middleware.ignore_for(event, namespace): - _middlewares.append(middleware) - if not _middlewares: + _interceptors = [] + for interceptor in interceptors: + if isinstance(interceptor, type): + interceptor = interceptor(self) + if not interceptor.ignore_for(event, namespace): + _interceptors.append(interceptor) + if not _interceptors: return handler def wrapped(*args): - def handle_exception(middlewares_processed, exc): - for middleware in \ - reversed(_middlewares[:middlewares_processed]): - if hasattr(middleware, 'handle_exception'): - try: - result = middleware.handle_exception( - event, namespace, exc) - # exception has been cought - return middlewares_processed, result - except Exception as e: - exc = e - middlewares_processed -= 1 + def handle_exception(interceptors_processed, exc): + for interceptor in \ + reversed(_interceptors[:interceptors_processed]): + try: + result = interceptor.handle_exception( + event, namespace, exc) + # exception has been cought + return interceptors_processed, result + except Exception as e: + exc = e + interceptors_processed -= 1 if exc is not None: - # Exception hasn't been cought by any middleware, + # Exception hasn't been cought by any interceptor, # hence we finally raise it. - raise e + raise exc def reformat_result(result): if result is None: @@ -502,46 +499,44 @@ class Server(object): return [result] args = list(args) - middlewares_processed = 0 + interceptors_processed = 0 result = None try: - # apply before_event methods - for middleware in _middlewares: - middlewares_processed += 1 - if hasattr(middleware, 'before_event'): - result = middleware.before_event( - event, namespace, args) - if result is not None: - break -# return result + # Apply before_event methods. + for interceptor in _interceptors: + interceptors_processed += 1 + result = interceptor.before_event( + event, namespace, args) + if result is not None: + break if result is None: # call the real event handler result = handler(*args) except Exception as exc: - middlewares_processed, result = \ - handle_exception(middlewares_processed, exc) + interceptors_processed, result = \ + handle_exception(interceptors_processed, exc) data = reformat_result(result) - while middlewares_processed > 0: + while interceptors_processed > 0: try: # In case there was an exception handled, we ensure - # only the middlewares that are less-specific + # only the interceptors that are less-specific # than the one that cought the exception get - # applied. More-specific middlewares should not be + # applied. More-specific interceptors should not be # aware of less-specific ones and thus not process # the output of them. - for middleware in \ - reversed(_middlewares[:middlewares_processed]): - if hasattr(middleware, 'after_event'): - result = middleware.after_event( - event, namespace, data) - if result is not None: - return result - middlewares_processed -= 1 + # Apply after_event methods. + for interceptor in \ + reversed(_interceptors[:interceptors_processed]): + result = interceptor.after_event( + event, namespace, data) + if result is not None: + return result + interceptors_processed -= 1 except Exception as exc: - middlewares_processed, result = \ - handle_exception(middlewares_processed, exc) + interceptors_processed, result = \ + handle_exception(interceptors_processed, exc) data = reformat_result(result) return tuple(data) diff --git a/socketio/util.py b/socketio/util.py index 9f01b4c..1d35a78 100644 --- a/socketio/util.py +++ b/socketio/util.py @@ -1,16 +1,16 @@ -def apply_middleware(middleware): +def apply_interceptor(interceptor): """Returns a decorator for event handlers that adds the given - middleware to the handler decorated with it. + interceptor to the handler decorated with it. - :param middleware: The middleware to add + :param interceptor: The interceptor to add Ensure that you only add well-behaving decorators after this one (meaning such that preserve attributes) because you'll loose them otherwise. """ def wrapper(handler): - if not hasattr(handler, '_sio_middlewares'): - handler._sio_middlewares = [] - handler._sio_middlewares.append(middleware) + if not hasattr(handler, '_sio_interceptors'): + handler._sio_interceptors = [] + handler._sio_interceptors.append(interceptor) return handler return wrapper diff --git a/tests/test_server.py b/tests/test_server.py index 5c50138..2d72d13 100644 --- a/tests/test_server.py +++ b/tests/test_server.py @@ -8,6 +8,7 @@ if six.PY3: else: import mock +from socketio import interceptor from socketio import namespace from socketio import packet from socketio import server @@ -47,17 +48,20 @@ class TestServer(unittest.TestCase): self.assertEqual(s.handlers['/']['disconnect'], bar) self.assertEqual(s.handlers['/foo']['disconnect'], bar) - def test_middleware(self, eio): - class MW: - def __init__(self): + def test_interceptor(self, eio): + class MW(interceptor.Interceptor): + def __init__(self, *args, **kwargs): + super(MW, self).__init__(*args, **kwargs) self.ignore_for = mock.MagicMock(side_effect=100 * [False]) self.before_event = mock.MagicMock(side_effect=100 * [None]) self.after_event = mock.MagicMock(side_effect=100 * [None]) - mw1 = MW() - mw2 = MW() - mw3 = MW() - mw4 = MW() + s = server.Server() + + mw1 = MW(s) + mw2 = MW(s) + mw3 = MW(s) + mw4 = MW(s) mw4.ignore_for = mock.MagicMock(side_effect=[True] + 100 * [False]) mw4.before_event = mock.MagicMock(side_effect=['x'] + 100 * [None]) mw4.after_event = mock.MagicMock(side_effect=['x'] + 100 * [None]) @@ -67,20 +71,19 @@ class TestServer(unittest.TestCase): pass @namespace.Namespace.event_name('foo bar') - @util.apply_middleware(mw4) + @util.apply_interceptor(mw4) def some_name(self, sid): pass - s = server.Server() - s.middlewares.append(mw1) + s.interceptors.append(mw1) @s.on('abc') - @util.apply_middleware(mw2) + @util.apply_interceptor(mw2) def abc(sid): pass ns = NS('/ns') - ns.middlewares.append(mw3) + ns.interceptors.append(mw3) s.register_namespace(ns) # only mw1 and mw3 should run completely @@ -291,7 +294,7 @@ class TestServer(unittest.TestCase): mgr = mock.MagicMock() s = server.Server(client_manager=mgr) handler = mock.MagicMock(return_value=False) - del handler._sio_middlewares + del handler._sio_interceptors s.on('connect', handler) s._handle_eio_connect('123', 'environ') handler.assert_called_once_with('123', 'environ') @@ -303,7 +306,7 @@ class TestServer(unittest.TestCase): mgr = mock.MagicMock() s = server.Server(client_manager=mgr) handler = mock.MagicMock(return_value=False) - del handler._sio_middlewares + del handler._sio_interceptors s.on('connect', handler, namespace='/foo') s._handle_eio_connect('123', 'environ') s._handle_eio_message('123', '0/foo')