Coder Social home page Coder Social logo

qamqp's Introduction

Build Status Coverage Status

QAMQP

A Qt5/Qt6 implementation of AMQP 0.9.1, focusing primarily on RabbitMQ support.

Usage

Documentation

Tests checked and integrated against rabbitmq 3.11 (August 1, 2022) Qt5.6.3 (MSVC2017) 32Bit Qt5.15.2 (MSVC2019, MSVC2022, MinGW, Clang) 32 and 64Bit Qt6.5 (MSVC2019, MSVC2022) 64Bit

A good starting point is:

  • running a local RabbitMQ,
  • browse to http://localhost:15672/#/queues (guest/guest)
  • Start the "receive" sample and see in your browser the "hello" queue appear
  • publish a message there

AMQP Support

connection

method supported
connection.start
connection.start-ok
connection.secure
connection.secure-ok
connection.tune
connection.tune-ok
connection.open
connection.open-ok
connection.close
connection.close-ok

channel

method supported
channel.open
channel.open-ok
channel.flow
channel.flow-ok
channel.close
channel.close-ok

exchange

method supported
exchange.declare
exchange.declare-ok
exchange.delete
exchange.delete-ok

queue

method supported
queue.declare
queue.declare-ok
queue.bind
queue.bind-ok
queue.unbind
queue.unbind-ok
queue.purge
queue.purge-ok
queue.delete
queue.delete-ok

basic

method supported
basic.qos
basic.consume
basic.consume-ok
basic.cancel
basic.cancel-ok
basic.publish
basic.return
basic.deliver
basic.get
basic.get-ok
basic.get-empty
basic.ack
basic.reject
basic.recover

tx

method supported
tx.select X
tx.select-ok X
tx.commit X
tx.commit-ok X
tx.rollback X
tx.rollback-ok X

confirm

method supported
confirm.select
confirm.select-ok

Credits

  • Thank you to @fuCtor for the original implementation work.

qamqp's People

Contributors

adammajer avatar aurelien35 avatar bboozzoo avatar dkormalev avatar droidsyer avatar fuctor avatar hxcan avatar johnzhanghua avatar juhk avatar matwey avatar mbroadst avatar mdhooge avatar mem avatar redkite127 avatar sergey-platonov avatar sjlongland avatar wollzebra avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

qamqp's Issues

open and close a channel and reopen again cause error

Hi I found there is a error when using the library in the following sequence.

  1. client->connectToHost()
  2. exchange = client->createExchange("myexchange");
    exchange->declare(QAmqpExchange::Topic);
  3. queue = client->createQueue();
    queue->declare();
    queue->bind(exchange, "routingkey");
    queue->consume(QAmqpQueue::coNoAck);
  4. exchange->publish(message.toUtf8(), routingKey);

Then do the remove sequence as below:

  1. queue->unbind(exchange, "routingkey");
    queue->close();
  2. exchange->close();
  3. client->disconnectFromHost();

Then repeat 1-4 again.
I got the following error log, could you please verify it is a bug or did I use the library wrong?

Thank you very much.

void QAmqpClientPrivate::_q_disconnect() already disconnected 
connecting to host:  "10.0.0.15" , port:  5672 
Connection: 
>> Start
>> version_major: 0
>> version_minor: 9
>> mechanisms:  ("PLAIN", "AMQPLAIN") 
>> locales: en_US
Connection: 
>> Tune
>> channel_max: 0
>> frame_max: 131072
>> heartbeat: 30
Connection: 
>> OpenOK
TRACE 2016-03-02T18:29:13.754 amqpmessageservice.cpp : 141 Connected : true  
TRACE 2016-03-02T18:29:13.754 amqpmessageservice.cpp : 177 Server connected   
Open channel #1
Channel#1:
>> OpenOK
declared exchange:  "myexchange" 
TRACE 2016-03-02T18:29:13.756 amqpmessageservice.cpp : 190 Declared : true  
TRACE 2016-03-02T18:29:13.756 amqpmessageservice.cpp : 210 Create queue  
Open channel #2
Channel#2:
>> OpenOK
declared queue:  "" 
message count 0
Consumer count: 0
TRACE 2016-03-02T18:29:13.762 amqpmessageservice.cpp : 227 Queue declared  
void QAmqpQueuePrivate::bindOk(const QAmqpMethodFrame&) bound to exchange 
void QAmqpQueuePrivate::bindOk(const QAmqpMethodFrame&) bound to exchange 
void QAmqpQueuePrivate::bindOk(const QAmqpMethodFrame&) bound to exchange 
TRACE 2016-03-02T18:29:13.767 amqpmessageservice.cpp : 237 Queue bound  
void QAmqpQueuePrivate::deliver(const QAmqpMethodFrame&) 

void QAmqpQueuePrivate::unbindOk(const QAmqpMethodFrame&) unbound from exchange 
void QAmqpQueuePrivate::unbindOk(const QAmqpMethodFrame&) unbound from exchange 
void QAmqpQueuePrivate::unbindOk(const QAmqpMethodFrame&) unbound from exchange 
TRACE 2016-03-02T18:29:18.129 amqpmessageservice.cpp : 254 Close queue  
Channel#2:
TRACE 2016-03-02T18:29:18.132 amqpmessageservice.cpp : 257 Remove queue  
TRACE 2016-03-02T18:29:18.132 amqpmessageservice.cpp : 202 Close exchange  
Channel#1:
TRACE 2016-03-02T18:29:18.132 amqpmessageservice.cpp : 169 Disconnect from Host  
Connection: 
void QAmqpClientPrivate::closeOk(const QAmqpMethodFrame&) received 
TRACE 2016-03-02T18:29:18.132 amqpmessageservice.cpp : 75 Connect connected :  false From: QAmqpClient(0x9a5e1b8)  
exchange  "myexchange"  disconnected 
connecting to host:  "10.0.0.15" , port:  5672 
Connection: 
>> Start
>> version_major: 0
>> version_minor: 9
>> mechanisms:  ("PLAIN", "AMQPLAIN") 
>> locales: en_US
Connection: 
>> Tune
>> channel_max: 0
>> frame_max: 131072
>> heartbeat: 30
Connection: 
>> OpenOK
Open channel #1
Open channel #2
Channel#1:
>> OpenOK
TRACE 2016-03-02T18:29:18.170 amqpmessageservice.cpp : 141 Connected : true  
TRACE 2016-03-02T18:29:18.170 amqpmessageservice.cpp : 177 Server connected   
Channel#2:
>> OpenOK
declared exchange:  "myexchange" 
TRACE 2016-03-02T18:29:18.170 amqpmessageservice.cpp : 190 Declared : true  
TRACE 2016-03-02T18:29:18.170 amqpmessageservice.cpp : 210 Create queue  
Open channel #1
Connection: 
>> CLOSE
>> code: 504
>> text: CHANNEL_ERROR - second 'channel.open' seen
>> class-id: 20
>> method-id: 10
TRACE 2016-03-02T18:29:18.171 amqpmessageservice.cpp : 75 Connect connected :  false From: QAmqpClient(0x9a5e1b8)  
exchange  "station_master"  disconnected 
socket error:  "The remote host closed the connection" 
ERROR 2016-03-02T18:29:18.171 amqpmessageservice.cpp : 158 Client connect error QAbstractSocket::RemoteHostClosedError  
TRACE 2016-03-02T18:29:18.672 amqpmessageservice.cpp : 220 Queue declare timeout  
TRACE 2016-03-02T18:29:18.672 amqpmessageservice.cpp : 227 Queue declared  
TRACE 2016-03-02T18:29:19.172 amqpmessageservice.cpp : 234 Queue bind timeout  

Hostname resolution

I am having very good performance and results using raw IP addresses (10.10.10.100 192.168.1.100 etc.) but when I try to use a hostname instead, I cannot connect to the rabbit server (connectToHost myhostname:5672 -> socketError 2).

On the same machine, ping of myhostname works correctly... these are mostly ubuntu and raspbian machines I'm testing on, similar results on both.

Any ideas?

Thanks,

Messages getting mixed up

Hi,

Just been experimenting with QAMQP, so far it's been a very easy introduction to AMQP on C++. The project I'm using AMQP for is a crude SCADA system for energy management. So we've got energy meters being polled (Modbus or BACnet) with data being collected and reported via AMQP messaging.

For testing purposes, I've done a dummy driver. It uses the rand() function out of stdlib.h to generate dummy readings. Message content uses YAML format.

I have some existing code using Python (and the Pika library) to act as a client, and I'm implementing the server in C++ for speed. One thing I observe is that sometimes QAMQP seems to get messages mixed up, so one message's content gets appended to another unrelated message sent to the same queue. (In this case, both are for an energy meter named "B79_04_RT"; so sent to a fan-out exchange named "entity.rq.B79_04_RT".)

Example:

1427720769 DEBUG AMQPQueueHandler.amq.gen-b20F7zyH9m-osJS8uSLQuA : New message received.
1427720769 DEBUG MessageParser : Content-type is text/x-yaml; charset=utf-8
1427720769 DEBUG MessageParser : Message payload:
_debug_: {created: ! '2015-03-30 13:06:07.446333+00:00', sent: ! '2015-03-30 13:06:08.062488+00:00'}
attributes: {FREQ: ! '2015-03-30 14:06:07.439992+00:00', I_AVG_TOT: ! '2015-03-30
    14:06:07.445290+00:00', I_PH4: ! '2015-03-30 14:06:07.441783+00:00', I_PHA: &id001 ! '2015-03-30
    14:06:07.428174+00:00', I_PHB: ! '2015-03-30 14:06:07.429421+00:00', I_PHC: ! '2015-03-30
    14:06:07.428822+00:00', KVAH_EXP: ! '2015-03-30 14:06:07.434671+00:00', KVAH_IMP: ! '2015-03-30
    14:06:07.431752+00:00', KVARH_CAP_EXP: ! '2015-03-30 14:06:07.444093+00:00', KVARH_CAP_IMP: ! '2015-03-30
    14:06:07.441202+00:00', KVARH_IND_EXP: ! '2015-03-30 14:06:07.432938+00:00', KVARH_IND_IMP: ! '2015-03-30
    14:06:07.437034+00:00', KVAR_PHA: ! '2015-03-30 14:06:07.439386+00:00', KVAR_PHB: ! '2015-03-30
    14:06:07.438808+00:00', KVAR_PHC: ! '2015-03-30 14:06:07.438191+00:00', KVA_TOT: ! '2015-03-30
    14:06:07.442939+00:00', KWH_EXP: ! '2015-03-30 14:06:07.442358+00:00', KWH_IMP: ! '2015-03-30
    14:06:07.435252+00:00', KW_PHA: ! '2015-03-30 14:06:07.443510+00:00', KW_PHB: ! '2015-03-30
    14:06:07.444674+00:00', KW_PHC: ! '2015-03-30 14:06:07.431175+00:00', KW_TOT: ! '2015-03-30
    14:06:07.440578+00:00', MAX_DEMAND: ! '2015-03-30 14:06:07.430599+00:00', PF_PHA: ! '2015-03-30
    14:06:07.432328+00:00', PF_PHB: ! '2015-03-30 14:06:07.434090+00:00', PF_PHC: ! '2015-03-30
    14:06:07.433517+00:00', PF_TOT: ! '2015-03-30 14:06:07.430015+00:00', V_PHA: ! '2015-03-30
    14:06:07.437617+00:00', V_PHB: ! '2015-03-30 14:06:07.435830+00:00', V_PHC: ! '2015-03-30
    14:06:07.436406+00:00'}
deadline: *id001
entity: B79_04_RT
priority: 127
type: demand_add
_debug_: {created: ! '2015-03-30 13:06:09.075516+00:00', sent: ! '2015-03-30 13:06:09.493511+00:00'}
attributes: [FREQ, I_AVG_TOT, I_PH4, I_PHA, I_PHB, I_PHC, KVAH_EXP, KVAH_IMP, KVARH_CAP_EXP,
  KVARH_CAP_IMP, KVARH_IND_EXP, KVARH_IND_IMP, KVAR_PHA, KVAR_PHB, KVAR_PHC, KVA_TOT,
  KWH_EXP, KWH_IMP, KW_PHA, KW_PHB, KW_PHC, KW_TOT, MAX_DEMAND, PF_PHA, PF_PHB, PF_PHC,
  PF_TOT, V_PHA, V_PHB, V_PHC]
deadline: 2015-03-30 13:07:06.700407+00:00
entity: B79_04_RT
priority: 127
type: rt_read

1427720769 DEBUG MessageParser RtReadRequest: Bad incoming message: yaml-cpp: error at line 0, column 0: bad conversion
1427720769 DEBUG MessageParser : Message at 0x8e1d730 has type UNDEFINED???
1427720769 DEBUG AMQPQueueHandler.amq.gen-b20F7zyH9m-osJS8uSLQuA : Bad message received.  Dropped.

The code I'm using looks like this:

void AMQPQueueHandler::messageReceived()
{
    QAmqpMessage raw_msg(this->queue->dequeue());
    log4cpp::Category& log(this->getLog());
    log.debugStream()
        << "New message received.";
    try {
        BaseMessage* msg = BaseMessage::fromMessage(raw_msg);
        if (msg != NULL) {
            emit messageReceived(
                    this->ref(),
                    MessageRef(msg));
        } else {
            log.debugStream()
                << "Bad message received.  Dropped.";
        }
    } catch (std::exception& ex) {
        log.errorStream()
                << "Bad message received: "
                << ex.what();
    }
}

/* ... */

/* 
 * Note, the charset is given here as normally the default is ISO8859-1
 * for text/... types.  We use x-yaml because at time of writing YAML was
 * not an official MIME type.
 */
static const QString CONTENT_TYPE("text/x-yaml; charset=utf-8");
static const QString ALT_CONTENT_TYPE("application/x-yaml");

BaseMessage* BaseMessage::fromMessage(const QAmqpMessage& amqp_msg)
{
    log4cpp::Category& log = log4cpp::Category::getInstance(
            "MessageParser");
    try {
        /* Verify all the required fields are present. */
        if (!amqp_msg.hasProperty(QAmqpMessage::CorrelationId)) {
            log.debugStream()
                << "No correlation ID";
            return NULL;
        }
        if (!amqp_msg.hasProperty(QAmqpMessage::MessageId)) {
            log.debugStream()
                << "No message ID";
            return NULL;
        }
        if (!amqp_msg.hasProperty(QAmqpMessage::ContentType)) {
            log.debugStream()
                << "No content-type";
            return NULL;
        }

        /* Verify that the content type is correct */
        QString content_type(amqp_msg.property(
                    QAmqpMessage::ContentType).toString());
        log.debugStream() << "Content-type is " << content_type;
        if ((content_type != CONTENT_TYPE) &&
                (content_type != ALT_CONTENT_TYPE))
            return NULL;

        /* Decode the message body */
        log.debugStream() << "Message payload:\n---\n"
                << amqp_msg.payload().constData()
                << "\n---";
        YAML::Node body = YAML::Load(amqp_msg.payload().constData());
        if (!body.IsMap())
            return NULL;

        /* What message type do we have? */
        MessageType type = body["type"].as<MessageType>();
        BaseMessage* msg = NULL;
        switch(type) {
            /* Diagnostics */
            case MSG_TYPE_PING:
                msg = new PingRequest(amqp_msg, body);
                break;
            case MSG_TYPE_PING_RES:
                msg = new PingResponse(amqp_msg, body);
                break;
            /* Namespace queries */
            case MSG_TYPE_NS_SEARCH:
                msg = new NamespaceSearchRequest(amqp_msg, body);
                break;
            case MSG_TYPE_NS_SEARCH_RES:
                msg = new NamespaceSearchResponse(amqp_msg, body);
                break;
            /* Configuration */
            case MSG_TYPE_CFG_INFO:
                msg = new CfgInfoRequest(amqp_msg, body);
                break;
            case MSG_TYPE_CFG_COC:
                msg = new CfgChangeOfConfigMessage(amqp_msg, body);
                break;
            case MSG_TYPE_CFG_INFO_RES:
                msg = new CfgInfoResponse(amqp_msg, body);
                break;
            /* Demand management */
            case MSG_TYPE_DEMAND_ADD:
                msg = new DemandAddRequest(amqp_msg, body);
                break;
            case MSG_TYPE_DEMAND_RM:
                msg = new DemandRmRequest(amqp_msg, body);
                break;
            case MSG_TYPE_DEMAND_RES:
                msg = new DemandResponse(amqp_msg, body);
                break;
            /* Real-time */
            case MSG_TYPE_RT_READ:
                msg = new RtReadRequest(amqp_msg, body);
                break;
            case MSG_TYPE_RT_READ_RES:
                msg = new RtReadResponse(amqp_msg, body);
                break;
            case MSG_TYPE_RT_COS:
                msg = new RtChangeOfStateMessage(amqp_msg, body);
                break;
            case MSG_TYPE_RT_WRITE:
                msg = new RtWriteRequest(amqp_msg, body);
                break;
            case MSG_TYPE_RT_WRITE_RES:
                msg = new RtWriteResponse(amqp_msg, body);
                break;
            /* Historical: TODO */
            /* Events: TODO */
            default:
                log.debugStream()
                    << "Unhandled message type: "
                    << body["type"].as<QString>();
                return NULL;
        }
        log.debugStream()
            << "Message at "
            << (void*)msg
            << " has type "
            << msg->type;
        if (msg->type == MSG_TYPE_INVALID) {
            delete(msg);
            msg = NULL;
        }
        return msg;
    } catch (YAML::Exception& ex) {
        /* Conversion fails */
        log.debugStream()
            << "YAML error: "
            << ex.what();
        return NULL;
    }
}

Somehow, a real-time read request got mixed up with a demand-add request. It could be a timing issue, my application is single-threaded however RabbitMQ is running on a dual-core industrial computer. The Python code inter-operates with other Python scripts without issues, so far this problem has been unique to QAMQP.

I'll see if I can trace what's going on a little more closely

License type

I noticed the license changed at some point from MIT to the GNU GPL and was wondering about the history behind this. Thx.

Empty virtualhost deduced from uri

When uri without a path specified (usual usage) virtualHost variable (what was initialized with AMQP_VHOST define) become empty. This makes it necessary to add a slash to the end of uri.

Only src project compiles

Perhaps I'm doing this wrong, but when I try to compile the hello worl project, I get the following error:
16:34:09: Running steps for project qamqp...
16:34:09: Starting: "/usr/bin/make" clean
rm -f main.moc
rm -f main.o
rm -f _~ core .core
16:34:09: The process "/usr/bin/make" exited normally.
16:34:09: Starting: "/usr/lib/x86_64-linux-gnu/qt4/bin/qmake" /home/will/Rail/rail/qamqp/tutorials/helloworld/receive/receive.pro -r -spec linux-g++-64 CONFIG+=debug
16:34:09: The process "/usr/lib/x86_64-linux-gnu/qt4/bin/qmake" exited normally.
16:34:09: Starting: "/usr/bin/make"
/usr/lib/x86_64-linux-gnu/qt4/bin/moc -DQAMQP_SHARED -DQT_GUI_LIB -DQT_NETWORK_LIB -DQT_CORE_LIB -DQT_SHARED -I/usr/share/qt4/mkspecs/linux-g++-64 -I../../../../qamqp/tutorials/helloworld/receive -I/usr/include/qt4/QtCore -I/usr/include/qt4/QtNetwork -I/usr/include/qt4/QtGui -I/usr/include/qt4 -I../../../../qamqp/src -I. -I../../../../qamqp/tutorials/helloworld/receive -I. ../../../../qamqp/tutorials/helloworld/receive/main.cpp -o main.moc
g++ -c -m64 -pipe -g -Wall -W -D_REENTRANT -DQAMQP_SHARED -DQT_GUI_LIB -DQT_NETWORK_LIB -DQT_CORE_LIB -DQT_SHARED -I/usr/share/qt4/mkspecs/linux-g++-64 -I../../../../qamqp/tutorials/helloworld/receive -I/usr/include/qt4/QtCore -I/usr/include/qt4/QtNetwork -I/usr/include/qt4/QtGui -I/usr/include/qt4 -I../../../../qamqp/src -I. -I../../../../qamqp/tutorials/helloworld/receive -I. -o main.o ../../../../qamqp/tutorials/helloworld/receive/main.cpp
g++ -m64 -o receive main.o -L/usr/lib/x86_64-linux-gnu -L../../../src -lqamqpd -lQtGui -lQtNetwork -lQtCore -lpthread
_/usr/bin/ld: cannot find -lqamqpd

Makefile:103: recipe for target 'receive' failed
collect2: error: ld returned 1 exit status
make: *** [receive] Error 1
16:34:09: The process "/usr/bin/make" exited with code 2.
Error while building/deploying project qamqp (kit: Desktop)
When executing step "Make"
16:34:09: Elapsed time: 00:00.

When building the root project I see the following error:
g++ -c -m64 -pipe -g -Wall -W -D_REENTRANT -fPIC -DQAMQP_SHARED -DQAMQP_BUILD -DQT_NETWORK_LIB -DQT_CORE_LIB -DQT_SHARED -I/usr/share/qt4/mkspecs/linux-g++-64 -I../../qamqp/src -I/usr/include/qt4/QtCore -I/usr/include/qt4/QtNetwork -I/usr/include/qt4 -I../../qamqp/src -I. -I. -o moc_qamqpexchange.o moc_qamqpexchange.cpp
rm -f libqamqp.so.0.4.0 libqamqp.so libqamqp.so.0 libqamqp.so.0.4
g++ -m64 -shared -Wl,-soname,libqamqp.so.0 -o libqamqp.so.0.4.0 qamqpauthenticator.o qamqpchannel.o qamqpchannelhash.o qamqpclient.o qamqpexchange.o qamqpframe.o qamqpmessage.o qamqpqueue.o qamqptable.o moc_qamqpexchange.o -L/usr/lib/x86_64-linux-gnu -lQtNetwork -lQtCore -lpthread
ln -s libqamqp.so.0.4.0 libqamqp.so
ln -s libqamqp.so.0.4.0 libqamqp.so.0
ln -s libqamqp.so.0.4.0 libqamqp.so.0.4
{ test -z "" || cd ""; } && test $(gdb --version | sed -e 's,[^0-9][^0-9]([0-9]).([0-9]).,\1\2,;q') -gt 72 && gdb --nx --batch --quiet -ex 'set confirm off' -ex "save gdb-index ." -ex quit 'libqamqp.so.0.4.0' && test -f libqamqp.so.0.4.0.gdb-index && objcopy --add-section '.gdb_index=libqamqp.so.0.4.0.gdb-index' --set-section-flags '.gdb_index=readonly' 'libqamqp.so.0.4.0' 'libqamqp.so.0.4.0' && rm -f libqamqp.so.0.4.0.gdb-index || true
make[1]: Leaving directory '/home/will/Rail/rail/build-qamqp-Desktop-Debug/src'
cd tests/ && /usr/bin/make -f Makefile
make[1]: Entering directory '/home/will/Rail/rail/build-qamqp-Desktop-Debug/tests'
cd auto/ && /usr/bin/make -f Makefile
make[2]: Entering directory '/home/will/Rail/rail/build-qamqp-Desktop-Debug/tests/auto'
cd qamqpclient/ && /usr/bin/make -f Makefile
make[3]: Entering directory '/home/will/Rail/rail/build-qamqp-Desktop-Debug/tests/auto/qamqpclient'
/usr/lib/x86_64-linux-gnu/qt4/bin/moc -DQAMQP_SHARED -DQT_TESTLIB_LIB -DQT_NETWORK_LIB -DQT_CORE_LIB -DQT_SHARED -I/usr/share/qt4/mkspecs/linux-g++-64 -I../../../../qamqp/tests/auto/qamqpclient -I/usr/include/qt4/QtCore -I/usr/include/qt4/QtNetwork -I/usr/include/qt4/QtTest -I/usr/include/qt4 -I../../../../qamqp/src -I../../../../qamqp/tests/common -I/home/will/Rail/rail/build-qamqp-Desktop-Debug/tests/auto/qamqpclient/. -I../../../../qamqp/tests/auto/qamqpclient -I. ../../../../qamqp/tests/auto/qamqpclient/tst_qamqpclient.cpp -o tst_qamqpclient.moc
g++ -c -m64 -pipe -g -Wall -W -D_REENTRANT -DQAMQP_SHARED -DQT_TESTLIB_LIB -DQT_NETWORK_LIB -DQT_CORE_LIB -DQT_SHARED -I/usr/share/qt4/mkspecs/linux-g++-64 -I../../../../qamqp/tests/auto/qamqpclient -I/usr/include/qt4/QtCore -I/usr/include/qt4/QtNetwork -I/usr/include/qt4/QtTest -I/usr/include/qt4 -I../../../../qamqp/src -I../../../../qamqp/tests/common -I/home/will/Rail/rail/build-qamqp-Desktop-Debug/tests/auto/qamqpclient/. -I../../../../qamqp/tests/auto/qamqpclient -I. -o tst_qamqpclient.o ../../../../qamqp/tests/auto/qamqpclient/tst_qamqpclient.cpp
/usr/lib/x86_64-linux-gnu/qt4/bin/rcc -name certs ../../../../qamqp/tests/auto/qamqpclient/certs.qrc -o qrc_certs.cpp
g++ -c -m64 -pipe -g -Wall -W -D_REENTRANT -DQAMQP_SHARED -DQT_TESTLIB_LIB -DQT_NETWORK_LIB -DQT_CORE_LIB -DQT_SHARED -I/usr/share/qt4/mkspecs/linux-g++-64 -I../../../../qamqp/tests/auto/qamqpclient -I/usr/include/qt4/QtCore -I/usr/include/qt4/QtNetwork -I/usr/include/qt4/QtTest -I/usr/include/qt4 -I../../../../qamqp/src -I../../../../qamqp/tests/common -I/home/will/Rail/rail/build-qamqp-Desktop-Debug/tests/auto/qamqpclient/. -I../../../../qamqp/tests/auto/qamqpclient -I. -o qrc_certs.o qrc_certs.cpp
g++ -m64 -Wl,-rpath,/home/will/Rail/rail/build-qamqp-Desktop-Debug/src -o tst_qamqpclient tst_qamqpclient.o qrc_certs.o -L/usr/lib/x86_64-linux-gnu -L../../../src -lqamqpd -lQtTest -lQtNetwork -lQtCore -lpthread
/usr/bin/ld: cannot find -lqamqpd

The src project seems to compile fine and does not complain.

Is there some issue with my setup or some dependency I am missing? Given all the projects and sub-projects contained here, a simple quick start guide would go a long way.

Create queues with limit length

Hi,
Can I configure a queue with limit of length ?
I need to create a queue with last value caching and max number of message equal to 1.
I set this policy with a command on rabbitmq server, without effects....
My problem is a queue grow up more than 1 message.
Example
1 sender publish a message per second
1 receiver receive a message one per second, but if I break it on a breakpoint then i resume from break, It will receive all message buffered on queue, what I want is receive only last value sent.

I think RabbitMQ server holds ready_messages =0
So doesn't purge oldest messages from queue, saving only last message sent.

Thanks,.

Problems with the worker example

Hi, I was testing the worker example and had some problems.

The official worker tutorial says:

Message acknowledgments are turned on by default. In previous examples we explicitly turned them off via the no_ack=True flag. It's time to remove this flag and send a proper acknowledgment from the worker, once we're done with a task.

However, I found that
m_queue->consume();
sends no ack. The rabbitmq's management interface counts "unacked" up for every received message.

Debug output:

connecting to host: "localhost" , port: 5672
-> connection#start( version_major=0, version_minor=9, mechanisms=(AMQPLAIN,PLAIN), locales=en_US )
<- connection#startOk()
-> connection#tune( channel_max=0, frame_max=131072, heartbeat=60 )
<- connection#tuneOk( channelMax=0, frameMax=131072, heartbeatDelay=60 )
<- connection#open( virtualHost=/, reserved-1=0, reserved-2=0 )
-> connection#openOk()
<- channel#open( channel=1, name= )
-> channel#openOk( channel=1, name=master )
<- queue#declare( queue=master, passive=0, durable=2, exclusive=0, auto-delete=8, no-wait=0 )
-> queue#declareOk( queue-name=master, message-count=0, consumer-count=0 )
<- basic#consume( queue=master, consumer-tag=, no-local=0, no-ack=0, exclusive=0, no-wait=0 )
-> queue[ master ]#consumeOk( consumer-tag=amq.ctag-M6Fw2YzXarbbo0EsUBdgUQ )
void QAmqpQueuePrivate::deliver(const QAmqpMethodFrame&)
[x] Received "..."
[x] Done
void QAmqpQueuePrivate::deliver(const QAmqpMethodFrame&)
[x] Received "..."
[x] Done
void QAmqpQueuePrivate::deliver(const QAmqpMethodFrame&)
[x] Received "..."
[x] Done

Is this the wanted behavior?

Additionally, I would recommend to use QThread::sleep instead of QTimer::singleShot, since sending a second "task" to the worker before the first one is finished, results in acking the second one two times.

void QAmqpQueuePrivate::deliver(const QAmqpMethodFrame&)
[x] Received "..."
void QAmqpQueuePrivate::deliver(const QAmqpMethodFrame&)
[x] Received "..."
[x] Done
<- basic#ack( delivery-tag=2, multiple=0 )
[x] Done
<- basic#ack( delivery-tag=2, multiple=0 )
-> channel#close( channel=1, name=master, reply-code=406, reply-text=PRECONDITION_FAILED - unknown delivery tag 2, class-id=60, method-id=80, )

Add API documentation

I just found this project. The code looks in great shape but is sorely lacking API documentation.

Can not connect to server with URI of default vhost

AMQP server usually provides a default vhost, which is "/", if i use a uri with default vhost, such as
QString uri = "amqp://guest:[email protected]:5672/";
then the client could not connect to the server, so this code in parseConnectionString() may has some problems
if (vhost.startsWith("/")) vhost = vhost.mid(1);

The timestamp in AMQP 0-9-1 should be in seconds other than miliseconds

As per the spec of AMQP 0-9-1, the timestamp value should be 👍

4.2.5.4 Timestamps
Time stamps are held in the 64-bit POSIX time_t format with an accuracy of one second. By using 64 bits
we avoid future wraparound issues associated with 31-bit and 32-bit time_t values.

So the qampqframe.cpp should be changed like:

--- a/src/qamqpframe.cpp
+++ b/src/qamqpframe.cpp
@@ -202,7 +202,7 @@ QVariant QAmqpFrame::readAmqpField(QDataStream &s, QAmqpMetaType::ValueType type
{
qulonglong tmp_value;
s >> tmp_value;

  •    return QDateTime::fromMSecsSinceEpoch(tmp_value);
    
  •    return QDateTime::fromTime_t(tmp_value);
    
    }
    case QAmqpMetaType::Hash:
    {
    @@ -256,7 +256,7 @@ void QAmqpFrame::writeAmqpField(QDataStream &s, QAmqpMetaType::ValueType type, c
    }
    break;
    case QAmqpMetaType::Timestamp:
  •    s << qulonglong(value.toDateTime().toMSecsSinceEpoch());
    
  •    s << qulonglong(value.toDateTime().toTime_t());
     break;
    

I'd like to push the commit ,but I got no permission error.

Could you add me the permission , or fix that yourself ?

Thanks,
John Zhang

waitForBytesWritten(1000) causing "Network operation timeout"

waitForBytesWritten(1000) on line:
https://github.com/mbroadst/qamqp/blob/master/src/qamqpframe.cpp#L61

Why are you using a so small timeout? (The default is 30000 ;-))

Well, I suppose that my application is really busy. But anyway, I'm trying to upgrading my app from fuCtor/QAMQP and I was wondering what was your reason for adding this?
As soon as I increase it, it's ok.
But as soon as I reduce it, I'm having problem more often.
And I checked with wireshark for the time between my exchange declaration and the error. It was always matching the time I set on this line.

(I know, I could simply add an option for that in a fork, but I'm curious :-))

Build on Windows

I got a problem for building this project on windows.
for now, the project is built well on windows, but it gets heap broken when run.
and i find it may be caused by the buffer in the client
buffer.resize(readSize);

then i compare this project to fuCtor 's implement, and i get that he initials the buffer's size in constructor like this
buffer_.reserve(Frame::HEADER_SIZE);

and i add the initial code to this project, and rebuild and run. there are no heap broken any more.

Connection suddenly closes with segfault

Sometimes during data sending amqp connection closes with error:
segfault at c4 ip 00007f6942cb3c9d sp 00007fff0094db18 error 6 in libqamqp.so.0.3.0[7f6942ca0000+3c000]

any advices?

Build under latest Ubuntu/Qt has a problem with include_next <stdlib.h>

In a fresh Ubuntu 18.04 install with a fresh Qt Creator & Qt 5.11.1 install (with apt-get install build-essential), when building qamqp, when rabbitinterface.h includes , it drills down to /usr/include/c++/7/bits/stl_algo.h which includes and in there, there is an #include_next <stdlib.h> which fails.

This is a fairly common problem described around the internet, unfortunately the qamqp code is encountering it in the latest system configurations... there are a number of suggested ways to get around it, mostly having to do with -isystem and /usr/include... something in the qamqp.pro file might be able to address it.

Qt 6 support

Hi!
Will be Qt 6 support?
Now it has compile errors.

Needs
#include QIODevice
and problem with QHash::unite()

Thanks!

Mass-publishing messages results in QAbstractSocket::UnconnectedState

Hi,
I'm emitting a lot of JSON messages from a thread (QAmqpClient runs in the main thread). About 100k-500k in less than 10s. The first 50k messages are correctly received by the exchange. After that the connection to RabbitMQ gets disconnected:

[11:33:31] unknown: void QAmqpClientPrivate::sendFrame(const QAmqpFrame&) socket not connected: QAbstractSocket::UnconnectedState

Disabling confirms delays the problem a little bit, and waitForConfirms + enabling confirms results in a segmentation fault (Have not analyzed this further).

This is my class which publishes the messages: https://gist.github.com/J4nsen/40da0d436e4f8e00dbdbb5cc0860b20f

What is the best way to fix this problem? Implement a local queue/rate limiter and give the main thread some time for its main event loop? Or should I investigate in segfault with waitForConfirms?

Thanks,
Jan

Message rejecting doesn't work

Hi,
I have a problem with message rejecting.
The following method
void QAmqpQueue::reject(const QAmqpMessage &message, bool requeue)
doesn't work due to calling inside it wrong method.
Current source code is:

void QAmqpQueue::reject(const QAmqpMessage &message, bool requeue)
{
    ack(message.deliveryTag(), requeue);
}

Have to be:

void QAmqpQueue::reject(const QAmqpMessage &message, bool requeue)
{
    reject(message.deliveryTag(), requeue);
}

Method
void QAmqpQueue::reject(qlonglong deliveryTag, bool requeue) nevertheless works fine.

Thanks,
Igor

Add texts for the tutorials

The tutorials are currently just code samples. It would be great if there was some explaining text for each tutorial, perhaps as GitHub wiki pages?

QSocketNotifier Warning when running from a QThread

I set up my application to spawn a new QThread to process data (convert to a JSON format) and then send to another application using a FanOut exchange. The new thread creates the exchange and processes data in the run method.

The message is being sent out (received by the other program) but I receive the following error-
QSocketNotifier: Socket notifiers cannot be enabled or disabled from another thread

I don't receive the error if the QDialog class creates the exchange directly and processes the data.

Tagged Versions

Could the community get some tagged revisions? It would be far cleaner if we could do packaging based on github tarball downloads or git tags vs revision locks.

Unable to get the default exchange

I am trying to use the qamqp with RabbitMQ broker to establish communication between two applications - let's call them a client (Windows UWP app ) and a server (Qt app). So far I am able to pass a message from the client to the server using a default exchange and the server successfully gets the message.

incomingQueue = amqpClient->createQueue(this->incomingQueueName);
connect(incomingQueue, SIGNAL(declared()), this, SLOT(handleIncomingQueueDeclared()));
connect(incomingQueue, SIGNAL(qosDefined()), this, SLOT(handleIncomingQosDefined()));
connect(incomingQueue, SIGNAL(messageReceived()), this, SLOT(handleIncomingMessageReceived()));
incomingQueue->declare(QAmqpQueue::Durable);

However when I try to send a message from the server to the client, I am unable to get the default exchange:

amqpExchange = amqpClient->createExchange(""); // also tried createExchange() and createExchange("amq.default")
connect(amqpExchange, SIGNAL(declared()), this, SLOT(handleExchangeDeclared()));
connect(amqpExchange, SIGNAL(error(QAMQP::Error)), this, SLOT(handleQAMQPError(QAMQP::Error)));
amqpExchange->declare();

I am sending a message with:

QString message = "Hello world";
amqpExchange->publish(message, this->outgoingQueueName);

So not handleExchangeDeclared nor handleQAMQPError get triggered, however if I try to create an exchange with any other name like "new_queue" everything works as expected and messages are sent. I could just create a new exchange and call it good, however the client uses AMQP 1.0 and exchange is no longer a thing, the client uses the following code to listen for incoming messages:

this._amqpReceiver = new ReceiverLink(_amqpSession, "receiver-link", "q1");
_amqpReceiver.Start(10, async (r, m) =>
{
// I do stuff here
});

The RabbitMQ management portal says that both client and server are connected to the broker, both queues are created. However no messages come from the server. Am I doing something wrong here?

Binding a queue to a headers exchange

I'm trying to bind a queue to a headers exchange with the bind matching on multiple headers and I'm a little bit stuck and wondering how to do this.

I've noticed that drivers in other languages have the queue bind function taking a third argument for arguments that can specify the headers to match on (e.g., http://stackoverflow.com/questions/19240290/how-do-i-implement-headers-exchange-in-rabbitmq-using-java) , but I don't see an equivalent in QAmqpQueue.

Is this something that's not currently implemented or I am just not understanding how I'm supposed to bind a queue to a headers exchange?

Thanks!

QAbstractSocket::SocketError catching (a question)

Is there currently a valid way to know that something like a SocketError happened while an attempt to establish connection with remote server. For example I'm trying to establish a connection from my program and calling something like m_client.connectToHost(ConnectionString) and I have a mistake in ConnectionString or remote amqp server is down, is there any way to have immediate feedback about that?
Thanks!

ChannelNumber doesn't work

Exchanges and Queues are build with default parameter channelNumber = -1 , so it is incremented each new instance of it.
If I try to set a fixed channelNumber, i.e. 1, this setting doesn't work as well:

No connection is established!

Do not use multiple threads

When I connect using the main thread and send using another thread, there are frequent frame error logs, but after merging the threads, the error disappears

Reconnection after server restart

Hi,
thanks for your work!
we are trying to make stronger our application if RabbitMQ server restart . No problem with the producers, when connect signal is emitted the exchages are redeclared and we can use publish methods successfully.
We have some problem with the queues: after reconnection, the queue object already created throws this error:

bool QAmqpQueue::consume(int) already consuming with tag: "amq.ctag-KAOuGyz97ePJf9ovxBfSLQ"

What is the correct approach? We must destroy and recreate the objects QAmqpClient, QAmqpQueue, QAmqpExchange or we can reuse them?

QAmqpClient not work with QtService

Now QAmqpClient not work with QtService.
For connect I use code from tutorials/helloworld/receive:

  1. Single run tutorials receive worked
  2. Same code in my project with QtService not worked (in debug write connecting to host: ... and nothing happens)

Please, help me

Trying to publish a message inside the 'connected' signal fails on reconnect.

If I create a simple sender that just publishes a message on 'connected' it fails during re-connecting. Just take a look at the following sample (a small modification of the sender-example):

Sender::Sender(QObject *parent) : QObject(parent) {
    m_client.setAutoReconnect(true);
}

void Sender::start() {
        connect(&m_client, SIGNAL(connected()), this, SLOT(clientConnected()));
        m_client.connectToHost();
}

void Sender::clientConnected() {
    QAmqpExchange *defaultExchange = m_client.createExchange("test");
    defaultExchange->publish("Hello World!", "test");
    qDebug() << " [x] Sent 'Hello World!'";
}

If I now restart the broker the client tries to publish before a channel is established:

...
trying to reconnect after: 5000 ms
connecting to host: "localhost" , port: 5672
-> connection#start( version_major=0, version_minor=9, mechanisms=(PLAIN,AMQPLAIN), locales=en_US
<- connection#startOk()
-> connection#tune( channel_max=0, frame_max=131072, heartbeat=60 )
<- connection#tuneOk( channelMax=0, frameMax=131072, heartbeatDelay=60
<- connection#open( virtualHost=/, reserved-1=0, reserved-2=0
-> connection#openOk()
<- basic#publish( exchange=test, routing-key=test, mandatory=0, immediate=0 )
[x] Sent 'Hello World!'
<- channel#open( channel=1 )
-> connection#close( reply-code=504, reply-text=CHANNEL_ERROR - expected 'channel.open', class-id=60, method-id:40 )
exchange disconnected: "test"
<- connection#closeOk()
socket error: "The remote host closed the connection"
exchange disconnected: "test"
trying to reconnect after: 1000 ms
connecting to host: "localhost" , port: 5672
-> connection#start( version_major=0, version_minor=9, mechanisms=(PLAIN,AMQPLAIN), locales=en_US
<- connection#startOk()
-> connection#tune( channel_max=0, frame_max=131072, heartbeat=60 )
<- connection#tuneOk( channelMax=0, frameMax=131072, heartbeatDelay=60
<- connection#open( virtualHost=/, reserved-1=0, reserved-2=0
-> connection#openOk()
<- basic#publish( exchange=test, routing-key=test, mandatory=0, immediate=0 )
[x] Sent 'Hello World!'
<- channel#open( channel=1 )
-> connection#close( reply-code=504, reply-text=CHANNEL_ERROR - expected 'channel.open', class-id=60, method-id:40 )
exchange disconnected: "test"
<- connection#closeOk()
socket error: "The remote host closed the connection"
exchange disconnected: "test"
trying to reconnect after: 1000 ms
...

For me it seems like this is a bug, but maybe I'm just making wrong assumptions about the API of qampq. If I was unclear or if you need more information just let me know. Thanks in advance for any assistance or any hint how to work around this problem.

what is the qamqpd lib file? The document coming soon!

Environment


  • Qt-Version: 5.12.8
  • OS: Ubuntu 20.04LTS

Problem


Follow these steps:

  1. Step 1: git clone https://github.com/mbroadst/qamqp.git
  2. Step 2: open qamqp.pro by Qt Creator
  3. Step 3: compile the src project
  4. Step 4: compile the hello world project
  5. these Errors
can not find -lqamqpd
collect2: error: ld returned 1 exit status

**So, where is the qamqpd lib file? **

At Last

  • if the repo is archived,please the world shutdown now

Tests currently fail on Debian Wheezy

Hi,

I'm just in the process of packaging on qamqp on Debian Wheezy (I'll make the debs available shortly) but stumbled on this issue:

make[4]: Entering directory `/tmp/qamqp-0.3.0/tests/auto/qamqpclient'
./tst_qamqpclient 
********* Start testing of tst_QAMQPClient *********
Config: Using QTest library 4.8.2, Qt 4.8.2
PASS   : tst_QAMQPClient::initTestCase()
FAIL!  : tst_QAMQPClient::connect() 'waitForSignal(&client, SIGNAL(connected()))' returned FALSE. ()
   Loc: [tst_qamqpclient.cpp(56)]
FAIL!  : tst_QAMQPClient::connectProperties() 'waitForSignal(&client, SIGNAL(connected()))' returned FALSE. ()
   Loc: [tst_qamqpclient.cpp(90)]
FAIL!  : tst_QAMQPClient::connectHostAddress() 'waitForSignal(&client, SIGNAL(connected()))' returned FALSE. ()
   Loc: [tst_qamqpclient.cpp(99)]
FAIL!  : tst_QAMQPClient::connectDisconnect() 'waitForSignal(&client, SIGNAL(connected()))' returned FALSE. ()
   Loc: [tst_qamqpclient.cpp(108)]
FAIL!  : tst_QAMQPClient::invalidAuthenticationMechanism() 'waitForSignal(&client, SIGNAL(disconnected()))' returned FALSE. ()
   Loc: [tst_qamqpclient.cpp(127)]
FAIL!  : tst_QAMQPClient::tune() 'waitForSignal(&client, SIGNAL(connected()))' returned FALSE. ()
   Loc: [tst_qamqpclient.cpp(153)]
PASS   : tst_QAMQPClient::socketError()
FAIL!  : tst_QAMQPClient::validateUri(empty) Compared values are not the same
   Actual (auth->login()): guest
   Expected (expectedUsername): 
   Loc: [tst_qamqpclient.cpp(218)]
FAIL!  : tst_QAMQPClient::validateUri(onlyuser) Compared values are not the same
   Actual (auth->login()): guest
   Expected (expectedUsername): user
   Loc: [tst_qamqpclient.cpp(218)]
FAIL!  : tst_QAMQPClient::validateUri(userpass) Compared values are not the same
   Actual (auth->login()): guest
   Expected (expectedUsername): user
   Loc: [tst_qamqpclient.cpp(218)]
FAIL!  : tst_QAMQPClient::validateUri(onlyport) Compared values are not the same
   Actual (auth->login()): guest
   Expected (expectedUsername): 
   Loc: [tst_qamqpclient.cpp(218)]
PASS   : tst_QAMQPClient::cleanupTestCase()
Totals: 3 passed, 10 failed, 0 skipped
********* Finished testing of tst_QAMQPClient *********

Current build host:

  • gcc 4.7.2-5
  • Qt 4.8.2+dfsg11
  • Architecture: i386 (LXC container on Gentoo host)

Binary messages

I'm considering using qamqp to deliver binary messages via a RabbitMQ server, but I am having difficulty with the null character, as well as any character > 0x7f.

I took one of the tutorials and replaced the message with a QByteArray filled with byte values 0-255 for testing... as long as the values range 1-127, it works as expected.

As I understand the AMQP protocol, any binary message should be valid, but I do notice many client implementations (Java, PHP, etc.) with trouble tickets concerning binary messages, especially NULL values causing trouble.

Is this a known problem in qamqp?

Is this project DEAD ?

Hi,

We would like to use your code. But there is not much activity ? Do you suggest to go or we should look another solution ?

Additionally how we can include newtask example in our code ? I saw there is -lqamqpd in config ?

Do we need to compile ? Or just include the headers is enough ?

Thx

What license is this released under?

I'm a little confused by what license this is actually released under. Your LICENSE file has the following:

QAMQP is Copyright (C) 2014-2015 Matt Broadstone

You may use, distribute and copy QJsonRpc under the terms of
GNU Library General Public License version 2, which is displayed below.

Should that QJsonRpc say QAMQP, or is there a QJsonRpc component that we could use if we wanted to that's GPL'ed? Also, your source files have:

[...] you can redistribute it and/or

  • modify it under the terms of the GNU Lesser General Public
  • License as published by the Free Software Foundation; either
  • version 2.1 of the License, or (at your option) any later version.

So it's a bit unclear as to what license applies here.

Thanks!

Skip test installation

How can I skip test installation? Tests do not follow PREFIX variable as library does and they always end up in "/usr". Shouldn't test be just run and not installed?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.