AMP Routes

Overview

Normally, an AMP connection is between two AMP instances; each instance receives all AMP boxes sent by the other side and handles them by interpreting them as commands, responses to commands, or in some other way. This typically means that the logic for handling boxes on each side of the connection is completely defined by a single object. Sometimes it is useful to allow multiple objects, perhaps of different types, to participate in defining this logic.

epsilon.amprouter implements utilities which allow an arbitrary number of objects, providers of IBoxReceiver (for example, instances of AMP ), to define how received AMP boxes are interpreted. This is useful to multiplex unrelated AMP instances over a single TCP connection, to split up a single AMP protocol into multiple simpler protocols, and for many other purposes.

Readers should familiarize themselves with the following concepts in order to understand all sections of this document:

Routers

When working with routes, the object primarily of interest will be a I DON’T KNOW WHAT TO DO WITH THIS LINK!``Router`` instance which they can use to create new routes. They will use its Router.bindRoute method to set up whatever routes they require.

Servers

epsilon.amprouter does not define a command for creating new routes because different applications have different requirements for how new routes are set up. An application may want to negotiate about the IBoxReceiver implementation which is associated with a route, it may want to supply initial arguments to that object, it may want to do version negotiation, and so on. The first thing an application using routes must do, then, is to define a way to create new routes. Consider the following example which allows routes to be created with a NewRoute AMP command and associates them with a parameterized IBoxReceiver implementation.

# Copyright (c) 2008 Divmod.  See LICENSE for details.

import operator

from twisted.internet.protocol import ServerFactory
from twisted.protocols.amp import Unicode, Command, AMP

from epsilon.amprouter import Router


class NewRoute(Command):
    arguments = [('name', Unicode())]
    response = [('name', Unicode())]



class RoutingAMP(AMP):
    @NewRoute.responder
    def newRoute(self, name):
        route = self.boxReceiver.bindRoute(self.factory.routeProtocol())
        route.connectTo(name)
        return {'name': route.localRouteName}



class AMPRouteServerFactory(ServerFactory):
    protocol = RoutingAMP
    routeProtocol = None

    def buildProtocol(self, addr):
        router = Router()
        proto = self.protocol(router)
        proto.factory = self
        default = router.bindRoute(proto, None)
        default.connectTo(None)
        return proto



def connect(proto, router, receiver):
    route = router.bindRoute(receiver)
    d = proto.callRemote(NewRoute, name=route.localRouteName)
    d.addCallback(operator.getitem, 'name')
    d.addCallback(lambda name: route.connectTo(name))
    def connectionFailed(err):
        route.unbind()
        return err
    d.addErrback(connectionFailed)
    return d

AMPRouteServerFactory.buildProtocol creates new RoutingAMP instances, each with a new Router . The Router instance will become the RoutingAMP instance’s boxReceiver attribute. This is important for two reasons. First, it allows the router to work by causing all AMP boxes received from the connection to be delivered to the router to be dispatched appropriately. Second, it gives the RoutingAMP instance a reference to the Router instance; this is necessary so that new routes can be created.

After creating the Router and RoutingAMP , buildProtocol also sets up the RoutingAMP instance to be the default receiver by binding it to the None . All AMP boxes without routing information will be delivered to the default receiver. This is important because it allows the NewRoute command to be handled by the RoutingAMP instance.

RoutingAMP ‘s NewRoute responder uses self.boxReceiver , the Router instance provided by the factory, to bind the return value of self.factory.routeProtocol() to a new route. Then, it connects the route to the identifier specified in the NewRoute command. Finally, it returns the identifier of the route it has just created. Once this has happened, the route is completely set up on the server.

Finally, the connect function wraps up the necessary calls to routing methods and a use of the NewRoute command to form the client side of the setup.

First, let’s look at an example of using AMPRouteServerFactory and RoutingAMP to run a server.

# Copyright (c) 2008 Divmod.  See LICENSE for details.

from sys import stdout

from twisted.python.log import startLogging
from twisted.protocols.amp import Integer, Command, AMP
from twisted.internet import reactor

from route_setup import AMPRouteServerFactory


class Count(Command):
    response = [('value', Integer())]



class Counter(AMP):
    _valueCounter = 0

    @Count.responder
    def count(self):
        self._valueCounter += 1
        return {'value': self._valueCounter}



def main():
    startLogging(stdout)
    serverFactory = AMPRouteServerFactory()
    serverFactory.routeProtocol = Counter
    reactor.listenTCP(7805, serverFactory)
    reactor.run()



if __name__ == '__main__':
    main()

In this example, a simple counting protocol is hooked up to the server. Each route which is created is associated with a new instance of this protocol. The protocol does just one simple thing, it keeps track of how many times the Count command is issued to it and returns this value in the response to that command.

Next we’ll look at how a client can connect to this server, create new routes, and issue commands over them.

Clients

Just as servers must, clients must first set up a route before they can send boxes over it. A client uses the same methods as the server, Router.bindRoute and Route.connectTo , to set up a new route. Here’s an example which makes one TCP connection to an AMP server, sets up three routes, and then issues multiple commands over each of them.

# Copyright (c) 2008 Divmod.  See LICENSE for details.

import random

from twisted.internet.defer import Deferred, gatherResults
from twisted.internet.protocol import ClientCreator
from twisted.protocols.amp import AMP

from epsilon.react import react
from epsilon.amprouter import Router

from route_setup import connect
from route_server import Count


def display(value, id):
    print id, value


class CountClient(AMP):
    def __init__(self, identifier):
        AMP.__init__(self)
        self.identifier = identifier
        self.finished = Deferred()

    def startReceivingBoxes(self, sender):
        AMP.startReceivingBoxes(self, sender)

        counts = []
        for i in range(random.randrange(1, 5)):
            d = self.callRemote(Count)
            d.addCallback(display, self.identifier)
            counts.append(d)
        gatherResults(counts).chainDeferred(self.finished)



def makeRoutes(proto, router):
    router.bindRoute(proto, None).connectTo(None)

    finish = []
    for i in range(3):
        client = CountClient(i)
        finish.append(connect(proto, router, client))
        finish.append(client.finished)
    return gatherResults(finish)



def main(reactor):
    router = Router()
    cc = ClientCreator(reactor, AMP, router)
    d = cc.connectTCP('localhost', 7805)
    d.addCallback(makeRoutes, router)
    return d


if __name__ == '__main__':
    from twisted.internet import reactor
    react(reactor, main, [])

Note first how main creates an AMP with a Router instance. Note also how makeRoutes binds and connects the protocol to the default route. This mirrors the route setup which was done on the server and is necessary for the same reasons.

Once an AMP connection is set up and the default route is bound, makeRoutes uses the previously defined connect function to establish three new routes. Each route is associated with a CountClient instance which will issue several count commands and report the results. The results of each command are tracked so that when they have all been received the client can exit.

Table Of Contents

Previous topic

AMP Authentication

This Page