AMPv2 Conversations =========================================================== .. toctree:: :maxdepth: 2 :caption: Contents: conversations_v2 **AMPv2** uses a simple wire 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 response argument named total. Sample key/values of a request to the ``Sum`` command: ======== ===== Key Value ======== ===== _ask 42 _command Sum a 13 b 81 ======== ===== Key/values of the response: ======= ===== Key Value ======= ===== _answer 42 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 (in response packets) to indicate the original request that is being answered. And the special ``_command`` key indicates which command-handler should be invoked to process the request. **AMPv2** is asynchronous, meaning that requests and responses may be interleaved on the wire at will. You may happily fire off several requests before receiving any responses. And responses may be returned in any order. This is often quite useful, though you may wish to enforce certain ordering for your **AMPv2** calls depending on the requirements of your application; **AMPv2** does not preclude you from doing this. Many other Remoting protocols enforce certain ordering, and do not fully support asynchronous interleaving of requests and responses on the wire. **AMPv2** is full-duplex (or symmetric), meaning that requests may be initiated from either side of the connection - provided that the peer implements a handler for it. Each side of the connection may choose to (and often will) implement a different set of Command handlers, or none at all. Sample Code +++++++++++ :doc:`Example Sum command is here `. You will need Python (2.x or 3.x) and `Twisted `_. Encoding AMP Conversations ++++++++++++++++++++++++++ Both in **AMPv1** and **AMPv2** any number of key/value pairs may be encoded in a single **AMP** packet. 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. All prefix values are in Network Byte Order (big-endian). In **AMPv1** :doc:`values are limited to 65,535 bytes `, but in **AMPv2** values of any length are automatically supported! A value length of ``0xffff`` (or 65,535) indicates a *continuation* - an additional length prefix is placed on the wire, *after the previous* ``0xffff`` *prefix and 64k of data*. The continuation prefix may be zero indicating the end of the data, greater than zero, indicating that number of bytes to follow, or ``0xffff`` indicating 64k of data to follow, *followed by an additional continuation*. On the wire, since a key-length prefix always comes first, but only the 2nd byte (in Network Byte Order) is used, it means that an **AMP** conversation always begins with a ``null`` byte. This has some advantages, as it means that you can, for instance, operate an **HTTP(S)** server and an **AMP** server on the same TCP port. Since an **HTTP(S)** client always begins the conversation with a ``non-null`` byte, it means you may easily distinguish between an **HTTP(S)** client and an **AMP** client. So, the request above (a *packet* or *box*, in **AMP** terms) looks like this on the wire: =================== ======================================================== Key Value =================== ======================================================== 0x00 0x04 A key name of length 4 follows... 0x5F 0x61 0x73 0x6B The utf-8 string "_ask" 0x00 0x02 A value of length 2 follows... 0x32 0x33 The utf-8 string "23" 0x00 0x08 A key name of length 8 follows... 0x5F 0x63 0x6F 0x6D 0x6D 0x61 0x6E 0x64 The utf-8 string "_command" 0x00 0x03 A value of length 3 follows... 0x53 0x75 0x6D The utf-8 string "Sum" 0x00 0x01 A key name of length 1 follows... 0x61 The utf-8 string "a" 0x00 0x02 A value of length 2 follows... 0x31 0x33 The utf-8 string "13" 0x00 0x01 A key name of length 1 follows... 0x62 The utf-8 string "b" 0x00 0x02 A value of length 2 follows... 0x38 0x31 The utf-8 string "81" 0x00 0x00 A key of length 0. THIS MARKS THE END OF THE BOX/PACKET. =================== ======================================================== Key names are UTF-8 encoded strings. Technically you may use any arbitrary byte sequence as a key name, but there is no practical reason for ever doing this - stick to UTF-8 strings. Also, some implementations of **AMP** may not work correctly with key names that are not UTF-8. The integer arguments, ``a`` and ``b``, are also encoded as UTF-8 strings. Many common **AMP** types are encoded as the Python ``repr()`` of the object being encoded. This is handy for ease-of-implemetation and debugging your implementations with a packet-capture tool; many other languages have compatible *to-string* functions/methods making **AMP** a good choice for implementing in a new language. The response is encoded like so: =================== ======================================================== Key Value =================== ======================================================== 0x00 0x07 A key name of length 7 follows... 0x5F 0x61 0x6E 0x73 0x77 0x65 0x72 The utf-8 string "_answer" 0x00 0x02 A value of length 2 follows... 0x32 0x33 The utf-8 string "23" 0x00 0x05 A key name of length 5 follows... 0x74 0x6F 0x74 0x61 0x6C The utf-8 string "total" 0x00 0x02 A value of length 2 follows... 0x39 0x34 The utf-8 string "94" 0x00 0x00 A key of length 0. THIS MARKS THE END OF THE BOX/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 will be quite different than the response seen above. Instead of the ``_answer`` key, there is an ``_error`` key instead. For example, the first thing you can do wrong is making a request for an **AMP** command which doesn't exist: ======== ============= Key Value ======== ============= _ask 1 _command GetSecretFile path /etc/shadow ======== ============= Presumably the other side won't know anything about a ``GetSecretFile`` command, so you get this: ================== ================================== Key Value ================== ================================== _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``). On most platforms, division will fail if you divide by zero, so if you need special handling for this error condition it is necessary to define what the ``_error_code`` will be. Using the `Twisted `_ implementation it would be done something like this:: 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: =================== ============== Key Value =================== ============== _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 the authors' consider it better design to make error-handling explicit rather than implicit. Lets say the responder for a given command raises a *UniverseImplodedException* for which you have not defined an ``_error_code``. (What would be the point, after all?) You will get something like this: ================== ============= Key Value ================== ============= _error 1 _error_code UNKNOWN _error_description Unknown Error ================== ============= The string ``UNKNOWN`` is a special code that all compliant **AMP** implementations use. For ``_error_code`` the values ``UNHANDLED`` and ``UNKNOWN`` are reserved, as detailed above. But to indicate custom errors, any arbitrary value may be used. However, by convention, an all-caps ``SNAKE_CASE`` UTF-8 encoded string is recommended for custom error codes. For ``_error_description`` any arbitrary UTF-8 encoded string may be used - it is up to the application to decide what information to provide here. Well, that's all there is to error-handling in **AMP**! Conclusion ++++++++++ As you've seen, **AMPv2** is simple, efficient and highly flexible. You can build virtually any kind of protocol, suitable for your application, on top of **AMP**. Security features are achieved by layering **AMP** on top of `TLS `_. **AMP** runs just as well over ordinary TCP sockets as it does over SSL/TLS sockets. Or you could run **AMP** over ``stdin``/``stdout`` between multiple processes - as the protocol itself is fully transport-agnostic. A standard set of :doc:`rich data types ` are shipped with the reference implementation. Other :doc:`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 typically very easy. -- Happy **AMP**'ing!