Browse Source

- Added handle_exception method.

- Fixed docs and middleware applying process.
pull/45/head
Robert Schindler 9 years ago
parent
commit
3ae318c49e
  1. 36
      docs/index.rst
  2. 90
      socketio/server.py
  3. 21
      tests/test_server.py

36
docs/index.rst

@ -265,19 +265,36 @@ subset of them:
* ``after_event(*args)`` is called after the event handler with the
event name, the namespace and the list of values the event handler
returned. It may alter that values eventually.
* ``ignore_event`` is called before the middleware is applied to an
* ``handle_exception`` is called when an exception is raised during the
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
(one that is applied more-specifically).
* ``ignore_for`` is called before the middleware is applied to an
event handler with the event name and namespace as arguments. If its
return value resolves to ``True`` the middleware is not applied to that
particular event handler.
If one of these methods returns something else than ``None``, execution
is stopped at that point and the returned value is treated as if it was
returned by the event handler.
return value resolves to ``True`` the middleware 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
``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
arguments the method returned to it and continues the regular processing
from there by rippling up until the least specific middleware 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
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,
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
@ -290,11 +307,14 @@ Example:
from socketio import server
class MyMiddleware(object):
def ignore_event(self, event, namespace):
def ignore_for(self, event, namespace):
# This middleware won't be applied to connect and disconnect
# event handlers.
return event in ("connect", "disconnect")
def handle_exception(self, event, namespace, e):
return "Exception: %r" % e
def before_event(self, event, namespace, args):
print("before_event called with:", args)
if len(args) < 2 or args[1] != "secret":

90
socketio/server.py

@ -469,34 +469,80 @@ class Server(object):
for middleware in middlewares:
if isinstance(middleware, type):
middleware = middleware()
if not hasattr(middleware, 'ignore_event') or \
not middleware.ignore_event(event, namespace):
if not hasattr(middleware, 'ignore_for') or \
not middleware.ignore_for(event, namespace):
_middlewares.append(middleware)
if not _middlewares:
return handler
def wrapped(*args):
args = list(args)
for middleware in _middlewares:
if hasattr(middleware, 'before_event'):
result = middleware.before_event(event, namespace, args)
if result is not None:
return result
result = handler(*args)
if result is None:
data = []
elif isinstance(result, tuple):
data = list(result)
else:
data = [result]
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
if exc is not None:
# Exception hasn't been cought by any middleware,
# hence we finally raise it.
raise e
def reformat_result(result):
if result is None:
return []
elif isinstance(result, tuple):
return list(result)
else:
return [result]
for middleware in reversed(_middlewares):
if hasattr(middleware, 'after_event'):
result = middleware.after_event(event, namespace, data)
if result is not None:
return result
args = list(args)
middlewares_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
if result is None:
# call the real event handler
result = handler(*args)
except Exception as exc:
middlewares_processed, result = \
handle_exception(middlewares_processed, exc)
data = reformat_result(result)
while middlewares_processed > 0:
try:
# In case there was an exception handled, we ensure
# only the middlewares that are less-specific
# than the one that cought the exception get
# applied. More-specific middlewares 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
except Exception as exc:
middlewares_processed, result = \
handle_exception(middlewares_processed, exc)
data = reformat_result(result)
return tuple(data)

21
tests/test_server.py

@ -50,7 +50,7 @@ class TestServer(unittest.TestCase):
def test_middleware(self, eio):
class MW:
def __init__(self):
self.ignore_event = mock.MagicMock(side_effect=100 * [False])
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])
@ -58,7 +58,7 @@ class TestServer(unittest.TestCase):
mw2 = MW()
mw3 = MW()
mw4 = MW()
mw4.ignore_event = mock.MagicMock(side_effect=[True] + 100 * [False])
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])
@ -105,7 +105,7 @@ class TestServer(unittest.TestCase):
self.assertEqual(mw4.before_event.call_count, 0)
self.assertEqual(mw4.after_event.call_count, 0)
# again, this time mw4 before_event should be triggered
# again, this time mw4 before + after should be triggered as well
s._trigger_event('foo bar', '/ns', '123')
self.assertEqual(mw1.before_event.call_count, 3)
self.assertEqual(mw1.after_event.call_count, 2)
@ -114,30 +114,29 @@ class TestServer(unittest.TestCase):
self.assertEqual(mw3.before_event.call_count, 3)
self.assertEqual(mw3.after_event.call_count, 2)
self.assertEqual(mw4.before_event.call_count, 1)
self.assertEqual(mw4.after_event.call_count, 0)
self.assertEqual(mw4.after_event.call_count, 1)
# again, this time mw4 before + after_event should be triggered
# but after_event should abort execution
s._trigger_event('foo bar', '/ns', '123')
self.assertEqual(mw1.before_event.call_count, 4)
self.assertEqual(mw1.after_event.call_count, 2)
self.assertEqual(mw1.after_event.call_count, 3)
self.assertEqual(mw2.before_event.call_count, 0)
self.assertEqual(mw2.after_event.call_count, 0)
self.assertEqual(mw3.before_event.call_count, 4)
self.assertEqual(mw3.after_event.call_count, 2)
self.assertEqual(mw3.after_event.call_count, 3)
self.assertEqual(mw4.before_event.call_count, 2)
self.assertEqual(mw4.after_event.call_count, 1)
self.assertEqual(mw4.after_event.call_count, 2)
# only mw1 and mw2 should run completely
s._trigger_event('abc', '/', '123')
self.assertEqual(mw1.before_event.call_count, 5)
self.assertEqual(mw1.after_event.call_count, 3)
self.assertEqual(mw1.after_event.call_count, 4)
self.assertEqual(mw2.before_event.call_count, 1)
self.assertEqual(mw2.after_event.call_count, 1)
self.assertEqual(mw3.before_event.call_count, 4)
self.assertEqual(mw3.after_event.call_count, 2)
self.assertEqual(mw3.after_event.call_count, 3)
self.assertEqual(mw4.before_event.call_count, 2)
self.assertEqual(mw4.after_event.call_count, 1)
self.assertEqual(mw4.after_event.call_count, 2)
def test_on_bad_event_name(self, eio):
s = server.Server()

Loading…
Cancel
Save