= 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!