Implementing your own middleware

A middleware class provides four handler functions, one for processing each of the four kinds of messages transports, applications and dispatchers typically send and receive (i.e. inbound user messages, outbound user messages, event messages and failure messages).

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. Failure messages are not seen by applications at all and are allowed only so that certain middleware may be used on both transports and applications. Dispatchers both consume and publish all kinds of messages except failure messages.

Middleware is required to have the same interface as the BaseMiddleware class which is described below. Two subclasses, TransportMiddleware and ApplicationMiddleware, are provided but subclassing from these is just a hint as to whether a piece of middleware is intended for use on transports or applications (middleware for use on both or for dispatchers may inherit from BaseMiddleware). The two subclasses provide identical interfaces and no extra functionality.

class vumi.middleware.BaseMiddleware(name, config, worker)

Common middleware base class.

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.

The __init__() method should take exactly the following options so that your class can be instantiated from configuration in a standard way:

Parameters:
  • name (string) – Name of the middleware.
  • config (dict) – Dictionary of configuraiton items.
  • worker (vumi.service.Worker) – Reference to the transport or application being wrapped by this middleware.

If you are subclassing this class, you should not override __init__(). Custom setup should be done in setup_middleware() instead. The config class can be overidden by replacing the config_class class variable.

CONFIG_CLASS

alias of BaseMiddlewareConfig

setup_middleware()

Any custom setup may be done here.

Return type:Deferred or None
Returns:May return a deferred that is called when setup is complete.
teardown_middleware()

“Any custom teardown may be done here

Return type:Deferred or None
Returns:May return a Deferred that is called when teardown is complete
handle_consume_inbound(message, connector_name)

Called when an inbound transport user message is consumed.

The other methods listed below all function in the same way. Only the kind and direction of the message being processed differs.

By default, the handle_consume_* and handle_publish_* methods call their handle_* equivalents.

Parameters:
  • message (vumi.message.TransportUserMessage) – Inbound message to process.
  • connector_name (string) – The name of the connector the message is being received on or sent to.
Return type:

vumi.message.TransportUserMessage

Returns:

The processed message.

handle_publish_inbound(message, connector_name)

Called when an inbound transport user message is published.

See handle_consume_inbound().

handle_inbound(message, connector_name)

Default handler for published and consumed inbound messages.

See handle_consume_inbound().

handle_consume_outbound(message, connector_name)

Called when an outbound transport user message is consumed.

See handle_consume_inbound().

handle_publish_outbound(message, connector_name)

Called when an outbound transport user message is published.

See handle_consume_inbound().

handle_outbound(message, connector_name)

Default handler for published and consumed outbound messages.

See handle_consume_inbound().

handle_consume_event(event, connector_name)

Called when a transport event is consumed.

See handle_consume_inbound().

handle_publish_event(event, connector_name)

Called when a transport event is published.

See handle_consume_inbound().

handle_event(event, connector_name)

Default handler for published and consumed events.

See handle_consume_inbound().

handle_consume_failure(failure, connector_name)

Called when a failure message is consumed.

See handle_consume_inbound().

handle_publish_failure(failure, connector_name)

Called when a failure message is published.

See handle_consume_inbound().

handle_failure(failure, connector_name)

Called to process a failure message ( vumi.transports.failures.FailureMessage).

See handle_consume_inbound().

Example of a simple middleware implementation from vumi.middleware.logging:

class LoggingMiddleware(BaseMiddleware):
    """Middleware for logging messages published and consumed by
    transports and applications.

    Optional configuration:

    :param string log_level:
        Log level from :mod:`vumi.log` to log inbound and outbound
        messages and events at. Default is `info`.
    :param string failure_log_level:
        Log level from :mod:`vumi.log` to log failure messages at.
        Default is `error`.
    """
    CONFIG_CLASS = LoggingMiddlewareConfig

    def setup_middleware(self):
        log_level = self.config.log_level
        self.message_logger = getattr(log, log_level)
        failure_log_level = self.config.failure_log_level
        self.failure_logger = getattr(log, failure_log_level)

    def _log(self, direction, logger, msg, connector_name):
        logger("Processed %s message for %s: %s" % (
                direction, connector_name, msg.to_json()))
        return msg

    def handle_inbound(self, message, connector_name):
        return self._log(
            "inbound", self.message_logger, message, connector_name)

    def handle_outbound(self, message, connector_name):
        return self._log(
            "outbound", self.message_logger, message, connector_name)

    def handle_event(self, event, connector_name):
        return self._log("event", self.message_logger, event, connector_name)

    def handle_failure(self, failure, connector_name):
        return self._log(
            "failure", self.failure_logger, failure, connector_name)

How your middleware is used inside Vumi

While writing complex middleware, it may help to understand how a middleware class is used by Vumi transports and applications.

When a transport or application is started a list of middleware to load is read from the configuration. An instance of each piece of middleware is created and then setup_middleware() is called on each middleware object in order. If any call to setup_middleware() returns a Deferred, setup will continue after the deferred has completed.

Once the middleware has been setup it is combined into a MiddlewareStack. A middleware stack has two important methods apply_consume() and apply_publish() The former is used when a message is being consumed and applies the appropriate handlers in the order listed in the configuration file. The latter is used when a message is being published and applies the handlers in the reverse order.