Menu

Command Handlers

Jordan Halterman

Command handlers

Command handlers represent the commands that are executed from the client side, for example SEND, RECEIVE, and COUNT are all server command handlers (see commands.py in the plugins directory for more examples). This allows developers to extend the basic functionality provided by RedMQ to support custom messaging processes.

The ICommand interface

# Command handlers must implement the `IPlugin` and `ICommand`
# interfaces. So, first we need to import those interfaces.
# Note also that we import the redmq_server.server module.
# this is because command handler registration is done through
# a decorator as you will see in a moment.
from zope.interface import implements
from twisted.plugin import IPlugin
from redmq_server import server, ICommand

Defining the command

Following is an example implementation of the SEND command, demonstrating the general relationship between command handlers, the RedMQ protocol instance, and Redis storage handlers.

# Use the redmq_server.server.command decorator to register
# the command. The command class name should be in all uppercase
# as is the standard with RedMQ commands.
@server.command
class SEND:

  # Here we implement IPlugin and ICommand. IPlugin
  # will help the Twisted plugin system find the handler
  # and ICommand is required for RedMQ server commands.
  implements(IPlugin, ICommand)

  # A list of arguments required by the handler. Arguments will be received
  # from the client's command execution and passed to the command's execute()
  # method as keyword arguments in the format of the second tuple value.
  required_arguments = [
    ('address', str),
    ('message', dict),
  ]

  # A list of optional arguments matching the format of required_arguments.
  optional_arguments = []

  # A dictionary of error types and their string representation.
  # Any instances of the given exception that are raised will be
  # represented on the wire as the given string representation.
  errors = {RouterError: 'ROUTER_ERROR'}

  # Indicates whether this command requires a channel to be registered
  # with the protocol instance.
  requires_channel = True

  # Indicates whether this command provides a result.
  provides_result = True

  # The one method that's required by ICommand is the execute() method. This
  # method requires a response in the form {'_result': foo}. However, it is
  # recommended that developers extend redmq_server.plugins.commands.Command
  # for ease of use (see below for an example).
  # Note that the first argument to execute() will always be the protocol
  # instance. Following that, arguments will be provided as defined in
  # required_arguments and optional_argument - in that order.
  def execute(self, protocol, address, message):
    """
    Executes the SEND command.
    """
    # The channel source in SEND operations is always the source channel address.
    # Also, ensure the message address is set as the given address, though
    # this may be changed later in the router (in topic messages for instance).
    message['source'] = protocol.channel.address
    message['address'] = address

    # Route the message to the appropriate address. Any time a message is
    # being *sent* you should use the router's route() method. This will load
    # and execute the appropriate routing handler for the address.
    return {'_result': protocol.router.route(address, message)}

    # Alternatively, we can access the protocol's Redis instance using:
    redis = protocol.redis

    # Or we can access the protocol's Channel instance (for commands that require
    # a channel or are called after a channel is registered) through the protocol.
    # If no channel has been registered then protocol.channel will be None.
    channel = protocol.channel

Alternatively, using the redmq_server.plugins.commands.Command base class, we can define the SEND command in a much cleaner way:

from zope.interface import implements
from twisted.plugin import IPlugin
from redmq_server import server, ICommand
from redmq_server.plugins.commands import Command

@server.command
class SEND(Command):
  implements(IPlugin, ICommand)

  # The Command class defines default values for all the attributes
  # required by ICommand, so we can just define what we need here.
  required_arguments = [
    ('address', str),
    ('message', dict),
  ]

  # Since we're extending the base Command class, we only need to define
  # a do_command() method. The protocol instance is no longer passed as
  # the first argument because it has become an instance variable.
  def do_command(self, address, message):
    """
    Executes the SEND command.
    """
    message['source'] = self.protocol.channel.address
    message['address'] = address

    # Command.execute() will handle populating the response dictionary
    # and even fires a deferred thread for the command's execution.
    return self.protocol.router.route(address, message)