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 * ``after_event(*args)`` is called after the event handler with the
event name, the namespace and the list of values the event handler event name, the namespace and the list of values the event handler
returned. It may alter that values eventually. 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 event handler with the event name and namespace as arguments. If its
return value resolves to ``True`` the middleware is not applied to that return value resolves to ``True`` the middleware is ignored and not
particular event handler. 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.
Event handler middlewares can be chained. The ``before_event`` methods Event handler middlewares can be chained. The ``before_event`` methods
will be executed in the same order the middlewares were added, the will be executed in the same order the middlewares were added, the
``after_event`` methods are called in reversed order. ``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 Note that you can also use classes (objects of type ``type``) instead
of instances as event handler middlewares. If you do so, they are of instances as event handler middlewares. If you do so, they are
instantiated with no arguments every time they are used to process instantiated with no arguments every time they are used to process
@ -290,11 +307,14 @@ Example:
from socketio import server from socketio import server
class MyMiddleware(object): 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 # This middleware won't be applied to connect and disconnect
# event handlers. # event handlers.
return event in ("connect", "disconnect") return event in ("connect", "disconnect")
def handle_exception(self, event, namespace, e):
return "Exception: %r" % e
def before_event(self, event, namespace, args): def before_event(self, event, namespace, args):
print("before_event called with:", args) print("before_event called with:", args)
if len(args) < 2 or args[1] != "secret": if len(args) < 2 or args[1] != "secret":

90
socketio/server.py

@ -469,34 +469,80 @@ class Server(object):
for middleware in middlewares: for middleware in middlewares:
if isinstance(middleware, type): if isinstance(middleware, type):
middleware = middleware() middleware = middleware()
if not hasattr(middleware, 'ignore_event') or \ if not hasattr(middleware, 'ignore_for') or \
not middleware.ignore_event(event, namespace): not middleware.ignore_for(event, namespace):
_middlewares.append(middleware) _middlewares.append(middleware)
if not _middlewares: if not _middlewares:
return handler return handler
def wrapped(*args): def wrapped(*args):
args = list(args) def handle_exception(middlewares_processed, exc):
for middleware in \
for middleware in _middlewares: reversed(_middlewares[:middlewares_processed]):
if hasattr(middleware, 'before_event'): if hasattr(middleware, 'handle_exception'):
result = middleware.before_event(event, namespace, args) try:
if result is not None: result = middleware.handle_exception(
return result event, namespace, exc)
# exception has been cought
result = handler(*args) return middlewares_processed, result
if result is None: except Exception as e:
data = [] exc = e
elif isinstance(result, tuple): middlewares_processed -= 1
data = list(result) if exc is not None:
else: # Exception hasn't been cought by any middleware,
data = [result] # 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): args = list(args)
if hasattr(middleware, 'after_event'): middlewares_processed = 0
result = middleware.after_event(event, namespace, data) result = None
if result is not None: try:
return result # 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) return tuple(data)

21
tests/test_server.py

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

Loading…
Cancel
Save