From 9a849b5b9d86389e5f0213fc3c7c5ef8cd0f6081 Mon Sep 17 00:00:00 2001 From: jessequ Date: Mon, 25 Aug 2025 21:10:21 +0800 Subject: [PATCH] first commit --- .drone.yml | 20 + .gitignore | 105 ++ CMakeLists.txt | 74 ++ README.md | 4 + main.cpp | 81 ++ qamqp/CMakeLists.txt | 90 ++ qamqp/cmake/qamqpConfig.cmake.in | 8 + qamqp/src/CMakeLists.txt | 95 ++ qamqp/src/qamqpauthenticator.cpp | 47 + qamqp/src/qamqpauthenticator.h | 55 + qamqp/src/qamqpchannel.cpp | 368 +++++++ qamqp/src/qamqpchannel.h | 83 ++ qamqp/src/qamqpchannel_p.h | 84 ++ qamqp/src/qamqpchannelhash.cpp | 113 +++ qamqp/src/qamqpchannelhash_p.h | 98 ++ qamqp/src/qamqpclient.cpp | 959 ++++++++++++++++++ qamqp/src/qamqpclient.h | 146 +++ qamqp/src/qamqpclient_p.h | 120 +++ qamqp/src/qamqpexchange.cpp | 369 +++++++ qamqp/src/qamqpexchange.h | 123 +++ qamqp/src/qamqpexchange_p.h | 45 + qamqp/src/qamqpframe.cpp | 486 +++++++++ qamqp/src/qamqpframe_p.h | 171 ++++ qamqp/src/qamqpglobal.h | 134 +++ qamqp/src/qamqpmessage.cpp | 124 +++ qamqp/src/qamqpmessage.h | 89 ++ qamqp/src/qamqpmessage_p.h | 26 + qamqp/src/qamqpqueue.cpp | 667 ++++++++++++ qamqp/src/qamqpqueue.h | 127 +++ qamqp/src/qamqpqueue_p.h | 64 ++ qamqp/src/qamqptable.cpp | 371 +++++++ qamqp/src/qamqptable.h | 43 + qamqp/tests/auto/auto.pro | 6 + .../tests/auto/qamqpchannel/qamqpchannel.pro | 6 + .../auto/qamqpchannel/tst_qamqpchannel.cpp | 97 ++ qamqp/tests/auto/qamqpclient/certs.qrc | 7 + qamqp/tests/auto/qamqpclient/qamqpclient.pro | 7 + .../auto/qamqpclient/tst_qamqpclient.cpp | 331 ++++++ .../auto/qamqpexchange/qamqpexchange.pro | 6 + .../auto/qamqpexchange/tst_qamqpexchange.cpp | 246 +++++ qamqp/tests/auto/qamqpqueue/qamqpqueue.pro | 6 + .../tests/auto/qamqpqueue/tst_qamqpqueue.cpp | 708 +++++++++++++ qamqp/tests/common/qamqptestcase.h | 50 + qamqp/tests/common/signalspy.h | 20 + qamqp/tests/files/certs/client/cert.pem | 18 + qamqp/tests/files/certs/client/key.pem | 27 + qamqp/tests/files/certs/client/keycert.p12 | Bin 0 -> 2349 bytes qamqp/tests/files/certs/client/req.pem | 16 + qamqp/tests/files/certs/server/cert.pem | 18 + qamqp/tests/files/certs/server/key.pem | 27 + qamqp/tests/files/certs/server/keycert.p12 | Bin 0 -> 2349 bytes qamqp/tests/files/certs/server/req.pem | 16 + qamqp/tests/files/certs/testca/cacert.cer | Bin 0 -> 714 bytes qamqp/tests/files/certs/testca/cacert.pem | 17 + qamqp/tests/files/certs/testca/certs/01.pem | 18 + qamqp/tests/files/certs/testca/certs/02.pem | 18 + qamqp/tests/files/certs/testca/index.txt | 2 + qamqp/tests/files/certs/testca/index.txt.attr | 1 + .../files/certs/testca/index.txt.attr.old | 1 + qamqp/tests/files/certs/testca/index.txt.old | 1 + qamqp/tests/files/certs/testca/openssl.cnf | 53 + .../files/certs/testca/private/cakey.pem | 28 + qamqp/tests/files/certs/testca/serial | 1 + qamqp/tests/files/certs/testca/serial.old | 1 + qamqp/tests/files/travis/rabbitmq-setup.sh | 14 + qamqp/tests/files/travis/test-deps.sh | 10 + qamqp/tests/gen-coverage.sh | 4 + qamqp/tests/tests.pri | 17 + qamqp/tests/tests.pro | 3 + qamqp/tutorials/helloworld/helloworld.pro | 4 + qamqp/tutorials/helloworld/receive/main.cpp | 81 ++ .../tutorials/helloworld/receive/receive.pro | 9 + qamqp/tutorials/helloworld/send/main.cpp | 60 ++ qamqp/tutorials/helloworld/send/send.pro | 9 + qamqp/tutorials/pubsub/emit_log/emit_log.pro | 9 + qamqp/tutorials/pubsub/emit_log/main.cpp | 57 ++ qamqp/tutorials/pubsub/pubsub.pro | 4 + qamqp/tutorials/pubsub/receive_logs/main.cpp | 75 ++ .../pubsub/receive_logs/receive_logs.pro | 9 + .../emit_log_direct/emit_log_direct.pro | 9 + .../routing/emit_log_direct/main.cpp | 64 ++ .../routing/receive_logs_direct/main.cpp | 78 ++ .../receive_logs_direct.pro | 9 + qamqp/tutorials/routing/routing.pro | 4 + qamqp/tutorials/rpc/rpc.pro | 4 + .../rpc/rpc_client/fibonaccirpcclient.cpp | 72 ++ .../rpc/rpc_client/fibonaccirpcclient.h | 36 + qamqp/tutorials/rpc/rpc_client/main.cpp | 13 + qamqp/tutorials/rpc/rpc_client/rpc_client.pro | 13 + qamqp/tutorials/rpc/rpc_server/main.cpp | 10 + qamqp/tutorials/rpc/rpc_server/rpc_server.pro | 13 + qamqp/tutorials/rpc/rpc_server/server.cpp | 71 ++ qamqp/tutorials/rpc/rpc_server/server.h | 33 + .../topics/emit_log_topic/emit_log_topic.pro | 9 + .../tutorials/topics/emit_log_topic/main.cpp | 64 ++ .../topics/receive_logs_topic/main.cpp | 83 ++ .../receive_logs_topic/receive_logs_topic.pro | 9 + qamqp/tutorials/topics/topics.pro | 4 + qamqp/tutorials/tutorials.pro | 8 + qamqp/tutorials/workqueues/new_task/main.cpp | 62 ++ .../workqueues/new_task/new_task.pro | 9 + qamqp/tutorials/workqueues/worker/main.cpp | 62 ++ qamqp/tutorials/workqueues/worker/worker.pro | 9 + qamqp/tutorials/workqueues/workqueues.pro | 4 + send/CMakeLists.txt | 37 + send/main.cpp | 60 ++ 106 files changed, 8356 insertions(+) create mode 100644 .drone.yml create mode 100644 .gitignore create mode 100644 CMakeLists.txt create mode 100644 README.md create mode 100644 main.cpp create mode 100644 qamqp/CMakeLists.txt create mode 100644 qamqp/cmake/qamqpConfig.cmake.in create mode 100644 qamqp/src/CMakeLists.txt create mode 100644 qamqp/src/qamqpauthenticator.cpp create mode 100644 qamqp/src/qamqpauthenticator.h create mode 100644 qamqp/src/qamqpchannel.cpp create mode 100644 qamqp/src/qamqpchannel.h create mode 100644 qamqp/src/qamqpchannel_p.h create mode 100644 qamqp/src/qamqpchannelhash.cpp create mode 100644 qamqp/src/qamqpchannelhash_p.h create mode 100644 qamqp/src/qamqpclient.cpp create mode 100644 qamqp/src/qamqpclient.h create mode 100644 qamqp/src/qamqpclient_p.h create mode 100644 qamqp/src/qamqpexchange.cpp create mode 100644 qamqp/src/qamqpexchange.h create mode 100644 qamqp/src/qamqpexchange_p.h create mode 100644 qamqp/src/qamqpframe.cpp create mode 100644 qamqp/src/qamqpframe_p.h create mode 100644 qamqp/src/qamqpglobal.h create mode 100644 qamqp/src/qamqpmessage.cpp create mode 100644 qamqp/src/qamqpmessage.h create mode 100644 qamqp/src/qamqpmessage_p.h create mode 100644 qamqp/src/qamqpqueue.cpp create mode 100644 qamqp/src/qamqpqueue.h create mode 100644 qamqp/src/qamqpqueue_p.h create mode 100644 qamqp/src/qamqptable.cpp create mode 100644 qamqp/src/qamqptable.h create mode 100644 qamqp/tests/auto/auto.pro create mode 100644 qamqp/tests/auto/qamqpchannel/qamqpchannel.pro create mode 100644 qamqp/tests/auto/qamqpchannel/tst_qamqpchannel.cpp create mode 100644 qamqp/tests/auto/qamqpclient/certs.qrc create mode 100644 qamqp/tests/auto/qamqpclient/qamqpclient.pro create mode 100644 qamqp/tests/auto/qamqpclient/tst_qamqpclient.cpp create mode 100644 qamqp/tests/auto/qamqpexchange/qamqpexchange.pro create mode 100644 qamqp/tests/auto/qamqpexchange/tst_qamqpexchange.cpp create mode 100644 qamqp/tests/auto/qamqpqueue/qamqpqueue.pro create mode 100644 qamqp/tests/auto/qamqpqueue/tst_qamqpqueue.cpp create mode 100644 qamqp/tests/common/qamqptestcase.h create mode 100644 qamqp/tests/common/signalspy.h create mode 100644 qamqp/tests/files/certs/client/cert.pem create mode 100644 qamqp/tests/files/certs/client/key.pem create mode 100644 qamqp/tests/files/certs/client/keycert.p12 create mode 100644 qamqp/tests/files/certs/client/req.pem create mode 100644 qamqp/tests/files/certs/server/cert.pem create mode 100644 qamqp/tests/files/certs/server/key.pem create mode 100644 qamqp/tests/files/certs/server/keycert.p12 create mode 100644 qamqp/tests/files/certs/server/req.pem create mode 100644 qamqp/tests/files/certs/testca/cacert.cer create mode 100644 qamqp/tests/files/certs/testca/cacert.pem create mode 100644 qamqp/tests/files/certs/testca/certs/01.pem create mode 100644 qamqp/tests/files/certs/testca/certs/02.pem create mode 100644 qamqp/tests/files/certs/testca/index.txt create mode 100644 qamqp/tests/files/certs/testca/index.txt.attr create mode 100644 qamqp/tests/files/certs/testca/index.txt.attr.old create mode 100644 qamqp/tests/files/certs/testca/index.txt.old create mode 100644 qamqp/tests/files/certs/testca/openssl.cnf create mode 100644 qamqp/tests/files/certs/testca/private/cakey.pem create mode 100644 qamqp/tests/files/certs/testca/serial create mode 100644 qamqp/tests/files/certs/testca/serial.old create mode 100755 qamqp/tests/files/travis/rabbitmq-setup.sh create mode 100755 qamqp/tests/files/travis/test-deps.sh create mode 100755 qamqp/tests/gen-coverage.sh create mode 100644 qamqp/tests/tests.pri create mode 100644 qamqp/tests/tests.pro create mode 100644 qamqp/tutorials/helloworld/helloworld.pro create mode 100644 qamqp/tutorials/helloworld/receive/main.cpp create mode 100644 qamqp/tutorials/helloworld/receive/receive.pro create mode 100644 qamqp/tutorials/helloworld/send/main.cpp create mode 100644 qamqp/tutorials/helloworld/send/send.pro create mode 100644 qamqp/tutorials/pubsub/emit_log/emit_log.pro create mode 100644 qamqp/tutorials/pubsub/emit_log/main.cpp create mode 100644 qamqp/tutorials/pubsub/pubsub.pro create mode 100644 qamqp/tutorials/pubsub/receive_logs/main.cpp create mode 100644 qamqp/tutorials/pubsub/receive_logs/receive_logs.pro create mode 100644 qamqp/tutorials/routing/emit_log_direct/emit_log_direct.pro create mode 100644 qamqp/tutorials/routing/emit_log_direct/main.cpp create mode 100644 qamqp/tutorials/routing/receive_logs_direct/main.cpp create mode 100644 qamqp/tutorials/routing/receive_logs_direct/receive_logs_direct.pro create mode 100644 qamqp/tutorials/routing/routing.pro create mode 100644 qamqp/tutorials/rpc/rpc.pro create mode 100644 qamqp/tutorials/rpc/rpc_client/fibonaccirpcclient.cpp create mode 100644 qamqp/tutorials/rpc/rpc_client/fibonaccirpcclient.h create mode 100644 qamqp/tutorials/rpc/rpc_client/main.cpp create mode 100644 qamqp/tutorials/rpc/rpc_client/rpc_client.pro create mode 100644 qamqp/tutorials/rpc/rpc_server/main.cpp create mode 100644 qamqp/tutorials/rpc/rpc_server/rpc_server.pro create mode 100644 qamqp/tutorials/rpc/rpc_server/server.cpp create mode 100644 qamqp/tutorials/rpc/rpc_server/server.h create mode 100644 qamqp/tutorials/topics/emit_log_topic/emit_log_topic.pro create mode 100644 qamqp/tutorials/topics/emit_log_topic/main.cpp create mode 100644 qamqp/tutorials/topics/receive_logs_topic/main.cpp create mode 100644 qamqp/tutorials/topics/receive_logs_topic/receive_logs_topic.pro create mode 100644 qamqp/tutorials/topics/topics.pro create mode 100644 qamqp/tutorials/tutorials.pro create mode 100644 qamqp/tutorials/workqueues/new_task/main.cpp create mode 100644 qamqp/tutorials/workqueues/new_task/new_task.pro create mode 100644 qamqp/tutorials/workqueues/worker/main.cpp create mode 100644 qamqp/tutorials/workqueues/worker/worker.pro create mode 100644 qamqp/tutorials/workqueues/workqueues.pro create mode 100644 send/CMakeLists.txt create mode 100644 send/main.cpp diff --git a/.drone.yml b/.drone.yml new file mode 100644 index 0000000..a91134f --- /dev/null +++ b/.drone.yml @@ -0,0 +1,20 @@ +kind: pipeline +type: ssh +name: default + +server: + host: 192.168.46.100:2223 + user: jessequ + password: + from_secret: password + +workspace: + path: /home/tmp + +steps: +- name: build + commands: + - mkdir -p build/debug + - cd build/debug + - /home/jessequ/Qt/Tools/CMake/bin/cmake -S ../.. -DCMAKE_BUILD_TYPE:STRING=Debug -DCMAKE_GENERATOR:STRING=Ninja -DCMAKE_MAKE_PROGRAM:FILEPATH=/home/jessequ/Qt/Tools/Ninja/ninja -DCMAKE_PREFIX_PATH:PATH=/home/jessequ/Qt/6.7.2/gcc_64 -DCMAKE_PROJECT_INCLUDE_BEFORE:FILEPATH=/home/jessequ/Qt/Platforms/package-manager/auto-setup.cmake -DPostgreSQL_INCLUDE_DIR:FILEPATH=/usr/pgsql-17/include -DPostgreSQL_LIBRARY:FILEPATH=/usr/pgsql-17/lib + - /home/jessequ/Qt/Tools/Ninja/ninja diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d8b4fd9 --- /dev/null +++ b/.gitignore @@ -0,0 +1,105 @@ +build/ +.vscode/ + +# ---> CMake +CMakeLists.txt.user +CMakeCache.txt +CMakeFiles +CMakeScripts +Testing +Makefile +cmake_install.cmake +install_manifest.txt +compile_commands.json +CTestTestfile.cmake +_deps + +# ---> C++ +# Prerequisites +*.d + +# Compiled Object files +*.slo +*.lo +*.o +*.obj + +# Precompiled Headers +*.gch +*.pch + +# Compiled Dynamic libraries +*.so +*.dylib +*.dll + +# Fortran module files +*.mod +*.smod + +# Compiled Static libraries +*.lai +*.la +*.a +*.lib + +# Executables +*.exe +*.out +*.app + +# ---> C +# Prerequisites +*.d + +# Object files +*.o +*.ko +*.obj +*.elf + +# Linker output +*.ilk +*.map +*.exp + +# Precompiled Headers +*.gch +*.pch + +# Libraries +*.lib +*.a +*.la +*.lo + +# Shared objects (inc. Windows DLLs) +*.dll +*.so +*.so.* +*.dylib + +# Executables +*.exe +*.out +*.app +*.i*86 +*.x86_64 +*.hex + +# Debug files +*.dSYM/ +*.su +*.idb +*.pdb + +# Kernel Module Compile Results +*.mod* +*.cmd +.tmp_versions/ +modules.order +Module.symvers +Mkfile.old +dkms.conf + + diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..102f53b --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,74 @@ +cmake_minimum_required(VERSION 3.16) +project(events VERSION 0.0.1 LANGUAGES CXX) + +set(CMAKE_CXX_STANDARD 14) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +find_package(Git QUIET) + +if(GIT_FOUND) + execute_process( + COMMAND ${GIT_EXECUTABLE} describe --long --dirty --tags + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} + OUTPUT_VARIABLE GIT_VERSION + ERROR_QUIET + OUTPUT_STRIP_TRAILING_WHITESPACE + ) + + execute_process( + COMMAND ${GIT_EXECUTABLE} describe --abbrev=0 + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} + OUTPUT_VARIABLE GIT_TAG + ERROR_QUIET + OUTPUT_STRIP_TRAILING_WHITESPACE + ) + + if(GIT_TAG) + string(REGEX REPLACE "^v" "" VERSION ${GIT_TAG}) + else() + set(VERSION ${PROJECT_VERSION}) + endif() +else() + set(GIT_VERSION "unknown") + set(VERSION ${PROJECT_VERSION}) +endif() + +add_compile_definitions(GIT_VERSION="${GIT_VERSION}") + +option(BUILD_SHARED_LIBS "Build shared libraries" ON) + +find_package(Qt6 COMPONENTS Core Network REQUIRED) + +set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}) +set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}) +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}) + +set(CMAKE_AUTOUIC ON) +set(CMAKE_AUTOMOC ON) +set(CMAKE_AUTORCC ON) + +add_subdirectory(qamqp) + +INCLUDE_DIRECTORIES(${PROJECT_SOURCE_DIR}) + +add_executable(events + main.cpp +) + +target_link_libraries(events Qt6::Core Qt6::Network qamqp) + +set_target_properties(events PROPERTIES + AUTOMOC ON + AUTORCC ON + AUTOUIC ON + CXX_STANDARD 14 + CXX_STANDARD_REQUIRED ON + CXX_EXTENSIONS OFF + VERSION 0.0.1 + EXPORT_NAME "Event Queue" + ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin" + LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib" + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin" +) + +add_subdirectory(send) diff --git a/README.md b/README.md new file mode 100644 index 0000000..41bcfa8 --- /dev/null +++ b/README.md @@ -0,0 +1,4 @@ +# events + +[![Build Status](http://192.168.46.100:4080/api/badges/CL-Softwares/events/status.svg)](http://192.168.46.100:4080/CL-Softwares/events) + diff --git a/main.cpp b/main.cpp new file mode 100644 index 0000000..08eb156 --- /dev/null +++ b/main.cpp @@ -0,0 +1,81 @@ +#include +#include + +#include "qamqpclient.h" +#include "qamqpexchange.h" +#include "qamqpqueue.h" + +class Receiver : public QObject +{ + Q_OBJECT +public: + Receiver(QObject *parent = 0) : QObject(parent) { + m_client.setAutoReconnect(true); + } + +public Q_SLOTS: + void start() { + connect(&m_client, SIGNAL(connected()), this, SLOT(clientConnected())); + m_client.connectToHost(); + } + +private Q_SLOTS: + void clientConnected() { + QAmqpQueue *queue = m_client.createQueue("hello"); + disconnect(queue, 0, 0, 0); // in case this is a reconnect + connect(queue, SIGNAL(declared()), this, SLOT(queueDeclared())); + queue->declare(); + } + + void queueDeclared() { + QAmqpQueue *queue = qobject_cast(sender()); + if (!queue) + return; + + connect(queue, SIGNAL(messageReceived()), this, SLOT(messageReceived())); + + // queue->consume(QAmqpQueue::coNoAck); + // queue->consume(QAmqpQueue::coNoLocal); + + qint32 sizeQueue = queue->messageCount(); + while (sizeQueue--) { + queue->get(false); + } + + qDebug() << " [*] Waiting for messages. To exit press CTRL+C"; + + queue->ack(3, false); // Acknowledgement the 3rd message. + + queue->reopen(); + + // m_client.disconnectFromHost(); + } + + void messageReceived() { + QAmqpQueue *queue = qobject_cast(sender()); + if (!queue) + return; + + QAmqpMessage message = queue->dequeue(); + qDebug() << " [x] Received in" << message.payload(); + + int input=0; + // std::scanf("%d", &input); + qDebug() << " [x] Received out, " << message.deliveryTag() << " | " << message.payload() << " , input = " << input; + } + +private: + QAmqpClient m_client; + +}; + +int main(int argc, char **argv) +{ + qDebug() << " Recieve starts ... "; + QCoreApplication app(argc, argv); + Receiver receiver; + receiver.start(); + return app.exec(); +} + +#include "main.moc" diff --git a/qamqp/CMakeLists.txt b/qamqp/CMakeLists.txt new file mode 100644 index 0000000..e4be59e --- /dev/null +++ b/qamqp/CMakeLists.txt @@ -0,0 +1,90 @@ +cmake_minimum_required(VERSION 3.16) +project(qamqp VERSION 0.0.1 LANGUAGES CXX) + +set(CMAKE_CXX_STANDARD 11) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +find_package(Git QUIET) + +if(GIT_FOUND) + execute_process( + COMMAND ${GIT_EXECUTABLE} describe --long --dirty --tags + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} + OUTPUT_VARIABLE GIT_VERSION + ERROR_QUIET + OUTPUT_STRIP_TRAILING_WHITESPACE + ) + + execute_process( + COMMAND ${GIT_EXECUTABLE} describe --abbrev=0 + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} + OUTPUT_VARIABLE GIT_TAG + ERROR_QUIET + OUTPUT_STRIP_TRAILING_WHITESPACE + ) + + if(GIT_TAG) + string(REGEX REPLACE "^v" "" VERSION ${GIT_TAG}) + else() + set(VERSION ${PROJECT_VERSION}) + endif() +else() + set(GIT_VERSION "unknown") + set(VERSION ${PROJECT_VERSION}) +endif() + +add_compile_definitions(GIT_VERSION="${GIT_VERSION}") + +option(BUILD_SHARED_LIBS "Build shared libraries" ON) + +find_package(Qt6 COMPONENTS Core Network REQUIRED) + +set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}) +set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}) +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}) + +set(CMAKE_AUTOUIC ON) +set(CMAKE_AUTOMOC ON) +set(CMAKE_AUTORCC ON) + +add_subdirectory(src) +export(TARGETS qamqp + NAMESPACE qamqp:: + FILE "${CMAKE_CURRENT_BINARY_DIR}/qamqpTargets.cmake" +) + +option(BUILD_TESTS "Build tests" OFF) +if(BUILD_TESTS) + enable_testing() + add_subdirectory(tests) +endif() + +option(BUILD_TUTORIALS "Build tutorials" OFF) +if(BUILD_TUTORIALS) + add_subdirectory(tutorials) +endif() + +include(GNUInstallDirs) +install(EXPORT qamqpTargets + FILE qamqpTargets.cmake + NAMESPACE qamqp:: + DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/qamqp +) + +include(CMakePackageConfigHelpers) +write_basic_package_version_file( + "${CMAKE_CURRENT_BINARY_DIR}/qamqpConfigVersion.cmake" + VERSION ${VERSION} + COMPATIBILITY SameMajorVersion +) + +configure_package_config_file(${CMAKE_CURRENT_SOURCE_DIR}/cmake/qamqpConfig.cmake.in + "${CMAKE_CURRENT_BINARY_DIR}/qamqpConfig.cmake" + INSTALL_DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/qamqp +) + +install(FILES + "${CMAKE_CURRENT_BINARY_DIR}/qamqpConfig.cmake" + "${CMAKE_CURRENT_BINARY_DIR}/qamqpConfigVersion.cmake" + DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/qamqp +) diff --git a/qamqp/cmake/qamqpConfig.cmake.in b/qamqp/cmake/qamqpConfig.cmake.in new file mode 100644 index 0000000..fe6fea3 --- /dev/null +++ b/qamqp/cmake/qamqpConfig.cmake.in @@ -0,0 +1,8 @@ +@PACKAGE_INIT@ + +include(CMakeFindDependencyMacro) +find_dependency(Qt6 COMPONENTS Core Network) + +include("${CMAKE_CURRENT_LIST_DIR}/qamqpTargets.cmake") + +check_required_components(qamqp) \ No newline at end of file diff --git a/qamqp/src/CMakeLists.txt b/qamqp/src/CMakeLists.txt new file mode 100644 index 0000000..aa4e8f4 --- /dev/null +++ b/qamqp/src/CMakeLists.txt @@ -0,0 +1,95 @@ +set(QAMQP_HEADERS + qamqpauthenticator.h + qamqpchannel.h + qamqpchannel_p.h + qamqpchannelhash_p.h + qamqpclient.h + qamqpclient_p.h + qamqpexchange.h + qamqpexchange_p.h + qamqpglobal.h + qamqpmessage.h + qamqpmessage_p.h + qamqpqueue.h + qamqpqueue_p.h + qamqptable.h + qamqpframe_p.h +) + +set(QAMQP_SOURCES + qamqpauthenticator.cpp + qamqpchannel.cpp + qamqpchannelhash.cpp + qamqpclient.cpp + qamqpexchange.cpp + qamqpframe.cpp + qamqpmessage.cpp + qamqpqueue.cpp + qamqptable.cpp +) + +if(MSVC) + set(BUILD_SHARED_LIBS OFF CACHE BOOL "Build static library for MSVC" FORCE) +endif() + +if(BUILD_SHARED_LIBS) + add_library(qamqp SHARED ${QAMQP_HEADERS} ${QAMQP_SOURCES}) +else() + add_library(qamqp STATIC ${QAMQP_HEADERS} ${QAMQP_SOURCES}) +endif() + +add_library(qamqp::qamqp ALIAS qamqp) + +set_target_properties(qamqp PROPERTIES + VERSION ${PROJECT_VERSION} + SOVERSION ${PROJECT_VERSION_MAJOR} + EXPORT_NAME qamqp + OUTPUT_NAME qamqp + DEBUG_POSTFIX d +) + +target_include_directories(qamqp + PUBLIC + $ + $ +) + +target_link_libraries(qamqp + PUBLIC + Qt6::Core + Qt6::Network +) + +target_compile_definitions(qamqp + PRIVATE + QAMQP_BUILD + PUBLIC + $<$>:QAMQP_STATIC> +) + +set_target_properties(qamqp PROPERTIES + AUTOMOC ON + AUTORCC ON + AUTOUIC ON + CXX_STANDARD 14 + CXX_STANDARD_REQUIRED ON + CXX_EXTENSIONS OFF + VERSION ${PROJECT_VERSION} + SOVERSION ${PROJECT_VERSION_MAJOR} + EXPORT_NAME qamqp + ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib" + LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib" + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin" +) + +install(TARGETS qamqp + EXPORT qamqpTargets + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} + INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/qamqp +) + +install(FILES ${QAMQP_HEADERS} + DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/qamqp +) diff --git a/qamqp/src/qamqpauthenticator.cpp b/qamqp/src/qamqpauthenticator.cpp new file mode 100644 index 0000000..73e19ef --- /dev/null +++ b/qamqp/src/qamqpauthenticator.cpp @@ -0,0 +1,47 @@ +#include "qamqptable.h" +#include "qamqpframe_p.h" +#include "qamqpauthenticator.h" + +QAmqpPlainAuthenticator::QAmqpPlainAuthenticator(const QString &l, const QString &p) + : login_(l), + password_(p) +{ +} + +QAmqpPlainAuthenticator::~QAmqpPlainAuthenticator() +{ +} + +QString QAmqpPlainAuthenticator::login() const +{ + return login_; +} + +QString QAmqpPlainAuthenticator::password() const +{ + return password_; +} + +QString QAmqpPlainAuthenticator::type() const +{ + return "AMQPLAIN"; +} + +void QAmqpPlainAuthenticator::setLogin(const QString &l) +{ + login_ = l; +} + +void QAmqpPlainAuthenticator::setPassword(const QString &p) +{ + password_ = p; +} + +void QAmqpPlainAuthenticator::write(QDataStream &out) +{ + QAmqpFrame::writeAmqpField(out, QAmqpMetaType::ShortString, type()); + QAmqpTable response; + response["LOGIN"] = login_; + response["PASSWORD"] = password_; + out << response; +} diff --git a/qamqp/src/qamqpauthenticator.h b/qamqp/src/qamqpauthenticator.h new file mode 100644 index 0000000..509c0ba --- /dev/null +++ b/qamqp/src/qamqpauthenticator.h @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2012-2014 Alexey Shcherbakov + * Copyright (C) 2014-2015 Matt Broadstone + * Contact: https://github.com/mbroadst/qamqp + * + * This file is part of the QAMQP Library. + * + * This library is free software; 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. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + */ +#ifndef QAMQPAUTHENTICATOR_H +#define QAMQPAUTHENTICATOR_H + +#include +#include + +#include "qamqpglobal.h" + +class QAMQP_EXPORT QAmqpAuthenticator +{ +public: + virtual ~QAmqpAuthenticator() {} + virtual QString type() const = 0; + virtual void write(QDataStream &out) = 0; +}; + +class QAMQP_EXPORT QAmqpPlainAuthenticator : public QAmqpAuthenticator +{ +public: + QAmqpPlainAuthenticator(const QString &login = QString(), const QString &password = QString()); + virtual ~QAmqpPlainAuthenticator(); + + QString login() const; + void setLogin(const QString &l); + + QString password() const; + void setPassword(const QString &p); + + virtual QString type() const; + virtual void write(QDataStream &out); + +private: + QString login_; + QString password_; + +}; + +#endif // QAMQPAUTHENTICATOR_H diff --git a/qamqp/src/qamqpchannel.cpp b/qamqp/src/qamqpchannel.cpp new file mode 100644 index 0000000..99c1f5a --- /dev/null +++ b/qamqp/src/qamqpchannel.cpp @@ -0,0 +1,368 @@ +#include +#include + +#include "qamqpchannel.h" +#include "qamqpchannel_p.h" +#include "qamqpclient.h" +#include "qamqpclient_p.h" + +quint16 QAmqpChannelPrivate::nextChannelNumber = 0; +QAmqpChannelPrivate::QAmqpChannelPrivate(QAmqpChannel *q) + : channelNumber(0), + opened(false), + needOpen(true), + prefetchSize(0), + requestedPrefetchSize(0), + prefetchCount(0), + requestedPrefetchCount(0), + error(QAMQP::NoError), + q_ptr(q) +{ +} + +QAmqpChannelPrivate::~QAmqpChannelPrivate() +{ + if (!client.isNull()) { + QAmqpClientPrivate *priv = client->d_func(); + priv->methodHandlersByChannel[channelNumber].removeAll(this); + } +} + +void QAmqpChannelPrivate::init(int channel, QAmqpClient *c) +{ + client = c; + needOpen = (channel <= nextChannelNumber && channel != -1) ? false : true; + channelNumber = channel == -1 ? ++nextChannelNumber : channel; + nextChannelNumber = qMax(channelNumber, nextChannelNumber); +} + +bool QAmqpChannelPrivate::_q_method(const QAmqpMethodFrame &frame) +{ + Q_ASSERT(frame.channel() == channelNumber); + if (frame.channel() != channelNumber) + return true; + + if (frame.methodClass() == QAmqpFrame::Basic) { + if (frame.id() == bmQosOk) { + qosOk(frame); + return true; + } + + return false; + } + + if (frame.methodClass() != QAmqpFrame::Channel) + return false; + + switch (frame.id()) { + case miOpenOk: + openOk(frame); + break; + case miFlow: + flow(frame); + break; + case miFlowOk: + flowOk(frame); + break; + case miClose: + close(frame); + break; + case miCloseOk: + closeOk(frame); + break; + } + + return true; +} + +void QAmqpChannelPrivate::_q_open() +{ + open(); +} + +void QAmqpChannelPrivate::sendFrame(const QAmqpFrame &frame) +{ + if (!client) { + qAmqpDebug() << Q_FUNC_INFO << "invalid client"; + return; + } + + client->d_func()->sendFrame(frame); +} + +void QAmqpChannelPrivate::resetInternalState() +{ + if (!opened) return; + opened = false; + needOpen = true; +} + +void QAmqpChannelPrivate::open() +{ + if (!needOpen || opened) + return; + + if (!client->isConnected()) + return; + + qAmqpDebug("<- channel#open( channel=%d, name=%s )", channelNumber, qPrintable(name)); + QAmqpMethodFrame frame(QAmqpFrame::Channel, miOpen); + frame.setChannel(channelNumber); + + QByteArray arguments; + arguments.resize(1); + arguments[0] = 0; + + frame.setArguments(arguments); + sendFrame(frame); +} + +void QAmqpChannelPrivate::flow(bool active) +{ + QByteArray arguments; + QDataStream stream(&arguments, QIODevice::WriteOnly); + QAmqpFrame::writeAmqpField(stream, QAmqpMetaType::ShortShortUint, (active ? 1 : 0)); + + QAmqpMethodFrame frame(QAmqpFrame::Channel, miFlow); + frame.setChannel(channelNumber); + frame.setArguments(arguments); + sendFrame(frame); +} + +// NOTE: not implemented until I can figure out a good way to force the server +// to pause the channel in a test. It seems like RabbitMQ just doesn't +// care about flow control, preferring rather to use basic.qos +void QAmqpChannelPrivate::flow(const QAmqpMethodFrame &frame) +{ + Q_UNUSED(frame); + qAmqpDebug("-> channel#flow( channel=%d, name=%s )", channelNumber, qPrintable(name)); +} + +void QAmqpChannelPrivate::flowOk() +{ + qAmqpDebug("<- channel#flowOk( channel=%d, name=%s )", channelNumber, qPrintable(name)); +} + +void QAmqpChannelPrivate::flowOk(const QAmqpMethodFrame &frame) +{ + Q_Q(QAmqpChannel); + qAmqpDebug("-> channel#flowOk( channel=%d, name=%s )", channelNumber, qPrintable(name)); + + QByteArray data = frame.arguments(); + QDataStream stream(&data, QIODevice::ReadOnly); + bool active = QAmqpFrame::readAmqpField(stream, QAmqpMetaType::Boolean).toBool(); + if (active) + Q_EMIT q->resumed(); + else + Q_EMIT q->paused(); +} + +void QAmqpChannelPrivate::close(int code, const QString &text, int classId, int methodId) +{ + qAmqpDebug("<- channel#close( channel=%d, name=%s, reply-code=%d, text=%s class-id=%d, method-id:%d, )", + channelNumber, qPrintable(name), code, qPrintable(text), classId, methodId); + + QByteArray arguments; + QDataStream stream(&arguments, QIODevice::WriteOnly); + + if (!code) code = 200; + QAmqpFrame::writeAmqpField(stream, QAmqpMetaType::ShortUint, code); + if (!text.isEmpty()) { + QAmqpFrame::writeAmqpField(stream, QAmqpMetaType::ShortString, text); + } else { + QAmqpFrame::writeAmqpField(stream, QAmqpMetaType::ShortString, QLatin1String("OK")); + } + + QAmqpFrame::writeAmqpField(stream, QAmqpMetaType::ShortUint, classId); + QAmqpFrame::writeAmqpField(stream, QAmqpMetaType::ShortUint, methodId); + + QAmqpMethodFrame frame(QAmqpFrame::Channel, miClose); + frame.setChannel(channelNumber); + frame.setArguments(arguments); + sendFrame(frame); +} + +void QAmqpChannelPrivate::close(const QAmqpMethodFrame &frame) +{ + Q_Q(QAmqpChannel); + QByteArray data = frame.arguments(); + QDataStream stream(&data, QIODevice::ReadOnly); + qint16 code = 0, classId, methodId; + stream >> code; + QString text = + QAmqpFrame::readAmqpField(stream, QAmqpMetaType::ShortString).toString(); + + stream >> classId; + stream >> methodId; + + QAMQP::Error checkError = static_cast(code); + if (checkError != QAMQP::NoError) { + error = checkError; + errorString = qPrintable(text); + Q_EMIT q->error(error); + } + + qAmqpDebug("-> channel#close( channel=%d, name=%s, reply-code=%d, reply-text=%s, class-id=%d, method-id=%d, )", + channelNumber, qPrintable(name), code, qPrintable(text), classId, methodId); + + // complete handshake + QAmqpMethodFrame closeOkFrame(QAmqpFrame::Channel, miCloseOk); + closeOkFrame.setChannel(channelNumber); + sendFrame(closeOkFrame); + + // notify everyone that the channel was closed on us. + notifyClosed(); +} + +void QAmqpChannelPrivate::closeOk(const QAmqpMethodFrame &) +{ + qAmqpDebug("-> channel#closeOk( channel=%d, name=%s )", channelNumber, qPrintable(name)); + notifyClosed(); +} + +void QAmqpChannelPrivate::notifyClosed() +{ + Q_Q(QAmqpChannel); + Q_EMIT q->closed(); + q->channelClosed(); + opened = false; +} + +void QAmqpChannelPrivate::openOk(const QAmqpMethodFrame &) +{ + Q_Q(QAmqpChannel); + qAmqpDebug("-> channel#openOk( channel=%d, name=%s )", channelNumber, qPrintable(name)); + opened = true; + Q_EMIT q->opened(); + q->channelOpened(); +} + +void QAmqpChannelPrivate::_q_disconnected() +{ + nextChannelNumber = 0; + opened = false; +} + +void QAmqpChannelPrivate::qosOk(const QAmqpMethodFrame &frame) +{ + Q_Q(QAmqpChannel); + Q_UNUSED(frame) + qAmqpDebug("-> basic#qosOk( channel=%d, name=%s )", channelNumber, qPrintable(name)); + + prefetchCount = requestedPrefetchCount; + prefetchSize = requestedPrefetchSize; + Q_EMIT q->qosDefined(); +} + +////////////////////////////////////////////////////////////////////////// + +QAmqpChannel::QAmqpChannel(QAmqpChannelPrivate *dd, QAmqpClient *parent) + : QObject(parent), + d_ptr(dd) +{ +} + +QAmqpChannel::~QAmqpChannel() +{ +} + +void QAmqpChannel::close() +{ + Q_D(QAmqpChannel); + d->needOpen = true; + if (d->opened) + d->close(0, QString(), 0,0); +} + +void QAmqpChannel::reopen() +{ + Q_D(QAmqpChannel); + if (d->opened) + close(); + d->open(); +} + +QString QAmqpChannel::name() const +{ + Q_D(const QAmqpChannel); + return d->name; +} + +int QAmqpChannel::channelNumber() const +{ + Q_D(const QAmqpChannel); + return d->channelNumber; +} + +void QAmqpChannel::setName(const QString &name) +{ + Q_D(QAmqpChannel); + d->name = name; +} + +bool QAmqpChannel::isOpen() const +{ + Q_D(const QAmqpChannel); + return d->opened; +} + +void QAmqpChannel::qos(qint16 prefetchCount, qint32 prefetchSize) +{ + Q_D(QAmqpChannel); + QAmqpMethodFrame frame(QAmqpFrame::Basic, QAmqpChannelPrivate::bmQos); + frame.setChannel(d->channelNumber); + + QByteArray arguments; + QDataStream stream(&arguments, QIODevice::WriteOnly); + + d->requestedPrefetchSize = prefetchSize; + d->requestedPrefetchCount = prefetchCount; + + stream << qint32(prefetchSize); + stream << qint16(prefetchCount); + stream << qint8(0x0); // global + + qAmqpDebug("<- basic#qos( channel=%d, name=%s, prefetch-size=%d, prefetch-count=%d, global=%d )", + d->channelNumber, qPrintable(d->name), prefetchSize, prefetchCount, 0); + + frame.setArguments(arguments); + d->sendFrame(frame); +} + +qint32 QAmqpChannel::prefetchSize() const +{ + Q_D(const QAmqpChannel); + return d->prefetchSize; +} + +qint16 QAmqpChannel::prefetchCount() const +{ + Q_D(const QAmqpChannel); + return d->prefetchCount; +} + +void QAmqpChannel::reset() +{ + Q_D(QAmqpChannel); + d->resetInternalState(); +} + +QAMQP::Error QAmqpChannel::error() const +{ + Q_D(const QAmqpChannel); + return d->error; +} + +QString QAmqpChannel::errorString() const +{ + Q_D(const QAmqpChannel); + return d->errorString; +} + +void QAmqpChannel::resume() +{ + Q_D(QAmqpChannel); + d->flow(true); +} + +#include "moc_qamqpchannel.cpp" diff --git a/qamqp/src/qamqpchannel.h b/qamqp/src/qamqpchannel.h new file mode 100644 index 0000000..42fcef7 --- /dev/null +++ b/qamqp/src/qamqpchannel.h @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2012-2014 Alexey Shcherbakov + * Copyright (C) 2014-2015 Matt Broadstone + * Contact: https://github.com/mbroadst/qamqp + * + * This file is part of the QAMQP Library. + * + * This library is free software; 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. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + */ +#ifndef QAMQPCHANNEL_H +#define QAMQPCHANNEL_H + +#include +#include "qamqpglobal.h" + +class QAmqpClient; +class QAmqpChannelPrivate; +class QAMQP_EXPORT QAmqpChannel : public QObject +{ + Q_OBJECT + Q_PROPERTY(int number READ channelNumber CONSTANT) + Q_PROPERTY(bool open READ isOpen CONSTANT) + Q_PROPERTY(QString name READ name WRITE setName) +public: + virtual ~QAmqpChannel(); + + int channelNumber() const; + bool isOpen() const; + + QString name() const; + void setName(const QString &name); + + QAMQP::Error error() const; + QString errorString() const; + + qint32 prefetchSize() const; + qint16 prefetchCount() const; + + void reset(); + + // AMQP Basic + void qos(qint16 prefetchCount, qint32 prefetchSize = 0); + +public Q_SLOTS: + void close(); + void reopen(); + void resume(); + +Q_SIGNALS: + void opened(); + void closed(); + void resumed(); + void paused(); + void error(QAMQP::Error error); + void qosDefined(); + +protected: + virtual void channelOpened() = 0; + virtual void channelClosed() = 0; + +protected: + explicit QAmqpChannel(QAmqpChannelPrivate *dd, QAmqpClient *client); + + Q_DISABLE_COPY(QAmqpChannel) + Q_DECLARE_PRIVATE(QAmqpChannel) + QScopedPointer d_ptr; + + Q_PRIVATE_SLOT(d_func(), void _q_open()) + Q_PRIVATE_SLOT(d_func(), void _q_disconnected()) + + friend class QAmqpClientPrivate; + friend class QAmqpExchangePrivate; +}; + +#endif // QAMQPCHANNEL_H diff --git a/qamqp/src/qamqpchannel_p.h b/qamqp/src/qamqpchannel_p.h new file mode 100644 index 0000000..e902e86 --- /dev/null +++ b/qamqp/src/qamqpchannel_p.h @@ -0,0 +1,84 @@ +#ifndef QAMQPCHANNEL_P_H +#define QAMQPCHANNEL_P_H + +#include +#include "qamqpframe_p.h" +#include "qamqptable.h" + +#define METHOD_ID_ENUM(name, id) name = id, name ## Ok + +class QAmqpChannel; +class QAmqpClient; +class QAmqpClientPrivate; +class QAmqpChannelPrivate : public QAmqpMethodFrameHandler +{ +public: + enum MethodId { + METHOD_ID_ENUM(miOpen, 10), + METHOD_ID_ENUM(miFlow, 20), + METHOD_ID_ENUM(miClose, 40) + }; + + enum BasicMethod { + METHOD_ID_ENUM(bmQos, 10), + METHOD_ID_ENUM(bmConsume, 20), + METHOD_ID_ENUM(bmCancel, 30), + bmPublish = 40, + bmReturn = 50, + bmDeliver = 60, + METHOD_ID_ENUM(bmGet, 70), + bmGetEmpty = 72, + bmAck = 80, + bmReject = 90, + bmRecoverAsync = 100, + METHOD_ID_ENUM(bmRecover, 110), + bmNack = 120 + }; + + QAmqpChannelPrivate(QAmqpChannel *q); + virtual ~QAmqpChannelPrivate(); + + void init(int channel, QAmqpClient *client); + void sendFrame(const QAmqpFrame &frame); + virtual void resetInternalState(); + + void open(); + void flow(bool active); + void flowOk(); + void close(int code, const QString &text, int classId, int methodId); + void notifyClosed(); + + // reimp MethodHandler + virtual bool _q_method(const QAmqpMethodFrame &frame); + void openOk(const QAmqpMethodFrame &frame); + void flow(const QAmqpMethodFrame &frame); + void flowOk(const QAmqpMethodFrame &frame); + void close(const QAmqpMethodFrame &frame); + void closeOk(const QAmqpMethodFrame &frame); + void qosOk(const QAmqpMethodFrame &frame); + + // private slots + virtual void _q_disconnected(); + void _q_open(); + + QPointer client; + QString name; + quint16 channelNumber; + static quint16 nextChannelNumber; + bool opened; + bool needOpen; + + qint32 prefetchSize; + qint32 requestedPrefetchSize; + qint16 prefetchCount; + qint16 requestedPrefetchCount; + + QAMQP::Error error; + QString errorString; + + Q_DECLARE_PUBLIC(QAmqpChannel) + QAmqpChannel * const q_ptr; + QAmqpTable arguments; +}; + +#endif // QAMQPCHANNEL_P_H diff --git a/qamqp/src/qamqpchannelhash.cpp b/qamqp/src/qamqpchannelhash.cpp new file mode 100644 index 0000000..9bff643 --- /dev/null +++ b/qamqp/src/qamqpchannelhash.cpp @@ -0,0 +1,113 @@ +/* + * Copyright (C) 2012-2014 Alexey Shcherbakov + * Copyright (C) 2014-2015 Matt Broadstone + * Contact: https://github.com/mbroadst/qamqp + * + * This file is part of the QAMQP Library. + * + * This library is free software; 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. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + */ +#include "qamqpqueue.h" +#include "qamqpexchange.h" +#include "qamqpchannelhash_p.h" + +/*! +* Retrieve a pointer to the named channel. +* +* A NULL string is assumed to be equivalent to "" for the purpose +* of retrieving the nameless (default) exchange. +* +* \param[in] name The name of the channel to retrieve. +* \retval NULL Channel does not exist. +*/ +QAmqpChannel* QAmqpChannelHash::get(const QString& name) const +{ + if (name.isEmpty()) + return m_channels.value(QString()); + return m_channels.value(name); +} + +QStringList QAmqpChannelHash::channels() const +{ + return m_channels.keys(); +} + +/*! +* Return true if the named channel exists. +*/ +bool QAmqpChannelHash::contains(const QString& name) const +{ + if (name.isEmpty()) + return m_channels.contains(QString()); + return m_channels.contains(name); +} + +/*! +* Store an exchange in the hash. The nameless exchange is stored under +* the name "". +*/ +void QAmqpChannelHash::put(QAmqpExchange* exchange) +{ + if (exchange->name().isEmpty()) + put(QString(), exchange); + else + put(exchange->name(), exchange); +} + +/*! +* Store a queue in the hash. If the queue is nameless, we hook its +* declared signal and store it when the queue receives a name from the +* broker, otherwise we store it under the name given. +*/ +void QAmqpChannelHash::put(QAmqpQueue* queue) +{ + if (queue->name().isEmpty()) + connect(queue, SIGNAL(declared()), this, SLOT(queueDeclared())); + else + put(queue->name(), queue); +} + +/*! +* Handle destruction of a channel. Do a full garbage collection run. +*/ +void QAmqpChannelHash::channelDestroyed(QObject* object) +{ + QList names(m_channels.keys()); + QList::iterator it; + for (it = names.begin(); it != names.end(); it++) { + if (m_channels.value(*it) == object) + m_channels.remove(*it); + } +} + +/*! +* Handle a queue that has just been declared and given a new name. The +* caller is assumed to be a QAmqpQueue instance. +*/ +void QAmqpChannelHash::queueDeclared() +{ + QAmqpQueue *queue = qobject_cast(sender()); + if (queue) + put(queue); +} + +/*! +* Store a channel in the hash. The channel is assumed +* to be named at the time of storage. This hooks the 'destroyed' signal +* so the channel can be removed from our list. +*/ +void QAmqpChannelHash::put(const QString& name, QAmqpChannel* channel) +{ + connect(channel, SIGNAL(destroyed(QObject*)), this, SLOT(channelDestroyed(QObject*))); + m_channels[name] = channel; +} + +/* vim: set ts=4 sw=4 et */ diff --git a/qamqp/src/qamqpchannelhash_p.h b/qamqp/src/qamqpchannelhash_p.h new file mode 100644 index 0000000..eab5124 --- /dev/null +++ b/qamqp/src/qamqpchannelhash_p.h @@ -0,0 +1,98 @@ +/* + * Copyright (C) 2012-2014 Alexey Shcherbakov + * Copyright (C) 2014-2015 Matt Broadstone + * Contact: https://github.com/mbroadst/qamqp + * + * This file is part of the QAMQP Library. + * + * This library is free software; 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. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + */ +#ifndef QAMQPCHANNELHASH_P_H +#define QAMQPCHANNELHASH_P_H + +#include +#include +#include +#include + +/* Forward declarations */ +class QAmqpChannel; +class QAmqpQueue; +class QAmqpExchange; + +/*! + * QAmqpChannelHash is a container for storing queues and exchanges for later + * retrieval. When the objects are destroyed, they are automatically removed + * from the container. + */ +class QAmqpChannelHash : public QObject +{ + Q_OBJECT +public: + /*! + * Retrieve a pointer to the named channel. + * + * A NULL string is assumed to be equivalent to "" for the purpose + * of retrieving the nameless (default) exchange. + * + * \param[in] name The name of the channel to retrieve. + * \retval NULL Channel does not exist. + */ + QAmqpChannel* get(const QString& name) const; + + /*! + * Return true if the named channel exists. + */ + bool contains(const QString& name) const; + + /** + * Returns a list of channels tracked by this hash + */ + QStringList channels() const; + + /*! + * Store an exchange in the hash. The nameless exchange is stored under + * the name "". + */ + void put(QAmqpExchange* exchange); + + /*! + * Store a queue in the hash. If the queue is nameless, we hook its + * declared signal and store it when the queue receives a name from the + * broker, otherwise we store it under the name given. + */ + void put(QAmqpQueue* queue); + +private Q_SLOTS: + /*! + * Handle destruction of a channel. Do a full garbage collection run. + */ + void channelDestroyed(QObject* object); + + /*! + * Handle a queue that has just been declared and given a new name. The + * caller is assumed to be a QAmqpQueue instance. + */ + void queueDeclared(); + +private: + /*! + * Store a channel in the hash. This hooks the 'destroyed' signal + * so the channel can be removed from our list. + */ + void put(const QString& name, QAmqpChannel* channel); + + /*! A collection of channels. Key is the channel's "name". */ + QHash m_channels; +}; + +/* vim: set ts=4 sw=4 et */ +#endif diff --git a/qamqp/src/qamqpclient.cpp b/qamqp/src/qamqpclient.cpp new file mode 100644 index 0000000..2b26464 --- /dev/null +++ b/qamqp/src/qamqpclient.cpp @@ -0,0 +1,959 @@ +#include +#include +#include +#include +#include + +#include "qamqpglobal.h" +#include "qamqpexchange.h" +#include "qamqpexchange_p.h" +#include "qamqpqueue.h" +#include "qamqpqueue_p.h" +#include "qamqpauthenticator.h" +#include "qamqptable.h" +#include "qamqpclient_p.h" +#include "qamqpclient.h" + +QAmqpClientPrivate::QAmqpClientPrivate(QAmqpClient *q) + : port(AMQP_PORT), + host(AMQP_HOST), + virtualHost(AMQP_VHOST), + autoReconnect(false), + reconnectFixedTimeout(false), + timeout(0), + connecting(false), + useSsl(false), + socket(0), + closed(false), + connected(false), + channelMax(0), + heartbeatDelay(0), + frameMax(AMQP_FRAME_MAX), + error(QAMQP::NoError), + q_ptr(q) +{ + qRegisterMetaType(); +} + +QAmqpClientPrivate::~QAmqpClientPrivate() +{ +} + +void QAmqpClientPrivate::init() +{ + Q_Q(QAmqpClient); + initSocket(); + heartbeatTimer = new QTimer(q); + QObject::connect(heartbeatTimer, SIGNAL(timeout()), q, SLOT(_q_heartbeat())); + reconnectTimer = new QTimer(q); + reconnectTimer->setSingleShot(true); + QObject::connect(reconnectTimer, SIGNAL(timeout()), q, SLOT(_q_connect())); + + authenticator = QSharedPointer( + new QAmqpPlainAuthenticator(QString::fromLatin1(AMQP_LOGIN), QString::fromLatin1(AMQP_PSWD))); +} + +void QAmqpClientPrivate::initSocket() +{ + Q_Q(QAmqpClient); + socket = new QSslSocket(q); + socket->setSocketOption(QAbstractSocket::LowDelayOption, 1); + socket->setSocketOption(QAbstractSocket::KeepAliveOption, 1); + QObject::connect(socket, SIGNAL(connected()), q, SLOT(_q_socketConnected())); + QObject::connect(socket, SIGNAL(disconnected()), q, SLOT(_q_socketDisconnected())); + QObject::connect(socket, SIGNAL(readyRead()), q, SLOT(_q_readyRead())); +#if QT_VERSION >= 0x060000 + QObject::connect(socket, + SIGNAL(errorOccurred(QAbstractSocket::SocketError)), + q, + SLOT(_q_socketError(QAbstractSocket::SocketError))); + QObject::connect(socket, + SIGNAL(errorOccurred(QAbstractSocket::SocketError)), + q, + SIGNAL(socketErrorOccurred(QAbstractSocket::SocketError))); +#else + QObject::connect(socket, + SIGNAL(error(QAbstractSocket::SocketError)), + q, + SLOT(_q_socketError(QAbstractSocket::SocketError))); + QObject::connect(socket, + SIGNAL(error(QAbstractSocket::SocketError)), + q, + SIGNAL(socketErrorOccurred(QAbstractSocket::SocketError))); +#endif + QObject::connect(socket, SIGNAL(stateChanged(QAbstractSocket::SocketState)), + q, SIGNAL(socketStateChanged(QAbstractSocket::SocketState))); + QObject::connect(socket, SIGNAL(sslErrors(QList)), + q, SIGNAL(sslErrors(QList))); +} + +void QAmqpClientPrivate::resetChannelState() +{ + foreach (QString exchangeName, exchanges.channels()) { + QAmqpExchange *exchange = + qobject_cast(exchanges.get(exchangeName)); + if (exchange) exchange->d_ptr->resetInternalState(); + } + + foreach (QString queueName, queues.channels()) { + QAmqpQueue *queue = + qobject_cast(queues.get(queueName)); + if (queue) queue->d_ptr->resetInternalState(); + } +} + +void QAmqpClientPrivate::setUsername(const QString &username) +{ + QAmqpAuthenticator *auth = authenticator.data(); + if (auth && auth->type() == QLatin1String("AMQPLAIN")) { + QAmqpPlainAuthenticator *a = static_cast(auth); + a->setLogin(username); + } +} + +void QAmqpClientPrivate::setPassword(const QString &password) +{ + QAmqpAuthenticator *auth = authenticator.data(); + if (auth && auth->type() == QLatin1String("AMQPLAIN")) { + QAmqpPlainAuthenticator *a = static_cast(auth); + a->setPassword(password); + } +} + +void QAmqpClientPrivate::parseConnectionString(const QString &uri) +{ + QUrl connectionString = QUrl::fromUserInput(uri); + + if (connectionString.scheme() != AMQP_SCHEME && + connectionString.scheme() != AMQP_SSL_SCHEME) { + qAmqpDebug() << Q_FUNC_INFO << "invalid scheme: " << connectionString.scheme(); + return; + } + + useSsl = (connectionString.scheme() == AMQP_SSL_SCHEME); + port = connectionString.port((useSsl ? AMQP_SSL_PORT : AMQP_PORT)); + host = connectionString.host(); + + QString vhost = connectionString.path(); + if (vhost.startsWith("/") && vhost.size() > 1) + vhost = vhost.mid(1); + virtualHost = vhost; + setPassword(connectionString.password()); + setUsername(connectionString.userName()); +} + +void QAmqpClientPrivate::_q_connect() +{ + if (reconnectTimer) + reconnectTimer->stop(); + if (socket->state() != QAbstractSocket::UnconnectedState) { + qAmqpDebug() << Q_FUNC_INFO << "socket already connected, disconnecting.."; + _q_disconnect(); + // We need to explicitly close connection here because either way it will not be closed until we receive closeOk + closeConnection(); + } + + qAmqpDebug() << "connecting to host: " << host << ", port: " << port; + if (useSsl) + socket->connectToHostEncrypted(host, port); + else + socket->connectToHost(host, port); +} + +void QAmqpClientPrivate::_q_disconnect() +{ + if (reconnectTimer) + reconnectTimer->stop(); + if (socket->state() == QAbstractSocket::UnconnectedState) { + qAmqpDebug() << Q_FUNC_INFO << "already disconnected"; + return; + } + + buffer.clear(); + close(200, "client disconnect"); +} + +// private slots +void QAmqpClientPrivate::_q_socketConnected() +{ + if (reconnectTimer) + reconnectTimer->stop(); + if(reconnectFixedTimeout == false) + timeout = 0; + char header[8] = {'A', 'M', 'Q', 'P', 0, 0, 9, 1}; + socket->write(header, 8); +} + +void QAmqpClientPrivate::_q_socketDisconnected() +{ + Q_Q(QAmqpClient); + buffer.clear(); + resetChannelState(); + if (connected) + connected = false; + Q_EMIT q->disconnected(); +} + +void QAmqpClientPrivate::_q_heartbeat() +{ + QAmqpHeartbeatFrame frame; + sendFrame(frame); +} + +void QAmqpClientPrivate::_q_socketError(QAbstractSocket::SocketError error) +{ + if(reconnectFixedTimeout == false) + { + if (timeout <= 0) { + timeout = 1000; + } else { + if (timeout < 120000) + timeout *= 5; + } + } + + switch (error) { + case QAbstractSocket::ConnectionRefusedError: + case QAbstractSocket::RemoteHostClosedError: + case QAbstractSocket::SocketTimeoutError: + case QAbstractSocket::NetworkError: + case QAbstractSocket::ProxyConnectionClosedError: + case QAbstractSocket::ProxyConnectionRefusedError: + case QAbstractSocket::ProxyConnectionTimeoutError: + + default: + qAmqpDebug() << "socket error: " << socket->errorString(); + break; + } + + // per spec, on any error we need to close the socket immediately + // and send no more data. only try to send the close message if we + // are actively connected + if (socket->state() == QAbstractSocket::ConnectedState || + socket->state() == QAbstractSocket::ConnectingState) { + socket->abort(); + } + + errorString = socket->errorString(); + + if (autoReconnect && reconnectTimer) { + qAmqpDebug() << "trying to reconnect after: " << timeout << "ms"; + reconnectTimer->start(timeout); + } +} + +void QAmqpClientPrivate::_q_readyRead() +{ + Q_Q(QAmqpClient); + + while (socket->bytesAvailable() >= QAmqpFrame::HEADER_SIZE) { + unsigned char headerData[QAmqpFrame::HEADER_SIZE]; + socket->peek((char*)headerData, QAmqpFrame::HEADER_SIZE); + const quint32 payloadSize = qFromBigEndian(headerData + 3); + const qint64 readSize = QAmqpFrame::HEADER_SIZE + payloadSize + QAmqpFrame::FRAME_END_SIZE; + + if (socket->bytesAvailable() < readSize) + return; + + buffer.resize(readSize); + socket->read(buffer.data(), readSize); + const char *bufferData = buffer.constData(); + const quint8 type = *(quint8*)&bufferData[0]; + const quint8 magic = *(quint8*)&bufferData[QAmqpFrame::HEADER_SIZE + payloadSize]; + if (Q_UNLIKELY(magic != QAmqpFrame::FRAME_END)) { + close(QAMQP::UnexpectedFrameError, "wrong end of frame"); + return; + } + + QDataStream streamB(&buffer, QIODevice::ReadOnly); + switch (static_cast(type)) { + case QAmqpFrame::Method: + { + QAmqpMethodFrame frame; + streamB >> frame; + + if (Q_UNLIKELY(frame.size() > frameMax)) { + close(QAMQP::FrameError, "frame size too large"); + return; + } + + if (frame.methodClass() == QAmqpFrame::Connection) { + _q_method(frame); + } else { + foreach (QAmqpMethodFrameHandler *methodHandler, methodHandlersByChannel[frame.channel()]) + methodHandler->_q_method(frame); + } + } + break; + case QAmqpFrame::Header: + { + QAmqpContentFrame frame; + streamB >> frame; + + if (Q_UNLIKELY(frame.size() > frameMax)) { + close(QAMQP::FrameError, "frame size too large"); + return; + } else if (Q_UNLIKELY(frame.channel() <= 0)) { + close(QAMQP::ChannelError, "channel number must be greater than zero"); + return; + } + + foreach (QAmqpContentFrameHandler *methodHandler, contentHandlerByChannel[frame.channel()]) + methodHandler->_q_content(frame); + } + break; + case QAmqpFrame::Body: + { + QAmqpContentBodyFrame frame; + streamB >> frame; + + if (Q_UNLIKELY(frame.size() > frameMax)) { + close(QAMQP::FrameError, "frame size too large"); + return; + } else if (Q_UNLIKELY(frame.channel() <= 0)) { + close(QAMQP::ChannelError, "channel number must be greater than zero"); + return; + } + + foreach (QAmqpContentBodyFrameHandler *methodHandler, bodyHandlersByChannel[frame.channel()]) + methodHandler->_q_body(frame); + } + break; + case QAmqpFrame::Heartbeat: + { + QAmqpMethodFrame frame; + streamB >> frame; + + if (Q_UNLIKELY(frame.channel() != 0)) { + close(QAMQP::FrameError, "heartbeat must have channel id zero"); + return; + } + + qAmqpDebug("AMQP: Heartbeat"); + Q_EMIT q->heartbeat(); + } + break; + default: + qAmqpDebug() << "AMQP: Unknown frame type: " << type; + close(QAMQP::FrameError, "invalid frame type"); + return; + } + } +} + +void QAmqpClientPrivate::sendFrame(const QAmqpFrame &frame) +{ + if (socket->state() != QAbstractSocket::ConnectedState) { + qAmqpDebug() << Q_FUNC_INFO << "socket not connected: " << socket->state(); + return; + } + + QDataStream stream(socket); + stream << frame; +} + +void QAmqpClientPrivate::closeConnection() +{ + qAmqpDebug("AMQP: closing connection"); + + connected = false; + if (reconnectTimer) + reconnectTimer->stop(); + if (heartbeatTimer) + heartbeatTimer->stop(); + socket->disconnectFromHost(); +} + +bool QAmqpClientPrivate::_q_method(const QAmqpMethodFrame &frame) +{ + Q_ASSERT(frame.methodClass() == QAmqpFrame::Connection); + if (frame.methodClass() != QAmqpFrame::Connection) + return false; + + if (closed) { + if (frame.id() == QAmqpClientPrivate::miCloseOk) + closeOk(frame); + return false; + } + + switch (QAmqpClientPrivate::MethodId(frame.id())) { + case QAmqpClientPrivate::miStart: + start(frame); + break; + case QAmqpClientPrivate::miSecure: + secure(frame); + break; + case QAmqpClientPrivate::miTune: + tune(frame); + break; + case QAmqpClientPrivate::miOpenOk: + openOk(frame); + break; + case QAmqpClientPrivate::miClose: + close(frame); + break; + case QAmqpClientPrivate::miCloseOk: + closeOk(frame); + break; + default: + qAmqpDebug("Unknown method-id %d", frame.id()); + } + + return true; +} + +void QAmqpClientPrivate::start(const QAmqpMethodFrame &frame) +{ + QByteArray data = frame.arguments(); + QDataStream stream(&data, QIODevice::ReadOnly); + + quint8 version_major = 0; + quint8 version_minor = 0; + stream >> version_major >> version_minor; + + QAmqpTable table; + stream >> table; + + QStringList mechanisms = + QAmqpFrame::readAmqpField(stream, QAmqpMetaType::LongString).toString().split(' '); + QString locales = QAmqpFrame::readAmqpField(stream, QAmqpMetaType::LongString).toString(); + + qAmqpDebug("-> connection#start( version_major=%d, version_minor=%d, mechanisms=(%s), locales=%s )", + version_major, version_minor, qPrintable(mechanisms.join(",")), qPrintable(locales)); + + if (!mechanisms.contains(authenticator->type())) { + socket->disconnectFromHost(); + return; + } + + startOk(); +} + +void QAmqpClientPrivate::secure(const QAmqpMethodFrame &frame) +{ + Q_UNUSED(frame) + qAmqpDebug("-> connection#secure()"); +} + +void QAmqpClientPrivate::tune(const QAmqpMethodFrame &frame) +{ + QByteArray data = frame.arguments(); + QDataStream stream(&data, QIODevice::ReadOnly); + + qint16 channel_max = 0, + heartbeat_delay = 0; + qint32 frame_max = 0; + + stream >> channel_max; + stream >> frame_max; + stream >> heartbeat_delay; + + if (!frameMax) + frameMax = frame_max; + channelMax = !channelMax ? channel_max : qMax(channel_max, channelMax); + heartbeatDelay = !heartbeatDelay ? heartbeat_delay: heartbeatDelay; + + qAmqpDebug("-> connection#tune( channel_max=%d, frame_max=%d, heartbeat=%d )", + channelMax, frameMax, heartbeatDelay); + + if (heartbeatTimer) { + heartbeatTimer->setInterval(heartbeatDelay * 1000); + if (heartbeatTimer->interval()) + heartbeatTimer->start(); + else + heartbeatTimer->stop(); + } + + tuneOk(); + open(); +} + +void QAmqpClientPrivate::openOk(const QAmqpMethodFrame &frame) +{ + Q_Q(QAmqpClient); + Q_UNUSED(frame) + qAmqpDebug("-> connection#openOk()"); + connected = true; + Q_EMIT q->connected(); +} + +void QAmqpClientPrivate::closeOk(const QAmqpMethodFrame &frame) +{ + Q_UNUSED(frame) + qAmqpDebug("-> connection#closeOk()"); + closeConnection(); +} + +void QAmqpClientPrivate::close(const QAmqpMethodFrame &frame) +{ + Q_Q(QAmqpClient); + QByteArray data = frame.arguments(); + QDataStream stream(&data, QIODevice::ReadOnly); + qint16 code = 0, classId, methodId; + stream >> code; + QString text = QAmqpFrame::readAmqpField(stream, QAmqpMetaType::ShortString).toString(); + stream >> classId; + stream >> methodId; + + qAmqpDebug("-> connection#close( reply-code=%d, reply-text=%s, class-id=%d, method-id:%d )", + code, qPrintable(text), classId, methodId); + + QAMQP::Error checkError = static_cast(code); + if (checkError != QAMQP::NoError) { + error = checkError; + errorString = qPrintable(text); + Q_EMIT q->error(error); + + // if it was a force disconnect, simulate receiving a closeOk + if (checkError == QAMQP::ConnectionForcedError) { + closeConnection(); + if (autoReconnect) { + qAmqpDebug() << "trying to reconnect after: " << timeout << "ms"; + QTimer::singleShot(timeout, q, SLOT(_q_connect())); + } + + return; + } + } + + connected = false; + Q_EMIT q->disconnected(); + + // complete handshake + QAmqpMethodFrame closeOkFrame(QAmqpFrame::Connection, QAmqpClientPrivate::miCloseOk); + qAmqpDebug("<- connection#closeOk()"); + sendFrame(closeOkFrame); + closeConnection(); +} + +void QAmqpClientPrivate::startOk() +{ + QAmqpMethodFrame frame(QAmqpFrame::Connection, QAmqpClientPrivate::miStartOk); + QByteArray arguments; + QDataStream stream(&arguments, QIODevice::WriteOnly); + + QAmqpTable clientProperties; + clientProperties["version"] = QString(QAMQP_VERSION); + clientProperties["platform"] = QString("Qt %1").arg(qVersion()); + clientProperties["product"] = QString("QAMQP"); +#if QT_VERSION >= 0x060000 + clientProperties.insert(customProperties); +#else + clientProperties.unite(customProperties); +#endif + stream << clientProperties; + + authenticator->write(stream); + QAmqpFrame::writeAmqpField(stream, QAmqpMetaType::ShortString, QLatin1String("en_US")); + frame.setArguments(arguments); + + qAmqpDebug("<- connection#startOk()"); // @todo: fill this out + sendFrame(frame); +} + +void QAmqpClientPrivate::secureOk() +{ + qAmqpDebug("-> connection#secureOk()"); +} + +void QAmqpClientPrivate::tuneOk() +{ + QAmqpMethodFrame frame(QAmqpFrame::Connection, QAmqpClientPrivate::miTuneOk); + QByteArray arguments; + QDataStream stream(&arguments, QIODevice::WriteOnly); + + stream << qint16(channelMax); + stream << qint32(frameMax); + stream << qint16(heartbeatDelay); + + qAmqpDebug("<- connection#tuneOk( channelMax=%d, frameMax=%d, heartbeatDelay=%d )", + channelMax, frameMax, heartbeatDelay); + + frame.setArguments(arguments); + sendFrame(frame); +} + +void QAmqpClientPrivate::open() +{ + QAmqpMethodFrame frame(QAmqpFrame::Connection, QAmqpClientPrivate::miOpen); + QByteArray arguments; + QDataStream stream(&arguments, QIODevice::WriteOnly); + + QAmqpFrame::writeAmqpField(stream, QAmqpMetaType::ShortString, virtualHost); + + stream << qint8(0); + stream << qint8(0); + + qAmqpDebug("<- connection#open( virtualHost=%s, reserved-1=%d, reserved-2=%d )", + qPrintable(virtualHost), 0, 0); + + frame.setArguments(arguments); + sendFrame(frame); +} + +void QAmqpClientPrivate::close(int code, const QString &text, int classId, int methodId) +{ + QByteArray arguments; + QDataStream stream(&arguments, QIODevice::WriteOnly); + stream << qint16(code); + QAmqpFrame::writeAmqpField(stream, QAmqpMetaType::ShortString, text); + stream << qint16(classId); + stream << qint16(methodId); + + qAmqpDebug("<- connection#close( reply-code=%d, reply-text=%s, class-id=%d, method-id:%d )", + code, qPrintable(text), classId, methodId); + + QAmqpMethodFrame frame(QAmqpFrame::Connection, QAmqpClientPrivate::miClose); + frame.setArguments(arguments); + sendFrame(frame); +} + +////////////////////////////////////////////////////////////////////////// + +QAmqpClient::QAmqpClient(QObject *parent) + : QObject(parent), + d_ptr(new QAmqpClientPrivate(this)) +{ + Q_D(QAmqpClient); + d->init(); +} + +QAmqpClient::QAmqpClient(QAmqpClientPrivate *dd, QObject *parent) + : QObject(parent), + d_ptr(dd) +{ +} + +QAmqpClient::~QAmqpClient() +{ + Q_D(QAmqpClient); + if (d->connected) + d->_q_disconnect(); +} + +bool QAmqpClient::isConnected() const +{ + Q_D(const QAmqpClient); + return d->connected; +} + +quint16 QAmqpClient::port() const +{ + Q_D(const QAmqpClient); + return d->port; +} + +void QAmqpClient::setPort(quint16 port) +{ + Q_D(QAmqpClient); + d->port = port; +} + +QString QAmqpClient::host() const +{ + Q_D(const QAmqpClient); + return d->host; +} + +void QAmqpClient::setHost(const QString &host) +{ + Q_D(QAmqpClient); + d->host = host; +} + +QString QAmqpClient::virtualHost() const +{ + Q_D(const QAmqpClient); + return d->virtualHost; +} + +void QAmqpClient::setVirtualHost(const QString &virtualHost) +{ + Q_D(QAmqpClient); + d->virtualHost = virtualHost; +} + +QString QAmqpClient::username() const +{ + Q_D(const QAmqpClient); + const QAmqpAuthenticator *auth = d->authenticator.data(); + if (auth && auth->type() == QLatin1String("AMQPLAIN")) { + const QAmqpPlainAuthenticator *a = static_cast(auth); + return a->login(); + } + + return QString(); +} + +void QAmqpClient::setUsername(const QString &username) +{ + Q_D(QAmqpClient); + d->setUsername(username); +} + +QString QAmqpClient::password() const +{ + Q_D(const QAmqpClient); + const QAmqpAuthenticator *auth = d->authenticator.data(); + if (auth && auth->type() == QLatin1String("AMQPLAIN")) { + const QAmqpPlainAuthenticator *a = static_cast(auth); + return a->password(); + } + + return QString(); +} + +void QAmqpClient::setPassword(const QString &password) +{ + Q_D(QAmqpClient); + d->setPassword(password); +} + +QAmqpExchange *QAmqpClient::createExchange(int channelNumber) +{ + return createExchange(QString(), channelNumber); +} + +QAmqpExchange *QAmqpClient::createExchange(const QString &name, int channelNumber) +{ + Q_D(QAmqpClient); + QAmqpExchange *exchange; + if (d->exchanges.contains(name)) { + exchange = qobject_cast(d->exchanges.get(name)); + if (exchange) + return exchange; + } + + exchange = new QAmqpExchange(channelNumber, this); + d->methodHandlersByChannel[exchange->channelNumber()].append(exchange->d_func()); + connect(this, SIGNAL(connected()), exchange, SLOT(_q_open())); + connect(this, SIGNAL(disconnected()), exchange, SLOT(_q_disconnected())); + exchange->d_func()->open(); + + if (!name.isEmpty()) + exchange->setName(name); + d->exchanges.put(exchange); + return exchange; +} + +QAmqpQueue *QAmqpClient::createQueue(int channelNumber) +{ + return createQueue(QString(), channelNumber); +} + +QAmqpQueue *QAmqpClient::createQueue(const QString &name, int channelNumber) +{ + Q_D(QAmqpClient); + QAmqpQueue *queue; + if (d->queues.contains(name)) { + queue = qobject_cast(d->queues.get(name)); + if (queue) + return queue; + } + + queue = new QAmqpQueue(channelNumber, this); + d->methodHandlersByChannel[queue->channelNumber()].append(queue->d_func()); + d->contentHandlerByChannel[queue->channelNumber()].append(queue->d_func()); + d->bodyHandlersByChannel[queue->channelNumber()].append(queue->d_func()); + connect(this, SIGNAL(connected()), queue, SLOT(_q_open())); + connect(this, SIGNAL(disconnected()), queue, SLOT(_q_disconnected())); + queue->d_func()->open(); + + if (!name.isEmpty()) + queue->setName(name); + d->queues.put(queue); + return queue; +} + +void QAmqpClient::setAuth(QAmqpAuthenticator *authenticator) +{ + Q_D(QAmqpClient); + d->authenticator = QSharedPointer(authenticator); +} + +QAmqpAuthenticator *QAmqpClient::auth() const +{ + Q_D(const QAmqpClient); + return d->authenticator.data(); +} + +bool QAmqpClient::autoReconnect() const +{ + Q_D(const QAmqpClient); + return d->autoReconnect; +} + +void QAmqpClient::setAutoReconnect(bool value, int timeout) +{ + Q_D(QAmqpClient); + d->autoReconnect = value; + + if((value == true) && (timeout > 0)) + { + d->timeout = timeout; + d->reconnectFixedTimeout = true; + } + else + { + d->timeout = 0; + d->reconnectFixedTimeout = false; + } +} + +qint16 QAmqpClient::channelMax() const +{ + Q_D(const QAmqpClient); + return d->channelMax; +} + +void QAmqpClient::setChannelMax(qint16 channelMax) +{ + Q_D(QAmqpClient); + if (d->connected) { + qAmqpDebug() << Q_FUNC_INFO << "can't modify value while connected"; + return; + } + + d->channelMax = channelMax; +} + +qint32 QAmqpClient::frameMax() const +{ + Q_D(const QAmqpClient); + return d->frameMax; +} + +void QAmqpClient::setFrameMax(qint32 frameMax) +{ + Q_D(QAmqpClient); + if (d->connected) { + qAmqpDebug() << Q_FUNC_INFO << "can't modify value while connected"; + return; + } + + d->frameMax = qMax(frameMax, AMQP_FRAME_MIN_SIZE); +} + +qint16 QAmqpClient::heartbeatDelay() const +{ + Q_D(const QAmqpClient); + return d->heartbeatDelay; +} + +void QAmqpClient::setHeartbeatDelay(qint16 delay) +{ + Q_D(QAmqpClient); + if (d->connected) { + qAmqpDebug() << Q_FUNC_INFO << "can't modify value while connected"; + return; + } + + d->heartbeatDelay = delay; +} + +int QAmqpClient::writeTimeout() const +{ + return QAmqpFrame::writeTimeout(); +} + +void QAmqpClient::setWriteTimeout(int msecs) +{ + QAmqpFrame::setWriteTimeout(msecs); +} + +void QAmqpClient::addCustomProperty(const QString &name, const QString &value) +{ + Q_D(QAmqpClient); + d->customProperties.insert(name, value); +} + +QString QAmqpClient::customProperty(const QString &name) const +{ + Q_D(const QAmqpClient); + return d->customProperties.value(name).toString(); +} + +QAbstractSocket::SocketError QAmqpClient::socketError() const +{ + Q_D(const QAmqpClient); + return d->socket->error(); +} + +QAbstractSocket::SocketState QAmqpClient::socketState() const +{ + Q_D(const QAmqpClient); + return d->socket->state(); +} + +QAMQP::Error QAmqpClient::error() const +{ + Q_D(const QAmqpClient); + return d->error; +} + +QString QAmqpClient::errorString() const +{ + Q_D(const QAmqpClient); + return d->errorString; +} + +QSslConfiguration QAmqpClient::sslConfiguration() const +{ + Q_D(const QAmqpClient); + return d->socket->sslConfiguration(); +} + +void QAmqpClient::setSslConfiguration(const QSslConfiguration &config) +{ + Q_D(QAmqpClient); + if (!config.isNull()) { + d->useSsl = true; + d->port = AMQP_SSL_PORT; + d->socket->setSslConfiguration(config); + } +} + +QString QAmqpClient::gitVersion() +{ + return QString(GIT_VERSION); +} + +void QAmqpClient::ignoreSslErrors(const QList &errors) +{ + Q_D(QAmqpClient); + d->socket->ignoreSslErrors(errors); +} + +void QAmqpClient::connectToHost(const QString &uri) +{ + Q_D(QAmqpClient); + if (uri.isEmpty()) { + d->_q_connect(); + return; + } + + d->parseConnectionString(uri); + d->_q_connect(); +} + +void QAmqpClient::connectToHost(const QHostAddress &address, quint16 port) +{ + Q_D(QAmqpClient); + d->host = address.toString(); + d->port = port; + d->_q_connect(); +} + +void QAmqpClient::disconnectFromHost() +{ + Q_D(QAmqpClient); + d->_q_disconnect(); +} + +void QAmqpClient::abort() +{ + Q_D(QAmqpClient); + d->closeConnection(); +} + +#include "moc_qamqpclient.cpp" diff --git a/qamqp/src/qamqpclient.h b/qamqp/src/qamqpclient.h new file mode 100644 index 0000000..62c6cc4 --- /dev/null +++ b/qamqp/src/qamqpclient.h @@ -0,0 +1,146 @@ +/* + * Copyright (C) 2012-2014 Alexey Shcherbakov + * Copyright (C) 2014-2015 Matt Broadstone + * Contact: https://github.com/mbroadst/qamqp + * + * This file is part of the QAMQP Library. + * + * This library is free software; 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. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + */ +#ifndef QAMQPCLIENT_H +#define QAMQPCLIENT_H + +#include +#include +#include +#include +#include + +#include "qamqpglobal.h" + +class QAmqpExchange; +class QAmqpQueue; +class QAmqpAuthenticator; +class QAmqpClientPrivate; +class QAMQP_EXPORT QAmqpClient : public QObject +{ + Q_OBJECT + Q_PROPERTY(quint32 port READ port WRITE setPort) + Q_PROPERTY(QString host READ host WRITE setHost) + Q_PROPERTY(QString virtualHost READ virtualHost WRITE setVirtualHost) + Q_PROPERTY(QString user READ username WRITE setUsername) + Q_PROPERTY(QString password READ password WRITE setPassword) + Q_PROPERTY(bool autoReconnect READ autoReconnect WRITE setAutoReconnect) + Q_PROPERTY(qint16 channelMax READ channelMax WRITE setChannelMax) + Q_PROPERTY(qint32 frameMax READ frameMax WRITE setFrameMax) + Q_PROPERTY(qint16 heartbeatDelay READ heartbeatDelay() WRITE setHeartbeatDelay) + +public: + explicit QAmqpClient(QObject *parent = 0); + ~QAmqpClient(); + + // properties + quint16 port() const; + void setPort(quint16 port); + + QString host() const; + void setHost(const QString &host); + + QString virtualHost() const; + void setVirtualHost(const QString &virtualHost); + + QString username() const; + void setUsername(const QString &username); + + QString password() const; + void setPassword(const QString &password); + + void setAuth(QAmqpAuthenticator *auth); + QAmqpAuthenticator *auth() const; + + bool autoReconnect() const; + void setAutoReconnect(bool value, int timeout = 0); + + bool isConnected() const; + + qint16 channelMax() const; + void setChannelMax(qint16 channelMax); + + qint32 frameMax() const; + void setFrameMax(qint32 frameMax); + + qint16 heartbeatDelay() const; + void setHeartbeatDelay(qint16 delay); + + int writeTimeout() const; + void setWriteTimeout(int msecs); + + void addCustomProperty(const QString &name, const QString &value); + QString customProperty(const QString &name) const; + + QAbstractSocket::SocketError socketError() const; + QAbstractSocket::SocketState socketState() const; + + QAMQP::Error error() const; + QString errorString() const; + + QSslConfiguration sslConfiguration() const; + void setSslConfiguration(const QSslConfiguration &config); + + static QString gitVersion(); + + // channels + QAmqpExchange *createExchange(int channelNumber = -1); + QAmqpExchange *createExchange(const QString &name, int channelNumber = -1); + + QAmqpQueue *createQueue(int channelNumber = -1); + QAmqpQueue *createQueue(const QString &name, int channelNumber = -1); + + // methods + void connectToHost(const QString &uri = QString()); + void connectToHost(const QHostAddress &address, quint16 port = AMQP_PORT); + void disconnectFromHost(); + void abort(); + +Q_SIGNALS: + void connected(); + void disconnected(); + void heartbeat(); + void error(QAMQP::Error error); + void socketErrorOccurred(QAbstractSocket::SocketError error); + void socketStateChanged(QAbstractSocket::SocketState state); + void sslErrors(const QList &errors); + +public Q_SLOTS: + void ignoreSslErrors(const QList &errors); + +protected: + QAmqpClient(QAmqpClientPrivate *dd, QObject *parent = 0); + + Q_DISABLE_COPY(QAmqpClient) + Q_DECLARE_PRIVATE(QAmqpClient) + QScopedPointer d_ptr; + +private: + Q_PRIVATE_SLOT(d_func(), void _q_socketConnected()) + Q_PRIVATE_SLOT(d_func(), void _q_socketDisconnected()) + Q_PRIVATE_SLOT(d_func(), void _q_readyRead()) + Q_PRIVATE_SLOT(d_func(), void _q_socketError(QAbstractSocket::SocketError error)) + Q_PRIVATE_SLOT(d_func(), void _q_heartbeat()) + Q_PRIVATE_SLOT(d_func(), void _q_connect()) + Q_PRIVATE_SLOT(d_func(), void _q_disconnect()) + + friend class QAmqpChannelPrivate; + friend class QAmqpQueuePrivate; + +}; + +#endif // QAMQPCLIENT_H diff --git a/qamqp/src/qamqpclient_p.h b/qamqp/src/qamqpclient_p.h new file mode 100644 index 0000000..397e426 --- /dev/null +++ b/qamqp/src/qamqpclient_p.h @@ -0,0 +1,120 @@ +#ifndef QAMQPCLIENT_P_H +#define QAMQPCLIENT_P_H + +#include +#include +#include +#include +#include + +#include "qamqpchannelhash_p.h" +#include "qamqpglobal.h" +#include "qamqpauthenticator.h" +#include "qamqptable.h" +#include "qamqpframe_p.h" + +#define METHOD_ID_ENUM(name, id) name = id, name ## Ok + +class QTimer; +class QSslSocket; +class QAmqpClient; +class QAmqpQueue; +class QAmqpExchange; +class QAmqpConnection; +class QAmqpAuthenticator; +class QAMQP_EXPORT QAmqpClientPrivate : public QAmqpMethodFrameHandler +{ +public: + enum MethodId { + METHOD_ID_ENUM(miStart, 10), + METHOD_ID_ENUM(miSecure, 20), + METHOD_ID_ENUM(miTune, 30), + METHOD_ID_ENUM(miOpen, 40), + METHOD_ID_ENUM(miClose, 50) + }; + + QAmqpClientPrivate(QAmqpClient *q); + virtual ~QAmqpClientPrivate(); + + virtual void init(); + virtual void initSocket(); + void resetChannelState(); + void setUsername(const QString &username); + void setPassword(const QString &password); + void parseConnectionString(const QString &uri); + void sendFrame(const QAmqpFrame &frame); + + void closeConnection(); + + // private slots + void _q_socketConnected(); + void _q_socketDisconnected(); + void _q_readyRead(); + void _q_socketError(QAbstractSocket::SocketError error); + void _q_heartbeat(); + virtual void _q_connect(); + void _q_disconnect(); + + virtual bool _q_method(const QAmqpMethodFrame &frame); + + // method handlers, FROM server + void start(const QAmqpMethodFrame &frame); + void secure(const QAmqpMethodFrame &frame); + void tune(const QAmqpMethodFrame &frame); + void openOk(const QAmqpMethodFrame &frame); + void closeOk(const QAmqpMethodFrame &frame); + + // method handlers, TO server + void startOk(); + void secureOk(); + void tuneOk(); + void open(); + + // method handlers, BOTH ways + void close(int code, const QString &text, int classId = 0, int methodId = 0); + void close(const QAmqpMethodFrame &frame); + + quint16 port; + QString host; + QString virtualHost; + + QSharedPointer authenticator; + + // Network + QByteArray buffer; + bool autoReconnect; + bool reconnectFixedTimeout; + int timeout; + bool connecting; + bool useSsl; + + QSslSocket *socket; + QHash > methodHandlersByChannel; + QHash > contentHandlerByChannel; + QHash > bodyHandlersByChannel; + + // Connection + bool closed; + bool connected; + QPointer heartbeatTimer; + QPointer reconnectTimer; + QAmqpTable customProperties; + qint16 channelMax; + qint16 heartbeatDelay; + qint32 frameMax; + + QAMQP::Error error; + QString errorString; + + /*! Exchange objects */ + QAmqpChannelHash exchanges; + + /*! Named queue objects */ + QAmqpChannelHash queues; + + QAmqpClient * const q_ptr; + Q_DECLARE_PUBLIC(QAmqpClient) + +}; + +#endif // QAMQPCLIENT_P_H diff --git a/qamqp/src/qamqpexchange.cpp b/qamqp/src/qamqpexchange.cpp new file mode 100644 index 0000000..e723c81 --- /dev/null +++ b/qamqp/src/qamqpexchange.cpp @@ -0,0 +1,369 @@ +#include +#include +#include +#include + +#include "qamqpexchange.h" +#include "qamqpexchange_p.h" +#include "qamqpqueue.h" +#include "qamqpglobal.h" +#include "qamqpclient.h" + +QString QAmqpExchangePrivate::typeToString(QAmqpExchange::ExchangeType type) +{ + switch (type) { + case QAmqpExchange::Direct: return QLatin1String("direct"); + case QAmqpExchange::FanOut: return QLatin1String("fanout"); + case QAmqpExchange::Topic: return QLatin1String("topic"); + case QAmqpExchange::Headers: return QLatin1String("headers"); + } + + return QLatin1String("direct"); +} + +QAmqpExchangePrivate::QAmqpExchangePrivate(QAmqpExchange *q) + : QAmqpChannelPrivate(q), + delayedDeclare(false), + declared(false), + nextDeliveryTag(0) +{ +} + +void QAmqpExchangePrivate::resetInternalState() +{ + QAmqpChannelPrivate::resetInternalState(); + delayedDeclare = false; + declared = false; + nextDeliveryTag = 0; +} + +void QAmqpExchangePrivate::declare() +{ + if (!opened) { + delayedDeclare = true; + return; + } + + if (name.isEmpty()) { + qAmqpDebug() << Q_FUNC_INFO << "attempting to declare an unnamed exchange, aborting..."; + return; + } + + QAmqpMethodFrame frame(QAmqpFrame::Exchange, QAmqpExchangePrivate::miDeclare); + frame.setChannel(channelNumber); + + QByteArray args; + QDataStream stream(&args, QIODevice::WriteOnly); + + stream << qint16(0); //reserved 1 + QAmqpFrame::writeAmqpField(stream, QAmqpMetaType::ShortString, name); + QAmqpFrame::writeAmqpField(stream, QAmqpMetaType::ShortString, type); + + stream << qint8(options); + QAmqpFrame::writeAmqpField(stream, QAmqpMetaType::Hash, arguments); + + qAmqpDebug("<- exchange#declare( name=%s, type=%s, passive=%d, durable=%d, no-wait=%d )", + qPrintable(name), qPrintable(type), + options.testFlag(QAmqpExchange::Passive), options.testFlag(QAmqpExchange::Durable), + options.testFlag(QAmqpExchange::NoWait)); + + frame.setArguments(args); + sendFrame(frame); + delayedDeclare = false; +} + +bool QAmqpExchangePrivate::_q_method(const QAmqpMethodFrame &frame) +{ + Q_Q(QAmqpExchange); + if (QAmqpChannelPrivate::_q_method(frame)) + return true; + + if (frame.methodClass() == QAmqpFrame::Basic) { + switch (frame.id()) { + case bmAck: + case bmNack: + handleAckOrNack(frame); + break; + case bmReturn: basicReturn(frame); break; + + default: + break; + } + + return true; + } + + if (frame.methodClass() == QAmqpFrame::Confirm) { + if (frame.id() == cmConfirmOk) { + Q_EMIT q->confirmsEnabled(); + return true; + } + } + + if (frame.methodClass() == QAmqpFrame::Exchange) { + switch (frame.id()) { + case miDeclareOk: declareOk(frame); break; + case miDeleteOk: deleteOk(frame); break; + + default: + break; + } + + return true; + } + + return false; +} + +void QAmqpExchangePrivate::declareOk(const QAmqpMethodFrame &frame) +{ + Q_UNUSED(frame) + Q_Q(QAmqpExchange); + qAmqpDebug("-> exchange[ %s ]#declareOk()", qPrintable(name)); + declared = true; + Q_EMIT q->declared(); +} + +void QAmqpExchangePrivate::deleteOk(const QAmqpMethodFrame &frame) +{ + Q_UNUSED(frame) + Q_Q(QAmqpExchange); + qAmqpDebug("-> exchange#deleteOk[ %s ]()", qPrintable(name)); + declared = false; + Q_EMIT q->removed(); +} + +void QAmqpExchangePrivate::_q_disconnected() +{ + QAmqpChannelPrivate::_q_disconnected(); + qAmqpDebug() << "exchange disconnected: " << name; + delayedDeclare = false; + declared = false; + unconfirmedDeliveryTags.clear(); +} + +void QAmqpExchangePrivate::basicReturn(const QAmqpMethodFrame &frame) +{ + Q_Q(QAmqpExchange); + QByteArray data = frame.arguments(); + QDataStream stream(&data, QIODevice::ReadOnly); + + quint16 replyCode; + stream >> replyCode; + QString replyText = QAmqpFrame::readAmqpField(stream, QAmqpMetaType::ShortString).toString(); + QString exchangeName = QAmqpFrame::readAmqpField(stream, QAmqpMetaType::ShortString).toString(); + QString routingKey = QAmqpFrame::readAmqpField(stream, QAmqpMetaType::ShortString).toString(); + + QAMQP::Error checkError = static_cast(replyCode); + if (checkError != QAMQP::NoError) { + error = checkError; + errorString = qPrintable(replyText); + Q_EMIT q->error(error); + } + + qAmqpDebug("-> basic#return( reply-code=%d, reply-text=%s, exchange=%s, routing-key=%s )", + replyCode, qPrintable(replyText), qPrintable(exchangeName), qPrintable(routingKey)); +} + +void QAmqpExchangePrivate::handleAckOrNack(const QAmqpMethodFrame &frame) +{ + Q_Q(QAmqpExchange); + QByteArray data = frame.arguments(); + QDataStream stream(&data, QIODevice::ReadOnly); + + qlonglong deliveryTag = + QAmqpFrame::readAmqpField(stream, QAmqpMetaType::LongLongUint).toLongLong(); + bool multiple = QAmqpFrame::readAmqpField(stream, QAmqpMetaType::Boolean).toBool(); + if (frame.id() == QAmqpExchangePrivate::bmAck) { + if (deliveryTag == 0) { + unconfirmedDeliveryTags.clear(); + } else { + int idx = unconfirmedDeliveryTags.indexOf(deliveryTag); + if (idx == -1) { + return; + } + + if (multiple) { + unconfirmedDeliveryTags.remove(0, idx + 1); + } else { + unconfirmedDeliveryTags.remove(idx); + } + } + + if (unconfirmedDeliveryTags.isEmpty()) + Q_EMIT q->allMessagesDelivered(); + + } else { + qAmqpDebug() << "nacked(" << deliveryTag << "), multiple=" << multiple; + } +} + +////////////////////////////////////////////////////////////////////////// + +QAmqpExchange::QAmqpExchange(int channelNumber, QAmqpClient *parent) + : QAmqpChannel(new QAmqpExchangePrivate(this), parent) +{ + Q_D(QAmqpExchange); + d->init(channelNumber, parent); +} + +QAmqpExchange::~QAmqpExchange() +{ +} + +void QAmqpExchange::channelOpened() +{ + Q_D(QAmqpExchange); + if (d->delayedDeclare) + d->declare(); +} + +void QAmqpExchange::channelClosed() +{ +} + +QAmqpExchange::ExchangeOptions QAmqpExchange::options() const +{ + Q_D(const QAmqpExchange); + return d->options; +} + +QString QAmqpExchange::type() const +{ + Q_D(const QAmqpExchange); + return d->type; +} + +bool QAmqpExchange::isDeclared() const +{ + Q_D(const QAmqpExchange); + return d->declared; +} + +void QAmqpExchange::declare(ExchangeType type, ExchangeOptions options, const QAmqpTable &args) +{ + declare(QAmqpExchangePrivate::typeToString(type), options, args); +} + +void QAmqpExchange::declare(const QString &type, ExchangeOptions options, const QAmqpTable &args) +{ + Q_D(QAmqpExchange); + d->type = type; + d->options = options; + d->arguments = args; + d->declare(); +} + +void QAmqpExchange::remove(int options) +{ + Q_D(QAmqpExchange); + QAmqpMethodFrame frame(QAmqpFrame::Exchange, QAmqpExchangePrivate::miDelete); + frame.setChannel(d->channelNumber); + + QByteArray arguments; + QDataStream stream(&arguments, QIODevice::WriteOnly); + + stream << qint16(0); //reserved 1 + QAmqpFrame::writeAmqpField(stream, QAmqpMetaType::ShortString, d->name); + stream << qint8(options); + + qAmqpDebug("<- exchange#delete( exchange=%s, if-unused=%d, no-wait=%d )", + qPrintable(d->name), options & QAmqpExchange::roIfUnused, options & QAmqpExchange::roNoWait); + + frame.setArguments(arguments); + d->sendFrame(frame); +} + +void QAmqpExchange::publish(const QString &message, const QString &routingKey, + const QAmqpMessage::PropertyHash &properties, int publishOptions) +{ + publish(message.toUtf8(), routingKey, QLatin1String("text.plain"), + QAmqpTable(), properties, publishOptions); +} + +void QAmqpExchange::publish(const QByteArray &message, const QString &routingKey, + const QString &mimeType, const QAmqpMessage::PropertyHash &properties, + int publishOptions) +{ + publish(message, routingKey, mimeType, QAmqpTable(), properties, publishOptions); +} + +void QAmqpExchange::publish(const QByteArray &message, const QString &routingKey, + const QString &mimeType, const QAmqpTable &headers, + const QAmqpMessage::PropertyHash &properties, int publishOptions) +{ + Q_D(QAmqpExchange); + if (d->nextDeliveryTag > 0) { + d->unconfirmedDeliveryTags.append(d->nextDeliveryTag); + d->nextDeliveryTag++; + } + + QAmqpMethodFrame frame(QAmqpFrame::Basic, QAmqpExchangePrivate::bmPublish); + frame.setChannel(d->channelNumber); + + QByteArray arguments; + QDataStream out(&arguments, QIODevice::WriteOnly); + + out << qint16(0); //reserved 1 + QAmqpFrame::writeAmqpField(out, QAmqpMetaType::ShortString, d->name); + QAmqpFrame::writeAmqpField(out, QAmqpMetaType::ShortString, routingKey); + out << qint8(publishOptions); + + qAmqpDebug("<- basic#publish( exchange=%s, routing-key=%s, mandatory=%d, immediate=%d )", + qPrintable(d->name), qPrintable(routingKey), + publishOptions & QAmqpExchange::poMandatory, publishOptions & QAmqpExchange::poImmediate); + + frame.setArguments(arguments); + d->sendFrame(frame); + + QAmqpContentFrame content(QAmqpFrame::Basic); + content.setChannel(d->channelNumber); + content.setProperty(QAmqpMessage::ContentType, mimeType); + content.setProperty(QAmqpMessage::ContentEncoding, "utf-8"); + content.setProperty(QAmqpMessage::Headers, headers); + + QAmqpMessage::PropertyHash::ConstIterator it; + QAmqpMessage::PropertyHash::ConstIterator itEnd = properties.constEnd(); + for (it = properties.constBegin(); it != itEnd; ++it) + content.setProperty(it.key(), it.value()); + content.setBodySize(message.size()); + d->sendFrame(content); + + int fullSize = message.size(); + for (int sent = 0; sent < fullSize; sent += (d->client->frameMax() - 7)) { + QAmqpContentBodyFrame body; + QByteArray partition = message.mid(sent, (d->client->frameMax() - 7)); + body.setChannel(d->channelNumber); + body.setBody(partition); + d->sendFrame(body); + } +} + +void QAmqpExchange::enableConfirms(bool noWait) +{ + Q_D(QAmqpExchange); + QAmqpMethodFrame frame(QAmqpFrame::Confirm, QAmqpExchangePrivate::cmConfirm); + frame.setChannel(d->channelNumber); + + QByteArray arguments; + QDataStream stream(&arguments, QIODevice::WriteOnly); + stream << qint8(noWait ? 1 : 0); + + frame.setArguments(arguments); + d->sendFrame(frame); + + // for tracking acks and nacks + if (d->nextDeliveryTag == 0) d->nextDeliveryTag = 1; +} + +bool QAmqpExchange::waitForConfirms(int msecs) +{ + Q_D(QAmqpExchange); + + QEventLoop loop; + connect(this, SIGNAL(allMessagesDelivered()), &loop, SLOT(quit())); + QTimer::singleShot(msecs, &loop, SLOT(quit())); + loop.exec(); + + return (d->unconfirmedDeliveryTags.isEmpty()); +} diff --git a/qamqp/src/qamqpexchange.h b/qamqp/src/qamqpexchange.h new file mode 100644 index 0000000..8247913 --- /dev/null +++ b/qamqp/src/qamqpexchange.h @@ -0,0 +1,123 @@ +/* + * Copyright (C) 2012-2014 Alexey Shcherbakov + * Copyright (C) 2014-2015 Matt Broadstone + * Contact: https://github.com/mbroadst/qamqp + * + * This file is part of the QAMQP Library. + * + * This library is free software; 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. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + */ +#ifndef QAMQPEXCHANGE_H +#define QAMQPEXCHANGE_H + +#include "qamqptable.h" +#include "qamqpchannel.h" +#include "qamqpmessage.h" + +class QAmqpClient; +class QAmqpQueue; +class QAmqpClientPrivate; +class QAmqpExchangePrivate; +class QAMQP_EXPORT QAmqpExchange : public QAmqpChannel +{ + Q_OBJECT + Q_PROPERTY(QString type READ type CONSTANT) + Q_PROPERTY(ExchangeOptions options READ options CONSTANT) + +public: + virtual ~QAmqpExchange(); + + enum ExchangeType { + Direct, + FanOut, + Topic, + Headers + }; + QString type() const; + + enum PublishOption { + poNoOptions = 0x0, + poMandatory = 0x01, + poImmediate = 0x02 + }; + Q_DECLARE_FLAGS(PublishOptions, PublishOption) + + enum RemoveOption { + roForce = 0x0, + roIfUnused = 0x01, + roNoWait = 0x04 + }; + Q_DECLARE_FLAGS(RemoveOptions, RemoveOption) + + enum ExchangeOption { + NoOptions = 0x0, + Passive = 0x01, + Durable = 0x02, + AutoDelete = 0x04, + Internal = 0x08, + NoWait = 0x10 + }; + Q_DECLARE_FLAGS(ExchangeOptions, ExchangeOption) + ExchangeOptions options() const; + Q_ENUM(ExchangeOptions) + + bool isDeclared() const; + + void enableConfirms(bool noWait = false); + bool waitForConfirms(int msecs = 30000); + +Q_SIGNALS: + void declared(); + void removed(); + + void confirmsEnabled(); + void allMessagesDelivered(); + +public Q_SLOTS: + // AMQP Exchange + void declare(QAmqpExchange::ExchangeType type = Direct, + QAmqpExchange::ExchangeOptions options = NoOptions, + const QAmqpTable &args = QAmqpTable()); + void declare(const QString &type, + QAmqpExchange::ExchangeOptions options = NoOptions, + const QAmqpTable &args = QAmqpTable()); + void remove(int options = roIfUnused|roNoWait); + + // AMQP Basic + void publish(const QString &message, const QString &routingKey, + const QAmqpMessage::PropertyHash &properties = QAmqpMessage::PropertyHash(), + int publishOptions = poNoOptions); + void publish(const QByteArray &message, const QString &routingKey, const QString &mimeType, + const QAmqpMessage::PropertyHash &properties = QAmqpMessage::PropertyHash(), + int publishOptions = poNoOptions); + void publish(const QByteArray &message, const QString &routingKey, + const QString &mimeType, const QAmqpTable &headers, + const QAmqpMessage::PropertyHash &properties = QAmqpMessage::PropertyHash(), + int publishOptions = poNoOptions); + +protected: + virtual void channelOpened(); + virtual void channelClosed(); + +private: + explicit QAmqpExchange(int channelNumber = -1, QAmqpClient *parent = 0); + + Q_DISABLE_COPY(QAmqpExchange) + Q_DECLARE_PRIVATE(QAmqpExchange) + friend class QAmqpClient; + friend class QAmqpClientPrivate; + +}; + +Q_DECLARE_OPERATORS_FOR_FLAGS(QAmqpExchange::ExchangeOptions) +Q_DECLARE_METATYPE(QAmqpExchange::ExchangeType) + +#endif // QAMQPEXCHANGE_H diff --git a/qamqp/src/qamqpexchange_p.h b/qamqp/src/qamqpexchange_p.h new file mode 100644 index 0000000..9e0e929 --- /dev/null +++ b/qamqp/src/qamqpexchange_p.h @@ -0,0 +1,45 @@ +#ifndef QAMQPEXCHANGE_P_H +#define QAMQPEXCHANGE_P_H + +#include "qamqptable.h" +#include "qamqpexchange.h" +#include "qamqpchannel_p.h" + +class QAmqpExchangePrivate: public QAmqpChannelPrivate +{ +public: + enum MethodId { + METHOD_ID_ENUM(miDeclare, 10), + METHOD_ID_ENUM(miDelete, 20) + }; + + enum ConfirmMethod { + METHOD_ID_ENUM(cmConfirm, 10) + }; + + QAmqpExchangePrivate(QAmqpExchange *q); + static QString typeToString(QAmqpExchange::ExchangeType type); + + virtual void resetInternalState(); + + void declare(); + + // method handler related + virtual void _q_disconnected(); + virtual bool _q_method(const QAmqpMethodFrame &frame); + void declareOk(const QAmqpMethodFrame &frame); + void deleteOk(const QAmqpMethodFrame &frame); + void basicReturn(const QAmqpMethodFrame &frame); + void handleAckOrNack(const QAmqpMethodFrame &frame); + + QString type; + QAmqpExchange::ExchangeOptions options; + bool delayedDeclare; + bool declared; + qlonglong nextDeliveryTag; + QVector unconfirmedDeliveryTags; + + Q_DECLARE_PUBLIC(QAmqpExchange) +}; + +#endif // QAMQPEXCHANGE_P_H diff --git a/qamqp/src/qamqpframe.cpp b/qamqp/src/qamqpframe.cpp new file mode 100644 index 0000000..13cdf5c --- /dev/null +++ b/qamqp/src/qamqpframe.cpp @@ -0,0 +1,486 @@ +#include +#include +#include +#include + +#include "qamqptable.h" +#include "qamqpglobal.h" +#include "qamqpframe_p.h" + +QReadWriteLock QAmqpFrame::lock_; +int QAmqpFrame::writeTimeout_ = 1000; + +QAmqpFrame::QAmqpFrame(FrameType type) + : size_(0), + type_(type), + channel_(0) +{ +} + +QAmqpFrame::FrameType QAmqpFrame::type() const +{ + return static_cast(type_); +} + +QAmqpFrame::~QAmqpFrame() +{ +} + +void QAmqpFrame::setChannel(quint16 channel) +{ + channel_ = channel; +} + +int QAmqpFrame::writeTimeout() +{ + QReadLocker locker(&lock_); + return writeTimeout_; +} + +void QAmqpFrame::setWriteTimeout(int msecs) +{ + QWriteLocker locker(&lock_); + writeTimeout_ = msecs; +} + +quint16 QAmqpFrame::channel() const +{ + return channel_; +} + +qint32 QAmqpFrame::size() const +{ + return 0; +} + +/* +void QAmqpFrame::readEnd(QDataStream &stream) +{ + unsigned char end_ = 0; + stream.readRawData(reinterpret_cast(&end_), sizeof(end_)); + if (end_ != FRAME_END) + qAmqpDebug("Wrong end of frame"); +} +*/ + +QDataStream &operator<<(QDataStream &stream, const QAmqpFrame &frame) +{ + // write header + stream << frame.type_; + stream << frame.channel_; + stream << frame.size(); + + frame.writePayload(stream); + + // write end + stream << qint8(QAmqpFrame::FRAME_END); + + int writeTimeout = QAmqpFrame::writeTimeout(); + if(writeTimeout >= -1) + { + stream.device()->waitForBytesWritten(writeTimeout); + } + + return stream; +} + +QDataStream &operator>>(QDataStream &stream, QAmqpFrame &frame) +{ + stream >> frame.type_; + stream >> frame.channel_; + stream >> frame.size_; + frame.readPayload(stream); + return stream; +} + +////////////////////////////////////////////////////////////////////////// + +QAmqpMethodFrame::QAmqpMethodFrame() + : QAmqpFrame(QAmqpFrame::Method), + methodClass_(0), + id_(0) +{ +} + +QAmqpMethodFrame::QAmqpMethodFrame(MethodClass methodClass, qint16 id) + : QAmqpFrame(QAmqpFrame::Method), + methodClass_(methodClass), + id_(id) +{ +} + +QAmqpFrame::MethodClass QAmqpMethodFrame::methodClass() const +{ + return MethodClass(methodClass_); +} + +qint16 QAmqpMethodFrame::id() const +{ + return id_; +} + +qint32 QAmqpMethodFrame::size() const +{ + return sizeof(id_) + sizeof(methodClass_) + arguments_.size(); +} + +void QAmqpMethodFrame::setArguments(const QByteArray &data) +{ + arguments_ = data; +} + +QByteArray QAmqpMethodFrame::arguments() const +{ + return arguments_; +} + +void QAmqpMethodFrame::readPayload(QDataStream &stream) +{ + stream >> methodClass_; + stream >> id_; + + arguments_.resize(size_ - (sizeof(id_) + sizeof(methodClass_))); + stream.readRawData(arguments_.data(), arguments_.size()); +} + +void QAmqpMethodFrame::writePayload(QDataStream &stream) const +{ + stream << quint16(methodClass_); + stream << quint16(id_); + stream.writeRawData(arguments_.data(), arguments_.size()); +} + +////////////////////////////////////////////////////////////////////////// + +QVariant QAmqpFrame::readAmqpField(QDataStream &s, QAmqpMetaType::ValueType type) +{ + switch (type) { + case QAmqpMetaType::Boolean: + { + quint8 octet = 0; + s >> octet; + return QVariant::fromValue(octet > 0); + } + case QAmqpMetaType::ShortShortUint: + { + quint8 octet = 0; + s >> octet; + return QVariant::fromValue(octet); + } + case QAmqpMetaType::ShortUint: + { + quint16 tmp_value = 0; + s >> tmp_value; + return QVariant::fromValue(tmp_value); + } + case QAmqpMetaType::LongUint: + { + quint32 tmp_value = 0; + s >> tmp_value; + return QVariant::fromValue(tmp_value); + } + case QAmqpMetaType::LongLongUint: + { + qulonglong v = 0 ; + s >> v; + return v; + } + case QAmqpMetaType::ShortString: + { + qint8 size = 0; + QByteArray buffer; + + s >> size; + buffer.resize(size); + s.readRawData(buffer.data(), buffer.size()); + return QString::fromLatin1(buffer.data(), size); + } + case QAmqpMetaType::LongString: + { + quint32 size = 0; + QByteArray buffer; + + s >> size; + buffer.resize(size); + s.readRawData(buffer.data(), buffer.size()); + return QString::fromUtf8(buffer.data(), buffer.size()); + } + case QAmqpMetaType::Timestamp: + { + qulonglong tmp_value; + s >> tmp_value; +#if (QT_VERSION >= QT_VERSION_CHECK(5, 8, 0)) + return QDateTime::fromSecsSinceEpoch(tmp_value); +#else + return QDateTime::fromTime_t(tmp_value); +#endif + } + case QAmqpMetaType::Hash: + { + QAmqpTable table; + s >> table; + return table; + } + case QAmqpMetaType::Void: + return QVariant(); + default: + qAmqpDebug() << Q_FUNC_INFO << "unsupported value type: " << type; + } + + return QVariant(); +} + +void QAmqpFrame::writeAmqpField(QDataStream &s, QAmqpMetaType::ValueType type, const QVariant &value) +{ + switch (type) { + case QAmqpMetaType::Boolean: + s << (value.toBool() ? qint8(1) : qint8(0)); + break; + case QAmqpMetaType::ShortShortUint: + s << qint8(value.toUInt()); + break; + case QAmqpMetaType::ShortUint: + s << quint16(value.toUInt()); + break; + case QAmqpMetaType::LongUint: + s << quint32(value.toUInt()); + break; + case QAmqpMetaType::LongLongUint: + s << qulonglong(value.toULongLong()); + break; + case QAmqpMetaType::ShortString: + { + QString str = value.toString(); + if (str.length() >= 256) { + qAmqpDebug() << Q_FUNC_INFO << "invalid shortstr length: " << str.length(); + } + + s << quint8(str.length()); + s.writeRawData(str.toUtf8().data(), str.length()); + } + break; + case QAmqpMetaType::LongString: + { + QString str = value.toString(); + s << quint32(str.length()); + s.writeRawData(str.toLatin1().data(), str.length()); + } + break; + case QAmqpMetaType::Timestamp: +#if (QT_VERSION >= QT_VERSION_CHECK(5, 8, 0)) + s << qulonglong(value.toDateTime().toSecsSinceEpoch()); +#else + s << qulonglong(value.toDateTime().toTime_t()); +#endif + break; + case QAmqpMetaType::Hash: + { + QAmqpTable table(value.toHash()); + s << table; + } + break; + default: + qAmqpDebug() << Q_FUNC_INFO << "unsupported value type: " << type; + } +} + +////////////////////////////////////////////////////////////////////////// + +QAmqpContentFrame::QAmqpContentFrame() + : QAmqpFrame(QAmqpFrame::Header) +{ +} + +QAmqpContentFrame::QAmqpContentFrame(QAmqpFrame::MethodClass methodClass) + : QAmqpFrame(QAmqpFrame::Header) +{ + methodClass_ = methodClass; +} + +QAmqpFrame::MethodClass QAmqpContentFrame::methodClass() const +{ + return MethodClass(methodClass_); +} + +qint32 QAmqpContentFrame::size() const +{ + QDataStream out(&buffer_, QIODevice::WriteOnly); + buffer_.clear(); + out << qint16(methodClass_); + out << qint16(0); //weight + out << qlonglong(bodySize_); + + qint16 prop_ = 0; + foreach (int p, properties_.keys()) + prop_ |= p; + out << prop_; + + if (prop_ & QAmqpMessage::ContentType) + writeAmqpField(out, QAmqpMetaType::ShortString, properties_[QAmqpMessage::ContentType]); + + if (prop_ & QAmqpMessage::ContentEncoding) + writeAmqpField(out, QAmqpMetaType::ShortString, properties_[QAmqpMessage::ContentEncoding]); + + if (prop_ & QAmqpMessage::Headers) + writeAmqpField(out, QAmqpMetaType::Hash, properties_[QAmqpMessage::Headers]); + + if (prop_ & QAmqpMessage::DeliveryMode) + writeAmqpField(out, QAmqpMetaType::ShortShortUint, properties_[QAmqpMessage::DeliveryMode]); + + if (prop_ & QAmqpMessage::Priority) + writeAmqpField(out, QAmqpMetaType::ShortShortUint, properties_[QAmqpMessage::Priority]); + + if (prop_ & QAmqpMessage::CorrelationId) + writeAmqpField(out, QAmqpMetaType::ShortString, properties_[QAmqpMessage::CorrelationId]); + + if (prop_ & QAmqpMessage::ReplyTo) + writeAmqpField(out, QAmqpMetaType::ShortString, properties_[QAmqpMessage::ReplyTo]); + + if (prop_ & QAmqpMessage::Expiration) + writeAmqpField(out, QAmqpMetaType::ShortString, properties_[QAmqpMessage::Expiration]); + + if (prop_ & QAmqpMessage::MessageId) + writeAmqpField(out, QAmqpMetaType::ShortString, properties_[QAmqpMessage::MessageId]); + + if (prop_ & QAmqpMessage::Timestamp) + writeAmqpField(out, QAmqpMetaType::Timestamp, properties_[QAmqpMessage::Timestamp]); + + if (prop_ & QAmqpMessage::Type) + writeAmqpField(out, QAmqpMetaType::ShortString, properties_[QAmqpMessage::Type]); + + if (prop_ & QAmqpMessage::UserId) + writeAmqpField(out, QAmqpMetaType::ShortString, properties_[QAmqpMessage::UserId]); + + if (prop_ & QAmqpMessage::AppId) + writeAmqpField(out, QAmqpMetaType::ShortString, properties_[QAmqpMessage::AppId]); + + if (prop_ & QAmqpMessage::ClusterID) + writeAmqpField(out, QAmqpMetaType::ShortString, properties_[QAmqpMessage::ClusterID]); + + return buffer_.size(); +} + +qlonglong QAmqpContentFrame::bodySize() const +{ + return bodySize_; +} + +void QAmqpContentFrame::setBodySize(qlonglong size) +{ + bodySize_ = size; +} + +void QAmqpContentFrame::setProperty(QAmqpMessage::Property prop, const QVariant &value) +{ + properties_[prop] = value; +} + +QVariant QAmqpContentFrame::property(QAmqpMessage::Property prop) const +{ + return properties_.value(prop); +} + +void QAmqpContentFrame::writePayload(QDataStream &out) const +{ + out.writeRawData(buffer_.data(), buffer_.size()); +} + +void QAmqpContentFrame::readPayload(QDataStream &in) +{ + in >> methodClass_; + in.skipRawData(2); //weight + in >> bodySize_; + qint16 flags_ = 0; + in >> flags_; + if (flags_ & QAmqpMessage::ContentType) + properties_[QAmqpMessage::ContentType] = readAmqpField(in, QAmqpMetaType::ShortString); + + if (flags_ & QAmqpMessage::ContentEncoding) + properties_[QAmqpMessage::ContentEncoding] = readAmqpField(in, QAmqpMetaType::ShortString); + + if (flags_ & QAmqpMessage::Headers) + properties_[QAmqpMessage::Headers] = readAmqpField(in, QAmqpMetaType::Hash); + + if (flags_ & QAmqpMessage::DeliveryMode) + properties_[QAmqpMessage::DeliveryMode] = readAmqpField(in, QAmqpMetaType::ShortShortUint); + + if (flags_ & QAmqpMessage::Priority) + properties_[QAmqpMessage::Priority] = readAmqpField(in, QAmqpMetaType::ShortShortUint); + + if (flags_ & QAmqpMessage::CorrelationId) + properties_[QAmqpMessage::CorrelationId] = readAmqpField(in, QAmqpMetaType::ShortString); + + if (flags_ & QAmqpMessage::ReplyTo) + properties_[QAmqpMessage::ReplyTo] = readAmqpField(in, QAmqpMetaType::ShortString); + + if (flags_ & QAmqpMessage::Expiration) + properties_[QAmqpMessage::Expiration] = readAmqpField(in, QAmqpMetaType::ShortString); + + if (flags_ & QAmqpMessage::MessageId) + properties_[QAmqpMessage::MessageId] = readAmqpField(in, QAmqpMetaType::ShortString); + + if (flags_ & QAmqpMessage::Timestamp) + properties_[QAmqpMessage::Timestamp] = readAmqpField(in, QAmqpMetaType::Timestamp); + + if (flags_ & QAmqpMessage::Type) + properties_[QAmqpMessage::Type] = readAmqpField(in, QAmqpMetaType::ShortString); + + if (flags_ & QAmqpMessage::UserId) + properties_[QAmqpMessage::UserId] = readAmqpField(in, QAmqpMetaType::ShortString); + + if (flags_ & QAmqpMessage::AppId) + properties_[QAmqpMessage::AppId] = readAmqpField(in, QAmqpMetaType::ShortString); + + if (flags_ & QAmqpMessage::ClusterID) + properties_[QAmqpMessage::ClusterID] = readAmqpField(in, QAmqpMetaType::ShortString); +} + +////////////////////////////////////////////////////////////////////////// + +QAmqpContentBodyFrame::QAmqpContentBodyFrame() + : QAmqpFrame(QAmqpFrame::Body) +{ +} + +void QAmqpContentBodyFrame::setBody(const QByteArray &data) +{ + body_ = data; +} + +QByteArray QAmqpContentBodyFrame::body() const +{ + return body_; +} + +void QAmqpContentBodyFrame::writePayload(QDataStream &out) const +{ + out.writeRawData(body_.data(), body_.size()); +} + +void QAmqpContentBodyFrame::readPayload(QDataStream &in) +{ + body_.resize(size_); + in.readRawData(body_.data(), body_.size()); +} + +qint32 QAmqpContentBodyFrame::size() const +{ + return body_.size(); +} + +////////////////////////////////////////////////////////////////////////// + +QAmqpHeartbeatFrame::QAmqpHeartbeatFrame() + : QAmqpFrame(QAmqpFrame::Heartbeat) +{ +} + +void QAmqpHeartbeatFrame::readPayload(QDataStream &stream) +{ + Q_UNUSED(stream) +} + +void QAmqpHeartbeatFrame::writePayload(QDataStream &stream) const +{ + Q_UNUSED(stream) +} diff --git a/qamqp/src/qamqpframe_p.h b/qamqp/src/qamqpframe_p.h new file mode 100644 index 0000000..644f6bf --- /dev/null +++ b/qamqp/src/qamqpframe_p.h @@ -0,0 +1,171 @@ +#ifndef QAMQPFRAME_P_H +#define QAMQPFRAME_P_H + +#include +#include +#include +#include + +#include "qamqpglobal.h" +#include "qamqpmessage.h" + +class QAmqpFramePrivate; +class QAmqpFrame +{ +public: + static const qint64 HEADER_SIZE = 7; + static const qint64 FRAME_END_SIZE = 1; + static const quint8 FRAME_END = 0xCE; + + enum FrameType + { + Method = 1, + Header = 2, + Body = 3, + Heartbeat = 8 + }; + + enum MethodClass + { + Connection = 10, + Channel = 20, + Exchange = 40, + Queue = 50, + Basic = 60, + Confirm = 85, + Tx = 90 + }; + + virtual ~QAmqpFrame(); + + FrameType type() const; + + quint16 channel() const; + void setChannel(quint16 channel); + + static int writeTimeout(); + static void setWriteTimeout(int msecs); + + virtual qint32 size() const; + + static QVariant readAmqpField(QDataStream &s, QAmqpMetaType::ValueType type); + static void writeAmqpField(QDataStream &s, QAmqpMetaType::ValueType type, const QVariant &value); + +protected: + explicit QAmqpFrame(FrameType type); + virtual void writePayload(QDataStream &stream) const = 0; + virtual void readPayload(QDataStream &stream) = 0; + + qint32 size_; + +private: + qint8 type_; + quint16 channel_; + + static QReadWriteLock lock_; + static int writeTimeout_; + + friend QDataStream &operator<<(QDataStream &stream, const QAmqpFrame &frame); + friend QDataStream &operator>>(QDataStream &stream, QAmqpFrame &frame); +}; + +QDataStream &operator<<(QDataStream &, const QAmqpFrame &frame); +QDataStream &operator>>(QDataStream &, QAmqpFrame &frame); + +class QAMQP_EXPORT QAmqpMethodFrame : public QAmqpFrame +{ +public: + QAmqpMethodFrame(); + QAmqpMethodFrame(MethodClass methodClass, qint16 id); + + qint16 id() const; + MethodClass methodClass() const; + + virtual qint32 size() const; + + QByteArray arguments() const; + void setArguments(const QByteArray &data); + +private: + void writePayload(QDataStream &stream) const; + void readPayload(QDataStream &stream); + + short methodClass_; + qint16 id_; + QByteArray arguments_; +}; + +class QAmqpContentFrame : public QAmqpFrame +{ +public: + QAmqpContentFrame(); + QAmqpContentFrame(MethodClass methodClass); + + MethodClass methodClass() const; + + virtual qint32 size() const; + + QVariant property(QAmqpMessage::Property prop) const; + void setProperty(QAmqpMessage::Property prop, const QVariant &value); + + qlonglong bodySize() const; + void setBodySize(qlonglong size); + +private: + void writePayload(QDataStream &stream) const; + void readPayload(QDataStream &stream); + friend class QAmqpQueuePrivate; + + short methodClass_; + qint16 id_; + mutable QByteArray buffer_; + QAmqpMessage::PropertyHash properties_; + qlonglong bodySize_; +}; + +class QAmqpContentBodyFrame : public QAmqpFrame +{ +public: + QAmqpContentBodyFrame(); + + void setBody(const QByteArray &data); + QByteArray body() const; + + virtual qint32 size() const; + +private: + void writePayload(QDataStream &stream) const; + void readPayload(QDataStream &stream); + + QByteArray body_; +}; + +class QAmqpHeartbeatFrame : public QAmqpFrame +{ +public: + QAmqpHeartbeatFrame(); + +private: + void writePayload(QDataStream &stream) const; + void readPayload(QDataStream &stream); +}; + +class QAmqpMethodFrameHandler +{ +public: + virtual bool _q_method(const QAmqpMethodFrame &frame) = 0; +}; + +class QAmqpContentFrameHandler +{ +public: + virtual void _q_content(const QAmqpContentFrame &frame) = 0; +}; + +class QAmqpContentBodyFrameHandler +{ +public: + virtual void _q_body(const QAmqpContentBodyFrame &frame) = 0; +}; + +#endif // QAMQPFRAME_P_H diff --git a/qamqp/src/qamqpglobal.h b/qamqp/src/qamqpglobal.h new file mode 100644 index 0000000..922ccd5 --- /dev/null +++ b/qamqp/src/qamqpglobal.h @@ -0,0 +1,134 @@ +/* + * Copyright (C) 2012-2014 Alexey Shcherbakov + * Copyright (C) 2014-2015 Matt Broadstone + * Contact: https://github.com/mbroadst/qamqp + * + * This file is part of the QAMQP Library. + * + * This library is free software; 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. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + */ +#ifndef QAMQPGLOBAL_H +#define QAMQPGLOBAL_H + +#include + +#define AMQP_SCHEME "amqp" +#define AMQP_SSL_SCHEME "amqps" +#define AMQP_PORT 5672 +#define AMQP_SSL_PORT 5671 +#define AMQP_HOST "192.168.46.100" +#define AMQP_VHOST "/" +#define AMQP_LOGIN "ecl3000" +#define AMQP_PSWD "ecl3000" + +#define AMQP_FRAME_MAX 131072 +#define AMQP_FRAME_MIN_SIZE 4096 + +#define AMQP_BASIC_CONTENT_TYPE_FLAG (1 << 15) +#define AMQP_BASIC_CONTENT_ENCODING_FLAG (1 << 14) +#define AMQP_BASIC_HEADERS_FLAG (1 << 13) +#define AMQP_BASIC_DELIVERY_MODE_FLAG (1 << 12) +#define AMQP_BASIC_PRIORITY_FLAG (1 << 11) +#define AMQP_BASIC_CORRELATION_ID_FLAG (1 << 10) +#define AMQP_BASIC_REPLY_TO_FLAG (1 << 9) +#define AMQP_BASIC_EXPIRATION_FLAG (1 << 8) +#define AMQP_BASIC_MESSAGE_ID_FLAG (1 << 7) +#define AMQP_BASIC_TIMESTAMP_FLAG (1 << 6) +#define AMQP_BASIC_TYPE_FLAG (1 << 5) +#define AMQP_BASIC_USER_ID_FLAG (1 << 4) +#define AMQP_BASIC_APP_ID_FLAG (1 << 3) +#define AMQP_BASIC_CLUSTER_ID_FLAG (1 << 2) + +#define QAMQP_VERSION "0.6.0" + +#define AMQP_CONNECTION_FORCED 320 + +#ifdef QAMQP_SHARED +# ifdef QAMQP_BUILD +# define QAMQP_EXPORT Q_DECL_EXPORT +# else +# define QAMQP_EXPORT Q_DECL_IMPORT +# endif +#else +# define QAMQP_EXPORT +#endif + +#define qAmqpDebug if (qEnvironmentVariableIsEmpty("QAMQP_DEBUG")); else qDebug + +namespace QAmqpMetaType { + +enum ValueType +{ + Invalid = -1, + + // basic AMQP types + Boolean, + ShortUint, + LongUint, + LongLongUint, + ShortString, + LongString, + + // field-value types + ShortShortInt, + ShortShortUint, + ShortInt, + LongInt, + LongLongInt, + Float, + Double, + Decimal, + Array, + Timestamp, + Hash, + Bytes, + Void +}; + +} // namespace QAmqpMetaType + +namespace QAMQP { + +enum Error +{ + NoError = 0, + ContentTooLargeError = 311, + NoRouteError = 312, + NoConsumersError = 313, + ConnectionForcedError = 320, + InvalidPathError = 402, + AccessRefusedError = 403, + NotFoundError = 404, + ResourceLockedError = 405, + PreconditionFailedError = 406, + FrameError = 501, + SyntaxError = 502, + CommandInvalidError = 503, + ChannelError = 504, + UnexpectedFrameError = 505, + ResourceError = 506, + NotAllowedError = 530, + NotImplementedError = 540, + InternalError = 541 +}; + +struct Decimal +{ + qint8 scale; + quint32 value; +}; + +} // namespace QAMQP + +Q_DECLARE_METATYPE(QAMQP::Error) +Q_DECLARE_METATYPE(QAMQP::Decimal) + +#endif // QAMQPGLOBAL_H diff --git a/qamqp/src/qamqpmessage.cpp b/qamqp/src/qamqpmessage.cpp new file mode 100644 index 0000000..82a1432 --- /dev/null +++ b/qamqp/src/qamqpmessage.cpp @@ -0,0 +1,124 @@ +#include + +#include "qamqpmessage.h" +#include "qamqpmessage_p.h" + +QAmqpMessagePrivate::QAmqpMessagePrivate() + : deliveryTag(0), + leftSize(0) +{ +} + +////////////////////////////////////////////////////////////////////////// + +QAmqpMessage::QAmqpMessage() + : d(new QAmqpMessagePrivate) +{ +} + +QAmqpMessage::QAmqpMessage(const QAmqpMessage &other) + : d(other.d) +{ +} + +QAmqpMessage::~QAmqpMessage() +{ +} + +QAmqpMessage &QAmqpMessage::operator=(const QAmqpMessage &other) +{ + d = other.d; + return *this; +} + +bool QAmqpMessage::operator==(const QAmqpMessage &message) const +{ + if (message.d == d) + return true; + + return (message.d->deliveryTag == d->deliveryTag && + message.d->redelivered == d->redelivered && + message.d->exchangeName == d->exchangeName && + message.d->routingKey == d->routingKey && + message.d->payload == d->payload && + message.d->properties == d->properties && + message.d->headers == d->headers && + message.d->leftSize == d->leftSize); +} + +bool QAmqpMessage::isValid() const +{ + return d->deliveryTag != 0 && + !d->exchangeName.isNull() && + !d->routingKey.isNull(); +} + +qlonglong QAmqpMessage::deliveryTag() const +{ + return d->deliveryTag; +} + +bool QAmqpMessage::isRedelivered() const +{ + return d->redelivered; +} + +QString QAmqpMessage::exchangeName() const +{ + return d->exchangeName; +} + +QString QAmqpMessage::routingKey() const +{ + return d->routingKey; +} + +QByteArray QAmqpMessage::payload() const +{ + return d->payload; +} + +bool QAmqpMessage::hasProperty(Property property) const +{ + return d->properties.contains(property); +} + +void QAmqpMessage::setProperty(Property property, const QVariant &value) +{ + d->properties.insert(property, value); +} + +QVariant QAmqpMessage::property(Property property, const QVariant &defaultValue) const +{ + return d->properties.value(property, defaultValue); +} + +bool QAmqpMessage::hasHeader(const QString &header) const +{ + return d->headers.contains(header); +} + +void QAmqpMessage::setHeader(const QString &header, const QVariant &value) +{ + d->headers.insert(header, value); +} + +QVariant QAmqpMessage::header(const QString &header, const QVariant &defaultValue) const +{ + return d->headers.value(header, defaultValue); +} + +QHash QAmqpMessage::headers() const +{ + return d->headers; +} + +uint qHash(const QAmqpMessage &message, uint seed) +{ + Q_UNUSED(seed); + return qHash(message.deliveryTag()) ^ + qHash(message.isRedelivered()) ^ + qHash(message.exchangeName()) ^ + qHash(message.routingKey()) ^ + qHash(message.payload()); +} diff --git a/qamqp/src/qamqpmessage.h b/qamqp/src/qamqpmessage.h new file mode 100644 index 0000000..74960a5 --- /dev/null +++ b/qamqp/src/qamqpmessage.h @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2012-2014 Alexey Shcherbakov + * Copyright (C) 2014-2015 Matt Broadstone + * Contact: https://github.com/mbroadst/qamqp + * + * This file is part of the QAMQP Library. + * + * This library is free software; 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. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + */ +#ifndef QAMQPMESSAGE_H +#define QAMQPMESSAGE_H + +#include +#include +#include +#include + +#include "qamqpglobal.h" + +class QAmqpMessagePrivate; +class QAMQP_EXPORT QAmqpMessage +{ +public: + QAmqpMessage(); + QAmqpMessage(const QAmqpMessage &other); + QAmqpMessage &operator=(const QAmqpMessage &other); + ~QAmqpMessage(); + + inline void swap(QAmqpMessage &other) { qSwap(d, other.d); } + + bool operator==(const QAmqpMessage &message) const; + inline bool operator!=(const QAmqpMessage &message) const { return !(operator==(message)); } + + enum Property { + ContentType = AMQP_BASIC_CONTENT_TYPE_FLAG, + ContentEncoding = AMQP_BASIC_CONTENT_ENCODING_FLAG, + Headers = AMQP_BASIC_HEADERS_FLAG, + DeliveryMode = AMQP_BASIC_DELIVERY_MODE_FLAG, + Priority = AMQP_BASIC_PRIORITY_FLAG, + CorrelationId = AMQP_BASIC_CORRELATION_ID_FLAG, + ReplyTo = AMQP_BASIC_REPLY_TO_FLAG, + Expiration = AMQP_BASIC_EXPIRATION_FLAG, + MessageId = AMQP_BASIC_MESSAGE_ID_FLAG, + Timestamp = AMQP_BASIC_TIMESTAMP_FLAG, + Type = AMQP_BASIC_TYPE_FLAG, + UserId = AMQP_BASIC_USER_ID_FLAG, + AppId = AMQP_BASIC_APP_ID_FLAG, + ClusterID = AMQP_BASIC_CLUSTER_ID_FLAG + }; + Q_DECLARE_FLAGS(Properties, Property) + typedef QHash PropertyHash; + + bool hasProperty(Property property) const; + void setProperty(Property property, const QVariant &value); + QVariant property(Property property, const QVariant &defaultValue = QVariant()) const; + + bool hasHeader(const QString &header) const; + void setHeader(const QString &header, const QVariant &value); + QVariant header(const QString &header, const QVariant &defaultValue = QVariant()) const; + QHash headers() const; + + bool isValid() const; + bool isRedelivered() const; + qlonglong deliveryTag() const; + QString exchangeName() const; + QString routingKey() const; + QByteArray payload() const; + +private: + QSharedDataPointer d; + friend class QAmqpQueuePrivate; + friend class QAmqpQueue; +}; + +Q_DECLARE_METATYPE(QAmqpMessage::PropertyHash) +Q_DECLARE_SHARED(QAmqpMessage) + +// NOTE: needed only for MSVC support, don't depend on this hash +QAMQP_EXPORT uint qHash(const QAmqpMessage &key, uint seed = 0); + +#endif // QAMQPMESSAGE_H diff --git a/qamqp/src/qamqpmessage_p.h b/qamqp/src/qamqpmessage_p.h new file mode 100644 index 0000000..cd4e8de --- /dev/null +++ b/qamqp/src/qamqpmessage_p.h @@ -0,0 +1,26 @@ +#ifndef QAMQPMESSAGE_P_H +#define QAMQPMESSAGE_P_H + +#include +#include + +#include "qamqpframe_p.h" +#include "qamqpmessage.h" + +class QAmqpMessagePrivate : public QSharedData +{ +public: + QAmqpMessagePrivate(); + + qlonglong deliveryTag; + bool redelivered; + QString exchangeName; + QString routingKey; + QByteArray payload; + QHash properties; + QHash headers; + int leftSize; + +}; + +#endif // QAMQPMESSAGE_P_H diff --git a/qamqp/src/qamqpqueue.cpp b/qamqp/src/qamqpqueue.cpp new file mode 100644 index 0000000..667b10d --- /dev/null +++ b/qamqp/src/qamqpqueue.cpp @@ -0,0 +1,667 @@ +#include +#include +#include +#include + +#include "qamqpclient.h" +#include "qamqpclient_p.h" +#include "qamqpqueue.h" +#include "qamqpqueue_p.h" +#include "qamqpexchange.h" +#include "qamqpmessage_p.h" +#include "qamqptable.h" +using namespace QAMQP; + +QAmqpQueuePrivate::QAmqpQueuePrivate(QAmqpQueue *q) + : QAmqpChannelPrivate(q), + delayedDeclare(false), + declared(false), + recievingMessage(false), + consuming(false), + consumeRequested(false), + messageCount(0), + consumerCount(0) +{ +} + +QAmqpQueuePrivate::~QAmqpQueuePrivate() +{ + if (!client.isNull()) { + QAmqpClientPrivate *priv = client->d_func(); + priv->contentHandlerByChannel[channelNumber].removeAll(this); + priv->bodyHandlersByChannel[channelNumber].removeAll(this); + } +} + + +void QAmqpQueuePrivate::resetInternalState() +{ + QAmqpChannelPrivate::resetInternalState(); + delayedDeclare = false; + declared = false; + recievingMessage = false; + consuming = false; + consumeRequested = false; +} + +bool QAmqpQueuePrivate::_q_method(const QAmqpMethodFrame &frame) +{ + Q_Q(QAmqpQueue); + if (QAmqpChannelPrivate::_q_method(frame)) + return true; + + if (frame.methodClass() == QAmqpFrame::Queue) { + switch (frame.id()) { + case miDeclareOk: + declareOk(frame); + break; + case miDeleteOk: + deleteOk(frame); + break; + case miBindOk: + bindOk(frame); + break; + case miUnbindOk: + unbindOk(frame); + break; + case miPurgeOk: + purgeOk(frame); + break; + } + + return true; + } + + if (frame.methodClass() == QAmqpFrame::Basic) { + switch(frame.id()) { + case bmConsumeOk: + consumeOk(frame); + break; + case bmDeliver: + deliver(frame); + break; + case bmGetOk: + getOk(frame); + break; + case bmGetEmpty: + Q_EMIT q->empty(); + break; + case bmCancelOk: + cancelOk(frame); + break; + } + + return true; + } + + return false; +} + +void QAmqpQueuePrivate::_q_content(const QAmqpContentFrame &frame) +{ + Q_Q(QAmqpQueue); + Q_ASSERT(frame.channel() == channelNumber); + if (frame.channel() != channelNumber) + return; + + if (!currentMessage.isValid()) { + qAmqpDebug() << "received content-header without delivered message"; + return; + } + + currentMessage.d->leftSize = frame.bodySize(); + QAmqpMessage::PropertyHash::ConstIterator it; + QAmqpMessage::PropertyHash::ConstIterator itEnd = frame.properties_.constEnd(); + for (it = frame.properties_.constBegin(); it != itEnd; ++it) { + QAmqpMessage::Property property = (it.key()); + if (property == QAmqpMessage::Headers) + currentMessage.d->headers = (it.value()).toHash(); + currentMessage.d->properties[property] = it.value(); + } + + if (currentMessage.d->leftSize == 0) { + // message with an empty body + q->enqueue(currentMessage); + Q_EMIT q->messageReceived(); + } +} + +void QAmqpQueuePrivate::_q_body(const QAmqpContentBodyFrame &frame) +{ + Q_Q(QAmqpQueue); + Q_ASSERT(frame.channel() == channelNumber); + if (frame.channel() != channelNumber) + return; + + if (!currentMessage.isValid()) { + qAmqpDebug() << "received content-body without delivered message"; + return; + } + + currentMessage.d->payload.append(frame.body()); + currentMessage.d->leftSize -= frame.body().size(); + if (currentMessage.d->leftSize == 0) { + q->enqueue(currentMessage); + Q_EMIT q->messageReceived(); + } +} + +void QAmqpQueuePrivate::declareOk(const QAmqpMethodFrame &frame) +{ + Q_Q(QAmqpQueue); + declared = true; + + QByteArray data = frame.arguments(); + QDataStream stream(&data, QIODevice::ReadOnly); + + name = QAmqpFrame::readAmqpField(stream, QAmqpMetaType::ShortString).toString(); + + stream >> messageCount >> consumerCount; + + qAmqpDebug("-> queue#declareOk( queue-name=%s, message-count=%d, consumer-count=%d )", + qPrintable(name), messageCount, consumerCount); + + Q_EMIT q->declared(); +} + +void QAmqpQueuePrivate::purgeOk(const QAmqpMethodFrame &frame) +{ + Q_Q(QAmqpQueue); + QByteArray data = frame.arguments(); + QDataStream stream(&data, QIODevice::ReadOnly); + + + stream >> messageCount; + + qAmqpDebug("-> queue#purgeOk( queue-name=%s, message-count=%d )", + qPrintable(name), messageCount); + + Q_EMIT q->purged(messageCount); +} + +void QAmqpQueuePrivate::deleteOk(const QAmqpMethodFrame &frame) +{ + Q_Q(QAmqpQueue); + declared = false; + + QByteArray data = frame.arguments(); + QDataStream stream(&data, QIODevice::ReadOnly); + + stream >> messageCount; + + qAmqpDebug("-> queue#deleteOk( queue-name=%s, message-count=%d )", + qPrintable(name), messageCount); + + + Q_EMIT q->removed(); +} + +void QAmqpQueuePrivate::bindOk(const QAmqpMethodFrame &frame) +{ + Q_UNUSED(frame) + Q_Q(QAmqpQueue); + qAmqpDebug("-> queue[ %s ]#bindOk()", qPrintable(name)); + Q_EMIT q->bound(); +} + +void QAmqpQueuePrivate::unbindOk(const QAmqpMethodFrame &frame) +{ + Q_UNUSED(frame) + Q_Q(QAmqpQueue); + qAmqpDebug("-> queue[ %s ]#unbindOk()", qPrintable(name)); + Q_EMIT q->unbound(); +} + +void QAmqpQueuePrivate::getOk(const QAmqpMethodFrame &frame) +{ + qAmqpDebug("-> queue[ %s ]#getOk()", qPrintable(name)); + + QByteArray data = frame.arguments(); + QDataStream in(&data, QIODevice::ReadOnly); + + QAmqpMessage message; + message.d->deliveryTag = QAmqpFrame::readAmqpField(in, QAmqpMetaType::LongLongUint).toLongLong(); + message.d->redelivered = QAmqpFrame::readAmqpField(in, QAmqpMetaType::Boolean).toBool(); + message.d->exchangeName = QAmqpFrame::readAmqpField(in, QAmqpMetaType::ShortString).toString(); + message.d->routingKey = QAmqpFrame::readAmqpField(in, QAmqpMetaType::ShortString).toString(); + currentMessage = message; +} + +void QAmqpQueuePrivate::consumeOk(const QAmqpMethodFrame &frame) +{ + Q_Q(QAmqpQueue); + QByteArray data = frame.arguments(); + QDataStream stream(&data, QIODevice::ReadOnly); + consumerTag = QAmqpFrame::readAmqpField(stream, QAmqpMetaType::ShortString).toString(); + consuming = true; + consumeRequested = false; + + qAmqpDebug("-> queue[ %s ]#consumeOk( consumer-tag=%s )", qPrintable(name), qPrintable(consumerTag)); + + Q_EMIT q->consuming(consumerTag); +} + +void QAmqpQueuePrivate::deliver(const QAmqpMethodFrame &frame) +{ + qAmqpDebug() << Q_FUNC_INFO; + QByteArray data = frame.arguments(); + QDataStream in(&data, QIODevice::ReadOnly); + QString consumer = QAmqpFrame::readAmqpField(in, QAmqpMetaType::ShortString).toString(); + if (consumerTag != consumer) { + qAmqpDebug() << Q_FUNC_INFO << "invalid consumer tag: " << consumer; + return; + } + + QAmqpMessage message; + message.d->deliveryTag = QAmqpFrame::readAmqpField(in, QAmqpMetaType::LongLongUint).toLongLong(); + message.d->redelivered = QAmqpFrame::readAmqpField(in, QAmqpMetaType::Boolean).toBool(); + message.d->exchangeName = QAmqpFrame::readAmqpField(in, QAmqpMetaType::ShortString).toString(); + message.d->routingKey = QAmqpFrame::readAmqpField(in, QAmqpMetaType::ShortString).toString(); + currentMessage = message; +} + +void QAmqpQueuePrivate::declare() +{ + QAmqpMethodFrame frame(QAmqpFrame::Queue, QAmqpQueuePrivate::miDeclare); + frame.setChannel(channelNumber); + + QByteArray args; + QDataStream out(&args, QIODevice::WriteOnly); + + out << qint16(0); //reserved 1 + QAmqpFrame::writeAmqpField(out, QAmqpMetaType::ShortString, name); + out << qint8(options); + QAmqpFrame::writeAmqpField(out, QAmqpMetaType::Hash, arguments); + + qAmqpDebug("<- queue#declare( queue=%s, passive=%d, durable=%d, exclusive=%d, auto-delete=%d, no-wait=%d )", + qPrintable(name), options & QAmqpQueue::Passive, options & QAmqpQueue::Durable, + options & QAmqpQueue::Exclusive, options & QAmqpQueue::AutoDelete, + options & QAmqpQueue::NoWait); + + frame.setArguments(args); + sendFrame(frame); + + if (delayedDeclare) + delayedDeclare = false; +} + +void QAmqpQueuePrivate::cancelOk(const QAmqpMethodFrame &frame) +{ + Q_Q(QAmqpQueue); + qAmqpDebug() << Q_FUNC_INFO; + QByteArray data = frame.arguments(); + QDataStream in(&data, QIODevice::ReadOnly); + QString consumer = QAmqpFrame::readAmqpField(in, QAmqpMetaType::ShortString).toString(); + if (consumerTag != consumer) { + qAmqpDebug() << Q_FUNC_INFO << "invalid consumer tag: " << consumer; + return; + } + + qAmqpDebug("-> queue[ %s ]#cancelOk( consumer-tag=%s )", qPrintable(name), qPrintable(consumerTag)); + + consumerTag.clear(); + consuming = false; + consumeRequested = false; + Q_EMIT q->cancelled(consumer); +} + +////////////////////////////////////////////////////////////////////////// + +QAmqpQueue::QAmqpQueue(int channelNumber, QAmqpClient *parent) + : QAmqpChannel(new QAmqpQueuePrivate(this), parent) +{ + Q_D(QAmqpQueue); + d->init(channelNumber, parent); +} + +QAmqpQueue::~QAmqpQueue() +{ +} + +void QAmqpQueue::channelOpened() +{ + Q_D(QAmqpQueue); + if (d->delayedDeclare) + d->declare(); + + if (!d->delayedBindings.isEmpty()) { + typedef QPair BindingPair; + foreach(BindingPair binding, d->delayedBindings) + bind(binding.first, binding.second); + d->delayedBindings.clear(); + } +} + +void QAmqpQueue::channelClosed() +{ +} + +int QAmqpQueue::options() const +{ + Q_D(const QAmqpQueue); + return d->options; +} + +qint32 QAmqpQueue::messageCount() const +{ + Q_D(const QAmqpQueue); + return d->messageCount; +} + +qint32 QAmqpQueue::consumerCount() const +{ + Q_D(const QAmqpQueue); + return d->consumerCount; +} + +void QAmqpQueue::declare(int options, const QAmqpTable &arguments) +{ + Q_D(QAmqpQueue); + d->options = options; + d->arguments = arguments; + + if (!d->opened) { + d->delayedDeclare = true; + return; + } + + d->declare(); +} + +void QAmqpQueue::remove(int options) +{ + Q_D(QAmqpQueue); + if (!d->declared) { + qAmqpDebug() << Q_FUNC_INFO << "trying to remove undeclared queue, aborting..."; + return; + } + + QAmqpMethodFrame frame(QAmqpFrame::Queue, QAmqpQueuePrivate::miDelete); + frame.setChannel(d->channelNumber); + + QByteArray arguments; + QDataStream out(&arguments, QIODevice::WriteOnly); + + out << qint16(0); //reserved 1 + QAmqpFrame::writeAmqpField(out, QAmqpMetaType::ShortString, d->name); + out << qint8(options); + + qAmqpDebug("<- queue#delete( queue=%s, if-unused=%d, if-empty=%d )", + qPrintable(d->name), options & QAmqpQueue::roIfUnused, options & QAmqpQueue::roIfEmpty); + + frame.setArguments(arguments); + d->sendFrame(frame); +} + +void QAmqpQueue::purge() +{ + Q_D(QAmqpQueue); + + if (!d->opened) + return; + + QAmqpMethodFrame frame(QAmqpFrame::Queue, QAmqpQueuePrivate::miPurge); + frame.setChannel(d->channelNumber); + + QByteArray arguments; + QDataStream out(&arguments, QIODevice::WriteOnly); + out << qint16(0); //reserved 1 + QAmqpFrame::writeAmqpField(out, QAmqpMetaType::ShortString, d->name); + out << qint8(0); // no-wait + + qAmqpDebug("<- queue#purge( queue=%s, no-wait=%d )", qPrintable(d->name), 0); + + frame.setArguments(arguments); + d->sendFrame(frame); +} + +void QAmqpQueue::bind(QAmqpExchange *exchange, const QString &key) +{ + if (!exchange) { + qAmqpDebug() << Q_FUNC_INFO << "invalid exchange provided"; + return; + } + + bind(exchange->name(), key); +} + +void QAmqpQueue::bind(const QString &exchangeName, const QString &key) +{ + Q_D(QAmqpQueue); + if (!d->opened) { + d->delayedBindings.append(QPair(exchangeName, key)); + return; + } + + QAmqpMethodFrame frame(QAmqpFrame::Queue, QAmqpQueuePrivate::miBind); + frame.setChannel(d->channelNumber); + + QByteArray arguments; + QDataStream out(&arguments, QIODevice::WriteOnly); + + out << qint16(0); // reserved 1 + QAmqpFrame::writeAmqpField(out, QAmqpMetaType::ShortString, d->name); + QAmqpFrame::writeAmqpField(out, QAmqpMetaType::ShortString, exchangeName); + QAmqpFrame::writeAmqpField(out, QAmqpMetaType::ShortString, key); + + out << qint8(0); // no-wait + QAmqpFrame::writeAmqpField(out, QAmqpMetaType::Hash, QAmqpTable()); + + qAmqpDebug("<- queue#bind( queue=%s, exchange=%s, routing-key=%s, no-wait=%d )", + qPrintable(d->name), qPrintable(exchangeName), qPrintable(key), + 0); + + frame.setArguments(arguments); + d->sendFrame(frame); +} + +void QAmqpQueue::unbind(QAmqpExchange *exchange, const QString &key) +{ + if (!exchange) { + qAmqpDebug() << Q_FUNC_INFO << "invalid exchange provided"; + return; + } + + unbind(exchange->name(), key); +} + +void QAmqpQueue::unbind(const QString &exchangeName, const QString &key) +{ + Q_D(QAmqpQueue); + if (!d->opened) { + qAmqpDebug() << Q_FUNC_INFO << "queue is not open"; + return; + } + + QAmqpMethodFrame frame(QAmqpFrame::Queue, QAmqpQueuePrivate::miUnbind); + frame.setChannel(d->channelNumber); + + QByteArray arguments; + QDataStream out(&arguments, QIODevice::WriteOnly); + out << qint16(0); //reserved 1 + QAmqpFrame::writeAmqpField(out, QAmqpMetaType::ShortString, d->name); + QAmqpFrame::writeAmqpField(out, QAmqpMetaType::ShortString, exchangeName); + QAmqpFrame::writeAmqpField(out, QAmqpMetaType::ShortString, key); + QAmqpFrame::writeAmqpField(out, QAmqpMetaType::Hash, QAmqpTable()); + + qAmqpDebug("<- queue#unbind( queue=%s, exchange=%s, routing-key=%s )", + qPrintable(d->name), qPrintable(exchangeName), qPrintable(key)); + + frame.setArguments(arguments); + d->sendFrame(frame); +} + +bool QAmqpQueue::consume(int options) +{ + Q_D(QAmqpQueue); + if (!d->opened) { + qAmqpDebug() << Q_FUNC_INFO << "queue is not open"; + return false; + } + + if (d->consumeRequested) { + qAmqpDebug() << Q_FUNC_INFO << "already attempting to consume"; + return false; + } + + if (d->consuming) { + qAmqpDebug() << Q_FUNC_INFO << "already consuming with tag: " << d->consumerTag; + return false; + } + + QAmqpMethodFrame frame(QAmqpFrame::Basic, QAmqpQueuePrivate::bmConsume); + frame.setChannel(d->channelNumber); + + QByteArray arguments; + QDataStream out(&arguments, QIODevice::WriteOnly); + + out << qint16(0); //reserved 1 + QAmqpFrame::writeAmqpField(out, QAmqpMetaType::ShortString, d->name); + QAmqpFrame::writeAmqpField(out, QAmqpMetaType::ShortString, d->consumerTag); + + out << qint8(options); + QAmqpFrame::writeAmqpField(out, QAmqpMetaType::Hash, QAmqpTable()); + + qAmqpDebug("<- basic#consume( queue=%s, consumer-tag=%s, no-local=%d, no-ack=%d, exclusive=%d, no-wait=%d )", + qPrintable(d->name), qPrintable(d->consumerTag), + options & QAmqpQueue::coNoLocal, options & QAmqpQueue::coNoAck, + options & QAmqpQueue::coExclusive, options & QAmqpQueue::coNoWait); + + frame.setArguments(arguments); + d->sendFrame(frame); + d->consumeRequested = true; + return true; +} + +void QAmqpQueue::setConsumerTag(const QString &consumerTag) +{ + Q_D(QAmqpQueue); + d->consumerTag = consumerTag; +} + +QString QAmqpQueue::consumerTag() const +{ + Q_D(const QAmqpQueue); + return d->consumerTag; +} + +bool QAmqpQueue::isConsuming() const +{ + Q_D(const QAmqpQueue); + return d->consuming; +} + +bool QAmqpQueue::isDeclared() const +{ + Q_D(const QAmqpQueue); + return d->declared; +} + +void QAmqpQueue::get(bool noAck) +{ + Q_D(QAmqpQueue); + if (!d->opened) { + qAmqpDebug() << Q_FUNC_INFO << "channel is not open"; + return; + } + + QAmqpMethodFrame frame(QAmqpFrame::Basic, QAmqpQueuePrivate::bmGet); + frame.setChannel(d->channelNumber); + + QByteArray arguments; + QDataStream out(&arguments, QIODevice::WriteOnly); + + out << qint16(0); //reserved 1 + QAmqpFrame::writeAmqpField(out, QAmqpMetaType::ShortString, d->name); + out << qint8(noAck ? 1 : 0); // no-ack + + qAmqpDebug("<- basic#get( queue=%s, no-ack=%d )", qPrintable(d->name), noAck); + + frame.setArguments(arguments); + d->sendFrame(frame); +} + +void QAmqpQueue::ack(const QAmqpMessage &message) +{ + ack(message.deliveryTag(), false); +} + +void QAmqpQueue::ack(qlonglong deliveryTag, bool multiple) +{ + Q_D(QAmqpQueue); + if (!d->opened) { + qAmqpDebug() << Q_FUNC_INFO << "channel is not open"; + return; + } + + QAmqpMethodFrame frame(QAmqpFrame::Basic, QAmqpQueuePrivate::bmAck); + frame.setChannel(d->channelNumber); + + QByteArray arguments; + QDataStream out(&arguments, QIODevice::WriteOnly); + + out << deliveryTag; + out << qint8(multiple ? 1 : 0); // multiple + + qAmqpDebug("<- basic#ack( delivery-tag=%llu, multiple=%d )", deliveryTag, multiple); + + frame.setArguments(arguments); + d->sendFrame(frame); +} + +void QAmqpQueue::reject(const QAmqpMessage &message, bool requeue) +{ + reject(message.deliveryTag(), requeue); +} + +void QAmqpQueue::reject(qlonglong deliveryTag, bool requeue) +{ + Q_D(QAmqpQueue); + if (!d->opened) { + qAmqpDebug() << Q_FUNC_INFO << "channel is not open"; + return; + } + + QAmqpMethodFrame frame(QAmqpFrame::Basic, QAmqpQueuePrivate::bmReject); + frame.setChannel(d->channelNumber); + + QByteArray arguments; + QDataStream out(&arguments, QIODevice::WriteOnly); + + out << deliveryTag; + out << qint8(requeue ? 1 : 0); + + qAmqpDebug("<- basic#reject( delivery-tag=%llu, requeue=%d )", deliveryTag, requeue); + + frame.setArguments(arguments); + d->sendFrame(frame); +} + +bool QAmqpQueue::cancel(bool noWait) +{ + Q_D(QAmqpQueue); + if (!d->consuming) { + qAmqpDebug() << Q_FUNC_INFO << "not consuming!"; + return false; + } + + if (d->consumerTag.isEmpty()) { + qAmqpDebug() << Q_FUNC_INFO << "consuming with an empty consumer tag, failing..."; + return false; + } + + QAmqpMethodFrame frame(QAmqpFrame::Basic, QAmqpQueuePrivate::bmCancel); + frame.setChannel(d->channelNumber); + + QByteArray arguments; + QDataStream out(&arguments, QIODevice::WriteOnly); + + QAmqpFrame::writeAmqpField(out, QAmqpMetaType::ShortString, d->consumerTag); + out << (noWait ? qint8(0x01) : qint8(0x0)); + + qAmqpDebug("<- basic#cancel( consumer-tag=%s, no-wait=%d )", qPrintable(d->consumerTag), noWait); + + frame.setArguments(arguments); + d->sendFrame(frame); + return true; +} diff --git a/qamqp/src/qamqpqueue.h b/qamqp/src/qamqpqueue.h new file mode 100644 index 0000000..c0d53b2 --- /dev/null +++ b/qamqp/src/qamqpqueue.h @@ -0,0 +1,127 @@ +/* + * Copyright (C) 2012-2014 Alexey Shcherbakov + * Copyright (C) 2014-2015 Matt Broadstone + * Contact: https://github.com/mbroadst/qamqp + * + * This file is part of the QAMQP Library. + * + * This library is free software; 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. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + */ +#ifndef QAMQPQUEUE_H +#define QAMQPQUEUE_H + +#include + +#include "qamqpchannel.h" +#include "qamqpmessage.h" +#include "qamqpglobal.h" +#include "qamqptable.h" + +class QAmqpClient; +class QAmqpClientPrivate; +class QAmqpExchange; +class QAmqpQueuePrivate; +class QAMQP_EXPORT QAmqpQueue : public QAmqpChannel, public QQueue +{ + Q_OBJECT + Q_PROPERTY(int options READ options CONSTANT) + Q_PROPERTY(QString consumerTag READ consumerTag WRITE setConsumerTag) + +public: + enum QueueOption { + NoOptions = 0x0, + Passive = 0x01, + Durable = 0x02, + Exclusive = 0x04, + AutoDelete = 0x08, + NoWait = 0x10 + }; + Q_DECLARE_FLAGS(QueueOptions, QueueOption) + Q_ENUM(QueueOption) + Q_ENUM(QueueOptions) + int options() const; + + enum ConsumeOption { + coNoLocal = 0x01, + coNoAck = 0x02, + coExclusive = 0x04, + coNoWait = 0x08 + }; + Q_DECLARE_FLAGS(ConsumeOptions, ConsumeOption) + Q_ENUM(ConsumeOption) + + enum RemoveOption { + roForce = 0x0, + roIfUnused = 0x01, + roIfEmpty = 0x02, + roNoWait = 0x04 + }; + Q_DECLARE_FLAGS(RemoveOptions, RemoveOption) + Q_ENUM(RemoveOption) + + ~QAmqpQueue(); + + bool isConsuming() const; + bool isDeclared() const; + + void setConsumerTag(const QString &consumerTag); + QString consumerTag() const; + + qint32 messageCount() const; + qint32 consumerCount() const; + +Q_SIGNALS: + void declared(); + void bound(); + void unbound(); + void removed(); + void purged(int messageCount); + + void messageReceived(); + void empty(); + void consuming(const QString &consumerTag); + void cancelled(const QString &consumerTag); + +public Q_SLOTS: + // AMQP Queue + void declare(int options = Durable|AutoDelete, const QAmqpTable &arguments = QAmqpTable()); + void bind(const QString &exchangeName, const QString &key); + void bind(QAmqpExchange *exchange, const QString &key); + void unbind(const QString &exchangeName, const QString &key); + void unbind(QAmqpExchange *exchange, const QString &key); + void purge(); + void remove(int options = roIfUnused|roIfEmpty|roNoWait); + + // AMQP Basic + bool consume(int options = NoOptions); + void get(bool noAck = true); + bool cancel(bool noWait = false); + void ack(const QAmqpMessage &message); + void ack(qlonglong deliveryTag, bool multiple); + void reject(const QAmqpMessage &message, bool requeue); + void reject(qlonglong deliveryTag, bool requeue); + +protected: + // reimp Channel + virtual void channelOpened(); + virtual void channelClosed(); + +private: + explicit QAmqpQueue(int channelNumber = -1, QAmqpClient *parent = 0); + + Q_DISABLE_COPY(QAmqpQueue) + Q_DECLARE_PRIVATE(QAmqpQueue) + friend class QAmqpClient; + friend class QAmqpClientPrivate; + +}; + +#endif // QAMQPQUEUE_H diff --git a/qamqp/src/qamqpqueue_p.h b/qamqp/src/qamqpqueue_p.h new file mode 100644 index 0000000..a80beb4 --- /dev/null +++ b/qamqp/src/qamqpqueue_p.h @@ -0,0 +1,64 @@ +#ifndef QAMQPQUEUE_P_H +#define QAMQPQUEUE_P_H + +#include +#include + +#include "qamqpchannel_p.h" + +class QAmqpQueuePrivate: public QAmqpChannelPrivate, + public QAmqpContentFrameHandler, + public QAmqpContentBodyFrameHandler +{ +public: + enum MethodId { + METHOD_ID_ENUM(miDeclare, 10), + METHOD_ID_ENUM(miBind, 20), + METHOD_ID_ENUM(miUnbind, 50), + METHOD_ID_ENUM(miPurge, 30), + METHOD_ID_ENUM(miDelete, 40) + }; + + QAmqpQueuePrivate(QAmqpQueue *q); + ~QAmqpQueuePrivate(); + + virtual void resetInternalState(); + + void declare(); + virtual bool _q_method(const QAmqpMethodFrame &frame); + + // AMQP Queue method handlers + void declareOk(const QAmqpMethodFrame &frame); + void deleteOk(const QAmqpMethodFrame &frame); + void purgeOk(const QAmqpMethodFrame &frame); + void bindOk(const QAmqpMethodFrame &frame); + void unbindOk(const QAmqpMethodFrame &frame); + void consumeOk(const QAmqpMethodFrame &frame); + + // AMQP Basic method handlers + virtual void _q_content(const QAmqpContentFrame &frame); + virtual void _q_body(const QAmqpContentBodyFrame &frame); + void deliver(const QAmqpMethodFrame &frame); + void getOk(const QAmqpMethodFrame &frame); + void cancelOk(const QAmqpMethodFrame &frame); + + QString type; + int options; + bool delayedDeclare; + bool declared; + QQueue > delayedBindings; + + QString consumerTag; + bool recievingMessage; + QAmqpMessage currentMessage; + bool consuming; + bool consumeRequested; + + qint32 messageCount; + qint32 consumerCount; + + Q_DECLARE_PUBLIC(QAmqpQueue) + +}; + +#endif // QAMQPQUEUE_P_H diff --git a/qamqp/src/qamqptable.cpp b/qamqp/src/qamqptable.cpp new file mode 100644 index 0000000..3ec9470 --- /dev/null +++ b/qamqp/src/qamqptable.cpp @@ -0,0 +1,371 @@ +#include + +#include +#include +#include + +#include "qamqpframe_p.h" +#include "qamqptable.h" + +/* + * field value types according to: https://www.rabbitmq.com/amqp-0-9-1-errata.html +t - Boolean +b - Signed 8-bit + Unsigned 8-bit +s - Signed 16-bit + Unsigned 16-bit +I - Signed 32-bit + Unsigned 32-bit +l - Signed 64-bit + Unsigned 64-bit +f - 32-bit float +d - 64-bit float +D - Decimal +S - Long string +A - Array +T - Timestamp (u64) +F - Nested Table +V - Void +x - Byte array +*/ + +QAmqpMetaType::ValueType valueTypeForOctet(qint8 octet) +{ + switch (octet) { + case 't': return QAmqpMetaType::Boolean; + case 'b': return QAmqpMetaType::ShortShortInt; + case 's': return QAmqpMetaType::ShortInt; + case 'I': return QAmqpMetaType::LongInt; + case 'l': return QAmqpMetaType::LongLongInt; + case 'f': return QAmqpMetaType::Float; + case 'd': return QAmqpMetaType::Double; + case 'D': return QAmqpMetaType::Decimal; + case 'S': return QAmqpMetaType::LongString; + case 'A': return QAmqpMetaType::Array; + case 'T': return QAmqpMetaType::Timestamp; + case 'F': return QAmqpMetaType::Hash; + case 'V': return QAmqpMetaType::Void; + case 'x': return QAmqpMetaType::Bytes; + default: + qAmqpDebug() << Q_FUNC_INFO << "invalid octet received: " << char(octet); + } + + return QAmqpMetaType::Invalid; +} + +qint8 valueTypeToOctet(QAmqpMetaType::ValueType type) +{ + switch (type) { + case QAmqpMetaType::Boolean: return 't'; + case QAmqpMetaType::ShortShortInt: return 'b'; + case QAmqpMetaType::ShortInt: return 's'; + case QAmqpMetaType::LongInt: return 'I'; + case QAmqpMetaType::LongLongInt: return 'l'; + case QAmqpMetaType::Float: return 'f'; + case QAmqpMetaType::Double: return 'd'; + case QAmqpMetaType::Decimal: return 'D'; + case QAmqpMetaType::LongString: return 'S'; + case QAmqpMetaType::Array: return 'A'; + case QAmqpMetaType::Timestamp: return 'T'; + case QAmqpMetaType::Hash: return 'F'; + case QAmqpMetaType::Void: return 'V'; + case QAmqpMetaType::Bytes: return 'x'; + default: + qAmqpDebug() << Q_FUNC_INFO << "invalid type received: " << char(type); + } + + return 'V'; +} + +void QAmqpTable::writeFieldValue(QDataStream &stream, const QVariant &value) +{ + QAmqpMetaType::ValueType type; + switch (value.userType()) { + case QMetaType::Bool: + type = QAmqpMetaType::Boolean; + break; + case QMetaType::QByteArray: + type = QAmqpMetaType::Bytes; + break; + case QMetaType::Int: + { + int i = qAbs(value.toInt()); + if (i <= qint8(SCHAR_MAX)) { + type = QAmqpMetaType::ShortShortInt; + } else if (i <= qint16(SHRT_MAX)) { + type = QAmqpMetaType::ShortInt; + } else { + type = QAmqpMetaType::LongInt; + } + } + break; + case QMetaType::UShort: + type = QAmqpMetaType::ShortInt; + break; + case QMetaType::UInt: + { + int i = value.toInt(); + if (i <= qint8(SCHAR_MAX)) { + type = QAmqpMetaType::ShortShortInt; + } else if (i <= qint16(SHRT_MAX)) { + type = QAmqpMetaType::ShortInt; + } else { + type = QAmqpMetaType::LongInt; + } + } + break; + case QMetaType::LongLong: + case QMetaType::ULongLong: + type = QAmqpMetaType::LongLongInt; + break; + case QMetaType::QString: + type = QAmqpMetaType::LongString; + break; + case QMetaType::QDateTime: + type = QAmqpMetaType::Timestamp; + break; + case QMetaType::Double: + type = value.toDouble() > FLT_MAX ? QAmqpMetaType::Double : QAmqpMetaType::Float; + break; + case QMetaType::QVariantHash: + type = QAmqpMetaType::Hash; + break; + case QMetaType::QVariantList: + type = QAmqpMetaType::Array; + break; + case QMetaType::Void: + type = QAmqpMetaType::Void; + break; + default: + if (value.userType() == qMetaTypeId()) { + type = QAmqpMetaType::Decimal; + break; + } else if (!value.isValid()) { + type = QAmqpMetaType::Void; + break; + } + + qAmqpDebug() << Q_FUNC_INFO << "unhandled type: " << value.userType(); + return; + } + + // write the field value type, a requirement for field tables only + stream << valueTypeToOctet(type); + writeFieldValue(stream, type, value); +} + +void QAmqpTable::writeFieldValue(QDataStream &stream, QAmqpMetaType::ValueType type, const QVariant &value) +{ + switch (type) { + case QAmqpMetaType::Boolean: + case QAmqpMetaType::ShortShortUint: + case QAmqpMetaType::ShortUint: + case QAmqpMetaType::LongUint: + case QAmqpMetaType::LongLongUint: + case QAmqpMetaType::ShortString: + case QAmqpMetaType::LongString: + case QAmqpMetaType::Timestamp: + case QAmqpMetaType::Hash: + return QAmqpFrame::writeAmqpField(stream, type, value); + + case QAmqpMetaType::ShortShortInt: + stream << qint8(value.toInt()); + break; + case QAmqpMetaType::ShortInt: + stream << qint16(value.toInt()); + break; + case QAmqpMetaType::LongInt: + stream << qint32(value.toInt()); + break; + case QAmqpMetaType::LongLongInt: + stream << qlonglong(value.toLongLong()); + break; + case QAmqpMetaType::Float: + { + float g = value.toFloat(); + QDataStream::FloatingPointPrecision oldPrecision = stream.floatingPointPrecision(); + stream.setFloatingPointPrecision(QDataStream::SinglePrecision); + stream << g; + stream.setFloatingPointPrecision(oldPrecision); + } + break; + case QAmqpMetaType::Double: + { + double g = value.toDouble(); + QDataStream::FloatingPointPrecision oldPrecision = stream.floatingPointPrecision(); + stream.setFloatingPointPrecision(QDataStream::DoublePrecision); + stream << g; + stream.setFloatingPointPrecision(oldPrecision); + } + break; + case QAmqpMetaType::Decimal: + { + QAMQP::Decimal v(value.value()); + stream << v.scale; + stream << v.value; + } + break; + case QAmqpMetaType::Array: + { + QByteArray buffer; + QDataStream arrayStream(&buffer, QIODevice::WriteOnly); + QVariantList array(value.toList()); + for (int i = 0; i < array.size(); ++i) + writeFieldValue(arrayStream, array.at(i)); + + if (buffer.isEmpty()) { + stream << qint32(0); + } else { + stream << buffer; + } + } + break; + case QAmqpMetaType::Bytes: + { + QByteArray ba = value.toByteArray(); + stream << quint32(ba.length()); + stream.writeRawData(ba.data(), ba.length()); + } + break; + case QAmqpMetaType::Void: + stream << qint32(0); + break; + + default: + qAmqpDebug() << Q_FUNC_INFO << "unhandled type: " << type; + } +} + +QVariant QAmqpTable::readFieldValue(QDataStream &stream, QAmqpMetaType::ValueType type) +{ + switch (type) { + case QAmqpMetaType::Boolean: + case QAmqpMetaType::ShortShortUint: + case QAmqpMetaType::ShortUint: + case QAmqpMetaType::LongUint: + case QAmqpMetaType::LongLongUint: + case QAmqpMetaType::ShortString: + case QAmqpMetaType::LongString: + case QAmqpMetaType::Timestamp: + case QAmqpMetaType::Hash: + return QAmqpFrame::readAmqpField(stream, type); + + case QAmqpMetaType::ShortShortInt: + { + char octet; + stream.readRawData(&octet, sizeof(octet)); + return QVariant::fromValue(octet); + } + case QAmqpMetaType::ShortInt: + { + qint16 tmp_value = 0; + stream >> tmp_value; + return QVariant::fromValue(tmp_value); + } + case QAmqpMetaType::LongInt: + { + qint32 tmp_value = 0; + stream >> tmp_value; + return QVariant::fromValue(tmp_value); + } + case QAmqpMetaType::LongLongInt: + { + qlonglong v = 0 ; + stream >> v; + return v; + } + case QAmqpMetaType::Float: + { + float tmp_value; + QDataStream::FloatingPointPrecision precision = stream.floatingPointPrecision(); + stream.setFloatingPointPrecision(QDataStream::SinglePrecision); + stream >> tmp_value; + stream.setFloatingPointPrecision(precision); + return QVariant::fromValue(tmp_value); + } + case QAmqpMetaType::Double: + { + double tmp_value; + QDataStream::FloatingPointPrecision precision = stream.floatingPointPrecision(); + stream.setFloatingPointPrecision(QDataStream::DoublePrecision); + stream >> tmp_value; + stream.setFloatingPointPrecision(precision); + return QVariant::fromValue(tmp_value); + } + case QAmqpMetaType::Decimal: + { + QAMQP::Decimal v; + stream >> v.scale; + stream >> v.value; + return QVariant::fromValue(v); + } + case QAmqpMetaType::Array: + { + QByteArray data; + quint32 size = 0; + stream >> size; + data.resize(size); + stream.readRawData(data.data(), data.size()); + + qint8 type = 0; + QVariantList result; + QDataStream arrayStream(&data, QIODevice::ReadOnly); + while (!arrayStream.atEnd()) { + arrayStream >> type; + result.append(readFieldValue(arrayStream, valueTypeForOctet(type))); + } + + return result; + } + case QAmqpMetaType::Bytes: + { + QByteArray bytes; + quint32 length = 0; + stream >> length; + bytes.resize(length); + stream.readRawData(bytes.data(), bytes.size()); + return bytes; + } + case QAmqpMetaType::Void: + break; + default: + qAmqpDebug() << Q_FUNC_INFO << "unhandled type: " << type; + } + + return QVariant(); +} + +QDataStream &operator<<(QDataStream &stream, const QAmqpTable &table) +{ + QByteArray data; + QDataStream s(&data, QIODevice::WriteOnly); + QAmqpTable::ConstIterator it; + QAmqpTable::ConstIterator itEnd = table.constEnd(); + for (it = table.constBegin(); it != itEnd; ++it) { + QAmqpTable::writeFieldValue(s, QAmqpMetaType::ShortString, it.key()); + QAmqpTable::writeFieldValue(s, it.value()); + } + + if (data.isEmpty()) { + stream << qint32(0); + } else { + stream << data; + } + + return stream; +} + +QDataStream &operator>>(QDataStream &stream, QAmqpTable &table) +{ + QByteArray data; + stream >> data; + QDataStream tableStream(&data, QIODevice::ReadOnly); + while (!tableStream.atEnd()) { + qint8 octet = 0; + QString field = QAmqpFrame::readAmqpField(tableStream, QAmqpMetaType::ShortString).toString(); + tableStream >> octet; + table[field] = QAmqpTable::readFieldValue(tableStream, valueTypeForOctet(octet)); + } + + return stream; +} diff --git a/qamqp/src/qamqptable.h b/qamqp/src/qamqptable.h new file mode 100644 index 0000000..454ee19 --- /dev/null +++ b/qamqp/src/qamqptable.h @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2012-2014 Alexey Shcherbakov + * Copyright (C) 2014-2015 Matt Broadstone + * Contact: https://github.com/mbroadst/qamqp + * + * This file is part of the QAMQP Library. + * + * This library is free software; 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. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + */ +#ifndef QAMQPTABLE_H +#define QAMQPTABLE_H + +#include + +#include "qamqpglobal.h" + +class QAMQP_EXPORT QAmqpTable : public QVariantHash +{ +public: + QAmqpTable() {} + inline QAmqpTable(const QVariantHash &variantHash) + : QVariantHash(variantHash) + { + } + + static void writeFieldValue(QDataStream &stream, const QVariant &value); + static void writeFieldValue(QDataStream &stream, QAmqpMetaType::ValueType type, const QVariant &value); + static QVariant readFieldValue(QDataStream &stream, QAmqpMetaType::ValueType type); +}; + +QAMQP_EXPORT QDataStream &operator<<(QDataStream &, const QAmqpTable &table); +QAMQP_EXPORT QDataStream &operator>>(QDataStream &, QAmqpTable &table); +Q_DECLARE_METATYPE(QAmqpTable) + +#endif // QAMQPTABLE_H diff --git a/qamqp/tests/auto/auto.pro b/qamqp/tests/auto/auto.pro new file mode 100644 index 0000000..d00173e --- /dev/null +++ b/qamqp/tests/auto/auto.pro @@ -0,0 +1,6 @@ +TEMPLATE = subdirs +SUBDIRS = \ + qamqpclient \ + qamqpexchange \ + qamqpqueue \ + qamqpchannel diff --git a/qamqp/tests/auto/qamqpchannel/qamqpchannel.pro b/qamqp/tests/auto/qamqpchannel/qamqpchannel.pro new file mode 100644 index 0000000..8faba30 --- /dev/null +++ b/qamqp/tests/auto/qamqpchannel/qamqpchannel.pro @@ -0,0 +1,6 @@ +DEPTH = ../../.. +include($${DEPTH}/qamqp.pri) +include($${DEPTH}/tests/tests.pri) + +TARGET = tst_qamqpchannel +SOURCES = tst_qamqpchannel.cpp diff --git a/qamqp/tests/auto/qamqpchannel/tst_qamqpchannel.cpp b/qamqp/tests/auto/qamqpchannel/tst_qamqpchannel.cpp new file mode 100644 index 0000000..dc4cf1a --- /dev/null +++ b/qamqp/tests/auto/qamqpchannel/tst_qamqpchannel.cpp @@ -0,0 +1,97 @@ +#include + +#include "signalspy.h" +#include "qamqptestcase.h" + +#include "qamqpclient.h" +#include "qamqpexchange.h" +#include "qamqpqueue.h" + +class tst_QAMQPChannel : public TestCase +{ + Q_OBJECT +private Q_SLOTS: + void init(); + void cleanup(); + + void close(); + void resume(); + void sharedChannel(); + void defineWithChannelNumber(); + +private: + QScopedPointer client; + +}; + +void tst_QAMQPChannel::init() +{ + client.reset(new QAmqpClient); + client->connectToHost(); + QVERIFY(waitForSignal(client.data(), SIGNAL(connected()))); +} + +void tst_QAMQPChannel::cleanup() +{ + if (client->isConnected()) { + client->disconnectFromHost(); + QVERIFY(waitForSignal(client.data(), SIGNAL(disconnected()))); + } +} + +void tst_QAMQPChannel::close() +{ + // exchange + QAmqpExchange *exchange = client->createExchange("test-close-channel"); + QVERIFY(waitForSignal(exchange, SIGNAL(opened()))); + exchange->declare(QAmqpExchange::Direct); + QVERIFY(waitForSignal(exchange, SIGNAL(declared()))); + exchange->close(); + QVERIFY(waitForSignal(exchange, SIGNAL(closed()))); + exchange->reopen(); + QVERIFY(waitForSignal(exchange, SIGNAL(opened()))); + exchange->remove(QAmqpExchange::roForce); + QVERIFY(waitForSignal(exchange, SIGNAL(removed()))); + + // queue + QAmqpQueue *queue = client->createQueue("test-close-channel"); + QVERIFY(waitForSignal(queue, SIGNAL(opened()))); + declareQueueAndVerifyConsuming(queue); + queue->close(); + QVERIFY(waitForSignal(queue, SIGNAL(closed()))); +} + +void tst_QAMQPChannel::resume() +{ + QAmqpQueue *queue = client->createQueue("test-resume"); + QVERIFY(waitForSignal(queue, SIGNAL(opened()))); + declareQueueAndVerifyConsuming(queue); + + queue->resume(); + QVERIFY(waitForSignal(queue, SIGNAL(resumed()))); +} + +void tst_QAMQPChannel::sharedChannel() +{ + QString routingKey = "test-shared-channel"; + QAmqpQueue *queue = client->createQueue(routingKey); + declareQueueAndVerifyConsuming(queue); + + QAmqpExchange *defaultExchange = client->createExchange("", queue->channelNumber()); + defaultExchange->publish("first message", routingKey); + QVERIFY(waitForSignal(queue, SIGNAL(messageReceived()))); + QAmqpMessage message = queue->dequeue(); + verifyStandardMessageHeaders(message, routingKey); + QCOMPARE(message.payload(), QByteArray("first message")); +} + +void tst_QAMQPChannel::defineWithChannelNumber() +{ + QString routingKey = "test-specific-channel-number"; + QAmqpQueue *queue = client->createQueue(routingKey, 25); + declareQueueAndVerifyConsuming(queue); + QCOMPARE(queue->channelNumber(), 25); +} + +QTEST_MAIN(tst_QAMQPChannel) +#include "tst_qamqpchannel.moc" diff --git a/qamqp/tests/auto/qamqpclient/certs.qrc b/qamqp/tests/auto/qamqpclient/certs.qrc new file mode 100644 index 0000000..5d9568c --- /dev/null +++ b/qamqp/tests/auto/qamqpclient/certs.qrc @@ -0,0 +1,7 @@ + + + ../../files/certs/testca/cacert.pem + ../../files/certs/client/cert.pem + ../../files/certs/client/key.pem + + diff --git a/qamqp/tests/auto/qamqpclient/qamqpclient.pro b/qamqp/tests/auto/qamqpclient/qamqpclient.pro new file mode 100644 index 0000000..3d4c9aa --- /dev/null +++ b/qamqp/tests/auto/qamqpclient/qamqpclient.pro @@ -0,0 +1,7 @@ +DEPTH = ../../.. +include($${DEPTH}/qamqp.pri) +include($${DEPTH}/tests/tests.pri) + +TARGET = tst_qamqpclient +SOURCES = tst_qamqpclient.cpp +RESOURCES = certs.qrc diff --git a/qamqp/tests/auto/qamqpclient/tst_qamqpclient.cpp b/qamqp/tests/auto/qamqpclient/tst_qamqpclient.cpp new file mode 100644 index 0000000..9dc76c8 --- /dev/null +++ b/qamqp/tests/auto/qamqpclient/tst_qamqpclient.cpp @@ -0,0 +1,331 @@ +#include +#include +#include + +#include "qamqptestcase.h" +#include "qamqpauthenticator.h" +#include "qamqpexchange.h" +#include "qamqpqueue.h" +#include "qamqpclient_p.h" +#include "qamqpclient.h" + +class tst_QAMQPClient : public TestCase +{ + Q_OBJECT +private Q_SLOTS: + void connect(); + void connectProperties(); + void connectHostAddress(); + void connectDisconnect(); + void invalidAuthenticationMechanism(); + void tune(); + void socketError(); + void validateUri_data(); + void validateUri(); + void issue38(); + void issue38_take2(); + +public Q_SLOTS: // temporarily disabled + void autoReconnect(); + void autoReconnectTimeout(); + void sslConnect(); + +private: + QSslConfiguration createSslConfiguration(); + void issue38_helper(QAmqpClient *client); + +}; + +QSslConfiguration tst_QAMQPClient::createSslConfiguration() +{ + QList caCerts = + QSslCertificate::fromPath(QLatin1String(":/certs/ca-cert.pem")); + QList localCerts = + QSslCertificate::fromPath(QLatin1String(":/certs/client-cert.pem")); + QFile keyFile( QLatin1String(":/certs/client-key.pem")); + keyFile.open(QIODevice::ReadOnly); + QSslKey key(&keyFile, QSsl::Rsa, QSsl::Pem, QSsl::PrivateKey); + keyFile.close(); + + QSslConfiguration sslConfiguration; + sslConfiguration.setCaCertificates(caCerts); + sslConfiguration.setLocalCertificate(localCerts.first()); + sslConfiguration.setPrivateKey(key); + sslConfiguration.setProtocol(QSsl::SecureProtocols); + return sslConfiguration; +} + +void tst_QAMQPClient::connect() +{ + QAmqpClient client; + client.connectToHost(); + QVERIFY(waitForSignal(&client, SIGNAL(connected()))); + + QCOMPARE(client.host(), QLatin1String(AMQP_HOST)); + QCOMPARE(client.port(), quint16(AMQP_PORT)); + QCOMPARE(client.virtualHost(), QLatin1String(AMQP_VHOST)); + QCOMPARE(client.username(), QLatin1String(AMQP_LOGIN)); + QCOMPARE(client.password(), QLatin1String(AMQP_PSWD)); + QCOMPARE(client.autoReconnect(), false); + + client.disconnectFromHost(); + QVERIFY(waitForSignal(&client, SIGNAL(disconnected()))); +} + +void tst_QAMQPClient::sslConnect() +{ + QAmqpClient client; + client.setSslConfiguration(createSslConfiguration()); + QObject::connect(&client, SIGNAL(sslErrors(QList)), + &client, SLOT(ignoreSslErrors(QList))); + + client.connectToHost(); + QVERIFY(waitForSignal(&client, SIGNAL(connected()))); +} + +void tst_QAMQPClient::connectProperties() +{ + QAmqpClient client; + client.setHost("localhost"); + client.setPort(5672); + client.setVirtualHost("/"); + client.setUsername("guest"); + client.setPassword("guest"); + client.setAutoReconnect(false); + client.connectToHost(); + QVERIFY(waitForSignal(&client, SIGNAL(connected()))); + client.disconnectFromHost(); + QVERIFY(waitForSignal(&client, SIGNAL(disconnected()))); +} + +void tst_QAMQPClient::connectHostAddress() +{ + QAmqpClient client; + client.connectToHost(QHostAddress::LocalHost, 5672); + QVERIFY(waitForSignal(&client, SIGNAL(connected()))); + client.disconnectFromHost(); + QVERIFY(waitForSignal(&client, SIGNAL(disconnected()))); +} + +void tst_QAMQPClient::connectDisconnect() +{ + QAmqpClient client; + client.connectToHost(); + QVERIFY(waitForSignal(&client, SIGNAL(connected()))); + client.disconnectFromHost(); + QVERIFY(waitForSignal(&client, SIGNAL(disconnected()))); +} + +class InvalidAuthenticator : public QAmqpAuthenticator +{ +public: + virtual QString type() const { return "CRAZYAUTH"; } + virtual void write(QDataStream &out) { + Q_UNUSED(out); + } +}; + +void tst_QAMQPClient::invalidAuthenticationMechanism() +{ + QAmqpClient client; + client.setAuth(new InvalidAuthenticator); + client.connectToHost(); + QVERIFY(waitForSignal(&client, SIGNAL(disconnected()))); +} + +void tst_QAMQPClient::autoReconnect() +{ + // TODO: this is a fairly crude way of testing this, research + // better alternatives + + QAmqpClient client; + client.setAutoReconnect(true); + client.connectToHost(); + QVERIFY(waitForSignal(&client, SIGNAL(connected()))); + QProcess::execute("rabbitmqctl", QStringList() << "stop_app"); + QVERIFY(waitForSignal(&client, SIGNAL(disconnected()))); + QProcess::execute("rabbitmqctl", QStringList() << "start_app"); + QVERIFY(waitForSignal(&client, SIGNAL(connected()), 2)); +} + +void tst_QAMQPClient::autoReconnectTimeout() +{ + // TODO: this is a fairly crude way of testing this, research + // better alternatives + + QAmqpClient client; + client.setAutoReconnect(true, 3); + client.connectToHost(); + QVERIFY(waitForSignal(&client, SIGNAL(connected()), 60)); + qDebug() <<"connected" ; + QProcess::execute("rabbitmqctl", QStringList() << "stop_app"); + QVERIFY(waitForSignal(&client, SIGNAL(disconnected()), 60)); + qDebug() <<"disconnected" ; + QProcess::execute("rabbitmqctl", QStringList() << "start_app"); + QVERIFY(waitForSignal(&client, SIGNAL(connected()), 60)); + qDebug() <<"connected" ; + + QVERIFY(waitForSignal(&client, SIGNAL(disconnected()), 60)); + QProcess::execute("rabbitmqctl", QStringList() << "start_app"); + QVERIFY(waitForSignal(&client, SIGNAL(connected()), 60)); +} + +void tst_QAMQPClient::tune() +{ + QAmqpClient client; + client.setChannelMax(15); + client.setFrameMax(5000); + client.setHeartbeatDelay(600); + + client.connectToHost(); + QVERIFY(waitForSignal(&client, SIGNAL(connected()))); + QCOMPARE((int) client.channelMax(), 2047); + QCOMPARE((int)client.heartbeatDelay(), 600); + QCOMPARE((int)client.frameMax(), 5000); + + client.disconnectFromHost(); + QVERIFY(waitForSignal(&client, SIGNAL(disconnected()))); +} + +void tst_QAMQPClient::socketError() +{ + QAmqpClient client; + client.connectToHost("amqp://127.0.0.1:56725/"); + QVERIFY(waitForSignal(&client, SIGNAL(socketErrorOccurred(QAbstractSocket::SocketError)))); + QCOMPARE(client.socketError(), QAbstractSocket::ConnectionRefusedError); +} + +void tst_QAMQPClient::validateUri_data() +{ + QTest::addColumn("uri"); + QTest::addColumn("expectedUsername"); + QTest::addColumn("expectedPassword"); + QTest::addColumn("expectedHost"); + QTest::addColumn("expectedPort"); + QTest::addColumn("expectedVirtualHost"); + + QTest::newRow("default vhost") << "amqp://guest:guest@192.168.1.10:5672/" + << "guest" << "guest" << "192.168.1.10" << quint16(5672) << "/"; + QTest::newRow("standard") << "amqp://user:pass@host:10000/vhost" + << "user" << "pass" << "host" << quint16(10000) << "vhost"; + QTest::newRow("urlencoded") << "amqp://user%61:%61pass@ho%61st:10000/v%2fhost" + << "usera" << "apass" << "hoast" << quint16(10000) << "v/host"; + QTest::newRow("empty") << "amqp://" << "" << "" << "" << quint16(AMQP_PORT) << ""; + QTest::newRow("empty2") << "amqp://:@/" << "" << "" << "" << quint16(AMQP_PORT) << "/"; + QTest::newRow("onlyuser") << "amqp://user@" << "user" << "" << "" << quint16(AMQP_PORT) << ""; + QTest::newRow("userpass") << "amqp://user:pass@" << "user" << "pass" << "" << quint16(AMQP_PORT) << ""; + QTest::newRow("onlyhost") << "amqp://host" << "" << "" << "host" << quint16(AMQP_PORT) << ""; + QTest::newRow("onlyport") << "amqp://:10000" << "" << "" << "" << quint16(10000) << ""; + QTest::newRow("onlyvhost") << "amqp:///vhost" << "" << "" << "" << quint16(AMQP_PORT) << "vhost"; + QTest::newRow("urlencodedvhost") << "amqp://host/%2f" + << "" << "" << "host" << quint16(AMQP_PORT) << "/"; + QTest::newRow("ipv6") << "amqp://[::1]" << "" << "" << "::1" << quint16(AMQP_PORT) << ""; +} + +void tst_QAMQPClient::validateUri() +{ + QFETCH(QString, uri); + QFETCH(QString, expectedUsername); + QFETCH(QString, expectedPassword); + QFETCH(QString, expectedHost); + QFETCH(quint16, expectedPort); + QFETCH(QString, expectedVirtualHost); + + QAmqpClientPrivate clientPrivate(0); + // fake init + clientPrivate.authenticator = QSharedPointer( + new QAmqpPlainAuthenticator(QString::fromLatin1(AMQP_LOGIN), QString::fromLatin1(AMQP_PSWD))); + + // test parsing + clientPrivate.parseConnectionString(uri); + QAmqpPlainAuthenticator *auth = + static_cast(clientPrivate.authenticator.data()); + + QCOMPARE(auth->login(), expectedUsername); + QCOMPARE(auth->password(), expectedPassword); + QCOMPARE(clientPrivate.host, expectedHost); + QCOMPARE(clientPrivate.port, expectedPort); + QCOMPARE(clientPrivate.virtualHost, expectedVirtualHost); +} + +void tst_QAMQPClient::issue38_helper(QAmqpClient *client) +{ + // connect + client->connectToHost(); + QVERIFY(waitForSignal(client, SIGNAL(connected()))); + + // create then declare, remove and close queue + QAmqpQueue *queue = client->createQueue(); + QVERIFY(waitForSignal(queue, SIGNAL(opened()))); + queue->declare(); + QVERIFY(waitForSignal(queue, SIGNAL(declared()))); + queue->remove(QAmqpExchange::roForce); + QVERIFY(waitForSignal(queue, SIGNAL(removed()))); + queue->close(); + QVERIFY(waitForSignal(queue, SIGNAL(closed()))); + queue->deleteLater(); + + // disconnect + client->disconnectFromHost(); + QVERIFY(waitForSignal(client, SIGNAL(disconnected()))); +} + +void tst_QAMQPClient::issue38() +{ + QAmqpClient client; + issue38_helper(&client); + issue38_helper(&client); +} + +void tst_QAMQPClient::issue38_take2() +{ + QAmqpClient client; + client.connectToHost(); + QVERIFY(waitForSignal(&client, SIGNAL(connected()))); + QAmqpExchange *exchange = client.createExchange("myexchange"); + exchange->declare(QAmqpExchange::Topic); + QVERIFY(waitForSignal(exchange, SIGNAL(declared()))); + QAmqpQueue *queue = client.createQueue(); + queue->declare(); + QVERIFY(waitForSignal(queue, SIGNAL(declared()))); + queue->bind(exchange, "routingKeyin"); + QVERIFY(waitForSignal(queue, SIGNAL(bound()))); + queue->consume(QAmqpQueue::coNoAck); + QVERIFY(waitForSignal(queue, SIGNAL(consuming(QString)))); + exchange->publish("test message", "routingKeyout"); + + // delete everything + queue->unbind(exchange, "routingKeyin"); + QVERIFY(waitForSignal(queue, SIGNAL(unbound()))); + queue->close(); + QVERIFY(waitForSignal(queue, SIGNAL(closed()))); + queue->deleteLater(); + + exchange->close(); + QVERIFY(waitForSignal(exchange, SIGNAL(closed()))); + exchange->deleteLater(); + + client.disconnectFromHost(); + QVERIFY(waitForSignal(&client,SIGNAL(disconnected()))); + + // repeat the connection and create again here + client.connectToHost(); + QVERIFY(waitForSignal(&client, SIGNAL(connected()))); + exchange = client.createExchange("myexchange"); + exchange->declare(QAmqpExchange::Topic); + QVERIFY(waitForSignal(exchange, SIGNAL(declared()))); + queue = client.createQueue(); + queue->declare(); + QVERIFY(waitForSignal(queue, SIGNAL(declared()))); + queue->bind(exchange, "routingKeyin"); + QVERIFY(waitForSignal(queue, SIGNAL(bound()))); + queue->consume(QAmqpQueue::coNoAck); + QVERIFY(waitForSignal(queue, SIGNAL(consuming(QString)))); + exchange->publish("test message", "routingKeyout"); + client.disconnectFromHost(); + QVERIFY(waitForSignal(&client,SIGNAL(disconnected()))); +} + + +QTEST_MAIN(tst_QAMQPClient) +#include "tst_qamqpclient.moc" diff --git a/qamqp/tests/auto/qamqpexchange/qamqpexchange.pro b/qamqp/tests/auto/qamqpexchange/qamqpexchange.pro new file mode 100644 index 0000000..285a75a --- /dev/null +++ b/qamqp/tests/auto/qamqpexchange/qamqpexchange.pro @@ -0,0 +1,6 @@ +DEPTH = ../../.. +include($${DEPTH}/qamqp.pri) +include($${DEPTH}/tests/tests.pri) + +TARGET = tst_qamqpexchange +SOURCES = tst_qamqpexchange.cpp diff --git a/qamqp/tests/auto/qamqpexchange/tst_qamqpexchange.cpp b/qamqp/tests/auto/qamqpexchange/tst_qamqpexchange.cpp new file mode 100644 index 0000000..e9420b8 --- /dev/null +++ b/qamqp/tests/auto/qamqpexchange/tst_qamqpexchange.cpp @@ -0,0 +1,246 @@ +#include + +#include "signalspy.h" +#include "qamqptestcase.h" + +#include "qamqpclient.h" +#include "qamqpexchange.h" +#include "qamqpqueue.h" + +class tst_QAMQPExchange : public TestCase +{ + Q_OBJECT +private Q_SLOTS: + void init(); + void cleanup(); + + void standardTypes_data(); + void standardTypes(); + void invalidStandardDeclaration_data(); + void invalidStandardDeclaration(); + void invalidDeclaration(); + void invalidRedeclaration(); + void removeIfUnused(); + void invalidMandatoryRouting(); + void invalidImmediateRouting(); + void confirmsSupport(); + void confirmDontLoseMessages(); + void passiveDeclareNotFound(); + void cleanupOnDeletion(); + void testQueuedPublish(); + +private: + QScopedPointer client; + +}; + +void tst_QAMQPExchange::init() +{ + client.reset(new QAmqpClient); + client->connectToHost(); + QVERIFY(waitForSignal(client.data(), SIGNAL(connected()))); +} + +void tst_QAMQPExchange::cleanup() +{ + if (client->isConnected()) { + client->disconnectFromHost(); + QVERIFY(waitForSignal(client.data(), SIGNAL(disconnected()))); + } +} + +void tst_QAMQPExchange::standardTypes_data() +{ + QTest::addColumn("type"); + QTest::addColumn("delayedDeclaration"); + + QTest::newRow("direct") << QAmqpExchange::Direct << false; + QTest::newRow("direct-delayed") << QAmqpExchange::Direct << true; + QTest::newRow("fanout") << QAmqpExchange::FanOut << false; + QTest::newRow("fanout-delayed") << QAmqpExchange::FanOut << true; + QTest::newRow("topic") << QAmqpExchange::Topic << false; + QTest::newRow("topic-delayed") << QAmqpExchange::Topic << true; + QTest::newRow("headers") << QAmqpExchange::Headers << false; + QTest::newRow("headers-delayed") << QAmqpExchange::Headers << true; +} + +void tst_QAMQPExchange::standardTypes() +{ + QFETCH(QAmqpExchange::ExchangeType, type); + QFETCH(bool, delayedDeclaration); + + QAmqpExchange *exchange = client->createExchange("test"); + if (!delayedDeclaration) + QVERIFY(waitForSignal(exchange, SIGNAL(opened()))); + + exchange->declare(type); + QVERIFY(waitForSignal(exchange, SIGNAL(declared()))); + exchange->remove(QAmqpExchange::roForce); + QVERIFY(waitForSignal(exchange, SIGNAL(removed()))); +} + +void tst_QAMQPExchange::invalidStandardDeclaration_data() +{ + QTest::addColumn("exchangeName"); + QTest::addColumn("type"); + QTest::addColumn("error"); + + QTest::newRow("amq.direct") << "amq.direct" << QAmqpExchange::Direct << QAMQP::PreconditionFailedError; + QTest::newRow("amq.fanout") << "amq.fanout" << QAmqpExchange::FanOut << QAMQP::PreconditionFailedError; + QTest::newRow("amq.headers") << "amq.headers" << QAmqpExchange::Headers << QAMQP::PreconditionFailedError; + QTest::newRow("amq.match") << "amq.match" << QAmqpExchange::Headers << QAMQP::PreconditionFailedError; + QTest::newRow("amq.topic") << "amq.topic" << QAmqpExchange::Topic << QAMQP::PreconditionFailedError; + QTest::newRow("amq.reserved") << "amq.reserved" << QAmqpExchange::Direct << QAMQP::AccessRefusedError; +} + +void tst_QAMQPExchange::invalidStandardDeclaration() +{ + QFETCH(QString, exchangeName); + QFETCH(QAmqpExchange::ExchangeType, type); + QFETCH(QAMQP::Error, error); + + QAmqpExchange *exchange = client->createExchange(exchangeName); + exchange->declare(type); + QVERIFY(waitForSignal(exchange, SIGNAL(error(QAMQP::Error)))); + QCOMPARE(exchange->error(), error); +} + +void tst_QAMQPExchange::invalidDeclaration() +{ + QAmqpExchange *exchange = client->createExchange("test-invalid-declaration"); + exchange->declare("invalidExchangeType"); + QVERIFY(waitForSignal(client.data(), SIGNAL(error(QAMQP::Error)))); + QCOMPARE(client->error(), QAMQP::CommandInvalidError); +} + +void tst_QAMQPExchange::invalidRedeclaration() +{ + QAmqpExchange *exchange = client->createExchange("test-invalid-redeclaration"); + exchange->declare(QAmqpExchange::Direct); + QVERIFY(waitForSignal(exchange, SIGNAL(declared()))); + + QAmqpExchange *redeclared = client->createExchange("test-invalid-redeclaration"); + redeclared->declare(QAmqpExchange::FanOut); + QVERIFY(waitForSignal(redeclared, SIGNAL(error(QAMQP::Error)))); + + // this is per spec: + // QCOMPARE(redeclared->error(), QAMQP::NotAllowedError); + + // this is for rabbitmq: + QCOMPARE(redeclared->error(), QAMQP::PreconditionFailedError); + + // Server has probably closed the channel on us, if so, re-open it. + if (!exchange->isOpen()) { + exchange->reopen(); + QVERIFY(waitForSignal(exchange, SIGNAL(opened()))); + } + + // cleanup + exchange->remove(); + QVERIFY(waitForSignal(exchange, SIGNAL(removed()))); +} + +void tst_QAMQPExchange::removeIfUnused() +{ + QAmqpExchange *exchange = client->createExchange("test-if-unused-exchange"); + exchange->declare(QAmqpExchange::Direct, QAmqpExchange::AutoDelete); + QVERIFY(waitForSignal(exchange, SIGNAL(declared()))); + + QAmqpQueue *queue = client->createQueue("test-if-unused-queue"); + queue->declare(); + QVERIFY(waitForSignal(queue, SIGNAL(declared()))); + queue->bind("test-if-unused-exchange", "testRoutingKey"); + QVERIFY(waitForSignal(queue, SIGNAL(bound()))); + + exchange->remove(QAmqpExchange::roIfUnused); + QVERIFY(waitForSignal(exchange, SIGNAL(error(QAMQP::Error)))); + QCOMPARE(exchange->error(), QAMQP::PreconditionFailedError); + QVERIFY(!exchange->errorString().isEmpty()); + + // cleanup + queue->remove(QAmqpQueue::roForce); + QVERIFY(waitForSignal(queue, SIGNAL(removed()))); +} + +void tst_QAMQPExchange::invalidMandatoryRouting() +{ + QAmqpExchange *defaultExchange = client->createExchange(); + defaultExchange->publish("some message", "unroutable-key", QAmqpMessage::PropertyHash(), QAmqpExchange::poMandatory); + QVERIFY(waitForSignal(defaultExchange, SIGNAL(error(QAMQP::Error)))); + QCOMPARE(defaultExchange->error(), QAMQP::NoRouteError); +} + +void tst_QAMQPExchange::invalidImmediateRouting() +{ + QAmqpExchange *defaultExchange = client->createExchange(); + defaultExchange->publish("some message", "unroutable-key", QAmqpMessage::PropertyHash(), QAmqpExchange::poImmediate); + QVERIFY(waitForSignal(client.data(), SIGNAL(error(QAMQP::Error)))); + QCOMPARE(client->error(), QAMQP::NotImplementedError); +} + +void tst_QAMQPExchange::confirmsSupport() +{ + QAmqpExchange *exchange = client->createExchange("confirm-test"); + exchange->enableConfirms(); + QVERIFY(waitForSignal(exchange, SIGNAL(confirmsEnabled()))); +} + +void tst_QAMQPExchange::confirmDontLoseMessages() +{ + QAmqpExchange *defaultExchange = client->createExchange(); + defaultExchange->enableConfirms(); + QVERIFY(waitForSignal(defaultExchange, SIGNAL(confirmsEnabled()))); + + QAmqpMessage::PropertyHash properties; + properties[QAmqpMessage::DeliveryMode] = "2"; // make message persistent + + for (int i = 0; i < 10000; ++i) + defaultExchange->publish("noop", "confirms-test", properties); + QVERIFY(defaultExchange->waitForConfirms()); +} + +void tst_QAMQPExchange::passiveDeclareNotFound() +{ + QAmqpExchange *nonExistentExchange = client->createExchange("this-does-not-exist"); + nonExistentExchange->declare(QAmqpExchange::Direct, QAmqpExchange::Passive); + QVERIFY(waitForSignal(nonExistentExchange, SIGNAL(error(QAMQP::Error)))); + QCOMPARE(nonExistentExchange->error(), QAMQP::NotFoundError); +} + +void tst_QAMQPExchange::cleanupOnDeletion() +{ + // create, declare, and close the wrong way + QAmqpExchange *exchange = client->createExchange("test-deletion"); + exchange->declare(); + QVERIFY(waitForSignal(exchange, SIGNAL(declared()))); + exchange->close(); + exchange->deleteLater(); + QVERIFY(waitForSignal(exchange, SIGNAL(destroyed()))); + + // now create, declare, and close the right way + exchange = client->createExchange("test-deletion"); + exchange->declare(); + QVERIFY(waitForSignal(exchange, SIGNAL(declared()))); + exchange->close(); + QVERIFY(waitForSignal(exchange, SIGNAL(closed()))); +} + +void tst_QAMQPExchange::testQueuedPublish() +{ + QAmqpExchange *defaultExchange = client->createExchange(); + defaultExchange->enableConfirms(); + QVERIFY(waitForSignal(defaultExchange, SIGNAL(confirmsEnabled()))); + + QAmqpMessage::PropertyHash properties; + properties[QAmqpMessage::DeliveryMode] = "2"; // make message persistent + for (int i = 0; i < 10000; ++i) { + QMetaObject::invokeMethod(defaultExchange, "publish", Qt::QueuedConnection, + Q_ARG(QString, "noop"), Q_ARG(QString, "confirms-test"), + Q_ARG(QAmqpMessage::PropertyHash, properties)); + } + + QVERIFY(defaultExchange->waitForConfirms()); +} + +QTEST_MAIN(tst_QAMQPExchange) +#include "tst_qamqpexchange.moc" diff --git a/qamqp/tests/auto/qamqpqueue/qamqpqueue.pro b/qamqp/tests/auto/qamqpqueue/qamqpqueue.pro new file mode 100644 index 0000000..38dedd1 --- /dev/null +++ b/qamqp/tests/auto/qamqpqueue/qamqpqueue.pro @@ -0,0 +1,6 @@ +DEPTH = ../../.. +include($${DEPTH}/qamqp.pri) +include($${DEPTH}/tests/tests.pri) + +TARGET = tst_qamqpqueue +SOURCES = tst_qamqpqueue.cpp diff --git a/qamqp/tests/auto/qamqpqueue/tst_qamqpqueue.cpp b/qamqp/tests/auto/qamqpqueue/tst_qamqpqueue.cpp new file mode 100644 index 0000000..e61c3a4 --- /dev/null +++ b/qamqp/tests/auto/qamqpqueue/tst_qamqpqueue.cpp @@ -0,0 +1,708 @@ +#include + +#include + +#include +#include "qamqptestcase.h" +#include "signalspy.h" + +#include "qamqpclient.h" +#include "qamqpqueue.h" +#include "qamqpexchange.h" + +class tst_QAMQPQueue : public TestCase +{ + Q_OBJECT +private Q_SLOTS: + void init(); + void cleanup(); + + void defaultExchange(); + void standardExchanges_data(); + void standardExchanges(); + void invalidDeclaration_data(); + void invalidDeclaration(); + void invalidBind(); + void unnamed(); + void exclusiveAccess(); + void exclusiveRemoval(); + void notFound(); + void remove(); + void removeIfUnused(); + void removeIfEmpty(); + void bindUnbind(); + void delayedBind(); + void purge(); + void canOnlyStartConsumingOnce(); + void ensureConsumeOnlySentOnce(); + void cancel(); + void invalidCancelBecauseNotConsuming(); + void invalidCancelBecauseInvalidConsumerTag(); + void getEmpty(); + void get(); + void verifyContentEncodingIssue33(); + void defineQos(); + void invalidQos(); + void qos(); + void invalidRoutingKey(); + void tableFieldDataTypes(); + void messageProperties(); + void emptyMessage(); + void cleanupOnDeletion(); + +private: + QScopedPointer client; + +}; + +void tst_QAMQPQueue::init() +{ + client.reset(new QAmqpClient); + client->connectToHost(); + QVERIFY(waitForSignal(client.data(), SIGNAL(connected()))); +} + +void tst_QAMQPQueue::cleanup() +{ + if (client->isConnected()) { + client->disconnectFromHost(); + QVERIFY(waitForSignal(client.data(), SIGNAL(disconnected()))); + } +} + +void tst_QAMQPQueue::defaultExchange() +{ + QAmqpQueue *queue = client->createQueue("test-default-exchange"); + declareQueueAndVerifyConsuming(queue); + + QAmqpExchange *defaultExchange = client->createExchange(); + defaultExchange->publish("first message", "test-default-exchange"); + QVERIFY(waitForSignal(queue, SIGNAL(messageReceived()))); + QAmqpMessage message = queue->dequeue(); + verifyStandardMessageHeaders(message, "test-default-exchange"); + QCOMPARE(message.payload(), QByteArray("first message")); +} + +void tst_QAMQPQueue::standardExchanges_data() +{ + QTest::addColumn("exchange"); + QTest::addColumn("delayedDeclaration"); + + QTest::newRow("amq.direct") << "amq.direct" << false; + QTest::newRow("amq.direct-delayed") << "amq.direct" << true; + QTest::newRow("amq.fanout") << "amq.fanout" << false; + QTest::newRow("amq.fanout-delayed") << "amq.fanout" << true; + QTest::newRow("amq.headers") << "amq.headers" << false; + QTest::newRow("amq.headers-delayed") << "amq.headers" << true; + QTest::newRow("amq.match") << "amq.match" << false; + QTest::newRow("amq.match-delayed") << "amq.match" << true; + QTest::newRow("amq.topic") << "amq.topic" << false; + QTest::newRow("amq.topic-delayed") << "amq.topic" << true; +} + +void tst_QAMQPQueue::standardExchanges() +{ + QFETCH(QString, exchange); + QFETCH(bool, delayedDeclaration); + + QString queueName = QString("test-%1").arg(exchange); + QString routingKey = QString("testRoutingKey-%1").arg(exchange); + + QAmqpQueue *queue = client->createQueue(queueName); + if (!delayedDeclaration) + QVERIFY(waitForSignal(queue, SIGNAL(opened()))); + declareQueueAndVerifyConsuming(queue); + + queue->bind(exchange, routingKey); + QVERIFY(waitForSignal(queue, SIGNAL(bound()))); + + QAmqpExchange *defaultExchange = client->createExchange(exchange); + defaultExchange->publish("test message", routingKey); + QVERIFY(waitForSignal(queue, SIGNAL(messageReceived()))); + QAmqpMessage message = queue->dequeue(); + verifyStandardMessageHeaders(message, routingKey, exchange); + QCOMPARE(message.payload(), QByteArray("test message")); +} + +void tst_QAMQPQueue::invalidDeclaration_data() +{ + QTest::addColumn("queueName"); + QTest::addColumn("error"); + + QTest::newRow("amq.direct") << "amq.direct" << QAMQP::AccessRefusedError; + QTest::newRow("amq.fanout") << "amq.fanout" << QAMQP::AccessRefusedError; + QTest::newRow("amq.headers") << "amq.headers" << QAMQP::AccessRefusedError; + QTest::newRow("amq.match") << "amq.match" << QAMQP::AccessRefusedError; + QTest::newRow("amq.topic") << "amq.topic" << QAMQP::AccessRefusedError; + QTest::newRow("amq.reserved") << "amq.reserved" << QAMQP::AccessRefusedError; +} + +void tst_QAMQPQueue::invalidDeclaration() +{ + QFETCH(QString, queueName); + QFETCH(QAMQP::Error, error); + + QAmqpQueue *queue = client->createQueue(queueName); + queue->declare(); + QVERIFY(waitForSignal(queue, SIGNAL(error(QAMQP::Error)))); + QCOMPARE(queue->error(), error); +} + +void tst_QAMQPQueue::invalidBind() +{ + QAmqpQueue *queue = client->createQueue("test-invalid-bind"); + declareQueueAndVerifyConsuming(queue); + + queue->bind("non-existant-exchange", "routingKey"); + QVERIFY(waitForSignal(queue, SIGNAL(error(QAMQP::Error)))); + QCOMPARE(queue->error(), QAMQP::NotFoundError); +} + +void tst_QAMQPQueue::unnamed() +{ + QAmqpQueue *queue = client->createQueue(); + declareQueueAndVerifyConsuming(queue); + QVERIFY(!queue->name().isEmpty()); +} + +void tst_QAMQPQueue::exclusiveAccess() +{ + QAmqpQueue *queue = client->createQueue("test-exclusive-queue"); + queue->declare(QAmqpQueue::Exclusive); + QVERIFY(waitForSignal(queue, SIGNAL(declared()))); + QVERIFY(queue->options() & QAmqpQueue::Exclusive); + + QAmqpClient secondClient; + secondClient.connectToHost(); + QVERIFY(waitForSignal(&secondClient, SIGNAL(connected()))); + QAmqpQueue *passiveQueue = secondClient.createQueue("test-exclusive-queue"); + passiveQueue->declare(QAmqpQueue::Passive); + QVERIFY(waitForSignal(passiveQueue, SIGNAL(error(QAMQP::Error)))); + QCOMPARE(passiveQueue->error(), QAMQP::ResourceLockedError); + + secondClient.disconnectFromHost(); + QVERIFY(waitForSignal(&secondClient, SIGNAL(disconnected()))); +} + +void tst_QAMQPQueue::exclusiveRemoval() +{ + QAmqpQueue *queue = client->createQueue("test-exclusive-queue"); + queue->declare(QAmqpQueue::Exclusive); + QVERIFY(waitForSignal(queue, SIGNAL(declared()))); + QVERIFY(queue->options() & QAmqpQueue::Exclusive); + client.data()->disconnectFromHost(); + QVERIFY(waitForSignal(client.data(), SIGNAL(disconnected()))); + + // create a new client and try to access the queue that should + // no longer exist + QAmqpClient secondClient; + secondClient.connectToHost(); + QVERIFY(waitForSignal(&secondClient, SIGNAL(connected()))); + QAmqpQueue *passiveQueue = secondClient.createQueue("test-exclusive-queue"); + passiveQueue->declare(QAmqpQueue::Passive); + QVERIFY(waitForSignal(passiveQueue, SIGNAL(error(QAMQP::Error)))); + QCOMPARE(passiveQueue->error(), QAMQP::NotFoundError); + secondClient.disconnectFromHost(); + QVERIFY(waitForSignal(&secondClient, SIGNAL(disconnected()))); +} + +void tst_QAMQPQueue::notFound() +{ + QAmqpQueue *queue = client->createQueue("test-not-found"); + queue->declare(QAmqpQueue::Passive); + QVERIFY(waitForSignal(queue, SIGNAL(error(QAMQP::Error)))); + QCOMPARE(queue->error(), QAMQP::NotFoundError); +} + +void tst_QAMQPQueue::remove() +{ + QAmqpQueue *queue = client->createQueue("test-remove"); + queue->declare(); + QVERIFY(waitForSignal(queue, SIGNAL(declared()))); + queue->remove(QAmqpQueue::roIfEmpty|QAmqpQueue::roIfUnused); + QVERIFY(waitForSignal(queue, SIGNAL(removed()))); +} + +void tst_QAMQPQueue::removeIfUnused() +{ + QAmqpQueue *queue = client->createQueue("test-remove-if-unused"); + declareQueueAndVerifyConsuming(queue); + + queue->remove(QAmqpQueue::roIfUnused); + QVERIFY(waitForSignal(queue, SIGNAL(error(QAMQP::Error)))); + QCOMPARE(queue->error(), QAMQP::PreconditionFailedError); + QVERIFY(!queue->errorString().isEmpty()); +} + +void tst_QAMQPQueue::removeIfEmpty() +{ + // declare the queue and send messages to it + QAmqpQueue *queue = client->createQueue("test-remove-if-empty"); + queue->declare(QAmqpQueue::Durable); + QVERIFY(waitForSignal(queue, SIGNAL(declared()))); + QVERIFY(queue->options() & QAmqpQueue::Durable); + + QAmqpExchange *defaultExchange = client->createExchange(); + defaultExchange->publish("first message", "test-remove-if-empty"); + + // create a second client and try to delete the queue + { + QAmqpClient secondClient; + secondClient.connectToHost(); + QVERIFY(waitForSignal(&secondClient, SIGNAL(connected()))); + QAmqpQueue *testDeleteQueue = secondClient.createQueue("test-remove-if-empty"); + testDeleteQueue->declare(QAmqpQueue::Passive); + QVERIFY(waitForSignal(testDeleteQueue, SIGNAL(declared()))); + QVERIFY(testDeleteQueue->options() & QAmqpQueue::Passive); + + testDeleteQueue->remove(QAmqpQueue::roIfEmpty); + QVERIFY(waitForSignal(testDeleteQueue, SIGNAL(error(QAMQP::Error)))); + QCOMPARE(testDeleteQueue->error(), QAMQP::PreconditionFailedError); + QVERIFY(!testDeleteQueue->errorString().isEmpty()); + + secondClient.disconnectFromHost(); + QVERIFY(waitForSignal(&secondClient, SIGNAL(disconnected()))); + } + + // clean up queue + queue->remove(QAmqpQueue::roForce); + QVERIFY(waitForSignal(queue, SIGNAL(removed()))); +} + +void tst_QAMQPQueue::bindUnbind() +{ + QAmqpQueue *queue = client->createQueue("test-bind-unbind"); + declareQueueAndVerifyConsuming(queue); + + queue->bind("amq.topic", "routingKey"); + QVERIFY(waitForSignal(queue, SIGNAL(bound()))); + queue->unbind("amq.topic", "routingKey"); + QVERIFY(waitForSignal(queue, SIGNAL(unbound()))); + + QAmqpExchange *amqTopic = client->createExchange("amq.topic"); + amqTopic->declare(QAmqpExchange::Direct, QAmqpExchange::Passive); + QVERIFY(waitForSignal(amqTopic, SIGNAL(declared()))); + queue->bind(amqTopic, "routingKey"); + QVERIFY(waitForSignal(queue, SIGNAL(bound()))); + queue->unbind(amqTopic, "routingKey"); + QVERIFY(waitForSignal(queue, SIGNAL(unbound()))); +} + +void tst_QAMQPQueue::delayedBind() +{ + client->disconnectFromHost(); + QVERIFY(waitForSignal(client.data(), SIGNAL(disconnected()))); + QAmqpQueue *queue = client->createQueue("test-delayed-bind"); + queue->declare(); + queue->bind("amq.topic", "routingKey"); + + client->connectToHost(); + QVERIFY(waitForSignal(client.data(), SIGNAL(connected()))); + QVERIFY(waitForSignal(queue, SIGNAL(declared()))); + QVERIFY(waitForSignal(queue, SIGNAL(bound()))); + + // clean up queue + queue->remove(QAmqpQueue::roForce); + QVERIFY(waitForSignal(queue, SIGNAL(removed()))); +} + +void tst_QAMQPQueue::purge() +{ + QAmqpQueue *queue = client->createQueue("test-purge"); + queue->declare(QAmqpQueue::Durable); + QVERIFY(waitForSignal(queue, SIGNAL(declared()))); + QVERIFY(queue->options() & QAmqpQueue::Durable); + + QAmqpExchange *defaultExchange = client->createExchange(); + defaultExchange->publish("first message", "test-purge"); + defaultExchange->publish("second message", "test-purge"); + defaultExchange->publish("third message", "test-purge"); + + // create second client to listen to messages and attempt purge + { + QAmqpClient secondClient; + secondClient.connectToHost(); + QVERIFY(waitForSignal(&secondClient, SIGNAL(connected()))); + QAmqpQueue *testPurgeQueue = secondClient.createQueue("test-purge"); + testPurgeQueue->declare(QAmqpQueue::Passive); + QVERIFY(waitForSignal(testPurgeQueue, SIGNAL(declared()))); + QVERIFY(testPurgeQueue->options() & QAmqpQueue::Passive); + + QSignalSpy spy(testPurgeQueue, SIGNAL(purged(int))); + testPurgeQueue->purge(); + QVERIFY(waitForSignal(testPurgeQueue, SIGNAL(purged(int)))); + QCOMPARE(spy.count(), 1); + QCOMPARE(testPurgeQueue->size(), 0); + QList arguments = spy.takeFirst(); + QCOMPARE(arguments.at(0).toInt(), 3); + + secondClient.disconnectFromHost(); + QVERIFY(waitForSignal(&secondClient, SIGNAL(disconnected()))); + } + + // clean up queue + queue->remove(QAmqpQueue::roForce); + QVERIFY(waitForSignal(queue, SIGNAL(removed()))); +} + +void tst_QAMQPQueue::canOnlyStartConsumingOnce() +{ + QAmqpQueue *queue = client->createQueue("test-single-consumer"); + QSignalSpy spy(queue, SIGNAL(consuming(QString))); + declareQueueAndVerifyConsuming(queue); + QCOMPARE(queue->consume(), false); +} + +void tst_QAMQPQueue::ensureConsumeOnlySentOnce() +{ + QAmqpQueue *queue = client->createQueue("test-single-consumer"); + QSignalSpy spy(queue, SIGNAL(consuming(QString))); + queue->declare(); + QVERIFY(waitForSignal(queue, SIGNAL(declared()))); + + // try to consume twice + QVERIFY(queue->consume()); + QCOMPARE(queue->consume(), false); + QVERIFY(spy.wait()); + QCOMPARE(spy.size(), 1); + + // clean up queue + queue->remove(QAmqpQueue::roForce); + QVERIFY(waitForSignal(queue, SIGNAL(removed()))); +} + +void tst_QAMQPQueue::cancel() +{ + QAmqpQueue *queue = client->createQueue("test-cancel"); + declareQueueAndVerifyConsuming(queue); + + QString consumerTag = queue->consumerTag(); + QSignalSpy cancelSpy(queue, SIGNAL(cancelled(QString))); + QVERIFY(queue->cancel()); + QVERIFY(waitForSignal(queue, SIGNAL(cancelled(QString)))); + QVERIFY(!cancelSpy.isEmpty()); + QList arguments = cancelSpy.takeFirst(); + QCOMPARE(arguments.at(0).toString(), consumerTag); +} + +void tst_QAMQPQueue::invalidCancelBecauseNotConsuming() +{ + QAmqpQueue *queue = client->createQueue("test-invalid-cancel-because-not-consuming"); + queue->declare(); + QVERIFY(waitForSignal(queue, SIGNAL(declared()))); + QCOMPARE(queue->cancel(), false); + + // clean up queue + queue->remove(QAmqpQueue::roForce); + QVERIFY(waitForSignal(queue, SIGNAL(removed()))); +} + +void tst_QAMQPQueue::invalidCancelBecauseInvalidConsumerTag() +{ + QAmqpQueue *queue = client->createQueue("test-invalid-cancel-because-invalid-consumer-tag"); + declareQueueAndVerifyConsuming(queue); + queue->setConsumerTag(QString()); + QCOMPARE(queue->cancel(), false); +} + +void tst_QAMQPQueue::getEmpty() +{ + QAmqpQueue *queue = client->createQueue("test-get-empty"); + declareQueueAndVerifyConsuming(queue); + + queue->get(); + QVERIFY(waitForSignal(queue, SIGNAL(empty()))); +} + +void tst_QAMQPQueue::get() +{ + QAmqpQueue *queue = client->createQueue("test-get"); + queue->declare(); + QVERIFY(waitForSignal(queue, SIGNAL(declared()))); + + const int messageCount = 200; + QAmqpExchange *defaultExchange = client->createExchange(); + for (int i = 0; i < messageCount; ++i) { + QString expected = QString("message %1").arg(i); + defaultExchange->publish(expected, "test-get"); + } + + for (int i = 0; i < messageCount; ++i) { + QString expected = QString("message %1").arg(i); + queue->get(false); + if (!waitForSignal(queue, SIGNAL(messageReceived()))) { + // NOTE: this is here instead of waiting for messages to be + // available with a sleep above. It makes the test a little + // longer if there's a miss, look into a proper fix in the future + i--; + continue; + } + + QAmqpMessage message = queue->dequeue(); + verifyStandardMessageHeaders(message, "test-get"); + QCOMPARE(message.payload(), expected.toUtf8()); + queue->ack(message); + } + + queue->get(false); + QVERIFY(waitForSignal(queue, SIGNAL(empty()))); + + // clean up queue + queue->remove(QAmqpQueue::roForce); + QVERIFY(waitForSignal(queue, SIGNAL(removed()))); +} + +void tst_QAMQPQueue::verifyContentEncodingIssue33() +{ + QAmqpQueue *queue = client->createQueue("test-issue-33"); + declareQueueAndVerifyConsuming(queue); + + QAmqpExchange *defaultExchange = client->createExchange(); + QAmqpMessage::PropertyHash properties; + properties.insert(QAmqpMessage::ContentEncoding, "fakeContentEncoding"); + defaultExchange->publish("some data", "test-issue-33", properties); + + QVERIFY(waitForSignal(queue, SIGNAL(messageReceived()))); + QAmqpMessage message = queue->dequeue(); + verifyStandardMessageHeaders(message, "test-issue-33"); + QVERIFY(message.hasProperty(QAmqpMessage::ContentEncoding)); + QString contentType = message.property(QAmqpMessage::ContentEncoding).toString(); + QCOMPARE(contentType, QLatin1String("fakeContentEncoding")); +} + +void tst_QAMQPQueue::defineQos() +{ + QAmqpQueue *queue = client->createQueue("test-define-qos"); + declareQueueAndVerifyConsuming(queue); + + queue->qos(10); + QVERIFY(waitForSignal(queue, SIGNAL(qosDefined()))); + QCOMPARE(queue->prefetchCount(), qint16(10)); + QCOMPARE(queue->prefetchSize(), 0); + + // clean up queue + queue->remove(QAmqpQueue::roForce); + QVERIFY(waitForSignal(queue, SIGNAL(removed()))); +} + +void tst_QAMQPQueue::invalidQos() +{ + QAmqpQueue *queue = client->createQueue("test-invalid-define-qos"); + declareQueueAndVerifyConsuming(queue); + + queue->qos(10, 10); + QVERIFY(waitForSignal(client.data(), SIGNAL(error(QAMQP::Error)))); + QCOMPARE(client->error(), QAMQP::NotImplementedError); +} + +void tst_QAMQPQueue::qos() +{ + QAmqpQueue *queue = client->createQueue("test-qos"); + queue->declare(); + QVERIFY(waitForSignal(queue, SIGNAL(declared()))); + + queue->qos(1); + QVERIFY(waitForSignal(queue, SIGNAL(qosDefined()))); + QCOMPARE(queue->prefetchCount(), qint16(1)); + QCOMPARE(queue->prefetchSize(), 0); + QVERIFY(queue->consume()); + QVERIFY(waitForSignal(queue, SIGNAL(consuming(QString)))); + + // load up the queue + const int messageCount = 10; + QAmqpExchange *defaultExchange = client->createExchange(); + for (int i = 0; i < messageCount; ++i) { + QString message = QString("message %1").arg(i); + defaultExchange->publish(message, "test-qos"); + } + + QVERIFY(waitForSignal(queue, SIGNAL(messageReceived()))); + int messageReceivedCount = 0; + while (!queue->isEmpty()) { + QString expected = QString("message %1").arg(messageReceivedCount); + QAmqpMessage message = queue->dequeue(); + verifyStandardMessageHeaders(message, "test-qos"); + QCOMPARE(message.payload(), expected.toUtf8()); + queue->ack(message); + messageReceivedCount++; + + if (messageReceivedCount < messageCount) + QVERIFY(waitForSignal(queue, SIGNAL(messageReceived()))); + } + + QCOMPARE(messageReceivedCount, messageCount); +} + +void tst_QAMQPQueue::invalidRoutingKey() +{ + QString routingKey = QString("%1").arg('1', 256, QLatin1Char('0')); + QAmqpQueue *queue = client->createQueue(routingKey); + queue->declare(); + QVERIFY(waitForSignal(client.data(), SIGNAL(error(QAMQP::Error)))); + QCOMPARE(client->error(), QAMQP::FrameError); +} + +void tst_QAMQPQueue::tableFieldDataTypes() +{ + QAmqpQueue *queue = client->createQueue("test-table-field-data-types"); + declareQueueAndVerifyConsuming(queue); + + QAMQP::Decimal decimal; + decimal.scale = 2; + decimal.value = 12345; + QVariant decimalVariant = QVariant::fromValue(decimal); + + QAmqpTable nestedTable; + nestedTable.insert("boolean", true); + nestedTable.insert("long-int", qint32(-65536)); + + QVariantList array; + array.append(true); + array.append(qint32(-65536)); + + QDateTime timestamp = QDateTime::currentDateTime(); + + QAmqpTable headers; + headers.insert("boolean", true); + headers.insert("short-short-int", qint8(-15)); + headers.insert("short-short-uint", quint8(15)); + headers.insert("short-int", qint16(-256)); + headers.insert("short-uint", QVariant::fromValue(quint16(256))); + headers.insert("long-int", qint32(-65536)); + headers.insert("long-uint", quint32(65536)); + headers.insert("long-long-int", qint64(-2147483648)); + headers.insert("long-long-uint", quint64(2147483648)); + headers.insert("float", 230.7); + headers.insert("double", double(FLT_MAX)); + headers.insert("decimal-value", decimalVariant); + headers.insert("short-string", QLatin1String("test")); + headers.insert("long-string", QLatin1String("test")); + headers.insert("timestamp", timestamp); + headers.insert("nested-table", nestedTable); + headers.insert("array", array); + headers.insert("bytes", QByteArray("abcdefg1234567")); + + QAmqpExchange *defaultExchange = client->createExchange(); + defaultExchange->publish("dummy", "test-table-field-data-types", "text.plain", headers); + + QVERIFY(waitForSignal(queue, SIGNAL(messageReceived()))); + QAmqpMessage message = queue->dequeue(); + + QCOMPARE(message.header("boolean").toBool(), true); + QCOMPARE(qint8(message.header("short-short-int").toInt()), qint8(-15)); + QCOMPARE(quint8(message.header("short-short-uint").toUInt()), quint8(15)); + QCOMPARE(qint16(message.header("short-int").toInt()), qint16(-256)); + QCOMPARE(quint16(message.header("short-uint").toUInt()), quint16(256)); + QCOMPARE(qint32(message.header("long-int").toInt()), qint32(-65536)); + QCOMPARE(quint32(message.header("long-uint").toUInt()), quint32(65536)); + QCOMPARE(qint64(message.header("long-long-int").toLongLong()), qint64(-2147483648)); + QCOMPARE(quint64(message.header("long-long-uint").toLongLong()), quint64(2147483648)); + QCOMPARE(message.header("float").toFloat(), float(230.7)); + QCOMPARE(message.header("double").toDouble(), double(FLT_MAX)); + QCOMPARE(message.header("short-string").toString(), QLatin1String("test")); + QCOMPARE(message.header("long-string").toString(), QLatin1String("test")); + QCOMPARE(message.header("bytes").toByteArray(), QByteArray("abcdefg1234567")); +#if (QT_VERSION >= QT_VERSION_CHECK(5, 8, 0)) + QCOMPARE(message.header("timestamp").toDateTime().toSecsSinceEpoch(), + timestamp.toSecsSinceEpoch()); +#else + QCOMPARE(message.header("timestamp").toDateTime().toTime_t(), timestamp.toTime_t()); +#endif + + QVERIFY(message.hasHeader("nested-table")); + QAmqpTable compareTable(message.header("nested-table").toHash()); + foreach (QString key, nestedTable.keys()) { + QVERIFY(compareTable.contains(key)); + QCOMPARE(nestedTable.value(key), compareTable.value(key)); + } + + QVERIFY(message.hasHeader("array")); + QVariantList compareArray = message.header("array").toList(); + QCOMPARE(array, compareArray); + + QAMQP::Decimal receivedDecimal = message.header("decimal-value").value(); + QCOMPARE(receivedDecimal.scale, qint8(2)); + QCOMPARE(receivedDecimal.value, quint32(12345)); +} + +void tst_QAMQPQueue::messageProperties() +{ + QAmqpQueue *queue = client->createQueue("test-message-properties"); + declareQueueAndVerifyConsuming(queue); + + QDateTime timestamp = QDateTime::currentDateTime(); + QAmqpMessage::PropertyHash properties; + properties.insert(QAmqpMessage::ContentType, "some-content-type"); + properties.insert(QAmqpMessage::ContentEncoding, "some-content-encoding"); + properties.insert(QAmqpMessage::DeliveryMode, 2); + properties.insert(QAmqpMessage::Priority, 5); + properties.insert(QAmqpMessage::CorrelationId, 42); + properties.insert(QAmqpMessage::ReplyTo, "another-queue"); + properties.insert(QAmqpMessage::MessageId, "some-message-id"); + properties.insert(QAmqpMessage::Expiration, "60000"); + properties.insert(QAmqpMessage::Timestamp, timestamp); + properties.insert(QAmqpMessage::Type, "some-message-type"); + properties.insert(QAmqpMessage::UserId, "guest"); + properties.insert(QAmqpMessage::AppId, "some-app-id"); + properties.insert(QAmqpMessage::ClusterID, "some-cluster-id"); + + QAmqpExchange *defaultExchange = client->createExchange(); + defaultExchange->publish("dummy", "test-message-properties", properties); + QVERIFY(waitForSignal(queue, SIGNAL(messageReceived()))); + QAmqpMessage message = queue->dequeue(); + + QCOMPARE(message.property(QAmqpMessage::ContentType).toString(), QLatin1String("some-content-type")); + QCOMPARE(message.property(QAmqpMessage::ContentEncoding).toString(), QLatin1String("some-content-encoding")); + QCOMPARE(message.property(QAmqpMessage::DeliveryMode).toInt(), 2); + QCOMPARE(message.property(QAmqpMessage::Priority).toInt(), 5); + QCOMPARE(message.property(QAmqpMessage::CorrelationId).toInt(), 42); + QCOMPARE(message.property(QAmqpMessage::ReplyTo).toString(), QLatin1String("another-queue")); + QCOMPARE(message.property(QAmqpMessage::MessageId).toString(), QLatin1String("some-message-id")); + QCOMPARE(message.property(QAmqpMessage::Expiration).toString(), QLatin1String("60000")); + QCOMPARE(message.property(QAmqpMessage::Type).toString(), QLatin1String("some-message-type")); + QCOMPARE(message.property(QAmqpMessage::UserId).toString(), QLatin1String("guest")); + QCOMPARE(message.property(QAmqpMessage::AppId).toString(), QLatin1String("some-app-id")); + QCOMPARE(message.property(QAmqpMessage::ClusterID).toString(), QLatin1String("some-cluster-id")); +#if (QT_VERSION >= QT_VERSION_CHECK(5, 8, 0)) + QCOMPARE(message.property(QAmqpMessage::Timestamp).toDateTime().toSecsSinceEpoch(), + timestamp.toSecsSinceEpoch()); +#else + QCOMPARE(message.property(QAmqpMessage::Timestamp).toDateTime().toTime_t(), + timestamp.toTime_t()); +#endif +} + +void tst_QAMQPQueue::emptyMessage() +{ + QAmqpQueue *queue = client->createQueue("test-issue-43"); + declareQueueAndVerifyConsuming(queue); + + QAmqpExchange *defaultExchange = client->createExchange(); + defaultExchange->publish("", "test-issue-43"); + + QVERIFY(waitForSignal(queue, SIGNAL(messageReceived()))); + QAmqpMessage message = queue->dequeue(); + verifyStandardMessageHeaders(message, "test-issue-43"); + QVERIFY(message.payload().isEmpty()); +} + +void tst_QAMQPQueue::cleanupOnDeletion() +{ + // create, declare, and close the wrong way + QAmqpQueue *queue = client->createQueue("test-deletion"); + queue->declare(); + QVERIFY(waitForSignal(queue, SIGNAL(declared()))); + queue->close(); + queue->deleteLater(); + QVERIFY(waitForSignal(queue, SIGNAL(destroyed()))); + + // now create, declare, and close the right way + queue = client->createQueue("test-deletion"); + queue->declare(); + QVERIFY(waitForSignal(queue, SIGNAL(declared()))); + queue->close(); + QVERIFY(waitForSignal(queue, SIGNAL(closed()))); +} + +QTEST_MAIN(tst_QAMQPQueue) +#include "tst_qamqpqueue.moc" diff --git a/qamqp/tests/common/qamqptestcase.h b/qamqp/tests/common/qamqptestcase.h new file mode 100644 index 0000000..78a8b70 --- /dev/null +++ b/qamqp/tests/common/qamqptestcase.h @@ -0,0 +1,50 @@ +#ifndef QAMQPTESTCASE_H +#define QAMQPTESTCASE_H + +#include +#include + +#include "qamqpqueue.h" + +class TestCase : public QObject +{ +public: + TestCase() {} + virtual ~TestCase() {} + +protected: + bool waitForSignal(QObject *obj, const char *signal, int delay = 5) + { + QObject::connect(obj, signal, &QTestEventLoop::instance(), SLOT(exitLoop())); + QPointer safe = obj; + + QTestEventLoop::instance().enterLoop(delay); + if (!safe.isNull()) + QObject::disconnect(safe, signal, &QTestEventLoop::instance(), SLOT(exitLoop())); + return !QTestEventLoop::instance().timeout(); + } + + void declareQueueAndVerifyConsuming(QAmqpQueue *queue) + { + queue->declare(); + QVERIFY(waitForSignal(queue, SIGNAL(declared()))); + QVERIFY(queue->consume()); + QSignalSpy spy(queue, SIGNAL(consuming(QString))); + QVERIFY(waitForSignal(queue, SIGNAL(consuming(QString)))); + QVERIFY(queue->isConsuming()); + QVERIFY(!spy.isEmpty()); + QList arguments = spy.takeFirst(); + QCOMPARE(arguments.at(0).toString(), queue->consumerTag()); + } + + void verifyStandardMessageHeaders(const QAmqpMessage &message, const QString &routingKey, + const QString &exchangeName = QLatin1String(""), + bool redelivered = false) + { + QCOMPARE(message.routingKey(), routingKey); + QCOMPARE(message.exchangeName(), exchangeName); + QCOMPARE(message.isRedelivered(), redelivered); + } +}; + +#endif // QAMQPTESTCASE_H diff --git a/qamqp/tests/common/signalspy.h b/qamqp/tests/common/signalspy.h new file mode 100644 index 0000000..ae81728 --- /dev/null +++ b/qamqp/tests/common/signalspy.h @@ -0,0 +1,20 @@ +#ifndef SIGNALSPY_H +#define SIGNALSPY_H + +namespace QAMQP { +namespace Test { + +bool waitForSignal(QObject *obj, const char *signal, int delay) +{ + QObject::connect(obj, signal, &QTestEventLoop::instance(), SLOT(exitLoop())); + QPointer safe = obj; + + QTestEventLoop::instance().enterLoop(delay); + if (!safe.isNull()) + QObject::disconnect(safe, signal, &QTestEventLoop::instance(), SLOT(exitLoop())); + return !QTestEventLoop::instance().timeout(); +} + +} // namespace Test +} // namespace QAMQP +#endif diff --git a/qamqp/tests/files/certs/client/cert.pem b/qamqp/tests/files/certs/client/cert.pem new file mode 100644 index 0000000..b074554 --- /dev/null +++ b/qamqp/tests/files/certs/client/cert.pem @@ -0,0 +1,18 @@ +-----BEGIN CERTIFICATE----- +MIIC5zCCAc+gAwIBAgIBAjANBgkqhkiG9w0BAQUFADATMREwDwYDVQQDEwhNeVRl +c3RDQTAeFw0xNDA4MjcxOTI2MDVaFw0xNTA4MjcxOTI2MDVaMCoxFzAVBgNVBAMM +Dm1icm9hZHN0LWJ1aWxkMQ8wDQYDVQQKDAZjbGllbnQwggEiMA0GCSqGSIb3DQEB +AQUAA4IBDwAwggEKAoIBAQDrzmzjgxNXxXX+fgfjB5Pt+YbO2uQR9PDADUyk+8Kw +/v1xjZBqKSHaBJLMv2nHlfGM8p92XQoepWKtG4z49UsT6MMppfUnZ/TO6LgUuJtw +FaVYdJmzK8SPvsQ331id9f4grgMTiff+i6hM2Bb9Jq83/jnglrBm8T4KHjPjJXQi +MN8d7ZkV2bo2vFQcO/KNTODntqINp5+OFPboyjDbMoMgUTqnXJBQsWwA9EVq2JYs +FYtA5xsqk0yG9DBgI5ClfxESQQo6lHKYeX2KIuHVO5awPpm+wZbIeR3l5QFqQrQZ +zfw7ANsA1RK4c85jb8K0vHxX1wV3kB+2kqpi4jxm/ucnAgMBAAGjLzAtMAkGA1Ud +EwQCMAAwCwYDVR0PBAQDAgeAMBMGA1UdJQQMMAoGCCsGAQUFBwMCMA0GCSqGSIb3 +DQEBBQUAA4IBAQAxoTOMViXAKveeYx7I0dve/Te3TXe6XTlF0iFNIMp0FB3X0OeA +Bjknf6SUxY4qYV9DsFBGtXg8irkbothVNQKrhSedb6n+OQGy5z24oJ+vWW5jCyf3 +TBoWRLnHY52j/4KElNpbEddacreYY6Ft5VYLZuyXy2G18xWjUnE5EG+QkizgAWzw +w9aTxS7qyGb7/FklJhH5OA8izi4JNbIrLEcUw4ECgYihtdLnZz/ANTp4kwz7qjaj +X7+8V3h7R59/HOHglCbjtkhBVuRyz5ljTfMbCava4Za2solujAo4tRxvmhioog0t +QplQjUP4QM5jfFlD/1HXY2SzYPG0FIiRj93L +-----END CERTIFICATE----- diff --git a/qamqp/tests/files/certs/client/key.pem b/qamqp/tests/files/certs/client/key.pem new file mode 100644 index 0000000..c5e2e26 --- /dev/null +++ b/qamqp/tests/files/certs/client/key.pem @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEpAIBAAKCAQEA685s44MTV8V1/n4H4weT7fmGztrkEfTwwA1MpPvCsP79cY2Q +aikh2gSSzL9px5XxjPKfdl0KHqVirRuM+PVLE+jDKaX1J2f0zui4FLibcBWlWHSZ +syvEj77EN99YnfX+IK4DE4n3/ouoTNgW/SavN/454JawZvE+Ch4z4yV0IjDfHe2Z +Fdm6NrxUHDvyjUzg57aiDaefjhT26Mow2zKDIFE6p1yQULFsAPRFatiWLBWLQOcb +KpNMhvQwYCOQpX8REkEKOpRymHl9iiLh1TuWsD6ZvsGWyHkd5eUBakK0Gc38OwDb +ANUSuHPOY2/CtLx8V9cFd5AftpKqYuI8Zv7nJwIDAQABAoIBAQDj32PSqIQ0uZlB +CcHNXzFRM2VW2Ki1waI1taRveuu153Q8G7WHIaCY8vp56i/qs7ftoTkARQDWhLRK +3OjqXQDkiHaw9LNoFUm5+aKKQ6vSMNjMFkHBp3YYAx3TcH5Oh73BDufiJd4FmihV +uizdDlkdHwwHQRfPIyn01SMHStZjgkOqIOkKq1Me8uggiYpTh/2sbX931cwxJnSF +EvDOLTvLjJdj6aWjupUaMvMsZDHJdtTZxl/YPV/KO49EkaOz0Ijv4mD8a0FQ5QRa +ud1xITFlFXOeZNjH3n6/+4ypIJDkXddpfZUuetoZ3DPRZY5aalKW+SGy3zqLu8qh +0VGPRQ7BAoGBAP7xNXis1ErCybdI1wvo3XIcsq7YHsssD+2IFmNomdBF5e9QzAwU +Q63WD6qmcLCSzjSm4dYZNvFL9RLWUCIkC7nkpqt0bftDw//NTdyinJo/JNf4Lprx +uji5njju+FuU83Whu3QoBXFTv7Ql09bX/6EgCfx1cWrJEHC6L3oGE3SpAoGBAOzI +5BfC+5TTbqWbjoH8ycdpjbEvyhpRKT920spa2j0kNjduryJHtq1AemLsR9NOH67h +cO5YHD9ClRMXxI4ogVbzOGqVAy3LdYXCJIV8GO/WTjjDINoPNb2+VfaHCkboS+8y +d1HwwcFbK7p3dJFNF3ppDVXsTfZZzDAQfFqa6A9PAoGAPzmYtjW+bFAEcJT65/Q3 +Pv6I/b2RXXeu94yBaOPfCXzcOk6CXBiGdE0bE4o1dkTiKMKeTVdxfcQFokdOFjl0 +QwTGpMy6Hc8/g2fqAGa/ia1RONJO1JRQR5MY/yucojG9cxXKBFOMjf9kEowzDhwB +RHdKoraJix8UGbDC53MsTgkCgYBepze23/Td219ByFtBTyICGwnPKMFrn8ITYpaE +2aigBFe/9PkBhRVbUIkb/kQADhzQNcKFJKe2ChG5niiugzag4X1N7d9lcQ27uI4M +5jy5szt1qVr6kFX1UZ7fe7/59GZWaiAUm194wc9LLPFmHCEkh9YS4PGRZvgexphP +R9k4NQKBgQDXYokjEt6jl67724/J8gP09oTAxZCBSweZkTHErUg8NdsUJWqBGLP9 +zFg1pOfAV9gy/qKm01SdG81lWcf8sDLa3QjB4WOW6x99DH2mQ7y69tStn8B3mAVB +o8Ddf50gjv54oSqFPrF1DAbBXWOEWfeLM44zyaBR9t28bNBJM4CEiQ== +-----END RSA PRIVATE KEY----- diff --git a/qamqp/tests/files/certs/client/keycert.p12 b/qamqp/tests/files/certs/client/keycert.p12 new file mode 100644 index 0000000000000000000000000000000000000000..a4e8643d623ef01e1f526b491d59bda6837327db GIT binary patch literal 2349 zcmV+|3DWj3f(a=C0Ru3C2=4|7Duzgg_YDCD0ic2i-~@sQ+%SR&*f4?vj|K@UhDe6@ z4FLxRpn?N{FoFYo0s#Opf&+C12`Yw2hW8Bt2LUh~1_~;MNQU%{^1jQA`qPGGbj z1L-W&g&gP>B)yF;=pO{~rh};Pm5|+uae|-C7iF4-M^+c2_UgC$`9L`{ME5Mx#PAOF zc*ibKH7Z_7RbczHR!5T+9R!gr${Az@2(Er(?^M7HekUaIev~)%{6q55e$09m#0h7A4oul%$>GtVVjJaqFPb&qH> z@@4gh2`csYhsIb~b53>yvC+=b3(t9UTK9~Jm_$6FBxCyyYwogEGxYz*f)cJl&s)y(szGgCW|1+h1oS+g% z#!N`Wh@O(CG4?JfERSt>S{Hd*7^*Tft~Jh!{Q3gEGBaEfg{LjxlescES6Yoi>B)nm zS?cG%RwIPok4k)n)2P}lHT+d)2+O>~n%3l9-~I_cQXg!oH7DBnSInfJr8c#Gc%8`U`xI>Lj%>n+ zkty-8u35$SaUuwKT)g(2{w8NDoP>7eK4{#TS0a6Vj!9~a4}8v}ZQ%2t`~B&*8Uqv$5WY=y(x z@LEtGo74!aV7V#=n`(tr**vE#9p|_MuD=5R|5gWkl`6|d*#ZpXuAk+fQ>&&W^B;u9 zfuavu<^b}B*rSi0OQ$M1?VL%9F6q!FZRkzoGcZkV*irKv(+ela{|GBeBmJ2w?a+1J z{A{r>D#H=SbLv0^o$88Y@26&MeU8|g+m9ww3Q^!cGmFtFXXX&2ka+D|7C0>#mQw4_ z(!S48ZiQ5H?ck;-^l}Qk7UNN6FoFd^1_>&LNQUUo%$G&$>8Ou5_p^tMIz2D zDEDOWl;nm`tfg-G*Iof6e~s>~f~D^jq!18QceP)oY#F^mJ`lV4v@d40gx~mNn2Cj- z@h0Y&bYF3fT_cwBy9(iiu)@CWsCc%$rO)hN+Zk732E2yeJs>~OOy5ciO`IpDRso30 zDmJ^q%&c!@BHh!pK-zN9agvjp7KGwqx9YU)=l6r&FIKzRw^-DAz=l9ApNh z8Oy{IgJm4wJc$14Gc+;^QURMaP zxTZ9rugE)s@DvGz{z}6)R$}B?Mj#m3pg`mBq)UfHqzIjXA45sfZ2M1Z6pXiiNiGJ> z2B*DfzbJl%_7EMlYp748c*|dvt zXb!_RsvM3~_+=hus%XgPH4**3#_*eEo4%E9S&oAatz?Q5y?I86MPYs)oMIqHG9 zy4$EdLeAM8!Wa85#oU8H)RUh6;|^%@1}r?Vj%GK+t9u+p2;2aqIG_Qj@z zG#%lr8|qs$arFv>z?V88`Z?woc|f+smo23h}@nh*nM`rAHZJe>2G*45mWd4dQwkh;W{7G z8}8AoDrM9%6URk@y>_Pr5pWd(e2UrtuF+{f8-b9PKVEiSLfz|swj=?|$F4ZPprU|J zI%!xL2mNiU;|nq8@gLdfH42lkwG2*U!8MIRaY8q>q&AP5AG)jy<Pzsto!V57br8{?xJqR>6~Sdr>axoNzveUyY@0OIVV%pq+U54 zA+nxD27NLUG_XL*)$5-=HD6v}0%*eHJ*to^{mMYIDu80yaJiqf#lm=`@MXCfuYzj9 zu*J2W5d7^!_xjGkHHv|QnOk|+%xb(`R}UG3sl;Bm7eLQO@fKuE`UlaKnU*v0+Ff$F=9RO>ueNFFzXP^ z&nBD+!g{1dLYxqIQ5-AIrA_?UXJiL!_OaBc%Dn97(nMcDXExs1QM~*2+)%Kxt#9C> z%G*lJphtnEn*VH=_!;@>3cz=;*&ka9HAZunGIwJmz+DvOK$sdE<0{f16V8IYu_{3> zbstlh3K@I(|2*uh^Ye|a>J5U!h;txbvf`NsLMbsNFe3&DDuzgg_YDCF6)_eB6mceH z=ro88GX{)#DNXH5e2+raDKIfGAutIB1uG5%0vZJX1QZhqWGdD|?r-~oeWi%f7k;9w Tb*}^ngp_br|I#Q`0s;sCu8d=2 literal 0 HcmV?d00001 diff --git a/qamqp/tests/files/certs/client/req.pem b/qamqp/tests/files/certs/client/req.pem new file mode 100644 index 0000000..38d7472 --- /dev/null +++ b/qamqp/tests/files/certs/client/req.pem @@ -0,0 +1,16 @@ +-----BEGIN CERTIFICATE REQUEST----- +MIICbzCCAVcCAQAwKjEXMBUGA1UEAwwObWJyb2Fkc3QtYnVpbGQxDzANBgNVBAoM +BmNsaWVudDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAOvObOODE1fF +df5+B+MHk+35hs7a5BH08MANTKT7wrD+/XGNkGopIdoEksy/aceV8Yzyn3ZdCh6l +Yq0bjPj1SxPowyml9Sdn9M7ouBS4m3AVpVh0mbMrxI++xDffWJ31/iCuAxOJ9/6L +qEzYFv0mrzf+OeCWsGbxPgoeM+MldCIw3x3tmRXZuja8VBw78o1M4Oe2og2nn44U +9ujKMNsygyBROqdckFCxbAD0RWrYliwVi0DnGyqTTIb0MGAjkKV/ERJBCjqUcph5 +fYoi4dU7lrA+mb7Blsh5HeXlAWpCtBnN/DsA2wDVErhzzmNvwrS8fFfXBXeQH7aS +qmLiPGb+5ycCAwEAAaAAMA0GCSqGSIb3DQEBCwUAA4IBAQAyDAY1l3GDSkLXOKId +GM0sB0Ve7tT64IsqFacp29wV15fgJH1368VOMwxiXRQVSvGQGWog0JzuX0qH12ZZ ++6zQnGhumuKtoqfwlPBFNtvFRFxQ61Dzk6RZaO5fC7ZW+cLrfcEjTh9X3ts2POwP +/iuFdr+r+422YDOmHY3gNKBYKg8MtaDUNSLSiwNEQ/CPNs3FsyObHutiMPgIKwqt +vZ2hkvvMWcYPf2dtPTS3AfMPWVP+zR4eDfeiKYoxCyYZHsvQEyYqP5P5U1elqia1 +gR9WUuC6Li+7wju6ksFrrLKGPNDXvfOm3Ecqfc5JPgU+U4bJLFRT1CFEOuYRnViK +V/jK +-----END CERTIFICATE REQUEST----- diff --git a/qamqp/tests/files/certs/server/cert.pem b/qamqp/tests/files/certs/server/cert.pem new file mode 100644 index 0000000..8bbd244 --- /dev/null +++ b/qamqp/tests/files/certs/server/cert.pem @@ -0,0 +1,18 @@ +-----BEGIN CERTIFICATE----- +MIIC5zCCAc+gAwIBAgIBATANBgkqhkiG9w0BAQUFADATMREwDwYDVQQDEwhNeVRl +c3RDQTAeFw0xNDA4MjcxOTI0MjNaFw0xNTA4MjcxOTI0MjNaMCoxFzAVBgNVBAMM +Dm1icm9hZHN0LWJ1aWxkMQ8wDQYDVQQKDAZzZXJ2ZXIwggEiMA0GCSqGSIb3DQEB +AQUAA4IBDwAwggEKAoIBAQDDV2SFR4wdqcGTJjXoxufcRcg1QPBrsglR2rvQL40i +oF9U9QAASwtn5c1+A5pkEdOb6xOrND/qiCW1jQgBKzi9qMnL9+61Z/Xykq5Op4qj +oqf1l6DV5nyHo9DOmqMKlBUGFR1PvwRcxmtl76+ekLxRP3Z38YbJHj1FT2H/9Dno +ThoImcxiSeMI1T7yBfv5SZ4TVheRIabkRcwT5FrU3P6TkVJq2PBjH4n6cNlLAMka +Ias4Jnxip4Xg/kk9JXlfce45EAMlgEpp/6zSYQqvpESo/2elElP39sFBPvv7HNIh +si7AKzIsFlEpsUFlcBkC1SD9jxV2xVbXZssCiX3ZM5F1AgMBAAGjLzAtMAkGA1Ud +EwQCMAAwCwYDVR0PBAQDAgUgMBMGA1UdJQQMMAoGCCsGAQUFBwMBMA0GCSqGSIb3 +DQEBBQUAA4IBAQCHXuSqK4vEDNIqZxMQiFqB4zwkz5KG3uZrbhqfHaqxxjinwlNJ +Sky9lAx2QN/sRDuk8M+8HZxRMsASIPzELMjjj19CduadkLFV4cj+0nP2m6K1li8y +RyGQpEwQi5MG2o+iQt3Ygw07KQJYhOXaifjEFJ8Q1U00KO+e9H7iLF8GrhLzmOv3 +usLPIvE8dnNu+EkrC57c48g9vkzR+BWl4TA1TcJBy9r219Z4jGrIysPWJUPwhKJj +tf9Uk9oHbMkuv5Qc+NhCumkB82phIt5WxeL1mKgwKVxiZJ+4DysfD7cgni8jhq86 +KZgEOMel6CekBa7ToLzUdvjU0SjT2DBBK6YD +-----END CERTIFICATE----- diff --git a/qamqp/tests/files/certs/server/key.pem b/qamqp/tests/files/certs/server/key.pem new file mode 100644 index 0000000..0d859d1 --- /dev/null +++ b/qamqp/tests/files/certs/server/key.pem @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEogIBAAKCAQEAw1dkhUeMHanBkyY16Mbn3EXINUDwa7IJUdq70C+NIqBfVPUA +AEsLZ+XNfgOaZBHTm+sTqzQ/6ogltY0IASs4vajJy/futWf18pKuTqeKo6Kn9Zeg +1eZ8h6PQzpqjCpQVBhUdT78EXMZrZe+vnpC8UT92d/GGyR49RU9h//Q56E4aCJnM +YknjCNU+8gX7+UmeE1YXkSGm5EXME+Ra1Nz+k5FSatjwYx+J+nDZSwDJGiGrOCZ8 +YqeF4P5JPSV5X3HuORADJYBKaf+s0mEKr6REqP9npRJT9/bBQT77+xzSIbIuwCsy +LBZRKbFBZXAZAtUg/Y8VdsVW12bLAol92TORdQIDAQABAoIBAFKRwEWuBoYLWW2P +uz3Xxe4P+R65gmajbNkSsky/rNK0I1fP794v2nRiaMgZUct21ZGUfk3h2hqSzg29 +vWJxGJzimdoDxP0dIpMUeWV54FpmyMRBAZUoxf63ue164+v2yCQ4DJnGzltA6+i8 +tek6mL9nKfZtO2ILzC5d7bi5TTjp/SXUiKG3VAFSxgxoBC9PGlL7BNFbm9JXSket +LVIWNj781pqBMEHvj9aLVG0uKpkY5jRjShHQ1a1v3l/WSDBsoVaG8xzvZSE5wd7s +Fjzk53siyzapOBhTCJc8NFoA88SfYYQfxCVrhIxpDhH6rYBMi/j99DTlj+Goi6eo +7aqEkwECgYEA7YwtwqGtPbakc5Y6shmLyniDah8xaVfrK4duGBvuoZCMek+ee2DN +WaeUKcSBBL0wWGVxnTm5MHleeadc92vF8eI/T7LKDnqPGfg9I9nteI43wCuRqKbz +YDseZnngBWFM6QnhYJrL1mH66zTAolaW8e+4U0ZsNO/Wk3RP8FrZDFUCgYEA0oPp +DIW+6i43dC9AOKxPv6lWdYOHnnh050WftR3sYfQ5FEqLln0jbio1WvaukQtPeruz +WhhAYbrkSjy1286NMjkhO3FbofiUkTgpI9YSubIchbGcem9G58IfhA41mAGzrGer +t65ip6f2jwOZkRM+t5/65iqQuGoCIWlnBpO3kKECgYB6mX6ElSz0TO9TOJXSlZyw +QsKQYsj9tYKKVLtddg0TFadq+OyygKN7QiIV7HUqHPp2pOSeYMxTWFCKOPaiO91N +mZdTatMd5eM1ZAkqF6+YKM5dQB9NC91QLTLjcMNOA4nOPGs1kK7jVm5KNk+1eTsu +YqqfUBlIuP/l2oHnavvagQKBgFPIYiE0vbXwLOvVvmaP1bF/EMT2Uyxz3nsJD7YC +sciObYUw4ftD1K0MqW2JjhJ2AOzk9U2fJ0h+HEube/l+bF2XtS02QXTmPSLKyjzT +/2HejFF9TbzAuuSUMvzYtuXHj53HKOWSxvrY810Z3q2JjkWAq1edizmKH0zy6SkJ +813hAoGAIGMMqi8HsqsvgTQebYohwkRBG+G+JPVF6rPD/+WfglnIoo4sNWNBnh6Z +e/+TkLsZR0QVnbQtStabroxxCkBkjzoDgu2Ff2mKhcFMsuNwWm/2hWHD8VLMomWi +7BK4OjVcBxOoQelmBEuwIaCiuADZBFgGEbV5yBdv8yD+ewUP/Z0= +-----END RSA PRIVATE KEY----- diff --git a/qamqp/tests/files/certs/server/keycert.p12 b/qamqp/tests/files/certs/server/keycert.p12 new file mode 100644 index 0000000000000000000000000000000000000000..d7477e712196787d1fd94439704d8edd2255ff73 GIT binary patch literal 2349 zcmV+|3DWj3f(a=C0Ru3C2=4|7Duzgg_YDCD0ic2i-~@sQ+%SR&*f4?vj|K@UhDe6@ z4FLxRpn?N{FoFYo0s#Opf&+C12`Yw2hW8Bt2LUh~1_~;MNQUC$|k2kmpcIwt~E&?A<{VkU#CQwm4Ci`JOMAhBkgb0BN$ zgtWshw9CfMwYs|3&|ScehIyV?&J7*r7GmS+e|*l z{y{SCp}XcDgB0i!P3H*{z}(T%BK?PRW}<{Tr}I-8{xvrt$6a0ZOE%3-+YL|!ffI_0 z;6{9T5p< z_C=El58}x96EEp<8rDrnKr(wC%O3Y3OsQhcT`o;K)(&XXm|`e%q96c=z@pGvGeBC{ zC17&ND{??=$GEPD^i*!7|JGSCAdGnXY~e(mV?0Xl43h$7J`e$vE8o8eccQz{X6d}$ zh&AaTihI@!s{+#cY?Y!?Tml6Z_NSLwKdC{>DQCe~bspyY_Bi~EaE0BdzjuNX$*M?z`j{2heS6Kmr&jYbuqBuSAq{|)7&Yj(2ci>!2vUzlmCO#${u5u_E;4C z1qt_;J?rQK3|^i5PKa1!quXHeLM9KLIlIgUT+J5ERNP5PT*M<*oWL`8otoY}YV-3X& zlKNC?k(vZL5ra6YMj=o(p}9-J$pFSs-Ktk(b>`x!04#Nw!eqjYC_n)AddFSEfTH9{XSe?E80` zQj!l4LJ=&*@9jWl(8kR5Z+wOeC*R(Ol952j&_$9S$+))g!##vu=`5_!6#b+b@~_VL z{QZeV@`3RXz%MV#(@10-bY&PdFoFd^1_>&LNQU8MQsJ-h@b!Aoa_J2#zwAUqyq8)WD-gGhin~YL3Z!*?QoX(FC z>x)f;kYj+P^5?yBxBlN|tLk?X5UkxDGS3h=2oSrfk|pFlCtLJL{1U}3doh}rOg?)5 zvW?oP#x2CZL##zU&fW)6U%1WX0=t^ zv+2VHnZC5v{#V*246ClL&`mL*a5q5rsa8L)%KJ4X07Hwk*2$X3qlYL3P_HOajTy~C z3eMxK-lByO`lE&}PJeJ3{5=Ko;^8PtMe$j14Dq%^=f<~wS>CB0;UHxzG>OnMYFQ77 z!V6WN#|5cz6H7<6fGZlt_YjHf8%d5lZ_!5VH-FDWgZ)&SmqccZMD3W~Ehf}^hn?uG zQj5EU(yp@(^$x%v`Lgqk{)G=(7;CV>_$72CDlx6_HJ@sAP1?(d%ZZR1nRQLiF&>>H zy0WA5_-)ACz59Y7v=vT#;J!xQ!D9qiXHuY5?e~pU=%g;JQ(NH6&JP>lfJDKkLKUD0 zs?{9M*HYIATMfeHLJkn%oVa^r!e^Qtfl8*oW)+>kLg>0i3Ss#?n%Y-wH53?Nv*Ytc zM1?Zq+@O6P0V|Gpe#wGVEYXXQ-8d$Bxp!#k_aJJ@I2LT9&2wLB^pelV#6Ha{JV+xQzse)%foFC=Gs;(9pUjM-;ApR z`+|ytpYD$7z~6XK{7D)`WC!snIUnid7s~N28%2Q7j=SnT=CYDd!b2zyS!7RecjBQGL!d2`uT_S=g=7( zctR*fF}YnFlLM{997!=ko3%`#FY^z^obp2Gc$(tGY^AHJf#sElf z;m{D?2G$;jJ7(?t>B^O4cA}eNeM(GJy*WN$_#o}J@_5^_zu}vtxMnOmbNCrm5p#JAe*loC7xoaCzv#SW8%zd9YCjupg zlC6b&Z7n6nbE(p+)ePhytsEoWEpe3urBM@nWJZ5S&`a7bjWN8b4Z@nQP)FA>MX77N z>$tTnUK?BRV2R@yC+Mh8cFe3&DDuzgg_YDCF6)_eB6o{@g zLLYQiR4{p4Qm)D78SG~9{aj|P^VpKvloRO7*xrvdV0q6)W zrY1&4hW-96iKmJl`DDs8{@Udm!xeV>;^~Y77Qt=r<|jP3B_C?GMed_W&n4jlU$qk% zZdq*UdLbRO?X+p8`6S+-65rVuPY=F%IWysW!n||E>!arXekk+D$lRJG=X*r(s=`mX zFKj0F|4|VB=^1P47I#Y7>B6Db9u}J`(`K-qJ|B6yKSAb-oZjh~q8kLJPZFK8yV2%| z+x%Ba3}03sy>hg%p^`UuZu7clI%NSC?f2>)`>5Yq@MQLtgc-g)CnBzXY*Rk;cg>SL zn_n}2FW#E)zy3l>^a`m`8;N-eBB6ecELj>)wL_v#O5R=+T9ajSR3Hv#|Z{Dis@_V*X2 zeU5qEpK86tHVZ%uL9bYm)_VNuya0pxRklcnQrSx zKI?AZdE)kKT1d!|G;5P3Di6NeHHSBEoUnXi(uTvo12%ne+3jbvYr|oMHB%1Ned5nl zd$7u6qTB)zo%Ww;f)jWnO=kFf@yKQ1DXJ)Zk?=-PcJm%9VV%tuo>kKX3*UrK`0{d| VY0c6Tyc#;5Q`67xn;9*A7XaT1DjWa+ literal 0 HcmV?d00001 diff --git a/qamqp/tests/files/certs/testca/cacert.pem b/qamqp/tests/files/certs/testca/cacert.pem new file mode 100644 index 0000000..090055e --- /dev/null +++ b/qamqp/tests/files/certs/testca/cacert.pem @@ -0,0 +1,17 @@ +-----BEGIN CERTIFICATE----- +MIICxjCCAa6gAwIBAgIJAOZK1btq1p0yMA0GCSqGSIb3DQEBBQUAMBMxETAPBgNV +BAMTCE15VGVzdENBMB4XDTE0MDgyNzE5MjI0MloXDTE1MDgyNzE5MjI0MlowEzER +MA8GA1UEAxMITXlUZXN0Q0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB +AQC/T4RhynLiTGkcgfq6TVwKVtvRy2jAOFOG7p9g4NofVTa0HvEUjNITwPUrYQDa +OLSK6BtStss1aTeSDfkY9wejl1PZ02lgz2CeznOvWp/74Rz8Mjc7BGz3WFOqcfJt +6DyTj/wgE/lJXTVGXsojQtDChYwEPNSWmAXLz1nLj2Ac1B4uy5kVsBCXkhWcu4E8 +xEaf6mIA9KvF1MWBgHkNbZ2DruYsdlA4h95+40wn2qDkm9RgmE2MyFjV8YYjwv2s +5G48+pj7o7Vg/3/QZFuoGnU8GJ4gFFVOQQRqKOUrVFrJGduiVXxqPNjTt1rfopX4 +pC8GZ69NotARfrwlBflM+Dm/AgMBAAGjHTAbMAwGA1UdEwQFMAMBAf8wCwYDVR0P +BAQDAgEGMA0GCSqGSIb3DQEBBQUAA4IBAQAJxWhxMpYywwJOkB243RhD76Y9OJ0q +RAboh03wrHpx5jgRZj4PxJrMdoDop/7mP9e/2UfIWVkVzKGH3ZPSrDaJ7bRooqgu +nR1T8yWm+/zDoKoZGl+pdc25rr+PcWbzXOuPZTukSM01AqgGuwmiRB70HzqJpV3u +IPLvkvrqUIcjpdi7ULmfB1caNzTMizviTK7b3ORG+pZUVMRmOzSkJOD1PoNXg7GQ +p5FisMP7ULL0RLtOMrqwwyCslMJ+8g9pJuCqNJEeoBQsh/lmEZANWTSYTPRIbQAM +cnhx6GDsER2zvDoTLLM4SXqWEXHsV5D06Z41fKXIDSgsSZVnzb6ZWxvd +-----END CERTIFICATE----- diff --git a/qamqp/tests/files/certs/testca/certs/01.pem b/qamqp/tests/files/certs/testca/certs/01.pem new file mode 100644 index 0000000..8bbd244 --- /dev/null +++ b/qamqp/tests/files/certs/testca/certs/01.pem @@ -0,0 +1,18 @@ +-----BEGIN CERTIFICATE----- +MIIC5zCCAc+gAwIBAgIBATANBgkqhkiG9w0BAQUFADATMREwDwYDVQQDEwhNeVRl +c3RDQTAeFw0xNDA4MjcxOTI0MjNaFw0xNTA4MjcxOTI0MjNaMCoxFzAVBgNVBAMM +Dm1icm9hZHN0LWJ1aWxkMQ8wDQYDVQQKDAZzZXJ2ZXIwggEiMA0GCSqGSIb3DQEB +AQUAA4IBDwAwggEKAoIBAQDDV2SFR4wdqcGTJjXoxufcRcg1QPBrsglR2rvQL40i +oF9U9QAASwtn5c1+A5pkEdOb6xOrND/qiCW1jQgBKzi9qMnL9+61Z/Xykq5Op4qj +oqf1l6DV5nyHo9DOmqMKlBUGFR1PvwRcxmtl76+ekLxRP3Z38YbJHj1FT2H/9Dno +ThoImcxiSeMI1T7yBfv5SZ4TVheRIabkRcwT5FrU3P6TkVJq2PBjH4n6cNlLAMka +Ias4Jnxip4Xg/kk9JXlfce45EAMlgEpp/6zSYQqvpESo/2elElP39sFBPvv7HNIh +si7AKzIsFlEpsUFlcBkC1SD9jxV2xVbXZssCiX3ZM5F1AgMBAAGjLzAtMAkGA1Ud +EwQCMAAwCwYDVR0PBAQDAgUgMBMGA1UdJQQMMAoGCCsGAQUFBwMBMA0GCSqGSIb3 +DQEBBQUAA4IBAQCHXuSqK4vEDNIqZxMQiFqB4zwkz5KG3uZrbhqfHaqxxjinwlNJ +Sky9lAx2QN/sRDuk8M+8HZxRMsASIPzELMjjj19CduadkLFV4cj+0nP2m6K1li8y +RyGQpEwQi5MG2o+iQt3Ygw07KQJYhOXaifjEFJ8Q1U00KO+e9H7iLF8GrhLzmOv3 +usLPIvE8dnNu+EkrC57c48g9vkzR+BWl4TA1TcJBy9r219Z4jGrIysPWJUPwhKJj +tf9Uk9oHbMkuv5Qc+NhCumkB82phIt5WxeL1mKgwKVxiZJ+4DysfD7cgni8jhq86 +KZgEOMel6CekBa7ToLzUdvjU0SjT2DBBK6YD +-----END CERTIFICATE----- diff --git a/qamqp/tests/files/certs/testca/certs/02.pem b/qamqp/tests/files/certs/testca/certs/02.pem new file mode 100644 index 0000000..b074554 --- /dev/null +++ b/qamqp/tests/files/certs/testca/certs/02.pem @@ -0,0 +1,18 @@ +-----BEGIN CERTIFICATE----- +MIIC5zCCAc+gAwIBAgIBAjANBgkqhkiG9w0BAQUFADATMREwDwYDVQQDEwhNeVRl +c3RDQTAeFw0xNDA4MjcxOTI2MDVaFw0xNTA4MjcxOTI2MDVaMCoxFzAVBgNVBAMM +Dm1icm9hZHN0LWJ1aWxkMQ8wDQYDVQQKDAZjbGllbnQwggEiMA0GCSqGSIb3DQEB +AQUAA4IBDwAwggEKAoIBAQDrzmzjgxNXxXX+fgfjB5Pt+YbO2uQR9PDADUyk+8Kw +/v1xjZBqKSHaBJLMv2nHlfGM8p92XQoepWKtG4z49UsT6MMppfUnZ/TO6LgUuJtw +FaVYdJmzK8SPvsQ331id9f4grgMTiff+i6hM2Bb9Jq83/jnglrBm8T4KHjPjJXQi +MN8d7ZkV2bo2vFQcO/KNTODntqINp5+OFPboyjDbMoMgUTqnXJBQsWwA9EVq2JYs +FYtA5xsqk0yG9DBgI5ClfxESQQo6lHKYeX2KIuHVO5awPpm+wZbIeR3l5QFqQrQZ +zfw7ANsA1RK4c85jb8K0vHxX1wV3kB+2kqpi4jxm/ucnAgMBAAGjLzAtMAkGA1Ud +EwQCMAAwCwYDVR0PBAQDAgeAMBMGA1UdJQQMMAoGCCsGAQUFBwMCMA0GCSqGSIb3 +DQEBBQUAA4IBAQAxoTOMViXAKveeYx7I0dve/Te3TXe6XTlF0iFNIMp0FB3X0OeA +Bjknf6SUxY4qYV9DsFBGtXg8irkbothVNQKrhSedb6n+OQGy5z24oJ+vWW5jCyf3 +TBoWRLnHY52j/4KElNpbEddacreYY6Ft5VYLZuyXy2G18xWjUnE5EG+QkizgAWzw +w9aTxS7qyGb7/FklJhH5OA8izi4JNbIrLEcUw4ECgYihtdLnZz/ANTp4kwz7qjaj +X7+8V3h7R59/HOHglCbjtkhBVuRyz5ljTfMbCava4Za2solujAo4tRxvmhioog0t +QplQjUP4QM5jfFlD/1HXY2SzYPG0FIiRj93L +-----END CERTIFICATE----- diff --git a/qamqp/tests/files/certs/testca/index.txt b/qamqp/tests/files/certs/testca/index.txt new file mode 100644 index 0000000..2598edd --- /dev/null +++ b/qamqp/tests/files/certs/testca/index.txt @@ -0,0 +1,2 @@ +V 150827192423Z 01 unknown /CN=mbroadst-build/O=server +V 150827192605Z 02 unknown /CN=mbroadst-build/O=client diff --git a/qamqp/tests/files/certs/testca/index.txt.attr b/qamqp/tests/files/certs/testca/index.txt.attr new file mode 100644 index 0000000..8f7e63a --- /dev/null +++ b/qamqp/tests/files/certs/testca/index.txt.attr @@ -0,0 +1 @@ +unique_subject = yes diff --git a/qamqp/tests/files/certs/testca/index.txt.attr.old b/qamqp/tests/files/certs/testca/index.txt.attr.old new file mode 100644 index 0000000..8f7e63a --- /dev/null +++ b/qamqp/tests/files/certs/testca/index.txt.attr.old @@ -0,0 +1 @@ +unique_subject = yes diff --git a/qamqp/tests/files/certs/testca/index.txt.old b/qamqp/tests/files/certs/testca/index.txt.old new file mode 100644 index 0000000..0082fe6 --- /dev/null +++ b/qamqp/tests/files/certs/testca/index.txt.old @@ -0,0 +1 @@ +V 150827192423Z 01 unknown /CN=mbroadst-build/O=server diff --git a/qamqp/tests/files/certs/testca/openssl.cnf b/qamqp/tests/files/certs/testca/openssl.cnf new file mode 100644 index 0000000..1e23cb5 --- /dev/null +++ b/qamqp/tests/files/certs/testca/openssl.cnf @@ -0,0 +1,53 @@ +[ ca ] +default_ca = testca + +[ testca ] +dir = . +certificate = $dir/cacert.pem +database = $dir/index.txt +new_certs_dir = $dir/certs +private_key = $dir/private/cakey.pem +serial = $dir/serial + +default_crl_days = 7 +default_days = 365 +default_md = sha1 + +policy = testca_policy +x509_extensions = certificate_extensions + +[ testca_policy ] +commonName = supplied +stateOrProvinceName = optional +countryName = optional +emailAddress = optional +organizationName = optional +organizationalUnitName = optional + +[ certificate_extensions ] +basicConstraints = CA:false + +[ req ] +default_bits = 2048 +default_keyfile = ./private/cakey.pem +default_md = sha1 +prompt = yes +distinguished_name = root_ca_distinguished_name +x509_extensions = root_ca_extensions + +[ root_ca_distinguished_name ] +commonName = hostname + +[ root_ca_extensions ] +basicConstraints = CA:true +keyUsage = keyCertSign, cRLSign + +[ client_ca_extensions ] +basicConstraints = CA:false +keyUsage = digitalSignature +extendedKeyUsage = 1.3.6.1.5.5.7.3.2 + +[ server_ca_extensions ] +basicConstraints = CA:false +keyUsage = keyEncipherment +extendedKeyUsage = 1.3.6.1.5.5.7.3.1 \ No newline at end of file diff --git a/qamqp/tests/files/certs/testca/private/cakey.pem b/qamqp/tests/files/certs/testca/private/cakey.pem new file mode 100644 index 0000000..c1bda7f --- /dev/null +++ b/qamqp/tests/files/certs/testca/private/cakey.pem @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC/T4RhynLiTGkc +gfq6TVwKVtvRy2jAOFOG7p9g4NofVTa0HvEUjNITwPUrYQDaOLSK6BtStss1aTeS +DfkY9wejl1PZ02lgz2CeznOvWp/74Rz8Mjc7BGz3WFOqcfJt6DyTj/wgE/lJXTVG +XsojQtDChYwEPNSWmAXLz1nLj2Ac1B4uy5kVsBCXkhWcu4E8xEaf6mIA9KvF1MWB +gHkNbZ2DruYsdlA4h95+40wn2qDkm9RgmE2MyFjV8YYjwv2s5G48+pj7o7Vg/3/Q +ZFuoGnU8GJ4gFFVOQQRqKOUrVFrJGduiVXxqPNjTt1rfopX4pC8GZ69NotARfrwl +BflM+Dm/AgMBAAECggEAUGYEhmxkN4JRMi/VxPG52oaCPvqy/QUu5SfnRvl38W8I +XE4clrxPlQmkfyR3DT6DcVT2Fp7Ha5zaQ8EnjDxUs4VnMcXNJWhBfLvaljkJvvru +CXa5C05i1NgD4T+d2F6fBoyeMoTyYMiRGQ/A92ye+wDQxP8jgF5HIU30uL16cOJh +/Znu6JJBjYgE9g7ce8REEpi2Fru2Ixj147ge1ICW801i0Xy5susCJvH3I817GKoq +NonAn5P+5zTv9mECDnNkhRViATigrQ8DYikNewPknrmfb0IMAvF8dTnCWI4KuorD +c4TD7w/zzrpncWNCnsDgWfgq9u9Anp6bvhED0VLiMQKBgQDgewv8skGmm4xAUKdR +BsDYIUgip57qj4EmPkjypn8lzVjDUnbBhr/NAUQK5pKnrzFE2H9/H7M1zlNwk1FS +m6GYjx4DmnGAvQ0LCBs4gxlT878n7TYTxkTge69tYQ0lmmAGGajmv2G4TtVADMlG +rojrQIYoSggVkUI+AyGhHhm8xQKBgQDaLCyBUSWPOMc33AozWMm2OYJ4nFWd1A0C +SLgpR6/+D8mT3o6YRYIMmh6AUFCENAKitbKujQOaKRll5aaNWO4JohhgcuzGEj5C +4F++7SXd6E/1+gtExnOHkPJ9z3FIeSoGCDK3DmfE8H9fcMM5mFZG7OVna3arINv4 +nT8s3aAMswKBgQDDM5yn3+Zg17AtGTV1qxa0mrRclkAFnkZjGBRdFNVJ7Pf72VC1 +VtSgkzI0/G2Y7So9wLmVtN4ksscyBJjZ6cWqoQErhvieR0b5SdJJ4Q58R1/5ezfk +GCw6vLM+vP8urMBFbbjG9rMmDz83FCdOlGUxlQlULZQ8FPVycUykC0W8NQKBgEjA +fj7JLnsp9dS8vXIN44Wue8F4cFxm/8eJNFAfpaJU5WU3y9kfJJTLN+yV26OaLF7R +tDncsBzSI7QE9psf0pDHytUuvaH3J2fppkPmlMAA3dkqfmN6wb+tKA+oAyCltsu4 +JCFC3nufrvnGgnNMR0jzajQoc7PxCylGVnDBnsNdAoGAV62Nc+T6HdZa9yDSPU5p +bNT40q0iZHmTUa6QQl+ZsRZP8u4w+RnfcUOK+QKJr43DraJgjWtwbO2VnCIMTA21 +EsSGuOCEMuYLMkswOrAJfM80FalsF3I74s5TbGYaczXXOe54XZJ8tyWSP0IiwreO ++eejI2bW3rU9TfBDdpR5Ks8= +-----END PRIVATE KEY----- diff --git a/qamqp/tests/files/certs/testca/serial b/qamqp/tests/files/certs/testca/serial new file mode 100644 index 0000000..75016ea --- /dev/null +++ b/qamqp/tests/files/certs/testca/serial @@ -0,0 +1 @@ +03 diff --git a/qamqp/tests/files/certs/testca/serial.old b/qamqp/tests/files/certs/testca/serial.old new file mode 100644 index 0000000..9e22bcb --- /dev/null +++ b/qamqp/tests/files/certs/testca/serial.old @@ -0,0 +1 @@ +02 diff --git a/qamqp/tests/files/travis/rabbitmq-setup.sh b/qamqp/tests/files/travis/rabbitmq-setup.sh new file mode 100755 index 0000000..15ff6d7 --- /dev/null +++ b/qamqp/tests/files/travis/rabbitmq-setup.sh @@ -0,0 +1,14 @@ +#!/bin/bash + +echo "[ + {rabbit, [ + {ssl_listeners, [5671]}, + {ssl_options, [{cacertfile,'${TRAVIS_BUILD_DIR}/tests/files/certs/testca/cacert.pem'}, + {certfile,'${TRAVIS_BUILD_DIR}/tests/files/certs/server/cert.pem'}, + {keyfile, '${TRAVIS_BUILD_DIR}/tests/files/certs/server/key.pem'}, + {verify,verify_peer}, + {fail_if_no_peer_cert,false}]} + ]} +]." >> rabbitmq.config + +sudo CONFIG_FILE=$PWD RABBITMQ_NODENAME=test-rabbitmq rabbitmq-server -detached diff --git a/qamqp/tests/files/travis/test-deps.sh b/qamqp/tests/files/travis/test-deps.sh new file mode 100755 index 0000000..6a4df66 --- /dev/null +++ b/qamqp/tests/files/travis/test-deps.sh @@ -0,0 +1,10 @@ +#!/bin/sh + +if [ "$QT_SELECT" = "qt4" ]; then + sudo apt-get update + sudo apt-get install libqt4-dev +else + sudo add-apt-repository -y ppa:canonical-qt5-edgers/ubuntu1204-qt5 + sudo apt-get update + sudo apt-get install qtbase5-dev +fi \ No newline at end of file diff --git a/qamqp/tests/gen-coverage.sh b/qamqp/tests/gen-coverage.sh new file mode 100755 index 0000000..d106568 --- /dev/null +++ b/qamqp/tests/gen-coverage.sh @@ -0,0 +1,4 @@ +#!/bin/bash +lcov --capture --directory . --output-file coverage-gcov.info --no-external +lcov --output-file coverage-gcov.info --remove coverage-gcov.info 'moc_*.cpp' '*.moc*' '.*rcc*' '*3rdparty*' +genhtml coverage-gcov.info --output-directory doc/coverage diff --git a/qamqp/tests/tests.pri b/qamqp/tests/tests.pri new file mode 100644 index 0000000..5880025 --- /dev/null +++ b/qamqp/tests/tests.pri @@ -0,0 +1,17 @@ +INCLUDEPATH += $${QAMQP_INCLUDEPATH} $${PWD}/common +LIBS += -L$${DEPTH}/src $${QAMQP_LIBS} + +unix:!macx:QMAKE_RPATHDIR += $${OUT_PWD}/$${DEPTH}/src +macx { + QMAKE_RPATHDIR += @loader_path/$${DEPTH}/src + QMAKE_LFLAGS += -Wl,-rpath,@loader_path/$${DEPTH}/src +} + +QT = core network testlib +QT -= gui +CONFIG -= app_bundle +CONFIG += testcase no_testcase_installs + +HEADERS += \ + $${PWD}/common/signalspy.h \ + $${PWD}/common/qamqptestcase.h diff --git a/qamqp/tests/tests.pro b/qamqp/tests/tests.pro new file mode 100644 index 0000000..b8445a7 --- /dev/null +++ b/qamqp/tests/tests.pro @@ -0,0 +1,3 @@ +TEMPLATE = subdirs +SUBDIRS = \ + auto diff --git a/qamqp/tutorials/helloworld/helloworld.pro b/qamqp/tutorials/helloworld/helloworld.pro new file mode 100644 index 0000000..bb957d8 --- /dev/null +++ b/qamqp/tutorials/helloworld/helloworld.pro @@ -0,0 +1,4 @@ +TEMPLATE = subdirs +SUBDIRS = \ + send \ + receive diff --git a/qamqp/tutorials/helloworld/receive/main.cpp b/qamqp/tutorials/helloworld/receive/main.cpp new file mode 100644 index 0000000..08eb156 --- /dev/null +++ b/qamqp/tutorials/helloworld/receive/main.cpp @@ -0,0 +1,81 @@ +#include +#include + +#include "qamqpclient.h" +#include "qamqpexchange.h" +#include "qamqpqueue.h" + +class Receiver : public QObject +{ + Q_OBJECT +public: + Receiver(QObject *parent = 0) : QObject(parent) { + m_client.setAutoReconnect(true); + } + +public Q_SLOTS: + void start() { + connect(&m_client, SIGNAL(connected()), this, SLOT(clientConnected())); + m_client.connectToHost(); + } + +private Q_SLOTS: + void clientConnected() { + QAmqpQueue *queue = m_client.createQueue("hello"); + disconnect(queue, 0, 0, 0); // in case this is a reconnect + connect(queue, SIGNAL(declared()), this, SLOT(queueDeclared())); + queue->declare(); + } + + void queueDeclared() { + QAmqpQueue *queue = qobject_cast(sender()); + if (!queue) + return; + + connect(queue, SIGNAL(messageReceived()), this, SLOT(messageReceived())); + + // queue->consume(QAmqpQueue::coNoAck); + // queue->consume(QAmqpQueue::coNoLocal); + + qint32 sizeQueue = queue->messageCount(); + while (sizeQueue--) { + queue->get(false); + } + + qDebug() << " [*] Waiting for messages. To exit press CTRL+C"; + + queue->ack(3, false); // Acknowledgement the 3rd message. + + queue->reopen(); + + // m_client.disconnectFromHost(); + } + + void messageReceived() { + QAmqpQueue *queue = qobject_cast(sender()); + if (!queue) + return; + + QAmqpMessage message = queue->dequeue(); + qDebug() << " [x] Received in" << message.payload(); + + int input=0; + // std::scanf("%d", &input); + qDebug() << " [x] Received out, " << message.deliveryTag() << " | " << message.payload() << " , input = " << input; + } + +private: + QAmqpClient m_client; + +}; + +int main(int argc, char **argv) +{ + qDebug() << " Recieve starts ... "; + QCoreApplication app(argc, argv); + Receiver receiver; + receiver.start(); + return app.exec(); +} + +#include "main.moc" diff --git a/qamqp/tutorials/helloworld/receive/receive.pro b/qamqp/tutorials/helloworld/receive/receive.pro new file mode 100644 index 0000000..6222eec --- /dev/null +++ b/qamqp/tutorials/helloworld/receive/receive.pro @@ -0,0 +1,9 @@ +DEPTH = ../../.. +include($${DEPTH}/qamqp.pri) + +TEMPLATE = app +INCLUDEPATH += $${QAMQP_INCLUDEPATH} +LIBS += -L$${DEPTH}/src $${QAMQP_LIBS} +macx:CONFIG -= app_bundle + +SOURCES += main.cpp diff --git a/qamqp/tutorials/helloworld/send/main.cpp b/qamqp/tutorials/helloworld/send/main.cpp new file mode 100644 index 0000000..c499594 --- /dev/null +++ b/qamqp/tutorials/helloworld/send/main.cpp @@ -0,0 +1,60 @@ +#include +#include +#include + +#include "qamqpclient.h" +#include "qamqpexchange.h" +#include "qamqpqueue.h" + +class Sender : public QObject +{ + Q_OBJECT +public: + Sender(QObject *parent = 0) : QObject(parent) {} + +public Q_SLOTS: + void start() { + connect(&m_client, SIGNAL(connected()), this, SLOT(clientConnected())); + connect(&m_client, SIGNAL(disconnected()), qApp, SLOT(quit())); + m_client.connectToHost(); + } + +private Q_SLOTS: + void clientConnected() { + QAmqpQueue *queue = m_client.createQueue("hello"); + connect(queue, SIGNAL(declared()), this, SLOT(queueDeclared())); + queue->declare(); + } + + void queueDeclared() { + QAmqpQueue *queue = qobject_cast(sender()); + if (!queue) + return; + QAmqpExchange *defaultExchange = m_client.createExchange(); + defaultExchange->publish("Hello World! A", "hello"); + qDebug() << " [x] Sent 'Hello World! A'"; + defaultExchange->publish("Hello World! B", "hello"); + qDebug() << " [x] Sent 'Hello World! B'"; + defaultExchange->publish("Hello World! C", "hello"); + qDebug() << " [x] Sent 'Hello World! C'"; + defaultExchange->publish("Hello World! D", "hello"); + qDebug() << " [x] Sent 'Hello World! D'"; + defaultExchange->publish("Hello World! E", "hello"); + qDebug() << " [x] Sent 'Hello World! E'"; + m_client.disconnectFromHost(); + } + +private: + QAmqpClient m_client; + +}; + +int main(int argc, char **argv) +{ + QCoreApplication app(argc, argv); + Sender sender; + sender.start(); + return app.exec(); +} + +#include "main.moc" diff --git a/qamqp/tutorials/helloworld/send/send.pro b/qamqp/tutorials/helloworld/send/send.pro new file mode 100644 index 0000000..6222eec --- /dev/null +++ b/qamqp/tutorials/helloworld/send/send.pro @@ -0,0 +1,9 @@ +DEPTH = ../../.. +include($${DEPTH}/qamqp.pri) + +TEMPLATE = app +INCLUDEPATH += $${QAMQP_INCLUDEPATH} +LIBS += -L$${DEPTH}/src $${QAMQP_LIBS} +macx:CONFIG -= app_bundle + +SOURCES += main.cpp diff --git a/qamqp/tutorials/pubsub/emit_log/emit_log.pro b/qamqp/tutorials/pubsub/emit_log/emit_log.pro new file mode 100644 index 0000000..6222eec --- /dev/null +++ b/qamqp/tutorials/pubsub/emit_log/emit_log.pro @@ -0,0 +1,9 @@ +DEPTH = ../../.. +include($${DEPTH}/qamqp.pri) + +TEMPLATE = app +INCLUDEPATH += $${QAMQP_INCLUDEPATH} +LIBS += -L$${DEPTH}/src $${QAMQP_LIBS} +macx:CONFIG -= app_bundle + +SOURCES += main.cpp diff --git a/qamqp/tutorials/pubsub/emit_log/main.cpp b/qamqp/tutorials/pubsub/emit_log/main.cpp new file mode 100644 index 0000000..b1bd02d --- /dev/null +++ b/qamqp/tutorials/pubsub/emit_log/main.cpp @@ -0,0 +1,57 @@ +#include +#include +#include + +#include "qamqpclient.h" +#include "qamqpexchange.h" +#include "qamqpqueue.h" + +class LogEmitter : public QObject +{ + Q_OBJECT +public: + LogEmitter(QObject *parent = 0) : QObject(parent) {} + +public Q_SLOTS: + void start() { + connect(&m_client, SIGNAL(connected()), this, SLOT(clientConnected())); + connect(&m_client, SIGNAL(disconnected()), qApp, SLOT(quit())); + m_client.connectToHost(); + } + +private Q_SLOTS: + void clientConnected() { + QAmqpExchange *exchange = m_client.createExchange("logs"); + connect(exchange, SIGNAL(declared()), this, SLOT(exchangeDeclared())); + exchange->declare(QAmqpExchange::FanOut); + } + + void exchangeDeclared() { + QAmqpExchange *exchange = qobject_cast(sender()); + if (!exchange) + return; + + QString message; + if (qApp->arguments().size() < 2) + message = "info: Hello World!"; + else + message = qApp->arguments().at(1); + exchange->publish(message, ""); + qDebug() << " [x] Sent " << message; + m_client.disconnectFromHost(); + } + +private: + QAmqpClient m_client; + +}; + +int main(int argc, char **argv) +{ + QCoreApplication app(argc, argv); + LogEmitter logEmitter; + logEmitter.start(); + return app.exec(); +} + +#include "main.moc" diff --git a/qamqp/tutorials/pubsub/pubsub.pro b/qamqp/tutorials/pubsub/pubsub.pro new file mode 100644 index 0000000..95d33dc --- /dev/null +++ b/qamqp/tutorials/pubsub/pubsub.pro @@ -0,0 +1,4 @@ +TEMPLATE = subdirs +SUBDIRS = \ + receive_logs \ + emit_log diff --git a/qamqp/tutorials/pubsub/receive_logs/main.cpp b/qamqp/tutorials/pubsub/receive_logs/main.cpp new file mode 100644 index 0000000..7b51d85 --- /dev/null +++ b/qamqp/tutorials/pubsub/receive_logs/main.cpp @@ -0,0 +1,75 @@ +#include +#include +#include + +#include "qamqpclient.h" +#include "qamqpexchange.h" +#include "qamqpqueue.h" + +class LogReceiver : public QObject +{ + Q_OBJECT +public: + LogReceiver(QObject *parent = 0) : QObject(parent) {} + +public Q_SLOTS: + void start() { + connect(&m_client, SIGNAL(connected()), this, SLOT(clientConnected())); + m_client.connectToHost(); + } + +private Q_SLOTS: + void clientConnected() { + QAmqpExchange *exchange = m_client.createExchange("logs"); + connect(exchange, SIGNAL(declared()), this, SLOT(exchangeDeclared())); + exchange->declare(QAmqpExchange::FanOut); + } + + void exchangeDeclared() { + QAmqpQueue *temporaryQueue = m_client.createQueue(); + connect(temporaryQueue, SIGNAL(declared()), this, SLOT(queueDeclared())); + connect(temporaryQueue, SIGNAL(bound()), this, SLOT(queueBound())); + connect(temporaryQueue, SIGNAL(messageReceived()), this, SLOT(messageReceived())); + temporaryQueue->declare(QAmqpQueue::Exclusive); + } + + void queueDeclared() { + QAmqpQueue *temporaryQueue = qobject_cast(sender()); + if (!temporaryQueue) + return; + + temporaryQueue->bind("logs", temporaryQueue->name()); + } + + void queueBound() { + QAmqpQueue *temporaryQueue = qobject_cast(sender()); + if (!temporaryQueue) + return; + + qDebug() << " [*] Waiting for logs. To exit press CTRL+C"; + temporaryQueue->consume(QAmqpQueue::coNoAck); + } + + void messageReceived() { + QAmqpQueue *temporaryQueue = qobject_cast(sender()); + if (!temporaryQueue) + return; + + QAmqpMessage message = temporaryQueue->dequeue(); + qDebug() << " [x] " << message.payload(); + } + +private: + QAmqpClient m_client; + +}; + +int main(int argc, char **argv) +{ + QCoreApplication app(argc, argv); + LogReceiver logReceiver; + logReceiver.start(); + return app.exec(); +} + +#include "main.moc" diff --git a/qamqp/tutorials/pubsub/receive_logs/receive_logs.pro b/qamqp/tutorials/pubsub/receive_logs/receive_logs.pro new file mode 100644 index 0000000..6222eec --- /dev/null +++ b/qamqp/tutorials/pubsub/receive_logs/receive_logs.pro @@ -0,0 +1,9 @@ +DEPTH = ../../.. +include($${DEPTH}/qamqp.pri) + +TEMPLATE = app +INCLUDEPATH += $${QAMQP_INCLUDEPATH} +LIBS += -L$${DEPTH}/src $${QAMQP_LIBS} +macx:CONFIG -= app_bundle + +SOURCES += main.cpp diff --git a/qamqp/tutorials/routing/emit_log_direct/emit_log_direct.pro b/qamqp/tutorials/routing/emit_log_direct/emit_log_direct.pro new file mode 100644 index 0000000..6222eec --- /dev/null +++ b/qamqp/tutorials/routing/emit_log_direct/emit_log_direct.pro @@ -0,0 +1,9 @@ +DEPTH = ../../.. +include($${DEPTH}/qamqp.pri) + +TEMPLATE = app +INCLUDEPATH += $${QAMQP_INCLUDEPATH} +LIBS += -L$${DEPTH}/src $${QAMQP_LIBS} +macx:CONFIG -= app_bundle + +SOURCES += main.cpp diff --git a/qamqp/tutorials/routing/emit_log_direct/main.cpp b/qamqp/tutorials/routing/emit_log_direct/main.cpp new file mode 100644 index 0000000..b6a2462 --- /dev/null +++ b/qamqp/tutorials/routing/emit_log_direct/main.cpp @@ -0,0 +1,64 @@ +#include +#include +#include + +#include "qamqpclient.h" +#include "qamqpexchange.h" +#include "qamqpqueue.h" + +class DirectLogEmitter : public QObject +{ + Q_OBJECT +public: + DirectLogEmitter(QObject *parent = 0) : QObject(parent) {} + +public Q_SLOTS: + void start() { + connect(&m_client, SIGNAL(connected()), this, SLOT(clientConnected())); + connect(&m_client, SIGNAL(disconnected()), qApp, SLOT(quit())); + m_client.connectToHost(); + } + +private Q_SLOTS: + void clientConnected() { + QAmqpExchange *direct_logs = m_client.createExchange("direct_logs"); + connect(direct_logs, SIGNAL(declared()), this, SLOT(exchangeDeclared())); + direct_logs->declare(QAmqpExchange::Direct); + } + + void exchangeDeclared() { + QAmqpExchange *direct_logs = qobject_cast(sender()); + if (!direct_logs) + return; + + QStringList args = qApp->arguments(); + args.takeFirst(); // remove executable name + + QString severity = (args.isEmpty() ? "info" : args.first()); + QString message; + if (args.size() > 1) { + args.takeFirst(); + message = args.join(" "); + } else { + message = "Hello World!"; + } + + direct_logs->publish(message, severity); + qDebug(" [x] Sent %s:%s", severity.toLatin1().constData(), message.toLatin1().constData()); + m_client.disconnectFromHost(); + } + +private: + QAmqpClient m_client; + +}; + +int main(int argc, char **argv) +{ + QCoreApplication app(argc, argv); + DirectLogEmitter logEmitter; + logEmitter.start(); + return app.exec(); +} + +#include "main.moc" diff --git a/qamqp/tutorials/routing/receive_logs_direct/main.cpp b/qamqp/tutorials/routing/receive_logs_direct/main.cpp new file mode 100644 index 0000000..e995f24 --- /dev/null +++ b/qamqp/tutorials/routing/receive_logs_direct/main.cpp @@ -0,0 +1,78 @@ +#include +#include +#include + +#include "qamqpclient.h" +#include "qamqpexchange.h" +#include "qamqpqueue.h" + +class DirectLogReceiver : public QObject +{ + Q_OBJECT +public: + DirectLogReceiver(QObject *parent = 0) : QObject(parent) {} + +public Q_SLOTS: + void start(const QStringList &severities) { + m_severities = severities; + connect(&m_client, SIGNAL(connected()), this, SLOT(clientConnected())); + m_client.connectToHost(); + } + +private Q_SLOTS: + void clientConnected() { + QAmqpExchange *exchange = m_client.createExchange("direct_logs"); + connect(exchange, SIGNAL(declared()), this, SLOT(exchangeDeclared())); + exchange->declare(QAmqpExchange::Direct); + } + + void exchangeDeclared() { + QAmqpQueue *temporaryQueue = m_client.createQueue(); + connect(temporaryQueue, SIGNAL(declared()), this, SLOT(queueDeclared())); + connect(temporaryQueue, SIGNAL(messageReceived()), this, SLOT(messageReceived())); + temporaryQueue->declare(QAmqpQueue::Exclusive); + } + + void queueDeclared() { + QAmqpQueue *temporaryQueue = qobject_cast(sender()); + if (!temporaryQueue) + return; + + // start consuming + temporaryQueue->consume(QAmqpQueue::coNoAck); + + foreach (QString severity, m_severities) + temporaryQueue->bind("direct_logs", severity); + qDebug() << " [*] Waiting for logs. To exit press CTRL+C"; + } + + void messageReceived() { + QAmqpQueue *temporaryQueue = qobject_cast(sender()); + if (!temporaryQueue) + return; + + QAmqpMessage message = temporaryQueue->dequeue(); + qDebug() << " [x] " << message.routingKey() << ":" << message.payload(); + } + +private: + QAmqpClient m_client; + QStringList m_severities; + +}; + +int main(int argc, char **argv) +{ + QCoreApplication app(argc, argv); + QStringList severities = app.arguments().mid(1); + if (severities.isEmpty()) { + qDebug("usage: %s [info] [warning] [error]", argv[0]); + return 1; + } + + DirectLogReceiver logReceiver; + logReceiver.start(severities); + return app.exec(); +} + +#include "main.moc" diff --git a/qamqp/tutorials/routing/receive_logs_direct/receive_logs_direct.pro b/qamqp/tutorials/routing/receive_logs_direct/receive_logs_direct.pro new file mode 100644 index 0000000..6222eec --- /dev/null +++ b/qamqp/tutorials/routing/receive_logs_direct/receive_logs_direct.pro @@ -0,0 +1,9 @@ +DEPTH = ../../.. +include($${DEPTH}/qamqp.pri) + +TEMPLATE = app +INCLUDEPATH += $${QAMQP_INCLUDEPATH} +LIBS += -L$${DEPTH}/src $${QAMQP_LIBS} +macx:CONFIG -= app_bundle + +SOURCES += main.cpp diff --git a/qamqp/tutorials/routing/routing.pro b/qamqp/tutorials/routing/routing.pro new file mode 100644 index 0000000..c15c8db --- /dev/null +++ b/qamqp/tutorials/routing/routing.pro @@ -0,0 +1,4 @@ +TEMPLATE = subdirs +SUBDIRS = \ + emit_log_direct \ + receive_logs_direct diff --git a/qamqp/tutorials/rpc/rpc.pro b/qamqp/tutorials/rpc/rpc.pro new file mode 100644 index 0000000..6216eb1 --- /dev/null +++ b/qamqp/tutorials/rpc/rpc.pro @@ -0,0 +1,4 @@ +TEMPLATE = subdirs +SUBDIRS = \ + rpc_server \ + rpc_client diff --git a/qamqp/tutorials/rpc/rpc_client/fibonaccirpcclient.cpp b/qamqp/tutorials/rpc/rpc_client/fibonaccirpcclient.cpp new file mode 100644 index 0000000..a2dd07b --- /dev/null +++ b/qamqp/tutorials/rpc/rpc_client/fibonaccirpcclient.cpp @@ -0,0 +1,72 @@ +#include +#include +#include + +#include "qamqpclient.h" +#include "qamqpexchange.h" +#include "qamqpqueue.h" + +#include "fibonaccirpcclient.h" + +FibonacciRpcClient::FibonacciRpcClient(QObject *parent) + : QObject(parent), + m_client(0), + m_responseQueue(0), + m_defaultExchange(0) +{ + m_client = new QAmqpClient(this); + connect(m_client, SIGNAL(connected()), this, SLOT(clientConnected())); +} + +FibonacciRpcClient::~FibonacciRpcClient() +{ +} + +bool FibonacciRpcClient::connectToServer() +{ + QEventLoop loop; + connect(this, SIGNAL(connected()), &loop, SLOT(quit())); + m_client->connectToHost(); + loop.exec(); + + return m_client->isConnected(); +} + +void FibonacciRpcClient::call(int number) +{ + qDebug() << " [x] Requesting fib(" << number << ")"; + m_correlationId = QUuid::createUuid().toString(); + QAmqpMessage::PropertyHash properties; + properties.insert(QAmqpMessage::ReplyTo, m_responseQueue->name()); + properties.insert(QAmqpMessage::CorrelationId, m_correlationId); + + m_defaultExchange->publish(QByteArray::number(number), "rpc_queue", properties); +} + +void FibonacciRpcClient::clientConnected() +{ + m_responseQueue = m_client->createQueue(); + connect(m_responseQueue, SIGNAL(declared()), this, SLOT(queueDeclared())); + connect(m_responseQueue, SIGNAL(messageReceived()), this, SLOT(responseReceived())); + m_responseQueue->declare(QAmqpQueue::Exclusive | QAmqpQueue::AutoDelete); + m_defaultExchange = m_client->createExchange(); +} + +void FibonacciRpcClient::queueDeclared() +{ + m_responseQueue->consume(); + Q_EMIT connected(); +} + +void FibonacciRpcClient::responseReceived() +{ + QAmqpMessage message = m_responseQueue->dequeue(); + if (message.property(QAmqpMessage::CorrelationId).toString() != m_correlationId) { + // requeue message, it wasn't meant for us + m_responseQueue->reject(message, true); + return; + } + + qDebug() << " [.] Got " << message.payload(); + qApp->quit(); +} diff --git a/qamqp/tutorials/rpc/rpc_client/fibonaccirpcclient.h b/qamqp/tutorials/rpc/rpc_client/fibonaccirpcclient.h new file mode 100644 index 0000000..0b6511a --- /dev/null +++ b/qamqp/tutorials/rpc/rpc_client/fibonaccirpcclient.h @@ -0,0 +1,36 @@ +#ifndef FIBONACCIRPCCLIENT_H +#define FIBONACCIRPCCLIENT_H + +#include + +class QAmqpQueue; +class QAmqpExchange; +class QAmqpClient; +class FibonacciRpcClient : public QObject +{ + Q_OBJECT +public: + explicit FibonacciRpcClient(QObject *parent = 0); + ~FibonacciRpcClient(); + +Q_SIGNALS: + void connected(); + +public Q_SLOTS: + bool connectToServer(); + void call(int number); + +private Q_SLOTS: + void clientConnected(); + void queueDeclared(); + void responseReceived(); + +private: + QAmqpClient *m_client; + QAmqpQueue *m_responseQueue; + QAmqpExchange *m_defaultExchange; + QString m_correlationId; + +}; + +#endif // FIBONACCIRPCCLIENT_H diff --git a/qamqp/tutorials/rpc/rpc_client/main.cpp b/qamqp/tutorials/rpc/rpc_client/main.cpp new file mode 100644 index 0000000..de28735 --- /dev/null +++ b/qamqp/tutorials/rpc/rpc_client/main.cpp @@ -0,0 +1,13 @@ +#include +#include "fibonaccirpcclient.h" + +int main(int argc, char **argv) +{ + QCoreApplication app(argc, argv); + FibonacciRpcClient client; + if (!client.connectToServer()) + return EXIT_FAILURE; + + client.call(30); + return app.exec(); +} diff --git a/qamqp/tutorials/rpc/rpc_client/rpc_client.pro b/qamqp/tutorials/rpc/rpc_client/rpc_client.pro new file mode 100644 index 0000000..fe243b8 --- /dev/null +++ b/qamqp/tutorials/rpc/rpc_client/rpc_client.pro @@ -0,0 +1,13 @@ +DEPTH = ../../.. +include($${DEPTH}/qamqp.pri) + +TEMPLATE = app +INCLUDEPATH += $${QAMQP_INCLUDEPATH} +LIBS += -L$${DEPTH}/src $${QAMQP_LIBS} +macx:CONFIG -= app_bundle + +HEADERS += \ + fibonaccirpcclient.h +SOURCES += \ + fibonaccirpcclient.cpp \ + main.cpp diff --git a/qamqp/tutorials/rpc/rpc_server/main.cpp b/qamqp/tutorials/rpc/rpc_server/main.cpp new file mode 100644 index 0000000..324e4ff --- /dev/null +++ b/qamqp/tutorials/rpc/rpc_server/main.cpp @@ -0,0 +1,10 @@ +#include +#include "server.h" + +int main(int argc, char **argv) +{ + QCoreApplication app(argc, argv); + Server server; + server.listen(); + return app.exec(); +} diff --git a/qamqp/tutorials/rpc/rpc_server/rpc_server.pro b/qamqp/tutorials/rpc/rpc_server/rpc_server.pro new file mode 100644 index 0000000..7a164c3 --- /dev/null +++ b/qamqp/tutorials/rpc/rpc_server/rpc_server.pro @@ -0,0 +1,13 @@ +DEPTH = ../../.. +include($${DEPTH}/qamqp.pri) + +TEMPLATE = app +INCLUDEPATH += $${QAMQP_INCLUDEPATH} +LIBS += -L$${DEPTH}/src $${QAMQP_LIBS} +macx:CONFIG -= app_bundle + +HEADERS += \ + server.h +SOURCES += \ + server.cpp \ + main.cpp diff --git a/qamqp/tutorials/rpc/rpc_server/server.cpp b/qamqp/tutorials/rpc/rpc_server/server.cpp new file mode 100644 index 0000000..1c45161 --- /dev/null +++ b/qamqp/tutorials/rpc/rpc_server/server.cpp @@ -0,0 +1,71 @@ +#include "qamqpclient.h" +#include "qamqpqueue.h" +#include "qamqpexchange.h" + +#include "server.h" + +Server::Server(QObject *parent) + : QObject(parent), + m_client(0), + m_rpcQueue(0), + m_defaultExchange(0) +{ + m_client = new QAmqpClient(this); + connect(m_client, SIGNAL(connected()), this, SLOT(clientConnected())); +} + +Server::~Server() +{ +} + +void Server::listen() +{ + m_client->connectToHost(); +} + +int Server::fib(int n) +{ + if (n == 0) + return 0; + + if (n == 1) + return 1; + + return fib(n - 1) + fib(n - 2); +} + +void Server::clientConnected() +{ + m_rpcQueue = m_client->createQueue("rpc_queue"); + connect(m_rpcQueue, SIGNAL(declared()), this, SLOT(queueDeclared())); + connect(m_rpcQueue, SIGNAL(qosDefined()), this, SLOT(qosDefined())); + connect(m_rpcQueue, SIGNAL(messageReceived()), this, SLOT(processRpcMessage())); + m_rpcQueue->declare(); + + m_defaultExchange = m_client->createExchange(); +} + +void Server::queueDeclared() +{ + m_rpcQueue->qos(1); +} + +void Server::qosDefined() +{ + m_rpcQueue->consume(); + qDebug() << " [x] Awaiting RPC requests"; +} + +void Server::processRpcMessage() +{ + QAmqpMessage rpcMessage = m_rpcQueue->dequeue(); + int n = rpcMessage.payload().toInt(); + + int response = fib(n); + m_rpcQueue->ack(rpcMessage); + + QString replyTo = rpcMessage.property(QAmqpMessage::ReplyTo).toString(); + QAmqpMessage::PropertyHash properties; + properties.insert(QAmqpMessage::CorrelationId, rpcMessage.property(QAmqpMessage::CorrelationId)); + m_defaultExchange->publish(QByteArray::number(response), replyTo, properties); +} diff --git a/qamqp/tutorials/rpc/rpc_server/server.h b/qamqp/tutorials/rpc/rpc_server/server.h new file mode 100644 index 0000000..d7fa221 --- /dev/null +++ b/qamqp/tutorials/rpc/rpc_server/server.h @@ -0,0 +1,33 @@ +#ifndef SERVER_H +#define SERVER_H + +#include + +class QAmqpQueue; +class QAmqpExchange; +class QAmqpClient; +class Server : public QObject +{ + Q_OBJECT +public: + explicit Server(QObject *parent = 0); + ~Server(); + +public Q_SLOTS: + void listen(); + int fib(int n); + +private Q_SLOTS: + void clientConnected(); + void queueDeclared(); + void qosDefined(); + void processRpcMessage(); + +private: + QAmqpClient *m_client; + QAmqpQueue *m_rpcQueue; + QAmqpExchange *m_defaultExchange; + +}; + +#endif // SERVER_H diff --git a/qamqp/tutorials/topics/emit_log_topic/emit_log_topic.pro b/qamqp/tutorials/topics/emit_log_topic/emit_log_topic.pro new file mode 100644 index 0000000..6222eec --- /dev/null +++ b/qamqp/tutorials/topics/emit_log_topic/emit_log_topic.pro @@ -0,0 +1,9 @@ +DEPTH = ../../.. +include($${DEPTH}/qamqp.pri) + +TEMPLATE = app +INCLUDEPATH += $${QAMQP_INCLUDEPATH} +LIBS += -L$${DEPTH}/src $${QAMQP_LIBS} +macx:CONFIG -= app_bundle + +SOURCES += main.cpp diff --git a/qamqp/tutorials/topics/emit_log_topic/main.cpp b/qamqp/tutorials/topics/emit_log_topic/main.cpp new file mode 100644 index 0000000..427fb7b --- /dev/null +++ b/qamqp/tutorials/topics/emit_log_topic/main.cpp @@ -0,0 +1,64 @@ +#include +#include +#include + +#include "qamqpclient.h" +#include "qamqpexchange.h" +#include "qamqpqueue.h" + +class TopicLogEmitter : public QObject +{ + Q_OBJECT +public: + TopicLogEmitter(QObject *parent = 0) : QObject(parent) {} + +public Q_SLOTS: + void start() { + connect(&m_client, SIGNAL(connected()), this, SLOT(clientConnected())); + connect(&m_client, SIGNAL(disconnected()), qApp, SLOT(quit())); + m_client.connectToHost(); + } + +private Q_SLOTS: + void clientConnected() { + QAmqpExchange *topic_logs = m_client.createExchange("topic_logs"); + connect(topic_logs, SIGNAL(declared()), this, SLOT(exchangeDeclared())); + topic_logs->declare(QAmqpExchange::Topic); + } + + void exchangeDeclared() { + QAmqpExchange *topic_logs = qobject_cast(sender()); + if (!topic_logs) + return; + + QStringList args = qApp->arguments(); + args.takeFirst(); // remove executable name + + QString routingKey = (args.isEmpty() ? "anonymous.info" : args.first()); + QString message; + if (args.size() > 1) { + args.takeFirst(); + message = args.join(" "); + } else { + message = "Hello World!"; + } + + topic_logs->publish(message, routingKey); + qDebug(" [x] Sent %s:%s", routingKey.toLatin1().constData(), message.toLatin1().constData()); + m_client.disconnectFromHost(); + } + +private: + QAmqpClient m_client; + +}; + +int main(int argc, char **argv) +{ + QCoreApplication app(argc, argv); + TopicLogEmitter logEmitter; + logEmitter.start(); + return app.exec(); +} + +#include "main.moc" diff --git a/qamqp/tutorials/topics/receive_logs_topic/main.cpp b/qamqp/tutorials/topics/receive_logs_topic/main.cpp new file mode 100644 index 0000000..1909d96 --- /dev/null +++ b/qamqp/tutorials/topics/receive_logs_topic/main.cpp @@ -0,0 +1,83 @@ +#include +#include +#include + +#include "qamqpclient.h" +#include "qamqpexchange.h" +#include "qamqpqueue.h" + +class TopicLogReceiver : public QObject +{ + Q_OBJECT +public: + TopicLogReceiver(QObject *parent = 0) : QObject(parent) {} + +public Q_SLOTS: + void start(const QStringList &bindingKeys) { + m_bindingKeys = bindingKeys; + connect(&m_client, SIGNAL(connected()), this, SLOT(clientConnected())); + m_client.connectToHost(); + } + +private Q_SLOTS: + void clientConnected() { + QAmqpExchange *topic_logs = m_client.createExchange("topic_logs"); + connect(topic_logs, SIGNAL(declared()), this, SLOT(exchangeDeclared())); + topic_logs->declare(QAmqpExchange::Topic); + } + + void exchangeDeclared() { + QAmqpQueue *temporaryQueue = m_client.createQueue(); + connect(temporaryQueue, SIGNAL(declared()), this, SLOT(queueDeclared())); + connect(temporaryQueue, SIGNAL(bound()), this, SLOT(queueBound())); + connect(temporaryQueue, SIGNAL(messageReceived()), this, SLOT(messageReceived())); + temporaryQueue->declare(QAmqpQueue::Exclusive); + } + + void queueDeclared() { + QAmqpQueue *temporaryQueue = qobject_cast(sender()); + if (!temporaryQueue) + return; + + foreach (QString bindingKey, m_bindingKeys) + temporaryQueue->bind("topic_logs", bindingKey); + qDebug() << " [*] Waiting for logs. To exit press CTRL+C"; + } + + void queueBound() { + QAmqpQueue *temporaryQueue = qobject_cast(sender()); + if (!temporaryQueue) + return; + temporaryQueue->consume(QAmqpQueue::coNoAck); + } + + void messageReceived() { + QAmqpQueue *temporaryQueue = qobject_cast(sender()); + if (!temporaryQueue) + return; + + QAmqpMessage message = temporaryQueue->dequeue(); + qDebug() << " [x] " << message.routingKey() << ":" << message.payload(); + } + +private: + QAmqpClient m_client; + QStringList m_bindingKeys; + +}; + +int main(int argc, char **argv) +{ + QCoreApplication app(argc, argv); + QStringList bindingKeys = app.arguments().mid(1); + if (bindingKeys.isEmpty()) { + qDebug("usage: %s [binding_key] ...", argv[0]); + return 1; + } + + TopicLogReceiver logReceiver; + logReceiver.start(bindingKeys); + return app.exec(); +} + +#include "main.moc" diff --git a/qamqp/tutorials/topics/receive_logs_topic/receive_logs_topic.pro b/qamqp/tutorials/topics/receive_logs_topic/receive_logs_topic.pro new file mode 100644 index 0000000..6222eec --- /dev/null +++ b/qamqp/tutorials/topics/receive_logs_topic/receive_logs_topic.pro @@ -0,0 +1,9 @@ +DEPTH = ../../.. +include($${DEPTH}/qamqp.pri) + +TEMPLATE = app +INCLUDEPATH += $${QAMQP_INCLUDEPATH} +LIBS += -L$${DEPTH}/src $${QAMQP_LIBS} +macx:CONFIG -= app_bundle + +SOURCES += main.cpp diff --git a/qamqp/tutorials/topics/topics.pro b/qamqp/tutorials/topics/topics.pro new file mode 100644 index 0000000..bd429e4 --- /dev/null +++ b/qamqp/tutorials/topics/topics.pro @@ -0,0 +1,4 @@ +TEMPLATE = subdirs +SUBDIRS = \ + emit_log_topic \ + receive_logs_topic diff --git a/qamqp/tutorials/tutorials.pro b/qamqp/tutorials/tutorials.pro new file mode 100644 index 0000000..55f979e --- /dev/null +++ b/qamqp/tutorials/tutorials.pro @@ -0,0 +1,8 @@ +TEMPLATE = subdirs +SUBDIRS = \ + helloworld \ + workqueues \ + pubsub \ + routing \ + topics \ + rpc diff --git a/qamqp/tutorials/workqueues/new_task/main.cpp b/qamqp/tutorials/workqueues/new_task/main.cpp new file mode 100644 index 0000000..ccff2a4 --- /dev/null +++ b/qamqp/tutorials/workqueues/new_task/main.cpp @@ -0,0 +1,62 @@ +#include +#include +#include + +#include "qamqpclient.h" +#include "qamqpexchange.h" +#include "qamqpqueue.h" + +class TaskCreator : public QObject +{ + Q_OBJECT +public: + TaskCreator(QObject *parent = 0) : QObject(parent) {} + +public Q_SLOTS: + void start() { + connect(&m_client, SIGNAL(connected()), this, SLOT(clientConnected())); + connect(&m_client, SIGNAL(disconnected()), qApp, SLOT(quit())); + m_client.connectToHost(); + } + +private Q_SLOTS: + void clientConnected() { + QAmqpQueue *queue = m_client.createQueue("task_queue"); + connect(queue, SIGNAL(declared()), this, SLOT(queueDeclared())); + queue->declare(); + } + + void queueDeclared() { + QAmqpQueue *queue = qobject_cast(sender()); + if (!queue) + return; + + QAmqpExchange *defaultExchange = m_client.createExchange(); + QAmqpMessage::PropertyHash properties; + properties[QAmqpMessage::DeliveryMode] = "2"; // make message persistent + + QString message; + if (qApp->arguments().size() < 2) + message = "Hello World!"; + else + message = qApp->arguments().at(1); + + defaultExchange->publish(message, "task_queue", properties); + qDebug(" [x] Sent '%s'", message.toLatin1().constData()); + m_client.disconnectFromHost(); + } + +private: + QAmqpClient m_client; + +}; + +int main(int argc, char **argv) +{ + QCoreApplication app(argc, argv); + TaskCreator taskCreator; + taskCreator.start(); + return app.exec(); +} + +#include "main.moc" diff --git a/qamqp/tutorials/workqueues/new_task/new_task.pro b/qamqp/tutorials/workqueues/new_task/new_task.pro new file mode 100644 index 0000000..6222eec --- /dev/null +++ b/qamqp/tutorials/workqueues/new_task/new_task.pro @@ -0,0 +1,9 @@ +DEPTH = ../../.. +include($${DEPTH}/qamqp.pri) + +TEMPLATE = app +INCLUDEPATH += $${QAMQP_INCLUDEPATH} +LIBS += -L$${DEPTH}/src $${QAMQP_LIBS} +macx:CONFIG -= app_bundle + +SOURCES += main.cpp diff --git a/qamqp/tutorials/workqueues/worker/main.cpp b/qamqp/tutorials/workqueues/worker/main.cpp new file mode 100644 index 0000000..c4ebfa4 --- /dev/null +++ b/qamqp/tutorials/workqueues/worker/main.cpp @@ -0,0 +1,62 @@ +#include +#include +#include + +#include "qamqpclient.h" +#include "qamqpqueue.h" + +class Worker : public QObject +{ + Q_OBJECT +public: + Worker(QObject *parent = 0) : QObject(parent), m_queue(0) {} + +public Q_SLOTS: + void start() { + connect(&m_client, SIGNAL(connected()), this, SLOT(clientConnected())); + m_client.connectToHost(); + } + +private Q_SLOTS: + void clientConnected() { + m_queue = m_client.createQueue("task_queue"); + connect(m_queue, SIGNAL(declared()), this, SLOT(queueDeclared())); + m_queue->declare(); + qDebug() << " [*] Waiting for messages. To exit press CTRL+C"; + } + + void queueDeclared() { + // m_queue->setPrefetchCount(1); + m_queue->consume(); + connect(m_queue, SIGNAL(messageReceived()), this, SLOT(messageReceived())); + } + + void messageReceived() { + m_currentMessage = m_queue->dequeue(); + qDebug() << " [x] Received " << m_currentMessage.payload(); + + int delay = m_currentMessage.payload().count(".") * 1000; + QTimer::singleShot(delay, this, SLOT(ackMessage())); + } + + void ackMessage() { + qDebug() << " [x] Done"; + m_queue->ack(m_currentMessage); + } + +private: + QAmqpClient m_client; + QAmqpQueue *m_queue; + QAmqpMessage m_currentMessage; + +}; + +int main(int argc, char **argv) +{ + QCoreApplication app(argc, argv); + Worker worker; + worker.start(); + return app.exec(); +} + +#include "main.moc" diff --git a/qamqp/tutorials/workqueues/worker/worker.pro b/qamqp/tutorials/workqueues/worker/worker.pro new file mode 100644 index 0000000..6222eec --- /dev/null +++ b/qamqp/tutorials/workqueues/worker/worker.pro @@ -0,0 +1,9 @@ +DEPTH = ../../.. +include($${DEPTH}/qamqp.pri) + +TEMPLATE = app +INCLUDEPATH += $${QAMQP_INCLUDEPATH} +LIBS += -L$${DEPTH}/src $${QAMQP_LIBS} +macx:CONFIG -= app_bundle + +SOURCES += main.cpp diff --git a/qamqp/tutorials/workqueues/workqueues.pro b/qamqp/tutorials/workqueues/workqueues.pro new file mode 100644 index 0000000..3f51159 --- /dev/null +++ b/qamqp/tutorials/workqueues/workqueues.pro @@ -0,0 +1,4 @@ +TEMPLATE = subdirs +SUBDIRS = \ + new_task \ + worker diff --git a/send/CMakeLists.txt b/send/CMakeLists.txt new file mode 100644 index 0000000..bda64fe --- /dev/null +++ b/send/CMakeLists.txt @@ -0,0 +1,37 @@ +cmake_minimum_required(VERSION 3.16) +project(send VERSION 0.0.1 LANGUAGES CXX) + +set(CMAKE_CXX_STANDARD 14) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +find_package(Qt6 COMPONENTS Core Network REQUIRED) + +set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}) +set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}) +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}) + +set(CMAKE_AUTOUIC ON) +set(CMAKE_AUTOMOC ON) +set(CMAKE_AUTORCC ON) + +INCLUDE_DIRECTORIES(${PROJECT_SOURCE_DIR}) + +add_executable(send + main.cpp +) + +target_link_libraries(send Qt6::Core Qt6::Network qamqp) + +set_target_properties(send PROPERTIES + AUTOMOC ON + AUTORCC ON + AUTOUIC ON + CXX_STANDARD 14 + CXX_STANDARD_REQUIRED ON + CXX_EXTENSIONS OFF + VERSION 0.0.1 + EXPORT_NAME "Event Queue Sender" + ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin" + LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib" + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin" +) \ No newline at end of file diff --git a/send/main.cpp b/send/main.cpp new file mode 100644 index 0000000..c499594 --- /dev/null +++ b/send/main.cpp @@ -0,0 +1,60 @@ +#include +#include +#include + +#include "qamqpclient.h" +#include "qamqpexchange.h" +#include "qamqpqueue.h" + +class Sender : public QObject +{ + Q_OBJECT +public: + Sender(QObject *parent = 0) : QObject(parent) {} + +public Q_SLOTS: + void start() { + connect(&m_client, SIGNAL(connected()), this, SLOT(clientConnected())); + connect(&m_client, SIGNAL(disconnected()), qApp, SLOT(quit())); + m_client.connectToHost(); + } + +private Q_SLOTS: + void clientConnected() { + QAmqpQueue *queue = m_client.createQueue("hello"); + connect(queue, SIGNAL(declared()), this, SLOT(queueDeclared())); + queue->declare(); + } + + void queueDeclared() { + QAmqpQueue *queue = qobject_cast(sender()); + if (!queue) + return; + QAmqpExchange *defaultExchange = m_client.createExchange(); + defaultExchange->publish("Hello World! A", "hello"); + qDebug() << " [x] Sent 'Hello World! A'"; + defaultExchange->publish("Hello World! B", "hello"); + qDebug() << " [x] Sent 'Hello World! B'"; + defaultExchange->publish("Hello World! C", "hello"); + qDebug() << " [x] Sent 'Hello World! C'"; + defaultExchange->publish("Hello World! D", "hello"); + qDebug() << " [x] Sent 'Hello World! D'"; + defaultExchange->publish("Hello World! E", "hello"); + qDebug() << " [x] Sent 'Hello World! E'"; + m_client.disconnectFromHost(); + } + +private: + QAmqpClient m_client; + +}; + +int main(int argc, char **argv) +{ + QCoreApplication app(argc, argv); + Sender sender; + sender.start(); + return app.exec(); +} + +#include "main.moc"