Skip to content

Commit eec2830

Browse files
Matthew WeppennaarMatthew Weppennaar
authored andcommitted
adding middleware docs
1 parent 0367da5 commit eec2830

3 files changed

Lines changed: 146 additions & 0 deletions

File tree

docs/index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ Welcome to vumi2's documentation!
1515
transports
1616
applications
1717
message_caches
18+
middleware
1819

1920

2021

docs/middleware.rst

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
Middleware
2+
==========
3+
Middleware provides additional functionality that can be attached to any existing transport,
4+
application or dispatcher worker. For example, middleware could log inbound and outbound messages, decode a unicoded message,
5+
store delivery reports in a database or modify a message.
6+
7+
Attaching middleware to your worker is fairly straight forward. Just extend your YAML configuration file with lines like:Attaching middleware to your worker is fairly straight forward. Just extend your YAML configuration file with lines like:
8+
9+
.. sourcecode:: YAML
10+
middlewares:
11+
- class_path: vumi2.middlewares.unidecoder.Unidecoder
12+
enable_for_connectors: ["mc_qa_expressway"]
13+
outbound_enabled: true
14+
15+
The middleware section contains a list of middleware items.
16+
Each item contains a :py:data:`class_path` class_path which is the full Python path to the class implementing the middleware,
17+
:py:data:`enable_for_connectors` enable_for_connectors this is the list of transports that the middleware will run on,
18+
and :py:data:`{type}_enabled` fields which describes which type of message the middleware is enabled on (inbound, outbound or event)
19+
20+
Multiple layers of middleware may be specified as follows:
21+
22+
.. sourcecode:: YAML
23+
middlewares:
24+
- class_path: vumi2.middlewares.unidecoder.Unidecoder
25+
enable_for_connectors: ["mc_qa_expressway"]
26+
outbound_enabled: true
27+
- class_path: vumi2.middlewares.logging.LoggingMiddlewarer
28+
enable_for_connectors: ["mc_qa_expressway"]
29+
outbound_enabled: true
30+
inbound_enabled: true
31+
event_enabled: true
32+
33+
You can think of the layers of middleware sitting on top of the underlying transport or application worker.
34+
Messages being consumed by the worker enter from the top and are processed by the middleware in the order you
35+
have defined them and eventually reach the worker at the bottom. Messages published by the worker start at the
36+
bottom and travel up through the layers of middleware before finally exiting the middleware at the top.
37+
38+
Adding your middleware:
39+
40+
.. toctree::
41+
:maxdepth: 2
42+
43+
middleware/building_your_own_middleware
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
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

Comments
 (0)