O'Reilly logo

Mobile and Web Messaging by Jeff Mesnil

Stay ahead with the world's most comprehensive technology and business learning platform.

With Safari, you learn the way you learn best. Get unlimited access to videos, live online training, learning paths, books, tutorials, and more.

Start Free Trial

No credit card required

Chapter 4. Advanced STOMP

In the two previous chapters, we have 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 make a tour of all the features provided by STOMP.

This chapter covers the latest version of the protocol when this book was written: STOMP 1.2 that was released on 2012, October 22nd.

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 have ever 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 acknowledgement and headers for hearbeat negotiation.

In this chapter, we will describe the features provided by the STOMP protocol that client and broker must support 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 such additional features in the next chapter.

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 modelled on HTTP and its frames follows a similar structure. Each frame is composed of 3 elements:

  • a command
  • an (optional) set of headers
  • an (optional) payload
Example 4-1. STOMP Frame Structure
COMMAND1
header1:value12
header2:value2
3
payload^@4
1

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

2

Header entries followed the format <key>:<value> and is 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 the two previous chapters, 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 receive them (with the MESSAGE command) but also for all its operation comands (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 may 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 a 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 the two previous chapters, 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 could 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.

ActiveMQ Authentication

By default, ActiveMQ accepts anonymous connections. To change its security settings to authenticate users and grant them restricted priviledges (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 Acknowledgement

Message acknowledgement 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 of the message by acknowledging the message.

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

However there are cases where the consumer may prefer to acknowledge explicity 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 chose 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 negatively acknowledgement, 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 3 types of acknowledgements:

  • auto (by default)
  • client
  • client-individual

If the client does not specify any type of acknowledgement or use default, it does not need to send any acknowledgement, 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 acknowledgements to the server with the message-id of the message that is acknowledged. The difference between client and client-individual is that client will acknowledged the message and all other messages delivered to the consumer before. Using client-individual will only acknowledge the message and no other messages. The consumer acknowledge a message by sending a ACK frame to the STOMP broker.

If client and client-individual is used, the consumer may explicitly refuse to handle the message by sending a NACK frame, a negative acknowledgement.

StompKit Example

The message acknowledgement is specified when the STOMPClient subscribes to a destination by calling its subscribeTo:headers:messageHandler: method. To specify a client or client-individual acknowledgement, you must set a 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 respectively acknowledge or nack the message.

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

// use client acknowledgement
[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 acknowledgement 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 respectively acknowledge or nack the message. If the acknowledgement 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 acknowledgement.

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

Besides, acknowledging every message would have a performance cost. Sending the acknowledgement 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 acknowledged 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. A common feature is to use a “dead letter queue” where messages that are nacked multiple times from a destination are 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, etc.

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 acknowledgements 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 acknowledgement processing is atomic. All transacted messages and acknowledgements 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 acknowledgement modes, it can also make the message acknowledgement (or nack) inside a transaction by setting the transaction header on the ACK (or NACK) frame.

Note

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

To complete this active transaction and allows the broker to process it, the client must send a COMMIT frame with the same transaction header than in the corresponding BEGIN frame that started the transaction. To abort (or roll back) a transaction and discard any messages or acknowledgements sent inside it, the client must send instead 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 that the message is part of the transaction and will process it when it receives it instead of waiting for the transaction completion. 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 send 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 a 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 returns a STOMPTransaction object. This object as a identifier property that contains the transaction identifier.

Sending a message, acknowledging, or nacking it 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 respectively commit or rollback the transaction.

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

// send 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 takes 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 a message, acknowledging, or nacking it 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 respectively the commit and abort method 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, 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 a ERROR frame to the client. This frame may 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 to handle errors and automatically reconnect to the STOMP broker after a delay.

The STOMPClient has a errorHandler property that is called if the client encounters any error. Error can come from the STOMP protocol (when the broker deliver 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:

  1. during the initial connection (for example if the broker is not up during the initial reconnect, we will continue to attempt to connect to it until it is up again).
  2. when we receive an error from the STOMPClient’s errorHandler property.

In both case, 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 of self and uses it from 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.html Web application to automatically reconnect when an error occurs.

We will create a reconnect method which 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 connects 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 can not just call the client’s connect method again as its Web socket is no longer usable. Instead we must create a new client which will open a new Web socket.

Whenever an error occurs (for example 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 send (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 can not 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 not successful, the broker will deliver a ERROR frame that has a receipt-id header corresponding to the RECEIPT’s receipt header to be able to correlate the error.

Another use case for receipts is to make sending a message synchronous. The client would send a message with a receipt header and blocks until it receives the corresponding RECEIPT frame. This would add 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 take 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 contains a kHeaderReceiptID key whose value match 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(@"Susbscribed 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 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 a 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.html 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 heartbeat periodically to inform the other that 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 accepts a heart-beat header whose value contains two integers separated by a comma.

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

CONNECTED:
heart-beat:<sx>,<sy>
  • <cx> is 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> is the desired number of milliseconds between heartbeats coming from the broker. If it is set to 0, the client is does not want to receive any heartbeat.
  • <sx> is the smallest number of milliseconds between heartbeats that the broker guarantees. If it is set to 0, the broker will not send any heartbeat at all.
  • <sy> is 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 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 healthiness 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 guaranted by the client, <cx>, and the value desired by the broker, <sy>. In other words, the client must send heartbeats at least MAX(cx,sy) milliseconds.

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

Let’s take a simple example to illustrate the algorithm. A STOMP client connect 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 ones 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 heartbeat every 20 seconds and desires to receive the client’s ones every 30 seconds).

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

Since 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 (although 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 seoncds). 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.

ActiveMQ Heart-beating

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 heartbeating 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 guaranted to send hearbeat every <cx> milliseconds, so the broker replied that it desires to receive them at this rate. The client desired to receive heartbeat 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 heartbeat every 5 seconds and receive them every 10 seconds).

Let’s add heart-beating to the Locations iOS application. The application often sends messages (every time the device GPS position is updated) but receive them less frequently (when an user sends a message from the web application). We will guarantee to send heartbeat every minute (60000ms) and desires 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 learn to use:

  • authentication to ensure that only authenticated clients can communicate with the STOMP broker
  • acknowledgement to let the client accepts explicitly 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 kill the connection if that is not the case

With Safari, you learn the way you learn best. Get unlimited access to videos, live online training, learning paths, books, interactive tutorials, and more.

Start Free Trial

No credit card required