|
| 1 | +Implementing your own middleware |
| 2 | +========== |
| 3 | + |
| 4 | +A middleware class provides three handler functions, one for processing each of the three kinds of messages transports, applications and dispatchers typically send and receive (i.e. inbound user messages, outbound user messages, event messages) and three enabled functions for each of the types of messages |
| 5 | + |
| 6 | +Although transport and application middleware potentially both provide the same sets of handlers, the two make use of them in slightly different ways. Inbound messages and events are published by transports but consumed by applications while outbound messages are opposite. |
| 7 | + |
| 8 | +Middleware is required to have the same interface as the BaseMiddleware class which is described below. |
| 9 | + |
| 10 | +BaseMiddleware |
| 11 | +----------------- |
| 12 | + |
| 13 | +``class vumi2.middlewares.BaseMiddleware(name, config)`` |
| 14 | + |
| 15 | +Common middleware base class. |
| 16 | + |
| 17 | +This is a convenient definition of and set of common functionality for middleware classes. You need not subclass this and should not instantiate this directly. |
| 18 | + |
| 19 | +The ``__init__()`` method should take exactly the following options so that your class can be instantiated from configuration in a standard way: |
| 20 | + |
| 21 | +Parameters: |
| 22 | +----------------- |
| 23 | +.. py:data:: config |
| 24 | + :type: dict[str] |
| 25 | + Dictionary of configuraiton items. |
| 26 | + |
| 27 | +If you are subclassing this class, you should not override ``__init__()``. Custom setup should be done in |
| 28 | +``setup()`` instead. The config class can be overidden by replacing the ``config`` variable. |
| 29 | + |
| 30 | +:py:data:`setup()` |
| 31 | + |
| 32 | +Any custom setup may be done here. |
| 33 | + |
| 34 | +Return type: Deferred or None |
| 35 | +Returns: May return a deferred that is called when setup is complete. |
| 36 | + |
| 37 | +``teardown()`` |
| 38 | +“Any custom teardown may be done here |
| 39 | + |
| 40 | +Return type: Deferred or None |
| 41 | +Returns: May return a Deferred that is called when teardown is complete |
| 42 | + |
| 43 | +For each of inbound, outbound, and event message types, basemiddleware class implements two methods: |
| 44 | + |
| 45 | +``{type}_enabled(self, connector_name)``: |
| 46 | +Checks the configuration to determine if the middleware is enabled for this message type and connector. |
| 47 | + |
| 48 | +``handle_{type}(self, msg)``: |
| 49 | +Processes the message before passing it to the next handler. |
| 50 | + |
| 51 | +Example of a simple middleware implementation from ``vumi2.middlewares.logging:`` |
| 52 | + |
| 53 | +.. code-block:: python |
| 54 | + from logging import getLevelNamesMapping, getLogger |
| 55 | +
|
| 56 | + from attr import define |
| 57 | +
|
| 58 | + from vumi2.middlewares.base import BaseMiddleware, BaseMiddlewareConfig |
| 59 | +
|
| 60 | +
|
| 61 | + @define |
| 62 | + class LoggingMiddlewareConfig(BaseMiddlewareConfig): |
| 63 | + log_level: str = "info" |
| 64 | + logger_name: str = __name__ |
| 65 | +
|
| 66 | +
|
| 67 | + class LoggingMiddleware(BaseMiddleware): |
| 68 | + config: LoggingMiddlewareConfig |
| 69 | +
|
| 70 | + async def setup(self): |
| 71 | + self.log_level = getLevelNamesMapping()[self.config.log_level.upper()] |
| 72 | + self.logger = getLogger(self.config.logger_name) |
| 73 | +
|
| 74 | + def _log_msg(self, direction, msg, connector_name): |
| 75 | + self.logger.log( |
| 76 | + self.log_level, |
| 77 | + f"Processed {direction} message for {connector_name}: {msg}", |
| 78 | + ) |
| 79 | + return msg |
| 80 | +
|
| 81 | + async def handle_inbound(self, message, connector_name): |
| 82 | + return self._log_msg("inbound", message, connector_name) |
| 83 | +
|
| 84 | + async def handle_outbound(self, message, connector_name): |
| 85 | + return self._log_msg("outbound", message, connector_name) |
| 86 | +
|
| 87 | + async def handle_event(self, event, connector_name): |
| 88 | + return self._log_msg("event", event, connector_name) |
| 89 | +
|
| 90 | +How your middleware is used inside Vumi: |
| 91 | +----------------- |
| 92 | + |
| 93 | +While writing complex middleware, it may help to understand how a middleware class is used by Vumi transports and applications. |
| 94 | + |
| 95 | +When a transport or application is started a list of middleware to load is read from the configuration. |
| 96 | +An instance of each piece of middleware is created and then ``setup()`` is called on each middleware object in |
| 97 | +order within the ``setup()`` of the worker |
| 98 | + |
| 99 | +``middleware_{type}_handler function`` (e.g., middleware_outbound_handler) of BaseWorker of each message type. This function will: |
| 100 | +Filter the middleware list based on connector_name and the middleware's ``{type}_enabled`` method. |
| 101 | +Create a decorated handler function that sequentially applies each enabled middleware's ``handle_{type}`` method to the message. |
| 102 | +Return the decorated handler. This decorated handler is then used in setting up the connection |
0 commit comments