| .. _tutorial: |
| |
| ######## |
| Tutorial |
| ######## |
| |
| ============ |
| Hello World! |
| ============ |
| |
| Tradition dictates that we start with hello world! However rather than |
| simply striving for the shortest program possible, we'll aim for a |
| more illustrative example while still restricting the functionality to |
| sending and receiving a single message. |
| |
| .. literalinclude:: ../examples/helloworld.py |
| :lines: 21- |
| :linenos: |
| |
| You can see the import of :py:class:`~proton.reactor.Container` from ``proton.reactor`` on the |
| second line. This is a class that makes programming with proton a |
| little easier for the common cases. It includes within it an event |
| loop, and programs written using this utility are generally structured |
| to react to various events. This reactive style is particularly suited |
| to messaging applications. |
| |
| To be notified of a particular event, you define a class with the |
| appropriately named method on it. That method is then called by the |
| event loop when the event occurs. |
| |
| We define a class here, ``HelloWorld``, which handles the key events of |
| interest in sending and receiving a message. |
| |
| The ``on_start()`` method is called when the event loop first |
| starts. We handle that by establishing our connection (line 13), a |
| sender over which to send the message (line 15) and a receiver over |
| which to receive it back again (line 14). |
| |
| The ``on_sendable()`` method is called when message can be transferred |
| over the associated sender link to the remote peer. We send out our |
| ``Hello World!`` message (line 18), then close the sender (line 19) as |
| we only want to send one message. The closing of the sender will |
| prevent further calls to ``on_sendable()``. |
| |
| The ``on_message()`` method is called when a message is |
| received. Within that we simply print the body of the message (line |
| 22) and then close the connection (line 23). |
| |
| Now that we have defined the logic for handling these events, we |
| create an instance of a :py:class:`~proton.reactor.Container`, pass it |
| our handler and then enter the event loop by calling |
| :py:meth:`~proton.reactor.Container.run()`. At this point, control |
| passes to the container instance, which will make the appropriate |
| callbacks to any defined handlers. |
| |
| To run the example, you will need to have a broker (or similar) |
| accepting connections on that url either with a queue (or topic) |
| matching the given address or else configured to create such a queue |
| (or topic) dynamically. There is a simple broker.py script included |
| alongside the examples that can be used for this purpose if |
| desired. (It is also written using the API described here, and as such |
| gives an example of a slightly more involved application). |
| |
| ==================== |
| Hello World, Direct! |
| ==================== |
| |
| Though often used in conjunction with a broker, AMQP does not |
| *require* this. It also allows senders and receivers to communicate |
| directly if desired. |
| |
| Let's modify our example to demonstrate this. |
| |
| .. literalinclude:: ../examples/helloworld_direct.py |
| :lines: 21- |
| :emphasize-lines: 12,22-23,25-26 |
| :linenos: |
| |
| The first difference, on line 12, is that rather than creating a |
| receiver on the same connection as our sender, we listen for incoming |
| connections by invoking the |
| :py:meth:`~proton.reactor.Container.listen()` method on the |
| container. |
| |
| As we only need then to initiate one link, the sender, we can do that |
| by passing in a url rather than an existing connection, and the |
| connection will also be automatically established for us. |
| |
| We send the message in response to the ``on_sendable()`` callback and |
| print the message out in response to the ``on_message()`` callback |
| exactly as before. |
| |
| However we also handle two new events. We now close the connection |
| from the senders side once the message has been accepted (line |
| 23). The acceptance of the message is an indication of successful |
| transfer to the peer. We are notified of that event through the |
| ``on_accepted()`` callback. Then, once the connection has been closed, |
| of which we are notified through the ``on_closed()`` callback, we stop |
| accepting incoming connections (line 26) at which point there is no |
| work to be done and the event loop exits, and the run() method will |
| return. |
| |
| So now we have our example working without a broker involved! |
| |
| ============================= |
| Asynchronous Send and Receive |
| ============================= |
| |
| Of course, these ``HelloWorld!`` examples are very artificial, |
| communicating as they do over a network connection but with the same |
| process. A more realistic example involves communication between |
| separate processes (which could indeed be running on completely |
| separate machines). |
| |
| Let's separate the sender from the receiver, and let's transfer more than |
| a single message between them. |
| |
| We'll start with a simple sender. |
| |
| .. literalinclude:: ../examples/simple_send.py |
| :lines: 21- |
| :linenos: |
| |
| As with the previous example, we define the application logic in a |
| class that handles various events. As before, we use the |
| ``on_start()`` event to establish our sender link over which we will |
| transfer messages and the ``on_sendable()`` event to know when we can |
| transfer our messages. |
| |
| Because we are transferring more than one message, we need to keep |
| track of how many we have sent. We'll use a ``sent`` member variable |
| for that. The ``total`` member variable will hold the number of |
| messages we want to send. |
| |
| AMQP defines a credit-based flow control mechanism. Flow control |
| allows the receiver to control how many messages it is prepared to |
| receive at a given time and thus prevents any component being |
| overwhelmed by the number of messages it is sent. |
| |
| In the ``on_sendable()`` callback, we check that our sender has credit |
| before sending messages. We also check that we haven't already sent |
| the required number of messages. |
| |
| The ``send()`` call on line 21 is of course asynchronous. When it |
| returns, the message has not yet actually been transferred across the |
| network to the receiver. By handling the ``on_accepted()`` event, we |
| can get notified when the receiver has received and accepted the |
| message. In our example we use this event to track the confirmation of |
| the messages we have sent. We only close the connection and exit when |
| the receiver has received all the messages we wanted to send. |
| |
| If we are disconnected after a message is sent and before it has been |
| confirmed by the receiver, it is said to be ``in doubt``. We don't |
| know whether or not it was received. In this example, we will handle |
| that by resending any in-doubt messages. This is known as an |
| 'at-least-once' guarantee, since each message should eventually be |
| received at least once, though a given message may be received more |
| than once (i.e. duplicates are possible). In the ``on_disconnected()`` |
| callback, we reset the sent count to reflect only those that have been |
| confirmed. The library will automatically try to reconnect for us, and |
| when our sender is sendable again, we can restart from the point we |
| know the receiver got to. |
| |
| Now let's look at the corresponding receiver: |
| |
| .. literalinclude:: ../examples/simple_recv.py |
| :lines: 21- |
| :linenos: |
| |
| Here we handle the ``on_start()`` by creating our receiver, much like |
| we did for the sender. We also handle the ``on_message()`` event for |
| received messages and print the message out as in the ``Hello World!`` |
| examples. However, we add some logic to allow the receiver to wait for |
| a given number of messages, then close the connection and exit. We |
| also add some logic to check for and ignore duplicates, using a simple |
| sequential id scheme. |
| |
| Again, though sending between these two examples requires some sort of |
| intermediary process (e.g. a broker), AMQP allows us to send messages |
| directly between two processes without this if we so wish. In that |
| case, one of the processes needs to accept incoming socket connections. |
| Let's create a modified version of the receiving example that does this: |
| |
| .. literalinclude:: ../examples/direct_recv.py |
| :lines: 21- |
| :emphasize-lines: 14,26 |
| :linenos: |
| |
| There are only two differences here. On line 14, instead of initiating |
| a link (and implicitly a connection), we listen for incoming |
| connections. On line 26, when we have received all the expected |
| messages, we then stop listening for incoming connections by closing |
| the listener object. |
| |
| You can use the original send example now to send to this receiver |
| directly. (Note: you will need to stop any broker that is listening on |
| the 5672 port, or else change the port used by specifying a different |
| address to each example via the -a command line switch). |
| |
| We could also modify the original sender to allow the original |
| receiver to connect to it. Again, that just requires two modifications: |
| |
| .. literalinclude:: ../examples/direct_send.py |
| :lines: 21- |
| :emphasize-lines: 16,29 |
| :linenos: |
| |
| As with the modified receiver, instead of initiating establishment of |
| a link, we listen for incoming connections on line 16 and then on line |
| 29, when we have received confirmation of all the messages we sent, we |
| can close the listener in order to exit. The symmetry in the |
| underlying AMQP that enables this is quite unique and elegant, and in |
| reflecting this the proton API provides a flexible toolkit for |
| implementing all sorts of interesting intermediaries (the broker.py |
| script provided as a simple broker for testing purposes provides an |
| example of this). |
| |
| To try this modified sender, run the original receiver against it. |
| |
| ================ |
| Request/Response |
| ================ |
| |
| A common pattern is to send a request message and expect a response |
| message in return. AMQP has special support for this pattern. Let's |
| have a look at a simple example. We'll start with the 'server', |
| i.e. the program that will process the request and send the |
| response. Note that we are still using a broker in this example. |
| |
| Our server will provide a very simple service: it will respond with |
| the body of the request converted to uppercase. |
| |
| .. literalinclude:: ../examples/server.py |
| :lines: 21- |
| :linenos: |
| |
| The code here is not too different from the simple receiver |
| example. When we receive a request however, we look at the |
| :py:attr:`~proton.Message.reply_to` address on the |
| :py:class:`~proton.Message` and create a sender for that over which to |
| send the response. We'll cache the senders in case we get further |
| requests with the same reply_to. |
| |
| Now let's create a simple client to test this service out. |
| |
| .. literalinclude:: ../examples/client.py |
| :lines: 21- |
| :linenos: |
| |
| As well as sending requests, we need to be able to get back the |
| responses. We create a receiver for that (see line 15), but we don't |
| specify an address, we set the dynamic option which tells the broker |
| to create a temporary address over which we can receive our responses. |
| |
| We need to use the address allocated by the broker as the reply_to |
| address of our requests, so we can't send them until the broker has |
| confirmed our receiving link has been set up (at which point we will |
| have our allocated address). To do that, we add an |
| ``on_link_opened()`` method to our handler class, and if the link |
| associated with the event is the receiver, we use that as the trigger to |
| send our first request. |
| |
| Again, we could avoid having any intermediary process here if we |
| wished. The following code implementas a server to which the client |
| above could connect directly without any need for a broker or similar. |
| |
| .. literalinclude:: ../examples/server_direct.py |
| :lines: 21- |
| :linenos: |
| |
| Though this requires some more extensive changes than the simple |
| sending and receiving examples, the essence of the program is still |
| the same. Here though, rather than the server establishing a link for |
| the response, it relies on the link that the client established, since |
| that now comes in directly to the server process. |
| |
| Miscellaneous |
| ============= |
| |
| Many brokers offer the ability to consume messages based on a |
| 'selector' that defines which messages are of interest based on |
| particular values of the headers. The following example shows how that |
| can be achieved: |
| |
| .. literalinclude:: ../examples/selected_recv.py |
| :lines: 21- |
| :emphasize-lines: 16 |
| :linenos: |
| |
| When creating the receiver, we specify a Selector object as an |
| option. The options argument can take a single object or a |
| list. Another option that is sometimes of interest when using a broker |
| is the ability to 'browse' the messages on a queue, rather than |
| consuming them. This is done in AMQP by specifying a distribution mode |
| of 'copy' (instead of 'move' which is the expected default for |
| queues). An example of that is shown next: |
| |
| .. literalinclude:: ../examples/queue_browser.py |
| :lines: 21- |
| :emphasize-lines: 11 |
| :linenos: |