Browse Source

- Renamed middlewares to interceptors.

- Made server instance available in interceptors.
- Fixed typo in variable name
pull/45/head
Robert Schindler 9 years ago
parent
commit
9ad229c191
  1. 61
      docs/index.rst
  2. 7
      socketio/__init__.py
  3. 17
      socketio/interceptor.py
  4. 2
      socketio/namespace.py
  5. 107
      socketio/server.py
  6. 12
      socketio/util.py
  7. 31
      tests/test_server.py

61
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):
# ...

7
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]

17
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

2
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

107
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)

12
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

31
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')

Loading…
Cancel
Save