diff --git a/README.md b/README.md index 6777e3f..cef887d 100644 --- a/README.md +++ b/README.md @@ -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 - -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 diff --git a/amqpcpp.h b/amqpcpp.h index 0ca4c9d..328cf08 100644 --- a/amqpcpp.h +++ b/amqpcpp.h @@ -6,6 +6,8 @@ * @documentation public */ +#pragma once + // base C++ include files #include #include @@ -16,6 +18,9 @@ #include #include #include +#include +#include +#include // base C include files #include @@ -29,6 +34,7 @@ #include #include #include +#include // amqp types #include @@ -48,7 +54,12 @@ // mid level includes #include #include -#include +#include +#include +#include +#include +#include +#include #include #include #include diff --git a/include/array.h b/include/array.h index befd1d6..054fac6 100644 --- a/include/array.h +++ b/include/array.h @@ -1,6 +1,7 @@ +#pragma once /** * AMQP field array - * + * * @copyright 2014 Copernica BV */ @@ -20,7 +21,7 @@ private: * @typedef */ typedef std::vector> 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 clone() const override { - return new Array(*this); + return std::make_shared(*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(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; + } }; /** diff --git a/include/booleanset.h b/include/booleanset.h index 6383ab2..b527048 100644 --- a/include/booleanset.h +++ b/include/booleanset.h @@ -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 clone() const override { - return new BooleanSet(*this); + return std::make_shared(*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 << ")"; } /** diff --git a/include/callbacks.h b/include/callbacks.h new file mode 100644 index 0000000..a974ef0 --- /dev/null +++ b/include/callbacks.h @@ -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; +using ErrorCallback = std::function; +using FinalizeCallback = std::function; +using MessageCallback = std::function; +using QueueCallback = std::function; +using DeleteCallback = std::function; +using ConsumeCallback = std::function; +using CancelCallback = std::function; + +/** + * End namespace + */ +} diff --git a/include/channel.h b/include/channel.h index 0b36dba..e722ebb 100644 --- a/include/channel.h +++ b/include/channel.h @@ -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 diff --git a/include/channelhandler.h b/include/channelhandler.h deleted file mode 100644 index ad0c001..0000000 --- a/include/channelhandler.h +++ /dev/null @@ -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 - */ -} diff --git a/include/channelimpl.h b/include/channelimpl.h index ad02d27..062c160 100644 --- a/include/channelimpl.h +++ b/include/channelimpl.h @@ -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::map _consumers; + + /** + * Pointer to the oldest deferred result (the first one that is going + * to be executed) + * + * @var Deferred + */ + std::unique_ptr _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> _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 + 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(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; - + }; /** diff --git a/include/classes.h b/include/classes.h index af49200..b4a5500 100644 --- a/include/classes.h +++ b/include/classes.h @@ -1,3 +1,4 @@ +#pragma once /** * Classes.h * diff --git a/include/connection.h b/include/connection.h index 64298a8..a67e6ad 100644 --- a/include/connection.h +++ b/include/connection.h @@ -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); } diff --git a/include/connectionhandler.h b/include/connectionhandler.h index 5233948..17b98b2 100644 --- a/include/connectionhandler.h +++ b/include/connectionhandler.h @@ -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) {} - + }; /** diff --git a/include/connectionimpl.h b/include/connectionimpl.h index 1755281..530888a 100644 --- a/include/connectionimpl.h +++ b/include/connectionimpl.h @@ -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 _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; }; /** diff --git a/include/decimalfield.h b/include/decimalfield.h index 5df085b..75a8ab0 100644 --- a/include/decimalfield.h +++ b/include/decimalfield.h @@ -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 clone() const override { - return new DecimalField(_places, _number); + return std::make_shared(_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) << ")"; } /** diff --git a/include/deferred.h b/include/deferred.h new file mode 100644 index 0000000..01a3fe8 --- /dev/null +++ b/include/deferred.h @@ -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 + */ +} diff --git a/include/deferredcancel.h b/include/deferredcancel.h new file mode 100644 index 0000000..0c1f8e6 --- /dev/null +++ b/include/deferredcancel.h @@ -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 + */ +} diff --git a/include/deferredconsumer.h b/include/deferredconsumer.h new file mode 100644 index 0000000..c656728 --- /dev/null +++ b/include/deferredconsumer.h @@ -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 + */ +} diff --git a/include/deferreddelete.h b/include/deferreddelete.h new file mode 100644 index 0000000..aa0d83e --- /dev/null +++ b/include/deferreddelete.h @@ -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 + */ +} diff --git a/include/deferredqueue.h b/include/deferredqueue.h new file mode 100644 index 0000000..6b5d930 --- /dev/null +++ b/include/deferredqueue.h @@ -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 + */ +} diff --git a/include/entityimpl.h b/include/entityimpl.h index 51ac4ac..be5207d 100644 --- a/include/entityimpl.h +++ b/include/entityimpl.h @@ -1,3 +1,4 @@ +#pragma once /** * EntityImpl.h * diff --git a/include/envelope.h b/include/envelope.h index f66331f..fbe646e 100644 --- a/include/envelope.h +++ b/include/envelope.h @@ -1,3 +1,4 @@ +#pragma once /** * Envelope.h * diff --git a/include/exchangetype.h b/include/exchangetype.h index a5e7929..ea6ade8 100644 --- a/include/exchangetype.h +++ b/include/exchangetype.h @@ -1,3 +1,4 @@ +#pragma once /** * ExchangeType.h * diff --git a/include/field.h b/include/field.h index 41fbdca..d6a7a88 100644 --- a/include/field.h +++ b/include/field.h @@ -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 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 */ diff --git a/include/fieldproxy.h b/include/fieldproxy.h index 947d6cc..2666850 100644 --- a/include/fieldproxy.h +++ b/include/fieldproxy.h @@ -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 + 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 AssociativeFieldProxy; typedef FieldProxy 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 */ diff --git a/include/flags.h b/include/flags.h index 0a9c2ee..c2de72f 100644 --- a/include/flags.h +++ b/include/flags.h @@ -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; diff --git a/include/login.h b/include/login.h index 7605696..51f79de 100644 --- a/include/login.h +++ b/include/login.h @@ -1,3 +1,4 @@ +#pragma once /** * The login information to access a server * diff --git a/include/message.h b/include/message.h index a036420..c92cc5e 100644 --- a/include/message.h +++ b/include/message.h @@ -1,3 +1,4 @@ +#pragma once /** * Message.h * diff --git a/include/metadata.h b/include/metadata.h index 7c1714a..42bf051 100644 --- a/include/metadata.h +++ b/include/metadata.h @@ -1,3 +1,4 @@ +#pragma once /** * MetaData.h * diff --git a/src/monitor.h b/include/monitor.h similarity index 100% rename from src/monitor.h rename to include/monitor.h diff --git a/include/numericfield.h b/include/numericfield.h index bcddf58..3cd4c93 100644 --- a/include/numericfield.h +++ b/include/numericfield.h @@ -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 clone() const override { // create a new copy of ourselves and return it - return new NumericField(_value); + return std::make_shared(_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() << ")"; + } }; /** diff --git a/include/outbuffer.h b/include/outbuffer.h index 70d4b56..54d5988 100644 --- a/include/outbuffer.h +++ b/include/outbuffer.h @@ -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; } diff --git a/include/receivedframe.h b/include/receivedframe.h index 0f9a0e4..03bd0ae 100644 --- a/include/receivedframe.h +++ b/include/receivedframe.h @@ -1,3 +1,4 @@ +#pragma once /** * ReceivedFrame.h * diff --git a/include/stringfield.h b/include/stringfield.h index 1a2ffd3..0408de7 100644 --- a/include/stringfield.h +++ b/include/stringfield.h @@ -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 clone() const override { // create a new copy of ourselves and return it - return new StringField(_data); + return std::make_shared(_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() << ")"; + } }; /** diff --git a/include/table.h b/include/table.h index c26fb6d..2faec2b 100644 --- a/include/table.h +++ b/include/table.h @@ -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 clone() const override { - return new Table(*this); + return std::make_shared(*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(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; + } + }; /** diff --git a/include/watchable.h b/include/watchable.h index dab54aa..a81e71d 100644 --- a/include/watchable.h +++ b/include/watchable.h @@ -1,3 +1,4 @@ +#pragma once /** * Watchable.h * diff --git a/src/Makefile b/src/Makefile index 3a9b4e9..c9330b9 100644 --- a/src/Makefile +++ b/src/Makefile @@ -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} diff --git a/src/array.cpp b/src/array.cpp index 3b5831b..d04c865 100644 --- a/src/array.cpp +++ b/src/array.cpp @@ -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(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(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); } } diff --git a/src/basicackframe.h b/src/basicackframe.h index 8337b9f..7549155 100644 --- a/src/basicackframe.h +++ b/src/basicackframe.h @@ -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 diff --git a/src/basiccancelframe.h b/src/basiccancelframe.h index ea65d3e..e665ab2 100644 --- a/src/basiccancelframe.h +++ b/src/basiccancelframe.h @@ -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); } diff --git a/src/basiccancelokframe.h b/src/basiccancelokframe.h index 0841379..6592166 100644 --- a/src/basiccancelokframe.h +++ b/src/basiccancelokframe.h @@ -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(consumerTag())) channel->synchronized(); + // done return true; } diff --git a/src/basicconsumeframe.h b/src/basicconsumeframe.h index 0bc7888..7e57eee 100644 --- a/src/basicconsumeframe.h +++ b/src/basicconsumeframe.h @@ -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 diff --git a/src/basicconsumeokframe.h b/src/basicconsumeokframe.h index cf7298a..e767f0f 100644 --- a/src/basicconsumeokframe.h +++ b/src/basicconsumeokframe.h @@ -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; } diff --git a/src/basicdeliverframe.h b/src/basicdeliverframe.h index aef677f..ac6128d 100644 --- a/src/basicdeliverframe.h +++ b/src/basicdeliverframe.h @@ -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 diff --git a/src/basicpublishframe.h b/src/basicpublishframe.h index 9f9eb69..975c2ab 100644 --- a/src/basicpublishframe.h +++ b/src/basicpublishframe.h @@ -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 diff --git a/src/basicqosokframe.h b/src/basicqosokframe.h index 5d8bca1..9c235e2 100644 --- a/src/basicqosokframe.h +++ b/src/basicqosokframe.h @@ -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; } diff --git a/src/basicrecoverasyncframe.h b/src/basicrecoverasyncframe.h index 7d7e036..68eb3ec 100644 --- a/src/basicrecoverasyncframe.h +++ b/src/basicrecoverasyncframe.h @@ -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 diff --git a/src/basicrecoverframe.h b/src/basicrecoverframe.h index d378393..ca67f16 100644 --- a/src/basicrecoverframe.h +++ b/src/basicrecoverframe.h @@ -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 diff --git a/src/basicrecoverokframe.h b/src/basicrecoverokframe.h index 66004b1..1bcd771 100644 --- a/src/basicrecoverokframe.h +++ b/src/basicrecoverokframe.h @@ -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; } - + }; diff --git a/src/basicrejectframe.h b/src/basicrejectframe.h index c5c1f6b..a77d7da 100644 --- a/src/basicrejectframe.h +++ b/src/basicrejectframe.h @@ -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 diff --git a/src/basicreturnframe.h b/src/basicreturnframe.h index 1759240..2fedce6 100644 --- a/src/basicreturnframe.h +++ b/src/basicreturnframe.h @@ -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; } }; diff --git a/src/channelcloseframe.h b/src/channelcloseframe.h index 6db745f..0d9c1d6 100644 --- a/src/channelcloseframe.h +++ b/src/channelcloseframe.h @@ -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; diff --git a/src/channelcloseokframe.h b/src/channelcloseokframe.h index 5eb5cda..d06e845 100644 --- a/src/channelcloseokframe.h +++ b/src/channelcloseokframe.h @@ -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; } diff --git a/src/channelflowokframe.h b/src/channelflowokframe.h index efc3ae6..c335564 100644 --- a/src/channelflowokframe.h +++ b/src/channelflowokframe.h @@ -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; } diff --git a/src/channelimpl.cpp b/src/channelimpl.cpp index 28a3f09..0f6171d 100644 --- a/src/channelimpl.cpp +++ b/src/channelimpl.cpp @@ -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 */ diff --git a/src/connectioncloseframe.h b/src/connectioncloseframe.h index 6bff24a..1c5059f 100644 --- a/src/connectioncloseframe.h +++ b/src/connectioncloseframe.h @@ -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; diff --git a/src/connectionimpl.cpp b/src/connectionimpl.cpp index 1a37f4a..a26f903 100644 --- a/src/connectionimpl.cpp +++ b/src/connectionimpl.cpp @@ -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; } diff --git a/src/consumedmessage.h b/src/consumedmessage.h index 30ed208..447bdc6 100644 --- a/src/consumedmessage.h +++ b/src/consumedmessage.h @@ -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); } }; diff --git a/src/deferredcancel.cpp b/src/deferredcancel.cpp new file mode 100644 index 0000000..aaecebe --- /dev/null +++ b/src/deferredcancel.cpp @@ -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 + */ +} + diff --git a/src/deferredconsumer.cpp b/src/deferredconsumer.cpp new file mode 100644 index 0000000..528cbcb --- /dev/null +++ b/src/deferredconsumer.cpp @@ -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 + */ +} diff --git a/src/exception.h b/src/exception.h index 17d81f7..e4bb35a 100644 --- a/src/exception.h +++ b/src/exception.h @@ -22,12 +22,6 @@ protected: * @param what */ explicit Exception(const std::string &what) : runtime_error(what) {} - -public: - /** - * Destructor - */ - virtual ~Exception() {} }; /** diff --git a/src/exchangebindframe.h b/src/exchangebindframe.h index e246fad..1d01161 100644 --- a/src/exchangebindframe.h +++ b/src/exchangebindframe.h @@ -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); } diff --git a/src/exchangebindokframe.h b/src/exchangebindokframe.h index ad3c54c..157d9e4 100644 --- a/src/exchangebindokframe.h +++ b/src/exchangebindokframe.h @@ -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 } diff --git a/src/exchangedeclareframe.h b/src/exchangedeclareframe.h index 9f36e64..aa940dd 100644 --- a/src/exchangedeclareframe.h +++ b/src/exchangedeclareframe.h @@ -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 diff --git a/src/exchangedeclareokframe.h b/src/exchangedeclareokframe.h index b271601..1938f73 100644 --- a/src/exchangedeclareokframe.h +++ b/src/exchangedeclareokframe.h @@ -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; } diff --git a/src/exchangedeleteframe.h b/src/exchangedeleteframe.h index 960690c..cdd906f 100644 --- a/src/exchangedeleteframe.h +++ b/src/exchangedeleteframe.h @@ -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 diff --git a/src/exchangedeleteokframe.h b/src/exchangedeleteokframe.h index c386358..b8ec41b 100644 --- a/src/exchangedeleteokframe.h +++ b/src/exchangedeleteokframe.h @@ -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; } diff --git a/src/exchangeunbindframe.h b/src/exchangeunbindframe.h index 64999d2..d92d941 100644 --- a/src/exchangeunbindframe.h +++ b/src/exchangeunbindframe.h @@ -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 } diff --git a/src/exchangeunbindokframe.h b/src/exchangeunbindokframe.h index f3a2a9b..d25e2a1 100644 --- a/src/exchangeunbindokframe.h +++ b/src/exchangeunbindokframe.h @@ -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 } diff --git a/src/field.cpp b/src/field.cpp index e49dc17..2655bb2 100644 --- a/src/field.cpp +++ b/src/field.cpp @@ -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 */ diff --git a/src/frame.h b/src/frame.h index ed8ad0a..2522e7c 100644 --- a/src/frame.h +++ b/src/frame.h @@ -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 */ diff --git a/src/includes.h b/src/includes.h index 52b31d0..2a75192 100644 --- a/src/includes.h +++ b/src/includes.h @@ -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" diff --git a/src/messageimpl.h b/src/messageimpl.h index 27d5b85..f9f839a 100644 --- a/src/messageimpl.h +++ b/src/messageimpl.h @@ -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; }; /** diff --git a/src/methodframe.h b/src/methodframe.h index 749e088..eac62b5 100644 --- a/src/methodframe.h +++ b/src/methodframe.h @@ -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; } }; diff --git a/src/protocolexception.h b/src/protocolexception.h index 9973f0b..7f829bc 100644 --- a/src/protocolexception.h +++ b/src/protocolexception.h @@ -23,11 +23,6 @@ public: * @param what */ explicit ProtocolException(const std::string &what) : Exception(what) {} - - /** - * Destructor - */ - virtual ~ProtocolException() {} }; /** diff --git a/src/queuebindframe.h b/src/queuebindframe.h index 93b30c6..bc4d2e9 100644 --- a/src/queuebindframe.h +++ b/src/queuebindframe.h @@ -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 diff --git a/src/queuebindokframe.h b/src/queuebindokframe.h index 6c66cb2..bf27b87 100644 --- a/src/queuebindokframe.h +++ b/src/queuebindokframe.h @@ -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; } diff --git a/src/queuedeclareframe.h b/src/queuedeclareframe.h index 214cd48..475c8b6 100644 --- a/src/queuedeclareframe.h +++ b/src/queuedeclareframe.h @@ -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); } diff --git a/src/queuedeclareokframe.h b/src/queuedeclareokframe.h index 3cbc113..852c4ec 100644 --- a/src/queuedeclareokframe.h +++ b/src/queuedeclareokframe.h @@ -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; } diff --git a/src/queuedeleteframe.h b/src/queuedeleteframe.h index 8892c5e..dc9a00a 100644 --- a/src/queuedeleteframe.h +++ b/src/queuedeleteframe.h @@ -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 diff --git a/src/queuedeleteokframe.h b/src/queuedeleteokframe.h index 881ed2b..c214aa3 100644 --- a/src/queuedeleteokframe.h +++ b/src/queuedeleteokframe.h @@ -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 */ } - + diff --git a/src/queuepurgeframe.h b/src/queuepurgeframe.h index e2a45b2..08cfad5 100644 --- a/src/queuepurgeframe.h +++ b/src/queuepurgeframe.h @@ -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; diff --git a/src/queuepurgeokframe.h b/src/queuepurgeokframe.h index 715ef00..aaa939d 100644 --- a/src/queuepurgeokframe.h +++ b/src/queuepurgeokframe.h @@ -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; } diff --git a/src/queueunbindokframe.h b/src/queueunbindokframe.h index cd779f0..cdcb570 100644 --- a/src/queueunbindokframe.h +++ b/src/queueunbindokframe.h @@ -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; } diff --git a/src/receivedframe.cpp b/src/receivedframe.cpp index cfe1c77..87aaba0 100644 --- a/src/receivedframe.cpp +++ b/src/receivedframe.cpp @@ -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" diff --git a/src/returnedmessage.h b/src/returnedmessage.h index 61115d7..776d72d 100644 --- a/src/returnedmessage.h +++ b/src/returnedmessage.h @@ -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); - } }; /** diff --git a/src/table.cpp b/src/table.cpp index ba41983..d0fb853 100644 --- a/src/table.cpp +++ b/src/table.cpp @@ -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(size()-4)); // loop through the fields for (auto iter(_fields.begin()); iter != _fields.end(); ++iter) diff --git a/src/transactioncommitokframe.h b/src/transactioncommitokframe.h index a10e1fe..3993a8f 100644 --- a/src/transactioncommitokframe.h +++ b/src/transactioncommitokframe.h @@ -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; + } }; /** diff --git a/src/transactionrollbackokframe.h b/src/transactionrollbackokframe.h index a203e4c..868626b 100644 --- a/src/transactionrollbackokframe.h +++ b/src/transactionrollbackokframe.h @@ -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; + } }; /** diff --git a/src/transactionselectokframe.h b/src/transactionselectokframe.h index 9529144..b66aede 100644 --- a/src/transactionselectokframe.h +++ b/src/transactionselectokframe.h @@ -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; + } }; /** diff --git a/tests/Makefile b/tests/Makefile index f1602b5..b5bf2f3 100644 --- a/tests/Makefile +++ b/tests/Makefile @@ -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} - diff --git a/tests/README.md b/tests/README.md index 99ebf42..07af04a 100644 --- a/tests/README.md +++ b/tests/README.md @@ -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? \ No newline at end of file diff --git a/tests/a.out b/tests/a.out new file mode 100755 index 0000000..fe76148 Binary files /dev/null and b/tests/a.out differ diff --git a/tests/main.cpp b/tests/main.cpp index 49076b7..fdd5ee0 100644 --- a/tests/main.cpp +++ b/tests/main.cpp @@ -51,5 +51,4 @@ int main(int argc, const char *argv[]) // done return 0; } -} - +} \ No newline at end of file diff --git a/tests/myconnection.cpp b/tests/myconnection.cpp index 64c82b4..dd6e265 100644 --- a/tests/myconnection.cpp +++ b/tests/myconnection.cpp @@ -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); } diff --git a/tests/myconnection.h b/tests/myconnection.h index 61b0e1e..25fb710 100644 --- a/tests/myconnection.h +++ b/tests/myconnection.h @@ -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: }; - diff --git a/tests/table/table.cpp b/tests/table/table.cpp new file mode 100644 index 0000000..8ff5e35 --- /dev/null +++ b/tests/table/table.cpp @@ -0,0 +1,19 @@ +#include +#include + + +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; +} +