Merge remote-tracking branch 'upstream/master'

This commit is contained in:
Richard Hodges 2014-07-28 15:07:06 +01:00
commit b41e2edc52
95 changed files with 3187 additions and 2041 deletions

595
README.md
View File

@ -1,27 +1,27 @@
AMQP-CPP
========
AMQP-CPP is a C++ library for communicating with a RabbitMQ message broker. The
library can be used to parse incoming data from a RabbitMQ server, and to
AMQP-CPP is a C++ library for communicating with a RabbitMQ message broker. The
library can be used to parse incoming data from a RabbitMQ server, and to
generate frames that can be sent to a RabbitMQ server.
Unlike all other AMQP libraries, this AMQP-CPP library does not make a connection to
RabbitMQ by itself, nor does it create sockets and/or performs IO operations. As
a user of this library, you first need to set up a socket connection
to RabbitMQ by yourself, and implement a certain interface that you pass to the
RabbitMQ by itself, nor does it create sockets and/or performs IO operations. As
a user of this library, you first need to set up a socket connection
to RabbitMQ by yourself, and implement a certain interface that you pass to the
AMQP-CPP library and that the library will use for IO operations.
This architecture makes the library extremely flexible: it does not rely on
operating system specific IO calls, and it can be easily integrated into any
event loop. It is fully asynchronous and does not do any blocking (system) calls,
event loop. It is fully asynchronous and does not do any blocking (system) calls,
so it can be used in high performance applications without the need for threads.
ABOUT
=====
This library is created and maintained by Copernica (www.copernica.com), and is
used inside the MailerQ (www.mailerq.com) application, MailerQ is a tool for
This library is created and maintained by Copernica (www.copernica.com), and is
used inside the MailerQ (www.mailerq.com) application, MailerQ is a tool for
sending large volumes of email, using AMQP message queues.
@ -42,34 +42,34 @@ class MyConnectionHandler : public AMQP::ConnectionHandler
{
/**
* Method that is called by the AMQP library every time it has data
* available that should be sent to RabbitMQ.
* @param connection pointer to the main connection object
* available that should be sent to RabbitMQ.
* @param connection pointer to the main connection object
* @param data memory buffer with the data that should be sent to RabbitMQ
* @param size size of the buffer
*/
virtual void onData(AMQP::Connection *connection, const char *data, size_t size)
{
// @todo
// @todo
// Add your own implementation, for example by doing a call to the
// send() system call. But be aware that the send() call may not
// send all data at once, so you also need to take care of buffering
// the bytes that could not immediately be sent, and try to send
// the bytes that could not immediately be sent, and try to send
// them again when the socket becomes writable again
}
/**
* Method that is called by the AMQP library when the login attempt
* succeeded. After this method has been called, the connection is ready
* Method that is called by the AMQP library when the login attempt
* succeeded. After this method has been called, the connection is ready
* to use.
* @param connection The connection that can now be used
*/
virtual void onConnected(Connection *connection)
virtual void onConnected(AMQP::Connection *connection)
{
// @todo
// add your own implementation, for example by creating a channel
// add your own implementation, for example by creating a channel
// instance, and start publishing or consuming
}
/**
* Method that is called by the AMQP library when a fatal error occurs
* on the connection, for example because data received from RabbitMQ
@ -77,13 +77,24 @@ class MyConnectionHandler : public AMQP::ConnectionHandler
* @param connection The connection on which the error occured
* @param message A human readable error message
*/
virtual void onError(Connection *connection, const std::string &message)
virtual void onError(AMQP::Connection *connection, const char *message)
{
// @todo
// add your own implementation, for example by reporting the error
// to the user of your program, log the error, and destruct the
// to the user of your program, log the error, and destruct the
// connection object because it is no longer in a usable state
}
/**
* Method that is called when the connection was closed. This is the
* counter part of a call to Connection::close() and it confirms that the
* connection was correctly closed.
*
* @param connection The connection that was closed and that is now unusable
*/
virtual void onClosed(AMQP::Connection *connection) {}
};
````
After you've implemented the ConnectionHandler class, you can start using
@ -108,18 +119,18 @@ channel.bindQueue("my-exchange", "my-queue");
A number of remarks about the example above. First you may have noticed that we've
created all objects on the stack. You are of course also free to create them
on the heap with the C++ operator 'new'. That works just as well, and is in real
life code probably more useful as you normally want to keep your handlers, connection
life code probably more useful as you normally want to keep your handlers, connection
and channel objects around for a much longer time.
But more importantly, you can see in the example above that we have created the
channel object directly after we made the connection object, and we also
But more importantly, you can see in the example above that we have created the
channel object directly after we made the connection object, and we also
started declaring exchanges and queues right away. However, under the hood, a handshake
protocol is executed between the server and the client when the Connection
object is first created. During this handshake procedure it is not permitted to send
other instructions (like opening a channel or declaring a queue). It would therefore have been better
if we had first waited for the connection to be ready (implement the MyConnectionHandler::onConnected() method),
and create the channel object only then. But this is not strictly necessary.
The methods that are called before the handshake is completed are cached by the
other instructions (like opening a channel or declaring a queue). It would therefore have been better
if we had first waited for the connection to be ready (implement the MyConnectionHandler::onConnected() method),
and create the channel object only then. But this is not strictly necessary.
The methods that are called before the handshake is completed are cached by the
AMQP library and will be executed the moment the handshake is completed and the
connection becomes ready for use.
@ -128,17 +139,17 @@ PARSING INCOMING DATA
=====================
The ConnectionHandler class has a method onData() that is called by the library
every time that it wants to send out data. We've explained that it is up to you to
implement that method. But what about data in the other direction? How does the
every time that it wants to send out data. We've explained that it is up to you to
implement that method. But what about data in the other direction? How does the
library receive data back from RabbitMQ?
As we've explained above, the AMQP-CPP library does not do any IO by itself
and it is therefore of course also not possible for the library to receive data from
a socket. It is again up to you to do this. If, for example, you notice in your
event loop that the socket that is connected with the RabbitMQ server becomes
readable, you should read out that data (for example by using the recv() system
call), and pass the received bytes to the AMQP-CPP library. This is done by
calling the parse() method in the Connection object.
The AMQP-CPP library does not do any IO by itself and it is therefore of course
also not possible for the library to receive data from a socket. It is again up
to you to do this. If, for example, you notice in your event loop that the socket
that is connected with the RabbitMQ server becomes readable, you should read out
that socket (for example by using the recv() system call), and pass the received
bytes to the AMQP-CPP library. This is done by calling the parse() method in the
Connection object.
The Connection::parse() method gets two parameters, a pointer to a buffer of
data received from RabbitMQ, and a parameter that holds the size of this buffer.
@ -147,15 +158,15 @@ The code snippet below comes from the Connection.h C++ header file.
````c++
/**
* Parse data that was recevied from RabbitMQ
*
* Every time that data comes in from RabbitMQ, you should call this method to parse
* the incoming data, and let it handle by the AMQP-CPP library. This method returns the number
* of bytes that were processed.
*
* If not all bytes could be processed because it only contained a partial frame, you should
* call this same method later on when more data is available. The AMQP-CPP library does not do
* any buffering, so it is up to the caller to ensure that the old data is also passed in that
* later call.
* Every time that data comes in from RabbitMQ, you should call this method to parse
* the incoming data, and let it handle by the AMQP-CPP library. This method returns
* the number of bytes that were processed.
*
* If not all bytes could be processed because it only contained a partial frame,
* you should call this same method later on when more data is available. The
* AMQP-CPP library does not do any buffering, so it is up to the caller to ensure
* that the old data is also passed in that later call.
*
* @param buffer buffer to decode
* @param size size of the buffer to decode
@ -167,39 +178,123 @@ size_t parse(char *buffer, size_t size)
}
````
You should do all the book keeping for the buffer yourselves. If you for example
call the Connection::parse() method with a buffer of 100 bytes, and the method
returns that only 60 bytes were processed, you should later call the method again,
with a buffer filled with the remaining 40 bytes. If the method returns 0, you should
make a new call to parse() when more data is available, with a buffer that contains
both the old data, and the new data.
CHANNELS
========
In the example you saw that we created a channel object. A channel is a virtual
connection over a single TCP connection, and it is possible to create many channels
that all use the same TCP connection.
In the example we created a channel object. A channel is a virtual connection over
a single TCP connection, and it is possible to create many channels that all use
the same TCP connection.
AMQP instructions are always sent over a channel, so before you can send the first
command to the RabbitMQ server, you first need a channel object. The channel
AMQP instructions are always sent over a channel, so before you can send the first
command to the RabbitMQ server, you first need a channel object. The channel
object has many methods to send instructions to the RabbitMQ server. It for
example has methods to declare queues and exchanges, to bind and unbind them,
and to publish and consume messages. You can best take a look at the channel.h
C++ header file for a list of all available methods. Every method in it is well
example has methods to declare queues and exchanges, to bind and unbind them,
and to publish and consume messages. You can best take a look at the channel.h
C++ header file for a list of all available methods. Every method in it is well
documented.
The constructor of the Channel object accepts two parameters: the connection object,
and a pointer to a ChannelHandler object. In the example we did
not use this ChannelHandler object. However, in normal circumstances, you should
always pass a pointer to a ChannelHandler object every time you construct a channel.
The constructor of the Channel object accepts one parameter: the connection object.
Unlike the connection it does not accept a handler. Instead of a handler object,
(almost) every method of the Channel class returns an instance of the 'Deferred'
class. This object can be used to install handlers that will be called in case
of success or failure.
Just like the ConnectionHandler class, the ChannelHandler class is a base class that
you can extend to override the virtual methods you need. The AMQP library
will call these methods to inform you that an operation on the channel has succeeded
or has failed.
For example, if you call the channel.declareQueue() method, the AMQP-CPP library will
send a message to the RabbitMQ message broker to ask it to declare the
For example, if you call the channel.declareExchange() method, the AMQP-CPP library
will send a message to the RabbitMQ message broker to ask it to declare the
queue. However, because all operations in the library are asynchronous, the
declareQueue() method immediately returns 'true', although it is at that time
not yet known whether the queue was correctly declared. Only after a while,
after the instruction has reached the server, and the confirmation from the server
has been sent back to the client, your ChannelHandler::onQueueDeclared()
method will be called to inform you that the operation was succesful.
declareExchange() method can not return 'true' or 'false' to inform you whether
the operation was succesful or not. Only after a while, after the instruction
has reached the RabbitMQ server, and the confirmation from the server has been
sent back to the client, the library can report the result of the declareExchange()
call.
To prevent any blocking calls, the channel.declareExchange() method returns a
'Deferred' result object, on which you can set callback functions that will be
called when the operation succeeds or fails.
````c++
// create a channel
Channel myChannel(&connection);
// declare an exchange, and install callbacks for success and failure
myChannel.declareExchange("my-exchange")
.onSuccess([]() {
// by now the exchange is created
})
.onError([](const char *message) {
// something went wrong creating the exchange
});
````
As you can see in the above example, we call the declareExchange() method, and
treat its return value as an object, on which we immediately install a lambda
callback function to handle success, and to handle failure.
Installing the callback methods is optional. If you're not interested in the
result of an operation, you do not have to install a callback for it. Next
to the onSuccess() and onError() callbacks that can be installed, you can also
install a onFinalize() method that gets called directly after the onSuccess()
and onError() methods, and that can be used to set a callback that should
run in either case: when the operation succeeds or when it fails.
The signature for the onError() method is always the same: it gets one parameter
with a human readable error message. The onSuccess() function has a different
signature depending on the method that you call. Most onSuccess() functions
(like the one we showed for the declareExchange() method) do not get any
parameters at all. Some specific onSuccess callbacks receive extra parameters
with additional information.
CHANNEL CALLBACKS
=================
As explained, most channel methods return a 'Deferred' object on which you can
install callbacks using the Deferred::onError() and Deferred::onSuccess() methods.
The callbacks that you install on a Deferred object, only apply to one specific
operation. If you want to install a generic error callback for the entire channel,
you can so so by using the Channel::onError() method. Next to the Channel::onError()
method, you can also install a callback to be notified when the channel is ready
for sending the first instruction to RabbitMQ.
````c++
// create a channel
Channel myChannel(&connection);
// install a generic channel-error handler that will be called for every
// error that occurs on the channel
myChannel.onError([](const char *message) {
// report error
std::cout << "channel error: " << message << std::endl;
});
// install a generic callback that will be called when the channel is ready
// for sending the first instruction
myChannel.onReady([]() {
// send the first instructions (like publishing messages)
});
````
In theory, you should wait for the onReady() callback to be called before you
send any other instructions over the channel. In practice however, the AMQP library
caches all instructions that were sent too early, so that you can use the
channel object right after it was constructed.
CHANNEL ERRORS
==============
It is important to realize that any error that occurs on a channel, will
invalidate the entire channel,. including all subsequent instructions that
@ -207,76 +302,32 @@ were already sent over it. This means that if you call multiple methods in a row
and the first method fails, all subsequent methods will not be executed either:
````c++
Channel myChannel(connection, &myHandler);
Channel myChannel(&connection);
myChannel.declareQueue("my-queue");
myChannel.declareExchange("my-exchange");
````
If the first declareQueue() call fails in the example above, your ChannelHandler::onError()
method will be called after a while to report this failure. And although the
second instruction to declare an exchange has already been sent to the server, it will be
ignored because the channel was already in an invalid state after the first failure.
If the first declareQueue() call fails in the example above, the second
myChannel.declareExchange() method will not be executed, even when this
second instruction was already sent to the server. The second instruction will be
ignored by the RabbitMQ server because the channel was already in an invalid
state after the first failure.
You can overcome this by using multiple channels:
````c++
Channel channel1(connection, &myHandler);
Channel channel2(connection, &myHandler);
Channel channel1(&connection);
Channel channel2(&connection);
channel1.declareQueue("my-queue");
channel2.declareQueue("my-exchange");
channel2.declareExchange("my-exchange");
````
Now, if an error occurs with declaring the queue, it will not have
consequences for the other call. But this comes at a small price:
setting up the extra channel requires and extra instruction to be sent to the
RabbitMQ server, so some extra bytes are sent over the network,
and some additional resources in both the client application and the
RabbitMQ server are used (although this is all very limited).
Now, if an error occurs with declaring the queue, it will not have consequences
for the other call. But this comes at a small price: setting up the extra channel
requires and extra instruction to be sent to the RabbitMQ server, so some extra
bytes are sent over the network, and some additional resources in both the client
application and the RabbitMQ server are used (although this is all very limited).
Let's get back to the ChannelHandler class. It has many methods that you can
implement - all of which are optional. All methods in it have a default empty implementation,
so you can choose to only override the ones that you are interested in. When you're
writing a consumer application for example, you probably are only interested in
errors that occur, and in incoming messages:
````c++
#include <amqpcpp.h>
class MyChannelHandler : public AMQP::ChannelHandler
{
public:
/**
* Method that is called when an error occurs on the channel, and
* the channel ends up in an error state
* @param channel the channel on which the error occured
* @param message human readable error message
*/
virtual void onError(AMQP::Channel *channel, const std::string &message)
{
// @todo
// do something with the error message (like reporting it to the end-user)
// and destruct the channel object because it now no longer is usable
}
/**
* Method that is called when a message has been received on a channel
* This message will be called for every message that is received after
* you started consuming. Make sure you acknowledge the messages when its
* safe to remove them from RabbitMQ (unless you set no-ack option when you
* started the consumer)
* @param channel the channel on which the consumer was started
* @param message the consumed message
* @param deliveryTag the delivery tag, you need this to acknowledge the message
* @param consumerTag the consumer identifier that was used to retrieve this message
* @param redelivered is this a redelivered message?
*/
virtual void onReceived(AMQP::Channel *channel, const AMQP::Message &message, uint64_t deliveryTag, const std::string &consumerTag, bool redelivered)
{
// @todo
// do something with the incoming message
}
};
````
FLAGS AND TABLES
================
@ -290,47 +341,64 @@ tables are used by many methods.
````c++
/**
* Declare a queue
*
*
* If you do not supply a name, a name will be assigned by the server.
*
*
* The flags can be a combination of the following values:
*
*
* - durable queue survives a broker restart
* - autodelete queue is automatically removed when all connected consumers are gone
* - passive only check if the queue exist
* - exclusive the queue only exists for this connection, and is automatically removed when connection is gone
*
*
* @param name name of the queue
* @param flags combination of flags
* @param arguments optional arguments
*/
bool declareQueue(const std::string &name, int flags, const AMQP::Table &arguments);
bool declareQueue(const std::string &name, const AMQP::Table &arguments);
bool declareQueue(const std::string &name, int flags = 0);
bool declareQueue(int flags, const AMQP::Table &arguments);
bool declareQueue(const AMQP::Table &arguments);
bool declareQueue(int flags = 0);
DeferredQueue &declareQueue(const std::string &name, int flags, const Table &arguments);
DeferredQueue &declareQueue(const std::string &name, const Table &arguments);
DeferredQueue &declareQueue(const std::string &name, int flags = 0);
DeferredQueue &declareQueue(int flags, const Table &arguments);
DeferredQueue &declareQueue(const Table &arguments);
DeferredQueue &declareQueue(int flags = 0);
````
As you can see, the method comes in many forms, and it is up to you to choose
the one that is most appropriate. We now take a look at the most complete
the one that is most appropriate. We now take a look at the most complete
one, the method with three parameters.
Many methods in the Channel class accept an integer parameter named 'flags'.
This is a variable in which you can set a number of options, by summing up
all the options that are described in the documentation. If you for example
want to create a durable, auto-deleted queue, you can pass in the value
AMQP::durable + AMQP::autodelete.
All above methods returns a 'DeferredQueue' object. The DeferredQueue class
extends from the AMQP::Deferred class and allows you to install a more powerful
onSuccess() callback function. The 'onSuccess' method for the declareQueue()
function gets three arguments:
````c++
// create a custom callback
auto callback = [](const std::string &name, int msgcount, int consumercount) {
// @todo add your own implementation
}
// declare the queue, and install the callback that is called on success
channel.declareQueue("myQueue").onSuccess(callback);
````
Just like many others methods in the Channel class, the declareQueue() method
accept an integer parameter named 'flags'. This is a variable in which you can
set method-specific options, by summing up all the options that are described in
the documentation above the method. If you for example want to create a durable,
auto-deleted queue, you can pass in the value AMQP::durable + AMQP::autodelete.
The declareQueue() method also accepts a parameter named 'arguments', which is of type
Table. This Table object can be used as an associative array to send additional
options to RabbitMQ, that are often custom RabbitMQ extensions to the AMQP
options to RabbitMQ, that are often custom RabbitMQ extensions to the AMQP
standard. For a list of all supported arguments, take a look at the documentation
on the RabbitMQ website. With every new RabbitMQ release more features, and
supported arguments are added.
The Table class is a very powerful class that enables you to build
complicated, deeply nested structures full of strings, arrays and even other
The Table class is a very powerful class that enables you to build
complicated, deeply nested structures full of strings, arrays and even other
tables. In reality, you only need strings and integers.
````c++
@ -353,14 +421,14 @@ exchange to publish to, the routing key to use, and the actual message that
you're publishing - all these parameters are standard C++ strings.
More extended versions of the publish() method exist that accept additional
arguments, and that enable you to publish entire Envelope objects, which are
objects that contain the message plus a list of optional meta information like
the content-type, content-encoding, priority, expire time and more. None of these
meta fields are interpreted by this library, and also the RabbitMQ ignores most
of them, but the AMQP protocol defines them, and they are free for you to use.
For an extensive list of the fields that are supported, take a look at the MetaData.h
header file (MetaData is the base class for Envelope). You should also check the
RabbitMQ documentation to find out if an envelope header is interpreted by the
arguments, and that enable you to publish entire Envelope objects. An envelope
is an object that contains the message plus a list of optional meta information like
the content-type, content-encoding, priority, expire time and more. None of these
meta fields are interpreted by this library, and also the RabbitMQ ignores most
of them, but the AMQP protocol defines them, and they are free for you to use.
For an extensive list of the fields that are supported, take a look at the MetaData.h
header file (MetaData is the base class for Envelope). You should also check the
RabbitMQ documentation to find out if an envelope header is interpreted by the
RabbitMQ server (at the time of this writing, only the expire time is being used).
The following snippet is copied from the Channel.h header file and lists all
@ -370,17 +438,20 @@ in almost any form:
````c++
/**
* Publish a message to an exchange
*
*
* The following flags can be used
*
* - mandatory if set, an unroutable message will be reported to the channel handler with the onReturned method
* - immediate if set, a message that could not immediately be consumed is returned to the onReturned method
*
*
* - mandatory if set, an unroutable message will be reported to the
* channel handler with the onReturned method
*
* - immediate if set, a message that could not immediately be consumed
* is returned to the onReturned method
*
* If either of the two flags is set, and the message could not immediately
* be published, the message is returned by the server to the client. If you
* want to catch such returned messages, you need to implement the
* want to catch such returned messages, you need to implement the
* ChannelHandler::onReturned() method.
*
*
* @param exchange the exchange to publish to
* @param routingkey the routing key
* @param flags optional flags (see above)
@ -396,18 +467,40 @@ bool publish(const std::string &exchange, const std::string &routingKey, int fla
bool publish(const std::string &exchange, const std::string &routingKey, const char *message, size_t size);
````
Published messages are normally not confirmed by the server, hence there is no
ChannelHandler::onPublished() method that you can implement to find out if
a message was correctly received by the server. That's by design in the
AMQP protocol, to not unnecessarily slow down message publishing. As long
as no error is reported via the ChannelHandler::onError() method, you can safely
Published messages are normally not confirmed by the server, and the RabbitMQ
will not send a report back to inform us whether the message was succesfully
published or not. Therefore the publish method does also not return a Deferred
object.
As long as no error is reported via the Channel::onError() method, you can safely
assume that your messages were delivered.
If you use the flags parameter to set either the option 'mandatory' or
'immediate', a message that could not be routed or directly delivered to a consumer
is sent back to the client, and ends up in the ChannelHandler::onReturned()
method. At the time of this writing however, the 'immediate' option does not
seem to be supported by RabbitMQ.
This can of course be a problem when you are publishing many messages. If you get
an error halfway through there is no way to know for sure how many messages made
it to the broker and how many should be republished. If this is important, you can
wrap the publish commands inside a transaction. In this case, if an error occurs,
the transaction is automatically rolled back by RabbitMQ and none of the messages
are actually published.
````c++
// start a transaction
channel.startTransaction();
// publish a number of messages
channel.publish("my-exchange", "my-key", "my first message");
channel.publish("my-exchange", "my-key", "another message");
// commit the transactions, and set up callbacks that are called when
// the transaction was successful or not
channel.commitTransaction()
.onSuccess([]() {
// all messages were successfully published
})
.onError([]() {
// none of the messages were published
// now we have to do it all over again
});
````
CONSUMING MESSAGES
@ -421,108 +514,117 @@ Just like the publish() method that we just described, the consume() method also
comes in many forms. The first parameter is always the name of the queue you like
to consume from. The subsequent parameters are an optional consumer tag, flags and
a table with custom arguments. The first additional parameter, the consumer tag,
is nothing more than a string identifier that will be passed with every consumed message.
This can be useful if you call the consume() methods a number of times to consume
from multiple queues, and you would like to know from which consume call the received messages came.
is nothing more than a string identifier that you can use when you want to stop
consuming.
The full documentation from the C++ Channel.h headerfile looks like this:
````c++
/**
* Tell the RabbitMQ server that we're ready to consume messages
*
*
* After this method is called, RabbitMQ starts delivering messages to the client
* application. The consume tag is a string identifier that will be passed to
* each received message, so that you can associate incoming messages with a
* each received message, so that you can associate incoming messages with a
* consumer. If you do not specify a consumer tag, the server will assign one
* for you.
*
*
* The following flags are supported:
*
* - nolocal if set, messages published on this channel are not also consumed
* - noack if set, consumed messages do not have to be acked, this happens automatically
* - exclusive request exclusive access, only this consumer can access the queue
* - nowait the server does not have to send a response back that consuming is active
*
* The method ChannelHandler::onConsumerStarted() will be called when the
* consumer has started (unless the nowait option was set, in which case
* no confirmation method is called)
*
*
* - nolocal if set, messages published on this channel are
* not also consumed
*
* - noack if set, consumed messages do not have to be acked,
* this happens automatically
*
* - exclusive request exclusive access, only this consumer can
* access the queue
*
* The method ChannelHandler::onConsumerStarted() will be called when the
* consumer has started.
*
* @param queue the queue from which you want to consume
* @param tag a consumer tag that will be associated with this consume operation
* @param flags additional flags
* @param arguments additional arguments
* @return bool
*/
bool consume(const std::string &queue, const std::string &tag, int flags, const AMQP::Table &arguments);
bool consume(const std::string &queue, const std::string &tag, int flags = 0);
bool consume(const std::string &queue, const std::string &tag, const AMQP::Table &arguments);
bool consume(const std::string &queue, int flags, const AMQP::Table &arguments);
bool consume(const std::string &queue, int flags = 0);
bool consume(const std::string &queue, const AMQP::Table &arguments);
DeferredConsumer &consume(const std::string &queue, const std::string &tag, int flags, const AMQP::Table &arguments);
DeferredConsumer &consume(const std::string &queue, const std::string &tag, int flags = 0);
DeferredConsumer &consume(const std::string &queue, const std::string &tag, const AMQP::Table &arguments);
DeferredConsumer &consume(const std::string &queue, int flags, const AMQP::Table &arguments);
DeferredConsumer &consume(const std::string &queue, int flags = 0);
DeferredConsumer &consume(const std::string &queue, const AMQP::Table &arguments);
````
In your ChannelHandler you can override the onConsumerStarted() method, that will be
first called before any messages are sent to you. Most users choose not to override this
method, because there is not much useful to do in it. After the consumer has started, however,
messages are starting to be sent from RabbitMQ to your client application, and they are all
passed to the ChannelHandler::onReceived() method. This method is thus very important to implement.
As you can see, the consume method returns a DeferredConsumer. This object is a
regular Deferred, with additions. The onSuccess() method of a
DeferredConsumer is slightly different than the onSuccess() method of a regular
Deferred object: one extra parameter will be supplied to your callback function
with the consumer tag.
The onSuccess() callback will be called when the consume operation _has started_,
but not when messages are actually consumed. For this you will have to install
a different callback, using the onReceived() method.
````c++
class MyChannelHandler : public AMQP::ChannelHandler
{
/**
* Method that is called when a message has been received on a channel
* This message will be called for every message that is received after
* you started consuming. Make sure you acknowledge the messages when its
* safe to remove them from RabbitMQ (unless you set no-ack option when you
* started the consumer)
* @param channel the channel on which the consumer was started
* @param message the consumed message
* @param deliveryTag the delivery tag, you need this to acknowledge the message
* @param consumerTag the consumer identifier that was used to retrieve this message
* @param redelivered is this a redelivered message?
*/
virtual void onReceived(AMQP::Channel *channel, const AMQP::Message &message, uint64_t deliveryTag, const std::string &consumerTag, bool redelivered)
{
// @todo
// add your own processing
// after the message was processed, acknowledge it
channel->ack(deliveryTag);
}
// callback function that is called when the consume operation starts
auto startCb = [](const std::string &consumertag) {
std::cout << "consume operation started" << std::endl;
};
// callback function that is called when the consume operation failed
auto errorCb = [](const char *message) {
std::cout << "consume operation failed" << std::endl;
}
// callback operation when a message was received
auto messageCb = [&channel](const AMQP::Message &message, uint64_t deliveryTag, bool redelivered) {
std::cout << "message received" << std::endl;
// acknowledge the message
channel.ack(deliveryTag);
}
// start consuming from the queue, and install the callbacks
channel.consume("my-queue")
.onSuccess(startCb)
.onError(errorCb)
.onReceived(messageCb);
````
The Message object holds all information of the delivered message: the actual content,
all meta information from the envelope (in fact, the Message class is derived from the Envelope class),
and even the name of the exchange and the routing key that were used when the message was originally
published. For a full list of all information in the Message class, you best have a look at the
The Message object holds all information of the delivered message: the actual
content, all meta information from the envelope (in fact, the Message class is
derived from the Envelope class), and even the name of the exchange and the
routing key that were used when the message was originally published. For a full
list of all information in the Message class, you best have a look at the
message.h, envelope.h and metadata.h header files.
Another important parameter to the onReceived() method is the deliveryTag parameter. This is a
unique identifier that you need to acknowledge an incoming message. RabbitMQ only removes the
message after it has been acknowledged, so that if your application crashes while it was busy
processing the message, the message does not get lost but remains in the queue. But this means that
after you've processed the message, you must inform RabbitMQ about it by calling the Channel:ack()
method. This method is very simple and takes in its simplest form only one parameter: the
Another important parameter to the onReceived() method is the deliveryTag parameter.
This is a unique identifier that you need to acknowledge an incoming message.
RabbitMQ only removes the message after it has been acknowledged, so that if your
application crashes while it was busy processing the message, the message does
not get lost but remains in the queue. But this means that after you've processed
the message, you must inform RabbitMQ about it by calling the Channel:ack() method.
This method is very simple and takes in its simplest form only one parameter: the
deliveryTag of the message.
The consumerTag that you see in the onReceived method() is the same string identifier that was
passed to the Channel::consume() method.
Consuming messages is a continuous process. RabbitMQ keeps sending messages, until
you stop the consumer, which can be done by calling the Channel::cancel() method.
If you close the channel, or the entire TCP connection, consuming also stops.
Consuming messages is a continuous process. RabbitMQ keeps sending messages, until you stop
the consumer, which can be done by calling the Channel::cancel() method. If you close the channel,
or the entire TCP connection, consuming also stops.
RabbitMQ throttles the number of messages that are delivered to you, to prevent that your application
is flooded with messages from the queue, and to spread out the messages over multiple consumers.
This is done with a setting called quality-of-service (QOS). The QOS setting is a numeric value which
holds the number of unacknowledged messages that you are allowed to have. RabbitMQ stops sending
additional messages when the number of unacknowledges messages has reached this limit, and only
sends additional messages when an earlier message gets acknowledged. To change the QOS, you can
simple call Channel::setQos().
RabbitMQ throttles the number of messages that are delivered to you, to prevent
that your application is flooded with messages from the queue, and to spread out
the messages over multiple consumers. This is done with a setting called
quality-of-service (QOS). The QOS setting is a numeric value which holds the number
of unacknowledged messages that you are allowed to have. RabbitMQ stops sending
additional messages when the number of unacknowledges messages has reached this
limit, and only sends additional messages when an earlier message gets acknowledged.
To change the QOS, you can simple call Channel::setQos().
WORK IN PROGRESS
@ -534,11 +636,12 @@ need additional attention:
- ability to set up secure connections (or is this fully done on the IO level)
- login with other protocols than login/password
- publish confirms
- returned messages
We also need to add more safety checks so that strange or invalid data from
RabbitMQ does not break the library (although in reality RabbitMQ only sends
We also need to add more safety checks so that strange or invalid data from
RabbitMQ does not break the library (although in reality RabbitMQ only sends
valid data). Also, when we now receive an answer from RabbitMQ that does not
match the request that we sent before, we do not report an error (this is also
match the request that we sent before, we do not report an error (this is also
an issue that only occurs in theory).
It would be nice to have sample implementations for the ConnectionHandler

View File

@ -6,6 +6,8 @@
* @documentation public
*/
#pragma once
// base C++ include files
#include <vector>
#include <string>
@ -16,6 +18,9 @@
#include <limits>
#include <cstddef>
#include <cstring>
#include <stdexcept>
#include <utility>
#include <iostream>
// base C include files
#include <stdint.h>
@ -29,6 +34,7 @@
#include <amqpcpp/receivedframe.h>
#include <amqpcpp/outbuffer.h>
#include <amqpcpp/watchable.h>
#include <amqpcpp/monitor.h>
// amqp types
#include <amqpcpp/field.h>
@ -48,7 +54,12 @@
// mid level includes
#include <amqpcpp/exchangetype.h>
#include <amqpcpp/flags.h>
#include <amqpcpp/channelhandler.h>
#include <amqpcpp/callbacks.h>
#include <amqpcpp/deferred.h>
#include <amqpcpp/deferredconsumer.h>
#include <amqpcpp/deferredqueue.h>
#include <amqpcpp/deferreddelete.h>
#include <amqpcpp/deferredcancel.h>
#include <amqpcpp/channelimpl.h>
#include <amqpcpp/channel.h>
#include <amqpcpp/login.h>

View File

@ -1,6 +1,7 @@
#pragma once
/**
* AMQP field array
*
*
* @copyright 2014 Copernica BV
*/
@ -20,7 +21,7 @@ private:
* @typedef
*/
typedef std::vector<std::shared_ptr<Field>> FieldArray;
/**
* The actual fields
* @var FieldArray
@ -30,7 +31,7 @@ private:
public:
/**
* Constructor to construct an array from a received frame
*
*
* @param frame received frame
*/
Array(ReceivedFrame &frame);
@ -61,9 +62,9 @@ public:
* Create a new instance of this object
* @return Field*
*/
virtual Field *clone() const override
virtual std::shared_ptr<Field> clone() const override
{
return new Array(*this);
return std::make_shared<Array>(*this);
}
/**
@ -82,8 +83,20 @@ public:
*/
Array set(uint8_t index, const Field &value)
{
// copy to a new pointer and store it
_fields[index] = std::shared_ptr<Field>(value.clone());
// construct a shared pointer
auto ptr = value.clone();
// should we overwrite an existing record?
if (index >= _fields.size())
{
// append index
_fields.push_back(ptr);
}
else
{
// overwrite pointer
_fields[index] = ptr;
}
// allow chaining
return *this;
@ -97,7 +110,26 @@ public:
* @param index field index
* @return Field
*/
const Field &get(uint8_t index);
const Field &get(uint8_t index) const;
/**
* Get number of elements on this array
*
* @return array size
*/
uint32_t count() const;
/**
* Remove last element from array
*/
void pop_back();
/**
* Add field to end of array
*
* @param value
*/
void push_back(const Field &value);
/**
* Get a field
@ -109,9 +141,19 @@ public:
{
return ArrayFieldProxy(this, index);
}
/**
* Get a const field
* @param index field index
* @return Field
*/
const Field &operator[](uint8_t index) const
{
return get(index);
}
/**
* Write encoded payload to the given buffer.
* Write encoded payload to the given buffer.
* @param buffer
*/
virtual void fill(OutBuffer& buffer) const override;
@ -125,6 +167,45 @@ public:
{
return 'A';
}
/**
* Output the object to a stream
* @param std::ostream
*/
virtual void output(std::ostream &stream) const
{
// prefix
stream << "array(";
// is this the first iteration
bool first = true;
// loop through all members
for (auto &iter : _fields)
{
// split with comma
if (!first) stream << ",";
// show output
stream << *iter;
// no longer first iter
first = false;
}
// postfix
stream << ")";
}
/**
* Cast to array
* @return Array
*/
virtual operator const Array& () const override
{
// this already is an array, so no cast is necessary
return *this;
}
};
/**

View File

@ -1,3 +1,4 @@
#pragma once
/**
* BooleanSet.h
*
@ -77,9 +78,25 @@ public:
* Extending from field forces us to implement a clone function.
* @return shared_ptr
*/
virtual Field *clone() const override
virtual std::shared_ptr<Field> clone() const override
{
return new BooleanSet(*this);
return std::make_shared<BooleanSet>(*this);
}
/**
* Output the object to a stream
* @param std::ostream
*/
virtual void output(std::ostream &stream) const override
{
// prefix
stream << "booleanset(";
// the members
for (int i=0; i<8; i++) stream << (i == 0 ? "" : ",") << (get(i) ? 1 : 0);
// postfix
stream << ")";
}
/**

32
include/callbacks.h Normal file
View File

@ -0,0 +1,32 @@
/**
* Callbacks.h
*
* Class storing deferred callbacks of different type.
*
* @copyright 2014 Copernica BV
*/
/**
* Set up namespace
*/
namespace AMQP {
/**
* All the callbacks that are supported
*
* When someone registers a callback function for certain events, it should
* match one of the following signatures.
*/
using SuccessCallback = std::function<void()>;
using ErrorCallback = std::function<void(const char *message)>;
using FinalizeCallback = std::function<void()>;
using MessageCallback = std::function<void(const Message &message, uint64_t deliveryTag, bool redelivered)>;
using QueueCallback = std::function<void(const std::string &name, uint32_t messagecount, uint32_t consumercount)>;
using DeleteCallback = std::function<void(uint32_t deletedmessages)>;
using ConsumeCallback = std::function<void(const std::string &consumer)>;
using CancelCallback = std::function<void(const std::string &consumer)>;
/**
* End namespace
*/
}

View File

@ -1,6 +1,7 @@
#pragma once
/**
* Class describing a (mid-level) AMQP channel implementation
*
*
* @copyright 2014 Copernica BV
*/
@ -25,9 +26,8 @@ public:
/**
* Construct a channel object
* @param connection
* @param handler
*/
Channel(Connection *connection, ChannelHandler *handler) : _implementation(this, connection, handler) {}
Channel(Connection *connection) : _implementation(this, connection) {}
/**
* Destructor
@ -35,27 +35,55 @@ public:
virtual ~Channel() {}
/**
* Pause deliveries on a channel
*
* This will stop all incoming messages
*
* This method returns true if the request to pause has been sent to the
* broker. This does not necessarily mean that the channel is already
* paused.
*
* @return bool
* Callback that is called when the channel was succesfully created.
*
* Only one callback can be registered. Calling this function multiple
* times will remove the old callback.
*
* @param callback the callback to execute
*/
bool pause()
void onReady(const SuccessCallback &callback)
{
// store callback in implementation
_implementation._readyCallback = callback;
}
/**
* Callback that is called when an error occurs.
*
* Only one error callback can be registered. Calling this function
* multiple times will remove the old callback.
*
* @param callback the callback to execute
*/
void onError(const ErrorCallback &callback)
{
// store callback in implementation
_implementation._errorCallback = callback;
}
/**
* Pause deliveries on a channel
*
* This will stop all incoming messages
*
* This function returns a deferred handler. Callbacks can be installed
* using onSuccess(), onError() and onFinalize() methods.
*/
Deferred &pause()
{
return _implementation.pause();
}
/**
* Resume a paused channel
*
* @return bool
*
* This will resume incoming messages
*
* This function returns a deferred handler. Callbacks can be installed
* using onSuccess(), onError() and onFinalize() methods.
*/
bool resume()
Deferred &resume()
{
return _implementation.resume();
}
@ -68,312 +96,340 @@ public:
{
return _implementation.connected();
}
/**
* Start a transaction
* @return bool
*
* This function returns a deferred handler. Callbacks can be installed
* using onSuccess(), onError() and onFinalize() methods.
*/
bool startTransaction()
Deferred &startTransaction()
{
return _implementation.startTransaction();
}
/**
* Commit the current transaction
* @return bool
*
* This function returns a deferred handler. Callbacks can be installed
* using onSuccess(), onError() and onFinalize() methods.
*/
bool commitTransaction()
Deferred &commitTransaction()
{
return _implementation.commitTransaction();
}
/**
* Rollback the current transaction
* @return bool
*
* This function returns a deferred handler. Callbacks can be installed
* using onSuccess(), onError() and onFinalize() methods.
*/
bool rollbackTransaction()
Deferred &rollbackTransaction()
{
return _implementation.rollbackTransaction();
}
/**
* Declare an exchange
*
*
* If an empty name is supplied, a name will be assigned by the server.
*
*
* The following flags can be used for the exchange:
*
*
* - durable exchange survives a broker restart
* - autodelete exchange is automatically removed when all connected queues are removed
* - passive only check if the exchange exist
*
*
* @param name name of the exchange
* @param type exchange type
* @param flags exchange flags
* @param arguments additional arguments
* @return bool
*
* This function returns a deferred handler. Callbacks can be installed
* using onSuccess(), onError() and onFinalize() methods.
*/
bool declareExchange(const std::string &name, ExchangeType type, int flags, const Table &arguments) { return _implementation.declareExchange(name, type, flags, arguments); }
bool declareExchange(const std::string &name, ExchangeType type, const Table &arguments) { return _implementation.declareExchange(name, type, 0, arguments); }
bool declareExchange(const std::string &name, ExchangeType type = fanout, int flags = 0) { return _implementation.declareExchange(name, type, flags, Table()); }
bool declareExchange(ExchangeType type, int flags, const Table &arguments) { return _implementation.declareExchange(std::string(), type, flags, arguments); }
bool declareExchange(ExchangeType type, const Table &arguments) { return _implementation.declareExchange(std::string(), type, 0, arguments); }
bool declareExchange(ExchangeType type = fanout, int flags = 0) { return _implementation.declareExchange(std::string(), type, flags, Table()); }
Deferred &declareExchange(const std::string &name, ExchangeType type, int flags, const Table &arguments) { return _implementation.declareExchange(name, type, flags, arguments); }
Deferred &declareExchange(const std::string &name, ExchangeType type, const Table &arguments) { return _implementation.declareExchange(name, type, 0, arguments); }
Deferred &declareExchange(const std::string &name, ExchangeType type = fanout, int flags = 0) { return _implementation.declareExchange(name, type, flags, Table()); }
Deferred &declareExchange(ExchangeType type, int flags, const Table &arguments) { return _implementation.declareExchange(std::string(), type, flags, arguments); }
Deferred &declareExchange(ExchangeType type, const Table &arguments) { return _implementation.declareExchange(std::string(), type, 0, arguments); }
Deferred &declareExchange(ExchangeType type = fanout, int flags = 0) { return _implementation.declareExchange(std::string(), type, flags, Table()); }
/**
* Remove an exchange
*
*
* The following flags can be used for the exchange:
*
*
* - ifunused only delete if no queues are connected
* @param name name of the exchange to remove
* @param flags optional flags
* @return bool
*
* This function returns a deferred handler. Callbacks can be installed
* using onSuccess(), onError() and onFinalize() methods.
*/
bool removeExchange(const std::string &name, int flags = 0) { return _implementation.removeExchange(name, flags); }
Deferred &removeExchange(const std::string &name, int flags = 0) { return _implementation.removeExchange(name, flags); }
/**
* Bind two exchanges to each other
*
* The following flags can be used for the exchange
*
* - nowait do not wait on response
*
*
* @param source the source exchange
* @param target the target exchange
* @param routingkey the routing key
* @param flags optional flags
* @param arguments additional bind arguments
* @return bool
*
* This function returns a deferred handler. Callbacks can be installed
* using onSuccess(), onError() and onFinalize() methods.
*/
bool bindExchange(const std::string &source, const std::string &target, const std::string &routingkey, int flags, const Table &arguments) { return _implementation.bindExchange(source, target, routingkey, flags, arguments); }
bool bindExchange(const std::string &source, const std::string &target, const std::string &routingkey, const Table &arguments) { return _implementation.bindExchange(source, target, routingkey, 0, arguments); }
bool bindExchange(const std::string &source, const std::string &target, const std::string &routingkey, int flags = 0) { return _implementation.bindExchange(source, target, routingkey, flags, Table()); }
Deferred &bindExchange(const std::string &source, const std::string &target, const std::string &routingkey, const Table &arguments) { return _implementation.bindExchange(source, target, routingkey, arguments); }
Deferred &bindExchange(const std::string &source, const std::string &target, const std::string &routingkey) { return _implementation.bindExchange(source, target, routingkey, Table()); }
/**
* Unbind two exchanges from one another
*
* The following flags can be used for the exchange
*
* - nowait do not wait on response
*
*
* @param target the target exchange
* @param source the source exchange
* @param routingkey the routing key
* @param flags optional flags
* @param arguments additional unbind arguments
* @return bool
*
* This function returns a deferred handler. Callbacks can be installed
* using onSuccess(), onError() and onFinalize() methods.
*/
bool unbindExchange(const std::string &target, const std::string &source, const std::string &routingkey, int flags, const Table &arguments) { return _implementation.unbindExchange(target, source, routingkey, flags, arguments); }
bool unbindExchange(const std::string &target, const std::string &source, const std::string &routingkey, const Table &arguments) { return _implementation.unbindExchange(target, source, routingkey, 0, arguments); }
bool unbindExchange(const std::string &target, const std::string &source, const std::string &routingkey, int flags = 0) { return _implementation.unbindExchange(target, source, routingkey, flags, Table()); }
Deferred &unbindExchange(const std::string &target, const std::string &source, const std::string &routingkey, const Table &arguments) { return _implementation.unbindExchange(target, source, routingkey, arguments); }
Deferred &unbindExchange(const std::string &target, const std::string &source, const std::string &routingkey) { return _implementation.unbindExchange(target, source, routingkey, Table()); }
/**
* Declare a queue
*
*
* If you do not supply a name, a name will be assigned by the server.
*
*
* The flags can be a combination of the following values:
*
*
* - durable queue survives a broker restart
* - autodelete queue is automatically removed when all connected consumers are gone
* - passive only check if the queue exist
* - exclusive the queue only exists for this connection, and is automatically removed when connection is gone
*
*
* @param name name of the queue
* @param flags combination of flags
* @param arguments optional arguments
*
* This function returns a deferred handler. Callbacks can be installed
* using onSuccess(), onError() and onFinalize() methods.
*
* The onSuccess() callback that you can install should have the following signature:
*
* void myCallback(AMQP::Channel *channel, const std::string &name, uint32_t messageCount, uint32_t consumerCount);
*
* For example: channel.declareQueue("myqueue").onSuccess([](AMQP::Channel *channel, const std::string &name, uint32_t messageCount, uint32_t consumerCount) {
*
* std::cout << "Queue '" << name << "' has been declared with " << messageCount << " messages and " << consumerCount << " consumers" << std::endl;
*
* });
*/
bool declareQueue(const std::string &name, int flags, const Table &arguments) { return _implementation.declareQueue(name, flags, arguments); }
bool declareQueue(const std::string &name, const Table &arguments) { return _implementation.declareQueue(name, 0, arguments); }
bool declareQueue(const std::string &name, int flags = 0) { return _implementation.declareQueue(name, flags, Table()); }
bool declareQueue(int flags, const Table &arguments) { return _implementation.declareQueue(std::string(), flags, arguments); }
bool declareQueue(const Table &arguments) { return _implementation.declareQueue(std::string(), 0, arguments); }
bool declareQueue(int flags = 0) { return _implementation.declareQueue(std::string(), flags, Table()); }
DeferredQueue &declareQueue(const std::string &name, int flags, const Table &arguments) { return _implementation.declareQueue(name, flags, arguments); }
DeferredQueue &declareQueue(const std::string &name, const Table &arguments) { return _implementation.declareQueue(name, 0, arguments); }
DeferredQueue &declareQueue(const std::string &name, int flags = 0) { return _implementation.declareQueue(name, flags, Table()); }
DeferredQueue &declareQueue(int flags, const Table &arguments) { return _implementation.declareQueue(std::string(), flags, arguments); }
DeferredQueue &declareQueue(const Table &arguments) { return _implementation.declareQueue(std::string(), 0, arguments); }
DeferredQueue &declareQueue(int flags = 0) { return _implementation.declareQueue(std::string(), flags, Table()); }
/**
* Bind a queue to an exchange
*
* The following flags can be used for the exchange
*
* - nowait do not wait on response
*
*
* @param exchange the source exchange
* @param queue the target queue
* @param routingkey the routing key
* @param flags additional flags
* @param arguments additional bind arguments
* @return bool
*
* This function returns a deferred handler. Callbacks can be installed
* using onSuccess(), onError() and onFinalize() methods.
*/
bool bindQueue(const std::string &exchange, const std::string &queue, const std::string &routingkey, int flags, const Table &arguments) { return _implementation.bindQueue(exchange, queue, routingkey, flags, arguments); }
bool bindQueue(const std::string &exchange, const std::string &queue, const std::string &routingkey, const Table &arguments) { return _implementation.bindQueue(exchange, queue, routingkey, 0, arguments); }
bool bindQueue(const std::string &exchange, const std::string &queue, const std::string &routingkey, int flags = 0) { return _implementation.bindQueue(exchange, queue, routingkey, flags, Table()); }
Deferred &bindQueue(const std::string &exchange, const std::string &queue, const std::string &routingkey, const Table &arguments) { return _implementation.bindQueue(exchange, queue, routingkey, arguments); }
Deferred &bindQueue(const std::string &exchange, const std::string &queue, const std::string &routingkey) { return _implementation.bindQueue(exchange, queue, routingkey, Table()); }
/**
* Unbind a queue from an exchange
* @param exchange the source exchange
* @param queue the target queue
* @param routingkey the routing key
* @param arguments additional bind arguments
* @return bool
*
* This function returns a deferred handler. Callbacks can be installed
* using onSuccess(), onError() and onFinalize() methods.
*/
bool unbindQueue(const std::string &exchange, const std::string &queue, const std::string &routingkey, const Table &arguments) { return _implementation.unbindQueue(exchange, queue, routingkey, arguments); }
bool unbindQueue(const std::string &exchange, const std::string &queue, const std::string &routingkey) { return _implementation.unbindQueue(exchange, queue, routingkey, Table()); }
Deferred &unbindQueue(const std::string &exchange, const std::string &queue, const std::string &routingkey, const Table &arguments) { return _implementation.unbindQueue(exchange, queue, routingkey, arguments); }
Deferred &unbindQueue(const std::string &exchange, const std::string &queue, const std::string &routingkey) { return _implementation.unbindQueue(exchange, queue, routingkey, Table()); }
/**
* Purge a queue
*
* The following flags can be used for the exchange
*
* - nowait do not wait on response
*
*
* @param name name of the queue
* @param flags additional flags
* @return bool
*
* This function returns a deferred handler. Callbacks can be installed
* using onSuccess(), onError() and onFinalize() methods.
*
* The onSuccess() callback that you can install should have the following signature:
*
* void myCallback(AMQP::Channel *channel, uint32_t messageCount);
*
* For example: channel.declareQueue("myqueue").onSuccess([](AMQP::Channel *channel, uint32_t messageCount) {
*
* std::cout << "Queue purged, all " << messageCount << " messages removed" << std::endl;
*
* });
*/
bool purgeQueue(const std::string &name, int flags = 0){ return _implementation.purgeQueue(name, flags); }
DeferredDelete &purgeQueue(const std::string &name){ return _implementation.purgeQueue(name); }
/**
* Remove a queue
*
*
* The following flags can be used for the exchange:
*
*
* - ifunused only delete if no consumers are connected
* - ifempty only delete if the queue is empty
*
* @param name name of the queue to remove
* @param flags optional flags
* @return bool
*
* This function returns a deferred handler. Callbacks can be installed
* using onSuccess(), onError() and onFinalize() methods.
*
* The onSuccess() callback that you can install should have the following signature:
*
* void myCallback(AMQP::Channel *channel, uint32_t messageCount);
*
* For example: channel.removeQueue("myqueue").onSuccess([](AMQP::Channel *channel, uint32_t messageCount) {
*
* std::cout << "Queue deleted, along with " << messageCount << " messages" << std::endl;
*
* });
*/
bool removeQueue(const std::string &name, int flags = 0) { return _implementation.removeQueue(name, flags); }
DeferredDelete &removeQueue(const std::string &name, int flags = 0) { return _implementation.removeQueue(name, flags); }
/**
* Publish a message to an exchange
*
* The following flags can be used
*
* - mandatory if set, an unroutable message will be reported to the channel handler with the onReturned method
* - immediate if set, a message that could not immediately be consumed is returned to the onReturned method
*
* If either of the two flags is set, and the message could not immediately
* be published, the message is returned by the server to the client. If you
* want to catch such returned messages, you need to implement the
* ChannelHandler::onReturned() method.
*
*
* @param exchange the exchange to publish to
* @param routingkey the routing key
* @param flags optional flags (see above)
* @param envelope the full envelope to send
* @param message the message to send
* @param size size of the message
*/
bool publish(const std::string &exchange, const std::string &routingKey, int flags, const Envelope &envelope) { return _implementation.publish(exchange, routingKey, flags, envelope); }
bool publish(const std::string &exchange, const std::string &routingKey, const Envelope &envelope) { return _implementation.publish(exchange, routingKey, 0, envelope); }
bool publish(const std::string &exchange, const std::string &routingKey, int flags, const std::string &message) { return _implementation.publish(exchange, routingKey, flags, Envelope(message)); }
bool publish(const std::string &exchange, const std::string &routingKey, const std::string &message) { return _implementation.publish(exchange, routingKey, 0, Envelope(message)); }
bool publish(const std::string &exchange, const std::string &routingKey, int flags, const char *message, size_t size) { return _implementation.publish(exchange, routingKey, flags, Envelope(message, size)); }
bool publish(const std::string &exchange, const std::string &routingKey, const char *message, size_t size) { return _implementation.publish(exchange, routingKey, 0, Envelope(message, size)); }
bool publish(const std::string &exchange, const std::string &routingKey, const Envelope &envelope) { return _implementation.publish(exchange, routingKey, envelope); }
bool publish(const std::string &exchange, const std::string &routingKey, const std::string &message) { return _implementation.publish(exchange, routingKey, Envelope(message)); }
bool publish(const std::string &exchange, const std::string &routingKey, const char *message, size_t size) { return _implementation.publish(exchange, routingKey, Envelope(message, size)); }
/**
* Set the Quality of Service (QOS) for this channel
*
* When you consume messages, every single messages needs to be ack'ed to inform
*
* When you consume messages, every single message needs to be ack'ed to inform
* the RabbitMQ server that is has been received. The Qos setting specifies the
* number of unacked messages that may exist in the client application. The server
* stops delivering more messages if the number of unack'ed messages has reached
* the prefetchCount
*
*
* @param prefetchCount maximum number of messages to prefetch
* @return bool whether the Qos frame is sent.
*/
bool setQos(uint16_t prefetchCount)
Deferred &setQos(uint16_t prefetchCount)
{
return _implementation.setQos(prefetchCount);
}
/**
* Tell the RabbitMQ server that we're ready to consume messages
*
*
* After this method is called, RabbitMQ starts delivering messages to the client
* application. The consume tag is a string identifier that will be passed to
* each received message, so that you can associate incoming messages with a
* each received message, so that you can associate incoming messages with a
* consumer. If you do not specify a consumer tag, the server will assign one
* for you.
*
*
* The following flags are supported:
*
*
* - nolocal if set, messages published on this channel are not also consumed
* - noack if set, consumed messages do not have to be acked, this happens automatically
* - exclusive request exclusive access, only this consumer can access the queue
* - nowait the server does not have to send a response back that consuming is active
*
* The method ChannelHandler::onConsumerStarted() will be called when the
* consumer has started (unless the nowait option was set, in which case
* no confirmation method is called)
*
*
* @param queue the queue from which you want to consume
* @param tag a consumer tag that will be associated with this consume operation
* @param flags additional flags
* @param arguments additional arguments
* @return bool
*
* This function returns a deferred handler. Callbacks can be installed
* using onSuccess(), onError() and onFinalize() methods.
*
* The onSuccess() callback that you can install should have the following signature:
*
* void myCallback(AMQP::Channel *channel, const std::string& tag);
*
* For example: channel.consume("myqueue").onSuccess([](AMQP::Channel *channel, const std::string& tag) {
*
* std::cout << "Started consuming under tag " << tag << std::endl;
*
* });
*/
bool consume(const std::string &queue, const std::string &tag, int flags, const Table &arguments) { return _implementation.consume(queue, tag, flags, arguments); }
bool consume(const std::string &queue, const std::string &tag, int flags = 0) { return _implementation.consume(queue, tag, flags, Table()); }
bool consume(const std::string &queue, const std::string &tag, const Table &arguments) { return _implementation.consume(queue, tag, 0, arguments); }
bool consume(const std::string &queue, int flags, const Table &arguments) { return _implementation.consume(queue, std::string(), flags, arguments); }
bool consume(const std::string &queue, int flags = 0) { return _implementation.consume(queue, std::string(), flags, Table()); }
bool consume(const std::string &queue, const Table &arguments) { return _implementation.consume(queue, std::string(), 0, arguments); }
DeferredConsumer &consume(const std::string &queue, const std::string &tag, int flags, const Table &arguments) { return _implementation.consume(queue, tag, flags, arguments); }
DeferredConsumer &consume(const std::string &queue, const std::string &tag, int flags = 0) { return _implementation.consume(queue, tag, flags, Table()); }
DeferredConsumer &consume(const std::string &queue, const std::string &tag, const Table &arguments) { return _implementation.consume(queue, tag, 0, arguments); }
DeferredConsumer &consume(const std::string &queue, int flags, const Table &arguments) { return _implementation.consume(queue, std::string(), flags, arguments); }
DeferredConsumer &consume(const std::string &queue, int flags = 0) { return _implementation.consume(queue, std::string(), flags, Table()); }
DeferredConsumer &consume(const std::string &queue, const Table &arguments) { return _implementation.consume(queue, std::string(), 0, arguments); }
/**
* Cancel a running consume call
*
*
* If you want to stop a running consumer, you can use this method with the consumer tag
*
* The following flags are supported:
*
* - nowait the server does not have to send a response back that the consumer has been cancelled
*
* The method ChannelHandler::onConsumerStopped() will be called when the consumer
* was succesfully stopped (unless the nowait option was used, in which case no
* confirmation method is called)
*
*
* @param tag the consumer tag
* @param flags optional additional flags
* @return bool
*
* This function returns a deferred handler. Callbacks can be installed
* using onSuccess(), onError() and onFinalize() methods.
*
* The onSuccess() callback that you can install should have the following signature:
*
* void myCallback(AMQP::Channel *channel, const std::string& tag);
*
* For example: channel.cancel("myqueue").onSuccess([](AMQP::Channel *channel, const std::string& tag) {
*
* std::cout << "Stopped consuming under tag " << tag << std::endl;
*
* });
*/
bool cancel(const std::string &tag, int flags = 0) { return _implementation.cancel(tag, flags); }
DeferredCancel &cancel(const std::string &tag) { return _implementation.cancel(tag); }
/**
* Acknoldge a received message
*
* When a message is received in the ChannelHandler::onReceived() method,
* you must acknoledge it so that RabbitMQ removes it from the queue (unless
*
* When a message is received in the DeferredConsumer::onReceived() method,
* you must acknowledge it so that RabbitMQ removes it from the queue (unless
* you are consuming with the noack option). This method can be used for
* this acknoledging.
*
* this acknowledging.
*
* The following flags are supported:
*
* - multiple acknoledge multiple messages: all un-acked messages that were earlier delivered are acknowledged too
*
*
* - multiple acknowledge multiple messages: all un-acked messages that were earlier delivered are acknowledged too
*
* @param deliveryTag the unique delivery tag of the message
* @param flags optional flags
* @return bool
*/
bool ack(uint64_t deliveryTag, int flags=0) { return _implementation.ack(deliveryTag, flags); }
/**
* Reject or nack a message
*
* When a message was received in the ChannelHandler::onReceived() method,
* and you don't want to acknoledge it, you can also choose to reject it by
* calling this reject method.
*
*
* When a message was received in the DeferredConsumer::onReceived() method,
* and you don't want to acknowledge it, you can also choose to reject it by
* calling this reject method.
*
* The following flags are supported:
*
*
* - multiple reject multiple messages: all un-acked messages that were earlier delivered are unacked too
* - requeue if set, the message is put back in the queue, otherwise it is dead-lettered/removed
*
*
* @param deliveryTag the unique delivery tag of the message
* @param flags optional flags
* @return bool
@ -382,27 +438,28 @@ public:
/**
* Recover all messages that were not yet acked
*
* This method asks the server to redeliver all unacknowledged messages on a specified
*
* This method asks the server to redeliver all unacknowledged messages on a specified
* channel. Zero or more messages may be redelivered.
*
*
* The following flags are supported:
*
*
* - requeue if set, the server will requeue the messages, so the could also end up with at different consumer
*
*
* @param flags
* @return bool
*
* This function returns a deferred handler. Callbacks can be installed
* using onSuccess(), onError() and onFinalize() methods.
*/
bool recover(int flags = 0) { return _implementation.recover(flags); }
Deferred &recover(int flags = 0) { return _implementation.recover(flags); }
/**
* Close the current channel
* @return bool
*
* This function returns a deferred handler. Callbacks can be installed
* using onSuccess(), onError() and onFinalize() methods.
*/
bool close()
{
return _implementation.close();
}
Deferred &close() { return _implementation.close(); }
/**
* Get the channel we're working on

View File

@ -1,208 +0,0 @@
/**
* ChannelHandler.h
*
* Interface that should be implemented by a user of the AMQP library,
* and that is passed to the Connection::createChannel() method.
*
* This interface contains a number of methods that are called when
* the channel changes state.
*
* @copyright 2014 Copernica BV
*/
/**
* Set up namespace
*/
namespace AMQP {
/**
* Class definition
*/
class ChannelHandler
{
public:
/**
* Method that is called when the channel was succesfully created.
* @param channel the channel that is ready
*/
virtual void onReady(Channel *channel) {}
/**
* An error has occured on the channel
* The channel is no longer usable after an error has occured on it.
* @param channel the channel on which the error occured
* @param message human readable error message
*/
virtual void onError(Channel *channel, const std::string &message) {}
/**
* Method that is called when the channel was paused
* This is the result of a call to Channel::pause()
* @param channel the channel that is now paused
*/
virtual void onPaused(Channel *channel) {}
/**
* Method that is called when the channel was resumed
* This is the result of a call to Channel::resume()
* @param channel the channel that is no longer paused
*/
virtual void onResumed(Channel *channel) {}
/**
* Method that is called when a channel is closed
* This is the result of a call to Channel::close()
* @param channel the channel that is closed
*/
virtual void onClosed(Channel *channel) {}
/**
* Method that is called when a transaction was started
* This is the result of a call to Channel::startTransaction()
* @param channel the channel on which the transaction was started
*/
virtual void onTransactionStarted(Channel *channel) {}
/**
* Method that is called when a transaction was committed
* This is the result of a call to Channel::commitTransaction()
* @param channel the channel on which the transaction was committed
*/
virtual void onTransactionCommitted(Channel *channel) {}
/**
* Method that is called when a transaction was rolled back
* This is the result of a call to Channel::rollbackTransaction()
* @param channel the channel on which the transaction was rolled back
*/
virtual void onTransactionRolledBack(Channel *channel) {}
/**
* Method that is called when an exchange is bound
* This is the result of a call to Channel::bindExchange()
* @param channel the channel on which the exchange was bound
*/
virtual void onExchangeBound(Channel *channel) {}
/**
* Method that is called when an exchange is unbound
* This is the result of a call to Channel::unbindExchange()
* @param channel the channel on which the exchange was unbound
*/
virtual void onExchangeUnbound(Channel *channel) {}
/**
* Method that is called when an exchange is deleted
* This is the result of a call to Channel::deleteExchange()
* @param channel the channel on which the exchange was deleted
*/
virtual void onExchangeDeleted(Channel *channel) {}
/**
* Mehod that is called when an exchange is declared
* This is the result of a call to Channel::declareExchange()
* @param channel the channel on which the exchange was declared
*/
virtual void onExchangeDeclared(Channel *channel) {}
/**
* Method that is called when a queue is declared
* This is the result of a call to Channel::declareQueue()
* @param channel the channel on which the queue was declared
* @param name name of the queue
* @param messageCount number of messages in queue
* @param consumerCount number of active consumers
*/
virtual void onQueueDeclared(Channel *channel, const std::string &name, uint32_t messageCount, uint32_t consumerCount) {}
/**
* Method that is called when a queue is bound
* This is the result of a call to Channel::bindQueue()
* @param channel the channel on which the queue was bound
*/
virtual void onQueueBound(Channel *channel) {}
/**
* Method that is called when a queue is deleted
* This is the result of a call to Channel::deleteQueue()
* @param channel the channel on which the queue was deleted
* @param messageCount number of messages deleted along with the queue
*/
virtual void onQueueDeleted(Channel *channel, uint32_t messageCount) {}
/**
* Method that is called when a queue is unbound
* This is the result of a call to Channel::unbindQueue()
* @param channel the channel on which the queue was unbound
*/
virtual void onQueueUnbound(Channel *channel) {}
/**
* Method that is called when a queue is purged
* This is the result of a call to Channel::purgeQueue()
* @param channel the channel on which the queue was emptied
* @param messageCount number of message purged
*/
virtual void onQueuePurged(Channel *channel, uint32_t messageCount) {}
/**
* Method that is called when the quality-of-service was changed
* This is the result of a call to Channel::setQos()
* @param channel the channel on which the qos was set
*/
virtual void onQosSet(Channel *channel) {}
/**
* Method that is called when a consumer was started
* This is the result of a call to Channel::consume()
* @param channel the channel on which the consumer was started
* @param tag the consumer tag
*/
virtual void onConsumerStarted(Channel *channel, const std::string &tag) {}
/**
* Method that is called when a consumer was stopped
* This is the result of a call to Channel::cancel()
* @param channel the channel on which the consumer was stopped
* @param tag the consumer tag
*/
virtual void onConsumerStopped(Channel *channel, const std::string &tag) {}
/**
* Method that is called when a message has been received on a channel
* This message will be called for every message that is received after
* you started consuming. Make sure you acknowledge the messages when its
* safe to remove them from RabbitMQ (unless you set no-ack option when you
* started the consumer)
* @param channel the channel on which the consumer was started
* @param message the consumed message
* @param deliveryTag the delivery tag, you need this to acknowledge the message
* @param consumerTag the consumer identifier that was used to retrieve this message
* @param redelivered is this a redelivered message?
*/
virtual void onReceived(Channel *channel, const Message &message, uint64_t deliveryTag, const std::string &consumerTag, bool redelivered) {}
/**
* Method that is called when a message you tried to publish was returned
* by the server. This only happens when the 'mandatory' or 'immediate' flag
* was set with the Channel::publish() call.
* @param channel the channel on which the message was returned
* @param message the returned message
* @param code the reply code
* @param text human readable reply reason
*/
virtual void onReturned(Channel *channel, const Message &message, int16_t code, const std::string &text) {}
/**
* Method that is called when the server starts recovering messages
* This is the result of a call to Channel::recover()
* @param channel the channel on which the recover method was called
*/
virtual void onRecovering(Channel *channel) {}
};
/**
* End of namespace
*/
}

View File

@ -1,3 +1,4 @@
#pragma once
/**
* ChannelImpl.h
*
@ -13,6 +14,11 @@
*/
namespace AMQP {
/**
* Forward declarations
*/
class ConsumedMessage;
/**
* Class definition
*/
@ -32,11 +38,38 @@ private:
ConnectionImpl *_connection;
/**
* The handler that is notified about events
* @var MyChannelHandler
* Callback when the channel is ready
* @var SuccessCallback
*/
ChannelHandler *_handler;
SuccessCallback _readyCallback;
/**
* Callback when the channel errors out
* @var ErrorCallback
*/
ErrorCallback _errorCallback;
/**
* Callbacks for all consumers that are active
* @var std::map<std::string,MessageCallback>
*/
std::map<std::string,MessageCallback> _consumers;
/**
* Pointer to the oldest deferred result (the first one that is going
* to be executed)
*
* @var Deferred
*/
std::unique_ptr<Deferred> _oldestCallback = nullptr;
/**
* Pointer to the newest deferred result (the last one to be added).
*
* @var Deferred
*/
Deferred *_newestCallback = nullptr;
/**
* The channel number
* @var uint16_t
@ -52,31 +85,52 @@ private:
state_closing,
state_closed
} _state = state_connected;
/**
* Is a transaction now active?
* @var bool
* The frames that still need to be send out
*
* We store the data as well as whether they
* should be handled synchronously.
*/
bool _transaction = false;
std::queue<std::pair<bool, OutBuffer>> _queue;
/**
* Are we currently operating in synchronous mode?
*/
bool _synchronous = false;
/**
* The message that is now being received
* @var MessageImpl
* @var ConsumedMessage
*/
MessageImpl *_message = nullptr;
ConsumedMessage *_message = nullptr;
/**
* Construct a channel object
*
*
* Note that the constructor is private, and that the Channel class is
* a friend. By doing this we ensure that nobody can instantiate this
* object, and that it can thus only be used inside the library.
*
*
* @param parent the public channel object
* @param connection pointer to the connection
* @param handler handler that is notified on events
*/
ChannelImpl(Channel *parent, Connection *connection, ChannelHandler *handler = nullptr);
ChannelImpl(Channel *parent, Connection *connection);
/**
* Push a deferred result
* @param result The deferred result
* @return Deferred The object just pushed
*/
Deferred &push(Deferred *deferred);
/**
* Send a framen and push a deferred result
* @param frame The frame to send
* @return Deferred The object just pushed
*/
Deferred &push(const Frame &frame);
public:
/**
@ -95,23 +149,23 @@ public:
/**
* Pause deliveries on a channel
*
*
* This will stop all incoming messages
*
* This method returns true if the request to pause has been sent to the
* broker. This does not necessarily mean that the channel is already
* paused.
*
* @return bool
*
* This function returns a deferred handler. Callbacks can be installed
* using onSuccess(), onError() and onFinalize() methods.
*/
bool pause();
Deferred &pause();
/**
* Resume a paused channel
*
* @return bool
*
* This will resume incoming messages
*
* This function returns a deferred handler. Callbacks can be installed
* using onSuccess(), onError() and onFinalize() methods.
*/
bool resume();
Deferred &resume();
/**
* Is the channel connected?
@ -121,138 +175,177 @@ public:
{
return _state == state_connected;
}
/**
* Start a transaction
* @return bool
*/
bool startTransaction();
Deferred &startTransaction();
/**
* Commit the current transaction
* @return bool
*
* This function returns a deferred handler. Callbacks can be installed
* using onSuccess(), onError() and onFinalize() methods.
*/
bool commitTransaction();
Deferred &commitTransaction();
/**
* Rollback the current transaction
* @return bool
*
* This function returns a deferred handler. Callbacks can be installed
* using onSuccess(), onError() and onFinalize() methods.
*/
bool rollbackTransaction();
Deferred &rollbackTransaction();
/**
* declare an exchange
*
* @param name name of the exchange to declare
* @param type type of exchange
* @param flags additional settings for the exchange
* @param arguments additional arguments
* @return bool
*
* This function returns a deferred handler. Callbacks can be installed
* using onSuccess(), onError() and onFinalize() methods.
*/
bool declareExchange(const std::string &name, ExchangeType type, int flags, const Table &arguments);
Deferred &declareExchange(const std::string &name, ExchangeType type, int flags, const Table &arguments);
/**
* bind two exchanges
* @param source exchange which binds to target
* @param target exchange to bind to
* @param routingKey routing key
* @param glags additional flags
* @param arguments additional arguments for binding
* @return bool
*
* This function returns a deferred handler. Callbacks can be installed
* using onSuccess(), onError() and onFinalize() methods.
*/
bool bindExchange(const std::string &source, const std::string &target, const std::string &routingkey, int flags, const Table &arguments);
Deferred &bindExchange(const std::string &source, const std::string &target, const std::string &routingkey, const Table &arguments);
/**
* unbind two exchanges
* @param source the source exchange
* @param target the target exchange
* @param routingkey the routing key
* @param flags optional flags
* @param arguments additional unbind arguments
* @return bool
*
* This function returns a deferred handler. Callbacks can be installed
* using onSuccess(), onError() and onFinalize() methods.
*/
bool unbindExchange(const std::string &source, const std::string &target, const std::string &routingkey, int flags, const Table &arguments);
Deferred &unbindExchange(const std::string &source, const std::string &target, const std::string &routingkey, const Table &arguments);
/**
* remove an exchange
*
* @param name name of the exchange to remove
* @param flags additional settings for deleting the exchange
* @return bool
*
* This function returns a deferred handler. Callbacks can be installed
* using onSuccess(), onError() and onFinalize() methods.
*/
bool removeExchange(const std::string &name, int flags);
Deferred &removeExchange(const std::string &name, int flags);
/**
* declare a queue
* @param name queue name
* @param flags additional settings for the queue
* @param arguments additional arguments
* @return bool
*
* This function returns a deferred handler. Callbacks can be installed
* using onSuccess(), onError() and onFinalize() methods.
*/
bool declareQueue(const std::string &name, int flags, const Table &arguments);
DeferredQueue &declareQueue(const std::string &name, int flags, const Table &arguments);
/**
* Bind a queue to an exchange
*
* @param exchangeName name of the exchange to bind to
* @param queueName name of the queue
* @param routingkey routingkey
* @param flags additional flags
* @param arguments additional arguments
* @return bool
*
* This function returns a deferred handler. Callbacks can be installed
* using onSuccess(), onError() and onFinalize() methods.
*/
bool bindQueue(const std::string &exchangeName, const std::string &queueName, const std::string &routingkey, int flags, const Table &arguments);
Deferred &bindQueue(const std::string &exchangeName, const std::string &queueName, const std::string &routingkey, const Table &arguments);
/**
* Unbind a queue from an exchange
*
* @param exchange the source exchange
* @param queue the target queue
* @param routingkey the routing key
* @param arguments additional bind arguments
* @return bool
*
* This function returns a deferred handler. Callbacks can be installed
* using onSuccess(), onError() and onFinalize() methods.
*/
bool unbindQueue(const std::string &exchangeName, const std::string &queueName, const std::string &routingkey, const Table &arguments);
Deferred &unbindQueue(const std::string &exchangeName, const std::string &queueName, const std::string &routingkey, const Table &arguments);
/**
* Purge a queue
* @param queue queue to purge
* @param flags additional flags
* @return bool
*
* This function returns a deferred handler. Callbacks can be installed
* using onSuccess(), onError() and onFinalize() methods.
*
* The onSuccess() callback that you can install should have the following signature:
*
* void myCallback(AMQP::Channel *channel, uint32_t messageCount);
*
* For example: channel.declareQueue("myqueue").onSuccess([](AMQP::Channel *channel, uint32_t messageCount) {
*
* std::cout << "Queue purged, all " << messageCount << " messages removed" << std::endl;
*
* });
*/
bool purgeQueue(const std::string &name, int flags);
DeferredDelete &purgeQueue(const std::string &name);
/**
* Remove a queue
* @param queue queue to remove
* @param flags additional flags
* @return bool
*
* This function returns a deferred handler. Callbacks can be installed
* using onSuccess(), onError() and onFinalize() methods.
*
* The onSuccess() callback that you can install should have the following signature:
*
* void myCallback(AMQP::Channel *channel, uint32_t messageCount);
*
* For example: channel.declareQueue("myqueue").onSuccess([](AMQP::Channel *channel, uint32_t messageCount) {
*
* std::cout << "Queue deleted, along with " << messageCount << " messages" << std::endl;
*
* });
*/
bool removeQueue(const std::string &name, int flags);
DeferredDelete &removeQueue(const std::string &name, int flags);
/**
* Publish a message to an exchange
*
* The following flags can be used
*
* - mandatory if set, an unroutable message will be reported to the channel handler with the onReturned method
* - immediate if set, a message that could not immediately be consumed is returned to the onReturned method
*
*
* If the mandatory or immediate flag is set, and the message could not immediately
* be published, the message will be returned to the client, and will eventually
* end up in your ChannelHandler::onReturned() method.
*
* end up in your onReturned() handler method.
*
* @param exchange the exchange to publish to
* @param routingkey the routing key
* @param flags optional flags (see above)
* @param envelope the full envelope to send
* @param message the message to send
* @param size size of the message
*/
bool publish(const std::string &exchange, const std::string &routingKey, int flags, const Envelope &envelope);
bool publish(const std::string &exchange, const std::string &routingKey, const Envelope &envelope);
/**
* Set the Quality of Service (QOS) of the entire connection
* @param prefetchCount maximum number of messages to prefetch
* @return bool whether the Qos frame is sent.
*
* This function returns a deferred handler. Callbacks can be installed
* using onSuccess(), onError() and onFinalize() methods.
*/
bool setQos(uint16_t prefetchCount);
Deferred &setQos(uint16_t prefetchCount);
/**
* Tell the RabbitMQ server that we're ready to consume messages
@ -260,20 +353,43 @@ public:
* @param tag a consumer tag that will be associated with this consume operation
* @param flags additional flags
* @param arguments additional arguments
* @return bool
*
* This function returns a deferred handler. Callbacks can be installed
* using onSuccess(), onError() and onFinalize() methods.
*
* The onSuccess() callback that you can install should have the following signature:
*
* void myCallback(AMQP::Channel *channel, const std::string& tag);
*
* For example: channel.declareQueue("myqueue").onSuccess([](AMQP::Channel *channel, const std::string& tag) {
*
* std::cout << "Started consuming under tag " << tag << std::endl;
*
* });
*/
bool consume(const std::string &queue, const std::string &tag, int flags, const Table &arguments);
DeferredConsumer& consume(const std::string &queue, const std::string &tag, int flags, const Table &arguments);
/**
* Cancel a running consumer
* @param tag the consumer tag
* @param flags optional flags
* @return bool
*
* This function returns a deferred handler. Callbacks can be installed
* using onSuccess(), onError() and onFinalize() methods.
*
* The onSuccess() callback that you can install should have the following signature:
*
* void myCallback(AMQP::Channel *channel, const std::string& tag);
*
* For example: channel.declareQueue("myqueue").onSuccess([](AMQP::Channel *channel, const std::string& tag) {
*
* std::cout << "Started consuming under tag " << tag << std::endl;
*
* });
*/
bool cancel(const std::string &tag, int flags);
DeferredCancel &cancel(const std::string &tag);
/**
* Acknoledge a message
* Acknowledge a message
* @param deliveryTag the delivery tag
* @param flags optional flags
* @return bool
@ -287,19 +403,23 @@ public:
* @return bool
*/
bool reject(uint64_t deliveryTag, int flags);
/**
* Recover messages that were not yet ack'ed
* @param flags optional flags
* @return bool
*
* This function returns a deferred handler. Callbacks can be installed
* using onSuccess(), onError() and onFinalize() methods.
*/
bool recover(int flags);
Deferred &recover(int flags);
/**
* Close the current channel
* @return bool
*
* This function returns a deferred handler. Callbacks can be installed
* using onSuccess(), onError() and onFinalize() methods.
*/
bool close();
Deferred &close();
/**
* Get the channel we're working on
@ -309,204 +429,185 @@ public:
{
return _id;
}
/**
* Send a frame over the channel
* @param frame frame to send
* @return bool was frame succesfully sent?
*/
bool send(const Frame &frame);
/**
* Report to the handler that the channel is closed
* Signal the channel that a synchronous operation
* was completed. After this operation, waiting
* frames can be sent out.
*/
void reportClosed()
{
// change state
_state = state_closed;
// inform handler
if (_handler) _handler->onClosed(_parent);
}
/**
* Report to the handler that the channel is paused
*/
void reportPaused()
{
// inform handler
if (_handler) _handler->onPaused(_parent);
}
/**
* Report to the handler that the channel is resumed
*/
void reportResumed()
{
// inform handler
if (_handler) _handler->onResumed(_parent);
}
void synchronized();
/**
* Report to the handler that the channel is opened
*/
void reportReady()
{
// callbacks could destroy us, so monitor it
Monitor monitor(this);
// inform handler
if (_handler) _handler->onReady(_parent);
if (_readyCallback) _readyCallback();
// if the monitor is still valid, we exit synchronous mode now
if (monitor.valid()) synchronized();
}
/**
* Report an error message on a channel
* @param message
* Report to the handler that the channel is closed
*
* Returns whether the channel object is still valid
*/
void reportError(const std::string &message)
bool reportClosed()
{
// change state
_state = state_closed;
// and pass on to the reportSuccess() method which will call the
// appropriate deferred object to report the successful operation
return reportSuccess();
// technically, we should exit synchronous method now
// since the synchronous channel close frame has been
// acknowledged by the server.
//
// but since the channel was just closed, there is no
// real point in doing this, as we cannot send frames
// out anymore.
}
/**
* Report success
*
* Returns whether the channel object is still valid
*/
template <typename... Arguments>
bool reportSuccess(Arguments ...parameters)
{
// skip if there is no oldest callback
if (!_oldestCallback) return true;
// we are going to call callbacks that could destruct the channel
Monitor monitor(this);
// call the callback
auto *next = _oldestCallback->reportSuccess(std::forward<Arguments>(parameters)...);
// leap out if channel no longer exists
if (!monitor.valid()) return false;
// set the oldest callback
_oldestCallback.reset(next);
// if there was no next callback, the newest callback was just used
if (!next) _newestCallback = nullptr;
// we are still valid
return true;
}
/**
* Report an error message on a channel
* @param message the error message
* @param notifyhandler should the channel-wide handler also be called?
*/
void reportError(const char *message, bool notifyhandler = true)
{
// change state
_state = state_closed;
// we are going to call callbacks that could destruct the channel
Monitor monitor(this);
// call the oldest
if (_oldestCallback)
{
// call the callback
auto *next = _oldestCallback->reportError(message);
// leap out if channel no longer exists
if (!monitor.valid()) return;
// set the oldest callback
_oldestCallback.reset(next);
}
// clean up all deferred other objects
while (_oldestCallback)
{
// call the callback
auto *next = _oldestCallback->reportError("Channel is in error state");
// leap out if channel no longer exists
if (!monitor.valid()) return;
// set the oldest callback
_oldestCallback.reset(next);
}
// all callbacks have been processed, so we also can reset the pointer to the newest
_newestCallback = nullptr;
// inform handler
if (_handler) _handler->onError(_parent, message);
if (notifyhandler && _errorCallback) _errorCallback(message);
}
/**
* Report that the exchange is succesfully declared
* Install a consumer callback
* @param consumertag The consumer tag
* @param callback The callback to be called
*/
void reportExchangeDeclared()
void install(const std::string &consumertag, const MessageCallback &callback)
{
if (_handler) _handler->onExchangeDeclared(_parent);
}
/**
* Report that the exchange is succesfully deleted
*/
void reportExchangeDeleted()
{
if (_handler) _handler->onExchangeDeleted(_parent);
}
/**
* Report that the exchange is bound
*/
void reportExchangeBound()
{
if (_handler) _handler->onExchangeBound(_parent);
}
/**
* Report that the exchange is unbound
*/
void reportExchangeUnbound()
{
if (_handler) _handler->onExchangeUnbound(_parent);
}
/**
* Report that the queue was succesfully declared
* @param queueName name of the queue which was declared
* @param messagecount number of messages currently in the queue
* @param consumerCount number of active consumers in the queue
*/
void reportQueueDeclared(const std::string &queueName, uint32_t messageCount, uint32_t consumerCount)
{
if (_handler) _handler->onQueueDeclared(_parent, queueName, messageCount, consumerCount);
}
/**
* Report that a queue was succesfully bound
*/
void reportQueueBound()
{
if (_handler) _handler->onQueueBound(_parent);
}
/**
* Report that a queue was succesfully unbound
*/
void reportQueueUnbound()
{
if (_handler) _handler->onQueueUnbound(_parent);
}
/**
* Report that a queue was succesfully deleted
* @param messageCount number of messages left in queue, now deleted
*/
void reportQueueDeleted(uint32_t messageCount)
{
if (_handler) _handler->onQueueDeleted(_parent, messageCount);
}
/**
* Report that a queue was succesfully purged
* @param messageCount number of messages purged
*/
void reportQueuePurged(uint32_t messageCount)
{
if (_handler) _handler->onQueuePurged(_parent, messageCount);
}
/**
* Report that the qos has been set
*/
void reportQosSet()
{
if (_handler) _handler->onQosSet(_parent);
}
/**
* Report that a consumer has started
* @param tag the consumer tag
*/
void reportConsumerStarted(const std::string &tag)
{
if (_handler) _handler->onConsumerStarted(_parent, tag);
// install the callback if it is assigned
if (callback) _consumers[consumertag] = callback;
// otherwise we erase the previously set callback
else _consumers.erase(consumertag);
}
/**
* Report that a consumer has stopped
* @param tag the consumer tag
* Uninstall a consumer callback
* @param consumertag The consumer tag
*/
void reportConsumerStopped(const std::string &tag)
void uninstall(const std::string &consumertag)
{
if (_handler) _handler->onConsumerStopped(_parent, tag);
// erase the callback
_consumers.erase(consumertag);
}
/**
* Report that a message was received
*/
void reportMessage();
/**
* Report that the recover operation has started
*/
void reportRecovering()
{
if (_handler) _handler->onRecovering(_parent);
}
/**
* Create an incoming message
* @param frame
* @return MessageImpl
* @return ConsumedMessage
*/
MessageImpl *message(const BasicDeliverFrame &frame);
MessageImpl *message(const BasicReturnFrame &frame);
ConsumedMessage *message(const BasicDeliverFrame &frame);
/**
* Retrieve the current incoming message
* @return MessageImpl
* @return ConsumedMessage
*/
MessageImpl *message()
ConsumedMessage *message()
{
return _message;
}
/**
* The channel class is its friend, thus can it instantiate this object
*/
friend class Channel;
};
/**

View File

@ -1,3 +1,4 @@
#pragma once
/**
* Classes.h
*

View File

@ -1,3 +1,4 @@
#pragma once
/**
* Class describing a mid-level Amqp connection
*
@ -74,7 +75,7 @@ public:
* @param size size of the buffer to decode
* @return number of bytes that were processed
*/
size_t parse(char *buffer, size_t size)
size_t parse(const char *buffer, size_t size)
{
return _implementation.parse(buffer, size);
}

View File

@ -1,9 +1,10 @@
#pragma once
/**
* ConnectionHandler.h
*
* Interface that should be implemented by the caller of the library and
* that is passed to the AMQP connection. This interface contains all sorts
* of methods that are called when data needs to be sent, or when the
* of methods that are called when data needs to be sent, or when the
* AMQP connection ends up in a broken state.
*
* @copyright 2014 Copernica BV
@ -23,7 +24,7 @@ public:
/**
* Method that is called when data needs to be sent over the network
*
* Note that the AMQP library does no buffering by itself. This means
* Note that the AMQP library does no buffering by itself. This means
* that this method should always send out all data or do the buffering
* itself.
*
@ -32,48 +33,49 @@ public:
* @param size Size of the buffer
*/
virtual void onData(Connection *connection, const char *buffer, size_t size) = 0;
/**
* When the connection ends up in an error state this method is called.
* This happens when data comes in that does not match the AMQP protocol
*
*
* After this method is called, the connection no longer is in a valid
* state and can no longer be used.
*
*
* This method has an empty default implementation, although you are very
* much advised to implement it. Because when an error occurs, the connection
* much advised to implement it. When an error occurs, the connection
* is no longer usable, so you probably want to know.
*
*
* @param connection The connection that entered the error state
* @param message Error message
*/
virtual void onError(Connection *connection, const std::string &message) {}
virtual void onError(Connection *connection, const char *message) {}
/**
* Method that is called when the login attempt succeeded. After this method
* was called, the connection is ready to use. This is the first method
* is called, the connection is ready to use. This is the first method
* that is normally called after you've constructed the connection object.
*
*
* According to the AMQP protocol, you must wait for the connection to become
* ready (and this onConnected method to be called) before you can start
* using the Connection object. However, this AMQP library will cache all
* methods that you call before the connection is ready, so in reality there
* is no real reason to wait.
* is no real reason to wait for this method to be called before you send
* the first instructions.
*
* @param connection The connection that can now be used
*/
virtual void onConnected(Connection *connection) {}
/**
* Method that is called when the connection was closed.
*
*
* This is the counter part of a call to Connection::close() and it confirms
* that the connection was correctly closed.
*
*
* @param connection The connection that was closed and that is now unusable
*/
virtual void onClosed(Connection *connection) {}
};
/**

View File

@ -1,10 +1,11 @@
#pragma once
/**
* Connection implementation
*
* This is the implementation of the connection - a class that can only be
* constructed by the connection class itselves and that has all sorts of
* methods that are only useful inside the library
*
*
* @copyright 2014 Copernica BV
*/
@ -43,13 +44,13 @@ protected:
state_closing, // connection is busy closing (we have sent the close frame)
state_closed // connection is closed
} _state = state_protocol;
/**
* Has the close() method been called?
* @var bool
*/
bool _closed = false;
/**
* All channels that are active
* @var map
@ -61,13 +62,13 @@ protected:
* @var uint16_t
*/
uint16_t _nextFreeChannel = 1;
/**
* Max number of channels (0 for unlimited)
* @var uint16_t
*/
uint16_t _maxChannels = 0;
/**
* Max frame size
* @var uint32_t
@ -79,19 +80,19 @@ protected:
* @var Login
*/
Login _login;
/**
* Vhost to connect to
* @var string
*/
std::string _vhost;
/**
* Queued messages that should be sent after the connection has been established
* @var queue
*/
std::queue<OutBuffer> _queue;
/**
* Helper method to send the close frame
* Return value tells if the connection is still valid
@ -99,17 +100,16 @@ protected:
*/
bool sendClose();
private:
/**
* Construct an AMQP object based on full login data
*
*
* The first parameter is a handler object. This handler class is
* an interface that should be implemented by the caller.
*
*
* Note that the constructor is private to ensure that nobody can construct
* this class, only the real Connection class via a friend construct
*
*
* @param parent Parent connection object
* @param handler Connection handler
* @param login Login data
@ -131,7 +131,7 @@ public:
// must be busy doing the connection handshake, or already connected
return _state == state_handshake || _state == state_connected;
}
/**
* Mark the protocol as being ok
*/
@ -140,7 +140,7 @@ public:
// move on to handshake state
if (_state == state_protocol) _state = state_handshake;
}
/**
* Are we fully connected?
* @return bool
@ -150,12 +150,12 @@ public:
// state must be connected
return _state == state_connected;
}
/**
* Mark the connection as connected
*/
void setConnected();
/**
* Retrieve the login data
* @return Login
@ -164,7 +164,7 @@ public:
{
return _login;
}
/**
* Retrieve the vhost
* @return string
@ -184,7 +184,7 @@ public:
_maxChannels = channels;
_maxFrame = size;
}
/**
* The max frame size
* @return uint32_t
@ -193,7 +193,7 @@ public:
{
return _maxFrame;
}
/**
* The max payload size for body frames
* @return uint32_t
@ -203,7 +203,7 @@ public:
// 8 bytes for header and end-of-frame byte
return _maxFrame - 8;
}
/**
* Add a channel to the connection, and return the channel ID that it
* is allowed to use, or 0 when no more ID's are available
@ -211,16 +211,16 @@ public:
* @return uint16_t
*/
uint16_t add(ChannelImpl *channel);
/**
* Remove a channel
* @param channel
*/
void remove(ChannelImpl *channel);
/**
* Parse the buffer into a recognized frame
*
*
* Every time that data comes in on the connection, you should call this method to parse
* the incoming data, and let it handle by the AMQP library. This method returns the number
* of bytes that were processed.
@ -234,7 +234,7 @@ public:
* @param size size of the buffer to decode
* @return number of bytes that were processed
*/
size_t parse(char *buffer, size_t size);
size_t parse(const char *buffer, size_t size);
/**
* Close the connection
@ -245,21 +245,28 @@ public:
/**
* Send a frame over the connection
*
*
* This is an internal method that you normally do not have to call yourself
*
*
* @param frame the frame to send
* @return bool
*/
bool send(const Frame &frame);
/**
* Send buffered data over the connection
*
* @param buffer the buffer with data to send
*/
bool send(OutBuffer &&buffer);
/**
* Get a channel by its identifier
*
*
* This method only works if you had already created the channel before.
* This is an internal method that you will not need if you cache the channel
* object.
*
*
* @param number channel identifier
* @return channel the channel object, or nullptr if not yet created
*/
@ -273,15 +280,28 @@ public:
* Report an error message
* @param message
*/
void reportError(const std::string &message)
void reportError(const char *message)
{
// set connection state to closed
_state = state_closed;
// monitor because every callback could invalidate the connection
Monitor monitor(this);
// all deferred result objects in the channels should report this error too
for (auto &iter : _channels)
{
// report the errors
iter.second->reportError(message, false);
// leap out if no longer valid
if (!monitor.valid()) return;
}
// inform handler
_handler->onError(_parent, message);
}
/**
* Report that the connection is closed
*/
@ -289,7 +309,7 @@ public:
{
// change state
_state = state_closed;
// inform the handler
_handler->onClosed(_parent);
}
@ -298,7 +318,7 @@ public:
* The actual connection is a friend and can construct this class
*/
friend class Connection;
friend class ChannelImpl;
};
/**

View File

@ -1,3 +1,4 @@
#pragma once
/**
* Decimal field type for AMQP
*
@ -82,9 +83,19 @@ public:
* Create a new identical instance of this object
* @return Field*
*/
virtual Field *clone() const override
virtual std::shared_ptr<Field> clone() const override
{
return new DecimalField(_places, _number);
return std::make_shared<DecimalField>(_places, _number);
}
/**
* Output the object to a stream
* @param std::ostream
*/
virtual void output(std::ostream &stream) const override
{
// output floating point value
stream << "decimal(" << _number / pow(10, _places) << ")";
}
/**

252
include/deferred.h Normal file
View File

@ -0,0 +1,252 @@
/**
* Deferred.h
*
* Class describing a set of actions that could
* possibly happen in the future that can be
* caught.
*
* @copyright 2014 Copernica BV
*/
/**
* Set up namespace
*/
namespace AMQP {
// forward declaration
class ChannelImpl;
class Callbacks;
/**
* Class definition
*/
class Deferred
{
protected:
/**
* Callback to execute on success
* @var SuccessCallback
*/
SuccessCallback _successCallback;
/**
* Callback to execute on failure
* @var ErrorCallback
*/
ErrorCallback _errorCallback;
/**
* Callback to execute either way
* @var FinalizeCallback
*/
FinalizeCallback _finalizeCallback;
/**
* Pointer to the next deferred object
* @var Deferred
*/
Deferred *_next = nullptr;
/**
* Do we already know we failed?
* @var bool
*/
bool _failed;
/**
* The next deferred object
* @return Deferred
*/
Deferred *next() const
{
return _next;
}
/**
* Indicate success
* @return Deferred Next deferred result
*/
Deferred *reportSuccess() const
{
// execute callbacks if registered
if (_successCallback) _successCallback();
if (_finalizeCallback) _finalizeCallback();
// return the next deferred result
return _next;
}
/**
* Report success for queue declared messages
* @param name Name of the new queue
* @param messagecount Number of messages in the queue
* @param consumercount Number of consumers linked to the queue
* @return Deferred Next deferred result
*/
virtual Deferred *reportSuccess(const std::string &name, uint32_t messagecount, uint32_t consumercount) const
{
// this is the same as a regular success message
return reportSuccess();
}
/**
* Report success for frames that report delete operations
* @param messagecount Number of messages that were deleted
* @return Deferred
*/
virtual Deferred *reportSuccess(uint32_t messagecount) const
{
// this is the same as a regular success message
return reportSuccess();
}
/**
* Report success for frames that report cancel operations
* @param name Consumer tag that is cancelled
* @return Deferred
*/
virtual Deferred *reportSuccess(const std::string &name) const
{
// this is the same as a regular success message
return reportSuccess();
}
/**
* Indicate failure
* @param error Description of the error that occured
* @return Deferred Next deferred result
*/
Deferred *reportError(const char *error)
{
// from this moment on the object should be listed as failed
_failed = true;
// execute callbacks if registered
if (_errorCallback) _errorCallback(error);
if (_finalizeCallback) _finalizeCallback();
// return the next deferred result
return _next;
}
/**
* Add a pointer to the next deferred result
* @param deferred
*/
void add(Deferred *deferred)
{
// store pointer
_next = deferred;
}
/**
* The channel implementation may call our
* private members and construct us
*/
friend class ChannelImpl;
friend class Callbacks;
protected:
/**
* Protected constructor that can only be called
* from within the channel implementation
*
* @param failed are we already failed?
*/
Deferred(bool failed = false) : _failed(failed) {}
public:
/**
* Deleted copy and move constructors
*/
Deferred(const Deferred &that) = delete;
Deferred(Deferred &&that) = delete;
/**
* Destructor
*/
virtual ~Deferred() {}
/**
* Cast to a boolean
*/
operator bool ()
{
return !_failed;
}
/**
* Register a function to be called
* if and when the operation succesfully
* completes.
*
* Only one callback can be registered at a time.
* Successive calls to this function will clear
* callbacks registered before.
*
* @param callback the callback to execute
*/
Deferred &onSuccess(const SuccessCallback &callback)
{
// store callback
_successCallback = callback;
// allow chaining
return *this;
}
/**
* Register a function to be called
* if and when the operation fails.
*
* Only one callback can be registered at a time.
* Successive calls to this function will clear
* callbacks registered before.
*
* @param callback the callback to execute
*/
Deferred &onError(const ErrorCallback &callback)
{
// store callback
_errorCallback = callback;
// if the object is already in a failed state, we call the callback right away
if (_failed) callback("Frame could not be sent");
// allow chaining
return *this;
}
/**
* Register a function to be called
* if and when the operation completes
* or fails. This function will be called
* either way.
*
* In the case of success, the provided
* error parameter will be an empty string.
*
* Only one callback can be registered at at time.
* Successive calls to this function will clear
* callbacks registered before.
*
* @param callback the callback to execute
*/
Deferred &onFinalize(const FinalizeCallback &callback)
{
// store callback
_finalizeCallback = callback;
// if the object is already in a failed state, we call the callback right away
if (_failed) callback();
// allow chaining
return *this;
}
};
/**
* End namespace
*/
}

94
include/deferredcancel.h Normal file
View File

@ -0,0 +1,94 @@
/**
* DeferredCancel.h
*
* Deferred callback for instructions that cancel a running consumer. This
* deferred object allows one to register a callback that also gets the
* consumer tag as one of its parameters.
*
* @copyright 2014 Copernica BV
*/
/**
* Set up namespace
*/
namespace AMQP {
/**
* We extend from the default deferred and add extra functionality
*/
class DeferredCancel : public Deferred
{
private:
/**
* Pointer to the channel
* @var ChannelImpl
*/
ChannelImpl *_channel;
/**
* Callback to execute when the instruction is completed
* @var CancelCallback
*/
CancelCallback _cancelCallback;
/**
* Report success for frames that report cancel operations
* @param name Consumer tag that is cancelled
* @return Deferred
*/
virtual Deferred *reportSuccess(const std::string &name) const override;
/**
* The channel implementation may call our
* private members and construct us
*/
friend class ChannelImpl;
friend class ConsumedMessage;
protected:
/**
* Protected constructor that can only be called
* from within the channel implementation
*
* @param channel Pointer to the channel
* @param failed Are we already failed?
*/
DeferredCancel(ChannelImpl *channel, bool failed = false) :
Deferred(failed), _channel(channel) {}
public:
/**
* Register a function to be called when the cancel operation succeeded
*
* Only one callback can be registered. Successive calls
* to this function will clear callbacks registered before.
*
* @param callback the callback to execute
*/
DeferredCancel &onSuccess(const CancelCallback &callback)
{
// store callback
_cancelCallback = callback;
// allow chaining
return *this;
}
/**
* Register the function that is called when the cancel operation succeeded
* @param callback
*/
DeferredCancel &onSuccess(const SuccessCallback &callback)
{
// call base
Deferred::onSuccess(callback);
// allow chaining
return *this;
}
};
/**
* End namespace
*/
}

123
include/deferredconsumer.h Normal file
View File

@ -0,0 +1,123 @@
/**
* DeferredConsumer.h
*
* Deferred callback for consumers
*
* @copyright 2014 Copernica BV
*/
/**
* Set up namespace
*/
namespace AMQP {
/**
* We extend from the default deferred and add extra functionality
*/
class DeferredConsumer : public Deferred
{
private:
/**
* The channel to which the consumer is linked
* @var ChannelImpl
*/
ChannelImpl *_channel;
/**
* Callback to execute when a message arrives
* @var ConsumeCallback
*/
ConsumeCallback _consumeCallback;
/**
* Callback for incoming messages
* @var MessageCallback
*/
MessageCallback _messageCallback;
/**
* Report success for frames that report start consumer operations
* @param name Consumer tag that is started
* @return Deferred
*/
virtual Deferred *reportSuccess(const std::string &name) const override;
/**
* The channel implementation may call our
* private members and construct us
*/
friend class ChannelImpl;
friend class ConsumedMessage;
protected:
/**
* Protected constructor that can only be called
* from within the channel implementation
*
* @param channel the channel implementation
* @param failed are we already failed?
*/
DeferredConsumer(ChannelImpl *channel, bool failed = false) :
Deferred(failed), _channel(channel) {}
public:
/**
* Register the function that is called when the consumer starts
* @param callback
*/
DeferredConsumer &onSuccess(const ConsumeCallback &callback)
{
// store the callback
_consumeCallback = callback;
// allow chaining
return *this;
}
/**
* Register the function that is called when the consumer starts
* @param callback
*/
DeferredConsumer &onSuccess(const SuccessCallback &callback)
{
// call base
Deferred::onSuccess(callback);
// allow chaining
return *this;
}
/**
* Register a function to be called when a message arrives
* This fuction is also available as onMessage() because I always forget which name I gave to it
* @param callback the callback to execute
*/
DeferredConsumer &onReceived(const MessageCallback &callback)
{
// store callback
_messageCallback = callback;
// allow chaining
return *this;
}
/**
* Register a function to be called when a message arrives
* This fuction is also available as onMessage() because I always forget which name I gave to it
* @param callback the callback to execute
*/
DeferredConsumer &onMessage(const MessageCallback &callback)
{
// store callback
_messageCallback = callback;
// allow chaining
return *this;
}
};
/**
* End namespace
*/
}

99
include/deferreddelete.h Normal file
View File

@ -0,0 +1,99 @@
/**
* DeferredDelete.h
*
* Deferred callback for instructions that delete or purge queues, and that
* want to report the number of deleted messages.
*
* @copyright 2014 Copernica BV
*/
/**
* Set up namespace
*/
namespace AMQP {
/**
* We extend from the default deferred and add extra functionality
*/
class DeferredDelete : public Deferred
{
private:
/**
* Callback to execute when the instruction is completed
* @var DeleteCallback
*/
DeleteCallback _deleteCallback;
/**
* Report success for queue delete and queue purge messages
* @param messagecount Number of messages that were deleted
* @return Deferred Next deferred result
*/
virtual Deferred *reportSuccess(uint32_t messagecount) const override
{
// skip if no special callback was installed
if (!_deleteCallback) return Deferred::reportSuccess();
// call the callback
_deleteCallback(messagecount);
// call finalize callback
if (_finalizeCallback) _finalizeCallback();
// return next object
return _next;
}
/**
* The channel implementation may call our
* private members and construct us
*/
friend class ChannelImpl;
friend class ConsumedMessage;
protected:
/**
* Protected constructor that can only be called
* from within the channel implementation
*
* @param boolean are we already failed?
*/
DeferredDelete(bool failed = false) : Deferred(failed) {}
public:
/**
* Register a function to be called when the queue is deleted or purged
*
* Only one callback can be registered. Successive calls
* to this function will clear callbacks registered before.
*
* @param callback the callback to execute
*/
DeferredDelete &onSuccess(const DeleteCallback &callback)
{
// store callback
_deleteCallback = callback;
// allow chaining
return *this;
}
/**
* Register the function that is called when the queue is deleted or purged
* @param callback
*/
DeferredDelete &onSuccess(const SuccessCallback &callback)
{
// call base
Deferred::onSuccess(callback);
// allow chaining
return *this;
}
};
/**
* End namespace
*/
}

99
include/deferredqueue.h Normal file
View File

@ -0,0 +1,99 @@
/**
* DeferredQueue.h
*
* Deferred callback for "declare-queue" instructions.
*
* @copyright 2014 Copernica BV
*/
/**
* Set up namespace
*/
namespace AMQP {
/**
* We extend from the default deferred and add extra functionality
*/
class DeferredQueue : public Deferred
{
private:
/**
* Callback to execute when the queue is declared
* @var QueueCallback
*/
QueueCallback _queueCallback;
/**
* Report success for queue declared messages
* @param name Name of the new queue
* @param messagecount Number of messages in the queue
* @param consumercount Number of consumers linked to the queue
* @return Deferred Next deferred result
*/
virtual Deferred *reportSuccess(const std::string &name, uint32_t messagecount, uint32_t consumercount) const override
{
// skip if no special callback was installed
if (!_queueCallback) return Deferred::reportSuccess();
// call the queue callback
_queueCallback(name, messagecount, consumercount);
// call finalize callback
if (_finalizeCallback) _finalizeCallback();
// return next object
return _next;
}
/**
* The channel implementation may call our
* private members and construct us
*/
friend class ChannelImpl;
friend class ConsumedMessage;
protected:
/**
* Protected constructor that can only be called
* from within the channel implementation
*
* @param boolea are we already failed?
*/
DeferredQueue(bool failed = false) : Deferred(failed) {}
public:
/**
* Register a function to be called when the queue is declared
*
* Only one callback can be registered. Successive calls
* to this function will clear callbacks registered before.
*
* @param callback the callback to execute
*/
DeferredQueue &onSuccess(const QueueCallback &callback)
{
// store callback
_queueCallback = callback;
// allow chaining
return *this;
}
/**
* Register the function that is called when the queue is declared
* @param callback
*/
DeferredQueue &onSuccess(const SuccessCallback &callback)
{
// call base
Deferred::onSuccess(callback);
// allow chaining
return *this;
}
};
/**
* End namespace
*/
}

View File

@ -1,3 +1,4 @@
#pragma once
/**
* EntityImpl.h
*

View File

@ -1,3 +1,4 @@
#pragma once
/**
* Envelope.h
*

View File

@ -1,3 +1,4 @@
#pragma once
/**
* ExchangeType.h
*

View File

@ -1,3 +1,4 @@
#pragma once
/**
* Available field types for AMQP
*
@ -36,7 +37,7 @@ public:
* Create a new instance on the heap of this object, identical to the object passed
* @return Field*
*/
virtual Field *clone() const = 0;
virtual std::shared_ptr<Field> clone() const = 0;
/**
* Get the size this field will take when
@ -58,9 +59,44 @@ public:
*/
virtual char typeID() const = 0;
/**
* Output the object to a stream
* @param std::ostream
*/
virtual void output(std::ostream &stream) const = 0;
/**
* Casting operators
* @return mixed
*/
virtual operator const std::string& () const;
virtual operator const char * () const { return nullptr; }
virtual operator uint8_t () const { return 0; }
virtual operator uint16_t () const { return 0; }
virtual operator uint32_t () const { return 0; }
virtual operator uint64_t () const { return 0; }
virtual operator int8_t () const { return 0; }
virtual operator int16_t () const { return 0; }
virtual operator int32_t () const { return 0; }
virtual operator int64_t () const { return 0; }
virtual operator float () const { return 0; }
virtual operator double () const { return 0; }
virtual operator const Array& () const;
virtual operator const Table& () const;
};
/**
* Custom output stream operator
* @param stream
* @param field
* @return ostream
*/
inline std::ostream &operator<<(std::ostream &stream, const Field &field)
{
field.output(stream);
return stream;
}
/**
* end namespace
*/

View File

@ -1,3 +1,4 @@
#pragma once
/**
* Field proxy. Returned by the table. Can be casted to the
* relevant native type (std::string or numeric)
@ -195,199 +196,49 @@ public:
// cast to a string
return operator=(std::string(value));
}
/**
* Assign an array value
* @param value
* @return FieldProxy
*/
FieldProxy &operator=(const Array &value)
{
// assign value and allow chaining
_source->set(_index, value);
return *this;
}
/**
* Get boolean value
* @return BooleanSet
* Assign a table value
* @param value
* @return FieldProxy
*/
operator BooleanSet ()
FieldProxy &operator=(const Table &value)
{
// the value
BooleanSet value;
// retrieve the value
_source->get(_index, value);
// return the result
return value;
// assign value and allow chaining
_source->set(_index, value);
return *this;
}
/**
* Get the underlying field
* @return Field
*/
const Field &get() const
{
return _source->get(_index);
}
/**
* Get a boolean
* @return bool
*/
operator bool ()
template <typename TARGET>
operator TARGET () const
{
// the value
BooleanSet value;
// retrieve the value
_source->get(_index, value);
// return the result
return value.value();
}
/**
* Get numeric value
* @return int8_t
*/
operator int8_t ()
{
// the value
Octet value;
// retrieve the value
_source->get(_index, value);
// return the result
return value.value();
}
/**
* Get numeric value
* @return uint8_t
*/
operator uint8_t ()
{
// the value
UOctet value;
// retrieve the value
_source->get(_index, value);
// return the result
return value.value();
}
/**
* Get numeric value
* @return int16_t
*/
operator int16_t ()
{
// the value
Short value;
// retrieve the value
_source->get(_index, value);
// return the result
return value.value();
}
/**
* Get numeric value
* @return uint16_t
*/
operator uint16_t ()
{
// the value
UShort value;
// retrieve the value
_source->get(_index, value);
// return the result
return value.value();
}
/**
* Get numeric value
* @return int32_t
*/
operator int32_t ()
{
// the value
Long value;
// retrieve the value
_source->get(_index, value);
// return the result
return value.value();
}
/**
* Get numeric value
* @return uint32_t
*/
operator uint32_t ()
{
// the value
ULong value;
// retrieve the value
_source->get(_index, value);
// return the result
return value.value();
}
/**
* Get numeric value
* @return int64_t
*/
operator int64_t ()
{
// the value
Long value;
// retrieve the value
_source->get(_index, value);
// return the result
return value.value();
}
/**
* Get numeric value
* @return uint64_t
*/
operator uint64_t ()
{
// the value
ULong value;
// retrieve the value
_source->get(_index, value);
// return the result
return value.value();
}
/**
* Get decimal value
* @return DecimalField
*/
operator DecimalField ()
{
// the value
DecimalField value;
// retrieve the value
_source->get(_index, value);
// return the result
return value.value();
}
/**
* Get string value
* @return string
*/
operator std::string ()
{
// it has to be either a short or a long string
ShortString shortValue;
LongString longValue;
// try to retrieve the value
if (_source->get(_index, shortValue)) return shortValue.value();
if (_source->get(_index, longValue)) return longValue.value();
// no valid string found
return std::string("");
return _source->get(_index);
}
};
@ -395,6 +246,30 @@ public:
typedef FieldProxy<Table, std::string> AssociativeFieldProxy;
typedef FieldProxy<Array, uint8_t> ArrayFieldProxy;
/**
* Custom output stream operator
* @param stream
* @param field
* @return ostream
*/
inline std::ostream &operator<<(std::ostream &stream, const AssociativeFieldProxy &field)
{
// get underlying field, and output that
return stream << field.get();
}
/**
* Custom output stream operator
* @param stream
* @param field
* @return ostream
*/
inline std::ostream &operator<<(std::ostream &stream, const ArrayFieldProxy &field)
{
// get underlying field, and output that
return stream << field.get();
}
/**
* end namespace
*/

View File

@ -1,3 +1,4 @@
#pragma once
/**
* AmqpFlags.h
*
@ -25,7 +26,6 @@ extern const int global;
extern const int nolocal;
extern const int noack;
extern const int exclusive;
extern const int nowait;
extern const int mandatory;
extern const int immediate;
extern const int redelivered;

View File

@ -1,3 +1,4 @@
#pragma once
/**
* The login information to access a server
*

View File

@ -1,3 +1,4 @@
#pragma once
/**
* Message.h
*

View File

@ -1,3 +1,4 @@
#pragma once
/**
* MetaData.h
*

View File

@ -1,3 +1,4 @@
#pragma once
/**
* Numeric field types for AMQP
*
@ -77,10 +78,10 @@ public:
* Create a new instance of this object
* @return Field*
*/
virtual Field *clone() const override
virtual std::shared_ptr<Field> clone() const override
{
// create a new copy of ourselves and return it
return new NumericField(_value);
return std::make_shared<NumericField>(_value);
}
/**
@ -99,7 +100,7 @@ public:
* Get the value
* @return mixed
*/
operator T () const
virtual operator T () const override
{
return _value;
}
@ -156,6 +157,16 @@ public:
{
return F;
}
/**
* Output the object to a stream
* @param std::ostream
*/
virtual void output(std::ostream &stream) const override
{
// show
stream << "numeric(" << value() << ")";
}
};
/**

View File

@ -1,3 +1,4 @@
#pragma once
/**
* OutBuffer.h
*
@ -23,7 +24,7 @@ private:
* @var char*
*/
char *_buffer;
/**
* Pointer to the buffer to be filled
* @var char*
@ -35,13 +36,13 @@ private:
* @var size_t
*/
size_t _size;
/**
* The total capacity of the out buffer
* @var size_t
*/
size_t _capacity;
public:
/**
@ -55,7 +56,7 @@ public:
_capacity = capacity;
_buffer = _current = new char[capacity];
}
/**
* Copy constructor
* @param that
@ -67,11 +68,11 @@ public:
_capacity = that._capacity;
_buffer = new char[_capacity];
_current = _buffer + _size;
// copy memory
memcpy(_buffer, that._buffer, _size);
}
/**
* Move constructor
* @param that
@ -83,7 +84,7 @@ public:
_capacity = that._capacity;
_buffer = that._buffer;
_current = that._current;
// reset the other object
that._size = 0;
that._capacity = 0;
@ -94,7 +95,7 @@ public:
/**
* Destructor
*/
virtual ~OutBuffer()
virtual ~OutBuffer()
{
if (_buffer) delete[] _buffer;
}
@ -103,7 +104,7 @@ public:
* Get access to the internal buffer
* @return const char*
*/
const char *data()
const char *data() const
{
return _buffer;
}
@ -112,7 +113,7 @@ public:
* Current size of the output buffer
* @return size_t
*/
size_t size()
size_t size() const
{
return _size;
}

View File

@ -1,3 +1,4 @@
#pragma once
/**
* ReceivedFrame.h
*

View File

@ -1,3 +1,4 @@
#pragma once
/**
* String field types for amqp
*
@ -57,10 +58,10 @@ public:
* Create a new instance of this object
* @return Field*
*/
virtual Field *clone() const override
virtual std::shared_ptr<Field> clone() const override
{
// create a new copy of ourselves and return it
return new StringField(_data);
return std::make_shared<StringField>(_data);
}
/**
@ -95,7 +96,7 @@ public:
* Get the value
* @return string
*/
operator const std::string& () const
virtual operator const std::string& () const override
{
return _data;
}
@ -144,6 +145,16 @@ public:
{
return F;
}
/**
* Output the object to a stream
* @param std::ostream
*/
virtual void output(std::ostream &stream) const override
{
// show
stream << "string(" << value() << ")";
}
};
/**

View File

@ -1,3 +1,4 @@
#pragma once
/**
* AMQP field table
*
@ -75,9 +76,9 @@ public:
* Create a new instance on the heap of this object, identical to the object passed
* @return Field*
*/
virtual Field *clone() const override
virtual std::shared_ptr<Field> clone() const override
{
return new Table(*this);
return std::make_shared<Table>(*this);
}
/**
@ -95,7 +96,7 @@ public:
Table set(const std::string& name, const Field &value)
{
// copy to a new pointer and store it
_fields[name] = std::shared_ptr<Field>(value.clone());
_fields[name] = value.clone();
// allow chaining
return *this;
@ -109,7 +110,7 @@ public:
* @param name field name
* @return the field value
*/
const Field &get(const std::string &name);
const Field &get(const std::string &name) const;
/**
* Get a field
@ -121,6 +122,36 @@ public:
return AssociativeFieldProxy(this, name);
}
/**
* Get a field
*
* @param name field name
*/
AssociativeFieldProxy operator[](const char *name)
{
return AssociativeFieldProxy(this, name);
}
/**
* Get a const field
*
* @param name field name
*/
const Field &operator[](const std::string& name) const
{
return get(name);
}
/**
* Get a const field
*
* @param name field name
*/
const Field &operator[](const char *name) const
{
return get(name);
}
/**
* Write encoded payload to the given buffer.
* @param buffer
@ -135,6 +166,46 @@ public:
{
return 'F';
}
/**
* Output the object to a stream
* @param std::ostream
*/
virtual void output(std::ostream &stream) const
{
// prefix
stream << "table(";
// is this the first iteration
bool first = true;
// loop through all members
for (auto &iter : _fields)
{
// split with comma
if (!first) stream << ",";
// show output
stream << iter.first << ":" << *iter.second;
// no longer first iter
first = false;
}
// postfix
stream << ")";
}
/**
* Cast to table
* @return Table
*/
virtual operator const Table& () const override
{
// this already is an array, so no cast is necessary
return *this;
}
};
/**

View File

@ -1,3 +1,4 @@
#pragma once
/**
* Watchable.h
*

View File

@ -1,6 +1,6 @@
CPP = g++
RM = rm -f
CPPFLAGS = -Wall -c -I. -O2 -flto -std=c++11 -g
CPPFLAGS = -Wall -c -I. -g -std=c++11 -g
LD = g++
LD_FLAGS = -Wall -shared -O2
SHARED_LIB = libamqpcpp.so
@ -25,7 +25,7 @@ clean:
${RM} *.obj *~* ${SHARED_OBJECTS} ${STATIC_OBJECTS} ${SHARED_LIB} ${STATIC_LIB}
${SHARED_OBJECTS}:
${CPP} ${CPPFLAGS} -fpic -o $@ ${@:%.o=%.cpp}
${CPP} ${CPPFLAGS} -flto -fpic -o $@ ${@:%.o=%.cpp}
${STATIC_OBJECTS}:
${CPP} ${CPPFLAGS} -o $@ ${@:%.s.o=%.cpp}

View File

@ -1,8 +1,8 @@
/**
* Array.cpp
*
*
* Implementation of an array
*
*
*/
#include "includes.h"
@ -23,7 +23,7 @@ Array::Array(ReceivedFrame &frame)
{
// one byte less for the field type
charsToRead -= 1;
// read the field type and construct the field
Field *field = Field::decode(frame);
if (!field) continue;
@ -58,11 +58,11 @@ Array::Array(const Array &array)
* @param index field index
* @return Field
*/
const Field &Array::get(uint8_t index)
const Field &Array::get(uint8_t index) const
{
// used if index does not exist
static ShortString empty;
// check whether we have that many elements
if (index >= _fields.size()) return empty;
@ -70,9 +70,36 @@ const Field &Array::get(uint8_t index)
return *_fields[index];
}
/**
* Number of entries in the array
* @return uint32_t
*/
uint32_t Array::count() const
{
return _fields.size();
}
/**
* Remove a field from the array
*/
void Array::pop_back()
{
_fields.pop_back();
}
/**
* Add a field to the array
* @param value
*/
void Array::push_back(const Field& value)
{
_fields.push_back(std::shared_ptr<Field>(value.clone()));
}
/**
* Get the size this field will take when
* encoded in the AMQP wire-frame format
* @return size_t
*/
size_t Array::size() const
{
@ -80,11 +107,11 @@ size_t Array::size() const
size_t size = 4;
// iterate over all elements
for (auto iter(_fields.begin()); iter != _fields.end(); ++iter)
for (auto item : _fields)
{
// add the size of the field type and size of element
size += sizeof((*iter)->typeID());
size += (*iter)->size();
size += sizeof(item->typeID());
size += item->size();
}
// return the result
@ -92,16 +119,20 @@ size_t Array::size() const
}
/**
* Write encoded payload to the given buffer.
* Write encoded payload to the given buffer.
* @param buffer
*/
void Array::fill(OutBuffer& buffer) const
{
// store total size for all elements
buffer.add(static_cast<uint32_t>(size()-4));
// iterate over all elements
for (auto iter(_fields.begin()); iter != _fields.end(); ++iter)
for (auto item : _fields)
{
// encode the element type and element
buffer.add((*iter)->typeID());
(*iter)->fill(buffer);
buffer.add((uint8_t)item->typeID());
item->fill(buffer);
}
}

View File

@ -1,6 +1,6 @@
/**
* Class describing a basic acknowledgement frame
*
*
* @copyright 2014 Copernica BV
*/
@ -38,10 +38,10 @@ protected:
{
// call base
BasicFrame::fill(buffer);
// add the delivery tag
buffer.add(_deliveryTag);
// add the booleans
_multiple.fill(buffer);
}
@ -54,25 +54,36 @@ public:
* @param deliveryTag server-assigned and channel specific delivery tag
* @param multiple acknowledge mutiple messages
*/
BasicAckFrame(uint16_t channel, uint64_t deliveryTag, bool multiple = false) :
BasicAckFrame(uint16_t channel, uint64_t deliveryTag, bool multiple = false) :
BasicFrame(channel, 9),
_deliveryTag(deliveryTag),
_multiple(multiple) {}
/**
* Construct based on received frame
* @param frame
*/
BasicAckFrame(ReceivedFrame &frame) :
BasicAckFrame(ReceivedFrame &frame) :
BasicFrame(frame),
_deliveryTag(frame.nextUint64()),
_multiple(frame) {}
/**
* Destructor
*/
virtual ~BasicAckFrame() {}
/**
* Is this a synchronous frame?
*
* After a synchronous frame no more frames may be
* sent until the accompanying -ok frame arrives
*/
virtual bool synchronous() const override
{
return false;
}
/**
* Return the method ID
* @return uint16_t

View File

@ -67,7 +67,19 @@ public:
* Destructor
*/
virtual ~BasicCancelFrame() {}
/**
* Is this a synchronous frame?
*
* After a synchronous frame no more frames may be
* sent until the accompanying -ok frame arrives
*/
bool synchronous() const override
{
// we are synchronous when the nowait option is not used
return !noWait();
}
/**
* Return the consumertag, which is specified by the client or provided by the server
* @return string
@ -90,7 +102,7 @@ public:
* Return whether to wait for a response
* @return boolean
*/
const bool noWait()
const bool noWait() const
{
return _noWait.get(0);
}

View File

@ -1,6 +1,6 @@
/**
* Class describing a basic cancel ok frame
*
*
* @copyright 2014 Copernica BV
*/
@ -42,7 +42,7 @@ public:
*
* @param frame received frame
*/
BasicCancelOKFrame(ReceivedFrame &frame) :
BasicCancelOKFrame(ReceivedFrame &frame) :
BasicFrame(frame),
_consumerTag(frame)
{}
@ -89,13 +89,13 @@ public:
{
// we need the appropriate channel
ChannelImpl *channel = connection->channel(this->channel());
// channel does not exist
if (!channel) return false;
if (!channel) return false;
// report
channel->reportConsumerStopped(consumerTag());
if (channel->reportSuccess<const std::string&>(consumerTag())) channel->synchronized();
// done
return true;
}

View File

@ -111,6 +111,18 @@ public:
*/
virtual ~BasicConsumeFrame() {}
/**
* Is this a synchronous frame?
*
* After a synchronous frame no more frames may be
* sent until the accompanying -ok frame arrives
*/
bool synchronous() const override
{
// we are synchronous when the nowait option is not set
return !noWait();
}
/**
* Return the method ID
* @return uint16_t

View File

@ -1,6 +1,6 @@
/**
* Class describing a basic consume ok frame
*
*
* @copyright 2014 Copernica BV
*/
@ -52,7 +52,7 @@ public:
*
* @param frame received frame
*/
BasicConsumeOKFrame(ReceivedFrame &frame) :
BasicConsumeOKFrame(ReceivedFrame &frame) :
BasicFrame(frame),
_consumerTag(frame)
{}
@ -89,13 +89,13 @@ public:
{
// we need the appropriate channel
ChannelImpl *channel = connection->channel(this->channel());
// channel does not exist
if (!channel) return false;
if (!channel) return false;
// report
channel->reportConsumerStarted(consumerTag());
if (channel->reportSuccess(consumerTag())) channel->synchronized();
// done
return true;
}

View File

@ -120,6 +120,17 @@ public:
return _routingKey;
}
/**
* Is this a synchronous frame?
*
* After a synchronous frame no more frames may be
* sent until the accompanying -ok frame arrives
*/
virtual bool synchronous() const override
{
return false;
}
/**
* Return the method ID
* @return uint16_t

View File

@ -94,6 +94,17 @@ public:
*/
virtual ~BasicPublishFrame() {}
/**
* Is this a synchronous frame?
*
* After a synchronous frame no more frames may be
* sent until the accompanying -ok frame arrives
*/
bool synchronous() const override
{
return false;
}
/**
* Return the name of the exchange to publish to
* @return string

View File

@ -1,6 +1,6 @@
/**
* Class describing a basic QOS frame
*
*
* @copyright 2014 Copernica BV
*/
@ -32,7 +32,7 @@ public:
* @param channel channel we're working on
*/
BasicQosOKFrame(uint16_t channel) : BasicFrame(channel, 0) {}
/**
* Constructor based on incoming data
* @param frame
@ -52,7 +52,7 @@ public:
{
return 11;
}
/**
* Process the frame
* @param connection The connection over which it was received
@ -62,13 +62,13 @@ public:
{
// we need the appropriate channel
ChannelImpl *channel = connection->channel(this->channel());
// channel does not exist
if (!channel) return false;
if (!channel) return false;
// report
channel->reportQosSet();
if (channel->reportSuccess()) channel->synchronized();
// done
return true;
}

View File

@ -62,6 +62,17 @@ public:
*/
virtual ~BasicRecoverAsyncFrame() {}
/**
* Is this a synchronous frame?
*
* After a synchronous frame no more frames may be
* sent until the accompanying -ok frame arrives
*/
virtual bool synchronous() const override
{
return false;
}
/**
* Return the method ID
* @return uint16_t

View File

@ -62,6 +62,17 @@ public:
*/
virtual ~BasicRecoverFrame() {}
/**
* Is this a synchronous frame?
*
* After a synchronous frame no more frames may be
* sent until the accompanying -ok frame arrives
*/
bool synchronous() const override
{
return false;
}
/**
* Return the method ID
* @return uint16_t

View File

@ -1,6 +1,6 @@
/**
* Class describing a basic recover-async frame
*
*
* @copyright 2014 Copernica BV
*/
@ -57,7 +57,7 @@ public:
{
return 111;
}
/**
* Process the frame
* @param connection The connection over which it was received
@ -67,17 +67,17 @@ public:
{
// we need the appropriate channel
ChannelImpl *channel = connection->channel(this->channel());
// channel does not exist
if (!channel) return false;
if (!channel) return false;
// report
channel->reportRecovering();
channel->reportSuccess();
// done
return true;
}
};

View File

@ -73,6 +73,17 @@ public:
*/
virtual ~BasicRejectFrame() {}
/**
* Is this a synchronous frame?
*
* After a synchronous frame no more frames may be
* sent until the accompanying -ok frame arrives
*/
bool synchronous() const override
{
return false;
}
/**
* Return the method ID
* @return uint16_t

View File

@ -92,6 +92,17 @@ public:
*/
virtual ~BasicReturnFrame() {}
/**
* Is this a synchronous frame?
*
* After a synchronous frame no more frames may be
* sent until the accompanying -ok frame arrives
*/
virtual bool synchronous() const override
{
return false;
}
/**
* Return the name of the exchange to publish to
* @return string
@ -144,17 +155,8 @@ public:
*/
virtual bool process(ConnectionImpl *connection) override
{
// we need the appropriate channel
ChannelImpl *channel = connection->channel(this->channel());
// channel does not exist
if (!channel) return false;
// construct the message
channel->message(*this);
// done
return true;
// we no longer support returned messages
return false;
}
};

View File

@ -156,7 +156,7 @@ public:
if (!channel) return false;
// report to the handler
channel->reportError(text());
channel->reportError(_text.value().c_str());
// done
return true;

View File

@ -1,6 +1,6 @@
/**
* Class describing a channel close acknowledgement frame
*
*
* @copyright 2014 Copernica BV
*/
@ -67,13 +67,13 @@ public:
{
// we need the appropriate channel
ChannelImpl *channel = connection->channel(this->channel());
// channel does not exist
if (!channel) return false;
if (!channel) return false;
// report that the channel is closed
channel->reportClosed();
if (channel->reportClosed()) channel->synchronized();
// done
return true;
}

View File

@ -88,14 +88,13 @@ public:
{
// we need the appropriate channel
ChannelImpl *channel = connection->channel(this->channel());
// channel does not exist
if (!channel) return false;
// is the flow active?
if (active()) channel->reportResumed();
else channel->reportPaused();
if (!channel) return false;
// report success for the call
if (channel->reportSuccess()) channel->synchronized();
// done
return true;
}

View File

@ -36,6 +36,7 @@
#include "basicackframe.h"
#include "basicnackframe.h"
#include "basicrecoverframe.h"
#include "basicrejectframe.h"
/**
* Set up namespace
@ -48,28 +49,24 @@ namespace AMQP {
* @param connection
* @param handler
*/
ChannelImpl::ChannelImpl(Channel *parent, Connection *connection, ChannelHandler *handler) :
ChannelImpl::ChannelImpl(Channel *parent, Connection *connection) :
_parent(parent),
_connection(&connection->_implementation),
_handler(handler)
_connection(&connection->_implementation)
{
// add the channel to the connection
_id = _connection->add(this);
// check if the id is valid
if (_id == 0)
{
// this is invalid
_state = state_closed;
// invalid id, this channel can not exist
handler->onError(_parent, "Max number of channels reached");
}
else
{
// busy connecting
_state = state_connected;
// valid id, send a channel open frame
send(ChannelOpenFrame(_id));
}
@ -80,106 +77,143 @@ ChannelImpl::ChannelImpl(Channel *parent, Connection *connection, ChannelHandler
*/
ChannelImpl::~ChannelImpl()
{
// remove incoming message
// remove incoming message
if (_message) delete _message;
_message = nullptr;
// remove this channel from the connection (but not if the connection is already destructed)
if (_connection) _connection->remove(this);
// close the channel now
close();
// destruct deferred results
while (_oldestCallback) _oldestCallback.reset(_oldestCallback->next());
}
/**
* Push a deferred result
* @param result The deferred object to push
*/
Deferred &ChannelImpl::push(Deferred *deferred)
{
// do we already have an oldest?
if (!_oldestCallback) _oldestCallback.reset(deferred);
// do we already have a newest?
if (_newestCallback) _newestCallback->add(deferred);
// store newest callback
_newestCallback = deferred;
// done
return *deferred;
}
/**
* Send a frame and push a deferred result
* @param frame The frame to send
*/
Deferred &ChannelImpl::push(const Frame &frame)
{
// send the frame, and push the result
return push(new Deferred(send(frame)));
}
/**
* Pause deliveries on a channel
*
*
* This will stop all incoming messages
*
* This method returns true if the request to pause has been sent to the
* broker. This does not necessarily mean that the channel is already
* paused.
*
* @return bool
*
* This function returns a deferred handler. Callbacks can be installed
* using onSuccess(), onError() and onFinalize() methods.
*/
bool ChannelImpl::pause()
Deferred &ChannelImpl::pause()
{
// send a flow frame
return send(ChannelFlowFrame(_id, false));
// send a channel flow frame
return push(ChannelFlowFrame(_id, false));
}
/**
* Resume a paused channel
*
* @return bool
*
* This will resume incoming messages
*
* This function returns a deferred handler. Callbacks can be installed
* using onSuccess(), onError() and onFinalize() methods.
*/
bool ChannelImpl::resume()
Deferred &ChannelImpl::resume()
{
// send a flow frame
return send(ChannelFlowFrame(_id, true));
// send a channel flow frame
return push(ChannelFlowFrame(_id, true));
}
/**
* Start a transaction
* @return bool
*
* This function returns a deferred handler. Callbacks can be installed
* using onSuccess(), onError() and onFinalize() methods.
*/
bool ChannelImpl::startTransaction()
Deferred &ChannelImpl::startTransaction()
{
// send a flow frame
return send(TransactionSelectFrame(_id));
}
// send a transaction frame
return push(TransactionSelectFrame(_id));
}
/**
* Commit the current transaction
* @return bool
*
* This function returns a deferred handler. Callbacks can be installed
* using onSuccess(), onError() and onFinalize() methods.
*/
bool ChannelImpl::commitTransaction()
Deferred &ChannelImpl::commitTransaction()
{
// send a flow frame
return send(TransactionCommitFrame(_id));
// send a transaction frame
return push(TransactionCommitFrame(_id));
}
/**
* Rollback the current transaction
* @return bool
*
* This function returns a deferred handler. Callbacks can be installed
* using onSuccess(), onError() and onFinalize() methods.
*/
bool ChannelImpl::rollbackTransaction()
Deferred &ChannelImpl::rollbackTransaction()
{
// send a flow frame
return send(TransactionRollbackFrame(_id));
// send a transaction frame
return push(TransactionRollbackFrame(_id));
}
/**
* Close the current channel
* @return bool
*
* This function returns a deferred handler. Callbacks can be installed
* using onSuccess(), onError() and onFinalize() methods.
*/
bool ChannelImpl::close()
Deferred &ChannelImpl::close()
{
// channel could be dead after send operation, we need to monitor that
Monitor monitor(this);
// send a channel close frame
auto &handler = push(ChannelCloseFrame(_id));
// send a flow frame
if (!send(ChannelCloseFrame(_id))) return false;
// leap out if channel was destructed
if (!monitor.valid()) return true;
// now it is closing
_state = state_closing;
// was the frame sent and are we still alive?
if (handler) _state = state_closing;
// done
return true;
return handler;
}
/**
* declare an exchange
* @param name name of the exchange to declare
* @param type type of exchange
* @param flags additional settings for the exchange
* @param arguments additional arguments
* @return bool
*
* This function returns a deferred handler. Callbacks can be installed
* using onSuccess(), onError() and onFinalize() methods.
*/
bool ChannelImpl::declareExchange(const std::string &name, ExchangeType type, int flags, const Table &arguments)
Deferred &ChannelImpl::declareExchange(const std::string &name, ExchangeType type, int flags, const Table &arguments)
{
// convert exchange type
std::string exchangeType;
@ -189,49 +223,56 @@ bool ChannelImpl::declareExchange(const std::string &name, ExchangeType type, in
if (type == ExchangeType::headers)exchangeType = "headers";
// send declare exchange frame
return send(ExchangeDeclareFrame(_id, name, exchangeType, flags & passive, flags & durable, flags & nowait, arguments));
return push(ExchangeDeclareFrame(_id, name, exchangeType, flags & passive, flags & durable, false, arguments));
}
/**
* bind an exchange
*
* @param source exchange which binds to target
* @param target exchange to bind to
* @param routingKey routing key
* @param flags additional flags
* @param arguments additional arguments for binding
* @return bool
*
* This function returns a deferred handler. Callbacks can be installed
* using onSuccess(), onError() and onFinalize() methods.
*/
bool ChannelImpl::bindExchange(const std::string &source, const std::string &target, const std::string &routingkey, int flags, const Table &arguments)
Deferred &ChannelImpl::bindExchange(const std::string &source, const std::string &target, const std::string &routingkey, const Table &arguments)
{
// send exchange bind frame
return send(ExchangeBindFrame(_id, target, source, routingkey, flags & nowait, arguments));
return push(ExchangeBindFrame(_id, target, source, routingkey, false, arguments));
}
/**
* unbind two exchanges
*
* @param source the source exchange
* @param target the target exchange
* @param routingkey the routing key
* @param flags optional flags
* @param arguments additional unbind arguments
* @return bool
*
* This function returns a deferred handler. Callbacks can be installed
* using onSuccess(), onError() and onFinalize() methods.
*/
bool ChannelImpl::unbindExchange(const std::string &source, const std::string &target, const std::string &routingkey, int flags, const Table &arguments)
Deferred &ChannelImpl::unbindExchange(const std::string &source, const std::string &target, const std::string &routingkey, const Table &arguments)
{
// send exchange unbind frame
return send(ExchangeUnbindFrame(_id, target, source, routingkey, flags & nowait, arguments));
return push(ExchangeUnbindFrame(_id, target, source, routingkey, false, arguments));
}
/**
* remove an exchange
*
* @param name name of the exchange to remove
* @param flags additional settings for deleting the exchange
* @return bool
*
* This function returns a deferred handler. Callbacks can be installed
* using onSuccess(), onError() and onFinalize() methods.
*/
bool ChannelImpl::removeExchange(const std::string &name, int flags)
Deferred &ChannelImpl::removeExchange(const std::string &name, int flags)
{
// send delete exchange frame
return send(ExchangeDeleteFrame(_id, name, flags & ifunused, flags & nowait));
return push(ExchangeDeleteFrame(_id, name, flags & ifunused, false));
}
/**
@ -239,127 +280,178 @@ bool ChannelImpl::removeExchange(const std::string &name, int flags)
* @param name queue name
* @param flags additional settings for the queue
* @param arguments additional arguments
* @return bool
*
* This function returns a deferred handler. Callbacks can be installed
* using onSuccess(), onError() and onFinalize() methods.
*/
bool ChannelImpl::declareQueue(const std::string &name, int flags, const Table &arguments)
DeferredQueue &ChannelImpl::declareQueue(const std::string &name, int flags, const Table &arguments)
{
// the frame to send
QueueDeclareFrame frame(_id, name, flags & passive, flags & durable, flags & exclusive, flags & autodelete, false, arguments);
// send the queuedeclareframe
return send(QueueDeclareFrame(_id, name, flags & passive, flags & durable, flags & exclusive, flags & autodelete, flags & nowait, arguments));
auto *result = new DeferredQueue(send(frame));
// add the deferred result
push(result);
// done
return *result;
}
/**
* Bind a queue to an exchange
*
* @param exchangeName name of the exchange to bind to
* @param queueName name of the queue
* @param routingkey routingkey
* @param flags additional flags
* @param arguments additional arguments
* @return bool
*
* This function returns a deferred handler. Callbacks can be installed
* using onSuccess(), onError() and onFinalize() methods.
*/
bool ChannelImpl::bindQueue(const std::string &exchangeName, const std::string &queueName, const std::string &routingkey, int flags, const Table &arguments)
Deferred &ChannelImpl::bindQueue(const std::string &exchangeName, const std::string &queueName, const std::string &routingkey, const Table &arguments)
{
// send the bind queue frame
return send(QueueBindFrame(_id, queueName, exchangeName, routingkey, flags & nowait, arguments));
return push(QueueBindFrame(_id, queueName, exchangeName, routingkey, false, arguments));
}
/**
* Unbind a queue from an exchange
*
* @param exchange the source exchange
* @param queue the target queue
* @param routingkey the routing key
* @param arguments additional bind arguments
* @return bool
*
* This function returns a deferred handler. Callbacks can be installed
* using onSuccess(), onError() and onFinalize() methods.
*/
bool ChannelImpl::unbindQueue(const std::string &exchange, const std::string &queue, const std::string &routingkey, const Table &arguments)
Deferred &ChannelImpl::unbindQueue(const std::string &exchange, const std::string &queue, const std::string &routingkey, const Table &arguments)
{
// send the unbind queue frame
return send(QueueUnbindFrame(_id, queue, exchange, routingkey, arguments));
return push(QueueUnbindFrame(_id, queue, exchange, routingkey, arguments));
}
/**
* Purge a queue
* @param queue queue to purge
* @param flags additional flags
* @return bool
*
* This function returns a deferred handler. Callbacks can be installed
* using onSuccess(), onError() and onFinalize() methods.
*
* The onSuccess() callback that you can install should have the following signature:
*
* void myCallback(AMQP::Channel *channel, uint32_t messageCount);
*
* For example: channel.declareQueue("myqueue").onSuccess([](AMQP::Channel *channel, uint32_t messageCount) {
*
* std::cout << "Queue purged, all " << messageCount << " messages removed" << std::endl;
*
* });
*/
bool ChannelImpl::purgeQueue(const std::string &name, int flags)
DeferredDelete &ChannelImpl::purgeQueue(const std::string &name)
{
// send the queue purge frame
return send(QueuePurgeFrame(_id, name, flags & nowait));
// the frame to send
QueuePurgeFrame frame(_id, name, false);
// send the frame, and create deferred object
auto *deferred = new DeferredDelete(send(frame));
// push to list
push(deferred);
// done
return *deferred;
}
/**
* Remove a queue
* @param queue queue to remove
* @param flags additional flags
* @return bool
*
* This function returns a deferred handler. Callbacks can be installed
* using onSuccess(), onError() and onFinalize() methods.
*
* The onSuccess() callback that you can install should have the following signature:
*
* void myCallback(AMQP::Channel *channel, uint32_t messageCount);
*
* For example: channel.declareQueue("myqueue").onSuccess([](AMQP::Channel *channel, uint32_t messageCount) {
*
* std::cout << "Queue deleted, along with " << messageCount << " messages" << std::endl;
*
* });
*/
bool ChannelImpl::removeQueue(const std::string &name, int flags)
DeferredDelete &ChannelImpl::removeQueue(const std::string &name, int flags)
{
// send the remove queue frame
return send(QueueDeleteFrame(_id, name, flags & ifunused, flags & ifempty, flags & nowait));
// the frame to send
QueueDeleteFrame frame(_id, name, flags & ifunused, flags & ifempty, false);
// send the frame, and create deferred object
auto *deferred = new DeferredDelete(send(frame));
// push to list
push(deferred);
// done
return *deferred;
}
/**
* Publish a message to an exchange
*
* The following flags can be used
*
* - mandatory if set, an unroutable message will be reported to the channel handler with the onReturned method
* - immediate if set, a message that could not immediately be consumed is returned to the onReturned method
*
*
* @param exchange the exchange to publish to
* @param routingkey the routing key
* @param flags optional flags (see above)
* @param envelope the full envelope to send
* @param message the message to send
* @param size size of the message
*/
bool ChannelImpl::publish(const std::string &exchange, const std::string &routingKey, int flags, const Envelope &envelope)
bool ChannelImpl::publish(const std::string &exchange, const std::string &routingKey, const Envelope &envelope)
{
// we are going to send out multiple frames, each one will trigger a call to the handler,
// which in turn could destruct the channel object, we need to monitor that
Monitor monitor(this);
// @todo do not copy the entire buffer to individual frames
// send the publish frame
if (!send(BasicPublishFrame(_id, exchange, routingKey, flags & mandatory, flags & immediate))) return false;
if (!send(BasicPublishFrame(_id, exchange, routingKey))) return false;
// channel still valid?
if (!monitor.valid()) return false;
// send header
if (!send(BasicHeaderFrame(_id, envelope))) return false;
// channel and connection still valid?
if (!monitor.valid() || !_connection) return false;
// the max payload size is the max frame size minus the bytes for headers and trailer
uint32_t maxpayload = _connection->maxPayload();
uint32_t bytessent = 0;
// the buffer
const char *data = envelope.body();
uint32_t bytesleft = envelope.bodySize();
// split up the body in multiple frames depending on the max frame size
while (bytesleft > 0)
{
// size of this chunk
uint32_t chunksize = std::min(maxpayload, bytesleft);
// send out a body frame
if (!send(BodyFrame(_id, data + bytessent, chunksize))) return false;
// channel still valid?
if (!monitor.valid()) return false;
// update counters
bytessent += chunksize;
bytesleft -= chunksize;
}
// done
return true;
}
@ -367,12 +459,14 @@ bool ChannelImpl::publish(const std::string &exchange, const std::string &routin
/**
* Set the Quality of Service (QOS) for this channel
* @param prefetchCount maximum number of messages to prefetch
* @return bool whether the Qos frame is sent.
*
* This function returns a deferred handler. Callbacks can be installed
* using onSuccess(), onError() and onFinalize() methods.
*/
bool ChannelImpl::setQos(uint16_t prefetchCount)
Deferred &ChannelImpl::setQos(uint16_t prefetchCount)
{
// send a qos frame
return send(BasicQosFrame(_id, prefetchCount, false));
return push(BasicQosFrame(_id, prefetchCount, false));
}
/**
@ -381,27 +475,69 @@ bool ChannelImpl::setQos(uint16_t prefetchCount)
* @param tag a consumer tag that will be associated with this consume operation
* @param flags additional flags
* @param arguments additional arguments
* @return bool
*
* This function returns a deferred handler. Callbacks can be installed
* using onSuccess(), onError() and onFinalize() methods.
*
* The onSuccess() callback that you can install should have the following signature:
*
* void myCallback(AMQP::Channel *channel, const std::string& tag);
*
* For example: channel.declareQueue("myqueue").onSuccess([](AMQP::Channel *channel, const std::string& tag) {
*
* std::cout << "Started consuming under tag " << tag << std::endl;
*
* });
*/
bool ChannelImpl::consume(const std::string &queue, const std::string &tag, int flags, const Table &arguments)
DeferredConsumer& ChannelImpl::consume(const std::string &queue, const std::string &tag, int flags, const Table &arguments)
{
// send a consume frame
return send(BasicConsumeFrame(_id, queue, tag, flags & nolocal, flags & noack, flags & exclusive, flags & nowait, arguments));
// the frame to send
BasicConsumeFrame frame(_id, queue, tag, flags & nolocal, flags & noack, flags & exclusive, false, arguments);
// send the frame, and create deferred object
auto *deferred = new DeferredConsumer(this, send(frame));
// push to list
push(deferred);
// done
return *deferred;
}
/**
* Cancel a running consumer
* @param tag the consumer tag
* @param flags optional flags
*
* This function returns a deferred handler. Callbacks can be installed
* using onSuccess(), onError() and onFinalize() methods.
*
* The onSuccess() callback that you can install should have the following signature:
*
* void myCallback(AMQP::Channel *channel, const std::string& tag);
*
* For example: channel.declareQueue("myqueue").onSuccess([](AMQP::Channel *channel, const std::string& tag) {
*
* std::cout << "Started consuming under tag " << tag << std::endl;
*
* });
*/
bool ChannelImpl::cancel(const std::string &tag, int flags)
DeferredCancel &ChannelImpl::cancel(const std::string &tag)
{
// send a cancel frame
return send(BasicCancelFrame(_id, tag, flags & nowait));
// the cancel frame to send
BasicCancelFrame frame(_id, tag, false);
// send the frame, and create deferred object
auto *deferred = new DeferredCancel(this, send(frame));
// push to list
push(deferred);
// done
return *deferred;
}
/**
* Acknoledge a message
* Acknowledge a message
* @param deliveryTag the delivery tag
* @param flags optional flags
* @return bool
@ -420,19 +556,30 @@ bool ChannelImpl::ack(uint64_t deliveryTag, int flags)
*/
bool ChannelImpl::reject(uint64_t deliveryTag, int flags)
{
// send a nack frame
return send(BasicNackFrame(_id, deliveryTag, flags & multiple, flags & requeue));
// should we reject multiple messages?
if (flags & multiple)
{
// send a nack frame
return send(BasicNackFrame(_id, deliveryTag, true, flags & requeue));
}
else
{
// send a reject frame
return send(BasicRejectFrame(_id, deliveryTag, flags & requeue));
}
}
/**
* Recover un-acked messages
* @param flags optional flags
* @return bool
*
* This function returns a deferred handler. Callbacks can be installed
* using onSuccess(), onError() and onFinalize() methods.
*/
bool ChannelImpl::recover(int flags)
Deferred &ChannelImpl::recover(int flags)
{
// send a nack frame
return send(BasicRecoverFrame(_id, flags & requeue));
return push(BasicRecoverFrame(_id, flags & requeue));
}
/**
@ -444,11 +591,57 @@ bool ChannelImpl::send(const Frame &frame)
{
// skip if channel is not connected
if (_state != state_connected || !_connection) return false;
// are we currently in synchronous mode or are there
// other frames waiting for their turn to be sent?
if (_synchronous || !_queue.empty())
{
// we need to wait until the synchronous frame has
// been processed, so queue the frame until it was
_queue.emplace(frame.synchronous(), frame.buffer());
// it was of course not actually sent but we pretend
// that it was, because no error occured
return true;
}
// enter synchronous mode if necessary
_synchronous = frame.synchronous();
// send to tcp connection
return _connection->send(frame);
}
/**
* Signal the channel that a synchronous operation
* was completed. After this operation, waiting
* frames can be sent out.
*/
void ChannelImpl::synchronized()
{
// we are no longer waiting for synchronous operations
_synchronous = false;
// we need to monitor the channel for validity
Monitor monitor(this);
// send all frames while not in synchronous mode
while (monitor.valid() && !_synchronous && !_queue.empty())
{
// retrieve the first buffer and synchronous
auto pair = std::move(_queue.front());
// remove from the list
_queue.pop();
// mark as synchronous if necessary
_synchronous = pair.first;
// send it over the connection
_connection->send(std::move(pair.second));
}
}
/**
* Report the received message
*/
@ -456,49 +649,41 @@ void ChannelImpl::reportMessage()
{
// skip if there is no message
if (!_message) return;
// look for the consumer
auto iter = _consumers.find(_message->consumer());
if (iter == _consumers.end()) return;
// is this a valid callback method
if (!iter->second) return;
// after the report the channel may be destructed, monitor that
Monitor monitor(this);
// do we have a handler?
if (_handler) _message->report(_parent, _handler);
// call the callback
_message->report(iter->second);
// skip if channel was destructed
if (!monitor.valid()) return;
// no longer need the message
delete _message;
_message = nullptr;
delete _message; _message = nullptr;
}
/**
* Create an incoming message
* @param frame
* @return MessageImpl
* @return ConsumedMessage
*/
MessageImpl *ChannelImpl::message(const BasicDeliverFrame &frame)
ConsumedMessage *ChannelImpl::message(const BasicDeliverFrame &frame)
{
// it should not be possible that a message already exists, but lets check it anyhow
// destruct if message is already set
if (_message) delete _message;
// construct a message
return _message = new ConsumedMessage(frame);
}
/**
* Create an incoming message
* @param frame
* @return MessageImpl
*/
MessageImpl *ChannelImpl::message(const BasicReturnFrame &frame)
{
// it should not be possible that a message already exists, but lets check it anyhow
if (_message) delete _message;
// construct a message
return _message = new ReturnedMessage(frame);
}
/**
* End of namespace
*/

View File

@ -157,7 +157,7 @@ public:
// no need to check for a channel, the error is connection wide
// report the error on the connection
connection->reportError(text());
connection->reportError(text().c_str());
// done
return true;

View File

@ -17,13 +17,13 @@ namespace AMQP {
/**
* Construct an AMQP object based on full login data
*
*
* The first parameter is a handler object. This handler class is
* an interface that should be implemented by the caller.
*
*
* Note that the constructor is private to ensure that nobody can construct
* this class, only the real Connection class via a friend construct
*
*
* @param parent Parent connection object
* @param handler Connection handler
* @param login Login data
@ -42,7 +42,7 @@ ConnectionImpl::~ConnectionImpl()
{
// close the connection in a nice fashion
close();
// invalidate all channels, so they will no longer call methods on this channel object
for (auto iter = _channels.begin(); iter != _channels.end(); iter++) iter->second->invalidate();
}
@ -57,20 +57,20 @@ uint16_t ConnectionImpl::add(ChannelImpl *channel)
{
// check if we have exceeded the limit already
if (_maxChannels > 0 && _channels.size() >= _maxChannels) return 0;
// keep looping to find an id that is not in use
while (true)
{
// is this id in use?
if (_nextFreeChannel > 0 && _channels.find(_nextFreeChannel) == _channels.end()) break;
// id is in use, move on
_nextFreeChannel++;
}
// we have a new channel
_channels[_nextFreeChannel] = channel;
// done
return _nextFreeChannel++;
}
@ -90,7 +90,7 @@ void ConnectionImpl::remove(ChannelImpl *channel)
/**
* Parse the buffer into a recognized frame
*
*
* Every time that data comes in on the connection, you should call this method to parse
* the incoming data, and let it handle by the AMQP library. This method returns the number
* of bytes that were processed.
@ -104,17 +104,17 @@ void ConnectionImpl::remove(ChannelImpl *channel)
* @param size size of the buffer to decode
* @return number of bytes that were processed
*/
size_t ConnectionImpl::parse(char *buffer, size_t size)
size_t ConnectionImpl::parse(const char *buffer, size_t size)
{
// do not parse if already in an error state
if (_state == state_closed) return 0;
// number of bytes processed
size_t processed = 0;
// create a monitor object that checks if the connection still exists
Monitor monitor(this);
// keep looping until we have processed all bytes, and the monitor still
// indicates that the connection is in a valid state
while (size > 0 && monitor.valid())
@ -131,7 +131,7 @@ size_t ConnectionImpl::parse(char *buffer, size_t size)
// number of bytes processed
size_t bytes = receivedFrame.totalSize();
// add bytes
processed += bytes; size -= bytes; buffer += bytes;
}
@ -139,12 +139,12 @@ size_t ConnectionImpl::parse(char *buffer, size_t size)
{
// something terrible happened on the protocol (like data out of range)
reportError(exception.what());
// done
return processed;
}
}
// done
return processed;
}
@ -161,13 +161,13 @@ bool ConnectionImpl::close()
// mark that the object is closed
_closed = true;
// if still busy with handshake, we delay closing for a while
if (_state == state_handshake || _state == state_protocol) return true;
// perform the close operation
sendClose();
// done
return true;
}
@ -181,26 +181,26 @@ bool ConnectionImpl::sendClose()
{
// after the send operation the object could be dead
Monitor monitor(this);
// loop over all channels
for (auto iter = _channels.begin(); iter != _channels.end(); iter++)
{
// close the channel
iter->second->close();
// we could be dead now
if (!monitor.valid()) return false;
}
// send the close frame
send(ConnectionCloseFrame(0, "shutdown"));
// leap out if object no longer is alive
if (!monitor.valid()) return false;
// we're in a new state
_state = state_closing;
// done
return true;
}
@ -213,35 +213,29 @@ void ConnectionImpl::setConnected()
// store connected state
_state = state_connected;
// if the close operation was already called, we do that again now again
// so that the actual messages to close down the connection and the channel
// are appended to the queue
// if the close method was called before, the frame was not
// sent. append it to the end of the queue to make sure we
// are correctly closed down.
if (_closed && !sendClose()) return;
// we're going to call the handler, which can destruct the connection,
// so we must monitor if the queue object is still valid after calling
Monitor monitor(this);
// inform handler
_handler->onConnected(_parent);
// leap out if the connection no longer exists
if (!monitor.valid()) return;
// empty the queue of messages
while (!_queue.empty())
while (monitor.valid() && !_queue.empty())
{
// get the next message
OutBuffer buffer(std::move(_queue.front()));
// remove it from the queue
_queue.pop();
// send it
_handler->onData(_parent, buffer.data(), buffer.size());
// leap out if the connection was destructed
if (!monitor.valid()) return;
}
}
@ -254,18 +248,12 @@ bool ConnectionImpl::send(const Frame &frame)
{
// its not possible to send anything if closed or closing down
if (_state == state_closing || _state == state_closed) return false;
// we need an output buffer
OutBuffer buffer(frame.totalSize());
// fill the buffer
frame.fill(buffer);
// append an end of frame byte (but not when still negotiating the protocol)
if (frame.needsSeparator()) buffer.add((uint8_t)206);
OutBuffer buffer(frame.buffer());
// are we still setting up the connection?
if ((_state == state_connected && _queue.size() == 0) || frame.partOfHandshake())
if ((_state == state_connected && _queue.empty()) || frame.partOfHandshake())
{
// send the buffer
_handler->onData(_parent, buffer.data(), buffer.size());
@ -275,7 +263,33 @@ bool ConnectionImpl::send(const Frame &frame)
// the connection is still being set up, so we need to delay the message sending
_queue.push(std::move(buffer));
}
// done
return true;
}
/**
* Send buffered data over the connection
*
* @param buffer the buffer with data to send
*/
bool ConnectionImpl::send(OutBuffer &&buffer)
{
// this only works when we are already connected
if (_state != state_connected) return false;
// are we waiting for other frames to be sent before us?
if (_queue.empty())
{
// send it directly
_handler->onData(_parent, buffer.data(), buffer.size());
}
else
{
// add to the list of waiting buffers
_queue.push(std::move(buffer));
}
// done
return true;
}

View File

@ -26,7 +26,7 @@ private:
* @var uint64_t
*/
uint64_t _deliveryTag;
/**
* Is this a redelivered message?
* @var bool
@ -39,25 +39,33 @@ public:
* Constructor
* @param frame
*/
ConsumedMessage(const BasicDeliverFrame &frame) :
MessageImpl(frame.exchange(), frame.routingKey()),
ConsumedMessage(const BasicDeliverFrame &frame) :
MessageImpl(frame.exchange(), frame.routingKey()),
_consumerTag(frame.consumerTag()), _deliveryTag(frame.deliveryTag()), _redelivered(frame.redelivered())
{}
/**
* Destructor
*/
virtual ~ConsumedMessage() {}
/**
* Retrieve the consumer tag
* @return std::string
*/
const std::string &consumer() const
{
return _consumerTag;
}
/**
* Report to the handler
* @param channel
* @param handler
* @param callback
*/
virtual void report(Channel *channel, ChannelHandler *handler) override
void report(const MessageCallback &callback) const
{
// report to the handler
handler->onReceived(channel, *this, _deliveryTag, _consumerTag, _redelivered);
// send ourselves to the consumer
if (callback) callback(*this, _deliveryTag, _redelivered);
}
};

42
src/deferredcancel.cpp Normal file
View File

@ -0,0 +1,42 @@
/**
* DeferredCancel.cpp
*
* Implementation file for the DeferredCancel class
*
* @copyright 2014 Copernica BV
*/
#include "includes.h"
/**
* Namespace
*/
namespace AMQP {
/**
* Report success for frames that report cancel operations
* @param name Consumer tag that is cancelled
* @return Deferred
*/
Deferred *DeferredCancel::reportSuccess(const std::string &name) const
{
// in the channel, we should uninstall the consumer
_channel->uninstall(name);
// skip if no special callback was installed
if (!_cancelCallback) return Deferred::reportSuccess();
// call the callback
_cancelCallback(name);
// call finalize callback
if (_finalizeCallback) _finalizeCallback();
// return next object
return _next;
}
/**
* End namespace
*/
}

41
src/deferredconsumer.cpp Normal file
View File

@ -0,0 +1,41 @@
/**
* DeferredConsumer.cpp
*
* Implementation file for the DeferredConsumer class
*
* @copyright 2014 Copernica BV
*/
#include "includes.h"
/**
* Namespace
*/
namespace AMQP {
/**
* Report success for frames that report start consumer operations
* @param name Consumer tag that is started
* @return Deferred
*/
Deferred *DeferredConsumer::reportSuccess(const std::string &name) const
{
// we now know the name, so we can install the message callback on the channel
_channel->install(name, _messageCallback);
// skip if no special callback was installed
if (!_consumeCallback) return Deferred::reportSuccess();
// call the callback
_consumeCallback(name);
// call finalize callback
if (_finalizeCallback) _finalizeCallback();
// return next object
return _next;
}
/**
* End namespace
*/
}

View File

@ -22,12 +22,6 @@ protected:
* @param what
*/
explicit Exception(const std::string &what) : runtime_error(what) {}
public:
/**
* Destructor
*/
virtual ~Exception() {}
};
/**

View File

@ -1,6 +1,6 @@
/**
* Exchangebindframe.h
*
*
* @copyright 2014 Copernica BV
*/
@ -26,25 +26,25 @@ private:
* @var ShortString
*/
ShortString _destination;
/**
* Exchange which is bound
* Exchange which is bound
* @var ShortString
*/
ShortString _source;
/**
* Routing key
* @var ShortString
*/
ShortString _routingKey;
/**
* contains: nowait do not wait on response
* @var booleanset
*/
BooleanSet _bools;
/**
* Additional arguments
* @var Table
@ -61,7 +61,7 @@ protected:
{
// call base
ExchangeFrame::fill(buffer);
buffer.add(_reserved);
_destination.fill(buffer);
_source.fill(buffer);
@ -70,13 +70,13 @@ protected:
_arguments.fill(buffer);
}
public:
public:
/**
* Constructor based on incoming data
*
*
* @param frame received frame to decode
*/
ExchangeBindFrame(ReceivedFrame &frame) :
ExchangeBindFrame(ReceivedFrame &frame) :
ExchangeFrame(frame),
_reserved(frame.nextUint16()),
_destination(frame),
@ -102,8 +102,19 @@ public:
_bools(noWait),
_arguments(arguments)
{}
/**
* Is this a synchronous frame?
*
* After a synchronous frame no more frames may be
* sent until the accompanying -ok frame arrives
*/
bool synchronous() const override
{
// we are synchronous when the nowait option has not been set
return !noWait();
}
/**
* Get the destination exchange
* @return string
@ -112,7 +123,7 @@ public:
{
return _destination;
}
/**
* Get the source exchange
* @return string
@ -121,7 +132,7 @@ public:
{
return _source;
}
/**
* Get the routing key
* @return string
@ -130,7 +141,7 @@ public:
{
return _routingKey;
}
/**
* Get the method id
* @return uint16_t
@ -139,7 +150,7 @@ public:
{
return 30;
}
/**
* Get the additional arguments
* @return Table
@ -148,12 +159,12 @@ public:
{
return _arguments;
}
/**
* Get the nowait bool
* @return bool
*/
bool noWait()
bool noWait() const
{
return _bools.get(0);
}

View File

@ -1,6 +1,6 @@
/**
* Exchangebindokframe.h
*
*
* @copyright 2014 Copernica BV
*/
@ -8,7 +8,7 @@
* Set up namespace
*/
namespace AMQP {
/**
* Class definition
*/
@ -29,10 +29,10 @@ protected:
public:
/**
* Constructor based on incoming data
*
*
* @param frame received frame to decode
*/
ExchangeBindOKFrame(ReceivedFrame &frame) :
ExchangeBindOKFrame(ReceivedFrame &frame) :
ExchangeFrame(frame)
{}
@ -47,12 +47,12 @@ public:
ExchangeBindOKFrame(uint16_t channel) :
ExchangeFrame(channel, 0)
{}
virtual uint16_t methodID() const override
{
return 31;
}
/**
* Process the frame
* @param connection The connection over which it was received
@ -62,17 +62,17 @@ public:
{
// check if we have a channel
ChannelImpl *channel = connection->channel(this->channel());
// channel does not exist
if(!channel) return false;
// report to handler
channel->reportExchangeBound();
if (channel->reportSuccess()) channel->synchronized();
// done
return true;
}
};
// end namespace
}

View File

@ -106,6 +106,18 @@ public:
*/
virtual ~ExchangeDeclareFrame() {}
/**
* Is this a synchronous frame?
*
* After a synchronous frame no more frames may be
* sent until the accompanying -ok frame arrives
*/
bool synchronous() const override
{
// we are synchronous without the nowait option
return !noWait();
}
/**
* Method id
* @return uint16_t

View File

@ -1,6 +1,6 @@
/**
* Class describing an AMQP exchange declare ok frame
*
*
* @copyright 2014 Copernica BV
*/
@ -55,7 +55,7 @@ public:
{
return 11;
}
/**
* Process the frame
* @param connection The connection over which it was received
@ -65,13 +65,13 @@ public:
{
// we need the appropriate channel
ChannelImpl *channel = connection->channel(this->channel());
// channel does not exist
if(!channel) return false;
// report exchange declare ok
channel->reportExchangeDeclared();
if (channel->reportSuccess()) channel->synchronized();
// done
return true;
}

View File

@ -83,6 +83,18 @@ public:
*/
virtual ~ExchangeDeleteFrame() {}
/**
* Is this a synchronous frame?
*
* After a synchronous frame no more frames may be
* sent until the accompanying -ok frame arrives
*/
bool synchronous() const override
{
// we are synchronous without the nowait option
return !noWait();
}
/**
* returns the method id
* @return uint16_t

View File

@ -1,6 +1,6 @@
/**
* Class describing an AMQP exchange delete ok frame
*
*
* @copyright 2014 Copernica BV
*/
@ -32,7 +32,7 @@ public:
*
* @param frame received frame
*/
ExchangeDeleteOKFrame(ReceivedFrame &frame) :
ExchangeDeleteOKFrame(ReceivedFrame &frame) :
ExchangeFrame(frame)
{}
@ -56,7 +56,7 @@ public:
{
return 21;
}
/**
* Process the frame
* @param connection The connection over which it was received
@ -66,13 +66,13 @@ public:
{
// check if we have a channel
ChannelImpl *channel = connection->channel(this->channel());
// channel does not exist
if(!channel) return false;
// report to handler
channel->reportExchangeDeleted();
if (channel->reportSuccess()) channel->synchronized();
// done
return true;
}

View File

@ -8,7 +8,7 @@
* Set up namespace
*/
namespace AMQP {
/**
* Class definition
*/
@ -26,31 +26,31 @@ private:
* @var ShortString
*/
ShortString _destination;
/**
* Exchange which is bound
* Exchange which is bound
* @var ShortString
*/
ShortString _source;
/**
* Routing key
* @var ShortString
*/
ShortString _routingKey;
/**
* contains: nowait do not wait on response
* @var booleanset
*/
BooleanSet _bools;
/**
* Additional arguments
* @var Table
*/
Table _arguments;
protected:
/**
* Encode a frame on a string buffer
@ -61,7 +61,7 @@ protected:
{
// call base
ExchangeFrame::fill(buffer);
buffer.add(_reserved);
_destination.fill(buffer);
_source.fill(buffer);
@ -73,10 +73,10 @@ protected:
public:
/**
* Constructor based on incoming data
*
*
* @param frame received frame to decode
*/
ExchangeUnbindFrame(ReceivedFrame &frame) :
ExchangeUnbindFrame(ReceivedFrame &frame) :
ExchangeFrame(frame),
_reserved(frame.nextUint16()),
_destination(frame),
@ -102,8 +102,19 @@ public:
_bools(noWait),
_arguments(arguments)
{}
/**
* Is this a synchronous frame?
*
* After a synchronous frame no more frames may be
* sent until the accompanying -ok frame arrives
*/
bool synchronous() const override
{
// we are synchronous without the nowait option
return !noWait();
}
/**
* Get the destination exchange
* @return string
@ -112,7 +123,7 @@ public:
{
return _destination;
}
/**
* Get the source exchange
* @return string
@ -121,7 +132,7 @@ public:
{
return _source;
}
/**
* Get the routing key
* @return string
@ -130,7 +141,7 @@ public:
{
return _routingKey;
}
/**
* Get the method id
* @return uint16_t
@ -139,7 +150,7 @@ public:
{
return 40;
}
/**
* Get the additional arguments
* @return Table
@ -148,16 +159,16 @@ public:
{
return _arguments;
}
/**
* Get the nowait bool
* @return bool
*/
bool noWait()
bool noWait() const
{
return _bools.get(0);
}
};
// leave namespace
}

View File

@ -1,5 +1,5 @@
/**
* Exchangeunbindokframe.h
* Exchangeunbindokframe.h
*
* @copyright 2014 Copernica BV
*/
@ -9,7 +9,7 @@
* Set up namespace
*/
namespace AMQP {
/**
* Class definition
*/
@ -30,10 +30,10 @@ protected:
public:
/**
* Constructor based on incoming data
*
*
* @param frame received frame to decode
*/
ExchangeUnbindOKFrame(ReceivedFrame &frame) :
ExchangeUnbindOKFrame(ReceivedFrame &frame) :
ExchangeFrame(frame)
{}
@ -48,12 +48,12 @@ public:
ExchangeUnbindOKFrame(uint16_t channel) :
ExchangeFrame(channel, 0)
{}
virtual uint16_t methodID() const override
{
return 51;
}
/**
* Process the frame
* @param connection The connection over which it was received
@ -63,17 +63,17 @@ public:
{
// check if we have a channel
ChannelImpl *channel = connection->channel(this->channel());
// channel does not exist
if(!channel) return false;
// report to handler
channel->reportExchangeUnbound();
if (channel->reportSuccess()) channel->synchronized();
// done
return true;
}
};
// end namespace
}

View File

@ -45,6 +45,45 @@ Field *Field::decode(ReceivedFrame &frame)
}
}
/**
* Cast to string
* @return std::string
*/
Field::operator const std::string& () const
{
// static empty string
static std::string empty;
// return it
return empty;
}
/**
* Cast to array
* @return Array
*/
Field::operator const Array& () const
{
// static empty array
static Array empty;
// return it
return empty;
}
/**
* Cast to object
* @return Array
*/
Field::operator const Table& () const
{
// static empty table
static Table empty;
// return it
return empty;
}
/**
* End of namespace
*/

View File

@ -1,9 +1,9 @@
/**
* Frame.h
*
*
* Base class for frames. This base class can not be constructed from outside
* the library, and is only used internally.
*
*
* @copyright 2014 Copernica BV
*/
@ -11,7 +11,7 @@
* Set up namespace
*/
namespace AMQP {
/**
* Class definition
*/
@ -19,11 +19,11 @@ class Frame
{
protected:
/**
* Protected constructor to ensure that no objects are created from
* Protected constructor to ensure that no objects are created from
* outside the library
*/
Frame() {}
public:
/**
* Destructor
@ -35,40 +35,67 @@ public:
* @return uint32_t
*/
virtual uint32_t totalSize() const = 0;
/**
* Fill an output buffer
* @param buffer
*/
virtual void fill(OutBuffer &buffer) const = 0;
/**
* Is this a frame that is part of the connection setup?
* @return bool
*/
virtual bool partOfHandshake() const { return false; }
/**
* Does this frame need an end-of-frame seperator?
* @return bool
*/
virtual bool needsSeparator() const { return true; }
/**
* Is this a synchronous frame?
*
* After a synchronous frame no more frames may be
* sent until the accompanying -ok frame arrives
*/
virtual bool synchronous() const { return false; }
/**
* Retrieve the buffer in AMQP wire-format for
* sending over the socket connection
*/
OutBuffer buffer() const
{
// we need an output buffer
OutBuffer buffer(totalSize());
// fill the buffer
fill(buffer);
// append an end of frame byte (but not when still negotiating the protocol)
if (needsSeparator()) buffer.add((uint8_t)206);
// return the created buffer
return buffer;
}
/**
* Process the frame
* @param connection The connection over which it was received
* @return bool Was it succesfully processed?
*/
virtual bool process(ConnectionImpl *connection)
virtual bool process(ConnectionImpl *connection)
{
// this is an exception
throw ProtocolException("unimplemented frame");
// unreachable
return false;
}
};
/**
* End of namespace
*/

View File

@ -11,7 +11,6 @@
#include "../amqpcpp.h"
// classes that are very commonly used
#include "monitor.h"
#include "exception.h"
#include "protocolexception.h"
#include "frame.h"

View File

@ -20,10 +20,10 @@ class MessageImpl : public Message
private:
/**
* How many bytes have been received?
* @var uint64_t
* @var uint64_t
*/
uint64_t _received;
/**
* Was the buffer allocated by us?
* @var bool
@ -36,8 +36,8 @@ protected:
* @param exchange
* @param routingKey
*/
MessageImpl(const std::string &exchange, const std::string &routingKey) :
Message(exchange, routingKey),
MessageImpl(const std::string &exchange, const std::string &routingKey) :
Message(exchange, routingKey),
_received(0), _selfAllocated(false)
{}
@ -45,12 +45,12 @@ public:
/**
* Destructor
*/
virtual ~MessageImpl()
virtual ~MessageImpl()
{
// clear up memory if it was self allocated
if (_selfAllocated) delete[] _body;
}
/**
* Set the body size
* This field is set when the header is received
@ -60,7 +60,7 @@ public:
{
_bodySize = size;
}
/**
* Append data
* @param buffer incoming data
@ -84,27 +84,20 @@ public:
// it does not yet fit, do we have to allocate?
if (!_body) _body = new char[_bodySize];
_selfAllocated = true;
// prevent that size is too big
if (size > _bodySize - _received) size = _bodySize - _received;
// append data
memcpy((char *)(_body + _received), buffer, size);
// we have more data now
_received += size;
// done
return _received >= _bodySize;
}
}
/**
* Report to the handler
* @param channel
* @param handler
*/
virtual void report(Channel *channel, ChannelHandler *handler) = 0;
};
/**

View File

@ -49,6 +49,14 @@ public:
*/
virtual ~MethodFrame() {}
/**
* Is this a synchronous frame?
*
* After a synchronous frame no more frames may be
* sent until the accompanying -ok frame arrives
*/
bool synchronous() const override { return true; }
/**
* Get the message type
* @return uint8_t
@ -79,9 +87,6 @@ public:
{
// this is an exception
throw ProtocolException("unimplemented frame type " + std::to_string(type()) + " class " + std::to_string(classID()) + " method " + std::to_string(methodID()));
// unreachable
return false;
}
};

View File

@ -23,11 +23,6 @@ public:
* @param what
*/
explicit ProtocolException(const std::string &what) : Exception(what) {}
/**
* Destructor
*/
virtual ~ProtocolException() {}
};
/**

View File

@ -110,6 +110,18 @@ public:
_arguments(frame)
{}
/**
* Is this a synchronous frame?
*
* After a synchronous frame no more frames may be
* sent until the accompanying -ok frame arrives
*/
bool synchronous() const override
{
// we are synchronous without the nowait option
return !noWait();
}
/**
* Returns the method id
* @return uint16_t

View File

@ -1,6 +1,6 @@
/**
* Class describing an AMQP queue bind ok frame
*
*
* @copyright 2014 Copernica BV
*/
@ -54,7 +54,7 @@ public:
{
return 21;
}
/**
* Process the frame
* @param connection The connection over which it was received
@ -64,13 +64,13 @@ public:
{
// check if we have a channel
ChannelImpl *channel = connection->channel(this->channel());
// channel does not exist
if(!channel) return false;
// report to handler
channel->reportQueueBound();
if (channel->reportSuccess()) channel->synchronized();
// done
return true;
}

View File

@ -98,6 +98,18 @@ public:
_arguments(frame)
{}
/**
* Is this a synchronous frame?
*
* After a synchronous frame no more frames may be
* sent until the accompanying -ok frame arrives
*/
bool synchronous() const override
{
// we are synchronous without the nowait option
return !noWait();
}
/**
* returns the method id
* @return string
@ -156,7 +168,7 @@ public:
* returns whether to wait for a response
* @return bool
*/
bool noWait()
bool noWait() const
{
return _bools.get(4);
}

View File

@ -1,6 +1,6 @@
/**
* Class describing an AMQP queue declare ok frame
*
*
* @copyright 2014 Copernica BV
*/
@ -70,7 +70,7 @@ public:
/**
* Constructor based on incoming data
* @param frame received frame
* @param frame received frame
*/
QueueDeclareOKFrame(ReceivedFrame &frame) :
QueueFrame(frame),
@ -91,7 +91,7 @@ public:
{
return 11;
}
/**
* Queue name
* @return string
@ -105,7 +105,7 @@ public:
* Number of messages
* @return int32_t
*/
int32_t messageCount() const
uint32_t messageCount() const
{
return _messageCount;
}
@ -114,11 +114,11 @@ public:
* Number of consumers
* @return int32_t
*/
int32_t consumerCount() const
uint32_t consumerCount() const
{
return _consumerCount;
}
/**
* Process the frame
* @param connection The connection over which it was received
@ -128,13 +128,13 @@ public:
{
// check if we have a channel
ChannelImpl *channel = connection->channel(this->channel());
// what if channel doesn't exist?
if (!channel) return false;
// report to the handler
channel->reportQueueDeclared(this->name(), this->messageCount(), this->consumerCount());
// report success
if (channel->reportSuccess(name(), messageCount(), consumerCount())) channel->synchronized();
// done
return true;
}

View File

@ -85,6 +85,18 @@ public:
_bools(frame)
{}
/**
* Is this a synchronous frame?
*
* After a synchronous frame no more frames may be
* sent until the accompanying -ok frame arrives
*/
bool synchronous() const override
{
// we are synchronous without the nowait option
return !noWait();
}
/**
* returns the method id
* @returns uint16_t

View File

@ -1,6 +1,6 @@
/**
* Class describing an AMQP queue delete frame
*
*
* @copyright 2014 Copernica BV
*/
@ -79,7 +79,7 @@ public:
{
return _messageCount;
}
/**
* Process the frame
* @param connection The connection over which it was received
@ -89,13 +89,13 @@ public:
{
// check if we have a channel
ChannelImpl *channel = connection->channel(this->channel());
// channel does not exist
if(!channel) return false;
// report queue deletion success
channel->reportQueueDeleted(this->messageCount());
if (channel->reportSuccess(this->messageCount())) channel->synchronized();
// done
return true;
}
@ -105,4 +105,4 @@ public:
* end namespace
*/
}

View File

@ -1,6 +1,6 @@
/**
* Class describing an AMQP queue purge frame
*
*
* @copyright 2014 Copernica BV
*/
@ -27,7 +27,7 @@ private:
ShortString _name;
/**
* Do not wait on response
* Do not wait on response
* @var BooleanSet
*/
BooleanSet _noWait;
@ -37,7 +37,7 @@ protected:
* Encode the frame into a buffer
*
* @param buffer buffer to write frame to
*/
*/
virtual void fill(OutBuffer& buffer) const override
{
// call base
@ -63,13 +63,13 @@ public:
* @param noWait Do not wait on response
*
* @return newly created Queuepurgeframe
*/
*/
QueuePurgeFrame(uint16_t channel, const std::string& name, bool noWait = false) :
QueueFrame(channel, name.length() + 4), // 1 extra for string length, 1 for bool, 2 for deprecated field
_name(name),
_noWait(noWait)
{}
/**
* Constructor based on received data
* @param frame received frame
@ -80,11 +80,23 @@ public:
_name(frame),
_noWait(frame)
{}
/**
* Is this a synchronous frame?
*
* After a synchronous frame no more frames may be
* sent until the accompanying -ok frame arrives
*/
bool synchronous() const override
{
// we are synchronous without the nowait option
return !noWait();
}
/**
* The method ID
* @return method id
*/
*/
virtual uint16_t methodID() const override
{
return 30;

View File

@ -1,6 +1,6 @@
/**
* Class describing an AMQP queue purge frame
*
*
* @copyright 2014 Copernica BV
*/
@ -35,7 +35,7 @@ protected:
// add fields
buffer.add(_messageCount);
}
public:
/**
* Construct a queuepurgeokframe
@ -79,7 +79,7 @@ public:
{
return _messageCount;
}
/**
* Process the frame
* @param connection The connection over which it was received
@ -89,13 +89,13 @@ public:
{
// check if we have a channel
ChannelImpl *channel = connection->channel(this->channel());
// channel does not exist
if(!channel) return false;
// report queue purge success
channel->reportQueuePurged(this->messageCount());
if (channel->reportSuccess(this->messageCount())) channel->synchronized();
// done
return true;
}

View File

@ -1,6 +1,6 @@
/**
* Class describing an AMQP queue unbind ok frame
*
*
* @copyright 2014 Copernica BV
*/
@ -24,7 +24,7 @@ protected:
{
// call base
QueueFrame::fill(buffer);
}
}
public:
/**
* Decode a queueunbindokframe from a received frame
@ -58,7 +58,7 @@ public:
{
return 51;
}
/**
* Process the frame
* @param connection The connection over which it was received
@ -68,13 +68,13 @@ public:
{
// check if we have a channel
ChannelImpl *channel = connection->channel(this->channel());
// channel does not exist
if(!channel) return false;
// report queue unbind success
channel->reportQueueUnbound();
if (channel->reportSuccess()) channel->synchronized();
// done
return true;
}

View File

@ -66,6 +66,7 @@
#include "transactionrollbackframe.h"
#include "transactionrollbackokframe.h"
#include "messageimpl.h"
#include "consumedmessage.h"
#include "bodyframe.h"
#include "basicheaderframe.h"
#include "framecheck.h"

View File

@ -24,7 +24,7 @@ private:
* @var int16_t
*/
int16_t _replyCode;
/**
* The reply message
* @var string
@ -40,22 +40,11 @@ public:
ReturnedMessage(const BasicReturnFrame &frame) :
MessageImpl(frame.exchange(), frame.routingKey()),
_replyCode(frame.replyCode()), _replyText(frame.replyText()) {}
/**
* Destructor
*/
virtual ~ReturnedMessage() {}
/**
* Report to the handler
* @param channel
* @param handler
*/
virtual void report(Channel *channel, ChannelHandler *handler) override
{
// report to the handler
handler->onReturned(channel, *this, _replyCode, _replyText);
}
};
/**

View File

@ -97,7 +97,7 @@ Table &Table::operator=(Table &&table)
* @param name field name
* @return the field value
*/
const Field &Table::get(const std::string &name)
const Field &Table::get(const std::string &name) const
{
// we need an empty string
static ShortString empty;
@ -145,7 +145,7 @@ size_t Table::size() const
void Table::fill(OutBuffer& buffer) const
{
// add size
buffer.add((uint32_t) size()-4);
buffer.add(static_cast<uint32_t>(size()-4));
// loop through the fields
for (auto iter(_fields.begin()); iter != _fields.end(); ++iter)

View File

@ -59,6 +59,26 @@ public:
{
return 21;
}
/**
* Process the frame
* @param connection The connection over which it was received
* @return bool Was it succesfully processed?
*/
virtual bool process(ConnectionImpl *connection) override
{
// we need the appropriate channel
ChannelImpl *channel = connection->channel(this->channel());
// channel does not exist
if(!channel) return false;
// report that the channel is open
if (channel->reportSuccess()) channel->synchronized();
// done
return true;
}
};
/**

View File

@ -58,7 +58,27 @@ public:
virtual uint16_t methodID() const override
{
return 31;
}
}
/**
* Process the frame
* @param connection The connection over which it was received
* @return bool Was it succesfully processed?
*/
virtual bool process(ConnectionImpl *connection) override
{
// we need the appropriate channel
ChannelImpl *channel = connection->channel(this->channel());
// channel does not exist
if(!channel) return false;
// report that the channel is open
if (channel->reportSuccess()) channel->synchronized();
// done
return true;
}
};
/**

View File

@ -59,6 +59,26 @@ public:
{
return 11;
}
/**
* Process the frame
* @param connection The connection over which it was received
* @return bool Was it succesfully processed?
*/
virtual bool process(ConnectionImpl *connection) override
{
// we need the appropriate channel
ChannelImpl *channel = connection->channel(this->channel());
// channel does not exist
if(!channel) return false;
// report that the channel is open
if (channel->reportSuccess()) channel->synchronized();
// done
return true;
}
};
/**

View File

@ -1,6 +1,6 @@
CPP = g++
CPP = g++
CPPFLAGS = -Wall -c -I. -O2 -flto -std=c++11 -g
LD = g++
LD = g++
LDFLAGS = -lamqpcpp -lcopernica_event -lcopernica_network -lev
RESULT = a.out
SOURCES = $(wildcard *.cpp)
@ -16,4 +16,3 @@ clean:
${OBJECTS}:
${CPP} ${CPPFLAGS} -o $@ ${@:%.o=%.cpp}

View File

@ -1,2 +1,2 @@
I'm sorry, the test case makes use of the closed source Copernica libraries. Maybe someone
is willing to provide a test case based on plain system calls?
is willing to provide a test case based on plain system calls?

BIN
tests/a.out Executable file

Binary file not shown.

View File

@ -51,5 +51,4 @@ int main(int argc, const char *argv[])
// done
return 0;
}
}
}

View File

@ -84,13 +84,41 @@ void MyConnection::onConnected(Network::TcpSocket *socket)
// create amqp connection, and a new channel
_connection = new AMQP::Connection(this, AMQP::Login("guest", "guest"), "/");
_channel = new AMQP::Channel(_connection, this);
_channel = new AMQP::Channel(_connection);
// install a handler when channel is in error
_channel->onError([](const char *message) {
std::cout << "channel error " << message << std::endl;
});
// install a handler when channel is ready
_channel->onReady([]() {
std::cout << "channel ready" << std::endl;
});
// we declare a queue, an exchange and we publish a message
_channel->declareQueue("my_queue");
// _channel->declareQueue("my_queue", AMQP::autodelete);
_channel->declareExchange("my_exchange", AMQP::direct);
_channel->bindQueue("my_exchange", "my_queue", "key");
_channel->declareQueue("my_queue").onSuccess([this]() {
std::cout << "queue declared" << std::endl;
// start consuming
_channel->consume("my_queue").onReceived([](const AMQP::Message &message, uint64_t deliveryTag, bool redelivered) {
std::cout << "received: " << message.message() << std::endl;
});
});
// declare an exchange
_channel->declareExchange("my_exchange", AMQP::direct).onSuccess([]() {
std::cout << "exchange declared" << std::endl;
});
// bind queue and exchange
_channel->bindQueue("my_exchange", "my_queue", "key").onSuccess([this]() {
std::cout << "queue bound to exchange" << std::endl;
_channel->publish("my_exchange", "key", "just a message");
});
}
/**
@ -183,7 +211,7 @@ void MyConnection::onData(AMQP::Connection *connection, const char *buffer, size
* @param connection The connection that entered the error state
* @param message Error message
*/
void MyConnection::onError(AMQP::Connection *connection, const std::string &message)
void MyConnection::onError(AMQP::Connection *connection, const char *message)
{
// report error
std::cout << "AMQP Connection error: " << message << std::endl;
@ -201,263 +229,6 @@ void MyConnection::onConnected(AMQP::Connection *connection)
std::cout << "AMQP login success" << std::endl;
// create channel if it does not yet exist
if (!_channel) _channel = new AMQP::Channel(connection, this);
}
/**
* Method that is called when the channel was succesfully created.
* Only after the channel was created, you can use it for subsequent messages over it
* @param channel
*/
void MyConnection::onReady(AMQP::Channel *channel)
{
// show
std::cout << "AMQP channel ready, id: " << (int) channel->id() << std::endl;
}
/**
* An error has occured on the channel
* @param channel
* @param message
*/
void MyConnection::onError(AMQP::Channel *channel, const std::string &message)
{
// show
std::cout << "AMQP channel error, id: " << (int) channel->id() << " - message: " << message << std::endl;
// main channel cause an error, get rid of if
delete _channel;
// reset pointer
_channel = nullptr;
}
/**
* Method that is called when the channel was paused
* @param channel
*/
void MyConnection::onPaused(AMQP::Channel *channel)
{
// show
std::cout << "AMQP channel paused" << std::endl;
}
/**
* Method that is called when the channel was resumed
* @param channel
*/
void MyConnection::onResumed(AMQP::Channel *channel)
{
// show
std::cout << "AMQP channel resumed" << std::endl;
}
/**
* Method that is called when a channel is closed
* @param channel
*/
void MyConnection::onClosed(AMQP::Channel *channel)
{
// show
std::cout << "AMQP channel closed" << std::endl;
}
/**
* Method that is called when a transaction was started
* @param channel
*/
void MyConnection::onTransactionStarted(AMQP::Channel *channel)
{
// show
std::cout << "AMQP transaction started" << std::endl;
}
/**
* Method that is called when a transaction was committed
* @param channel
*/
void MyConnection::onTransactionCommitted(AMQP::Channel *channel)
{
// show
std::cout << "AMQP transaction committed" << std::endl;
}
/**
* Method that is called when a transaction was rolled back
* @param channel
*/
void MyConnection::onTransactionRolledBack(AMQP::Channel *channel)
{
// show
std::cout << "AMQP transaction rolled back" << std::endl;
}
/**
* Mehod that is called when an exchange is declared
* @param channel
*/
void MyConnection::onExchangeDeclared(AMQP::Channel *channel)
{
// show
std::cout << "AMQP exchange declared" << std::endl;
}
/**
* Method that is called when an exchange is bound
* @param channel
*/
void MyConnection::onExchangeBound(AMQP::Channel *channel)
{
// show
std::cout << "AMQP Exchange bound" << std::endl;
}
/**
* Method that is called when an exchange is unbound
* @param channel
*/
void MyConnection::onExchangeUnbound(AMQP::Channel *channel)
{
// show
std::cout << "AMQP Exchange unbound" << std::endl;
}
/**
* Method that is called when an exchange is deleted
* @param channel
*/
void MyConnection::onExchangeDeleted(AMQP::Channel *channel)
{
// show
std::cout << "AMQP Exchange deleted" << std::endl;
}
/**
* Method that is called when a queue is declared
* @param channel
* @param name name of the queue
* @param messageCount number of messages in queue
* @param consumerCount number of active consumers
*/
void MyConnection::onQueueDeclared(AMQP::Channel *channel, const std::string &name, uint32_t messageCount, uint32_t consumerCount)
{
// show
std::cout << "AMQP Queue declared" << std::endl;
}
/**
* Method that is called when a queue is bound
* @param channel
* @param
*/
void MyConnection::onQueueBound(AMQP::Channel *channel)
{
// show
std::cout << "AMQP Queue bound" << std::endl;
// _connection->setQos(10);
// _channel->setQos(1);
_channel->publish("my_exchange", "invalid-key", AMQP::mandatory, "this is the message");
// _channel->consume("my_queue");
}
/**
* Method that is called when a queue is deleted
* @param channel
* @param messageCount number of messages deleted along with the queue
*/
void MyConnection::onQueueDeleted(AMQP::Channel *channel, uint32_t messageCount)
{
// show
std::cout << "AMQP Queue deleted" << std::endl;
}
/**
* Method that is called when a queue is unbound
* @param channel
*/
void MyConnection::onQueueUnbound(AMQP::Channel *channel)
{
// show
std::cout << "AMQP Queue unbound" << std::endl;
}
/**
* Method that is called when a queue is purged
* @param messageCount number of message purged
*/
void MyConnection::onQueuePurged(AMQP::Channel *channel, uint32_t messageCount)
{
// show
std::cout << "AMQP Queue purged" << std::endl;
}
/**
* Method that is called when the quality-of-service was changed
* This is the result of a call to Channel::setQos()
*/
void MyConnection::onQosSet(AMQP::Channel *channel)
{
// show
std::cout << "AMQP Qos set" << std::endl;
}
/**
* Method that is called when a consumer was started
* This is the result of a call to Channel::consume()
* @param channel the channel on which the consumer was started
* @param tag the consumer tag
*/
void MyConnection::onConsumerStarted(AMQP::Channel *channel, const std::string &tag)
{
// show
std::cout << "AMQP consumer started" << std::endl;
}
/**
* Method that is called when a message has been received on a channel
* @param channel the channel on which the consumer was started
* @param message the consumed message
* @param deliveryTag the delivery tag, you need this to acknowledge the message
* @param consumerTag the consumer identifier that was used to retrieve this message
* @param redelivered is this a redelivered message?
*/
void MyConnection::onReceived(AMQP::Channel *channel, const AMQP::Message &message, uint64_t deliveryTag, const std::string &consumerTag, bool redelivered)
{
// show
std::cout << "AMQP consumed: " << message.message() << std::endl;
// ack the message
channel->ack(deliveryTag);
}
/**
* Method that is called when a message you tried to publish was returned
* by the server. This only happens when the 'mandatory' or 'immediate' flag
* was set with the Channel::publish() call.
* @param channel the channel on which the message was returned
* @param message the returned message
* @param code the reply code
* @param text human readable reply reason
*/
void MyConnection::onReturned(AMQP::Channel *channel, const AMQP::Message &message, int16_t code, const std::string &text)
{
// show
std::cout << "AMQP message returned: " << text << std::endl;
}
/**
* Method that is called when a consumer was stopped
* This is the result of a call to Channel::cancel()
* @param channel the channel on which the consumer was stopped
* @param tag the consumer tag
*/
void MyConnection::onConsumerStopped(AMQP::Channel *channel, const std::string &tag)
{
// show
std::cout << "AMQP consumer stopped" << std::endl;
if (!_channel) _channel = new AMQP::Channel(connection);
}

View File

@ -11,7 +11,6 @@
*/
class MyConnection :
public AMQP::ConnectionHandler,
public AMQP::ChannelHandler,
public Network::TcpHandler
{
private:
@ -93,7 +92,7 @@ private:
* @param connection The connection that entered the error state
* @param message Error message
*/
virtual void onError(AMQP::Connection *connection, const std::string &message) override;
virtual void onError(AMQP::Connection *connection, const char *message) override;
/**
* Method that is called when the login attempt succeeded. After this method
@ -103,163 +102,6 @@ private:
*/
virtual void onConnected(AMQP::Connection *connection) override;
/**
* Method that is called when the channel was succesfully created.
* Only after the channel was created, you can use it for subsequent messages over it
* @param channel
*/
virtual void onReady(AMQP::Channel *channel) override;
/**
* An error has occured on the channel
* @param channel
* @param message
*/
virtual void onError(AMQP::Channel *channel, const std::string &message) override;
/**
* Method that is called when the channel was paused
* @param channel
*/
virtual void onPaused(AMQP::Channel *channel) override;
/**
* Method that is called when the channel was resumed
* @param channel
*/
virtual void onResumed(AMQP::Channel *channel) override;
/**
* Method that is called when a channel is closed
* @param channel
*/
virtual void onClosed(AMQP::Channel *channel) override;
/**
* Method that is called when a transaction was started
* @param channel
*/
virtual void onTransactionStarted(AMQP::Channel *channel) override;
/**
* Method that is called when a transaction was committed
* @param channel
*/
virtual void onTransactionCommitted(AMQP::Channel *channel) override;
/**
* Method that is called when a transaction was rolled back
* @param channel
*/
virtual void onTransactionRolledBack(AMQP::Channel *channel) override;
/**
* Method that is called when an exchange is bound
* @param channel
*/
virtual void onExchangeBound(AMQP::Channel *channel) override;
/**
* Method that is called when an exchange is unbound
* @param channel
*/
virtual void onExchangeUnbound(AMQP::Channel *channel) override;
/**
* Method that is called when an exchange is deleted
* @param channel
*/
virtual void onExchangeDeleted(AMQP::Channel *channel) override;
/**
* Mehod that is called when an exchange is declared
* @param channel
*/
virtual void onExchangeDeclared(AMQP::Channel *channel) override;
/**
* Method that is called when a queue is declared
* @param channel
* @param name name of the queue
* @param messageCount number of messages in queue
* @param consumerCount number of active consumers
*/
virtual void onQueueDeclared(AMQP::Channel *channel, const std::string &name, uint32_t messageCount, uint32_t consumerCount) override;
/**
* Method that is called when a queue is bound
* @param channel
* @param
*/
virtual void onQueueBound(AMQP::Channel *channel) override;
/**
* Method that is called when a queue is deleted
* @param channel
* @param messageCount number of messages deleted along with the queue
*/
virtual void onQueueDeleted(AMQP::Channel *channel, uint32_t messageCount) override;
/**
* Method that is called when a queue is unbound
* @param channel
*/
virtual void onQueueUnbound(AMQP::Channel *channel) override;
/**
* Method that is called when a queue is purged
* @param messageCount number of message purged
*/
virtual void onQueuePurged(AMQP::Channel *channel, uint32_t messageCount) override;
/**
* Method that is called when the quality-of-service was changed
* This is the result of a call to Channel::setQos()
*/
virtual void onQosSet(AMQP::Channel *channel) override;
/**
* Method that is called when a consumer was started
* This is the result of a call to Channel::consume()
* @param channel the channel on which the consumer was started
* @param tag the consumer tag
*/
virtual void onConsumerStarted(AMQP::Channel *channel, const std::string &tag) override;
/**
* Method that is called when a consumer was stopped
* This is the result of a call to Channel::cancel()
* @param channel the channel on which the consumer was stopped
* @param tag the consumer tag
*/
virtual void onConsumerStopped(AMQP::Channel *channel, const std::string &tag) override;
/**
* Method that is called when a message has been received on a channel
* This message will be called for every message that is received after
* you started consuming. Make sure you acknowledge the messages when its
* safe to remove them from RabbitMQ (unless you set no-ack option when you
* started the consumer)
* @param channel the channel on which the consumer was started
* @param message the consumed message
* @param deliveryTag the delivery tag, you need this to acknowledge the message
* @param consumerTag the consumer identifier that was used to retrieve this message
* @param redelivered is this a redelivered message?
*/
virtual void onReceived(AMQP::Channel *channel, const AMQP::Message &message, uint64_t deliveryTag, const std::string &consumerTag, bool redelivered) override;
/**
* Method that is called when a message you tried to publish was returned
* by the server. This only happens when the 'mandatory' or 'immediate' flag
* was set with the Channel::publish() call.
* @param channel the channel on which the message was returned
* @param message the returned message
* @param code the reply code
* @param text human readable reply reason
*/
virtual void onReturned(AMQP::Channel *channel, const AMQP::Message &message, int16_t code, const std::string &text) override;
public:
/**
* Constructor
@ -274,4 +116,3 @@ public:
};

19
tests/table/table.cpp Normal file
View File

@ -0,0 +1,19 @@
#include <iostream>
#include <amqpcpp.h>
int main()
{
AMQP::Array x;
x[0] = "abc";
x[1] = "xyz";
std::cout << x << std::endl;
std::cout << x[0] << std::endl;
std::cout << x[1] << std::endl;
return 0;
}