= AMP - Asynchronous Messaging Protocol =
AMP is an [[http://en.wikipedia.org/wiki/Remote_procedure_call|RPC]] protocol for sending multiple asynchronous request/response pairs over the same<
>
connection. Requests and responses are both collections of key/value pairs. Keys are limited to 255<
> bytes in length, and values to 65,535 bytes ([[DontPanic|Why?]]). Any number of key/value pairs may be encoded<
>
in a single AMP packet.
AMP enables a rich set of Internet applications, from Client-Server "traditional" web-service operations, to highly<
>
custom and efficient RPC protocols, to distributed peer-to-peer messaging configurations. Once connected, the<
>
AMP protocol is symmetric, allowing either client or server to initiate an asynchronous request, provided the other<
>
side implements a handler for it.
For values, AMP defines a number of [[Types|Standard Data Types]], while keys are always strings.
AMP originally appeared in [[http://twistedmatrix.com|Twisted]], a Python networking framework, however AMP is '''''not Python-specific'''''.
Implementation are available for a variety of languages including Python, C, C#, Java, PHP and Erlang.<
>
See the [[Implementations|implementations page]] for details.
== Wire-Level Format ==
AMP uses a very simple and logical wire-level format, making it easy to implement in your language of choice.
Lets take a hypothetical '''Sum''' command which accepts two arguments, '''a''' and '''b''',<
>
and returns '''a+b''' as the argument named '''total'''.
Sample key/values of a request to the '''Sum''' command:<
><
>
||'''_ask''' ||23 ||
||'''_command''' ||Sum ||
||'''a''' ||13 ||
||'''b''' ||81 ||
Key/values of the response:<
><
>
||'''_answer''' ||23 ||
||'''total''' ||94 ||
That's all there is to it for a successful invocation of the '''Sum''' command!<
>
(We'll talk about error handling later.)
The special <>'''_ask'''<> key is a unique-per-connection identifier, which identifies individual requests<
>
on a particular connection (e.g. a TCP socket).
Its value is used with the special <>'''_answer'''<> key to indicate the original request that is being answered.
And obviously the special <>'''_command'''<> key indicates which command-handler should be invoked to process the request.
AMP is '''''asynchronous''''', meaning that requests and responses may be interleaved on the wire at will.<
>
You can happily fire off several requests before receiving any responses... and responses may be returned in ''any order''.
AMP is '''''full-duplex''''' (or '''''symmetric'''''), meaning that requests may be initiated from either side of the connection.
==== Sample Code ====
[[SumClientServer|Client and server for a "Sum" command is here.]] You will need Python (2.x) and [[http://twistedmatrix.com|Twisted]].
Or check out a [[AmpSharp|C# example]].
== The Actual Bytes ==
Keys and values in AMP are '''''length-prefixed'''''. Keys and values both use a 16-bit (2 byte) prefix value,<
>
'''''however''''', key length is restricted to 8 bits (1 byte). This means keys have a maximum length of 255 bytes,<
>
and values a maximum length of 65,535 bytes.
Prefix values are in '''''Network Byte Order''''' (big-endian).
So, the request above (a "packet", in AMP terms) looks like this on the wire:<
><
>
||0x00 0x04 || A key name of length 4 follows... ||
||0x5F 0x61 0x73 0x6B|| "_ask" ||
||0x00 0x02|| A value of length 2 follows... ||
||0x32 0x33|| "23" ||
||0x00 0x08|| A key name of length 8 follows... ||
||0x5F 0x63 0x6F 0x6D 0x6D 0x61 0x6E 0x64 || "_command" ||
||0x00 0x03|| A value of length 3 follows... ||
||0x53 0x75 0x6D|| "Sum" ||
||0x00 0x01|| A key name of length 1 follows... ||
||0x61|| "a" ||
||0x00 0x02|| A value of length 2 follows...||
||0x31 0x33|| "13" ||
||0x00 0x01|| A key name of length 1 follows... ||
||0x62|| "b" ||
||0x00 0x02|| A value of length 2 follows...||
||0x38 0x31|| "81" ||
||0x00 0x00|| A key of length 0. THIS MARKS THE END OF THE PACKET. ||
Pretty simple, eh? Key names are UTF-8 encoded strings.
The integer arguments, '''a''' and '''b''', are also UTF-8 strings.
The response is encoded like so:<
><
>
||0x00 0x07 || A key name of length 7 follows... ||
||0x5F 0x61 0x6E 0x73 0x77 0x65 0x72|| "_answer" ||
||0x00 0x02|| A value of length 2 follows... ||
||0x32 0x33|| "23" ||
||0x00 0x05|| A key name of length 5 follows... ||
||0x74 0x6F 0x74 0x61 0x6C || "total" ||
||0x00 0x02|| A value of length 2 follows... ||
||0x39 0x34 || "94" ||
||0x00 0x00|| A key of length 0. THIS MARKS THE END OF THE PACKET. ||
== Fire and Forget Commands ==
With AMP it is possible to fire off a request for which '''''no result is generated''''', or for which you simply '''''don't care what the result will be'''''.
You do this by omitting the <>'''_ask'''<> key. The only request key that is absolutely required by the wire-protocol is <>'''_command'''<>.
== Error Handling ==
When something goes wrong making an AMP call the response we get back will be quite different<
>
than the response we saw above. Instead of the <>'''_answer'''<> key, there is an <>'''_error'''<> key instead.
For example, the first thing you can do wrong is to make a request for an AMP command which doesn't exist:<
><
>
||'''_ask''' ||1 ||
||'''_command''' ||!GetSecretFile ||
||'''path''' ||/etc/shadow ||
Presumably the other side won't know anything about a '''!GetSecretFile''' command, so you get this:<
><
>
||'''_error''' ||1 ||
||'''_error_code''' ||UNHANDLED ||
||'''_error_description''' ||Unhandled Command: '!GetSecretFile' ||
The string "'''UNHANDLED'''" is a special code that all compliant AMP implementations use.
== Custom Errors ==
Let's imagine a '''Divide''' command which takes two integers, '''numerator''' and '''denominator''', and returns<
>
a floating-point value '''result''' (the result of '''numerator/denominator''').
Clearly, division can fail if you divide by zero, so we need to define what the <>'''_error_code'''<> will
be in that case.
Using the Twisted implementation we would do it something like this:<
><
>
{{{
#!code python
class Divide(amp.Command):
arguments = [('numerator', amp.Integer()),
('denominator', amp.Integer())]
response = [('result', amp.Float())]
errors = {ZeroDivisionError: 'ZERO_DIVISION'}
}}}
Then, if the responder for the '''Divide''' command raises a '''!ZeroDivisionError''' exception, the response will<
>
look something like this:<
><
>
||'''_error''' ||1 ||
||'''_error_code''' ||ZERO_DIVISION ||
||'''_error_description''' ||float division ||
AMP implementations should then raise this same exception on the client (calling) side. In<
>
languages without exceptions the raw error code should be made available so that special handling<
>
may be implemented.
== Unknown Errors ==
So what if something happens that wasn't planned for? AMP doesn't just encode information about<
>
arbitrary exceptions because this could represent a potentially harmful information leak. And because<
>
it's just better design to make things '''''explicit''''' rather than '''''implicit'''''.
Lets say the responder for a given command raises a '''!WorldExplodedException''' for which you have<
>
'''''not''''' defined an <>'''_error_code'''<>. (What would be the point, after all?)
You will get something like this:<
><
>
||'''_error''' ||1 ||
||'''_error_code''' ||UNKNOWN ||
||'''_error_description''' ||Unknown Error ||
The string "'''UNKNOWN'''" is a special code that all compliant AMP implementations use.
Well, that's all there is to error handling!
== Conclusion ==
As you've seen, AMP is simple, efficient and flexible. You can build virtually any kind of<
> high-level protocol on top of it.
Security features are achieved by layering AMP on top of [[http://en.wikipedia.org/wiki/Transport_Layer_Security|TLS]].
A standard set of rich [[Types|data types]] are shipped with the [[ReferenceImplementation|reference implementation.]]
Other [[Implementations|implementations]] may or may not provide the full set of types out of the box.<
>
However, adding custom data types with your own serialization format is fairly simple.
--
Happy AMP'ing!