Part of twisted.protocols View Source
This module implements AMP, the Asynchronous Messaging Protocol.
AMP is a protocol for sending multiple asynchronous request/response pairs over the same connection. Requests and responses are both collections of key/value pairs.
AMP is a very simple protocol which is not an application. This module is a "protocol construction kit" of sorts; it attempts to be the simplest wire-level implementation of Deferreds. AMP provides the following base-level features:
class Sum(amp.Command):
arguments = [('a', amp.Integer()),
('b', amp.Integer())]
response = [('total', amp.Integer())]
Once you have specified a command, you need to make it part of a
protocol, and define a responder for it. Here's a 'JustSum' protocol that
includes a responder for our 'Sum' command:
class JustSum(amp.AMP):
def sum(self, a, b):
total = a + b
print 'Did a sum: %d + %d = %d' % (a, b, total)
return {'total': total}
Sum.responder(sum)
Later, when you want to actually do a sum, the following expression
will return a Deferred which will fire with the result:
ClientCreator(reactor, amp.AMP).connectTCP(...).addCallback(
lambda p: p.callRemote(Sum, a=13, b=81)).addCallback(
lambda result: result['total'])
You can also define the propagation of specific errors in AMP. For
example, for the slightly more complicated case of division, we might
have to deal with division by zero:
class Divide(amp.Command):
arguments = [('numerator', amp.Integer()),
('denominator', amp.Integer())]
response = [('result', amp.Float())]
errors = {ZeroDivisionError: 'ZERO_DIVISION'}
The 'errors' mapping here tells AMP that if a responder to Divide
emits a ZeroDivisionError, then the other side should be
informed that an error of the type 'ZERO_DIVISION' has occurred. Writing
a responder which takes advantage of this is very simple - just raise
your exception normally:
class JustDivide(amp.AMP):
def divide(self, numerator, denominator):
result = numerator / denominator
print 'Divided: %d / %d = %d' % (numerator, denominator, total)
return {'result': result}
Divide.responder(divide)
On the client side, the errors mapping will be used to determine what
the 'ZERO_DIVISION' error means, and translated into an asynchronous
exception, which can be handled normally as any Deferred
would be:
def trapZero(result):
result.trap(ZeroDivisionError)
print "Divided by zero: returning INF"
return 1e1000
ClientCreator(reactor, amp.AMP).connectTCP(...).addCallback(
lambda p: p.callRemote(Divide, numerator=1234,
denominator=0)
).addErrback(trapZero)
For a complete, runnable example of both of these commands, see the
files in the Twisted repository:
doc/core/examples/ampserver.py doc/core/examples/ampclient.pyOn the wire, AMP is a protocol which uses 2-byte lengths to prefix keys and values, and empty keys to separate messages:
<2-byte length><key><2-byte length><value> <2-byte length><key><2-byte length><value> ... <2-byte length><key><2-byte length><value> <NUL><NUL> # Empty Key == End of MessageAnd so on. Because it's tedious to refer to lengths and NULs constantly, the documentation will refer to packets as if they were newline delimited, like so:
C: _command: sum C: _ask: ef639e5c892ccb54 C: a: 13 C: b: 81 S: _answer: ef639e5c892ccb54 S: total: 94
Notes:
Values are limited to the maximum encodable size in a 16-bit length, 65535 bytes.
Keys are limited to the maximum encodable size in a 8-bit length, 255 bytes. Note that we still use 2-byte lengths to encode keys. This small redundancy has several features:| Class | AmpError | Base class of all Amp-related exceptions. |
| Class | ProtocolSwitched | Connections which have been switched to other protocols can no longer |
| Class | OnlyOneTLS | This is an implementation limitation; TLS may only be started once per |
| Class | NoEmptyBoxes | You can't have empty boxes on the connection. This is raised when you |
| Class | InvalidSignature | You didn't pass all the required arguments. |
| Class | TooLong | One of the protocol's length limitations was violated. |
| Class | BadLocalReturn | A bad value was returned from a local command; we were unable to coerce it. |
| Class | RemoteAmpError | This error indicates that something went wrong on the remote end of the |
| Class | UnknownRemoteError | This means that an error whose type we can't identify was raised from the |
| Class | MalformedAmpBox | This error indicates that the wire-level protocol was malformed. |
| Class | UnhandledCommand | A command received via amp could not be dispatched. |
| Class | IncompatibleVersions | It was impossible to negotiate a compatible version of the protocol with |
| Class | AmpBox | I am a packet in the AMP protocol, much like a regular str:str dictionary. |
| Class | QuitBox | I am an AmpBox that, upon being sent, terminates the connection. |
| Class | _SwitchBox | Implementation detail of ProtocolSwitchCommand: I am a AmpBox which sets |
| Class | _DispatchMixin | I help AMP dispatch commands based on strings. |
| Function | _wireNameToPythonIdentifier | (Private) Normalize an argument name from the wire for use with Python |
| Class | _AmpParserBase | Base class for parsing AMP boxes. |
| Class | Argument | Base-class of all objects that take values from Amp packets and convert |
| Class | Integer | Convert to and from 'int'. |
| Class | String | Don't do any conversion at all; just pass through 'str'. |
| Class | Float | Encode floating-point values on the wire as their repr. |
| Class | Boolean | Encode True or False as "True" or "False" on the wire. |
| Class | Unicode | Encode a unicode string on the wire as UTF-8. |
| Class | Path | Encode and decode filepath.FilePath
instances as paths on the wire.
|
| Class | AmpList | Convert a list of dictionaries into a list of AMP boxes on the wire. |
| Class | Command | Subclass me to specify an AMP Command. |
| Class | _NoCertificate | This is for peers which don't want to use a local certificate. Used by |
| Class | _TLSBox | I am an AmpBox that, upon being sent, initiates a TLS connection. |
| Class | _LocalArgument | Local arguments are never actually relayed across the wire. This is just a |
| Class | StartTLS | Use, or subclass, me to implement a command that starts TLS. |
| Class | ProtocolSwitchCommand | Use this command to switch from something Amp-derived to a different |
| Class | AMP | This protocol is an AMP connection. See the module docstring for protocol |
| Class | _ParserHelper | Utility subclass to help with string parsing. |
| Function | _stringsToObjects | Convert an AmpBox to a dictionary of python objects, converting through a |
| Function | _objectsToStrings | Convert a dictionary of python objects to an AmpBox, converting through a |
(Private) Normalize an argument name from the wire for use with Python code. If the return value is going to be a python keyword it will be capitalized. If it contains any dashes they will be replaced with underscores.
The rationale behind this method is that AMP should be an inherently multi-language protocol, so message keys may contain all manner of bizarre bytes. This is not a complete solution; there are still forms of arguments that this implementation will be unable to parse. However, Python identifiers share a huge raft of properties with identifiers from many other languages, so this is a 'good enough' effort for now. We deal explicitly with dashes because that is the most likely departure: Lisps commonly use dashes to separate method names, so protocols initially implemented in a lisp amp dialect may use dashes in argument or command names.| Parameters | key | a str, looking something like 'foo-bar-baz' or 'from' |
| Returns | a str which is a valid python identifier, looking something like 'foo_bar_baz' or 'From'. | |
| Parameters | strings | an AmpBox (or dict of strings) |
| arglist | a list of 2-tuples of strings and Argument objects, as described in
Command.arguments.
| |
| proto | an AMP
instance.
| |
| Returns | the converted dictionary mapping names to argument objects. | |
| Parameters | objects | a dict mapping names to python objects |
| arglist | a list of 2-tuples of strings and Argument objects, as described in
Command.arguments.
| |
| proto | an AMP
instance.
| |
| Returns | the converted dictionary mapping names to encoded argument strings. | |