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)
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:
-
Handle
ERRORS
frames coming from the broker -
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
’serrorHandler
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 to0
, 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 to0
, 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 to0
, the broker will not send any heartbeat. -
<sy>
The desired number of milliseconds between heartbeats coming from the client. If it is set to0
, 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 books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.