Chapter 4. Advanced STOMP

In Chapters 2 and 3, we used STOMP to send and receive messages from a native iOS application and a web application. STOMP provides additional features that we did not use to write these applications. In this chapter, we will take a tour of all the features provided by STOMP.

This chapter covers the latest version of the protocol at the time of this writing (i.e., STOMP 1.2, which was released on October 22, 2012).

As we present these features, we will show how to use them from either StompKit or stomp.js. The API may vary depending on the platform and language, but the concept remains the same. If you ever need to use STOMP from another language or platform, you will have to adapt to the library API—but the underlying concept will still apply.

Clients and brokers can use these features by sending additional frames (defined in the protocol) or by using message headers. The STOMP protocol defines frames to provide transactions, a combination of frames and headers for message acknowledgment, and headers for heartbeat negotiation.

In this chapter, we will describe the features provided by the STOMP protocol that the client and broker must support in order to be compliant.

STOMP is also extensible, and clients and brokers can support additional features by using headers not defined in the protocol. We will describe these additional features in .

In any case, the features (defined in the protocol or supported only by some brokers) rely on the structure of STOMP frames.

Frame Representation

All data exchanged between a client and a broker is done using STOMP frames. STOMP is modeled on HTTP and its frames follow a similar structure. Each frame is composed of three elements (Example 4-1):

  • A command

  • A set of headers

  • A payload (optional)

Example 4-1. STOMP Frame Structure
COMMAND 1
header1:value1 2
header2:value2 
3
payload^@ 4
1

A frame starts with a  command string followed by an end-of-line (EOL)

2

Header entries follow the format <key>:<value> and are ended by EOL

3

A blank line separates the set of headers from the payload

4

A frame is ended by a NULL octet (represented by ^@ in ASCII)

STOMP is based on text (using UTF-8 for its default encoding), but it can also transmit binary data in its payload by specifying an alternative encoding.

In Chapters 2 and 3, we were sending JSON data in the messages. We were setting the string representation of the JSON structure in the message body and setting the content-type header to application/json; charset=utf-8.

STOMP uses frames not only to send messages (with the SEND command) and deliver them (with the MESSAGE command) but also for all its operation commands (such as CONNECT, DISCONNECT, SUBSCRIBE, etc.).

Headers

STOMP defines only a handful of headers for its different frames. 

It is also possible to add other headers when sending a frame, as long as it does not collide with the headers already specified in the STOMP protocol.

An application can use headers instead of the message’s payload to transmit data. The fundamental difference between headers and payload is that headers can be read (and modified) by the STOMP brokers. Payload is treated as a black box and the broker never reads or modifies it.

In practice, headers are most often used to specify additional features or constraints to the message processing.

Authentication

In Chapters 2 and 3, the STOMP clients are connected to the broker anonymously. We do not pass any user credentials that the broker could use to authenticate the user and check whether he or she can send and/or receive messages.

If the STOMP broker is configured to accept secured connections, the client needs to pass login and passcode parameters when it connects to the STOMP broker.

Note

By default, ActiveMQ accepts anonymous connections. To change its security settings to authenticate users and grant them restricted privileges (so that they can only receive messages from example), consult its security documentation page.

StompKit Example

To use an authenticated connection with StompKit, you need to pass the login and passcode parameters to the headers dictionary when calling the connectWithHeaders:completionHandler method on the STOMPClient. StompKit defines the kHeaderLogin and kHeaderPasscode constants to represent the headers keys:

- (void)connect
{
    NSLog(@"Connecting...");

    NSString *login = @"...";
    NSString *passcode = @"...";
    [self.client connectWithHeaders:@{ @"client-id": self.deviceID,
                                       kHeaderLogin: login,
                                       kHeaderPasscode: passcode }
                  completionHandler:^(STOMPFrame *connectedFrame,
                                      NSError *error) {
                      ...
                  }];
}

stomp.js Example

A stomp.js client can be authenticated by adding the login and passcode headers to the first parameter of its connect method:

function connect(url) {
  // create the STOMP client
  client = Stomp.client(url);

  ...

  var connectedCallback =  function(frame) {
    ...
  };

  var login = ...;
  var passcode = ...;

  client.connect({
      login: login,
      passcode: passcode,
    }, connectedCallback);
}

Message Acknowledgment

Message acknowledgment is a feature available to STOMP consumers.

When the broker delivers a message to a consumer, there is a transfer of responsibility between the broker and the consumer to determine which is the owner of the message. The consumer becomes responsible for the message by acknowledging the message.

By default, the STOMP broker will consider the message automatically acknowledged when it is delivered to the consumer.

However, there are cases in which the consumer may prefer to explicitly acknowledge the message. It leaves a window of opportunity to determine whether it can handle the message or not. For example, the client needs to write the message payload in a data store. There may be issues with opening a connection to the data store and the client could choose to acknowledge the message only after having successfully written its body to the data store. In case of failure, it will instead nack the message (explicitly refuse to take ownership of it). When the STOMP broker is informed of this negative acknowledgment, it may then decide to deliver the message to another consumer subscribed to the destination or try again some time later depending on its configuration.

The consumer specifies its type of acknowledgement when it subscribes to a destination. STOMP supports three types of acknowledgements:

  • auto (by default)

  • client

  • client-individual

If the client does not specify any type of acknowledgment or if it uses default, it does not need to send any acknowledgment; the STOMP broker will consider the message acknowledged as soon as it is delivered to the client.

If client or client-individual is used, the consumer must send acknowledgments to the server with the message-id of the message that is acknowledged. The difference between client and client-individual is that client will acknowledge the message and all other messages previously delivered to the consumer client-individual will only acknowledge the message and no other messages. The consumer acknowledges a message by sending an ACK frame to the STOMP broker.

If client and client-individual are used, the consumer can explicitly refuse to handle the message by sending a NACK frame, which is a negative acknowledgment.

StompKit Example

The message acknowledgment is specified when the STOMPClient subscribes to a destination by calling its subscribeTo:headers:messageHandler: method. To specify a client or client-individual acknowledgment, you must set an ack header. StompKit.h defines constants to represent the header name, kHeaderAck, and its accepted values, kAckAuto, kAckClient, and kAckClientIndividual.

The STOMPMessage parameter of the messageHandler has two methods, ack and nack, to acknowledge or nack the message, respectively.

If the ack header is not set or if it set to auto, message acknowledgment is performed by the broker and calling the STOMPMessage’s ack and nack methods will do nothing:

// use client acknowledgment
[self.client subscribeTo:destination
                 headers:@{kHeaderAck: kAckClient}
          messageHandler:^(STOMPMessage *message) {
              // process the message
              // ...

              // acknowledge it
              [message ack];
              // or nack it with
              // [message nack]
          }];

stomp.js Example

The client can specify the type of acknowledgment by passing a dictionary with the ack header as the last parameter of its subscribe message.

The message parameter of the subscribe callback has two methods, ack and nack, to acknowledge or nack the message, respectively. If the acknowledgment type is auto (or if it is not specified at all), these ack and nack methods will do nothing:

client.subscribe(destination,
  function(message) {
    // process the message
    ...

    // acknowledge it
    message.ack();
    // or you can nack it by calling message.nack() instead.
  },
  {"ack": "client"}
);

There are many use cases where it is not necessary to use explicit acknowledgment.

For example, in the Locations web application, we do not need to acknowledge every message that we receive from the devices with its GPS position. At worst, there may be a problem displaying the position, but we know that other messages will come later to update the device’s position.

Besides, acknowledging every message would have a performance cost. Sending the acknowledgment back to the broker would involve an additional network trip for every message.

The Locations iOS application is also consuming messages from the device’s text queue. These messages may be more important to acknowledge explicitly. We could enhance the application by letting the user confirm that it has read the message’s text and the message would be acknowledged after this confirmation only.

We could also let the user reject it by negatively acknowledging the message. In that case, these nacked messages would be handled back by the STOMP broker. Depending on the broker you use, it may provide additional features to handle these messages. Messages that are nacked multiple times from a destination are commonly sent to a dead letter queue. An administrator can then inspect this dead letter queue to determine what to do with these messages. For example, it can send them to another device, send alerts about the device that rejected them, and so on.

Transactions

STOMP has basic support for transactions.

Sending a message or acknowledging the consumption of messages can be performed inside a transaction. This means that the messages and acknowledgments are not processed by the broker when it receives the corresponding frames but when the transaction completes. If the client does not complete the transaction or aborts it, the broker will not process the frames that it received inside the transaction and will just discard them. Transactions ensure that messages and acknowledgment processing are atomic. All transacted messages and acknowledgments will be processed by the broker when the transaction is committed or none will be if the transaction is aborted.

A transaction is started by the client by sending a BEGIN frame to the broker. This frame must have a transaction header whose value is a transaction identifier that must be unique within a STOMP connection.

Sending a message can then be part of this transaction by adding a transaction header to its SEND frames set to the same transaction identifier. If a consumer is subscribed to a STOMP destination with client or client-individual acknowledgment modes, it can also make the message acknowledgment (or nack) inside a transaction by setting the transaction header on the ACK (or NACK) frame.

Note

By default, STOMP consumers use auto acknowledgment. In that case, the message acknowledgment is performed automatically by the STOMP broker when the message is delivered to the client and the acknowledgment cannot be put inside a transaction.

To complete this active transaction and allow the broker to process it, the client must send a COMMIT frame with the same transaction header as in the corresponding BEGIN frame that started the transaction. To abort (or roll back) a transaction and discard any messages or acknowledgments inside it, the client must instead send an ABORT frame with this transaction header.

Warning

Beginning a transaction is not sufficient to send subsequent messages inside it. If a transaction is begun, the message to send must have its transaction header set to the transaction identifier. Otherwise, the STOMP broker will not consider the message to be part of the transaction and will process it when it receives it instead of waiting for the transaction to complete. If the client decides to abort the transaction, the message will have already been processed by the broker and will not be discarded.

STOMP does not provide a transaction timeout that would abort the transaction if it is not completed in a timely fashion. The transaction lifecycle (controlled by BEGIN and COMMIT/ABORT frames) is the responsibility of the client. However, the broker will automatically abort any active transaction if the client sends a DISCONNECT frame or if the underlying TCP connection fails.

StompKit Example

The STOMPClient can begin a transaction by calling its begin: method and passing an NSString that will be used to identify the transaction. Alternatively, you can call its begin method (without any parameter) and a transaction identifier will be automatically generated. Both begin: and begin methods return a STOMPTransaction object. This object has a identifier property that contains the transaction identifier.

Sending, acknowledging, or nacking a message can then be part of a transaction by adding a transaction header set to the transaction identifier (StompKit.h defines a kHeaderTransaction to represent this transaction header).

Finally, the STOMPTransaction object has two methods, commit and abort, to commit or rollback the transaction, respectively:

// begin a transaction
STOMPTransaction *transaction = [self.client begin];
// or STOMPTransaction *transaction = [self.client begin:mytxid];
NSLog(@"started transaction %@", transaction.identifier);

// send a message inside a transaction
[self.client sendTo:destination
            headers:@{kHeaderTransaction: transaction.identifier}
               body:body];

// acknowledge a message inside a transaction
[message ack:@{kHeaderTransaction: transaction.identifier}];
// or nack a message inside a transaction with
// [message nack:@{kHeaderTransaction: transaction.identifier}];

// commit the transaction
[transaction commit];
// or abort it
[transaction abort];

stomp.js Example

The API is very similar to StompKit. The client object has a begin method that can take a parameter corresponding to the transaction identifier. If there is no parameter, an identifier is automatically generated. The begin method returns a transaction object that has an id property corresponding to the transaction identifier.

Sending, acknowledging, or nacking a message can be part of a transaction by passing a transaction header set to the transaction identifier to these methods.

Finally, committing or aborting a transaction is performed by calling the commit or abort method, respectively, on the transaction object:

// begin a transaction
var tx = client.begin();
// or var tx = client.begin(mytxid);
console.log("started transaction " + tx.id);

// send a message inside a transaction
client.send(destination, {transaction: tx.id}, body);

// acknowledge a message inside a transaction
var subscription = client.subscribe(destination,
    function(message) {
      // do something with the message
      ...
      // and acknowledge it inside the transaction
      message.ack({ transaction: tx.id});
      // or nack it inside the transaction
      // message.nack({ transaction: tx.id});
    },
    {ack: 'client'}
  );

// commit the transaction
tx.commit();
// or abort it
tx.abort();

Error Handling

Until now, we have used STOMP in a perfect world where no unexpected problems happened. Realistically, problems will occur. On mobile devices, the network will be lost and the connection to the STOMP broker will be broken.

STOMP provides basic support to handle errors. The STOMP broker can inform the client that an error occurs by sending an ERROR frame to the client. This frame can contain a message header that contains a short description of the error. Most STOMP brokers deliver ERROR frames with a message payload containing more detailed information on the error.

STOMP specifies that after delivering an ERROR frame to the client, the broker must close the connection. This means that STOMP is not resilient to error. If a single error occurs on the server, the broker will close the connection to the client. In addition, there is no guarantee that the client will be able to receive the ERROR frame before the connection is closed.

In practice, this implies that to be able to handle any errors in the client, we should:

  1. Handle ERRORS frames coming from the broker

  2. Handle unexpected connection closed events

StompKit Example

We will modify the Locations iOS application to handle errors and automatically reconnect to the STOMP broker after a delay.

The STOMPClient has an errorHandler property that is called if the client encounters any error. Errors can come from the STOMP protocol (when the broker delivers an ERROR frame) or from the underlying network connection (e.g., if the network is lost or if the broker closes the connection before any ERROR frame is delivered).

The errorHandler property is a block with a standard NSSError parameter. If the error is coming from the STOMP broker, the corresponding STOMPFrame is stored in the error’s userInfo dictionary with the key frame.

There are two places where we must handle reconnection:

  • During the initial connection (e.g., if the broker is not up during the initial reconnect, we will continue to attempt to connect to it until it is up again).

  • When we receive an error from the STOMPClient’s errorHandler property.

In both cases, we will attempt to reconnect by disconnecting first (in the eventual case where the client is already connected), waiting for 10 seconds, and connecting again. This code can be  encapsulated in a reconnect: method of the MWMViewController implementation:

#pragma mark - Messaging

- (void)reconnect:(NSError *)error {
    NSLog(@"got error %@", error);
    STOMPFrame *frame = error.userInfo[@"frame"];
    if (frame) {
        NSString *message = frame.headers[@"message"];
        NSLog(@"error from the STOMP protocol: %@", message);
    }
    [self disconnect];
    sleep(10);
    NSLog(@"Reconnecting...");
    [self connect];
}

We then must call this reconnect: method from the client’s errorHandler property and the completionHandler block of its connectWithHeaders:completionHandler: method (both called from the MWMViewController connect method):

- (void)connect
{
    NSLog(@"Connecting...");
    __weak typeof(self) weakSelf = self;
    self.client.errorHandler = ^(NSError* error) {
        [weakSelf reconnect:error];
    };
    [self.client connectWithHeaders:@{ @"client-id": self.deviceID }
                  completionHandler:^(STOMPFrame *connectedFrame,
                                      NSError *error) {
                      if (error) {
                          NSLog(@"Error during connection: %@", error);
                          [weakSelf reconnect:error];
                      } else {
                          // we are connected to the STOMP broker without
                          // an error
                          NSLog(@"Connected");
                          [self subscribe];
                      }
                  }];
    // when the method returns, we can not assume that the client is connected
}

To avoid a retain/release cycle between self and the blocks, we need to create a weak reference to self to use it inside the blocks.

stomp.js Example

A stomp.js client can specify an errorCallback handler as the last parameter of its connect method. This handler will be called whenever the client encounters an error (whether coming from the STOMP protocol or the underlying network connection).

We can modify the Locations web application to automatically reconnect when an error occurs.

We will create a reconnect method that disconnects the stomp.js client if it is connected and calls connect again with the Web socket URL:

function reconnect(url) {
  if (client.connected) {
    console.log("disconnecting...");
    disconnect()
  }
  console.log("reconnecting");
  connect(url);
}

We then need to create an errorCallback handler that calls this reconnect method and pass it as the last parameter of the client’s connect method:

function connect(url) {
  var connectedCallback =  function(frame) {
    ...
  };
  var errorCallback = function(error) {
    client.debug("received error: " + error);
    reconnect(url);
  };

  // create the STOMP client
  client = Stomp.client(url);
  // and connect to the STOMP broker
  client.connect({}, connectedCallback, errorCallback);
}
Warning

A Web socket can be opened only once. If a problem occurs and the socket is closed, it can no longer be used. This implies that we cannot just call the client’s connect method again as its Web Socket is no longer usable. Instead, we must create a new client that will open a new Web Socket.

Whenever an error occurs (e.g., if the network connection is broken or the STOMP broker becomes temporarily unavailable), the errorCallback will be called and the client will try to reconnect.

Depending on your application, you may instead decide to report the error to the user and let him know that the client is no longer able to exchange messages with the broker.

Receipts

STOMP provides a basic mechanism to let a client know when the broker has received and processed its frames. This can be used with any STOMP frames. For example, a client can be notified when the broker receives a message that a producer sent (using a SEND frame) or when a consumer subscribes to a destination (with a SUBSCRIBE frame).

To use this mechanism, the frame that is sent to the broker must include a receipt header with any arbitrary value. After the broker has processed the frame, it will deliver a RECEIPT frame to the client with a receipt-id header corresponding to the receipt header in the frame that has been processed.

As an example, we can use receipt to confirm that a consumer has been subscribed successfully to a destination. If the broker cannot successfully create the subscription, it will send back an ERROR frame to the client and close the connection. In practice, this means that a successful subscription is silent: the client is not informed of its success. We can use receipts to have an explicit confirmation of the subscription by adding a receipt header when the client subscribes to a destination. The broker must then deliver a RECEIPT frame that will inform the client that the broker has processed its subscription successfully. If the subscription is unsuccessful, the broker will deliver an ERROR frame that has a receipt-id header corresponding to the RECEIPT’s receipt header to correlate the error.

Another use case for receipts is to make sending a message synchronous. The client sends a message with a receipt header and blocks until it receives the corresponding RECEIPT frame. This adds reliability (the client is sure that the broker has processed its message) at the cost of performance (the client can do nothing until the RECEIPT frame is received).

StompKit Example

A STOMPClient has a receiptHandler property that can be set to handle receipts. The receiptHandler is a block that takes a STOMPFrame corresponding to a RECEIPT frame.

Let’s add a receipt for the device text queue’s subscription to the Locations iOS application.

In its subscribe method, we will build a receipt identifier for the subscription receipt and set the client’s receiptHandler. In this block, we just check if the headers of the frame parameter contain a kHeaderReceiptID key with a value that matches the receipt identifier.

To receive such a receipt from the subscription, we need to add a kHeaderReceipt header to the subscribeTo:headers:messageHandler: and set it to the receipt identifier:

- (void)subscribe
{
    // susbscribes to the device text queue:
    NSString *destination =
        [NSString stringWithFormat:@"/queue/device.%@.text", self.deviceID];

    // build a receipt identifier
    NSString *receipt = [NSString stringWithFormat:@"%@-%@",
                         self.deviceID, destination];
    // set the client's receiptHandler to handle any receipt delivered by
    // the broker
    self.client.receiptHandler = ^(STOMPFrame *frame) {
        NSString *receiptID = [frame.headers objectForKey:kHeaderReceiptID];
        if ([receiptID isEqualToString:receipt]) {
            NSLog(@"Subscribed to %@", destination);
        }
    };
    NSLog(@"subscribing to %@", destination);
    // pass a receipt header to be informed of the subscription processing
    subscription = [self.client subscribeTo:destination
                                    headers:@{kHeaderReceipt: receipt}
                             messageHandler:^(STOMPMessage *message) {
             ...
    }];
}

If the Locations iOS application is run with this code, we see the log that confirms that the client is successfully subscribed to the destination:

2014-04-21 17:30:39.205 Locations[2384:3903] Subscribing to /queue/device.2262EC25-E9FD-4578-BADE-4E113DE45934.text
2014-04-21 17:30:39.208 Locations[2384:3903] Subscribed to /queue/device.2262EC25-E9FD-4578-BADE-4E113DE45934.text

Note that the client’s receiptHandler will receive any receipt delivered to the client. If you expect receipts from different STOMP frames, the client will have to handle all of them from a single receiptHandler block.

stomp.js Example

The stomp.js client has an onreceipt handler that can be set to receive receipts. It takes a function with a single frame parameter corresponding to a RECEIPT frame.

To receive a receipt for a subscription, we just need to add a receipt header to the headers passed as the last parameter of the subscribe method:

var destination = "/topic/device.*.location";

var receipt = "receipt_" + destination;
client.onreceipt = function(frame) {
  var receiptID = frame.headers['receipt-id'];
  if (receipt === receiptID) {
    console.log("subscribed to " + destination);
  }
}
client.subscribe(destination, function(message) {
  ...
}, {receipt: receipt});

If we reload the Locations web application, the browser console will display a log when the receipt confirming the subscription is handled by the client.

All stomp.js method that corresponds to STOMP frames accept a headers parameter that can be used to receive RECEIPT frames from the broker.

Heart-Beating

STOMP offers a mechanism to test the healthiness of a network connection between a STOMP client and a broker using heart-beating. In the absence of messages exchanged between the STOMP client and the broker, both can send a heartbeat periodically to inform the other that it is alive but has no activity.

If heart-beating is enabled, this allows the client and the broker to be informed in case of network failures and act accordingly (the client could try to reconnect to the broker, the broker could clean up the resources created on behalf of the client, etc.).

Heart-beating is negotiated between the client and the broker when the client connects to the broker (by sending a CONNECT frame) and the broker accepts the connection (by sending a CONNECTED frame to the client). Both frames accept a heart-beat header whose value contains two integers separated by a comma:

CONNECT
heart-beat:<cx>,<cy>

CONNECTED:
heart-beat:<sx>,<sy>

Let’s take a look at what this code means:

  • <cx> The smallest number of milliseconds between heartbeats that the client guarantees. If it is set to 0, the client will not send any heartbeat at all.

  • <cy> The desired number of milliseconds between heartbeats coming from the broker. If it is set to 0, the client does not want to receive any heartbeat.

  • <sx> The smallest number of milliseconds between heartbeats that the broker guarantees. If it is set to 0, the broker will not send any heartbeat.

  • <sy> The desired number of milliseconds between heartbeats coming from the client. If it is set to 0, the broker does not want to receive any heartbeat.

When the client is successfully connected to the STOMP broker (it has received the CONNECTED frame), it must determine the frequency of the heartbeats to send to the broker and the frequency of heartbeats coming from the broker.

The values that are used to determine the frequency of heartbeats sent to the broker are <cx> and <sy>. If <cx> is 0 (the client will send no heartbeat) or if <sy> is 0 (the broker does not expect any client heartbeats), there will be no client heartbeating at all. This means that the broker will not be able to test the health of the client connection. Otherwise, both server and broker expect to exchange client heartbeats. The frequency is then determined by the maximum value between the value guaranteed by the client, <cx>, and the value desired by the broker, <sy>. In other words, the client must send heartbeats at a frequency of at least MAX(cx,sy) milliseconds.

For the heartbeats sent by the broker to the client, the algorithm is the same but uses the <cy> and <sx> values.

Let’s take a simple example to illustrate the algorithm. We have a STOMP client that connects to the broker with the heart-beat header set to 0,60000 (the client will not send any heartbeats, but desires to receive the broker’s heartbeats every minute):

CONNECT
heart-beat:0,60000
....

The broker accepts the connection and replies with a CONNECTED frame that contains a heart-beat header set to 20000,30000 (the broker guarantees to send heartbeats every 20 seconds and desires to receive the client’s hearbeats every 30 seconds):

CONNECTED
heart-beat:20000,30000
....

Because the client specified that it will send no heartbeat (0 as the first value of the CONNECT’s heart-beat header), client heartbeating is disabled and the broker should not expect any (even though it desired to receive them every 20 seconds).

The client desired to receive the broker heartbeat every minute (60000 as the second value of the CONNECT’s heart-beat header). The broker replied that it can guarantee to send them at least every 30 seconds (second value of the CONNECTED’s heart-beat header). In that case, the broker and the client agrees that the broker must send heartbeats every minute (the maximum between 1 minute and 30 seconds). In other words, the broker could send heartbeats every 30 seconds (as it guaranteed in the CONNECTED frame), but the client will only check them every minute.

Note

ActiveMQ supports heart-beating and mirrors the heartbeat values sent by the STOMP client. This lets the STOMP client be the sole decider of the heart-beating values.

This means that if a client connects with a heart-beat header set to <cx>,<sy>, the broker will accept the connection with a heart-beat header set to <sy>,<cx>.

The client guaranteed to send hearbeats every <cx> milliseconds, so the broker replied that it desires to receive them at this rate. The client desired to receive heartbeats every <sy> milliseconds, so the broker replied that it guarantees to send its heartbeat at this rate.

The client should set its heart-beat header according to its usage. For example, if an application is sending messages at a regular rate (such as the Locations iOS application), there is no need to send heartbeats to the broker at a similar (or faster) rate. The messages sent are proof enough of the client activity. Likewise, if a client expects to receive messages at a regular rate (such as the Locations web application), there is no need to require the broker to send frequent heartbeats.

However, if the application does not send messages often (the Locations web application will seldom sent text messages to the device’ text topics), it probably should send heartbeats more frequently to inform the broker of its healthiness. Likewise, if the application does not receive messages often (such as the Locations iOS application), it should desire more frequent heartbeats from the broker.

StompKit Example

A STOMPClient supports heart-beating by passing the heart-beat header when it connects to the broker using its connectWithHeaders:completionHandler method.

By default, StompKit defines a heartbeat of 5000,10000 (send heartbeats every 5 seconds and receive them every 10 seconds).

Let’s add heart-beating to the Locations iOS application. The application sends messages often (every time the device GPS position is updated), but receives them less frequently (when a user sends a message from the web application). We will guarantee to send heartbeats every minute (60000ms) and desire to receive them from the broker every 20 seconds (20000ms):

- (void)connect
{
    NSLog(@"Connecting...");
    self.client.errorHandler = ^(NSError* error) {
        NSLog(@"got error from STOMP: %@", error);
    };
    // will send a heartbeat at most every minute.
    // expect broker's heartbeat at least every 20 seconds.
    NSString *heartbeat = @"60000,20000";
    [self.client connectWithHeaders:@{ @"client-id": self.deviceID,
                                       kHeaderHeartBeat: heartbeat }
                  completionHandler:^(STOMPFrame *connectedFrame,
                                      NSError *error) {
                      ...
                  }];
}

stomp.js Example

The STOMP client has a heartbeat property composed of two properties:

  • heartbeat.outgoing is the guaranteed frequency of heartbeat it can send to the broker (i.e., <cx>)

  • heartbeat.incoming is the desired frequency of heartbeat coming from the broker (i.e., <cy>)

By default, stomp.js defines a heartbeat of 10000,10000 (to send and receive heartbeats every 10 seconds).

These properties must be modified before the connect method is called to take them into account:

// create the STOMP client
client = Stomp.client(url);

// will send a heartbeat at most every 20 seconds
client.heartbeat.outgoing = 20000;
// expects broker's heartbeat at least every minute
client.heartbeat.incoming = 60000;
client.connect({}, function(frame) {
  ...
});

Summary

In an ideal world, only the basic features of STOMP would be required to use messaging in mobile and web applications. However, to handle errors that will eventually happen under normal use, we need to leverage advanced STOMP features.

In this chapter, we learned to use:

  • Authentication to ensure that only authenticated clients can communicate with the STOMP broker

  • Acknowledgment to let the client explicitly accept the delivery of a message

  • Transaction to send messages as a single atomic unit of work

  • Error handling to face unexpected issues and eventually reconnect to the broker

  • Receipt to receive confirmation that a frame has been succesfully processed by the broker

  • Heart-beating to ensure that the network connection between the client and broker is healthy (and to ensure that it will kill the connection if that is not the case)

Get Mobile and Web Messaging now with the O’Reilly learning platform.

O’Reilly members experience live online training, plus books, videos, and digital content from nearly 200 publishers.