diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile new file mode 100644 index 00000000..637e3be7 --- /dev/null +++ b/.devcontainer/Dockerfile @@ -0,0 +1,7 @@ +FROM rabbitmq:3.7-management + +ENV NVM_DIR="/usr/local/share/nvm" +ENV NVM_SYMLINK_CURRENT=true \ + PATH=${NVM_DIR}/current/bin:${PATH} +COPY library-scripts/node-debian.sh /tmp/library-scripts/ +RUN apt-get update && bash /tmp/library-scripts/node-debian.sh "${NVM_DIR}" \ No newline at end of file diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 00000000..6accc0ea --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,16 @@ +{ + "name": "RabbitMQ Tutorials", + "dockerFile": "Dockerfile", + "forwardPorts": [ 4369, 5671, 5672, 15691, 15692, 25672 ], + + // Set *default* container specific settings.json values on container create. + "settings": { + "terminal.integrated.shell.linux": "/bin/bash" + }, + + // Add the IDs of extensions you want installed when the container is created. + "extensions": [], + + // Uncomment to connect as a non-root user. See https://aka.ms/vscode-remote/containers/non-root. + // "remoteUser": "vscode" +} \ No newline at end of file diff --git a/.devcontainer/library-scripts/node-debian.sh b/.devcontainer/library-scripts/node-debian.sh new file mode 100644 index 00000000..9e7c2f0d --- /dev/null +++ b/.devcontainer/library-scripts/node-debian.sh @@ -0,0 +1,109 @@ +#!/bin/bash +#------------------------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See https://go.microsoft.com/fwlink/?linkid=2090316 for license information. +#------------------------------------------------------------------------------------------------------------- + +# File taken from https://github.com/microsoft/vscode-dev-containers/blob/master/containers/azure-static-web-apps/.devcontainer/library-scripts/node-debian.sh + +# Syntax: ./node-debian.sh [directory to install nvm] [node version to install (use "none" to skip)] [non-root user] + +export NVM_DIR=${1:-"/usr/local/share/nvm"} +export NODE_VERSION=${2:-"lts/*"} +USERNAME=${3:-"vscode"} +UPDATE_RC=${4:-"true"} + +mkdir -p "/etc/zsh/" + +set -e + +if [ "$(id -u)" -ne 0 ]; then + echo -e 'Script must be run a root. Use sudo, su, or add "USER root" to your Dockerfile before running this script.' + exit 1 +fi + +# Treat a user name of "none" or non-existant user as root +if [ "${USERNAME}" = "none" ] || ! id -u ${USERNAME} > /dev/null 2>&1; then + USERNAME=root +fi + +if [ "${NODE_VERSION}" = "none" ]; then + export NODE_VERSION= +fi + +# Ensure apt is in non-interactive to avoid prompts +export DEBIAN_FRONTEND=noninteractive + +# Install curl, apt-transport-https, tar, or gpg if missing +if ! dpkg -s apt-transport-https curl ca-certificates tar > /dev/null 2>&1 || ! type gpg > /dev/null 2>&1; then + if [ ! -d "/var/lib/apt/lists" ] || [ "$(ls /var/lib/apt/lists/ | wc -l)" = "0" ]; then + apt-get update + fi + apt-get -y install --no-install-recommends apt-transport-https curl ca-certificates tar gnupg2 +fi + +# Install yarn +if type yarn > /dev/null 2>&1; then + echo "Yarn already installed." +else + curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | (OUT=$(apt-key add - 2>&1) || echo $OUT) + echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list + apt-get update + apt-get -y install --no-install-recommends yarn +fi + +# Install the specified node version if NVM directory already exists, then exit +if [ -d "${NVM_DIR}" ]; then + echo "NVM already installed." + if [ "${NODE_VERSION}" != "" ]; then + su ${USERNAME} -c "source $NVM_DIR/nvm.sh && nvm install ${NODE_VERSION} && nvm clear-cache" + fi + exit 0 +fi + + +# Run NVM installer as non-root if needed +mkdir -p ${NVM_DIR} +chown ${USERNAME} ${NVM_DIR} +su ${USERNAME} -c "$(cat << EOF + set -e + + # Do not update profile - we'll do this manually + export PROFILE=/dev/null + + curl -so- https://raw.githubusercontent.com/nvm-sh/nvm/v0.35.3/install.sh | bash + source ${NVM_DIR}/nvm.sh + if [ "${NODE_VERSION}" != "" ]; then + nvm alias default ${NODE_VERSION} + fi + nvm clear-cache +EOF +)" 2>&1 + +if [ "${UPDATE_RC}" = "true" ]; then + echo "Updating /etc/bash.bashrc and /etc/zsh/zshrc with NVM scripts..." +(cat < /dev/null 2>&1; then + echo "Fixing permissions of \"\$NVM_DIR\"..." + sudoIf chown -R ${USERNAME}:root \$NVM_DIR + else + echo "Warning: NVM directory is not owned by ${USERNAME} and sudo is not installed. Unable to correct permissions." + fi +fi +[ -s "\$NVM_DIR/nvm.sh" ] && . "\$NVM_DIR/nvm.sh" +[ -s "\$NVM_DIR/bash_completion" ] && . "\$NVM_DIR/bash_completion" +EOF +) | tee -a /etc/bash.bashrc >> /etc/zsh/zshrc +fi + +echo "Done!" \ No newline at end of file diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 00000000..6a4aa284 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,70 @@ +# Please see the documentation for all configuration options: +# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates +version: 2 +updates: + - package-ecosystem: "maven" + directory: "/java-mvn" + schedule: + interval: "daily" + open-pull-requests-limit: 20 + target-branch: "main" + ignore: + - dependency-name: "org.junit.jupiter:*" + versions: [ "[6.0,)" ] + - dependency-name: "org.junit.platform:*" + versions: [ "[6.0,)" ] + - package-ecosystem: "maven" + directory: "/java-amqp" + schedule: + interval: "daily" + open-pull-requests-limit: 20 + target-branch: "main" + ignore: + - dependency-name: "org.junit.jupiter:*" + versions: [ "[6.0,)" ] + - dependency-name: "org.junit.platform:*" + versions: [ "[6.0,)" ] + - package-ecosystem: "maven" + directory: "/java-stream-mvn" + schedule: + interval: "daily" + open-pull-requests-limit: 20 + target-branch: "main" + ignore: + - dependency-name: "org.junit.jupiter:*" + versions: [ "[6.0,)" ] + - dependency-name: "org.junit.platform:*" + versions: [ "[6.0,)" ] + - package-ecosystem: "maven" + directory: "/spring-amqp" + schedule: + interval: "daily" + open-pull-requests-limit: 20 + target-branch: "main" + ignore: + - dependency-name: "org.junit.jupiter:*" + versions: [ "[6.0,)" ] + - dependency-name: "org.junit.platform:*" + versions: [ "[6.0,)" ] + - package-ecosystem: "maven" + directory: "/spring-amqp-stream" + schedule: + interval: "daily" + open-pull-requests-limit: 20 + target-branch: "main" + ignore: + - dependency-name: "org.junit.jupiter:*" + versions: [ "[6.0,)" ] + - dependency-name: "org.junit.platform:*" + versions: [ "[6.0,)" ] + - package-ecosystem: "gradle" + directory: "/java-gradle" + schedule: + interval: "daily" + open-pull-requests-limit: 20 + target-branch: "main" + ignore: + - dependency-name: "org.junit.jupiter:*" + versions: [ "[6.0,)" ] + - dependency-name: "org.junit.platform:*" + versions: [ "[6.0,)" ] diff --git a/.gitignore b/.gitignore index a345f396..35e5b18c 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,7 @@ erlang/rabbit_common* erlang/recon* python/venv dotnet/*.exe +dotnet/*/bin/* dotnet/lib java/*.class java/*.jar @@ -17,14 +18,30 @@ ruby*/gems/ venv/* ruby*/rubygems* +elixir-stream/deps +elixir-stream/_build +elixir-stream/.elixir_ls + java*/.idea/workspace.xml java*/.idea/encodings.xml *~ .vscode/ obj/ -bin/ +bin/* +!src/bin/* target/ .DS_Store *.iml .idea/ +.vs/ + +*.log +.packages +.python-version + +.classpath +.project +.settings + +/.cursor/plans diff --git a/.travis.yml b/.travis.yml index 8e9edb81..152d7d8b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,3 +9,7 @@ branches: - master env: - RUBY=ruby GEM=gem SLOWNESS=6 + +cache: + directories: + - $HOME/.m2 diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 00000000..334cbe38 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,58 @@ +# Instructions for AI Agents + +## Overview + +This repository contains runnable code for the [RabbitMQ tutorials](https://www.rabbitmq.com/tutorials) +in many languages and client libraries. The accompanying prose lives on the website: this repo holds only executable examples. + +All tutorials require a RabbitMQ node running on `localhost` with default settings and use +the default `localhost`-constrained seed user `guest` with well known credentials. + +Tutorial code here and the prose at [`rabbitmq/rabbitmq-website`](https://github.com/rabbitmq/rabbitmq-website) +should be kept in sync: not necessarily in lockstep, but updated consistently within a few days of each other. + + +## Repository Structure + +Each top-level directory (called a tutorial port) is a self-contained tutorial set for one language (client library), +with its own `README.md` covering setup and run instructions. Some directories use symlinks +to reuse code. + +Directories with a `-stream` suffix cover the [RabbitMQ Stream protocol](https://www.rabbitmq.com/docs/streams); the rest cover AMQP 0-9-1. + +Individual directories (ports) can have their local `AGENTS.md` with language-specific instructions, e.g. +around how to avoid standard input and output pitfalls when asked to run permutations of these tutorials +(e.g. T1 publishers in Java and a T1 consumer that uses Swift 6 and Bunny.swift). + +AMQP 0-9-1 tutorials follow a standard numbering with consistent base filenames across languages: + + 1. Tutorial 1 (a.k.a. T1, "Hello World"): `send` / `receive` + 2. Tutorial 2 (a.k.a. T2, "Work Queues"): `new_task` / `worker` + 3. Tutorial 3 (T3, "Publish/Subscribe"): `emit_log` / `receive_logs` + 4. Tutorial 4 (T4, "Routing"): `emit_log_direct` / `receive_logs_direct` + 5. Tutorial 5 (T5, "Topics"): `emit_log_topic` / `receive_logs_topic` + 6. Tutorial 6 (T6, "RPC"): `rpc_client` / `rpc_server` + 7. Tutorial 7 (T7, "Publisher Confirms"): `publisher_confirms` + +Stream tutorials cover `send` / `receive` and `offset_tracking_send` / `offset_tracking_receive`. + + +## Adding or Modifying Tutorials + + * Read the language directory's README first + * Follow the existing code style and naming conventions of the directory you are editing + * Tutorials are learning material, not production code: keep them simple, readable, and self-contained + * Keep dependencies minimal; use the idiomatic RabbitMQ client library for the language + + +## Style + + * Only add comments where the logic isn't self-evident; keep them concise + * Use proper English grammar and punctuation + * Never add full stops to Markdown list items + + +## Git + + * Never add yourself to the list of commit co-authors + * Never mention yourself in commit messages in any way diff --git a/LICENSE.txt b/LICENSE.txt index 06697641..161e6985 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -1,7 +1,7 @@ Apache License Version 2.0, January 2004 - http://www.apache.org/licenses/ + https://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION @@ -187,13 +187,13 @@ same "printed page" as the copyright notice for easier identification within third-party archives. - Copyright 2007-2015 Pivotal Software, Inc. + Copyright 2007-2026 Broadcom. The term “Broadcom” refers to Broadcom Inc. and/or its subsidiaries. All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 + https://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, diff --git a/Makefile b/Makefile deleted file mode 100644 index e51e6781..00000000 --- a/Makefile +++ /dev/null @@ -1,155 +0,0 @@ -# Ignore this file, go straight to the directory with your language of -# choice and read the readme there. -# -# This makefile is for testing only. It's intended to install -# dependencies for all source code languages, which is an overkill for -# the great majority of users. -# - -all: - @echo "Review README in the directory with your langage of choice." - -### Test all combinations of languages -# -# -# Running everything requires quite a lot of dependencies you need at -# least (as tested on debian 5.0): -# -# apt-get install python-virtualenv git-core php5-cli \ -# ruby1.9 ruby1.9-dev rdoc1.9 unzip mono-gmcs sun-java5-jdk \ -# cpan perl -# -# -# You also need recent erlang, you may install it from sources following -# this commands: -# cd /usr/src -# apt-get -y install libncurses-dev libssl-dev -# [ -e otp_src_R14B03.tar.gz ] || wget http://www.erlang.org/download/otp_src_R14B03.tar.gz -# [ -e otp_src_R14B03 ] || tar xzf otp_src_R14B03.tar.gz -# cd otp_src_R14B03/ -# ./configure -# make -# make install -# -setup: dotnet/.ok erlang/.ok java/.ok python/.ok php/.ok ruby/.ok clojure/.ok - -setup-travisci: dotnet/.ok erlang/.ok java/.ok python/.ok ruby/.ok php/.ok clojure/.ok - -test: setup - RUBY=$(RUBY) python test.py - -test-travisci: setup-travisci - SLOWNESS=4 RUBY=ruby python travisci.py - -RABBITVER:=$(shell curl -s "http://www.rabbitmq.com/releases/rabbitmq-server/" | grep -oE '([0-9\.]{5,})' | tail -n 1) -R=http://www.rabbitmq.com/releases - -# Default value assumes CI environment -RUBY?=ruby1.9.1 - -DVER=$(RABBITVER) -dotnet/.ok: - (cd dotnet && \ - mkdir -p lib && \ - cd lib && \ - wget -qc $(R)/rabbitmq-dotnet-client/v$(DVER)/rabbitmq-dotnet-client-$(DVER)-dotnet-4.0.zip && \ - unzip -q rabbitmq-dotnet-client-$(DVER)-dotnet-4.0.zip && \ - cd .. && \ - for f in *.cs; do \ - gmcs -r:lib/bin/RabbitMQ.Client.dll $$f; \ - done && \ - touch .ok) -clean:: - (cd dotnet && \ - rm -rf .ok *.zip lib *.exe) - -EVER=$(RABBITVER) -erlang/.ok: - (cd erlang && \ - wget -qc $(R)/rabbitmq-erlang-client/v$(EVER)/rabbit_common-$(EVER).ez \ - $(R)/rabbitmq-erlang-client/v$(EVER)/amqp_client-$(EVER).ez && \ - unzip -q rabbit_common-$(EVER).ez && \ - ln -s rabbit_common-$(EVER) rabbit_common && \ - unzip -q amqp_client-$(EVER).ez && \ - ln -s amqp_client-$(EVER) amqp_client && \ - touch .ok) -clean:: - (cd erlang && \ - rm -rf .ok *.ez amqp_client* rabbit_common*) - -JVER=$(RABBITVER) -java/.ok: - (cd java && \ - wget -qc $(R)/rabbitmq-java-client/v$(JVER)/rabbitmq-java-client-bin-$(JVER).zip && \ - unzip -q rabbitmq-java-client-bin-$(JVER).zip && \ - cp rabbitmq-java-client-bin-$(JVER)/*.jar . && \ - javac -cp rabbitmq-client.jar *.java && \ - touch .ok) -clean:: - (cd java && \ - rm -rf .ok *.jar *.class *.zip rabbitmq-java-client-bin*) - -PVER=0.10.0 -python/.ok: - (cd python && \ - virtualenv venv && \ - ./venv/bin/easy_install pip && \ - ./venv/bin/pip install pika==$(PVER) && \ - touch .ok) -clean:: - (cd python && \ - rm -rf .ok venv distribute*.tar.gz) - -php/.ok: - (cd php && \ - mkdir -p ./bin && \ - curl -sS https://getcomposer.org/installer | php -- --install-dir=bin && \ - php ./bin/composer.phar install --prefer-source && \ - touch .ok) -clean:: - (cd php && \ - rm -rf .ok lib) - -GEM?=gem1.9.1 -TOPDIR:=$(PWD) -ruby/.ok: - (cd ruby && \ - GEM_HOME=gems/gems RUBYLIB=gems/lib $(GEM) install bunny --version "~> 2.7.0" --no-ri --no-rdoc && \ - touch .ok) -clean:: - (cd ruby && \ - rm -rf .ok gems) - -clean:: - (cd ruby-amqp && \ - rm -rf .ok gems) - -perl/.ok: - (cd perl && \ - PERL_MM_USE_DEFAULT=1 cpan -i -f Net::RabbitFoot && \ - PERL_MM_USE_DEFAULT=1 cpan -i -f UUID::Tiny && \ - touch .ok) - -clean:: - (cd python-puka && \ - rm -rf .ok venv distribute*.tar.gz) - -clojure/.ok: - (cd clojure && \ - mkdir -p bin && cd bin && \ - wget -qc https://raw.github.com/technomancy/leiningen/stable/bin/lein && \ - chmod +x ./lein && cd .. && \ - ./bin/lein deps && \ - touch .ok) -clean:: - (cd clojure && \ - rm -rf .ok bin/*) - -go/.ok: - (cd go && \ - go get https://github.com/streadway/amqp && \ - touch .ok) - -clean:: - (cd go && \ - rm -rf .ok) diff --git a/README.md b/README.md index 25b66931..1be1520e 100644 --- a/README.md +++ b/README.md @@ -1,29 +1,44 @@ # RabbitMQ Tutorials -This project contains code for [RabbitMQ tutorials](http://www.rabbitmq.com/getstarted.html) with +This project contains code for [RabbitMQ tutorials](https://www.rabbitmq.com/getstarted.html) with their ports to various languages. -This repository only contains runnable code. Please consult [tutorials on the site](http://www.rabbitmq.com/getstarted.html) -to learn more about [the concepts](http://www.rabbitmq.com/getstarted.html), requirements, supported client library version and so on. +This repository only contains runnable code. Please consult [tutorials on the site](https://www.rabbitmq.com/getstarted.html) +to learn more about [the concepts](https://www.rabbitmq.com/getstarted.html), requirements, supported client library version and so on. + +And please check out the rest of the [RabbitMQ documentation](https://www.rabbitmq.com/documentation.html)! + +## Prerequisites All tutorials **require a RabbitMQ node running on localhost** with stock (default) settings. +Please refer to RabbitMQ documentation to learn +more about various [installation options](https://www.rabbitmq.com/download.html): + + * A [Windows installer](https://www.rabbitmq.com/install-windows.html) + * A [Docker image](https://hub.docker.com/_/rabbitmq/) + * [Homebrew](https://www.rabbitmq.com/install-homebrew.html) on MacOS + * Packages for [Ubuntu and Debian](https://www.rabbitmq.com/install-debian.html) as well as [RPM-based distributions](https://www.rabbitmq.com/install-rpm.html) + * A generic [binary build](https://www.rabbitmq.com/install-generic-unix.html) for Linux, *BSD and other UNIX-like systems + ## Languages The following ports are available: * [C#](./dotnet) - * [C# (with Visual Studio)](./dotnet-visual-studio) * [Clojure](./clojure) * [Common Lisp](./common-lisp) + * [Dart](./dart) * [Elixir](./elixir) + * [Elixir (Streams)](./elixir-stream) * [Erlang](./erlang) * [Go](./go) * [Haskell](./haskell) - * [JavaScript (with Node and amqp-node)](./javascript-nodejs) (using callbacks) - * [JavaScript (with Node and amqp-node)](https://github.com/squaremo/amqp.node/tree/master/examples) (using promises/futures) - * [Java](./java) - * [Java (with IntelliJ IDEA)](./java-idea) + * [JavaScript (with Node and amqplib)](./javascript-nodejs) (using callbacks) + * [JavaScript (with Node and amqplib)](https://github.com/amqp-node/amqplib) (using promises/futures) + * [Java](./java) (with manual dependency management) + * [Java with Gradle](./java-gradle) + * [Java with Maven](./java-mvn) * [Kotlin](./kotlin) * [PHP (with php-amqplib)](./php) * [PHP (with php-amqp)](./php-amqp) @@ -31,10 +46,13 @@ The following ports are available: * [Perl](./perl) * [Python (with Pika)](./python) * [Ruby (with Bunny)](./ruby) + * [Rust with amqprs](./rust-amqprs) + * [Rust with Lapin](./rust-lapin) * [Scala](./scala) - * [Swift](./swift) + * [Swift](./swift) (Swift 6 or later) * [Spring AMQP](./spring-amqp) + * [SoapUI](./soapui) ## License -Released under the [Apache License 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt). +Released under the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0.txt). diff --git a/clojure/README.md b/clojure/README.md index 7287a2a5..3d5fe516 100644 --- a/clojure/README.md +++ b/clojure/README.md @@ -1,11 +1,11 @@ # Clojure code for RabbitMQ tutorials Here you can find Clojure code examples from -[RabbitMQ tutorials](http://www.rabbitmq.com/getstarted.html). +[RabbitMQ tutorials](https://www.rabbitmq.com/getstarted.html). ## Requirements -To run this code you need [Leiningen](http://leiningen.org). +To run this code you need [Leiningen](https://leiningen.org). These tutorials will work on JDK 6 through 8 (Oracle or OpenJDK). @@ -13,32 +13,32 @@ These tutorials will work on JDK 6 through 8 (Oracle or OpenJDK). Code examples are executed via `lein run`: -[Tutorial one: "Hello World!"](http://www.rabbitmq.com/tutorial-one-java.html): +Tutorial one: "Hello World!" lein run -m rabbitmq.tutorials.send lein run -m rabbitmq.tutorials.receive -[Tutorial two: Work Queues](http://www.rabbitmq.com/tutorial-two-java.html): +Tutorial two: Work Queues lein run -m rabbitmq.tutorials.new-task lein run -m rabbitmq.tutorials.worker -[Tutorial three: Publish/Subscribe](http://www.rabbitmq.com/tutorial-three-java.html) +Tutorial three: Publish/Subscribe lein run -m rabbitmq.tutorials.receive-logs lein run -m rabbitmq.tutorials.emit-log -[Tutorial four: Routing](http://www.rabbitmq.com/tutorial-four-java.html) +Tutorial four: Routing lein run -m rabbitmq.tutorials.receive-logs-direct lein run -m rabbitmq.tutorials.emit-log-direct info -[Tutorial five: Topics](http://www.rabbitmq.com/tutorial-five-java.html) +Tutorial five: Topics lein run -m rabbitmq.tutorials.receive-logs-topic lein run -m rabbitmq.tutorials.emit-log-topic info -[Tutorial six: RPC](http://www.rabbitmq.com/tutorial-six-java.html) +Tutorial six: RPC lein run -m rabbitmq.tutorials.rpc-server lein run -m rabbitmq.tutorials.rpc-client diff --git a/clojure/project.clj b/clojure/project.clj index 46eb44e7..f93131d0 100644 --- a/clojure/project.clj +++ b/clojure/project.clj @@ -1,7 +1,7 @@ (defproject com.rabbitmq/tutorials "1.0.0-SNAPSHOT" :description "RabbitMQ tutorials using Langohr" - :url "http://github.com/rabbitmq/rabbitmq-tutorials" + :url "https://github.com/rabbitmq/rabbitmq-tutorials" :license {:name "Eclipse Public License" - :url "http://www.eclipse.org/legal/epl-v10.html"} + :url "https://www.eclipse.org/legal/epl-v10.html"} :dependencies [[org.clojure/clojure "1.6.0"] [com.novemberain/langohr "3.3.0"]]) diff --git a/common-lisp/README.md b/common-lisp/README.md index 8ed6dac3..1b52d6e7 100644 --- a/common-lisp/README.md +++ b/common-lisp/README.md @@ -1,11 +1,11 @@ # Common Lisp code for RabbitMQ tutorials Here you can find Common Lisp code examples from -[RabbitMQ tutorials](http://cl-rabbit.io/cl-bunny/tutorials/). +[RabbitMQ tutorials](https://cl-rabbit.io/cl-bunny/tutorials/). ## Requirements -To run this code you need [Cl-Bunny](http://cl-rabbit.io/cl-bunny). +To run this code you need [Cl-Bunny](https://cl-rabbit.io/cl-bunny). You can install it via Quicklisp: @@ -17,34 +17,34 @@ All our examples are in fact executable sbcl scripts. You can run them from comm ## Code -[Tutorial one: "Hello World!"](http://cl-rabbit.io/cl-bunny/tutorials/tutorial-one-cl.html): +Tutorial one: "Hello World!"]: ./send.lisp ./receive.lisp -[Tutorial two: Work Queues](http://cl-rabbit.io/cl-bunny/tutorials/tutorial-two-cl.html): +Tutorial two: Work Queues: ./new-task.lisp ./worker.lisp -[Tutorial three: Publish/Subscribe](http://cl-rabbit.io/cl-bunny/tutorials/tutorial-three-cl.html) +Tutorial three: Publish/Subscribe ./receive-logs.lisp ./emit-log.lisp -[Tutorial four: Routing](http://cl-rabbit.io/cl-bunny/tutorials/tutorial-four-cl.html) +Tutorial four: Routing ./receive-logs-direct.lisp ./emit-log-direct.lisp -[Tutorial five: Topics](http://cl-rabbit.io/cl-bunny/tutorials/tutorial-five-cl.html) +Tutorial five: Topics ./receive-logs-topic.lisp ./emit-log-topic.lisp -[Tutorial six: RPC](http://cl-rabbit.io/cl-bunny/tutorials/tutorial-six-cl.html) +Tutorial six: RPC ./rpc-server.lisp ./rpc-client.lisp -To learn more, visit [Cl-Bunny documentation](http://cl-rabbit.io/cl-bunny) site. +To learn more, visit [Cl-Bunny documentation](https://cl-rabbit.io/cl-bunny) site. diff --git a/common-lisp/rpc-server.lisp b/common-lisp/rpc-server.lisp index 15a19b94..73111a8f 100755 --- a/common-lisp/rpc-server.lisp +++ b/common-lisp/rpc-server.lisp @@ -12,7 +12,7 @@ sbcl --noinform --noprint < +#include +#include +#include + +#include +#include + +int main(int argc, char const *const *argv) +{ + amqp_connection_state_t conn = amqp_new_connection(); + amqp_socket_t *socket = amqp_tcp_socket_new(conn); + amqp_socket_open(socket, "localhost", AMQP_PROTOCOL_PORT); + amqp_login(conn, "/", 0, AMQP_DEFAULT_FRAME_SIZE, 0, AMQP_SASL_METHOD_PLAIN, "guest", "guest"); + const amqp_channel_t KChannel = 1; + amqp_channel_open(conn, KChannel); + + amqp_bytes_t exchangeName(amqp_cstring_bytes("logs")); + amqp_exchange_declare(conn, KChannel, exchangeName, amqp_cstring_bytes("fanout"), + false, false, false, false, amqp_empty_table); + + std::string message("info: Hello World!"); + if (argc > 1) + { + std::stringstream s; + copy(&argv[1], &argv[argc], std::ostream_iterator(s, " ")); + message = s.str(); + } + + amqp_basic_publish(conn, KChannel, exchangeName, amqp_empty_bytes, false, false, nullptr, amqp_cstring_bytes(message.c_str())); + std::cout << " [x] Sent " << message << std::endl; + + amqp_channel_close(conn, KChannel, AMQP_REPLY_SUCCESS); + amqp_connection_close(conn, AMQP_REPLY_SUCCESS); + amqp_destroy_connection(conn); + return 0; +} diff --git a/cpp/emit_log_direct.cpp b/cpp/emit_log_direct.cpp new file mode 100644 index 00000000..856448c8 --- /dev/null +++ b/cpp/emit_log_direct.cpp @@ -0,0 +1,38 @@ +#include +#include +#include +#include + +#include +#include + +int main(int argc, char const *const *argv) +{ + amqp_connection_state_t conn = amqp_new_connection(); + amqp_socket_t *socket = amqp_tcp_socket_new(conn); + amqp_socket_open(socket, "localhost", AMQP_PROTOCOL_PORT); + amqp_login(conn, "/", 0, AMQP_DEFAULT_FRAME_SIZE, 0, AMQP_SASL_METHOD_PLAIN, "guest", "guest"); + const amqp_channel_t KChannel = 1; + amqp_channel_open(conn, KChannel); + + amqp_bytes_t exchangeName(amqp_cstring_bytes("direct_logs")); + amqp_exchange_declare(conn, KChannel, exchangeName, amqp_cstring_bytes("direct"), + false, false, false, false, amqp_empty_table); + + std::string severity = argc > 1 ? argv[1] : "info"; + std::string message(" Hello World!"); + if (argc > 2) + { + std::stringstream s; + copy(&argv[2], &argv[argc], std::ostream_iterator(s, " ")); + message = s.str(); + } + + amqp_basic_publish(conn, KChannel, exchangeName, amqp_cstring_bytes(severity.c_str()), false, false, nullptr, amqp_cstring_bytes(message.c_str())); + std::cout << " [x] Sent " << severity << ":" << message << std::endl; + + amqp_channel_close(conn, KChannel, AMQP_REPLY_SUCCESS); + amqp_connection_close(conn, AMQP_REPLY_SUCCESS); + amqp_destroy_connection(conn); + return 0; +} diff --git a/cpp/emit_log_topic.cpp b/cpp/emit_log_topic.cpp new file mode 100644 index 00000000..9074030c --- /dev/null +++ b/cpp/emit_log_topic.cpp @@ -0,0 +1,38 @@ +#include +#include +#include +#include + +#include +#include + +int main(int argc, char const *const *argv) +{ + amqp_connection_state_t conn = amqp_new_connection(); + amqp_socket_t *socket = amqp_tcp_socket_new(conn); + amqp_socket_open(socket, "localhost", AMQP_PROTOCOL_PORT); + amqp_login(conn, "/", 0, AMQP_DEFAULT_FRAME_SIZE, 0, AMQP_SASL_METHOD_PLAIN, "guest", "guest"); + const amqp_channel_t KChannel = 1; + amqp_channel_open(conn, KChannel); + + amqp_bytes_t exchangeName(amqp_cstring_bytes("topic_logs")); + amqp_exchange_declare(conn, KChannel, exchangeName, amqp_cstring_bytes("topic"), + false, false, false, false, amqp_empty_table); + + std::string routing_key = argc > 2 ? argv[1] : "anonymous.info"; + std::string message(" Hello World!"); + if (argc > 2) + { + std::stringstream s; + copy(&argv[2], &argv[argc], std::ostream_iterator(s, " ")); + message = s.str(); + } + + amqp_basic_publish(conn, KChannel, exchangeName, amqp_cstring_bytes(routing_key.c_str()), false, false, nullptr, amqp_cstring_bytes(message.c_str())); + std::cout << " [x] Sent " << routing_key << ":" << message << std::endl; + + amqp_channel_close(conn, KChannel, AMQP_REPLY_SUCCESS); + amqp_connection_close(conn, AMQP_REPLY_SUCCESS); + amqp_destroy_connection(conn); + return 0; +} diff --git a/cpp/new_task.cpp b/cpp/new_task.cpp new file mode 100644 index 00000000..9db362ba --- /dev/null +++ b/cpp/new_task.cpp @@ -0,0 +1,40 @@ +#include +#include +#include +#include + +#include +#include + +int main(int argc, char const *const *argv) +{ + amqp_connection_state_t conn = amqp_new_connection(); + amqp_socket_t *socket = amqp_tcp_socket_new(conn); + amqp_socket_open(socket, "localhost", AMQP_PROTOCOL_PORT); + amqp_login(conn, "/", 0, AMQP_DEFAULT_FRAME_SIZE, 0, AMQP_SASL_METHOD_PLAIN, "guest", "guest"); + const amqp_channel_t KChannel = 1; + amqp_channel_open(conn, KChannel); + + amqp_bytes_t queueName(amqp_cstring_bytes("task_queue")); + amqp_queue_declare(conn, KChannel, queueName, false, /*durable*/ true, false, true, amqp_empty_table); + + std::string message("Hello World!"); + if (argc > 1) + { + std::stringstream s; + copy(&argv[1], &argv[argc], std::ostream_iterator(s, " ")); + message = s.str(); + } + + amqp_basic_properties_t props; + props._flags = AMQP_BASIC_DELIVERY_MODE_FLAG; + props.delivery_mode = AMQP_DELIVERY_PERSISTENT; + + amqp_basic_publish(conn, KChannel, amqp_empty_bytes, /* routing key*/ queueName, false, false, &props, amqp_cstring_bytes(message.c_str())); + std::cout << " [x] Sent " << message << std::endl; + + amqp_channel_close(conn, KChannel, AMQP_REPLY_SUCCESS); + amqp_connection_close(conn, AMQP_REPLY_SUCCESS); + amqp_destroy_connection(conn); + return 0; +} diff --git a/cpp/publisher_confirms.cpp b/cpp/publisher_confirms.cpp new file mode 100644 index 00000000..2857ea09 --- /dev/null +++ b/cpp/publisher_confirms.cpp @@ -0,0 +1,52 @@ +#include +#include + +#include +#include + +int main(int argc, char const *const *argv) +{ + amqp_connection_state_t conn = amqp_new_connection(); + amqp_socket_t *socket = amqp_tcp_socket_new(conn); + amqp_socket_open(socket, "localhost", AMQP_PROTOCOL_PORT); + amqp_login(conn, "/", 0, AMQP_DEFAULT_FRAME_SIZE, 0, AMQP_SASL_METHOD_PLAIN, "guest", "guest"); + const amqp_channel_t KChannel = 1; + amqp_channel_open(conn, KChannel); + + amqp_bytes_t queueName(amqp_cstring_bytes("hello")); + amqp_queue_declare(conn, KChannel, queueName, false, false, false, false, amqp_empty_table); + + amqp_confirm_select(conn, KChannel); + amqp_basic_publish(conn, KChannel, amqp_empty_bytes, /* routing key*/ queueName, false, false, nullptr, amqp_cstring_bytes("Hello World!")); + amqp_basic_publish(conn, KChannel, amqp_empty_bytes, /* routing key*/ queueName, false, false, nullptr, amqp_cstring_bytes("Hello World!")); + + amqp_frame_t frame; + amqp_simple_wait_frame(conn, &frame); + if (frame.channel == KChannel) + { + if (frame.payload.method.id == AMQP_BASIC_ACK_METHOD) + { + amqp_basic_ack_t *ack = (amqp_basic_ack_t *)frame.payload.method.decoded; + if (ack->multiple) + std::cout << "Sucessfully sent messages up to delivery tag: " << ack->delivery_tag << std::endl; + else + std::cout << "Sucessfully sent message with delivery tag: " << ack->delivery_tag << std::endl; + } + else if (frame.payload.method.id == AMQP_BASIC_RETURN_METHOD) + { + // message wasn't routed to a queue, but returned + amqp_message_t returned_message; + amqp_read_message(conn, 1, &returned_message, 0); + amqp_destroy_message(&returned_message); + + amqp_simple_wait_frame(conn, &frame); + if (frame.payload.method.id == AMQP_BASIC_ACK_METHOD) + std::cout << "Message returned" << std::endl; + } + } + + amqp_channel_close(conn, KChannel, AMQP_REPLY_SUCCESS); + amqp_connection_close(conn, AMQP_REPLY_SUCCESS); + amqp_destroy_connection(conn); + return 0; +} diff --git a/cpp/receive.cpp b/cpp/receive.cpp new file mode 100644 index 00000000..5f4d68e4 --- /dev/null +++ b/cpp/receive.cpp @@ -0,0 +1,41 @@ +#include +#include + +#include +#include + + +int main(int argc, char const *const *argv) +{ + + amqp_connection_state_t conn = amqp_new_connection(); + + amqp_socket_t *socket = amqp_tcp_socket_new(conn); + + amqp_socket_open(socket, "localhost", AMQP_PROTOCOL_PORT); + + amqp_login(conn, "/", 0, AMQP_DEFAULT_FRAME_SIZE, 0, AMQP_SASL_METHOD_PLAIN, "guest", "guest"); + const amqp_channel_t KChannel = 1; + amqp_channel_open(conn, KChannel); + + amqp_bytes_t queueName(amqp_cstring_bytes("hello")); + amqp_queue_declare(conn, KChannel, queueName, false, false, false, false, amqp_empty_table); + + amqp_basic_consume(conn, KChannel, queueName, amqp_empty_bytes, false, /* auto ack*/true, false, amqp_empty_table); + + for (;;) + { + amqp_maybe_release_buffers(conn); + amqp_envelope_t envelope; + amqp_consume_message(conn, &envelope, nullptr, 0); + + std::cout << " [x] Received " << std::string((char *)envelope.message.body.bytes,(int)envelope.message.body.len) << std::endl; + amqp_destroy_envelope(&envelope); + } + + amqp_channel_close(conn, KChannel, AMQP_REPLY_SUCCESS); + amqp_connection_close(conn, AMQP_REPLY_SUCCESS); + amqp_destroy_connection(conn); + + return 0; +} diff --git a/cpp/receive_logs.cpp b/cpp/receive_logs.cpp new file mode 100644 index 00000000..f7d1ed48 --- /dev/null +++ b/cpp/receive_logs.cpp @@ -0,0 +1,52 @@ + #include +#include +#include +#include +#include + +#include +#include + + +int main(int argc, char const *const *argv) +{ + + amqp_connection_state_t conn = amqp_new_connection(); + amqp_socket_t *socket = amqp_tcp_socket_new(conn); + amqp_socket_open(socket, "localhost", AMQP_PROTOCOL_PORT); + + amqp_login(conn, "/", 0, AMQP_DEFAULT_FRAME_SIZE, 0, AMQP_SASL_METHOD_PLAIN, "guest", "guest"); + const amqp_channel_t KChannel = 1; + amqp_channel_open(conn, KChannel); + + amqp_bytes_t exchangeName(amqp_cstring_bytes("logs")); + amqp_exchange_declare(conn, KChannel, exchangeName, amqp_cstring_bytes("fanout"), + false, false, false, false, amqp_empty_table); + + amqp_queue_declare_ok_t *r = amqp_queue_declare(conn, KChannel, amqp_empty_bytes, false, false, true, false, amqp_empty_table); + amqp_bytes_t queueName = amqp_bytes_malloc_dup(r->queue); + + amqp_queue_bind(conn, KChannel, queueName, exchangeName, amqp_empty_bytes, amqp_empty_table); + + std::cout << "[*] Waiting for logs. To exit press CTRL+C'" << std::endl; + amqp_basic_consume(conn, KChannel, queueName, amqp_empty_bytes, false, /* auto ack*/true, false, amqp_empty_table); + + for (;;) + { + amqp_maybe_release_buffers(conn); + amqp_envelope_t envelope; + amqp_consume_message(conn, &envelope, nullptr, 0); + + std::string message((char *)envelope.message.body.bytes,(int)envelope.message.body.len); + std::cout << " [x] Received " << message << std::endl; + + amqp_destroy_envelope(&envelope); + } + + amqp_bytes_free(queueName); + amqp_channel_close(conn, KChannel, AMQP_REPLY_SUCCESS); + amqp_connection_close(conn, AMQP_REPLY_SUCCESS); + amqp_destroy_connection(conn); + + return 0; +} diff --git a/cpp/receive_logs_direct.cpp b/cpp/receive_logs_direct.cpp new file mode 100644 index 00000000..84addf51 --- /dev/null +++ b/cpp/receive_logs_direct.cpp @@ -0,0 +1,60 @@ +#include +#include +#include +#include +#include + +#include +#include + +int main(int argc, char const *const *argv) +{ + + amqp_connection_state_t conn = amqp_new_connection(); + amqp_socket_t *socket = amqp_tcp_socket_new(conn); + amqp_socket_open(socket, "localhost", AMQP_PROTOCOL_PORT); + + amqp_login(conn, "/", 0, AMQP_DEFAULT_FRAME_SIZE, 0, AMQP_SASL_METHOD_PLAIN, "guest", "guest"); + const amqp_channel_t KChannel = 1; + amqp_channel_open(conn, KChannel); + + amqp_bytes_t exchangeName(amqp_cstring_bytes("direct_logs")); + amqp_exchange_declare(conn, KChannel, exchangeName, amqp_cstring_bytes("direct"), + false, false, false, false, amqp_empty_table); + + amqp_queue_declare_ok_t *r = amqp_queue_declare(conn, KChannel, amqp_empty_bytes, false, false, /*exclusive*/ true, false, amqp_empty_table); + amqp_bytes_t queueName = amqp_bytes_malloc_dup(r->queue); + + if (argc > 1) + { + for (int i = 1; i < argc; ++i) + amqp_queue_bind(conn, KChannel, queueName, exchangeName, amqp_cstring_bytes(argv[i]), amqp_empty_table); + } + else + { + std::cout << "Usage: " << argv[0] << " [info] [warning] [error]" << std::endl; + return 1; + } + + std::cout << "[*] Waiting for logs. To exit press CTRL+C'" << std::endl; + amqp_basic_consume(conn, KChannel, queueName, amqp_empty_bytes, false, /* auto ack*/ true, false, amqp_empty_table); + + for (;;) + { + amqp_maybe_release_buffers(conn); + amqp_envelope_t envelope; + amqp_consume_message(conn, &envelope, nullptr, 0); + + std::string message((char *)envelope.message.body.bytes, (int)envelope.message.body.len); + std::cout << " [x] Received " << std::string((char*)envelope.routing_key.bytes, (int)envelope.routing_key.len) << ":" << message << std::endl; + + amqp_destroy_envelope(&envelope); + } + + amqp_bytes_free(queueName); + amqp_channel_close(conn, KChannel, AMQP_REPLY_SUCCESS); + amqp_connection_close(conn, AMQP_REPLY_SUCCESS); + amqp_destroy_connection(conn); + + return 0; +} diff --git a/cpp/receive_logs_topic.cpp b/cpp/receive_logs_topic.cpp new file mode 100644 index 00000000..8bbdbada --- /dev/null +++ b/cpp/receive_logs_topic.cpp @@ -0,0 +1,60 @@ +#include +#include +#include +#include +#include + +#include +#include + +int main(int argc, char const *const *argv) +{ + + amqp_connection_state_t conn = amqp_new_connection(); + amqp_socket_t *socket = amqp_tcp_socket_new(conn); + amqp_socket_open(socket, "localhost", AMQP_PROTOCOL_PORT); + + amqp_login(conn, "/", 0, AMQP_DEFAULT_FRAME_SIZE, 0, AMQP_SASL_METHOD_PLAIN, "guest", "guest"); + const amqp_channel_t KChannel = 1; + amqp_channel_open(conn, KChannel); + + amqp_bytes_t exchangeName(amqp_cstring_bytes("topic_logs")); + amqp_exchange_declare(conn, KChannel, exchangeName, amqp_cstring_bytes("topic"), + false, false, false, false, amqp_empty_table); + + amqp_queue_declare_ok_t *r = amqp_queue_declare(conn, KChannel, amqp_empty_bytes, false, false, /*exclusive*/ true, false, amqp_empty_table); + amqp_bytes_t queueName = amqp_bytes_malloc_dup(r->queue); + + if (argc > 1) + { + for (int i = 1; i < argc; ++i) + amqp_queue_bind(conn, KChannel, queueName, exchangeName, amqp_cstring_bytes(argv[i]), amqp_empty_table); + } + else + { + std::cout << "Usage: " << argv[0] << " [binding_key] ..." << std::endl; + return 1; + } + + std::cout << "[*] Waiting for logs. To exit press CTRL+C'" << std::endl; + amqp_basic_consume(conn, KChannel, queueName, amqp_empty_bytes, false, /* auto ack*/ true, false, amqp_empty_table); + + for (;;) + { + amqp_maybe_release_buffers(conn); + amqp_envelope_t envelope; + amqp_consume_message(conn, &envelope, nullptr, 0); + + std::string message((char *)envelope.message.body.bytes, (int)envelope.message.body.len); + std::cout << " [x] Received " << std::string((char*)envelope.routing_key.bytes, (int)envelope.routing_key.len) << ":" << message << std::endl; + + amqp_destroy_envelope(&envelope); + } + + amqp_bytes_free(queueName); + amqp_channel_close(conn, KChannel, AMQP_REPLY_SUCCESS); + amqp_connection_close(conn, AMQP_REPLY_SUCCESS); + amqp_destroy_connection(conn); + + return 0; +} diff --git a/cpp/rpc_client.cpp b/cpp/rpc_client.cpp new file mode 100644 index 00000000..383df8f6 --- /dev/null +++ b/cpp/rpc_client.cpp @@ -0,0 +1,89 @@ +#include +#include + +#include +#include + +class FibonacciRpcClient +{ +public: + FibonacciRpcClient() + { + m_conn = amqp_new_connection(); + amqp_socket_t *socket = amqp_tcp_socket_new(m_conn); + amqp_socket_open(socket, "localhost", AMQP_PROTOCOL_PORT); + amqp_login(m_conn, "/", 0, AMQP_DEFAULT_FRAME_SIZE, 0, AMQP_SASL_METHOD_PLAIN, "guest", "guest"); + + amqp_channel_open(m_conn, KChannel); + + amqp_queue_declare_ok_t *r = amqp_queue_declare(m_conn, KChannel, amqp_empty_bytes, false, false, false, /*auto delete*/true, amqp_empty_table); + m_callbackQueue = amqp_bytes_malloc_dup(r->queue); + } + + int call(int n) + { + m_corr_id = std::to_string(++m_requestCount); + + amqp_basic_properties_t props; + props._flags = AMQP_BASIC_CORRELATION_ID_FLAG | AMQP_BASIC_REPLY_TO_FLAG; + props.correlation_id = amqp_cstring_bytes(m_corr_id.c_str()); + props.reply_to = m_callbackQueue; + + amqp_bytes_t n_; + n_.bytes = &n; + n_.len = sizeof(n); + amqp_basic_publish(m_conn, KChannel, amqp_empty_bytes, /* routing key*/ amqp_cstring_bytes("rpc_queue"), false, false, &props, n_); + + amqp_basic_consume(m_conn, KChannel, m_callbackQueue, amqp_empty_bytes, false, /* auto ack*/ true, false, amqp_empty_table); + + int response = 0; + bool keepProcessing = true; + while (keepProcessing) + { + amqp_maybe_release_buffers(m_conn); + amqp_envelope_t envelope; + amqp_consume_message(m_conn, &envelope, nullptr, 0); + + std::string correlation_id((char *)envelope.message.properties.correlation_id.bytes, (int)envelope.message.properties.correlation_id.len); + if (correlation_id == m_corr_id) + { + response = *(int *)envelope.message.body.bytes; + keepProcessing = false; + } + + amqp_destroy_envelope(&envelope); + } + + return response; + } + + ~FibonacciRpcClient() + { + amqp_bytes_free(m_callbackQueue); + amqp_channel_close(m_conn, KChannel, AMQP_REPLY_SUCCESS); + amqp_connection_close(m_conn, AMQP_REPLY_SUCCESS); + amqp_destroy_connection(m_conn); + } + +private: + amqp_connection_state_t m_conn; + const amqp_channel_t KChannel = 1; + amqp_bytes_t m_callbackQueue; + std::string m_corr_id; + int m_requestCount = 0; +}; + +int main(int argc, char const *const *argv) +{ + int n = 30; + if (argc > 1) + { + n = std::stoi(argv[1]); + } + + FibonacciRpcClient fibonacciRpcClient; + std::cout << " [x] Requesting fib(" << n << ")" << std::endl; + int response = fibonacciRpcClient.call(n); + std::cout << " [.] Got " << response << std::endl; + return 0; +} diff --git a/cpp/rpc_server.cpp b/cpp/rpc_server.cpp new file mode 100644 index 00000000..ec0b32c0 --- /dev/null +++ b/cpp/rpc_server.cpp @@ -0,0 +1,62 @@ +#include +#include + +#include +#include + +int fib(int n) +{ + if (n == 0) + return 0; + else if (n == 1) + return 1; + else + return fib(n-1) + fib(n-2); +} + +int main(int argc, char const *const *argv) +{ + amqp_connection_state_t conn = amqp_new_connection(); + amqp_socket_t *socket = amqp_tcp_socket_new(conn); + amqp_socket_open(socket, "localhost", AMQP_PROTOCOL_PORT); + amqp_login(conn, "/", 0, AMQP_DEFAULT_FRAME_SIZE, 0, AMQP_SASL_METHOD_PLAIN, "guest", "guest"); + const amqp_channel_t KChannel = 1; + amqp_channel_open(conn, KChannel); + + amqp_bytes_t queueName(amqp_cstring_bytes("rpc_queue")); + amqp_queue_declare(conn, KChannel, queueName, false, false, false, false, amqp_empty_table); + + amqp_basic_qos(conn, KChannel, 0, /*prefetch_count*/1, 0); + amqp_basic_consume(conn, KChannel, queueName, amqp_empty_bytes, false, /* auto ack*/false, false, amqp_empty_table); + std::cout << " [x] Awaiting RPC requests" << std::endl; + + for (;;) + { + amqp_maybe_release_buffers(conn); + amqp_envelope_t envelope; + amqp_rpc_reply_t res = amqp_consume_message(conn, &envelope, nullptr, 0); + + int n = *(int *)envelope.message.body.bytes; + std::cout << " [.] fib(" << n << ")" << std::endl; + int response = fib(n); + amqp_bytes_t response_; + response_.bytes = &response; + response_.len = sizeof(response); + + amqp_basic_properties_t props; + props._flags = AMQP_BASIC_CORRELATION_ID_FLAG; + props.correlation_id = envelope.message.properties.correlation_id; + + amqp_channel_t replyChannel = envelope.channel; + amqp_basic_publish(conn, replyChannel, amqp_empty_bytes, /* routing key*/ envelope.message.properties.reply_to, false, false, &props, response_); + amqp_basic_ack(conn, replyChannel, envelope.delivery_tag, false); + + amqp_destroy_envelope(&envelope); + } + + amqp_channel_close(conn, KChannel, AMQP_REPLY_SUCCESS); + amqp_connection_close(conn, AMQP_REPLY_SUCCESS); + amqp_destroy_connection(conn); + + return 0; +} diff --git a/cpp/send.cpp b/cpp/send.cpp new file mode 100644 index 00000000..9fcf0821 --- /dev/null +++ b/cpp/send.cpp @@ -0,0 +1,26 @@ +#include +#include + +#include +#include + +int main(int argc, char const *const *argv) +{ + amqp_connection_state_t conn = amqp_new_connection(); + amqp_socket_t *socket = amqp_tcp_socket_new(conn); + amqp_socket_open(socket, "localhost", AMQP_PROTOCOL_PORT); + amqp_login(conn, "/", 0, AMQP_DEFAULT_FRAME_SIZE, 0, AMQP_SASL_METHOD_PLAIN, "guest", "guest"); + const amqp_channel_t KChannel = 1; + amqp_channel_open(conn, KChannel); + + amqp_bytes_t queueName(amqp_cstring_bytes("hello")); + amqp_queue_declare(conn, KChannel, queueName, false, false, false, false, amqp_empty_table); + + amqp_basic_publish(conn, KChannel, amqp_empty_bytes, /* routing key*/ queueName, false, false, nullptr, amqp_cstring_bytes("Hello World!")); + std::cout << " [x] Sent 'Hello World!'" << std::endl; + + amqp_channel_close(conn, KChannel, AMQP_REPLY_SUCCESS); + amqp_connection_close(conn, AMQP_REPLY_SUCCESS); + amqp_destroy_connection(conn); + return 0; +} diff --git a/cpp/worker.cpp b/cpp/worker.cpp new file mode 100644 index 00000000..14af2290 --- /dev/null +++ b/cpp/worker.cpp @@ -0,0 +1,49 @@ +#include +#include +#include +#include +#include + +#include +#include + + +int main(int argc, char const *const *argv) +{ + + amqp_connection_state_t conn = amqp_new_connection(); + amqp_socket_t *socket = amqp_tcp_socket_new(conn); + amqp_socket_open(socket, "localhost", AMQP_PROTOCOL_PORT); + + amqp_login(conn, "/", 0, AMQP_DEFAULT_FRAME_SIZE, 0, AMQP_SASL_METHOD_PLAIN, "guest", "guest"); + const amqp_channel_t KChannel = 1; + amqp_channel_open(conn, KChannel); + + amqp_bytes_t queueName(amqp_cstring_bytes("task_queue")); + amqp_queue_declare(conn, KChannel, queueName, false, /*durable*/ true, false, true, amqp_empty_table); + + amqp_basic_qos(conn, KChannel, 0, /*prefetch_count*/1, 0); + amqp_basic_consume(conn, KChannel, queueName, amqp_empty_bytes, false, /* auto ack*/false, false, amqp_empty_table); + + for (;;) + { + amqp_maybe_release_buffers(conn); + amqp_envelope_t envelope; + amqp_consume_message(conn, &envelope, nullptr, 0); + + std::string message((char *)envelope.message.body.bytes,(int)envelope.message.body.len); + std::cout << " [x] Received " << message << std::endl; + const int seconds = std::count(std::begin(message), std::end(message), '.'); + std::this_thread::sleep_for(std::chrono::seconds(seconds)); + std::cout << " [x] Done" << std::endl; + + amqp_basic_ack(conn, KChannel, envelope.delivery_tag, false); + amqp_destroy_envelope(&envelope); + } + + amqp_channel_close(conn, KChannel, AMQP_REPLY_SUCCESS); + amqp_connection_close(conn, AMQP_REPLY_SUCCESS); + amqp_destroy_connection(conn); + + return 0; +} diff --git a/dart/README.md b/dart/README.md new file mode 100644 index 00000000..558793c2 --- /dev/null +++ b/dart/README.md @@ -0,0 +1,53 @@ +# Dart code for RabbitMQ tutorials + +Here you can find an [Dart](https://www.dartlang.org/) port of +[RabbitMQ tutorials](https://www.rabbitmq.com/getstarted.html). + + +## Requirements + +To run this code you need a [Dart 2 server platform installed](https://www.dartlang.org/tools/sdk#install) + +### Dart 2.0+ + +These tutorials use [dart_amqp](https://github.com/achilleasa/dart_amqp). + +To install dependencies with pub, run: + + pub get + +## Code + +To run the examples, use `dart source_file.dart`. + +Tutorial one: "Hello World!": + + dart receive.dart + dart send.dart + +Tutorial two: Work Queues + + dart worker.dart + dart new_task.dart + +Tutorial three: Publish/Subscribe + + dart receive_logs.dart + dart emit_log.dart + +Tutorial four: Routing + + dart receive_logs_direct.dart info warning + dart emit_log_direct.dart info "A message" + dart emit_log_direct.dart warning "A warning" + +Tutorial five: Topics + + dart receive_logs_topic.dart "info.*" "warn.*" + dart emit_log_topic.dart "info.connections" "Connected" + dart emit_log_topic.dart "warn.connecctions" "A warning" + +Tutorial six: RPC (Request/Response) + + dart rpc_server.dart + dart rpc_client.dart diff --git a/dart/emit_log.dart b/dart/emit_log.dart new file mode 100644 index 00000000..e8e06d55 --- /dev/null +++ b/dart/emit_log.dart @@ -0,0 +1,21 @@ +import "package:dart_amqp/dart_amqp.dart"; + +void main (List arguments) { + ConnectionSettings settings = new ConnectionSettings( + host: "localhost" + ); + + Client client = new Client(settings: settings); + + String msg = arguments.isEmpty ? "Hello World!" : arguments[0]; + + client + .channel() + .then((Channel channel) => + channel.exchange("logs", ExchangeType.FANOUT, durable: false)) + .then((Exchange exchange) { + exchange.publish(msg, null); + print(" [x] Sent ${msg}"); + return client.close(); + }); +} diff --git a/dart/emit_log_direct.dart b/dart/emit_log_direct.dart new file mode 100644 index 00000000..9ebf39f7 --- /dev/null +++ b/dart/emit_log_direct.dart @@ -0,0 +1,23 @@ +import "package:dart_amqp/dart_amqp.dart"; + +void main(List arguments) { + ConnectionSettings settings = new ConnectionSettings( + host: "localhost" + ); + + Client client = new Client(settings: settings); + + String routingKey = arguments.length < 1 ? "info" : arguments[0]; + String msg = arguments.length < 2 ? "Hello World!" : arguments[1]; + + client + .channel() + .then((Channel channel) => + channel.exchange("direct_logs", ExchangeType.DIRECT, + durable: false)) + .then((Exchange exchange) { + exchange.publish(msg, routingKey); + print(" [x] Sent ${routingKey}: ${msg}"); + return client.close(); + }); +} diff --git a/dart/emit_log_topic.dart b/dart/emit_log_topic.dart new file mode 100644 index 00000000..8014a329 --- /dev/null +++ b/dart/emit_log_topic.dart @@ -0,0 +1,23 @@ +import "package:dart_amqp/dart_amqp.dart"; + +void main(List arguments) { + ConnectionSettings settings = new ConnectionSettings( + host: "localhost" + ); + + Client client = new Client(settings: settings); + + String routingKey = arguments.length < 1 ? "anonymous.info" : arguments[0]; + String msg = arguments.length < 2 ? "Hello World!" : arguments[1]; + + client + .channel() + .then((Channel channel) => + channel.exchange("topic_logs", ExchangeType.TOPIC, + durable: false)) + .then((Exchange exchange) { + exchange.publish(msg, routingKey); + print(" [x] Sent ${routingKey}: ${msg}"); + return client.close(); + }); +} diff --git a/dart/new_task.dart b/dart/new_task.dart new file mode 100644 index 00000000..1baf3492 --- /dev/null +++ b/dart/new_task.dart @@ -0,0 +1,24 @@ +import "package:dart_amqp/dart_amqp.dart"; + +void main(List arguments) { + ConnectionSettings settings = new ConnectionSettings( + host: "localhost" + ); + + Client client = new Client(settings: settings); + + String consumeTag = "task_queue"; + String msg = arguments.isEmpty ? "Hello World!" : arguments[0]; + + MessageProperties properties = new MessageProperties.persistentMessage(); + + client + .channel() + .then((Channel channel) => + channel.queue(consumeTag, durable: true)) + .then((Queue queue) { + queue.publish(msg, properties: properties); + print(" [x] Sent ${msg}"); + return client.close(); + }); +} diff --git a/dart/pubspec.lock b/dart/pubspec.lock new file mode 100644 index 00000000..6cdc0680 --- /dev/null +++ b/dart/pubspec.lock @@ -0,0 +1,19 @@ +# Generated by pub +# See https://www.dartlang.org/tools/pub/glossary#lockfile +packages: + dart_amqp: + dependency: "direct main" + description: + name: dart_amqp + url: "https://pub.dartlang.org" + source: hosted + version: "0.1.1" + logging: + dependency: transitive + description: + name: logging + url: "https://pub.dartlang.org" + source: hosted + version: "0.11.3+2" +sdks: + dart: ">=2.0.0-dev <3.0.0" diff --git a/dart/pubspec.yaml b/dart/pubspec.yaml new file mode 100644 index 00000000..34c3f053 --- /dev/null +++ b/dart/pubspec.yaml @@ -0,0 +1,3 @@ +name: dart_rabbitmq_example +dependencies: + dart_amqp: 0.1.1 diff --git a/dart/receive.dart b/dart/receive.dart new file mode 100644 index 00000000..ce40a4e9 --- /dev/null +++ b/dart/receive.dart @@ -0,0 +1,34 @@ +import "dart:io"; +import "package:dart_amqp/dart_amqp.dart"; + +void main (List arguments) { + ConnectionSettings settings = new ConnectionSettings( + host: "localhost" + ); + + Client client = new Client(settings: settings); + + ProcessSignal.sigint.watch().listen((_) { + client.close().then((_) { + print("close client"); + exit(0); + }); + }); + + String msg = arguments.isEmpty ? "Hello World!": arguments[0]; + + String queueTag = "hello"; + + client + .channel() + .then((Channel channel) => channel.queue(queueTag, durable: false)) + .then((Queue queue) { + print(" [*] Waiting for messages in ${queueTag}. To Exit press CTRL+C"); + return queue.consume(consumerTag: queueTag, noAck: true); + }) + .then((Consumer consumer) { + consumer.listen((AmqpMessage event) { + print(" [x] Received ${event.payloadAsString}"); + }); + }); +} diff --git a/dart/receive_logs.dart b/dart/receive_logs.dart new file mode 100644 index 00000000..703d8252 --- /dev/null +++ b/dart/receive_logs.dart @@ -0,0 +1,34 @@ +import "dart:io"; +import "package:dart_amqp/dart_amqp.dart"; + +void main (List arguments) { + ConnectionSettings settings = new ConnectionSettings( + host: "localhost" + ); + + Client client = new Client(settings: settings); + + ProcessSignal.sigint.watch().listen((_) { + client.close().then((_) { + print("close client"); + exit(0); + }); + }); + + String msg = arguments.isEmpty ? "Hello World!": arguments[0]; + + client + .channel() + .then((Channel channel) { + return channel.exchange("logs", ExchangeType.FANOUT, durable: false); + }) + .then((Exchange exchange) { + print(" [*] Waiting for messages in logs. To Exit press CTRL+C"); + return exchange.bindPrivateQueueConsumer(null); + }) + .then((Consumer consumer) { + consumer.listen((AmqpMessage event) { + print(" [x] Received ${event.payloadAsString}"); + }); + }); +} diff --git a/dart/receive_logs_direct.dart b/dart/receive_logs_direct.dart new file mode 100644 index 00000000..6041a07e --- /dev/null +++ b/dart/receive_logs_direct.dart @@ -0,0 +1,40 @@ +import "dart:io"; +import "package:dart_amqp/dart_amqp.dart"; + +void main (List arguments) { + if (arguments.isEmpty) { + print("Usage: receive_logs_direct.dart [info] [warning] [error]"); + return; + } + + ConnectionSettings settings = new ConnectionSettings( + host: "localhost" + ); + + Client client = new Client(settings: settings); + + ProcessSignal.sigint.watch().listen((_) { + client.close().then((_) { + print("close client"); + exit(0); + }); + }); + + List routingKeys = arguments.sublist(0, 2); + client + .channel() + .then((Channel channel) { + return channel.exchange("direct_logs", ExchangeType.DIRECT, durable: false); + }) + .then((Exchange exchange) { + print(" [*] Waiting for messages in logs. To Exit press CTRL+C"); + return exchange.bindPrivateQueueConsumer(routingKeys, + consumerTag: "direct_logs", noAck: true + ); + }) + .then((Consumer consumer) { + consumer.listen((AmqpMessage event) { + print(" [x] ${event.routingKey}:'${event.payloadAsString}'"); + }); + }); +} diff --git a/dart/receive_logs_topic.dart b/dart/receive_logs_topic.dart new file mode 100644 index 00000000..486393c1 --- /dev/null +++ b/dart/receive_logs_topic.dart @@ -0,0 +1,40 @@ +import "dart:io"; +import "package:dart_amqp/dart_amqp.dart"; + +void main (List arguments) { + if (arguments.isEmpty) { + print("Usage: receive_logs_direct.dart [, ...]"); + return; + } + + ConnectionSettings settings = new ConnectionSettings( + host: "localhost" + ); + + Client client = new Client(settings: settings); + + ProcessSignal.sigint.watch().listen((_) { + client.close().then((_) { + print("close client"); + exit(0); + }); + }); + + List routingKeys = arguments.sublist(0); + client + .channel() + .then((Channel channel) { + return channel.exchange("topic_logs", ExchangeType.TOPIC, durable: false); + }) + .then((Exchange exchange) { + print(" [*] Waiting for messages in logs. To Exit press CTRL+C"); + return exchange.bindPrivateQueueConsumer(routingKeys, + consumerTag: "topic_logs", noAck: true + ); + }) + .then((Consumer consumer) { + consumer.listen((AmqpMessage event) { + print(" [x] ${event.routingKey}:'${event.payloadAsString}'"); + }); + }); +} diff --git a/dart/rpc_client.dart b/dart/rpc_client.dart new file mode 100644 index 00000000..4def3669 --- /dev/null +++ b/dart/rpc_client.dart @@ -0,0 +1,80 @@ +import "dart:io"; +import "dart:async"; +import "dart:math"; +import "package:dart_amqp/dart_amqp.dart"; + +var UUID = () => "${(new Random()).nextDouble()}"; + +class RPCClient { + Client client; + String queueTag; + String _replyQueueTag; + Completer contextChannel; + Map _channels = new Map(); + Queue _queue; + RPCClient() : client = new Client(), + queueTag = "rpc_queue" { + contextChannel = new Completer(); + client + .channel() + .then((Channel channel) => channel.queue(queueTag)) + .then((Queue rpcQueue) { + _queue = rpcQueue; + return rpcQueue.channel.privateQueue(); + }) + .then((Queue rpcQueue) { + rpcQueue.consume(noAck: true) + .then((Consumer consumer) { + _replyQueueTag = consumer.queue.name; + consumer.listen(handler); + contextChannel.complete(); + }); + }); + } + + void handler (AmqpMessage event) { + if (!_channels + .containsKey( + event.properties.corellationId)) return; + print(" [.] Got ${event.payloadAsString}"); + _channels + .remove(event.properties.corellationId) + .complete(event.payloadAsString); + } + + Future call(int n) { + return contextChannel.future + .then((_) { + String uuid = UUID(); + Completer channel = new Completer(); + MessageProperties properties = new MessageProperties() + ..replyTo = _replyQueueTag + ..corellationId = uuid; + _channels[uuid] = channel; + print(" [x] Requesting ${n}"); + _queue.publish({"n": n}, properties: properties); + return channel.future; + }); + } + + Future close() { + _channels.forEach((_, var next) => next.complete("RPC client closed")); + _channels.clear(); + client.close(); + } +} + +void main(List arguments) { + if (arguments.isEmpty) { + print("Usage: rpc_client.dart num"); + return; + } + RPCClient client = new RPCClient(); + int n = arguments.isEmpty ? 30 : num.parse(arguments[0]); + client.call(n) + .then((String res) { + print(" [x] fib(${n}) = ${res}"); + }) + .then((_) => client.close()) + .then((_) => null); +} \ No newline at end of file diff --git a/dart/rpc_server.dart b/dart/rpc_server.dart new file mode 100644 index 00000000..e6432fc2 --- /dev/null +++ b/dart/rpc_server.dart @@ -0,0 +1,36 @@ +import "dart:io"; +import "package:dart_amqp/dart_amqp.dart"; + +// Slow implementation of fib +int fib(int n) { + if (n >= 0 && n <= 1) { + return n; + } else + return fib(n - 1) + fib(n - 2); +} + +void main(List args) { + + Client client = new Client(); + + // Setup a signal handler to cleanly exit if CTRL+C is pressed + ProcessSignal.sigint.watch().listen((_) { + client.close().then((_) { + exit(0); + }); + }); + + client + .channel() + .then((Channel channel) => channel.qos(0, 1)) + .then((Channel channel) => channel.queue("rpc_queue")) + .then((Queue queue) => queue.consume()) + .then((Consumer consumer) { + print(" [x] Awaiting RPC request"); + consumer.listen((AmqpMessage message) { + var n = message.payloadAsJson["n"]; + print(" [.] fib(${n})"); + message.reply(fib(n).toString()); + }); + }); +} diff --git a/dart/send.dart b/dart/send.dart new file mode 100644 index 00000000..3e5d8252 --- /dev/null +++ b/dart/send.dart @@ -0,0 +1,23 @@ +import "package:dart_amqp/dart_amqp.dart"; + +void main (List arguments) { + ConnectionSettings settings = new ConnectionSettings( + host: "localhost" + ); + + Client client = new Client(settings: settings); + + String consumeTag = "hello"; + String msg = "hello"; + + client + .channel() + .then((Channel channel) { + return channel.queue(consumeTag, durable: false); + }) + .then((Queue queue) { + queue.publish(msg); + print(" [x] Sent ${msg}"); + client.close(); + }); +} diff --git a/dart/worker.dart b/dart/worker.dart new file mode 100644 index 00000000..bf91258f --- /dev/null +++ b/dart/worker.dart @@ -0,0 +1,37 @@ +import "dart:io"; +import "package:dart_amqp/dart_amqp.dart"; + +void main (List arguments) { + ConnectionSettings settings = new ConnectionSettings( + host: "localhost" + ); + + Client client = new Client(settings: settings); + + ProcessSignal.sigint.watch().listen((_) { + client.close().then((_) { + print("close client"); + exit(0); + }); + }); + + String consumeTag = "task_queue"; + + client + .channel() + .then((Channel channel) { + return channel.qos(0, 1) + .then((Channel channel) => + channel.queue(consumeTag, durable: true)); + }) + .then((Queue queue) => queue.consume(noAck: false)) + .then((Consumer consumer) { + consumer.listen((AmqpMessage event) { + String payload = event.payloadAsString; + print(" [x] Received ${payload}"); + sleep(new Duration(seconds : payload.split(".").length)); + print(" [x] Done"); + event.ack(); + }); + }); +} diff --git a/dotnet-amqp/.gitattributes b/dotnet-amqp/.gitattributes new file mode 100644 index 00000000..27ab416b --- /dev/null +++ b/dotnet-amqp/.gitattributes @@ -0,0 +1,13 @@ +# Set the default behavior, in case people don't have core.autocrlf set. +* text=auto + +# Auto detect text files and perform LF normalization +*.cs text=auto eol=lf +*.txt text=auto + +# Declare files that will always have CRLF line endings on checkout. +*.sln text eol=crlf +*.csproj text eol=crlf + +# Custom for Visual Studio +*.cs diff=csharp diff --git a/dotnet-amqp/.gitignore b/dotnet-amqp/.gitignore new file mode 100644 index 00000000..4cbb9f26 --- /dev/null +++ b/dotnet-amqp/.gitignore @@ -0,0 +1,6 @@ +*.dll +*.exe +*.lock.json +packages/ +bin/ +obj/ diff --git a/dotnet-amqp/EmitLog/EmitLog.csproj b/dotnet-amqp/EmitLog/EmitLog.csproj new file mode 100644 index 00000000..a0a7e254 --- /dev/null +++ b/dotnet-amqp/EmitLog/EmitLog.csproj @@ -0,0 +1,14 @@ + + + + Exe + net8.0;net10.0 + enable + enable + + + + + + + diff --git a/dotnet-amqp/EmitLog/Program.cs b/dotnet-amqp/EmitLog/Program.cs new file mode 100644 index 00000000..78e95436 --- /dev/null +++ b/dotnet-amqp/EmitLog/Program.cs @@ -0,0 +1,58 @@ +using System.Text; +using RabbitMQ.AMQP.Client; +using RabbitMQ.AMQP.Client.Impl; + +const string brokerUri = "amqp://guest:guest@localhost:5672/%2f"; +const string exchangeName = "logs"; + +string message = args.Length < 1 ? "info: Hello World!" : string.Join(" ", args); + +ConnectionSettings settings = ConnectionSettingsBuilder.Create() + .Uri(new Uri(brokerUri)) + .ContainerId("tutorial-emitlog") + .Build(); + +IEnvironment environment = AmqpEnvironment.Create(settings); +IConnection connection = await environment.CreateConnectionAsync(); + +try +{ + IManagement management = connection.Management(); + IExchangeSpecification exchangeSpec = management.Exchange(exchangeName).Type("fanout"); + await exchangeSpec.DeclareAsync(); + + IPublisher publisher = await connection.PublisherBuilder().Exchange(exchangeName).BuildAsync(); + try + { + var amqpMessage = new AmqpMessage(Encoding.UTF8.GetBytes(message)); + PublishResult pr = await publisher.PublishAsync(amqpMessage); + switch (pr.Outcome.State) + { + case OutcomeState.Accepted: + break; + case OutcomeState.Released: + Console.Error.WriteLine($"Released message: {pr.Message.BodyAsString()}"); + Environment.Exit(1); + break; + case OutcomeState.Rejected: + Console.Error.WriteLine($"[Publisher] Message: {pr.Message.BodyAsString()} rejected with error: {pr.Outcome.Error}"); + Environment.Exit(1); + break; + default: + Console.Error.WriteLine($"Unexpected publish outcome: {pr.Outcome.State}"); + Environment.Exit(1); + break; + } + + Console.WriteLine($" [x] Sent '{message}'"); + } + finally + { + await publisher.CloseAsync(); + } +} +finally +{ + await connection.CloseAsync(); + await environment.CloseAsync(); +} diff --git a/dotnet-amqp/EmitLogDirect/EmitLogDirect.csproj b/dotnet-amqp/EmitLogDirect/EmitLogDirect.csproj new file mode 100644 index 00000000..a0a7e254 --- /dev/null +++ b/dotnet-amqp/EmitLogDirect/EmitLogDirect.csproj @@ -0,0 +1,14 @@ + + + + Exe + net8.0;net10.0 + enable + enable + + + + + + + diff --git a/dotnet-amqp/EmitLogDirect/Program.cs b/dotnet-amqp/EmitLogDirect/Program.cs new file mode 100644 index 00000000..004036d0 --- /dev/null +++ b/dotnet-amqp/EmitLogDirect/Program.cs @@ -0,0 +1,71 @@ +using System.Text; +using RabbitMQ.AMQP.Client; +using RabbitMQ.AMQP.Client.Impl; + +const string brokerUri = "amqp://guest:guest@localhost:5672/%2f"; +const string exchangeName = "logs_direct"; + +string severity = GetSeverity(args); +string message = GetMessage(args); + +ConnectionSettings settings = ConnectionSettingsBuilder.Create() + .Uri(new Uri(brokerUri)) + .ContainerId("tutorial-emitlogdirect") + .Build(); + +IEnvironment environment = AmqpEnvironment.Create(settings); +IConnection connection = await environment.CreateConnectionAsync(); + +try +{ + IManagement management = connection.Management(); + IExchangeSpecification exchangeSpec = management.Exchange(exchangeName).Type("direct"); + await exchangeSpec.DeclareAsync(); + + IPublisher publisher = await connection.PublisherBuilder().Exchange(exchangeName).Key(severity).BuildAsync(); + try + { + var amqpMessage = new AmqpMessage(Encoding.UTF8.GetBytes(message)); + PublishResult pr = await publisher.PublishAsync(amqpMessage); + switch (pr.Outcome.State) + { + case OutcomeState.Accepted: + break; + case OutcomeState.Released: + Console.Error.WriteLine($"Released message: {pr.Message.BodyAsString()}"); + Environment.Exit(1); + break; + case OutcomeState.Rejected: + Console.Error.WriteLine($"[Publisher] Message: {pr.Message.BodyAsString()} rejected with error: {pr.Outcome.Error}"); + Environment.Exit(1); + break; + default: + Console.Error.WriteLine($"Unexpected publish outcome: {pr.Outcome.State}"); + Environment.Exit(1); + break; + } + + Console.WriteLine($" [x] Sent '{severity}':'{message}'"); + } + finally + { + await publisher.CloseAsync(); + } +} +finally +{ + await connection.CloseAsync(); + await environment.CloseAsync(); +} + +static string GetSeverity(string[] strings) => strings.Length < 1 ? "info" : strings[0]; + +static string GetMessage(string[] strings) +{ + if (strings.Length < 2) + { + return "Hello World!"; + } + + return string.Join(" ", strings.Skip(1)); +} diff --git a/dotnet-amqp/EmitLogTopic/EmitLogTopic.csproj b/dotnet-amqp/EmitLogTopic/EmitLogTopic.csproj new file mode 100644 index 00000000..a0a7e254 --- /dev/null +++ b/dotnet-amqp/EmitLogTopic/EmitLogTopic.csproj @@ -0,0 +1,14 @@ + + + + Exe + net8.0;net10.0 + enable + enable + + + + + + + diff --git a/dotnet-amqp/EmitLogTopic/Program.cs b/dotnet-amqp/EmitLogTopic/Program.cs new file mode 100644 index 00000000..388717e9 --- /dev/null +++ b/dotnet-amqp/EmitLogTopic/Program.cs @@ -0,0 +1,71 @@ +using System.Text; +using RabbitMQ.AMQP.Client; +using RabbitMQ.AMQP.Client.Impl; + +const string brokerUri = "amqp://guest:guest@localhost:5672/%2f"; +const string exchangeName = "logs_topic"; + +string routingKey = GetRouting(args); +string message = GetMessage(args); + +ConnectionSettings settings = ConnectionSettingsBuilder.Create() + .Uri(new Uri(brokerUri)) + .ContainerId("tutorial-emitlogtopic") + .Build(); + +IEnvironment environment = AmqpEnvironment.Create(settings); +IConnection connection = await environment.CreateConnectionAsync(); + +try +{ + IManagement management = connection.Management(); + IExchangeSpecification exchangeSpec = management.Exchange(exchangeName).Type("topic"); + await exchangeSpec.DeclareAsync(); + + IPublisher publisher = await connection.PublisherBuilder().Exchange(exchangeName).Key(routingKey).BuildAsync(); + try + { + var amqpMessage = new AmqpMessage(Encoding.UTF8.GetBytes(message)); + PublishResult pr = await publisher.PublishAsync(amqpMessage); + switch (pr.Outcome.State) + { + case OutcomeState.Accepted: + break; + case OutcomeState.Released: + Console.Error.WriteLine($"Released message: {pr.Message.BodyAsString()}"); + Environment.Exit(1); + break; + case OutcomeState.Rejected: + Console.Error.WriteLine($"[Publisher] Message: {pr.Message.BodyAsString()} rejected with error: {pr.Outcome.Error}"); + Environment.Exit(1); + break; + default: + Console.Error.WriteLine($"Unexpected publish outcome: {pr.Outcome.State}"); + Environment.Exit(1); + break; + } + + Console.WriteLine($" [x] Sent '{routingKey}':'{message}'"); + } + finally + { + await publisher.CloseAsync(); + } +} +finally +{ + await connection.CloseAsync(); + await environment.CloseAsync(); +} + +static string GetRouting(string[] strings) => strings.Length < 1 ? "anonymous.info" : strings[0]; + +static string GetMessage(string[] strings) +{ + if (strings.Length < 2) + { + return "Hello World!"; + } + + return string.Join(" ", strings.Skip(1)); +} diff --git a/dotnet-amqp/NewTask/NewTask.csproj b/dotnet-amqp/NewTask/NewTask.csproj new file mode 100644 index 00000000..a0a7e254 --- /dev/null +++ b/dotnet-amqp/NewTask/NewTask.csproj @@ -0,0 +1,14 @@ + + + + Exe + net8.0;net10.0 + enable + enable + + + + + + + diff --git a/dotnet-amqp/NewTask/Program.cs b/dotnet-amqp/NewTask/Program.cs new file mode 100644 index 00000000..62b110a5 --- /dev/null +++ b/dotnet-amqp/NewTask/Program.cs @@ -0,0 +1,58 @@ +using System.Text; +using RabbitMQ.AMQP.Client; +using RabbitMQ.AMQP.Client.Impl; + +const string brokerUri = "amqp://guest:guest@localhost:5672/%2f"; +const string taskQueueName = "task_queue"; + +string message = args.Length > 0 ? string.Join(" ", args) : "Hello World!"; + +ConnectionSettings settings = ConnectionSettingsBuilder.Create() + .Uri(new Uri(brokerUri)) + .ContainerId("tutorial-newtask") + .Build(); + +IEnvironment environment = AmqpEnvironment.Create(settings); +IConnection connection = await environment.CreateConnectionAsync(); + +try +{ + IManagement management = connection.Management(); + IQueueSpecification queueSpec = management.Queue(taskQueueName).Type(QueueType.QUORUM); + await queueSpec.DeclareAsync(); + + IPublisher publisher = await connection.PublisherBuilder().Queue(taskQueueName).BuildAsync(); + try + { + var amqpMessage = new AmqpMessage(Encoding.UTF8.GetBytes(message)); + PublishResult pr = await publisher.PublishAsync(amqpMessage); + switch (pr.Outcome.State) + { + case OutcomeState.Accepted: + break; + case OutcomeState.Released: + Console.Error.WriteLine($"Released message: {pr.Message.BodyAsString()}"); + Environment.Exit(1); + break; + case OutcomeState.Rejected: + Console.Error.WriteLine($"[Publisher] Message: {pr.Message.BodyAsString()} rejected with error: {pr.Outcome.Error}"); + Environment.Exit(1); + break; + default: + Console.Error.WriteLine($"Unexpected publish outcome: {pr.Outcome.State}"); + Environment.Exit(1); + break; + } + + Console.WriteLine($" [x] Sent '{message}'"); + } + finally + { + await publisher.CloseAsync(); + } +} +finally +{ + await connection.CloseAsync(); + await environment.CloseAsync(); +} diff --git a/dotnet-amqp/PublisherConfirms/Program.cs b/dotnet-amqp/PublisherConfirms/Program.cs new file mode 100644 index 00000000..b7d1aa57 --- /dev/null +++ b/dotnet-amqp/PublisherConfirms/Program.cs @@ -0,0 +1,76 @@ +using System.Text; +using RabbitMQ.AMQP.Client; +using RabbitMQ.AMQP.Client.Impl; + +const string brokerUri = "amqp://guest:guest@localhost:5672/%2f"; + +ConnectionSettings settings = ConnectionSettingsBuilder.Create() + .Uri(new Uri(brokerUri)) + .ContainerId("tutorial-publisherconfirms") + .Build(); + +IEnvironment environment = AmqpEnvironment.Create(settings); +IConnection connection = await environment.CreateConnectionAsync(); + +try +{ + IManagement management = connection.Management(); + string queueName = Guid.NewGuid().ToString(); + IQueueSpecification queueSpec = management.Queue(queueName).Exclusive(true).AutoDelete(true); + await queueSpec.DeclareAsync(); + + IConsumer consumer = await connection.ConsumerBuilder() + .Queue(queueName) + .MessageHandler((ctx, message) => + { + Console.WriteLine($"Received a message: {message.BodyAsString()}"); + ctx.Accept(); + return Task.CompletedTask; + }) + .BuildAndStartAsync(); + + try + { + await Task.Delay(300); + + IPublisher publisher = await connection.PublisherBuilder().Queue(queueName).BuildAsync(); + try + { + const string text = "hello"; + PublishResult pr = await publisher.PublishAsync(new AmqpMessage(Encoding.UTF8.GetBytes(text))); + switch (pr.Outcome.State) + { + case OutcomeState.Accepted: + Console.WriteLine($" Accepted Message: {pr.Message.BodyAsString()} confirmed"); + break; + case OutcomeState.Released: + Console.Error.WriteLine($"Released message: {pr.Message.BodyAsString()}"); + Environment.Exit(1); + break; + case OutcomeState.Rejected: + Console.Error.WriteLine($"[Publisher] Message: {pr.Message.BodyAsString()} rejected with error: {pr.Outcome.Error}"); + Environment.Exit(1); + break; + default: + Console.Error.WriteLine($"Unexpected publish outcome: {pr.Outcome.State}"); + Environment.Exit(1); + break; + } + + await Task.Delay(500); + } + finally + { + await publisher.CloseAsync(); + } + } + finally + { + await consumer.CloseAsync(); + } +} +finally +{ + await connection.CloseAsync(); + await environment.CloseAsync(); +} diff --git a/dotnet-amqp/PublisherConfirms/PublisherConfirms.csproj b/dotnet-amqp/PublisherConfirms/PublisherConfirms.csproj new file mode 100644 index 00000000..a0a7e254 --- /dev/null +++ b/dotnet-amqp/PublisherConfirms/PublisherConfirms.csproj @@ -0,0 +1,14 @@ + + + + Exe + net8.0;net10.0 + enable + enable + + + + + + + diff --git a/dotnet-amqp/README.md b/dotnet-amqp/README.md new file mode 100644 index 00000000..f4aa527e --- /dev/null +++ b/dotnet-amqp/README.md @@ -0,0 +1,96 @@ +# Dotnet C# code for RabbitMQ tutorials (AMQP 1.0) + +Here you can find C# examples from the [RabbitMQ tutorials](https://www.rabbitmq.com/getstarted.html), using the [RabbitMQ AMQP 1.0 .NET client](https://github.com/rabbitmq/rabbitmq-amqp-dotnet-client) (`RabbitMQ.AMQP.Client` on NuGet) for RabbitMQ 4.x. + +You need a RabbitMQ node running locally. The client requires **RabbitMQ 4.0 or newer** with AMQP 1.0 on port **5672**. The examples use the default `guest` user. See the [AMQP 1.0 client libraries overview](https://www.rabbitmq.com/client-libraries/amqp-client-libraries) and [AMQP in RabbitMQ](https://www.rabbitmq.com/docs/amqp). + +There is a solution file for Visual Studio 2022 (`dotnet-amqp.sln`). From this directory, use `dotnet run -f net8.0 --project` as below. + +End-to-end smoke tests (broker on `localhost`, management plugin used to reset `hello` / `task_queue` when needed): + +```bash +./test-tutorials.sh +``` + +## Requirements + +- [.NET 8 or .NET 10 SDK](https://dotnet.microsoft.com/download) (projects target `net8.0` and `net10.0`; use `-f net10.0` instead of `-f net8.0` in the commands below if you prefer) +- NuGet package: `RabbitMQ.AMQP.Client` (see each `.csproj`; restore runs automatically with `dotnet run` or `dotnet build`) + +## Code + +Run each example from the `dotnet-amqp` directory. + +#### Tutorial one: "Hello World!" + +```bash +dotnet run -f net8.0 --project Receive/Receive.csproj +dotnet run -f net8.0 --project Send/Send.csproj +``` + +#### Tutorial two: Work Queues + +```bash +dotnet run -f net8.0 --project Worker/Worker.csproj +dotnet run -f net8.0 --project Worker/Worker.csproj +dotnet run -f net8.0 --project NewTask/NewTask.csproj "First Message" +dotnet run -f net8.0 --project NewTask/NewTask.csproj "Second Message" +dotnet run -f net8.0 --project NewTask/NewTask.csproj "Third Message" +dotnet run -f net8.0 --project NewTask/NewTask.csproj "Fourth Message" +dotnet run -f net8.0 --project NewTask/NewTask.csproj "Fifth Message" +``` + +#### Tutorial three: Publish/Subscribe + +```bash +dotnet run -f net8.0 --project ReceiveLogs/ReceiveLogs.csproj +dotnet run -f net8.0 --project ReceiveLogs/ReceiveLogs.csproj +dotnet run -f net8.0 --project EmitLog/EmitLog.csproj +``` + +#### Tutorial four: Routing + +```bash +dotnet run -f net8.0 --project ReceiveLogsDirect/ReceiveLogsDirect.csproj warning error +dotnet run -f net8.0 --project ReceiveLogsDirect/ReceiveLogsDirect.csproj info warning error +dotnet run -f net8.0 --project EmitLogDirect/EmitLogDirect.csproj info "Run. Run. Or it will explode." +dotnet run -f net8.0 --project EmitLogDirect/EmitLogDirect.csproj warning "Run. Run. Or it will explode." +dotnet run -f net8.0 --project EmitLogDirect/EmitLogDirect.csproj error "Run. Run. Or it will explode." +``` + +#### Tutorial five: Topics + +```bash +dotnet run -f net8.0 --project ReceiveLogsTopic/ReceiveLogsTopic.csproj "#" +dotnet run -f net8.0 --project ReceiveLogsTopic/ReceiveLogsTopic.csproj "kern.*" +dotnet run -f net8.0 --project ReceiveLogsTopic/ReceiveLogsTopic.csproj "*.critical" +dotnet run -f net8.0 --project ReceiveLogsTopic/ReceiveLogsTopic.csproj "kern.*" "*.critical" +dotnet run -f net8.0 --project EmitLogTopic/EmitLogTopic.csproj kern.critical "A critical kernel error" +``` + +#### Tutorial six: RPC + +```bash +dotnet run -f net8.0 --project RPCServer/RPCServer.csproj +dotnet run -f net8.0 --project RPCClient/RPCClient.csproj +``` + +The client requests Fibonacci numbers for `0` through `31`, matching the Java AMQP 1.0 tutorial client. + +#### Publisher confirms (AMQP 1.0 publish outcomes) + +```bash +dotnet run -f net8.0 --project PublisherConfirms/PublisherConfirms.csproj +``` + +#### AMQP 1.0 Direct Reply-To RPC + +On RabbitMQ **4.2 and newer**, the .NET `Requester` uses [Direct Reply-To](https://www.rabbitmq.com/docs/direct-reply-to) when no explicit reply queue is set (see the client’s requester implementation). Older brokers fall back to a temporary reply queue. + +```bash +dotnet run -f net8.0 --project RpcAmqp10/RpcAmqp10.csproj +``` + +This sample runs a responder and a requester in one process; press CTRL+C to exit. + +To learn more, see the [API documentation](https://rabbitmq.github.io/rabbitmq-amqp-dotnet-client/api/RabbitMQ.AMQP.Client.html). diff --git a/dotnet-amqp/RPCClient/Program.cs b/dotnet-amqp/RPCClient/Program.cs new file mode 100644 index 00000000..0825cdde --- /dev/null +++ b/dotnet-amqp/RPCClient/Program.cs @@ -0,0 +1,44 @@ +using System.Text; +using RabbitMQ.AMQP.Client; +using RabbitMQ.AMQP.Client.Impl; + +const string brokerUri = "amqp://guest:guest@localhost:5672/%2f"; + +ConnectionSettings settings = ConnectionSettingsBuilder.Create() + .Uri(new Uri(brokerUri)) + .ContainerId("tutorial-rpcclient") + .Build(); + +IEnvironment environment = AmqpEnvironment.Create(settings); +IConnection connection = await environment.CreateConnectionAsync(); + +try +{ + IRequester requester = await connection.RequesterBuilder() + .RequestAddress() + .Queue("rpc_queue") + .Requester() + .BuildAsync(); + + try + { + for (int i = 0; i < 32; i++) + { + string iStr = i.ToString(); + Console.WriteLine($" [x] Requesting fib({iStr})"); + IMessage request = new AmqpMessage(Encoding.UTF8.GetBytes(iStr)); + IMessage reply = await requester.PublishAsync(request); + string response = reply.BodyAsString(); + Console.WriteLine($" [.] Got '{response}'"); + } + } + finally + { + await requester.CloseAsync(); + } +} +finally +{ + await connection.CloseAsync(); + await environment.CloseAsync(); +} diff --git a/dotnet-amqp/RPCClient/RPCClient.csproj b/dotnet-amqp/RPCClient/RPCClient.csproj new file mode 100644 index 00000000..a0a7e254 --- /dev/null +++ b/dotnet-amqp/RPCClient/RPCClient.csproj @@ -0,0 +1,14 @@ + + + + Exe + net8.0;net10.0 + enable + enable + + + + + + + diff --git a/dotnet-amqp/RPCServer/Program.cs b/dotnet-amqp/RPCServer/Program.cs new file mode 100644 index 00000000..bac2b135 --- /dev/null +++ b/dotnet-amqp/RPCServer/Program.cs @@ -0,0 +1,83 @@ +using System.Text; +using RabbitMQ.AMQP.Client; +using RabbitMQ.AMQP.Client.Impl; + +const string brokerUri = "amqp://guest:guest@localhost:5672/%2f"; +const string rpcQueueName = "rpc_queue"; + +ConnectionSettings settings = ConnectionSettingsBuilder.Create() + .Uri(new Uri(brokerUri)) + .ContainerId("tutorial-rpcserver") + .Build(); + +IEnvironment environment = AmqpEnvironment.Create(settings); +IConnection connection = await environment.CreateConnectionAsync(); + +try +{ + IManagement management = connection.Management(); + IQueueSpecification queueSpec = management.Queue(rpcQueueName).Type(QueueType.QUORUM); + await queueSpec.DeclareAsync(); + await queueSpec.PurgeAsync(); + + Console.WriteLine(" [x] Awaiting RPC requests"); + + IResponder responder = await connection.ResponderBuilder() + .RequestQueue(rpcQueueName) + .Handler((ctx, request) => + { + string response = ""; + try + { + string message = request.BodyAsString(); + int n = int.Parse(message); + Console.WriteLine($" [.] fib({message})"); + response += Fib(n); + } + catch (Exception e) + { + Console.WriteLine($" [.] {e.Message}"); + } + + return Task.FromResult(ctx.Message(Encoding.UTF8.GetBytes(response))); + }) + .BuildAsync(); + + try + { + using var cts = new CancellationTokenSource(); + Console.CancelKeyPress += (_, e) => + { + e.Cancel = true; + cts.Cancel(); + }; + await Task.Delay(Timeout.Infinite, cts.Token); + } + catch (OperationCanceledException) + { + } + finally + { + await responder.CloseAsync(); + } +} +finally +{ + await connection.CloseAsync(); + await environment.CloseAsync(); +} + +static int Fib(int n) +{ + if (n == 0) + { + return 0; + } + + if (n == 1) + { + return 1; + } + + return Fib(n - 1) + Fib(n - 2); +} diff --git a/dotnet-amqp/RPCServer/RPCServer.csproj b/dotnet-amqp/RPCServer/RPCServer.csproj new file mode 100644 index 00000000..a0a7e254 --- /dev/null +++ b/dotnet-amqp/RPCServer/RPCServer.csproj @@ -0,0 +1,14 @@ + + + + Exe + net8.0;net10.0 + enable + enable + + + + + + + diff --git a/dotnet-amqp/Receive/Program.cs b/dotnet-amqp/Receive/Program.cs new file mode 100644 index 00000000..c0012dd0 --- /dev/null +++ b/dotnet-amqp/Receive/Program.cs @@ -0,0 +1,53 @@ +using RabbitMQ.AMQP.Client; +using RabbitMQ.AMQP.Client.Impl; + +const string brokerUri = "amqp://guest:guest@localhost:5672/%2f"; + +ConnectionSettings settings = ConnectionSettingsBuilder.Create() + .Uri(new Uri(brokerUri)) + .ContainerId("tutorial-receive") + .Build(); + +IEnvironment environment = AmqpEnvironment.Create(settings); +IConnection connection = await environment.CreateConnectionAsync(); + +try +{ + IManagement management = connection.Management(); + IQueueSpecification queueSpec = management.Queue("hello").Type(QueueType.QUORUM); + await queueSpec.DeclareAsync(); + + IConsumer consumer = await connection.ConsumerBuilder() + .Queue("hello") + .MessageHandler((ctx, message) => + { + Console.WriteLine($"Received a message: {message.BodyAsString()}"); + ctx.Accept(); + return Task.CompletedTask; + }) + .BuildAndStartAsync(); + + try + { + Console.WriteLine(" [*] Waiting for messages. To exit press CTRL+C"); + using var cts = new CancellationTokenSource(); + Console.CancelKeyPress += (_, e) => + { + e.Cancel = true; + cts.Cancel(); + }; + await Task.Delay(Timeout.Infinite, cts.Token); + } + catch (OperationCanceledException) + { + } + finally + { + await consumer.CloseAsync(); + } +} +finally +{ + await connection.CloseAsync(); + await environment.CloseAsync(); +} diff --git a/dotnet-amqp/Receive/Receive.csproj b/dotnet-amqp/Receive/Receive.csproj new file mode 100644 index 00000000..a0a7e254 --- /dev/null +++ b/dotnet-amqp/Receive/Receive.csproj @@ -0,0 +1,14 @@ + + + + Exe + net8.0;net10.0 + enable + enable + + + + + + + diff --git a/dotnet-amqp/ReceiveLogs/Program.cs b/dotnet-amqp/ReceiveLogs/Program.cs new file mode 100644 index 00000000..60d1f0b2 --- /dev/null +++ b/dotnet-amqp/ReceiveLogs/Program.cs @@ -0,0 +1,65 @@ +using RabbitMQ.AMQP.Client; +using RabbitMQ.AMQP.Client.Impl; + +const string brokerUri = "amqp://guest:guest@localhost:5672/%2f"; +const string exchangeName = "logs"; + +ConnectionSettings settings = ConnectionSettingsBuilder.Create() + .Uri(new Uri(brokerUri)) + .ContainerId("tutorial-receivelogs") + .Build(); + +IEnvironment environment = AmqpEnvironment.Create(settings); +IConnection connection = await environment.CreateConnectionAsync(); + +try +{ + IManagement management = connection.Management(); + IExchangeSpecification exchangeSpec = management.Exchange(exchangeName).Type("fanout"); + await exchangeSpec.DeclareAsync(); + + IQueueSpecification tempQueue = management.Queue().Exclusive(true).AutoDelete(true); + IQueueInfo queueInfo = await tempQueue.DeclareAsync(); + string queueName = queueInfo.Name(); + + IBindingSpecification binding = management.Binding() + .SourceExchange(exchangeSpec) + .DestinationQueue(queueName) + .Key(string.Empty); + await binding.BindAsync(); + + IConsumer consumer = await connection.ConsumerBuilder() + .Queue(queueName) + .MessageHandler((ctx, message) => + { + string body = message.BodyAsString(); + Console.WriteLine($" [x] Received '{body}'"); + ctx.Accept(); + return Task.CompletedTask; + }) + .BuildAndStartAsync(); + + try + { + Console.WriteLine(" [*] Waiting for messages. To exit press CTRL+C"); + using var cts = new CancellationTokenSource(); + Console.CancelKeyPress += (_, e) => + { + e.Cancel = true; + cts.Cancel(); + }; + await Task.Delay(Timeout.Infinite, cts.Token); + } + catch (OperationCanceledException) + { + } + finally + { + await consumer.CloseAsync(); + } +} +finally +{ + await connection.CloseAsync(); + await environment.CloseAsync(); +} diff --git a/dotnet-amqp/ReceiveLogs/ReceiveLogs.csproj b/dotnet-amqp/ReceiveLogs/ReceiveLogs.csproj new file mode 100644 index 00000000..a0a7e254 --- /dev/null +++ b/dotnet-amqp/ReceiveLogs/ReceiveLogs.csproj @@ -0,0 +1,14 @@ + + + + Exe + net8.0;net10.0 + enable + enable + + + + + + + diff --git a/dotnet-amqp/ReceiveLogsDirect/Program.cs b/dotnet-amqp/ReceiveLogsDirect/Program.cs new file mode 100644 index 00000000..fbafc3cb --- /dev/null +++ b/dotnet-amqp/ReceiveLogsDirect/Program.cs @@ -0,0 +1,86 @@ +using RabbitMQ.AMQP.Client; +using RabbitMQ.AMQP.Client.Impl; + +const string brokerUri = "amqp://guest:guest@localhost:5672/%2f"; +const string exchangeName = "logs_direct"; + +if (args.Length < 1) +{ + Console.Error.WriteLine("Usage: ReceiveLogsDirect [info] [warning] [error]"); + Environment.Exit(1); +} + +ConnectionSettings settings = ConnectionSettingsBuilder.Create() + .Uri(new Uri(brokerUri)) + .ContainerId("tutorial-receivelogsdirect") + .Build(); + +IEnvironment environment = AmqpEnvironment.Create(settings); +IConnection connection = await environment.CreateConnectionAsync(); + +try +{ + IManagement management = connection.Management(); + IExchangeSpecification exchangeSpec = management.Exchange(exchangeName).Type("direct"); + await exchangeSpec.DeclareAsync(); + + IQueueSpecification tempQueue = management.Queue().Exclusive(true).AutoDelete(true); + IQueueInfo queueInfo = await tempQueue.DeclareAsync(); + string queueName = queueInfo.Name(); + + foreach (string severity in args) + { + IBindingSpecification binding = management.Binding() + .SourceExchange(exchangeSpec) + .DestinationQueue(queueName) + .Key(severity); + await binding.BindAsync(); + } + + IConsumer consumer = await connection.ConsumerBuilder() + .Queue(queueName) + .MessageHandler((ctx, message) => + { + string body = message.BodyAsString(); + string routingKey = RoutingKey(message); + Console.WriteLine($" [x] Received '{routingKey}':'{body}'"); + ctx.Accept(); + return Task.CompletedTask; + }) + .BuildAndStartAsync(); + + try + { + Console.WriteLine(" [*] Waiting for messages. To exit press CTRL+C"); + using var cts = new CancellationTokenSource(); + Console.CancelKeyPress += (_, e) => + { + e.Cancel = true; + cts.Cancel(); + }; + await Task.Delay(Timeout.Infinite, cts.Token); + } + catch (OperationCanceledException) + { + } + finally + { + await consumer.CloseAsync(); + } +} +finally +{ + await connection.CloseAsync(); + await environment.CloseAsync(); +} + +static string RoutingKey(IMessage message) +{ + object? rk = message.Annotation("x-routing-key"); + if (rk != null) + { + return rk.ToString() ?? ""; + } + + return message.Subject() ?? ""; +} diff --git a/dotnet-amqp/ReceiveLogsDirect/ReceiveLogsDirect.csproj b/dotnet-amqp/ReceiveLogsDirect/ReceiveLogsDirect.csproj new file mode 100644 index 00000000..a0a7e254 --- /dev/null +++ b/dotnet-amqp/ReceiveLogsDirect/ReceiveLogsDirect.csproj @@ -0,0 +1,14 @@ + + + + Exe + net8.0;net10.0 + enable + enable + + + + + + + diff --git a/dotnet-amqp/ReceiveLogsTopic/Program.cs b/dotnet-amqp/ReceiveLogsTopic/Program.cs new file mode 100644 index 00000000..7a033a4b --- /dev/null +++ b/dotnet-amqp/ReceiveLogsTopic/Program.cs @@ -0,0 +1,86 @@ +using RabbitMQ.AMQP.Client; +using RabbitMQ.AMQP.Client.Impl; + +const string brokerUri = "amqp://guest:guest@localhost:5672/%2f"; +const string exchangeName = "logs_topic"; + +if (args.Length < 1) +{ + Console.Error.WriteLine("Usage: ReceiveLogsTopic [binding_key]..."); + Environment.Exit(1); +} + +ConnectionSettings settings = ConnectionSettingsBuilder.Create() + .Uri(new Uri(brokerUri)) + .ContainerId("tutorial-receivelogstopic") + .Build(); + +IEnvironment environment = AmqpEnvironment.Create(settings); +IConnection connection = await environment.CreateConnectionAsync(); + +try +{ + IManagement management = connection.Management(); + IExchangeSpecification exchangeSpec = management.Exchange(exchangeName).Type("topic"); + await exchangeSpec.DeclareAsync(); + + IQueueSpecification tempQueue = management.Queue().Exclusive(true).AutoDelete(true); + IQueueInfo queueInfo = await tempQueue.DeclareAsync(); + string queueName = queueInfo.Name(); + + foreach (string bindingKey in args) + { + IBindingSpecification binding = management.Binding() + .SourceExchange(exchangeSpec) + .DestinationQueue(queueName) + .Key(bindingKey); + await binding.BindAsync(); + } + + IConsumer consumer = await connection.ConsumerBuilder() + .Queue(queueName) + .MessageHandler((ctx, message) => + { + string body = message.BodyAsString(); + string routingKey = RoutingKey(message); + Console.WriteLine($" [x] Received '{routingKey}':'{body}'"); + ctx.Accept(); + return Task.CompletedTask; + }) + .BuildAndStartAsync(); + + try + { + Console.WriteLine(" [*] Waiting for messages. To exit press CTRL+C"); + using var cts = new CancellationTokenSource(); + Console.CancelKeyPress += (_, e) => + { + e.Cancel = true; + cts.Cancel(); + }; + await Task.Delay(Timeout.Infinite, cts.Token); + } + catch (OperationCanceledException) + { + } + finally + { + await consumer.CloseAsync(); + } +} +finally +{ + await connection.CloseAsync(); + await environment.CloseAsync(); +} + +static string RoutingKey(IMessage message) +{ + object? rk = message.Annotation("x-routing-key"); + if (rk != null) + { + return rk.ToString() ?? ""; + } + + return message.Subject() ?? ""; +} diff --git a/dotnet-amqp/ReceiveLogsTopic/ReceiveLogsTopic.csproj b/dotnet-amqp/ReceiveLogsTopic/ReceiveLogsTopic.csproj new file mode 100644 index 00000000..a0a7e254 --- /dev/null +++ b/dotnet-amqp/ReceiveLogsTopic/ReceiveLogsTopic.csproj @@ -0,0 +1,14 @@ + + + + Exe + net8.0;net10.0 + enable + enable + + + + + + + diff --git a/dotnet-amqp/RpcAmqp10/Program.cs b/dotnet-amqp/RpcAmqp10/Program.cs new file mode 100644 index 00000000..48fc7b21 --- /dev/null +++ b/dotnet-amqp/RpcAmqp10/Program.cs @@ -0,0 +1,162 @@ +using System.Text; +using RabbitMQ.AMQP.Client; +using RabbitMQ.AMQP.Client.Impl; + +const string brokerUri = "amqp://guest:guest@localhost:5672/%2f"; +const string requestQueue = "rpc-requests"; + +using var cts = new CancellationTokenSource(); +Console.CancelKeyPress += (_, e) => +{ + e.Cancel = true; + cts.Cancel(); +}; + +Console.WriteLine("Starting Direct Reply-To RPC example (RabbitMQ 4.2+ uses Direct Reply-To for the requester when no reply queue is set)."); +Console.WriteLine("Ensure RabbitMQ is running on localhost:5672."); + +Task serverTask = RunServerAsync(cts.Token); +await Task.Delay(500, CancellationToken.None); + +Task clientTask = RunClientAsync(cts.Token); + +try +{ + await Task.Delay(Timeout.Infinite, cts.Token); +} +catch (OperationCanceledException) +{ +} + +Console.WriteLine("Application shutting down..."); +await Task.WhenAll( + AwaitCancelled(serverTask), + AwaitCancelled(clientTask)); + +static async Task AwaitCancelled(Task task) +{ + try + { + await task; + } + catch (OperationCanceledException) + { + } +} + +async Task RunServerAsync(CancellationToken cancellationToken) +{ + ConnectionSettings settings = ConnectionSettingsBuilder.Create() + .Uri(new Uri(brokerUri)) + .ContainerId("rpc-amqp10-server") + .Build(); + + IEnvironment environment = AmqpEnvironment.Create(settings); + IConnection connection = await environment.CreateConnectionAsync(); + + try + { + IManagement management = connection.Management(); + IQueueSpecification queueSpec = management.Queue(requestQueue).Type(QueueType.QUORUM); + await queueSpec.DeclareAsync(); + + IResponder responder = await connection.ResponderBuilder() + .RequestQueue(requestQueue) + .Handler((ctx, request) => + { + string payload = request.BodyAsString(); + object? mid = request.MessageId(); + if (mid != null) + { + Console.WriteLine($"RPC Server: Received {payload} request (ID: {mid})"); + } + else + { + Console.WriteLine($"RPC Server: Received {payload} request"); + } + + return Task.FromResult(ctx.Message(Encoding.UTF8.GetBytes("pong"))); + }) + .BuildAsync(); + + try + { + Console.WriteLine("RPC Server: Started and listening for requests..."); + await Task.Delay(Timeout.Infinite, cancellationToken); + } + catch (OperationCanceledException) + { + Console.WriteLine("RPC Server: Shutting down..."); + } + finally + { + await responder.CloseAsync(); + } + } + finally + { + await connection.CloseAsync(); + await environment.CloseAsync(); + } +} + +async Task RunClientAsync(CancellationToken cancellationToken) +{ + ConnectionSettings settings = ConnectionSettingsBuilder.Create() + .Uri(new Uri(brokerUri)) + .ContainerId("rpc-amqp10-client") + .Build(); + + IEnvironment environment = AmqpEnvironment.Create(settings); + IConnection connection = await environment.CreateConnectionAsync(); + + try + { + IRequester requester = await connection.RequesterBuilder() + .RequestAddress() + .Queue(requestQueue) + .Requester() + .BuildAsync(); + + try + { + Console.WriteLine($"RPC Client: Reply address: {requester.GetReplyToQueue()}"); + Console.WriteLine("RPC Client: Started. Sending ping every second (CTRL+C to exit)."); + + int requestId = 0; + using var ticker = new PeriodicTimer(TimeSpan.FromSeconds(1)); + while (await ticker.WaitForNextTickAsync(cancellationToken)) + { + requestId++; + IMessage reply; + try + { + reply = await requester.PublishAsync(new AmqpMessage(Encoding.UTF8.GetBytes("ping")), + cancellationToken); + } + catch (Exception ex) + { + Console.WriteLine($"RPC Client: Error sending request: {ex.Message}"); + continue; + } + + string payload = reply.BodyAsString(); + Console.WriteLine($"RPC Client: Sent ping request ({requestId})"); + Console.WriteLine($"RPC Client: Received reply - {payload}"); + } + } + catch (OperationCanceledException) + { + Console.WriteLine("RPC Client: Shutting down..."); + } + finally + { + await requester.CloseAsync(); + } + } + finally + { + await connection.CloseAsync(); + await environment.CloseAsync(); + } +} diff --git a/dotnet-amqp/RpcAmqp10/RpcAmqp10.csproj b/dotnet-amqp/RpcAmqp10/RpcAmqp10.csproj new file mode 100644 index 00000000..a0a7e254 --- /dev/null +++ b/dotnet-amqp/RpcAmqp10/RpcAmqp10.csproj @@ -0,0 +1,14 @@ + + + + Exe + net8.0;net10.0 + enable + enable + + + + + + + diff --git a/dotnet-amqp/Send/Program.cs b/dotnet-amqp/Send/Program.cs new file mode 100644 index 00000000..0a00594e --- /dev/null +++ b/dotnet-amqp/Send/Program.cs @@ -0,0 +1,56 @@ +using System.Text; +using RabbitMQ.AMQP.Client; +using RabbitMQ.AMQP.Client.Impl; + +const string brokerUri = "amqp://guest:guest@localhost:5672/%2f"; + +ConnectionSettings settings = ConnectionSettingsBuilder.Create() + .Uri(new Uri(brokerUri)) + .ContainerId("tutorial-send") + .Build(); + +IEnvironment environment = AmqpEnvironment.Create(settings); +IConnection connection = await environment.CreateConnectionAsync(); + +try +{ + IManagement management = connection.Management(); + IQueueSpecification queueSpec = management.Queue("hello").Type(QueueType.QUORUM); + await queueSpec.DeclareAsync(); + + IPublisher publisher = await connection.PublisherBuilder().Queue("hello").BuildAsync(); + try + { + const string body = "Hello World!"; + var message = new AmqpMessage(Encoding.UTF8.GetBytes(body)); + PublishResult pr = await publisher.PublishAsync(message); + switch (pr.Outcome.State) + { + case OutcomeState.Accepted: + break; + case OutcomeState.Released: + Console.Error.WriteLine($"Released message: {pr.Message.BodyAsString()}"); + Environment.Exit(1); + break; + case OutcomeState.Rejected: + Console.Error.WriteLine($"[Publisher] Message: {pr.Message.BodyAsString()} rejected with error: {pr.Outcome.Error}"); + Environment.Exit(1); + break; + default: + Console.Error.WriteLine($"Unexpected publish outcome: {pr.Outcome.State}"); + Environment.Exit(1); + break; + } + + Console.WriteLine($" [x] Sent {body}"); + } + finally + { + await publisher.CloseAsync(); + } +} +finally +{ + await connection.CloseAsync(); + await environment.CloseAsync(); +} diff --git a/dotnet-amqp/Send/Send.csproj b/dotnet-amqp/Send/Send.csproj new file mode 100644 index 00000000..a0a7e254 --- /dev/null +++ b/dotnet-amqp/Send/Send.csproj @@ -0,0 +1,14 @@ + + + + Exe + net8.0;net10.0 + enable + enable + + + + + + + diff --git a/dotnet-amqp/Worker/Program.cs b/dotnet-amqp/Worker/Program.cs new file mode 100644 index 00000000..cbae24cd --- /dev/null +++ b/dotnet-amqp/Worker/Program.cs @@ -0,0 +1,71 @@ +using RabbitMQ.AMQP.Client; +using RabbitMQ.AMQP.Client.Impl; + +const string brokerUri = "amqp://guest:guest@localhost:5672/%2f"; +const string taskQueueName = "task_queue"; + +ConnectionSettings settings = ConnectionSettingsBuilder.Create() + .Uri(new Uri(brokerUri)) + .ContainerId("tutorial-worker") + .Build(); + +IEnvironment environment = AmqpEnvironment.Create(settings); +IConnection connection = await environment.CreateConnectionAsync(); + +try +{ + IManagement management = connection.Management(); + IQueueSpecification queueSpec = management.Queue(taskQueueName).Type(QueueType.QUORUM); + await queueSpec.DeclareAsync(); + + IConsumer consumer = await connection.ConsumerBuilder() + .Queue(taskQueueName) + .InitialCredits(1) + .MessageHandler((ctx, message) => + { + string body = message.BodyAsString(); + Console.WriteLine($" [x] Received '{body}'"); + try + { + DoWork(body); + } + finally + { + Console.WriteLine(" [x] Done"); + ctx.Accept(); + } + + return Task.CompletedTask; + }) + .BuildAndStartAsync(); + + try + { + Console.WriteLine(" [*] Waiting for messages. To exit press CTRL+C"); + using var cts = new CancellationTokenSource(); + Console.CancelKeyPress += (_, e) => + { + e.Cancel = true; + cts.Cancel(); + }; + await Task.Delay(Timeout.Infinite, cts.Token); + } + catch (OperationCanceledException) + { + } + finally + { + await consumer.CloseAsync(); + } +} +finally +{ + await connection.CloseAsync(); + await environment.CloseAsync(); +} + +static void DoWork(string task) +{ + int dots = task.ToCharArray().Count(ch => ch == '.'); + Thread.Sleep(dots * 1000); +} diff --git a/dotnet-amqp/Worker/Worker.csproj b/dotnet-amqp/Worker/Worker.csproj new file mode 100644 index 00000000..a0a7e254 --- /dev/null +++ b/dotnet-amqp/Worker/Worker.csproj @@ -0,0 +1,14 @@ + + + + Exe + net8.0;net10.0 + enable + enable + + + + + + + diff --git a/dotnet-amqp/dotnet-amqp.sln b/dotnet-amqp/dotnet-amqp.sln new file mode 100644 index 00000000..c04dc1c8 --- /dev/null +++ b/dotnet-amqp/dotnet-amqp.sln @@ -0,0 +1,223 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.0.31903.59 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Send", "Send\Send.csproj", "{43E0129B-2E72-4F72-81E3-7C330EF2CF47}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Receive", "Receive\Receive.csproj", "{E2EBD25D-57C5-41B8-8061-6ECF5E8179E4}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NewTask", "NewTask\NewTask.csproj", "{5B2CCD62-8809-4323-8285-362865708A74}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Worker", "Worker\Worker.csproj", "{26A2EC9E-6A41-481F-B062-AEC85A271EA3}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EmitLog", "EmitLog\EmitLog.csproj", "{CFA7D080-D6ED-4DAF-B3B0-E4894D0568CE}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ReceiveLogs", "ReceiveLogs\ReceiveLogs.csproj", "{8A6F8803-3BF3-42A5-B9A0-E2B6205C39B0}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EmitLogDirect", "EmitLogDirect\EmitLogDirect.csproj", "{256AE298-12EF-41E6-AC16-B92AE1038729}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ReceiveLogsDirect", "ReceiveLogsDirect\ReceiveLogsDirect.csproj", "{DB1CD74C-6816-4FB3-8E89-F481AFC272F9}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EmitLogTopic", "EmitLogTopic\EmitLogTopic.csproj", "{1E7B25AD-4D61-4B66-8F4C-2B1556D5CFB9}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ReceiveLogsTopic", "ReceiveLogsTopic\ReceiveLogsTopic.csproj", "{B2F3320F-FB70-41F0-8EB7-F1298E6AE652}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RPCServer", "RPCServer\RPCServer.csproj", "{C0C23D16-8819-4795-9CDA-B33E70A7D431}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RPCClient", "RPCClient\RPCClient.csproj", "{3B406A89-7605-4FCB-BF3A-BC097378FF73}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PublisherConfirms", "PublisherConfirms\PublisherConfirms.csproj", "{7DFF910C-284B-42F1-BDA2-00B4CC61719E}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RpcAmqp10", "RpcAmqp10\RpcAmqp10.csproj", "{FB9E3563-8EF8-4723-A9E0-798B1C1A4250}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{8F2A9C10-4E5B-4D7A-9C3E-1A2B3C4D5E6F}" + ProjectSection(SolutionItems) = preProject + .gitattributes = .gitattributes + .gitignore = .gitignore + README.md = README.md + EndProjectSection +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|Any CPU = Release|Any CPU + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {43E0129B-2E72-4F72-81E3-7C330EF2CF47}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {43E0129B-2E72-4F72-81E3-7C330EF2CF47}.Debug|Any CPU.Build.0 = Debug|Any CPU + {43E0129B-2E72-4F72-81E3-7C330EF2CF47}.Debug|x64.ActiveCfg = Debug|Any CPU + {43E0129B-2E72-4F72-81E3-7C330EF2CF47}.Debug|x64.Build.0 = Debug|Any CPU + {43E0129B-2E72-4F72-81E3-7C330EF2CF47}.Debug|x86.ActiveCfg = Debug|Any CPU + {43E0129B-2E72-4F72-81E3-7C330EF2CF47}.Debug|x86.Build.0 = Debug|Any CPU + {43E0129B-2E72-4F72-81E3-7C330EF2CF47}.Release|Any CPU.ActiveCfg = Release|Any CPU + {43E0129B-2E72-4F72-81E3-7C330EF2CF47}.Release|Any CPU.Build.0 = Release|Any CPU + {43E0129B-2E72-4F72-81E3-7C330EF2CF47}.Release|x64.ActiveCfg = Release|Any CPU + {43E0129B-2E72-4F72-81E3-7C330EF2CF47}.Release|x64.Build.0 = Release|Any CPU + {43E0129B-2E72-4F72-81E3-7C330EF2CF47}.Release|x86.ActiveCfg = Release|Any CPU + {43E0129B-2E72-4F72-81E3-7C330EF2CF47}.Release|x86.Build.0 = Release|Any CPU + {E2EBD25D-57C5-41B8-8061-6ECF5E8179E4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E2EBD25D-57C5-41B8-8061-6ECF5E8179E4}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E2EBD25D-57C5-41B8-8061-6ECF5E8179E4}.Debug|x64.ActiveCfg = Debug|Any CPU + {E2EBD25D-57C5-41B8-8061-6ECF5E8179E4}.Debug|x64.Build.0 = Debug|Any CPU + {E2EBD25D-57C5-41B8-8061-6ECF5E8179E4}.Debug|x86.ActiveCfg = Debug|Any CPU + {E2EBD25D-57C5-41B8-8061-6ECF5E8179E4}.Debug|x86.Build.0 = Debug|Any CPU + {E2EBD25D-57C5-41B8-8061-6ECF5E8179E4}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E2EBD25D-57C5-41B8-8061-6ECF5E8179E4}.Release|Any CPU.Build.0 = Release|Any CPU + {E2EBD25D-57C5-41B8-8061-6ECF5E8179E4}.Release|x64.ActiveCfg = Release|Any CPU + {E2EBD25D-57C5-41B8-8061-6ECF5E8179E4}.Release|x64.Build.0 = Release|Any CPU + {E2EBD25D-57C5-41B8-8061-6ECF5E8179E4}.Release|x86.ActiveCfg = Release|Any CPU + {E2EBD25D-57C5-41B8-8061-6ECF5E8179E4}.Release|x86.Build.0 = Release|Any CPU + {5B2CCD62-8809-4323-8285-362865708A74}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5B2CCD62-8809-4323-8285-362865708A74}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5B2CCD62-8809-4323-8285-362865708A74}.Debug|x64.ActiveCfg = Debug|Any CPU + {5B2CCD62-8809-4323-8285-362865708A74}.Debug|x64.Build.0 = Debug|Any CPU + {5B2CCD62-8809-4323-8285-362865708A74}.Debug|x86.ActiveCfg = Debug|Any CPU + {5B2CCD62-8809-4323-8285-362865708A74}.Debug|x86.Build.0 = Debug|Any CPU + {5B2CCD62-8809-4323-8285-362865708A74}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5B2CCD62-8809-4323-8285-362865708A74}.Release|Any CPU.Build.0 = Release|Any CPU + {5B2CCD62-8809-4323-8285-362865708A74}.Release|x64.ActiveCfg = Release|Any CPU + {5B2CCD62-8809-4323-8285-362865708A74}.Release|x64.Build.0 = Release|Any CPU + {5B2CCD62-8809-4323-8285-362865708A74}.Release|x86.ActiveCfg = Release|Any CPU + {5B2CCD62-8809-4323-8285-362865708A74}.Release|x86.Build.0 = Release|Any CPU + {26A2EC9E-6A41-481F-B062-AEC85A271EA3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {26A2EC9E-6A41-481F-B062-AEC85A271EA3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {26A2EC9E-6A41-481F-B062-AEC85A271EA3}.Debug|x64.ActiveCfg = Debug|Any CPU + {26A2EC9E-6A41-481F-B062-AEC85A271EA3}.Debug|x64.Build.0 = Debug|Any CPU + {26A2EC9E-6A41-481F-B062-AEC85A271EA3}.Debug|x86.ActiveCfg = Debug|Any CPU + {26A2EC9E-6A41-481F-B062-AEC85A271EA3}.Debug|x86.Build.0 = Debug|Any CPU + {26A2EC9E-6A41-481F-B062-AEC85A271EA3}.Release|Any CPU.ActiveCfg = Release|Any CPU + {26A2EC9E-6A41-481F-B062-AEC85A271EA3}.Release|Any CPU.Build.0 = Release|Any CPU + {26A2EC9E-6A41-481F-B062-AEC85A271EA3}.Release|x64.ActiveCfg = Release|Any CPU + {26A2EC9E-6A41-481F-B062-AEC85A271EA3}.Release|x64.Build.0 = Release|Any CPU + {26A2EC9E-6A41-481F-B062-AEC85A271EA3}.Release|x86.ActiveCfg = Release|Any CPU + {26A2EC9E-6A41-481F-B062-AEC85A271EA3}.Release|x86.Build.0 = Release|Any CPU + {CFA7D080-D6ED-4DAF-B3B0-E4894D0568CE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CFA7D080-D6ED-4DAF-B3B0-E4894D0568CE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CFA7D080-D6ED-4DAF-B3B0-E4894D0568CE}.Debug|x64.ActiveCfg = Debug|Any CPU + {CFA7D080-D6ED-4DAF-B3B0-E4894D0568CE}.Debug|x64.Build.0 = Debug|Any CPU + {CFA7D080-D6ED-4DAF-B3B0-E4894D0568CE}.Debug|x86.ActiveCfg = Debug|Any CPU + {CFA7D080-D6ED-4DAF-B3B0-E4894D0568CE}.Debug|x86.Build.0 = Debug|Any CPU + {CFA7D080-D6ED-4DAF-B3B0-E4894D0568CE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CFA7D080-D6ED-4DAF-B3B0-E4894D0568CE}.Release|Any CPU.Build.0 = Release|Any CPU + {CFA7D080-D6ED-4DAF-B3B0-E4894D0568CE}.Release|x64.ActiveCfg = Release|Any CPU + {CFA7D080-D6ED-4DAF-B3B0-E4894D0568CE}.Release|x64.Build.0 = Release|Any CPU + {CFA7D080-D6ED-4DAF-B3B0-E4894D0568CE}.Release|x86.ActiveCfg = Release|Any CPU + {CFA7D080-D6ED-4DAF-B3B0-E4894D0568CE}.Release|x86.Build.0 = Release|Any CPU + {8A6F8803-3BF3-42A5-B9A0-E2B6205C39B0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8A6F8803-3BF3-42A5-B9A0-E2B6205C39B0}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8A6F8803-3BF3-42A5-B9A0-E2B6205C39B0}.Debug|x64.ActiveCfg = Debug|Any CPU + {8A6F8803-3BF3-42A5-B9A0-E2B6205C39B0}.Debug|x64.Build.0 = Debug|Any CPU + {8A6F8803-3BF3-42A5-B9A0-E2B6205C39B0}.Debug|x86.ActiveCfg = Debug|Any CPU + {8A6F8803-3BF3-42A5-B9A0-E2B6205C39B0}.Debug|x86.Build.0 = Debug|Any CPU + {8A6F8803-3BF3-42A5-B9A0-E2B6205C39B0}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8A6F8803-3BF3-42A5-B9A0-E2B6205C39B0}.Release|Any CPU.Build.0 = Release|Any CPU + {8A6F8803-3BF3-42A5-B9A0-E2B6205C39B0}.Release|x64.ActiveCfg = Release|Any CPU + {8A6F8803-3BF3-42A5-B9A0-E2B6205C39B0}.Release|x64.Build.0 = Release|Any CPU + {8A6F8803-3BF3-42A5-B9A0-E2B6205C39B0}.Release|x86.ActiveCfg = Release|Any CPU + {8A6F8803-3BF3-42A5-B9A0-E2B6205C39B0}.Release|x86.Build.0 = Release|Any CPU + {256AE298-12EF-41E6-AC16-B92AE1038729}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {256AE298-12EF-41E6-AC16-B92AE1038729}.Debug|Any CPU.Build.0 = Debug|Any CPU + {256AE298-12EF-41E6-AC16-B92AE1038729}.Debug|x64.ActiveCfg = Debug|Any CPU + {256AE298-12EF-41E6-AC16-B92AE1038729}.Debug|x64.Build.0 = Debug|Any CPU + {256AE298-12EF-41E6-AC16-B92AE1038729}.Debug|x86.ActiveCfg = Debug|Any CPU + {256AE298-12EF-41E6-AC16-B92AE1038729}.Debug|x86.Build.0 = Debug|Any CPU + {256AE298-12EF-41E6-AC16-B92AE1038729}.Release|Any CPU.ActiveCfg = Release|Any CPU + {256AE298-12EF-41E6-AC16-B92AE1038729}.Release|Any CPU.Build.0 = Release|Any CPU + {256AE298-12EF-41E6-AC16-B92AE1038729}.Release|x64.ActiveCfg = Release|Any CPU + {256AE298-12EF-41E6-AC16-B92AE1038729}.Release|x64.Build.0 = Release|Any CPU + {256AE298-12EF-41E6-AC16-B92AE1038729}.Release|x86.ActiveCfg = Release|Any CPU + {256AE298-12EF-41E6-AC16-B92AE1038729}.Release|x86.Build.0 = Release|Any CPU + {DB1CD74C-6816-4FB3-8E89-F481AFC272F9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {DB1CD74C-6816-4FB3-8E89-F481AFC272F9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {DB1CD74C-6816-4FB3-8E89-F481AFC272F9}.Debug|x64.ActiveCfg = Debug|Any CPU + {DB1CD74C-6816-4FB3-8E89-F481AFC272F9}.Debug|x64.Build.0 = Debug|Any CPU + {DB1CD74C-6816-4FB3-8E89-F481AFC272F9}.Debug|x86.ActiveCfg = Debug|Any CPU + {DB1CD74C-6816-4FB3-8E89-F481AFC272F9}.Debug|x86.Build.0 = Debug|Any CPU + {DB1CD74C-6816-4FB3-8E89-F481AFC272F9}.Release|Any CPU.ActiveCfg = Release|Any CPU + {DB1CD74C-6816-4FB3-8E89-F481AFC272F9}.Release|Any CPU.Build.0 = Release|Any CPU + {DB1CD74C-6816-4FB3-8E89-F481AFC272F9}.Release|x64.ActiveCfg = Release|Any CPU + {DB1CD74C-6816-4FB3-8E89-F481AFC272F9}.Release|x64.Build.0 = Release|Any CPU + {DB1CD74C-6816-4FB3-8E89-F481AFC272F9}.Release|x86.ActiveCfg = Release|Any CPU + {DB1CD74C-6816-4FB3-8E89-F481AFC272F9}.Release|x86.Build.0 = Release|Any CPU + {1E7B25AD-4D61-4B66-8F4C-2B1556D5CFB9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1E7B25AD-4D61-4B66-8F4C-2B1556D5CFB9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1E7B25AD-4D61-4B66-8F4C-2B1556D5CFB9}.Debug|x64.ActiveCfg = Debug|Any CPU + {1E7B25AD-4D61-4B66-8F4C-2B1556D5CFB9}.Debug|x64.Build.0 = Debug|Any CPU + {1E7B25AD-4D61-4B66-8F4C-2B1556D5CFB9}.Debug|x86.ActiveCfg = Debug|Any CPU + {1E7B25AD-4D61-4B66-8F4C-2B1556D5CFB9}.Debug|x86.Build.0 = Debug|Any CPU + {1E7B25AD-4D61-4B66-8F4C-2B1556D5CFB9}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1E7B25AD-4D61-4B66-8F4C-2B1556D5CFB9}.Release|Any CPU.Build.0 = Release|Any CPU + {1E7B25AD-4D61-4B66-8F4C-2B1556D5CFB9}.Release|x64.ActiveCfg = Release|Any CPU + {1E7B25AD-4D61-4B66-8F4C-2B1556D5CFB9}.Release|x64.Build.0 = Release|Any CPU + {1E7B25AD-4D61-4B66-8F4C-2B1556D5CFB9}.Release|x86.ActiveCfg = Release|Any CPU + {1E7B25AD-4D61-4B66-8F4C-2B1556D5CFB9}.Release|x86.Build.0 = Release|Any CPU + {B2F3320F-FB70-41F0-8EB7-F1298E6AE652}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B2F3320F-FB70-41F0-8EB7-F1298E6AE652}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B2F3320F-FB70-41F0-8EB7-F1298E6AE652}.Debug|x64.ActiveCfg = Debug|Any CPU + {B2F3320F-FB70-41F0-8EB7-F1298E6AE652}.Debug|x64.Build.0 = Debug|Any CPU + {B2F3320F-FB70-41F0-8EB7-F1298E6AE652}.Debug|x86.ActiveCfg = Debug|Any CPU + {B2F3320F-FB70-41F0-8EB7-F1298E6AE652}.Debug|x86.Build.0 = Debug|Any CPU + {B2F3320F-FB70-41F0-8EB7-F1298E6AE652}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B2F3320F-FB70-41F0-8EB7-F1298E6AE652}.Release|Any CPU.Build.0 = Release|Any CPU + {B2F3320F-FB70-41F0-8EB7-F1298E6AE652}.Release|x64.ActiveCfg = Release|Any CPU + {B2F3320F-FB70-41F0-8EB7-F1298E6AE652}.Release|x64.Build.0 = Release|Any CPU + {B2F3320F-FB70-41F0-8EB7-F1298E6AE652}.Release|x86.ActiveCfg = Release|Any CPU + {B2F3320F-FB70-41F0-8EB7-F1298E6AE652}.Release|x86.Build.0 = Release|Any CPU + {C0C23D16-8819-4795-9CDA-B33E70A7D431}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C0C23D16-8819-4795-9CDA-B33E70A7D431}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C0C23D16-8819-4795-9CDA-B33E70A7D431}.Debug|x64.ActiveCfg = Debug|Any CPU + {C0C23D16-8819-4795-9CDA-B33E70A7D431}.Debug|x64.Build.0 = Debug|Any CPU + {C0C23D16-8819-4795-9CDA-B33E70A7D431}.Debug|x86.ActiveCfg = Debug|Any CPU + {C0C23D16-8819-4795-9CDA-B33E70A7D431}.Debug|x86.Build.0 = Debug|Any CPU + {C0C23D16-8819-4795-9CDA-B33E70A7D431}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C0C23D16-8819-4795-9CDA-B33E70A7D431}.Release|Any CPU.Build.0 = Release|Any CPU + {C0C23D16-8819-4795-9CDA-B33E70A7D431}.Release|x64.ActiveCfg = Release|Any CPU + {C0C23D16-8819-4795-9CDA-B33E70A7D431}.Release|x64.Build.0 = Release|Any CPU + {C0C23D16-8819-4795-9CDA-B33E70A7D431}.Release|x86.ActiveCfg = Release|Any CPU + {C0C23D16-8819-4795-9CDA-B33E70A7D431}.Release|x86.Build.0 = Release|Any CPU + {3B406A89-7605-4FCB-BF3A-BC097378FF73}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3B406A89-7605-4FCB-BF3A-BC097378FF73}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3B406A89-7605-4FCB-BF3A-BC097378FF73}.Debug|x64.ActiveCfg = Debug|Any CPU + {3B406A89-7605-4FCB-BF3A-BC097378FF73}.Debug|x64.Build.0 = Debug|Any CPU + {3B406A89-7605-4FCB-BF3A-BC097378FF73}.Debug|x86.ActiveCfg = Debug|Any CPU + {3B406A89-7605-4FCB-BF3A-BC097378FF73}.Debug|x86.Build.0 = Debug|Any CPU + {3B406A89-7605-4FCB-BF3A-BC097378FF73}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3B406A89-7605-4FCB-BF3A-BC097378FF73}.Release|Any CPU.Build.0 = Release|Any CPU + {3B406A89-7605-4FCB-BF3A-BC097378FF73}.Release|x64.ActiveCfg = Release|Any CPU + {3B406A89-7605-4FCB-BF3A-BC097378FF73}.Release|x64.Build.0 = Release|Any CPU + {3B406A89-7605-4FCB-BF3A-BC097378FF73}.Release|x86.ActiveCfg = Release|Any CPU + {3B406A89-7605-4FCB-BF3A-BC097378FF73}.Release|x86.Build.0 = Release|Any CPU + {7DFF910C-284B-42F1-BDA2-00B4CC61719E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7DFF910C-284B-42F1-BDA2-00B4CC61719E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7DFF910C-284B-42F1-BDA2-00B4CC61719E}.Debug|x64.ActiveCfg = Debug|Any CPU + {7DFF910C-284B-42F1-BDA2-00B4CC61719E}.Debug|x64.Build.0 = Debug|Any CPU + {7DFF910C-284B-42F1-BDA2-00B4CC61719E}.Debug|x86.ActiveCfg = Debug|Any CPU + {7DFF910C-284B-42F1-BDA2-00B4CC61719E}.Debug|x86.Build.0 = Debug|Any CPU + {7DFF910C-284B-42F1-BDA2-00B4CC61719E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7DFF910C-284B-42F1-BDA2-00B4CC61719E}.Release|Any CPU.Build.0 = Release|Any CPU + {7DFF910C-284B-42F1-BDA2-00B4CC61719E}.Release|x64.ActiveCfg = Release|Any CPU + {7DFF910C-284B-42F1-BDA2-00B4CC61719E}.Release|x64.Build.0 = Release|Any CPU + {7DFF910C-284B-42F1-BDA2-00B4CC61719E}.Release|x86.ActiveCfg = Release|Any CPU + {7DFF910C-284B-42F1-BDA2-00B4CC61719E}.Release|x86.Build.0 = Release|Any CPU + {FB9E3563-8EF8-4723-A9E0-798B1C1A4250}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {FB9E3563-8EF8-4723-A9E0-798B1C1A4250}.Debug|Any CPU.Build.0 = Debug|Any CPU + {FB9E3563-8EF8-4723-A9E0-798B1C1A4250}.Debug|x64.ActiveCfg = Debug|Any CPU + {FB9E3563-8EF8-4723-A9E0-798B1C1A4250}.Debug|x64.Build.0 = Debug|Any CPU + {FB9E3563-8EF8-4723-A9E0-798B1C1A4250}.Debug|x86.ActiveCfg = Debug|Any CPU + {FB9E3563-8EF8-4723-A9E0-798B1C1A4250}.Debug|x86.Build.0 = Debug|Any CPU + {FB9E3563-8EF8-4723-A9E0-798B1C1A4250}.Release|Any CPU.ActiveCfg = Release|Any CPU + {FB9E3563-8EF8-4723-A9E0-798B1C1A4250}.Release|Any CPU.Build.0 = Release|Any CPU + {FB9E3563-8EF8-4723-A9E0-798B1C1A4250}.Release|x64.ActiveCfg = Release|Any CPU + {FB9E3563-8EF8-4723-A9E0-798B1C1A4250}.Release|x64.Build.0 = Release|Any CPU + {FB9E3563-8EF8-4723-A9E0-798B1C1A4250}.Release|x86.ActiveCfg = Release|Any CPU + {FB9E3563-8EF8-4723-A9E0-798B1C1A4250}.Release|x86.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/dotnet-amqp/test-tutorials.sh b/dotnet-amqp/test-tutorials.sh new file mode 100755 index 00000000..8dde91ba --- /dev/null +++ b/dotnet-amqp/test-tutorials.sh @@ -0,0 +1,280 @@ +#!/usr/bin/env bash +# End-to-end smoke tests for dotnet-amqp tutorials. +# Requires: bash, dotnet 8+, RabbitMQ 4.x on localhost:5672 (guest/guest). +# Does not start RabbitMQ; ensure the broker is already running. +# +# Usage: +# ./dotnet-amqp/test-tutorials.sh +# cd dotnet-amqp && ./test-tutorials.sh +# +# Environment: +# SKIP_BROKER_CHECK=1 Skip TCP check on port 5672 (tests still need a broker) +# SKIP_MGMT_DELETE=1 Do not DELETE tutorial queues via management HTTP API before T1/T2 +# +# If tutorial queues (hello, task_queue) already have consumers from other processes, T1/T2 can +# fail until those queues are deleted or the other consumers stop. By default this script tries +# DELETE http://localhost:15672/api/queues/%2F/ (requires the management plugin). + +set -euo pipefail + +ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +cd "$ROOT" + +PASS=0 +FAIL=0 + +pass() { + printf 'ok\t%s\n' "$1" + PASS=$((PASS + 1)) +} + +fail() { + printf 'FAIL\t%s\n' "$1" >&2 + FAIL=$((FAIL + 1)) +} + +require_dotnet() { + command -v dotnet >/dev/null 2>&1 || { + echo "dotnet is not on PATH" >&2 + exit 1 + } +} + +check_broker() { + if command -v nc >/dev/null 2>&1; then + nc -z localhost 5672 2>/dev/null + return + fi + { echo >/dev/tcp/127.0.0.1/5672; } 2>/dev/null +} + +require_broker() { + if [[ "${SKIP_BROKER_CHECK:-}" == "1" ]]; then + return 0 + fi + if ! check_broker; then + echo "RabbitMQ does not appear to be reachable on localhost:5672" >&2 + echo "Start the broker or set SKIP_BROKER_CHECK=1 to skip this check." >&2 + exit 1 + fi +} + +build_once() { + dotnet build "$ROOT/dotnet-amqp.sln" -c Release -v minimal >/dev/null +} + +# Wait until log file contains a line (consumer ready, etc.). Handles slow `dotnet run` cold start. +wait_for_log() { + local file=$1 + local needle=$2 + local max_attempts=${3:-120} + local i=0 + while [[ $i -lt $max_attempts ]]; do + if [[ -f "$file" ]] && grep -qF -- "$needle" "$file" 2>/dev/null; then + return 0 + fi + sleep 0.25 + i=$((i + 1)) + done + return 1 +} + +# Remove a queue so stale consumers from other tutorial runs cannot steal messages (needs management plugin). +delete_queue_mgmt() { + local name=$1 + if [[ "${SKIP_MGMT_DELETE:-}" == "1" ]]; then + return 0 + fi + if ! command -v curl >/dev/null 2>&1; then + return 0 + fi + curl -sf -o /dev/null -u guest:guest -X DELETE "http://localhost:15672/api/queues/%2F/${name}" 2>/dev/null || true +} + +# dotnet run --project Dir/Dir.csproj -- args... +# Use line-buffered stdout when not attached to a TTY so logs capture "Received..." lines before kill. +runproj() { + local proj=$1 + shift + if command -v stdbuf >/dev/null 2>&1; then + stdbuf -oL -eL dotnet run --no-build -c Release -f net8.0 --project "$ROOT/$proj/$proj.csproj" -- "$@" + else + dotnet run --no-build -c Release -f net8.0 --project "$ROOT/$proj/$proj.csproj" -- "$@" + fi +} + +assert_file_contains() { + local file=$1 + local needle=$2 + local msg=$3 + if [[ -f "$file" ]] && grep -qF -- "$needle" "$file"; then + pass "$msg" + else + fail "$msg (log should contain: $needle)" + if [[ -f "$file" ]]; then + echo "--- $file (first 40 lines) ---" >&2 + head -n 40 "$file" >&2 + fi + fi +} + +# Run command in background; sleep $secs; kill. Captures stdout/stderr to $log. +run_timed() { + local log=$1 + local secs=$2 + shift 2 + : >"$log" + "$@" >"$log" 2>&1 & + local pid=$! + sleep "$secs" + kill "$pid" 2>/dev/null || true + wait "$pid" 2>/dev/null || true +} + +test_t1_hello() { + local log recv_log + log=$(mktemp) + recv_log=$(mktemp) + delete_queue_mgmt hello + runproj Receive >"$recv_log" 2>&1 & + local rpid=$! + wait_for_log "$recv_log" "Waiting for messages" || true + sleep 1 + runproj Send >"$log" 2>&1 + sleep 3 + kill "$rpid" 2>/dev/null || true + wait "$rpid" 2>/dev/null || true + assert_file_contains "$log" "Sent" "T1 send publishes" + assert_file_contains "$recv_log" "Hello World" "T1 receive gets message" + rm -f "$log" "$recv_log" +} + +test_t2_work_queues() { + local log recv_log + log=$(mktemp) + recv_log=$(mktemp) + delete_queue_mgmt task_queue + runproj Worker >"$recv_log" 2>&1 & + local rpid=$! + wait_for_log "$recv_log" "Waiting for messages" || true + sleep 1 + runproj NewTask "e2e.task.test" >"$log" 2>&1 + sleep 4 + kill "$rpid" 2>/dev/null || true + wait "$rpid" 2>/dev/null || true + assert_file_contains "$log" "Sent" "T2 NewTask publishes" + assert_file_contains "$recv_log" "e2e.task.test" "T2 Worker receives task" + rm -f "$log" "$recv_log" +} + +test_t3_pubsub() { + local log recv_log + log=$(mktemp) + recv_log=$(mktemp) + runproj ReceiveLogs >"$recv_log" 2>&1 & + local rpid=$! + wait_for_log "$recv_log" "Waiting for messages" || true + sleep 1 + runproj EmitLog "e2e" "pubsub" "hello" >"$log" 2>&1 + sleep 3 + kill "$rpid" 2>/dev/null || true + wait "$rpid" 2>/dev/null || true + assert_file_contains "$log" "Sent" "T3 EmitLog publishes" + assert_file_contains "$recv_log" "e2e pubsub hello" "T3 ReceiveLogs receives broadcast" + rm -f "$log" "$recv_log" +} + +test_t4_routing() { + local log recv_log + log=$(mktemp) + recv_log=$(mktemp) + runproj ReceiveLogsDirect warn >"$recv_log" 2>&1 & + local rpid=$! + wait_for_log "$recv_log" "Waiting for messages" || true + sleep 1 + runproj EmitLogDirect warn "e2e routing" >"$log" 2>&1 + sleep 3 + kill "$rpid" 2>/dev/null || true + wait "$rpid" 2>/dev/null || true + assert_file_contains "$log" "Sent" "T4 EmitLogDirect publishes" + assert_file_contains "$recv_log" "e2e routing" "T4 ReceiveLogsDirect gets routed message" + rm -f "$log" "$recv_log" +} + +test_t5_topics() { + local log recv_log + log=$(mktemp) + recv_log=$(mktemp) + runproj ReceiveLogsTopic "kern.*" >"$recv_log" 2>&1 & + local rpid=$! + wait_for_log "$recv_log" "Waiting for messages" || true + sleep 1 + runproj EmitLogTopic kern.info "e2e topic" >"$log" 2>&1 + sleep 3 + kill "$rpid" 2>/dev/null || true + wait "$rpid" 2>/dev/null || true + assert_file_contains "$log" "Sent" "T5 EmitLogTopic publishes" + assert_file_contains "$recv_log" "e2e topic" "T5 ReceiveLogsTopic matches binding" + rm -f "$log" "$recv_log" +} + +test_t6_rpc() { + local log srv_log + log=$(mktemp) + srv_log=$(mktemp) + runproj RPCServer >"$srv_log" 2>&1 & + local spid=$! + sleep 3 + if ! runproj RPCClient >"$log" 2>&1; then + kill "$spid" 2>/dev/null || true + wait "$spid" 2>/dev/null || true + fail "T6 RPCClient process failed" + rm -f "$log" "$srv_log" + return + fi + kill "$spid" 2>/dev/null || true + wait "$spid" 2>/dev/null || true + assert_file_contains "$log" "Requesting fib(10)" "T6 RPCClient sends request" + assert_file_contains "$log" "Got '55'" "T6 RPCClient gets fib(10)==55" + rm -f "$log" "$srv_log" +} + +test_publisher_confirms() { + local log + log=$(mktemp) + run_timed "$log" 6 runproj PublisherConfirms + assert_file_contains "$log" "Accepted Message" "PublisherConfirms sees accepted publish" + assert_file_contains "$log" "Received a message" "PublisherConfirms consumer sees message" + rm -f "$log" +} + +test_rpc_amqp10() { + local log + log=$(mktemp) + run_timed "$log" 8 runproj RpcAmqp10 + assert_file_contains "$log" "pong" "RpcAmqp10 ping/pong (Direct Reply-To)" + rm -f "$log" +} + +main() { + require_dotnet + require_broker + echo "Building dotnet-amqp (Release)..." + build_once + echo "Running dotnet-amqp tutorial smoke tests from $ROOT" + test_t1_hello + test_t2_work_queues + test_t3_pubsub + test_t4_routing + test_t5_topics + test_t6_rpc + test_publisher_confirms + test_rpc_amqp10 + echo "----" + echo "Passed: $PASS Failed: $FAIL" + if [[ "$FAIL" -gt 0 ]]; then + exit 1 + fi +} + +main "$@" diff --git a/dotnet-stream/.gitignore b/dotnet-stream/.gitignore new file mode 100644 index 00000000..7c5f7a3b --- /dev/null +++ b/dotnet-stream/.gitignore @@ -0,0 +1,113 @@ +## Misc files +*.bak +.DS_Store +.idea +InternalTrace* +[Ll]ocal.dist +[Ll]ocal.props +*.lock.json +nunit-agent* +*.pyc +*.VisualState.xml +.vscode + +## Misc directories +.fake/ +gensrc/ +.ionide/ +NuGet/ +tmp/ +.vscode/ + +################# +## Visual Studio +################# + +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. + +# User-specific files +*.suo +*.user +*.sln.docstates + +# Build results +[Dd]ebug/ +[Rr]elease/ +x64/ +build/ +[Bb]in/ +[Oo]bj/ +*.lock.json + +BenchmarkDotNet.Artifacts/* + +APIApproval.Approve.received.txt + +# Visual Studio 2015 cache/options directory +.vs/ + +# Visual Studio profiler +*.psess +*.vsp +*.vspx + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper + +# DotCover is a Code Coverage Tool +*.dotCover + +# NCrunch +*.ncrunch* +.*crunch*.local.xml + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# NuGet Packages Directory +packages/ +/packages + +# Windows Store app package directory +AppPackages/ + +# Others +sql/ +*.Cache +ClientBin/ +[Ss]tyle[Cc]op.* +~$* +*~ +*.dbmdl +*.[Pp]ublish.xml +*.pfx +*.publishsettings + +# Backup & report files from converting an old project file to a newer +# Visual Studio version. Backup files are not needed, because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm + +# Unit tests +projects/Unit*/TestResult.xml + +# Development scripts +*.pcap + +# Vim +.sw? +.*.sw? diff --git a/dotnet-stream/OffsetTrackingReceive/OffsetTrackingReceive.cs b/dotnet-stream/OffsetTrackingReceive/OffsetTrackingReceive.cs new file mode 100644 index 00000000..d567f87f --- /dev/null +++ b/dotnet-stream/OffsetTrackingReceive/OffsetTrackingReceive.cs @@ -0,0 +1,49 @@ +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using RabbitMQ.Stream.Client; +using RabbitMQ.Stream.Client.Reliable; + +var streamSystem = await StreamSystem.Create(new StreamSystemConfig()); + +var stream = "stream-offset-tracking-dotnet"; +await streamSystem.CreateStream(new StreamSpec(stream)); + +var consumerName = "offset-tracking-tutorial"; +IOffsetType offsetSpecification; +try { + ulong storedOffset = await streamSystem.QueryOffset(consumerName, stream).ConfigureAwait(false); + offsetSpecification = new OffsetTypeOffset(storedOffset + 1); +} catch (OffsetNotFoundException) { + offsetSpecification = new OffsetTypeFirst(); +} +ulong initialValue = UInt64.MaxValue; +ulong firstOffset = initialValue; +int messageCount = 0; +ulong lastOffset = initialValue; +var consumedCde = new CountdownEvent(1); +var consumer = await Consumer.Create(new ConsumerConfig(streamSystem, stream) +{ + OffsetSpec = offsetSpecification, + Reference = consumerName, + MessageHandler = async (_, consumer, context, message) => { + if (Interlocked.CompareExchange(ref firstOffset, context.Offset, initialValue) == initialValue) { + Console.WriteLine("First message received."); + } + if (Interlocked.Increment(ref messageCount) % 10 == 0) { + await consumer.StoreOffset(context.Offset).ConfigureAwait(false); + } + if ("marker".Equals(Encoding.UTF8.GetString(message.Data.Contents))) { + Interlocked.Exchange(ref lastOffset, context.Offset); + await consumer.StoreOffset(context.Offset).ConfigureAwait(false); + await consumer.Close(); + consumedCde.Signal(); + } + await Task.CompletedTask; + } +}); +Console.WriteLine("Started consuming..."); + +consumedCde.Wait(); +Console.WriteLine("Done consuming, first offset {0}, last offset {1}", firstOffset, lastOffset); +await streamSystem.Close(); diff --git a/dotnet-stream/OffsetTrackingReceive/OffsetTrackingReceive.csproj b/dotnet-stream/OffsetTrackingReceive/OffsetTrackingReceive.csproj new file mode 100644 index 00000000..f700c37a --- /dev/null +++ b/dotnet-stream/OffsetTrackingReceive/OffsetTrackingReceive.csproj @@ -0,0 +1,14 @@ + + + + Exe + net8.0 + enable + enable + + + + + + + diff --git a/dotnet-stream/OffsetTrackingSend/OffsetTrackingSend.cs b/dotnet-stream/OffsetTrackingSend/OffsetTrackingSend.cs new file mode 100644 index 00000000..9fe16d25 --- /dev/null +++ b/dotnet-stream/OffsetTrackingSend/OffsetTrackingSend.cs @@ -0,0 +1,31 @@ +using System.Text; +using System.Threading.Tasks; +using RabbitMQ.Stream.Client; +using RabbitMQ.Stream.Client.Reliable; + +var streamSystem = await StreamSystem.Create(new StreamSystemConfig()); + +var stream = "stream-offset-tracking-dotnet"; +await streamSystem.CreateStream(new StreamSpec(stream)); + +var messageCount = 100; +var confirmedCde = new CountdownEvent(messageCount); +var producer = await Producer.Create(new ProducerConfig(streamSystem, stream) { + ConfirmationHandler = async confirmation => { + if (confirmation.Status == ConfirmationStatus.Confirmed) { + confirmedCde.Signal(); + } + await Task.CompletedTask.ConfigureAwait(false); + } +}); + +Console.WriteLine("Publishing {0} messages...", messageCount); +for (int i = 0; i < messageCount; i++) { + var body = i == messageCount - 1 ? "marker" : "hello"; + await producer.Send(new Message(Encoding.UTF8.GetBytes(body))); +} + +confirmedCde.Wait(); +Console.WriteLine("Messages confirmed."); +await producer.Close(); +await streamSystem.Close(); diff --git a/dotnet-stream/OffsetTrackingSend/OffsetTrackingSend.csproj b/dotnet-stream/OffsetTrackingSend/OffsetTrackingSend.csproj new file mode 100644 index 00000000..f700c37a --- /dev/null +++ b/dotnet-stream/OffsetTrackingSend/OffsetTrackingSend.csproj @@ -0,0 +1,14 @@ + + + + Exe + net8.0 + enable + enable + + + + + + + diff --git a/dotnet-stream/README.md b/dotnet-stream/README.md new file mode 100644 index 00000000..8175f5d0 --- /dev/null +++ b/dotnet-stream/README.md @@ -0,0 +1,31 @@ +# Dotnet Stream C# code for RabbitMQ tutorials + +Here you can find the C# code examples for [RabbitMQ +tutorials](https://www.rabbitmq.com/getstarted.html) using .NET 8.0. + +To successfully use the examples you will need a running RabbitMQ server with the [stream plugin enabled](https://www.rabbitmq.com/docs/stream#enabling-plugin). + +See [First Application With RabbitMQ Streams](https://www.rabbitmq.com/blog/2021/07/19/rabbitmq-streams-first-application), [Stream plugin documentation](https://www.rabbitmq.com/docs/stream) and [how to preconfigure plugins](https://www.rabbitmq.com/docs/plugins#enabled-plugins-file). + +## Requirements + +### Requirements on Windows + +* [dotnet core](https://www.microsoft.com/net/core) + +We're using the command line (`Start` -> `Run cmd.exe`) to +compile and run the code. + +### Requirements on Linux + +* [dotnet core](https://www.microsoft.com/net/core) + +### Code + +Each command is best run in a separate console/terminal instance run from the root +of the tutorial directory. + +#### [Tutorial one: "Hello World!"](https://www.rabbitmq.com/tutorials/tutorial-one-dotnet-stream.html) + + dotnet run --project Receive/Receive.csproj + dotnet run --project Send/Send.csproj diff --git a/dotnet-stream/Receive/Receive.cs b/dotnet-stream/Receive/Receive.cs new file mode 100644 index 00000000..b65a75d1 --- /dev/null +++ b/dotnet-stream/Receive/Receive.cs @@ -0,0 +1,30 @@ +// See https://aka.ms/new-console-template for more information + +using System.Text; +using RabbitMQ.Stream.Client; +using RabbitMQ.Stream.Client.Reliable; + +var streamSystem = await StreamSystem.Create(new StreamSystemConfig()); + +await streamSystem.CreateStream(new StreamSpec("hello-stream") +{ + MaxLengthBytes = 5_000_000_000 +}); + + +var consumer = await Consumer.Create(new ConsumerConfig(streamSystem, "hello-stream") +{ + OffsetSpec = new OffsetTypeFirst(), + MessageHandler = async (stream, _, _, message) => + { + Console.WriteLine($"Stream: {stream} - " + + $"Received message: {Encoding.UTF8.GetString(message.Data.Contents)}"); + await Task.CompletedTask; + } +}); + +Console.WriteLine(" [x] Press any key to exit"); +Console.ReadKey(); + +await consumer.Close(); +await streamSystem.Close(); \ No newline at end of file diff --git a/dotnet-stream/Receive/Receive.csproj b/dotnet-stream/Receive/Receive.csproj new file mode 100644 index 00000000..cdeff191 --- /dev/null +++ b/dotnet-stream/Receive/Receive.csproj @@ -0,0 +1,15 @@ + + + + Exe + net8.0 + enable + enable + ReceiveHello + + + + + + + diff --git a/dotnet-stream/Send/Send.cs b/dotnet-stream/Send/Send.cs new file mode 100644 index 00000000..55617e3d --- /dev/null +++ b/dotnet-stream/Send/Send.cs @@ -0,0 +1,21 @@ +using System.Text; +using RabbitMQ.Stream.Client; +using RabbitMQ.Stream.Client.Reliable; + +var streamSystem = await StreamSystem.Create(new StreamSystemConfig()); + +await streamSystem.CreateStream(new StreamSpec("hello-stream") +{ + MaxLengthBytes = 5_000_000_000 +}); + +var producer = await Producer.Create(new ProducerConfig(streamSystem, "hello-stream")); + + +await producer.Send(new Message(Encoding.UTF8.GetBytes($"Hello, World"))); +Console.WriteLine(" [x] Sent 'Hello, World'"); + +Console.WriteLine(" [x] Press any key to exit"); +Console.ReadKey(); +await producer.Close(); +await streamSystem.Close(); \ No newline at end of file diff --git a/dotnet-stream/Send/Send.csproj b/dotnet-stream/Send/Send.csproj new file mode 100644 index 00000000..f1c5fe93 --- /dev/null +++ b/dotnet-stream/Send/Send.csproj @@ -0,0 +1,15 @@ + + + + Exe + net8.0 + enable + enable + SendHello + + + + + + + diff --git a/dotnet-stream/dotnet-stream.sln b/dotnet-stream/dotnet-stream.sln new file mode 100644 index 00000000..e1b05502 --- /dev/null +++ b/dotnet-stream/dotnet-stream.sln @@ -0,0 +1,22 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Send", "Send\Send.csproj", "{C23444A1-BCB9-46A6-8D6D-7783C03E2A2E}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Receive", "Receive\Receive.csproj", "{37F1D7F4-58B1-4A1B-BE76-178A9D7A91E8}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {C23444A1-BCB9-46A6-8D6D-7783C03E2A2E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C23444A1-BCB9-46A6-8D6D-7783C03E2A2E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C23444A1-BCB9-46A6-8D6D-7783C03E2A2E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C23444A1-BCB9-46A6-8D6D-7783C03E2A2E}.Release|Any CPU.Build.0 = Release|Any CPU + {37F1D7F4-58B1-4A1B-BE76-178A9D7A91E8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {37F1D7F4-58B1-4A1B-BE76-178A9D7A91E8}.Debug|Any CPU.Build.0 = Debug|Any CPU + {37F1D7F4-58B1-4A1B-BE76-178A9D7A91E8}.Release|Any CPU.ActiveCfg = Release|Any CPU + {37F1D7F4-58B1-4A1B-BE76-178A9D7A91E8}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection +EndGlobal diff --git a/dotnet-visual-studio/.gitignore b/dotnet-visual-studio/.gitignore deleted file mode 100644 index 0ca5cd49..00000000 --- a/dotnet-visual-studio/.gitignore +++ /dev/null @@ -1,199 +0,0 @@ -## Ignore Visual Studio temporary files, build results, and -## files generated by popular Visual Studio add-ons. - -# User-specific files -*.suo -*.user -*.userosscache -*.sln.docstates - -# User-specific files (MonoDevelop/Xamarin Studio) -*.userprefs - -# Build results -[Dd]ebug/ -[Dd]ebugPublic/ -[Rr]elease/ -[Rr]eleases/ -x64/ -x86/ -build/ -bld/ -[Bb]in/ -[Oo]bj/ - -# Visual Studo 2015 cache/options directory -.vs/ - -# MSTest test Results -[Tt]est[Rr]esult*/ -[Bb]uild[Ll]og.* - -# NUNIT -*.VisualState.xml -TestResult.xml - -# Build Results of an ATL Project -[Dd]ebugPS/ -[Rr]eleasePS/ -dlldata.c - -*_i.c -*_p.c -*_i.h -*.ilk -*.meta -*.obj -*.pch -*.pdb -*.pgc -*.pgd -*.rsp -*.sbr -*.tlb -*.tli -*.tlh -*.tmp -*.tmp_proj -*.log -*.vspscc -*.vssscc -.builds -*.pidb -*.svclog -*.scc - -# Chutzpah Test files -_Chutzpah* - -# Visual C++ cache files -ipch/ -*.aps -*.ncb -*.opensdf -*.sdf -*.cachefile - -# Visual Studio profiler -*.psess -*.vsp -*.vspx - -# TFS 2012 Local Workspace -$tf/ - -# Guidance Automation Toolkit -*.gpState - -# ReSharper is a .NET coding add-in -_ReSharper*/ -*.[Rr]e[Ss]harper -*.DotSettings.user - -# JustCode is a .NET coding addin-in -.JustCode - -# TeamCity is a build add-in -_TeamCity* - -# DotCover is a Code Coverage Tool -*.dotCover - -# NCrunch -_NCrunch_* -.*crunch*.local.xml - -# MightyMoose -*.mm.* -AutoTest.Net/ - -# Web workbench (sass) -.sass-cache/ - -# Installshield output folder -[Ee]xpress/ - -# DocProject is a documentation generator add-in -DocProject/buildhelp/ -DocProject/Help/*.HxT -DocProject/Help/*.HxC -DocProject/Help/*.hhc -DocProject/Help/*.hhk -DocProject/Help/*.hhp -DocProject/Help/Html2 -DocProject/Help/html - -# Click-Once directory -publish/ - -# Publish Web Output -*.[Pp]ublish.xml -*.azurePubxml -# TODO: Comment the next line if you want to checkin your web deploy settings -# but database connection strings (with potential passwords) will be unencrypted -*.pubxml -*.publishproj - -# NuGet Packages -*.nupkg -# The packages folder can be ignored because of Package Restore -**/packages/* -# except build/, which is used as an MSBuild target. -!**/packages/build/ -# Uncomment if necessary however generally it will be regenerated when needed -#!**/packages/repositories.config - -# Windows Azure Build Output -csx/ -*.build.csdef - -# Windows Store app package directory -AppPackages/ - -# Others -*.[Cc]ache -ClientBin/ -[Ss]tyle[Cc]op.* -~$* -*~ -*.dbmdl -*.dbproj.schemaview -*.pfx -*.publishsettings -node_modules/ -bower_components/ - -# RIA/Silverlight projects -Generated_Code/ - -# Backup & report files from converting an old project file -# to a newer Visual Studio version. Backup files are not needed, -# because we have git ;-) -_UpgradeReport_Files/ -Backup*/ -UpgradeLog*.XML -UpgradeLog*.htm - -# SQL Server files -*.mdf -*.ldf - -# Business Intelligence projects -*.rdl.data -*.bim.layout -*.bim_*.settings - -# Microsoft Fakes -FakesAssemblies/ - -# Node.js Tools for Visual Studio -.ntvs_analysis.dat - -# Visual Studio 6 build log -*.plg - -# Visual Studio 6 workspace options file -*.opt - -# Nuget -.nuget/ \ No newline at end of file diff --git a/dotnet-visual-studio/.nuget/NuGet.Config b/dotnet-visual-studio/.nuget/NuGet.Config deleted file mode 100644 index 67f8ea04..00000000 --- a/dotnet-visual-studio/.nuget/NuGet.Config +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/dotnet-visual-studio/.nuget/NuGet.exe b/dotnet-visual-studio/.nuget/NuGet.exe deleted file mode 100644 index 9ca66594..00000000 Binary files a/dotnet-visual-studio/.nuget/NuGet.exe and /dev/null differ diff --git a/dotnet-visual-studio/.nuget/NuGet.targets b/dotnet-visual-studio/.nuget/NuGet.targets deleted file mode 100644 index 045aa847..00000000 --- a/dotnet-visual-studio/.nuget/NuGet.targets +++ /dev/null @@ -1,136 +0,0 @@ - - - - $(MSBuildProjectDirectory)\..\ - - - false - - - false - - - true - - - true - - - - - - - - - - - $([System.IO.Path]::Combine($(SolutionDir), ".nuget")) - $([System.IO.Path]::Combine($(ProjectDir), "packages.config")) - - - - - $(SolutionDir).nuget - packages.config - - - - - $(NuGetToolsPath)\NuGet.exe - @(PackageSource) - - "$(NuGetExePath)" - mono --runtime=v4.0.30319 $(NuGetExePath) - - $(TargetDir.Trim('\\')) - - -RequireConsent - -NonInteractive - - "$(SolutionDir) " - "$(SolutionDir)" - - - $(NuGetCommand) install "$(PackagesConfig)" -source "$(PackageSources)" $(NonInteractiveSwitch) $(RequireConsentSwitch) -solutionDir $(PaddedSolutionDir) - $(NuGetCommand) pack "$(ProjectPath)" -Properties "Configuration=$(Configuration);Platform=$(Platform)" $(NonInteractiveSwitch) -OutputDirectory "$(PackageOutputDir)" -symbols - - - - RestorePackages; - $(BuildDependsOn); - - - - - $(BuildDependsOn); - BuildPackage; - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/dotnet-visual-studio/1_Receive/1_Receive.csproj b/dotnet-visual-studio/1_Receive/1_Receive.csproj deleted file mode 100644 index 516c7847..00000000 --- a/dotnet-visual-studio/1_Receive/1_Receive.csproj +++ /dev/null @@ -1,107 +0,0 @@ - - - - - Debug - AnyCPU - {DABA9504-8375-46DE-8349-A2DFCA9B1F2D} - Exe - Properties - _1_Receive - 1_Receive - v4.5.1 - 512 - true - publish\ - true - Disk - false - Foreground - 7 - Days - false - false - true - 0 - 1.0.0.%2a - false - false - true - ..\ - true - - - AnyCPU - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - - - AnyCPU - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - - - - ..\packages\Microsoft.Diagnostics.Tracing.EventSource.Redist.1.1.28\lib\net40\Microsoft.Diagnostics.Tracing.EventSource.dll - - - ..\packages\RabbitMQ.Client.5.0.1\lib\net451\RabbitMQ.Client.dll - - - - - - - - - - - - - - - - - - - - False - Microsoft .NET Framework 4.5.1 %28x86 and x64%29 - true - - - False - .NET Framework 3.5 SP1 Client Profile - false - - - False - .NET Framework 3.5 SP1 - false - - - - - - - This project references NuGet package(s) that are missing on this computer. Enable NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - - - - - \ No newline at end of file diff --git a/dotnet-visual-studio/1_Receive/App.config b/dotnet-visual-studio/1_Receive/App.config deleted file mode 100644 index 9c05822f..00000000 --- a/dotnet-visual-studio/1_Receive/App.config +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/dotnet-visual-studio/1_Receive/Program.cs b/dotnet-visual-studio/1_Receive/Program.cs deleted file mode 100644 index 608b3bf6..00000000 --- a/dotnet-visual-studio/1_Receive/Program.cs +++ /dev/null @@ -1,31 +0,0 @@ -using RabbitMQ.Client; -using RabbitMQ.Client.Events; -using System; -using System.Text; - -class Program -{ - public static void Main() - { - var factory = new ConnectionFactory() { HostName = "localhost" }; - using(var connection = factory.CreateConnection()) - using(var channel = connection.CreateModel()) - { - channel.QueueDeclare(queue: "hello", durable: false, exclusive: false, autoDelete: false, arguments: null); - - Console.WriteLine(" [*] Waiting for messages."); - - var consumer = new EventingBasicConsumer(channel); - consumer.Received += (model, ea) => - { - var body = ea.Body; - var message = Encoding.UTF8.GetString(body); - Console.WriteLine(" [x] Received {0}", message); - }; - channel.BasicConsume(queue: "hello", autoAck: true, consumer: consumer); - - Console.WriteLine(" Press [enter] to exit."); - Console.ReadLine(); - } - } -} diff --git a/dotnet-visual-studio/1_Receive/Properties/AssemblyInfo.cs b/dotnet-visual-studio/1_Receive/Properties/AssemblyInfo.cs deleted file mode 100644 index 26ef7fb6..00000000 --- a/dotnet-visual-studio/1_Receive/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle( "1_Receive" )] -[assembly: AssemblyDescription( "" )] -[assembly: AssemblyConfiguration( "" )] -[assembly: AssemblyCompany( "" )] -[assembly: AssemblyProduct( "1_Receive" )] -[assembly: AssemblyCopyright( "Copyright © 2015" )] -[assembly: AssemblyTrademark( "" )] -[assembly: AssemblyCulture( "" )] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible( false )] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid( "b2273483-b55d-4b82-aa7a-a4a0ba32d3e2" )] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion( "1.0.0.0" )] -[assembly: AssemblyFileVersion( "1.0.0.0" )] diff --git a/dotnet-visual-studio/1_Receive/packages.config b/dotnet-visual-studio/1_Receive/packages.config deleted file mode 100644 index 5b0c07d4..00000000 --- a/dotnet-visual-studio/1_Receive/packages.config +++ /dev/null @@ -1,5 +0,0 @@ - - - - - \ No newline at end of file diff --git a/dotnet-visual-studio/1_Send/1_Send.csproj b/dotnet-visual-studio/1_Send/1_Send.csproj deleted file mode 100644 index dd0506c9..00000000 --- a/dotnet-visual-studio/1_Send/1_Send.csproj +++ /dev/null @@ -1,75 +0,0 @@ - - - - - Debug - AnyCPU - {5FCA7642-F829-4AB9-BC2D-C5FDEFCA80BF} - Exe - Properties - _1_Send - 1_Send - v4.5.1 - 512 - true - ..\ - true - - - AnyCPU - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - - - AnyCPU - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - - - - ..\packages\Microsoft.Diagnostics.Tracing.EventSource.Redist.1.1.28\lib\net40\Microsoft.Diagnostics.Tracing.EventSource.dll - - - ..\packages\RabbitMQ.Client.5.0.1\lib\net451\RabbitMQ.Client.dll - - - - - - - - - - - - - - - - - - - - - - This project references NuGet package(s) that are missing on this computer. Enable NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - - - - - \ No newline at end of file diff --git a/dotnet-visual-studio/1_Send/App.config b/dotnet-visual-studio/1_Send/App.config deleted file mode 100644 index 9c05822f..00000000 --- a/dotnet-visual-studio/1_Send/App.config +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/dotnet-visual-studio/1_Send/Program.cs b/dotnet-visual-studio/1_Send/Program.cs deleted file mode 100644 index 11edd2cd..00000000 --- a/dotnet-visual-studio/1_Send/Program.cs +++ /dev/null @@ -1,25 +0,0 @@ -using System; -using RabbitMQ.Client; -using System.Text; - -class Program -{ - public static void Main() - { - var factory = new ConnectionFactory() { HostName = "localhost" }; - using(var connection = factory.CreateConnection()) - using(var channel = connection.CreateModel()) - { - channel.QueueDeclare(queue: "hello", durable: false, exclusive: false, autoDelete: false, arguments: null); - - string message = "Hello World!"; - var body = Encoding.UTF8.GetBytes(message); - - channel.BasicPublish(exchange: "", routingKey: "hello", basicProperties: null, body: body); - Console.WriteLine(" [x] Sent {0}", message); - } - - Console.WriteLine(" Press [enter] to exit."); - Console.ReadLine(); - } -} diff --git a/dotnet-visual-studio/1_Send/Properties/AssemblyInfo.cs b/dotnet-visual-studio/1_Send/Properties/AssemblyInfo.cs deleted file mode 100644 index e67f4bf4..00000000 --- a/dotnet-visual-studio/1_Send/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle( "1_Send" )] -[assembly: AssemblyDescription( "" )] -[assembly: AssemblyConfiguration( "" )] -[assembly: AssemblyCompany( "" )] -[assembly: AssemblyProduct( "1_Send" )] -[assembly: AssemblyCopyright( "Copyright © 2015" )] -[assembly: AssemblyTrademark( "" )] -[assembly: AssemblyCulture( "" )] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible( false )] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid( "ddc440db-d97d-4ff5-8987-359745a68cb2" )] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion( "1.0.0.0" )] -[assembly: AssemblyFileVersion( "1.0.0.0" )] diff --git a/dotnet-visual-studio/1_Send/packages.config b/dotnet-visual-studio/1_Send/packages.config deleted file mode 100644 index 5b0c07d4..00000000 --- a/dotnet-visual-studio/1_Send/packages.config +++ /dev/null @@ -1,5 +0,0 @@ - - - - - \ No newline at end of file diff --git a/dotnet-visual-studio/2_NewTask/2_NewTask.csproj b/dotnet-visual-studio/2_NewTask/2_NewTask.csproj deleted file mode 100644 index 1145afa5..00000000 --- a/dotnet-visual-studio/2_NewTask/2_NewTask.csproj +++ /dev/null @@ -1,75 +0,0 @@ - - - - - Debug - AnyCPU - {79410434-9BDD-49CE-A3EE-CE85526F317D} - Exe - Properties - _2_NewTask - 2_NewTask - v4.5.1 - 512 - true - ..\ - true - - - AnyCPU - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - - - AnyCPU - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - - - - ..\packages\Microsoft.Diagnostics.Tracing.EventSource.Redist.1.1.28\lib\net40\Microsoft.Diagnostics.Tracing.EventSource.dll - - - ..\packages\RabbitMQ.Client.5.0.1\lib\net451\RabbitMQ.Client.dll - - - - - - - - - - - - - - - - - - - - - - This project references NuGet package(s) that are missing on this computer. Enable NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - - - - - \ No newline at end of file diff --git a/dotnet-visual-studio/2_NewTask/App.config b/dotnet-visual-studio/2_NewTask/App.config deleted file mode 100644 index 9c05822f..00000000 --- a/dotnet-visual-studio/2_NewTask/App.config +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/dotnet-visual-studio/2_NewTask/Program.cs b/dotnet-visual-studio/2_NewTask/Program.cs deleted file mode 100644 index f3928ec9..00000000 --- a/dotnet-visual-studio/2_NewTask/Program.cs +++ /dev/null @@ -1,33 +0,0 @@ -using System; -using RabbitMQ.Client; -using System.Text; - -class Program -{ - public static void Main(string[] args) - { - var factory = new ConnectionFactory() { HostName = "localhost" }; - using(var connection = factory.CreateConnection()) - using(var channel = connection.CreateModel()) - { - channel.QueueDeclare(queue: "task_queue", durable: true, exclusive: false, autoDelete: false, arguments: null); - - var message = GetMessage(args); - var body = Encoding.UTF8.GetBytes(message); - - var properties = channel.CreateBasicProperties(); - properties.SetPersistent(true); - - channel.BasicPublish(exchange: "", routingKey: "task_queue", basicProperties: properties, body: body); - Console.WriteLine(" [x] Sent {0}", message); - } - - Console.WriteLine(" Press [enter] to exit."); - Console.ReadLine(); - } - - private static string GetMessage(string[] args) - { - return ((args.Length > 0) ? string.Join(" ", args) : "Hello World!"); - } -} diff --git a/dotnet-visual-studio/2_NewTask/Properties/AssemblyInfo.cs b/dotnet-visual-studio/2_NewTask/Properties/AssemblyInfo.cs deleted file mode 100644 index 08f59d14..00000000 --- a/dotnet-visual-studio/2_NewTask/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle( "2_NewTask" )] -[assembly: AssemblyDescription( "" )] -[assembly: AssemblyConfiguration( "" )] -[assembly: AssemblyCompany( "" )] -[assembly: AssemblyProduct( "2_NewTask" )] -[assembly: AssemblyCopyright( "Copyright © 2015" )] -[assembly: AssemblyTrademark( "" )] -[assembly: AssemblyCulture( "" )] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible( false )] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid( "5923d0e0-e014-4c1a-9a9d-8fdbdda83706" )] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion( "1.0.0.0" )] -[assembly: AssemblyFileVersion( "1.0.0.0" )] diff --git a/dotnet-visual-studio/2_NewTask/packages.config b/dotnet-visual-studio/2_NewTask/packages.config deleted file mode 100644 index 5b0c07d4..00000000 --- a/dotnet-visual-studio/2_NewTask/packages.config +++ /dev/null @@ -1,5 +0,0 @@ - - - - - \ No newline at end of file diff --git a/dotnet-visual-studio/2_Worker/2_Worker.csproj b/dotnet-visual-studio/2_Worker/2_Worker.csproj deleted file mode 100644 index 0bb9fe5e..00000000 --- a/dotnet-visual-studio/2_Worker/2_Worker.csproj +++ /dev/null @@ -1,75 +0,0 @@ - - - - - Debug - AnyCPU - {76C2AE27-9C4B-439B-B647-1CE709E59ABF} - Exe - Properties - _2_Worker - 2_Worker - v4.5.1 - 512 - true - ..\ - true - - - AnyCPU - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - - - AnyCPU - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - - - - ..\packages\Microsoft.Diagnostics.Tracing.EventSource.Redist.1.1.28\lib\net40\Microsoft.Diagnostics.Tracing.EventSource.dll - - - ..\packages\RabbitMQ.Client.5.0.1\lib\net451\RabbitMQ.Client.dll - - - - - - - - - - - - - - - - - - - - - - This project references NuGet package(s) that are missing on this computer. Enable NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - - - - - \ No newline at end of file diff --git a/dotnet-visual-studio/2_Worker/App.config b/dotnet-visual-studio/2_Worker/App.config deleted file mode 100644 index 9c05822f..00000000 --- a/dotnet-visual-studio/2_Worker/App.config +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/dotnet-visual-studio/2_Worker/Program.cs b/dotnet-visual-studio/2_Worker/Program.cs deleted file mode 100644 index 886799fa..00000000 --- a/dotnet-visual-studio/2_Worker/Program.cs +++ /dev/null @@ -1,41 +0,0 @@ -using System; -using RabbitMQ.Client; -using RabbitMQ.Client.Events; -using System.Text; -using System.Threading; - -class Program -{ - public static void Main() - { - var factory = new ConnectionFactory() { HostName = "localhost" }; - using(var connection = factory.CreateConnection()) - using(var channel = connection.CreateModel()) - { - channel.QueueDeclare(queue: "task_queue", durable: true, exclusive: false, autoDelete: false, arguments: null); - - channel.BasicQos(prefetchSize: 0, prefetchCount: 1, global: false); - - Console.WriteLine(" [*] Waiting for messages."); - - var consumer = new EventingBasicConsumer(channel); - consumer.Received += (model, ea) => - { - var body = ea.Body; - var message = Encoding.UTF8.GetString(body); - Console.WriteLine(" [x] Received {0}", message); - - int dots = message.Split('.').Length - 1; - Thread.Sleep(dots * 1000); - - Console.WriteLine(" [x] Done"); - - channel.BasicAck(deliveryTag: ea.DeliveryTag, multiple: false); - }; - channel.BasicConsume(queue: "task_queue", autoAck: false, consumer: consumer); - - Console.WriteLine(" Press [enter] to exit."); - Console.ReadLine(); - } - } -} diff --git a/dotnet-visual-studio/2_Worker/Properties/AssemblyInfo.cs b/dotnet-visual-studio/2_Worker/Properties/AssemblyInfo.cs deleted file mode 100644 index 3f031adf..00000000 --- a/dotnet-visual-studio/2_Worker/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle( "2_Worker" )] -[assembly: AssemblyDescription( "" )] -[assembly: AssemblyConfiguration( "" )] -[assembly: AssemblyCompany( "" )] -[assembly: AssemblyProduct( "2_Worker" )] -[assembly: AssemblyCopyright( "Copyright © 2015" )] -[assembly: AssemblyTrademark( "" )] -[assembly: AssemblyCulture( "" )] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible( false )] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid( "3bee6e98-7376-4d37-839a-6bde8b09bcce" )] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion( "1.0.0.0" )] -[assembly: AssemblyFileVersion( "1.0.0.0" )] diff --git a/dotnet-visual-studio/2_Worker/packages.config b/dotnet-visual-studio/2_Worker/packages.config deleted file mode 100644 index 5b0c07d4..00000000 --- a/dotnet-visual-studio/2_Worker/packages.config +++ /dev/null @@ -1,5 +0,0 @@ - - - - - \ No newline at end of file diff --git a/dotnet-visual-studio/3_EmitLog/3_EmitLog.csproj b/dotnet-visual-studio/3_EmitLog/3_EmitLog.csproj deleted file mode 100644 index af54e5ce..00000000 --- a/dotnet-visual-studio/3_EmitLog/3_EmitLog.csproj +++ /dev/null @@ -1,75 +0,0 @@ - - - - - Debug - AnyCPU - {EE47BA8A-4A5B-45F8-969A-C0DDCA667BF8} - Exe - Properties - _3_EmitLog - 3_EmitLog - v4.5.1 - 512 - true - ..\ - true - - - AnyCPU - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - - - AnyCPU - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - - - - ..\packages\Microsoft.Diagnostics.Tracing.EventSource.Redist.1.1.28\lib\net40\Microsoft.Diagnostics.Tracing.EventSource.dll - - - ..\packages\RabbitMQ.Client.5.0.1\lib\net451\RabbitMQ.Client.dll - - - - - - - - - - - - - - - - - - - - - - This project references NuGet package(s) that are missing on this computer. Enable NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - - - - - \ No newline at end of file diff --git a/dotnet-visual-studio/3_EmitLog/App.config b/dotnet-visual-studio/3_EmitLog/App.config deleted file mode 100644 index 9c05822f..00000000 --- a/dotnet-visual-studio/3_EmitLog/App.config +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/dotnet-visual-studio/3_EmitLog/Program.cs b/dotnet-visual-studio/3_EmitLog/Program.cs deleted file mode 100644 index 8e5a65e9..00000000 --- a/dotnet-visual-studio/3_EmitLog/Program.cs +++ /dev/null @@ -1,29 +0,0 @@ -using System; -using RabbitMQ.Client; -using System.Text; - -class Program -{ - public static void Main(string[] args) - { - var factory = new ConnectionFactory() { HostName = "localhost" }; - using(var connection = factory.CreateConnection()) - using(var channel = connection.CreateModel()) - { - channel.ExchangeDeclare(exchange: "logs", type: "fanout"); - - var message = GetMessage(args); - var body = Encoding.UTF8.GetBytes(message); - channel.BasicPublish(exchange: "logs", routingKey: "", basicProperties: null, body: body); - Console.WriteLine(" [x] Sent {0}", message); - } - - Console.WriteLine(" Press [enter] to exit."); - Console.ReadLine(); - } - - private static string GetMessage(string[] args) - { - return (( args.Length > 0) ? string.Join(" ", args) : "info: Hello World!"); - } -} diff --git a/dotnet-visual-studio/3_EmitLog/Properties/AssemblyInfo.cs b/dotnet-visual-studio/3_EmitLog/Properties/AssemblyInfo.cs deleted file mode 100644 index 4752a8fb..00000000 --- a/dotnet-visual-studio/3_EmitLog/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle( "3_EmitLog" )] -[assembly: AssemblyDescription( "" )] -[assembly: AssemblyConfiguration( "" )] -[assembly: AssemblyCompany( "" )] -[assembly: AssemblyProduct( "3_EmitLog" )] -[assembly: AssemblyCopyright( "Copyright © 2015" )] -[assembly: AssemblyTrademark( "" )] -[assembly: AssemblyCulture( "" )] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible( false )] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid( "e03f6b1b-3c75-48c1-abe1-b5283910dc8c" )] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion( "1.0.0.0" )] -[assembly: AssemblyFileVersion( "1.0.0.0" )] diff --git a/dotnet-visual-studio/3_EmitLog/packages.config b/dotnet-visual-studio/3_EmitLog/packages.config deleted file mode 100644 index 5b0c07d4..00000000 --- a/dotnet-visual-studio/3_EmitLog/packages.config +++ /dev/null @@ -1,5 +0,0 @@ - - - - - \ No newline at end of file diff --git a/dotnet-visual-studio/3_ReceiveLogs/3_ReceiveLogs.csproj b/dotnet-visual-studio/3_ReceiveLogs/3_ReceiveLogs.csproj deleted file mode 100644 index 5254070d..00000000 --- a/dotnet-visual-studio/3_ReceiveLogs/3_ReceiveLogs.csproj +++ /dev/null @@ -1,75 +0,0 @@ - - - - - Debug - AnyCPU - {164DB92B-2FE7-424C-8F79-EC23998F568B} - Exe - Properties - _3_ReceiveLogs - 3_ReceiveLogs - v4.5.1 - 512 - true - ..\ - true - - - AnyCPU - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - - - AnyCPU - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - - - - ..\packages\Microsoft.Diagnostics.Tracing.EventSource.Redist.1.1.28\lib\net40\Microsoft.Diagnostics.Tracing.EventSource.dll - - - ..\packages\RabbitMQ.Client.5.0.1\lib\net451\RabbitMQ.Client.dll - - - - - - - - - - - - - - - - - - - - - - This project references NuGet package(s) that are missing on this computer. Enable NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - - - - - \ No newline at end of file diff --git a/dotnet-visual-studio/3_ReceiveLogs/App.config b/dotnet-visual-studio/3_ReceiveLogs/App.config deleted file mode 100644 index 9c05822f..00000000 --- a/dotnet-visual-studio/3_ReceiveLogs/App.config +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/dotnet-visual-studio/3_ReceiveLogs/Program.cs b/dotnet-visual-studio/3_ReceiveLogs/Program.cs deleted file mode 100644 index b01ef1aa..00000000 --- a/dotnet-visual-studio/3_ReceiveLogs/Program.cs +++ /dev/null @@ -1,32 +0,0 @@ -using System; -using RabbitMQ.Client; -using RabbitMQ.Client.Events; -using System.Text; - -class Program -{ - public static void Main() - { - var factory = new ConnectionFactory() { HostName = "localhost" }; - using(var connection = factory.CreateConnection()) - using(var channel = connection.CreateModel()) - { - channel.ExchangeDeclare(exchange: "logs", type: "fanout"); - - var queueName = channel.QueueDeclare().QueueName; - channel.QueueBind(queue: queueName, exchange: "logs", routingKey: ""); - - var consumer = new EventingBasicConsumer(channel); - consumer.Received += (model, ea) => - { - var body = ea.Body; - var message = Encoding.UTF8.GetString(body); - Console.WriteLine(" [x] {0}", message); - }; - channel.BasicConsume(queue: queueName, autoAck: true, consumer: consumer); - - Console.WriteLine(" Press [enter] to exit."); - Console.ReadLine(); - } - } -} diff --git a/dotnet-visual-studio/3_ReceiveLogs/Properties/AssemblyInfo.cs b/dotnet-visual-studio/3_ReceiveLogs/Properties/AssemblyInfo.cs deleted file mode 100644 index 7186d9a7..00000000 --- a/dotnet-visual-studio/3_ReceiveLogs/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle( "3_ReceiveLogs" )] -[assembly: AssemblyDescription( "" )] -[assembly: AssemblyConfiguration( "" )] -[assembly: AssemblyCompany( "" )] -[assembly: AssemblyProduct( "3_ReceiveLogs" )] -[assembly: AssemblyCopyright( "Copyright © 2015" )] -[assembly: AssemblyTrademark( "" )] -[assembly: AssemblyCulture( "" )] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible( false )] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid( "789fd469-3b50-4bc0-93d5-2644e3fd7cff" )] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion( "1.0.0.0" )] -[assembly: AssemblyFileVersion( "1.0.0.0" )] diff --git a/dotnet-visual-studio/3_ReceiveLogs/packages.config b/dotnet-visual-studio/3_ReceiveLogs/packages.config deleted file mode 100644 index 5b0c07d4..00000000 --- a/dotnet-visual-studio/3_ReceiveLogs/packages.config +++ /dev/null @@ -1,5 +0,0 @@ - - - - - \ No newline at end of file diff --git a/dotnet-visual-studio/4_EmitLogDirect/4_EmitLogDirect.csproj b/dotnet-visual-studio/4_EmitLogDirect/4_EmitLogDirect.csproj deleted file mode 100644 index aaaed82f..00000000 --- a/dotnet-visual-studio/4_EmitLogDirect/4_EmitLogDirect.csproj +++ /dev/null @@ -1,75 +0,0 @@ - - - - - Debug - AnyCPU - {74E16495-2A01-4314-BE72-8ADAE64666BC} - Exe - Properties - _4_EmitLogDirect - 4_EmitLogDirect - v4.5.1 - 512 - true - ..\ - true - - - AnyCPU - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - - - AnyCPU - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - - - - ..\packages\Microsoft.Diagnostics.Tracing.EventSource.Redist.1.1.28\lib\net40\Microsoft.Diagnostics.Tracing.EventSource.dll - - - ..\packages\RabbitMQ.Client.5.0.1\lib\net451\RabbitMQ.Client.dll - - - - - - - - - - - - - - - - - - - - - - This project references NuGet package(s) that are missing on this computer. Enable NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - - - - - \ No newline at end of file diff --git a/dotnet-visual-studio/4_EmitLogDirect/App.config b/dotnet-visual-studio/4_EmitLogDirect/App.config deleted file mode 100644 index 9c05822f..00000000 --- a/dotnet-visual-studio/4_EmitLogDirect/App.config +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/dotnet-visual-studio/4_EmitLogDirect/Program.cs b/dotnet-visual-studio/4_EmitLogDirect/Program.cs deleted file mode 100644 index 8ef94594..00000000 --- a/dotnet-visual-studio/4_EmitLogDirect/Program.cs +++ /dev/null @@ -1,26 +0,0 @@ -using System; -using System.Linq; -using RabbitMQ.Client; -using System.Text; - -class Program -{ - public static void Main(string[] args) - { - var factory = new ConnectionFactory() { HostName = "localhost" }; - using(var connection = factory.CreateConnection()) - using(var channel = connection.CreateModel()) - { - channel.ExchangeDeclare(exchange: "direct_logs", type: "direct"); - - var severity = (args.Length > 0) ? args[0] : "info"; - var message = (args.Length > 1) ? string.Join(" ", args.Skip(1).ToArray()) : "Hello World!"; - var body = Encoding.UTF8.GetBytes(message); - channel.BasicPublish(exchange: "direct_logs", routingKey: severity, basicProperties: null, body: body); - Console.WriteLine(" [x] Sent '{0}':'{1}'", severity, message); - } - - Console.WriteLine(" Press [enter] to exit."); - Console.ReadLine(); - } -} diff --git a/dotnet-visual-studio/4_EmitLogDirect/Properties/AssemblyInfo.cs b/dotnet-visual-studio/4_EmitLogDirect/Properties/AssemblyInfo.cs deleted file mode 100644 index 5cfd13ba..00000000 --- a/dotnet-visual-studio/4_EmitLogDirect/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle( "4_EmitLogDirect" )] -[assembly: AssemblyDescription( "" )] -[assembly: AssemblyConfiguration( "" )] -[assembly: AssemblyCompany( "" )] -[assembly: AssemblyProduct( "4_EmitLogDirect" )] -[assembly: AssemblyCopyright( "Copyright © 2015" )] -[assembly: AssemblyTrademark( "" )] -[assembly: AssemblyCulture( "" )] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible( false )] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid( "64de668c-2d20-4a8f-9156-ac294d4af3fc" )] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion( "1.0.0.0" )] -[assembly: AssemblyFileVersion( "1.0.0.0" )] diff --git a/dotnet-visual-studio/4_EmitLogDirect/packages.config b/dotnet-visual-studio/4_EmitLogDirect/packages.config deleted file mode 100644 index 5b0c07d4..00000000 --- a/dotnet-visual-studio/4_EmitLogDirect/packages.config +++ /dev/null @@ -1,5 +0,0 @@ - - - - - \ No newline at end of file diff --git a/dotnet-visual-studio/4_ReceiveLogsDirect/4_ReceiveLogsDirect.csproj b/dotnet-visual-studio/4_ReceiveLogsDirect/4_ReceiveLogsDirect.csproj deleted file mode 100644 index 3ec0b58e..00000000 --- a/dotnet-visual-studio/4_ReceiveLogsDirect/4_ReceiveLogsDirect.csproj +++ /dev/null @@ -1,75 +0,0 @@ - - - - - Debug - AnyCPU - {2DE4DE5A-7377-4914-9E32-014D9BD1CD44} - Exe - Properties - _4_ReceiveLogsDirect - 4_ReceiveLogsDirect - v4.5.1 - 512 - true - ..\ - true - - - AnyCPU - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - - - AnyCPU - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - - - - ..\packages\Microsoft.Diagnostics.Tracing.EventSource.Redist.1.1.28\lib\net40\Microsoft.Diagnostics.Tracing.EventSource.dll - - - ..\packages\RabbitMQ.Client.5.0.1\lib\net451\RabbitMQ.Client.dll - - - - - - - - - - - - - - - - - - - - - - This project references NuGet package(s) that are missing on this computer. Enable NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - - - - - \ No newline at end of file diff --git a/dotnet-visual-studio/4_ReceiveLogsDirect/App.config b/dotnet-visual-studio/4_ReceiveLogsDirect/App.config deleted file mode 100644 index 9c05822f..00000000 --- a/dotnet-visual-studio/4_ReceiveLogsDirect/App.config +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/dotnet-visual-studio/4_ReceiveLogsDirect/Program.cs b/dotnet-visual-studio/4_ReceiveLogsDirect/Program.cs deleted file mode 100644 index c67cac1d..00000000 --- a/dotnet-visual-studio/4_ReceiveLogsDirect/Program.cs +++ /dev/null @@ -1,47 +0,0 @@ -using System; -using RabbitMQ.Client; -using RabbitMQ.Client.Events; -using System.Text; - -class Program -{ - public static void Main(string[] args) - { - var factory = new ConnectionFactory() { HostName = "localhost" }; - using(var connection = factory.CreateConnection()) - using(var channel = connection.CreateModel()) - { - channel.ExchangeDeclare(exchange: "direct_logs", type: "direct"); - var queueName = channel.QueueDeclare().QueueName; - - if(args.Length < 1) - { - Console.Error.WriteLine("Usage: {0} [info] [warning] [error]", Environment.GetCommandLineArgs()[0]); - Console.WriteLine(" Press [enter] to exit."); - Console.ReadLine(); - Environment.ExitCode = 1; - return; - } - - foreach(var severity in args) - { - channel.QueueBind(queue: queueName, exchange: "direct_logs", routingKey: severity); - } - - Console.WriteLine(" [*] Waiting for messages."); - - var consumer = new EventingBasicConsumer(channel); - consumer.Received += (model, ea) => - { - var body = ea.Body; - var message = Encoding.UTF8.GetString(body); - var routingKey = ea.RoutingKey; - Console.WriteLine(" [x] Received '{0}':'{1}'", routingKey, message); - }; - channel.BasicConsume(queue: queueName, autoAck: true, consumer: consumer); - - Console.WriteLine(" Press [enter] to exit."); - Console.ReadLine(); - } - } -} diff --git a/dotnet-visual-studio/4_ReceiveLogsDirect/Properties/AssemblyInfo.cs b/dotnet-visual-studio/4_ReceiveLogsDirect/Properties/AssemblyInfo.cs deleted file mode 100644 index 485a4139..00000000 --- a/dotnet-visual-studio/4_ReceiveLogsDirect/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle( "4_ReceiveLogsDirect" )] -[assembly: AssemblyDescription( "" )] -[assembly: AssemblyConfiguration( "" )] -[assembly: AssemblyCompany( "" )] -[assembly: AssemblyProduct( "4_ReceiveLogsDirect" )] -[assembly: AssemblyCopyright( "Copyright © 2015" )] -[assembly: AssemblyTrademark( "" )] -[assembly: AssemblyCulture( "" )] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible( false )] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid( "1dd3ed22-9811-423d-9414-98338ac8a07e" )] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion( "1.0.0.0" )] -[assembly: AssemblyFileVersion( "1.0.0.0" )] diff --git a/dotnet-visual-studio/4_ReceiveLogsDirect/packages.config b/dotnet-visual-studio/4_ReceiveLogsDirect/packages.config deleted file mode 100644 index 5b0c07d4..00000000 --- a/dotnet-visual-studio/4_ReceiveLogsDirect/packages.config +++ /dev/null @@ -1,5 +0,0 @@ - - - - - \ No newline at end of file diff --git a/dotnet-visual-studio/5_EmitLogTopic/5_EmitLogTopic.csproj b/dotnet-visual-studio/5_EmitLogTopic/5_EmitLogTopic.csproj deleted file mode 100644 index 62ede5f7..00000000 --- a/dotnet-visual-studio/5_EmitLogTopic/5_EmitLogTopic.csproj +++ /dev/null @@ -1,75 +0,0 @@ - - - - - Debug - AnyCPU - {86A66C9E-E479-4EA1-803D-E703F77581DC} - Exe - Properties - _5_EmitLogTopic - 5_EmitLogTopic - v4.5.1 - 512 - true - ..\ - true - - - AnyCPU - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - - - AnyCPU - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - - - - ..\packages\Microsoft.Diagnostics.Tracing.EventSource.Redist.1.1.28\lib\net40\Microsoft.Diagnostics.Tracing.EventSource.dll - - - ..\packages\RabbitMQ.Client.5.0.1\lib\net451\RabbitMQ.Client.dll - - - - - - - - - - - - - - - - - - - - - - This project references NuGet package(s) that are missing on this computer. Enable NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - - - - - \ No newline at end of file diff --git a/dotnet-visual-studio/5_EmitLogTopic/App.config b/dotnet-visual-studio/5_EmitLogTopic/App.config deleted file mode 100644 index 9c05822f..00000000 --- a/dotnet-visual-studio/5_EmitLogTopic/App.config +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/dotnet-visual-studio/5_EmitLogTopic/Program.cs b/dotnet-visual-studio/5_EmitLogTopic/Program.cs deleted file mode 100644 index af376b0e..00000000 --- a/dotnet-visual-studio/5_EmitLogTopic/Program.cs +++ /dev/null @@ -1,23 +0,0 @@ -using System; -using System.Linq; -using RabbitMQ.Client; -using System.Text; - -class Program -{ - public static void Main(string[] args) - { - var factory = new ConnectionFactory() { HostName = "localhost" }; - using(var connection = factory.CreateConnection()) - using(var channel = connection.CreateModel()) - { - channel.ExchangeDeclare(exchange: "topic_logs", type: "topic"); - - var routingKey = (args.Length > 0) ? args[0] : "anonymous.info"; - var message = (args.Length > 1) ? string.Join(" ", args.Skip(1).ToArray()) : "Hello World!"; - var body = Encoding.UTF8.GetBytes(message); - channel.BasicPublish(exchange: "topic_logs", routingKey: routingKey, basicProperties: null, body: body); - Console.WriteLine(" [x] Sent '{0}':'{1}'", routingKey, message); - } - } -} diff --git a/dotnet-visual-studio/5_EmitLogTopic/Properties/AssemblyInfo.cs b/dotnet-visual-studio/5_EmitLogTopic/Properties/AssemblyInfo.cs deleted file mode 100644 index 82992420..00000000 --- a/dotnet-visual-studio/5_EmitLogTopic/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle( "5_EmitLogTopic" )] -[assembly: AssemblyDescription( "" )] -[assembly: AssemblyConfiguration( "" )] -[assembly: AssemblyCompany( "" )] -[assembly: AssemblyProduct( "5_EmitLogTopic" )] -[assembly: AssemblyCopyright( "Copyright © 2015" )] -[assembly: AssemblyTrademark( "" )] -[assembly: AssemblyCulture( "" )] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible( false )] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid( "e2658cce-06d2-4507-8397-f726d54f4fcf" )] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion( "1.0.0.0" )] -[assembly: AssemblyFileVersion( "1.0.0.0" )] diff --git a/dotnet-visual-studio/5_EmitLogTopic/packages.config b/dotnet-visual-studio/5_EmitLogTopic/packages.config deleted file mode 100644 index 5b0c07d4..00000000 --- a/dotnet-visual-studio/5_EmitLogTopic/packages.config +++ /dev/null @@ -1,5 +0,0 @@ - - - - - \ No newline at end of file diff --git a/dotnet-visual-studio/5_ReceiveLogsTopic/5_ReceiveLogsTopic.csproj b/dotnet-visual-studio/5_ReceiveLogsTopic/5_ReceiveLogsTopic.csproj deleted file mode 100644 index 284b6131..00000000 --- a/dotnet-visual-studio/5_ReceiveLogsTopic/5_ReceiveLogsTopic.csproj +++ /dev/null @@ -1,75 +0,0 @@ - - - - - Debug - AnyCPU - {61A68101-F68B-4333-91C5-6F2B7BB0D5F6} - Exe - Properties - _5_ReceiveLogsTopic - 5_ReceiveLogsTopic - v4.5.1 - 512 - true - ..\ - true - - - AnyCPU - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - - - AnyCPU - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - - - - ..\packages\Microsoft.Diagnostics.Tracing.EventSource.Redist.1.1.28\lib\net40\Microsoft.Diagnostics.Tracing.EventSource.dll - - - ..\packages\RabbitMQ.Client.5.0.1\lib\net451\RabbitMQ.Client.dll - - - - - - - - - - - - - - - - - - - - - - This project references NuGet package(s) that are missing on this computer. Enable NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - - - - - \ No newline at end of file diff --git a/dotnet-visual-studio/5_ReceiveLogsTopic/App.config b/dotnet-visual-studio/5_ReceiveLogsTopic/App.config deleted file mode 100644 index 9c05822f..00000000 --- a/dotnet-visual-studio/5_ReceiveLogsTopic/App.config +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/dotnet-visual-studio/5_ReceiveLogsTopic/Program.cs b/dotnet-visual-studio/5_ReceiveLogsTopic/Program.cs deleted file mode 100644 index b10dde8d..00000000 --- a/dotnet-visual-studio/5_ReceiveLogsTopic/Program.cs +++ /dev/null @@ -1,46 +0,0 @@ -using System; -using RabbitMQ.Client; -using RabbitMQ.Client.Events; -using System.Text; - -class Program -{ - public static void Main(string[] args) - { - var factory = new ConnectionFactory() { HostName = "localhost" }; - using(var connection = factory.CreateConnection()) - using(var channel = connection.CreateModel()) - { - channel.ExchangeDeclare(exchange: "topic_logs", type: "topic"); - var queueName = channel.QueueDeclare().QueueName; - - if(args.Length < 1) - { - Console.Error.WriteLine("Usage: {0} [binding_key...]", Environment.GetCommandLineArgs()[0]); - Console.WriteLine(" Press [enter] to exit."); - Console.ReadLine(); - Environment.ExitCode = 1; - return; - } - - foreach(var bindingKey in args) - { - channel.QueueBind(queue: queueName, exchange: "topic_logs", routingKey: bindingKey); - } - - Console.WriteLine(" [*] Waiting for messages. To exit press CTRL+C"); - - var consumer = new QueueingBasicConsumer(channel); - channel.BasicConsume(queue: queueName, autoAck: true, consumer: consumer); - - while(true) - { - var ea = (BasicDeliverEventArgs)consumer.Queue.Dequeue(); - var body = ea.Body; - var message = Encoding.UTF8.GetString(body); - var routingKey = ea.RoutingKey; - Console.WriteLine(" [x] Received '{0}':'{1}'", routingKey, message); - } - } - } -} diff --git a/dotnet-visual-studio/5_ReceiveLogsTopic/Properties/AssemblyInfo.cs b/dotnet-visual-studio/5_ReceiveLogsTopic/Properties/AssemblyInfo.cs deleted file mode 100644 index 1df4cc70..00000000 --- a/dotnet-visual-studio/5_ReceiveLogsTopic/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle( "5_ReceiveLogsTopic" )] -[assembly: AssemblyDescription( "" )] -[assembly: AssemblyConfiguration( "" )] -[assembly: AssemblyCompany( "" )] -[assembly: AssemblyProduct( "4_ReceiveLogsTopic" )] -[assembly: AssemblyCopyright( "Copyright © 2015" )] -[assembly: AssemblyTrademark( "" )] -[assembly: AssemblyCulture( "" )] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible( false )] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid( "42a93100-47b5-4eda-add3-5b554b4bfbc0" )] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion( "1.0.0.0" )] -[assembly: AssemblyFileVersion( "1.0.0.0" )] diff --git a/dotnet-visual-studio/5_ReceiveLogsTopic/packages.config b/dotnet-visual-studio/5_ReceiveLogsTopic/packages.config deleted file mode 100644 index 5b0c07d4..00000000 --- a/dotnet-visual-studio/5_ReceiveLogsTopic/packages.config +++ /dev/null @@ -1,5 +0,0 @@ - - - - - \ No newline at end of file diff --git a/dotnet-visual-studio/6_RPCClient/6_RPCClient.csproj b/dotnet-visual-studio/6_RPCClient/6_RPCClient.csproj deleted file mode 100644 index 4c6b4819..00000000 --- a/dotnet-visual-studio/6_RPCClient/6_RPCClient.csproj +++ /dev/null @@ -1,76 +0,0 @@ - - - - - Debug - AnyCPU - {CAF32E20-FC0F-4FB5-8158-31DA442696EB} - Exe - Properties - _6_RPCCLient - 6_RPCCLient - v4.5.1 - 512 - true - ..\ - true - - - AnyCPU - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - - - AnyCPU - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - - - - ..\packages\Microsoft.Diagnostics.Tracing.EventSource.Redist.1.1.28\lib\net40\Microsoft.Diagnostics.Tracing.EventSource.dll - - - ..\packages\RabbitMQ.Client.5.0.1\lib\net451\RabbitMQ.Client.dll - - - - - - - - - - - - - - - - - - - - - - - This project references NuGet package(s) that are missing on this computer. Enable NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - - - - - \ No newline at end of file diff --git a/dotnet-visual-studio/6_RPCClient/App.config b/dotnet-visual-studio/6_RPCClient/App.config deleted file mode 100644 index 9c05822f..00000000 --- a/dotnet-visual-studio/6_RPCClient/App.config +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/dotnet-visual-studio/6_RPCClient/Program.cs b/dotnet-visual-studio/6_RPCClient/Program.cs deleted file mode 100644 index 4148060c..00000000 --- a/dotnet-visual-studio/6_RPCClient/Program.cs +++ /dev/null @@ -1,22 +0,0 @@ -using System; -using RabbitMQ.Client; -using RabbitMQ.Client.Events; -using System.Text; - -class Program -{ - public static void Main( string[] args ) - { - var rpcClient = new RPCClient(); - - var n = args.Length > 0 ? args[0] : "30"; - Console.WriteLine( " [x] Requesting fib({0})", n ); - var response = rpcClient.Call( n ); - Console.WriteLine( " [.] Got '{0}'", response ); - - rpcClient.Close(); - - Console.WriteLine( " Press [enter] to exit." ); - Console.ReadLine(); - } -} diff --git a/dotnet-visual-studio/6_RPCClient/Properties/AssemblyInfo.cs b/dotnet-visual-studio/6_RPCClient/Properties/AssemblyInfo.cs deleted file mode 100644 index 111a3440..00000000 --- a/dotnet-visual-studio/6_RPCClient/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle( "6_RPCClient" )] -[assembly: AssemblyDescription( "" )] -[assembly: AssemblyConfiguration( "" )] -[assembly: AssemblyCompany( "" )] -[assembly: AssemblyProduct( "6_RPCClient" )] -[assembly: AssemblyCopyright( "Copyright © 2015" )] -[assembly: AssemblyTrademark( "" )] -[assembly: AssemblyCulture( "" )] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible( false )] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid( "fb86635f-cd04-4e69-84af-27b2d806519d" )] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion( "1.0.0.0" )] -[assembly: AssemblyFileVersion( "1.0.0.0" )] diff --git a/dotnet-visual-studio/6_RPCClient/RPCClient.cs b/dotnet-visual-studio/6_RPCClient/RPCClient.cs deleted file mode 100644 index 414e414d..00000000 --- a/dotnet-visual-studio/6_RPCClient/RPCClient.cs +++ /dev/null @@ -1,50 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using RabbitMQ.Client; -using RabbitMQ.Client.Events; - -class RPCClient -{ - private IConnection connection; - private IModel channel; - private string replyQueueName; - private QueueingBasicConsumer consumer; - - public RPCClient() - { - var factory = new ConnectionFactory() { HostName = "localhost" }; - connection = factory.CreateConnection(); - channel = connection.CreateModel(); - replyQueueName = channel.QueueDeclare().QueueName; - consumer = new QueueingBasicConsumer(channel); - channel.BasicConsume(queue: replyQueueName, autoAck: true, consumer: consumer); - } - - public string Call(string message) - { - var corrId = Guid.NewGuid().ToString(); - var props = channel.CreateBasicProperties(); - props.ReplyTo = replyQueueName; - props.CorrelationId = corrId; - - var messageBytes = Encoding.UTF8.GetBytes(message); - channel.BasicPublish(exchange: "", routingKey: "rpc_queue", basicProperties: props, body: messageBytes); - - while(true) - { - var ea = (BasicDeliverEventArgs)consumer.Queue.Dequeue(); - if(ea.BasicProperties.CorrelationId == corrId) - { - return Encoding.UTF8.GetString(ea.Body); - } - } - } - - public void Close() - { - connection.Close(); - } -} diff --git a/dotnet-visual-studio/6_RPCClient/packages.config b/dotnet-visual-studio/6_RPCClient/packages.config deleted file mode 100644 index 5b0c07d4..00000000 --- a/dotnet-visual-studio/6_RPCClient/packages.config +++ /dev/null @@ -1,5 +0,0 @@ - - - - - \ No newline at end of file diff --git a/dotnet-visual-studio/6_RPCServer/6_RPCServer.csproj b/dotnet-visual-studio/6_RPCServer/6_RPCServer.csproj deleted file mode 100644 index ef21f8bc..00000000 --- a/dotnet-visual-studio/6_RPCServer/6_RPCServer.csproj +++ /dev/null @@ -1,75 +0,0 @@ - - - - - Debug - AnyCPU - {FD2191EB-380C-426D-A629-53DF095E6295} - Exe - Properties - _6_RPCServer - 6_RPCServer - v4.5.1 - 512 - true - ..\ - true - - - AnyCPU - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - - - AnyCPU - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - - - - ..\packages\Microsoft.Diagnostics.Tracing.EventSource.Redist.1.1.28\lib\net40\Microsoft.Diagnostics.Tracing.EventSource.dll - - - ..\packages\RabbitMQ.Client.5.0.1\lib\net451\RabbitMQ.Client.dll - - - - - - - - - - - - - - - - - - - - - - This project references NuGet package(s) that are missing on this computer. Enable NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - - - - - \ No newline at end of file diff --git a/dotnet-visual-studio/6_RPCServer/App.config b/dotnet-visual-studio/6_RPCServer/App.config deleted file mode 100644 index 9c05822f..00000000 --- a/dotnet-visual-studio/6_RPCServer/App.config +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/dotnet-visual-studio/6_RPCServer/Program.cs b/dotnet-visual-studio/6_RPCServer/Program.cs deleted file mode 100644 index 7a8969d9..00000000 --- a/dotnet-visual-studio/6_RPCServer/Program.cs +++ /dev/null @@ -1,67 +0,0 @@ -using System; -using RabbitMQ.Client; -using RabbitMQ.Client.Events; -using System.Text; - -class Program -{ - public static void Main() - { - var factory = new ConnectionFactory() { HostName = "localhost" }; - using (var connection = factory.CreateConnection()) - using (var channel = connection.CreateModel()) - { - channel.QueueDeclare(queue: "rpc_queue", durable: false, exclusive: false, autoDelete: false, arguments: null); - channel.BasicQos(0, 1, false); - var consumer = new EventingBasicConsumer(channel); - channel.BasicConsume(queue: "rpc_queue", autoAck: false, consumer: consumer); - Console.WriteLine(" [x] Awaiting RPC requests"); - - consumer.Received += (model, ea) => - { - string response = null; - - var body = ea.Body; - var props = ea.BasicProperties; - var replyProps = channel.CreateBasicProperties(); - replyProps.CorrelationId = props.CorrelationId; - - try - { - var message = Encoding.UTF8.GetString(body); - int n = int.Parse(message); - Console.WriteLine(" [.] fib({0})", message); - response = fib(n).ToString(); - } - catch (Exception e) - { - Console.WriteLine(" [.] " + e.Message); - response = ""; - } - finally - { - var responseBytes = Encoding.UTF8.GetBytes(response); - channel.BasicPublish(exchange: "", routingKey: props.ReplyTo, basicProperties: replyProps, body: responseBytes); - channel.BasicAck(deliveryTag: ea.DeliveryTag, multiple: false); - } - }; - - Console.WriteLine(" Press [enter] to exit."); - Console.ReadLine(); - } - } - - /// - /// Assumes only valid positive integer input. - /// Don't expect this one to work for big numbers, and it's probably the slowest recursive implementation possible. - /// - private static int fib(int n) - { - if (n == 0 || n == 1) - { - return n; - } - - return fib(n - 1) + fib(n - 2); - } -} diff --git a/dotnet-visual-studio/6_RPCServer/Properties/AssemblyInfo.cs b/dotnet-visual-studio/6_RPCServer/Properties/AssemblyInfo.cs deleted file mode 100644 index ac706aac..00000000 --- a/dotnet-visual-studio/6_RPCServer/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle( "6_RPCServer" )] -[assembly: AssemblyDescription( "" )] -[assembly: AssemblyConfiguration( "" )] -[assembly: AssemblyCompany( "" )] -[assembly: AssemblyProduct( "6_RPCServer" )] -[assembly: AssemblyCopyright( "Copyright © 2015" )] -[assembly: AssemblyTrademark( "" )] -[assembly: AssemblyCulture( "" )] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible( false )] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid( "2904cec2-363c-4594-8adb-0579e40e1352" )] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion( "1.0.0.0" )] -[assembly: AssemblyFileVersion( "1.0.0.0" )] diff --git a/dotnet-visual-studio/6_RPCServer/packages.config b/dotnet-visual-studio/6_RPCServer/packages.config deleted file mode 100644 index 5b0c07d4..00000000 --- a/dotnet-visual-studio/6_RPCServer/packages.config +++ /dev/null @@ -1,5 +0,0 @@ - - - - - \ No newline at end of file diff --git a/dotnet-visual-studio/README.md b/dotnet-visual-studio/README.md deleted file mode 100644 index 4eca3dcd..00000000 --- a/dotnet-visual-studio/README.md +++ /dev/null @@ -1,44 +0,0 @@ -# .NET C# Visual Studio Solution for RabbitMQ tutorials - -Here you can find the C# code examples for [RabbitMQ -tutorials](http://www.rabbitmq.com/getstarted.html) compiled -into a Visual Studio solution. - -To successfully use the examples you will need a running RabbitMQ server. - -## NuGet Restore - -You may need to "Enable NuGet Package Restore" on the solution upon the first -time opening it in order to get the .NET RabbitMQ dependency from NuGet. - -## Code - -#### [Tutorial 1: "Hello World!"](http://www.rabbitmq.com/tutorial-one-dotnet.html) - -- 1_Receive -- 1_Send - -#### [Tutorial 2: Work Queues](http://www.rabbitmq.com/tutorial-two-dotnet.html) - -- 2_Worker -- 2_NewTask - -#### [Tutorial 3: Publish/Subscribe](http://www.rabbitmq.com/tutorial-three-dotnet.html) - -- 3_ReceiveLogs -- 3_EmitLog - -#### [Tutorial 4: Routing](http://www.rabbitmq.com/tutorial-four-dotnet.html) - -- 4_ReceiveLogsDirect -- 4_EmitLogDirect - -#### [Tutorial 5: Topics](http://www.rabbitmq.com/tutorial-five-dotnet.html) - -- 5_ReceiveLogsTopic -- 5_EmitLogTopic - -#### [Tutorial 6: RPC](http://www.rabbitmq.com/tutorial-six-dotnet.html) - -- 6_RPCServer -- 6_RPCClient diff --git a/dotnet-visual-studio/RabbitMQ.Tutorials.sln b/dotnet-visual-studio/RabbitMQ.Tutorials.sln deleted file mode 100644 index 8b41722d..00000000 --- a/dotnet-visual-studio/RabbitMQ.Tutorials.sln +++ /dev/null @@ -1,95 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 2013 -VisualStudioVersion = 12.0.31101.0 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "1_Send", "1_Send\1_Send.csproj", "{5FCA7642-F829-4AB9-BC2D-C5FDEFCA80BF}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "1_Receive", "1_Receive\1_Receive.csproj", "{DABA9504-8375-46DE-8349-A2DFCA9B1F2D}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "2_NewTask", "2_NewTask\2_NewTask.csproj", "{79410434-9BDD-49CE-A3EE-CE85526F317D}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "2_Worker", "2_Worker\2_Worker.csproj", "{76C2AE27-9C4B-439B-B647-1CE709E59ABF}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "3_EmitLog", "3_EmitLog\3_EmitLog.csproj", "{EE47BA8A-4A5B-45F8-969A-C0DDCA667BF8}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "3_ReceiveLogs", "3_ReceiveLogs\3_ReceiveLogs.csproj", "{164DB92B-2FE7-424C-8F79-EC23998F568B}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "4_EmitLogDirect", "4_EmitLogDirect\4_EmitLogDirect.csproj", "{74E16495-2A01-4314-BE72-8ADAE64666BC}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "4_ReceiveLogsDirect", "4_ReceiveLogsDirect\4_ReceiveLogsDirect.csproj", "{2DE4DE5A-7377-4914-9E32-014D9BD1CD44}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "5_EmitLogTopic", "5_EmitLogTopic\5_EmitLogTopic.csproj", "{86A66C9E-E479-4EA1-803D-E703F77581DC}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "5_ReceiveLogsTopic", "5_ReceiveLogsTopic\5_ReceiveLogsTopic.csproj", "{61A68101-F68B-4333-91C5-6F2B7BB0D5F6}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "6_RPCClient", "6_RPCClient\6_RPCClient.csproj", "{CAF32E20-FC0F-4FB5-8158-31DA442696EB}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "6_RPCServer", "6_RPCServer\6_RPCServer.csproj", "{FD2191EB-380C-426D-A629-53DF095E6295}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".nuget", ".nuget", "{1CAFD4AB-D050-49DF-97B5-EEC43D4D1EA0}" - ProjectSection(SolutionItems) = preProject - .nuget\NuGet.Config = .nuget\NuGet.Config - .nuget\NuGet.exe = .nuget\NuGet.exe - .nuget\NuGet.targets = .nuget\NuGet.targets - EndProjectSection -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {5FCA7642-F829-4AB9-BC2D-C5FDEFCA80BF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {5FCA7642-F829-4AB9-BC2D-C5FDEFCA80BF}.Debug|Any CPU.Build.0 = Debug|Any CPU - {5FCA7642-F829-4AB9-BC2D-C5FDEFCA80BF}.Release|Any CPU.ActiveCfg = Release|Any CPU - {5FCA7642-F829-4AB9-BC2D-C5FDEFCA80BF}.Release|Any CPU.Build.0 = Release|Any CPU - {DABA9504-8375-46DE-8349-A2DFCA9B1F2D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {DABA9504-8375-46DE-8349-A2DFCA9B1F2D}.Debug|Any CPU.Build.0 = Debug|Any CPU - {DABA9504-8375-46DE-8349-A2DFCA9B1F2D}.Release|Any CPU.ActiveCfg = Release|Any CPU - {DABA9504-8375-46DE-8349-A2DFCA9B1F2D}.Release|Any CPU.Build.0 = Release|Any CPU - {79410434-9BDD-49CE-A3EE-CE85526F317D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {79410434-9BDD-49CE-A3EE-CE85526F317D}.Debug|Any CPU.Build.0 = Debug|Any CPU - {79410434-9BDD-49CE-A3EE-CE85526F317D}.Release|Any CPU.ActiveCfg = Release|Any CPU - {79410434-9BDD-49CE-A3EE-CE85526F317D}.Release|Any CPU.Build.0 = Release|Any CPU - {76C2AE27-9C4B-439B-B647-1CE709E59ABF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {76C2AE27-9C4B-439B-B647-1CE709E59ABF}.Debug|Any CPU.Build.0 = Debug|Any CPU - {76C2AE27-9C4B-439B-B647-1CE709E59ABF}.Release|Any CPU.ActiveCfg = Release|Any CPU - {76C2AE27-9C4B-439B-B647-1CE709E59ABF}.Release|Any CPU.Build.0 = Release|Any CPU - {EE47BA8A-4A5B-45F8-969A-C0DDCA667BF8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {EE47BA8A-4A5B-45F8-969A-C0DDCA667BF8}.Debug|Any CPU.Build.0 = Debug|Any CPU - {EE47BA8A-4A5B-45F8-969A-C0DDCA667BF8}.Release|Any CPU.ActiveCfg = Release|Any CPU - {EE47BA8A-4A5B-45F8-969A-C0DDCA667BF8}.Release|Any CPU.Build.0 = Release|Any CPU - {164DB92B-2FE7-424C-8F79-EC23998F568B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {164DB92B-2FE7-424C-8F79-EC23998F568B}.Debug|Any CPU.Build.0 = Debug|Any CPU - {164DB92B-2FE7-424C-8F79-EC23998F568B}.Release|Any CPU.ActiveCfg = Release|Any CPU - {164DB92B-2FE7-424C-8F79-EC23998F568B}.Release|Any CPU.Build.0 = Release|Any CPU - {74E16495-2A01-4314-BE72-8ADAE64666BC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {74E16495-2A01-4314-BE72-8ADAE64666BC}.Debug|Any CPU.Build.0 = Debug|Any CPU - {74E16495-2A01-4314-BE72-8ADAE64666BC}.Release|Any CPU.ActiveCfg = Release|Any CPU - {74E16495-2A01-4314-BE72-8ADAE64666BC}.Release|Any CPU.Build.0 = Release|Any CPU - {2DE4DE5A-7377-4914-9E32-014D9BD1CD44}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {2DE4DE5A-7377-4914-9E32-014D9BD1CD44}.Debug|Any CPU.Build.0 = Debug|Any CPU - {2DE4DE5A-7377-4914-9E32-014D9BD1CD44}.Release|Any CPU.ActiveCfg = Release|Any CPU - {2DE4DE5A-7377-4914-9E32-014D9BD1CD44}.Release|Any CPU.Build.0 = Release|Any CPU - {86A66C9E-E479-4EA1-803D-E703F77581DC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {86A66C9E-E479-4EA1-803D-E703F77581DC}.Debug|Any CPU.Build.0 = Debug|Any CPU - {86A66C9E-E479-4EA1-803D-E703F77581DC}.Release|Any CPU.ActiveCfg = Release|Any CPU - {86A66C9E-E479-4EA1-803D-E703F77581DC}.Release|Any CPU.Build.0 = Release|Any CPU - {61A68101-F68B-4333-91C5-6F2B7BB0D5F6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {61A68101-F68B-4333-91C5-6F2B7BB0D5F6}.Debug|Any CPU.Build.0 = Debug|Any CPU - {61A68101-F68B-4333-91C5-6F2B7BB0D5F6}.Release|Any CPU.ActiveCfg = Release|Any CPU - {61A68101-F68B-4333-91C5-6F2B7BB0D5F6}.Release|Any CPU.Build.0 = Release|Any CPU - {CAF32E20-FC0F-4FB5-8158-31DA442696EB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {CAF32E20-FC0F-4FB5-8158-31DA442696EB}.Debug|Any CPU.Build.0 = Debug|Any CPU - {CAF32E20-FC0F-4FB5-8158-31DA442696EB}.Release|Any CPU.ActiveCfg = Release|Any CPU - {CAF32E20-FC0F-4FB5-8158-31DA442696EB}.Release|Any CPU.Build.0 = Release|Any CPU - {FD2191EB-380C-426D-A629-53DF095E6295}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {FD2191EB-380C-426D-A629-53DF095E6295}.Debug|Any CPU.Build.0 = Debug|Any CPU - {FD2191EB-380C-426D-A629-53DF095E6295}.Release|Any CPU.ActiveCfg = Release|Any CPU - {FD2191EB-380C-426D-A629-53DF095E6295}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection -EndGlobal diff --git a/dotnet/.gitattributes b/dotnet/.gitattributes new file mode 100644 index 00000000..47a8f29a --- /dev/null +++ b/dotnet/.gitattributes @@ -0,0 +1,14 @@ +# Set the default behavior, in case people don't have core.autocrlf set. +* text=auto + +# Auto detect text files and perform LF normalization +*.cs text=auto eol=lf +*.txt text=auto + +# Declare files that will always have CRLF line endings on checkout. +*.sln text eol=crlf +*.csproj text eol=crlf + +# Custom for Visual Studio +*.cs diff=csharp + diff --git a/dotnet/.gitignore b/dotnet/.gitignore index 305ed7b0..52800612 100644 --- a/dotnet/.gitignore +++ b/dotnet/.gitignore @@ -1,4 +1,4 @@ -recompile.sh *.dll *.exe *.lock.json +packages/ diff --git a/dotnet/AGENTS.md b/dotnet/AGENTS.md new file mode 100644 index 00000000..32ee4f21 --- /dev/null +++ b/dotnet/AGENTS.md @@ -0,0 +1,19 @@ +# Agent Notes for These .NET Tutorials + +## `Console.ReadLine` in non-TTY environments + +Consumer programs (Receive, Worker, ReceiveLogs, ReceiveLogsDirect, ReceiveLogsTopic, +RPCServer) use `Console.ReadLine` to block until the user presses `[Enter]`. + +When standard input is not connected to a TTY, `ReadLine()` returns `null` immediately and the process +exits before receiving any deliveries. + +Keep stdin open in automated or background contexts by piping from a blocking process: + +```sh +(while true; do sleep 20; done) | dotnet run --project Receive & +(while true; do sleep 20; done) | dotnet run --project ReceiveLogs & +``` + +The `Console.ReadLine()` and the "Press [enter] to +exit." must never be deleted as a workaround: they exist for interactive use. diff --git a/dotnet/EmitLog/EmitLog.cs b/dotnet/EmitLog/EmitLog.cs index ec4a781c..a8d09eda 100644 --- a/dotnet/EmitLog/EmitLog.cs +++ b/dotnet/EmitLog/EmitLog.cs @@ -1,29 +1,21 @@ -using System; using RabbitMQ.Client; using System.Text; -class EmitLog -{ - public static void Main(string[] args) - { - var factory = new ConnectionFactory() { HostName = "localhost" }; - using(var connection = factory.CreateConnection()) - using(var channel = connection.CreateModel()) - { - channel.ExchangeDeclare(exchange: "logs", type: "fanout"); +var factory = new ConnectionFactory { HostName = "localhost" }; +using var connection = await factory.CreateConnectionAsync(); +using var channel = await connection.CreateChannelAsync(); + +await channel.ExchangeDeclareAsync(exchange: "logs", type: ExchangeType.Fanout); - var message = GetMessage(args); - var body = Encoding.UTF8.GetBytes(message); - channel.BasicPublish(exchange: "logs", routingKey: "", basicProperties: null, body: body); - Console.WriteLine(" [x] Sent {0}", message); - } +var message = GetMessage(args); +var body = Encoding.UTF8.GetBytes(message); +await channel.BasicPublishAsync(exchange: "logs", routingKey: string.Empty, body: body); +Console.WriteLine($" [x] Sent {message}"); - Console.WriteLine(" Press [enter] to exit."); - Console.ReadLine(); - } +Console.WriteLine(" Press [enter] to exit."); +Console.ReadLine(); - private static string GetMessage(string[] args) - { - return ((args.Length > 0) ? string.Join(" ", args) : "info: Hello World!"); - } +static string GetMessage(string[] args) +{ + return ((args.Length > 0) ? string.Join(" ", args) : "info: Hello World!"); } diff --git a/dotnet/EmitLog/EmitLog.csproj b/dotnet/EmitLog/EmitLog.csproj old mode 100755 new mode 100644 index 2eabb57d..29a2e9d9 --- a/dotnet/EmitLog/EmitLog.csproj +++ b/dotnet/EmitLog/EmitLog.csproj @@ -1,17 +1,14 @@  - netcoreapp1.0 - portable - EmitLog + net8.0 Exe - EmitLog - $(PackageTargetFallback);dnxcore50 - 1.0.4 + enable + enable - - + + diff --git a/dotnet/EmitLogDirect/EmitLogDirect.cs b/dotnet/EmitLogDirect/EmitLogDirect.cs index 707267f9..b0918c30 100644 --- a/dotnet/EmitLogDirect/EmitLogDirect.cs +++ b/dotnet/EmitLogDirect/EmitLogDirect.cs @@ -1,26 +1,17 @@ -using System; -using System.Linq; using RabbitMQ.Client; using System.Text; -class EmitLogDirect -{ - public static void Main(string[] args) - { - var factory = new ConnectionFactory() { HostName = "localhost" }; - using(var connection = factory.CreateConnection()) - using(var channel = connection.CreateModel()) - { - channel.ExchangeDeclare(exchange: "direct_logs", type: "direct"); +var factory = new ConnectionFactory { HostName = "localhost" }; +using var connection = await factory.CreateConnectionAsync(); +using var channel = await connection.CreateChannelAsync(); - var severity = (args.Length > 0) ? args[0] : "info"; - var message = (args.Length > 1) ? string.Join(" ", args.Skip(1).ToArray()) : "Hello World!"; - var body = Encoding.UTF8.GetBytes(message); - channel.BasicPublish(exchange: "direct_logs", routingKey: severity, basicProperties: null, body: body); - Console.WriteLine(" [x] Sent '{0}':'{1}'", severity, message); - } +await channel.ExchangeDeclareAsync(exchange: "direct_logs", type: ExchangeType.Direct); - Console.WriteLine(" Press [enter] to exit."); - Console.ReadLine(); - } -} +var severity = (args.Length > 0) ? args[0] : "info"; +var message = (args.Length > 1) ? string.Join(" ", args.Skip(1).ToArray()) : "Hello World!"; +var body = Encoding.UTF8.GetBytes(message); +await channel.BasicPublishAsync(exchange: "direct_logs", routingKey: severity, body: body); +Console.WriteLine($" [x] Sent '{severity}':'{message}'"); + +Console.WriteLine(" Press [enter] to exit."); +Console.ReadLine(); diff --git a/dotnet/EmitLogDirect/EmitLogDirect.csproj b/dotnet/EmitLogDirect/EmitLogDirect.csproj old mode 100755 new mode 100644 index 8c596aff..29a2e9d9 --- a/dotnet/EmitLogDirect/EmitLogDirect.csproj +++ b/dotnet/EmitLogDirect/EmitLogDirect.csproj @@ -1,17 +1,14 @@  - netcoreapp1.0 - portable - EmitLogDirect + net8.0 Exe - EmitLogDirect - $(PackageTargetFallback);dnxcore50 - 1.0.4 + enable + enable - - + + diff --git a/dotnet/EmitLogTopic/EmitLogTopic.cs b/dotnet/EmitLogTopic/EmitLogTopic.cs index f50fee0a..e64efee1 100644 --- a/dotnet/EmitLogTopic/EmitLogTopic.cs +++ b/dotnet/EmitLogTopic/EmitLogTopic.cs @@ -1,23 +1,14 @@ -using System; -using System.Linq; using RabbitMQ.Client; using System.Text; -class EmitLogTopic -{ - public static void Main(string[] args) - { - var factory = new ConnectionFactory() { HostName = "localhost" }; - using(var connection = factory.CreateConnection()) - using(var channel = connection.CreateModel()) - { - channel.ExchangeDeclare(exchange: "topic_logs", type: "topic"); +var factory = new ConnectionFactory { HostName = "localhost" }; +using var connection = await factory.CreateConnectionAsync(); +using var channel = await connection.CreateChannelAsync(); - var routingKey = (args.Length > 0) ? args[0] : "anonymous.info"; - var message = (args.Length > 1) ? string.Join(" ", args.Skip(1).ToArray()) : "Hello World!"; - var body = Encoding.UTF8.GetBytes(message); - channel.BasicPublish(exchange: "topic_logs", routingKey: routingKey, basicProperties: null, body: body); - Console.WriteLine(" [x] Sent '{0}':'{1}'", routingKey, message); - } - } -} +await channel.ExchangeDeclareAsync(exchange: "topic_logs", type: ExchangeType.Topic); + +var routingKey = (args.Length > 0) ? args[0] : "anonymous.info"; +var message = (args.Length > 1) ? string.Join(" ", args.Skip(1).ToArray()) : "Hello World!"; +var body = Encoding.UTF8.GetBytes(message); +await channel.BasicPublishAsync(exchange: "topic_logs", routingKey: routingKey, body: body); +Console.WriteLine($" [x] Sent '{routingKey}':'{message}'"); diff --git a/dotnet/EmitLogTopic/EmitLogTopic.csproj b/dotnet/EmitLogTopic/EmitLogTopic.csproj old mode 100755 new mode 100644 index 35b146c3..5c27afb4 --- a/dotnet/EmitLogTopic/EmitLogTopic.csproj +++ b/dotnet/EmitLogTopic/EmitLogTopic.csproj @@ -1,17 +1,14 @@  - netcoreapp1.0 - portable - EmitLogTopic + net8.0 Exe - EmitLogTopic - $(PackageTargetFallback);dnxcore50 - 1.0.4 + enable + enable - - + + diff --git a/dotnet/NewTask/NewTask.cs b/dotnet/NewTask/NewTask.cs index 9ee3b664..ebaf9a85 100644 --- a/dotnet/NewTask/NewTask.cs +++ b/dotnet/NewTask/NewTask.cs @@ -1,33 +1,26 @@ -using System; using RabbitMQ.Client; using System.Text; -class NewTask -{ - public static void Main(string[] args) - { - var factory = new ConnectionFactory() { HostName = "localhost" }; - using(var connection = factory.CreateConnection()) - using(var channel = connection.CreateModel()) - { - channel.QueueDeclare(queue: "task_queue", durable: true, exclusive: false, autoDelete: false, arguments: null); +var factory = new ConnectionFactory { HostName = "localhost" }; +using var connection = await factory.CreateConnectionAsync(); +using var channel = await connection.CreateChannelAsync(); - var message = GetMessage(args); - var body = Encoding.UTF8.GetBytes(message); +await channel.QueueDeclareAsync(queue: "task_queue", durable: true, exclusive: false, + autoDelete: false, arguments: new Dictionary { { "x-queue-type", "quorum" } }); - var properties = channel.CreateBasicProperties(); - properties.Persistent = true; +var message = GetMessage(args); +var body = Encoding.UTF8.GetBytes(message); - channel.BasicPublish(exchange: "", routingKey: "task_queue", basicProperties: properties, body: body); - Console.WriteLine(" [x] Sent {0}", message); - } +var properties = new BasicProperties +{ + Persistent = true +}; - Console.WriteLine(" Press [enter] to exit."); - Console.ReadLine(); - } +await channel.BasicPublishAsync(exchange: string.Empty, routingKey: "task_queue", mandatory: true, + basicProperties: properties, body: body); +Console.WriteLine($" [x] Sent {message}"); - private static string GetMessage(string[] args) - { - return ((args.Length > 0) ? string.Join(" ", args) : "Hello World!"); - } +static string GetMessage(string[] args) +{ + return ((args.Length > 0) ? string.Join(" ", args) : "Hello World!"); } diff --git a/dotnet/NewTask/NewTask.csproj b/dotnet/NewTask/NewTask.csproj old mode 100755 new mode 100644 index 0ff0415e..3e47c5b7 --- a/dotnet/NewTask/NewTask.csproj +++ b/dotnet/NewTask/NewTask.csproj @@ -1,17 +1,14 @@  - netcoreapp1.0 - portable - NewTask + net8.0 Exe - NewTask - $(PackageTargetFallback);dnxcore50 - 1.0.4 + enable + enable - - + + diff --git a/dotnet/PublisherConfirms/PublisherConfirms.cs b/dotnet/PublisherConfirms/PublisherConfirms.cs new file mode 100644 index 00000000..5fbb1a10 --- /dev/null +++ b/dotnet/PublisherConfirms/PublisherConfirms.cs @@ -0,0 +1,281 @@ +using RabbitMQ.Client; +using System.Buffers.Binary; +using System.Diagnostics; +using System.Text; + +const ushort MAX_OUTSTANDING_CONFIRMS = 256; + +const int MESSAGE_COUNT = 50_000; +bool debug = false; + +var channelOpts = new CreateChannelOptions( + publisherConfirmationsEnabled: true, + publisherConfirmationTrackingEnabled: true, + outstandingPublisherConfirmationsRateLimiter: new ThrottlingRateLimiter(MAX_OUTSTANDING_CONFIRMS) +); + +var props = new BasicProperties +{ + Persistent = true +}; + +string hostname = "localhost"; +if (args.Length > 0) +{ + if (false == string.IsNullOrWhiteSpace(args[0])) + { + hostname = args[0]; + } +} + +#pragma warning disable CS8321 // Local function is declared but never used + +await PublishMessagesIndividuallyAsync(); +await PublishMessagesInBatchAsync(); +await HandlePublishConfirmsAsynchronously(); + +Task CreateConnectionAsync() +{ + var factory = new ConnectionFactory { HostName = hostname }; + return factory.CreateConnectionAsync(); +} + +async Task PublishMessagesIndividuallyAsync() +{ + Console.WriteLine($"{DateTime.Now} [INFO] publishing {MESSAGE_COUNT:N0} messages and handling confirms per-message"); + + await using IConnection connection = await CreateConnectionAsync(); + await using IChannel channel = await connection.CreateChannelAsync(channelOpts); + + // declare a server-named queue + QueueDeclareOk queueDeclareResult = await channel.QueueDeclareAsync(); + string queueName = queueDeclareResult.QueueName; + + var sw = new Stopwatch(); + sw.Start(); + + for (int i = 0; i < MESSAGE_COUNT; i++) + { + byte[] body = Encoding.UTF8.GetBytes(i.ToString()); + try + { + await channel.BasicPublishAsync(exchange: string.Empty, routingKey: queueName, body: body, basicProperties: props, mandatory: true); + } + catch (Exception ex) + { + Console.Error.WriteLine($"{DateTime.Now} [ERROR] saw nack or return, ex: {ex}"); + } + } + + sw.Stop(); + + Console.WriteLine($"{DateTime.Now} [INFO] published {MESSAGE_COUNT:N0} messages individually in {sw.ElapsedMilliseconds:N0} ms"); +} + +async Task PublishMessagesInBatchAsync() +{ + Console.WriteLine($"{DateTime.Now} [INFO] publishing {MESSAGE_COUNT:N0} messages and handling confirms in batches"); + + await using IConnection connection = await CreateConnectionAsync(); + await using IChannel channel = await connection.CreateChannelAsync(channelOpts); + + // declare a server-named queue + QueueDeclareOk queueDeclareResult = await channel.QueueDeclareAsync(); + string queueName = queueDeclareResult.QueueName; + + int batchSize = Math.Max(1, MAX_OUTSTANDING_CONFIRMS / 2); + + var sw = Stopwatch.StartNew(); + + var publishTasks = new List(); + for (int i = 0; i < MESSAGE_COUNT; i++) + { + byte[] body = Encoding.UTF8.GetBytes(i.ToString()); + ValueTask publishTask = channel.BasicPublishAsync(exchange: string.Empty, routingKey: queueName, body: body, mandatory: true, basicProperties: props); + publishTasks.Add(publishTask); + + await MaybeAwaitPublishes(publishTasks, batchSize); + } + + // Await any remaining tasks in case message count was not + // evenly divisible by batch size. + await MaybeAwaitPublishes(publishTasks, 0); + + sw.Stop(); + Console.WriteLine($"{DateTime.Now} [INFO] published {MESSAGE_COUNT:N0} messages in batch in {sw.ElapsedMilliseconds:N0} ms"); +} + +static async Task MaybeAwaitPublishes(List publishTasks, int batchSize) +{ + if (publishTasks.Count >= batchSize) + { + foreach (ValueTask pt in publishTasks) + { + try + { + await pt; + } + catch (Exception ex) + { + Console.Error.WriteLine($"{DateTime.Now} [ERROR] saw nack or return, ex: '{ex}'"); + } + } + publishTasks.Clear(); + } +} + +async Task HandlePublishConfirmsAsynchronously() +{ + Console.WriteLine($"{DateTime.Now} [INFO] publishing {MESSAGE_COUNT:N0} messages and handling confirms asynchronously"); + + await using IConnection connection = await CreateConnectionAsync(); + + channelOpts = new CreateChannelOptions(publisherConfirmationsEnabled: true, publisherConfirmationTrackingEnabled: false); + await using IChannel channel = await connection.CreateChannelAsync(channelOpts); + + // declare a server-named queue + QueueDeclareOk queueDeclareResult = await channel.QueueDeclareAsync(); + string queueName = queueDeclareResult.QueueName; + + var allMessagesConfirmedTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + var outstandingConfirms = new LinkedList(); + var semaphore = new SemaphoreSlim(1, 1); + int confirmedCount = 0; + async Task CleanOutstandingConfirms(ulong deliveryTag, bool multiple) + { + if (debug) + { + Console.WriteLine("{0} [DEBUG] confirming message: {1} (multiple: {2})", + DateTime.Now, deliveryTag, multiple); + } + + await semaphore.WaitAsync(); + try + { + if (multiple) + { + do + { + LinkedListNode? node = outstandingConfirms.First; + if (node is null) + { + break; + } + if (node.Value <= deliveryTag) + { + outstandingConfirms.RemoveFirst(); + } + else + { + break; + } + + confirmedCount++; + } while (true); + } + else + { + confirmedCount++; + outstandingConfirms.Remove(deliveryTag); + } + } + finally + { + semaphore.Release(); + } + + if (outstandingConfirms.Count == 0 || confirmedCount == MESSAGE_COUNT) + { + allMessagesConfirmedTcs.SetResult(true); + } + } + + channel.BasicReturnAsync += (sender, ea) => + { + ulong sequenceNumber = 0; + + IReadOnlyBasicProperties props = ea.BasicProperties; + if (props.Headers is not null) + { + object? maybeSeqNum = props.Headers[Constants.PublishSequenceNumberHeader]; + if (maybeSeqNum is not null) + { + sequenceNumber = BinaryPrimitives.ReadUInt64BigEndian((byte[])maybeSeqNum); + } + } + + Console.WriteLine($"{DateTime.Now} [WARNING] message sequence number {sequenceNumber} has been basic.return-ed"); + return CleanOutstandingConfirms(sequenceNumber, false); + }; + + channel.BasicAcksAsync += (sender, ea) => CleanOutstandingConfirms(ea.DeliveryTag, ea.Multiple); + channel.BasicNacksAsync += (sender, ea) => + { + Console.WriteLine($"{DateTime.Now} [WARNING] message sequence number: {ea.DeliveryTag} has been nacked (multiple: {ea.Multiple})"); + return CleanOutstandingConfirms(ea.DeliveryTag, ea.Multiple); + }; + + var sw = new Stopwatch(); + sw.Start(); + + var publishTasks = new List>(); + for (int i = 0; i < MESSAGE_COUNT; i++) + { + string msg = i.ToString(); + byte[] body = Encoding.UTF8.GetBytes(msg); + ulong nextPublishSeqNo = await channel.GetNextPublishSequenceNumberAsync(); + if ((ulong)(i + 1) != nextPublishSeqNo) + { + Console.WriteLine($"{DateTime.Now} [WARNING] i {i + 1} does not equal next sequence number: {nextPublishSeqNo}"); + } + await semaphore.WaitAsync(); + try + { + outstandingConfirms.AddLast(nextPublishSeqNo); + } + finally + { + semaphore.Release(); + } + + string rk = queueName; + if (i % 1000 == 0) + { + // This will cause a basic.return, for fun + rk = Guid.NewGuid().ToString(); + } + (ulong, ValueTask) data = + (nextPublishSeqNo, channel.BasicPublishAsync(exchange: string.Empty, routingKey: rk, body: body, mandatory: true, basicProperties: props)); + publishTasks.Add(data); + } + + using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(10)); + // await Task.WhenAll(publishTasks).WaitAsync(cts.Token); + foreach ((ulong SeqNo, ValueTask PublishTask) datum in publishTasks) + { + try + { + await datum.PublishTask; + } + catch (Exception ex) + { + Console.Error.WriteLine($"{DateTime.Now} [ERROR] saw nack, seqNo: '{datum.SeqNo}', ex: '{ex}'"); + } + } + + try + { + await allMessagesConfirmedTcs.Task.WaitAsync(cts.Token); + } + catch (OperationCanceledException) + { + Console.Error.WriteLine("{0} [ERROR] all messages could not be published and confirmed within 10 seconds", DateTime.Now); + } + catch (TimeoutException) + { + Console.Error.WriteLine("{0} [ERROR] all messages could not be published and confirmed within 10 seconds", DateTime.Now); + } + + sw.Stop(); + Console.WriteLine($"{DateTime.Now} [INFO] published {MESSAGE_COUNT:N0} messages and handled confirm asynchronously {sw.ElapsedMilliseconds:N0} ms"); +} diff --git a/dotnet/PublisherConfirms/PublisherConfirms.csproj b/dotnet/PublisherConfirms/PublisherConfirms.csproj new file mode 100644 index 00000000..2f9b2487 --- /dev/null +++ b/dotnet/PublisherConfirms/PublisherConfirms.csproj @@ -0,0 +1,14 @@ + + + + net8.0 + Exe + enable + enable + + + + + + + diff --git a/dotnet/README.md b/dotnet/README.md index 848f3df3..b42814ae 100644 --- a/dotnet/README.md +++ b/dotnet/README.md @@ -1,10 +1,14 @@ # Dotnet C# code for RabbitMQ tutorials Here you can find the C# code examples for [RabbitMQ -tutorials](http://www.rabbitmq.com/getstarted.html). +tutorials](https://www.rabbitmq.com/tutorials) using .NET 8 and RabbitMQ.Client 7.2.x. + +You will also find a solution file for Visual Studio 2022. To successfully use the examples you will need a running RabbitMQ server. +You can easily set this up by [installing RabbitMQ](https://www.rabbitmq.com/docs/download). + ## Requirements ### Requirements on Windows @@ -12,48 +16,109 @@ To successfully use the examples you will need a running RabbitMQ server. * [dotnet core](https://www.microsoft.com/net/core) We're using the command line (start->run cmd.exe) to -compile and run -p the code. Alternatively you could [use Visual Studio](https://github.com/rabbitmq/rabbitmq-tutorials/tree/master/dotnet-visual-studio) and [NuGet package](https://www.nuget.org/packages/RabbitMQ.Client/), but this set of tutorials assumes +compile and run the code. Alternatively you could use Visual Studio, but this set of tutorials assumes the command line. ### Requirements on Linux * [dotnet core](https://www.microsoft.com/net/core) -## Code +### Code Each command is best run in a separate console/terminal instance run from the root of the tutorial directory. -First run the `recompile.sh` script. This will run `dotnet restore` and build -each project which is required before they can be run. Alternatively or if you are -on windows cd into each project and run `dotnet restore` manually. +#### [Tutorial one: "Hello World!"](https://www.rabbitmq.com/tutorials/tutorial-one-dotnet.html) + +```bash +# terminal tab 1 +dotnet run --project Receive/Receive.csproj + +# terminal tab 2 +dotnet run --project Send/Send.csproj +``` + +#### [Tutorial two: Work Queues](https://www.rabbitmq.com/tutorials/tutorial-two-dotnet.html) + +```bash +# terminal tab 1 +dotnet run --project Worker/Worker.csproj + +# terminal tab 2 +dotnet run --project Worker/Worker.csproj + +# terminal tab 3 +dotnet run --project NewTask/NewTask.csproj "First Message" +dotnet run --project NewTask/NewTask.csproj "Second Message" +dotnet run --project NewTask/NewTask.csproj "Third Message" +dotnet run --project NewTask/NewTask.csproj "Fourth Message" +dotnet run --project NewTask/NewTask.csproj "Fifth Message" +``` + +#### [Tutorial three: Publish/Subscribe](https://www.rabbitmq.com/tutorials/tutorial-three-dotnet.html) + +```bash +# terminal tab 1 +dotnet run --project ReceiveLogs/ReceiveLogs.csproj + +# terminal tab 2 +dotnet run --project ReceiveLogs/ReceiveLogs.csproj + +# terminal tab 3 +dotnet run --project EmitLog/EmitLog.csproj +``` + +#### [Tutorial four: Routing](https://www.rabbitmq.com/tutorials/tutorial-four-dotnet.html) + +```bash +# terminal tab 1 +dotnet run --project ReceiveLogsDirect/ReceiveLogsDirect.csproj warning error + +# terminal tab 2 +dotnet run --project ReceiveLogsDirect/ReceiveLogsDirect.csproj info warning error -#### [Tutorial one: "Hello World!"](http://www.rabbitmq.com/tutorial-one-dotnet.html) +# terminal tab 3 +dotnet run --project EmitLogDirect/EmitLogDirect.csproj info "Run. Run. Or it will explode." +dotnet run --project EmitLogDirect/EmitLogDirect.csproj warning "Run. Run. Or it will explode." +dotnet run --project EmitLogDirect/EmitLogDirect.csproj error "Run. Run. Or it will explode." +``` - dotnet run -p Receive/Receive.csproj - dotnet run -p Send/Send.csproj +#### [Tutorial five: Topics](https://www.rabbitmq.com/tutorials/tutorial-five-dotnet.html) -#### [Tutorial two: Work Queues](http://www.rabbitmq.com/tutorial-two-dotnet.html) +```bash +# terminal tab 1 +# To receive all the logs: +dotnet run --project ReceiveLogsTopic/ReceiveLogsTopic.csproj "#" - dotnet run -p Worker/Worker.csproj - dotnet run -p NewTask/NewTask.csproj +# To receive all logs from the facility "kern": +dotnet run --project ReceiveLogsTopic/ReceiveLogsTopic.csproj "kern.*" -#### [Tutorial three: Publish/Subscribe](http://www.rabbitmq.com/tutorial-three-dotnet.html) +# Or if you want to hear only about "critical" logs: +dotnet run --project ReceiveLogsTopic/ReceiveLogsTopic.csproj "*.critical" - dotnet run -p ReceiveLogs/ReceiveLogs.csproj - dotnet run -p EmitLog/EmitLog.csproj +# You can create multiple bindings: +dotnet run --project ReceiveLogsTopic/ReceiveLogsTopic.csproj "kern.*" "*.critical" -#### [Tutorial four: Routing](http://www.rabbitmq.com/tutorial-four-dotnet.html) +# terminal tab 2 +# And to emit a log with a routing key "kern.critical" type: +dotnet run --project EmitLogTopic/EmitLogTopic.csproj kern.critical "A critical kernel error" +``` - dotnet run -p ReceiveLogsDirect/ReceiveLogsDirect.csproj info - dotnet run -p EmitLogDirect/EmitLogDirect.csproj +#### [Tutorial six: RPC](https://www.rabbitmq.com/tutorials/tutorial-six-dotnet.html) -#### [Tutorial five: Topics](http://www.rabbitmq.com/tutorial-five-dotnet.html) +```bash +# terminal tab 1 +# Our RPC service is now ready. We can start the server: +dotnet run --project RPCServer/RPCServer.csproj - dotnet run -p ReceiveLogsTopic/ReceiveLogsTopic.csproj anonymous.info - dotnet run -p EmitLogTopic/EmitLogTopic.csproj +# terminal tab 2 +# To request a fibonacci number run the client: +dotnet run --project RPCClient/RPCClient.csproj +``` -#### [Tutorial six: RPC](http://www.rabbitmq.com/tutorial-six-dotnet.html) +#### [Tutorial seven: Publisher Confirms](https://www.rabbitmq.com/tutorials/tutorial-seven-dotnet.html) - dotnet run -p RPCServer/RPCServer.csproj - dotnet run -p RPCClient/RPCClient.csproj +```bash +# terminal tab 1 +dotnet run --project PublisherConfirms/PublisherConfirms.csproj +``` diff --git a/dotnet/RPCClient/RPCClient.cs b/dotnet/RPCClient/RPCClient.cs index a683d8f3..7e3239ae 100644 --- a/dotnet/RPCClient/RPCClient.cs +++ b/dotnet/RPCClient/RPCClient.cs @@ -1,85 +1,121 @@ -using System; -using System.Threading.Tasks; -using System.Text; using RabbitMQ.Client; using RabbitMQ.Client.Events; +using System.Collections.Concurrent; +using System.Text; -public class RpcClient +public class RpcClient : IAsyncDisposable { - private readonly IConnection connection; - private readonly IModel channel; - private readonly string replyQueueName; - private readonly EventingBasicConsumer consumer; + private const string QUEUE_NAME = "rpc_queue"; + + private readonly IConnectionFactory _connectionFactory; + private readonly ConcurrentDictionary> _callbackMapper + = new(); + + private IConnection? _connection; + private IChannel? _channel; + private string? _replyQueueName; public RpcClient() { - var factory = new ConnectionFactory() { HostName = "localhost" }; - - connection = factory.CreateConnection(); - channel = connection.CreateModel(); - replyQueueName = channel.QueueDeclare().QueueName; - consumer = new EventingBasicConsumer(channel); + _connectionFactory = new ConnectionFactory { HostName = "localhost" }; } - public string Call(string message) + public async Task StartAsync() { - var tcs = new TaskCompletionSource(); - var resultTask = tcs.Task; - - var correlationId = Guid.NewGuid().ToString(); + _connection = await _connectionFactory.CreateConnectionAsync(); + _channel = await _connection.CreateChannelAsync(); - IBasicProperties props = channel.CreateBasicProperties(); - props.CorrelationId = correlationId; - props.ReplyTo = replyQueueName; + // declare a server-named queue + QueueDeclareOk queueDeclareResult = await _channel.QueueDeclareAsync(); + _replyQueueName = queueDeclareResult.QueueName; + var consumer = new AsyncEventingBasicConsumer(_channel); - EventHandler handler = null; - handler = (model, ea) => + consumer.ReceivedAsync += (model, ea) => { - if (ea.BasicProperties.CorrelationId == correlationId) + string? correlationId = ea.BasicProperties.CorrelationId; + + if (false == string.IsNullOrEmpty(correlationId)) { - consumer.Received -= handler; + if (_callbackMapper.TryRemove(correlationId, out var tcs)) + { + var body = ea.Body.ToArray(); + var response = Encoding.UTF8.GetString(body); + tcs.TrySetResult(response); + } + } - var body = ea.Body; - var response = Encoding.UTF8.GetString(body); + return Task.CompletedTask; + }; - tcs.SetResult(response); - } + await _channel.BasicConsumeAsync(_replyQueueName, true, consumer); + } + + public async Task CallAsync(string message, + CancellationToken cancellationToken = default) + { + if (_channel is null) + { + throw new InvalidOperationException(); + } + + string correlationId = Guid.NewGuid().ToString(); + var props = new BasicProperties + { + CorrelationId = correlationId, + ReplyTo = _replyQueueName }; - consumer.Received += handler; - var messageBytes = Encoding.UTF8.GetBytes(message); - channel.BasicPublish( - exchange: "", - routingKey: "rpc_queue", - basicProperties: props, - body: messageBytes); + var tcs = new TaskCompletionSource( + TaskCreationOptions.RunContinuationsAsynchronously); + _callbackMapper.TryAdd(correlationId, tcs); + var messageBytes = Encoding.UTF8.GetBytes(message); + await _channel.BasicPublishAsync(exchange: string.Empty, routingKey: QUEUE_NAME, + mandatory: true, basicProperties: props, body: messageBytes); - channel.BasicConsume( - consumer: consumer, - queue: replyQueueName, - autoAck: true); + using CancellationTokenRegistration ctr = + cancellationToken.Register(() => + { + _callbackMapper.TryRemove(correlationId, out _); + tcs.SetCanceled(); + }); - return resultTask.Result; + return await tcs.Task; } - public void Close() + public async ValueTask DisposeAsync() { - connection.Close(); + if (_channel is not null) + { + await _channel.CloseAsync(); + } + + if (_connection is not null) + { + await _connection.CloseAsync(); + } } } public class Rpc { - public static void Main() + public static async Task Main(string[] args) { - var rpcClient = new RpcClient(); + Console.WriteLine("RPC Client"); + string n = args.Length > 0 ? args[0] : "30"; + await InvokeAsync(n); - Console.WriteLine(" [x] Requesting fib(30)"); - var response = rpcClient.Call("30"); + Console.WriteLine(" Press [enter] to exit."); + Console.ReadLine(); + } + private static async Task InvokeAsync(string n) + { + var rpcClient = new RpcClient(); + await rpcClient.StartAsync(); + + Console.WriteLine(" [x] Requesting fib({0})", n); + var response = await rpcClient.CallAsync(n); Console.WriteLine(" [.] Got '{0}'", response); - rpcClient.Close(); } } - diff --git a/dotnet/RPCClient/RPCClient.csproj b/dotnet/RPCClient/RPCClient.csproj old mode 100755 new mode 100644 index 5c1a2a15..ec2c1b54 --- a/dotnet/RPCClient/RPCClient.csproj +++ b/dotnet/RPCClient/RPCClient.csproj @@ -1,17 +1,14 @@  - netcoreapp1.0 - portable - RPCClient + net8.0 Exe - RPCClient - $(PackageTargetFallback);dnxcore50 - 1.0.4 + enable + enable - - + + diff --git a/dotnet/RPCServer/RPCServer.cs b/dotnet/RPCServer/RPCServer.cs index 15b9853f..6dbf23f9 100644 --- a/dotnet/RPCServer/RPCServer.cs +++ b/dotnet/RPCServer/RPCServer.cs @@ -1,67 +1,67 @@ -using System; using RabbitMQ.Client; using RabbitMQ.Client.Events; using System.Text; -class RPCServer -{ - public static void Main() - { - var factory = new ConnectionFactory() { HostName = "localhost" }; - using (var connection = factory.CreateConnection()) - using (var channel = connection.CreateModel()) - { - channel.QueueDeclare(queue: "rpc_queue", durable: false, exclusive: false, autoDelete: false, arguments: null); - channel.BasicQos(0, 1, false); - var consumer = new EventingBasicConsumer(channel); - channel.BasicConsume(queue: "rpc_queue", autoAck: false, consumer: consumer); - Console.WriteLine(" [x] Awaiting RPC requests"); +const string QUEUE_NAME = "rpc_queue"; - consumer.Received += (model, ea) => - { - string response = null; +var factory = new ConnectionFactory { HostName = "localhost" }; +using var connection = await factory.CreateConnectionAsync(); +using var channel = await connection.CreateChannelAsync(); - var body = ea.Body; - var props = ea.BasicProperties; - var replyProps = channel.CreateBasicProperties(); - replyProps.CorrelationId = props.CorrelationId; +await channel.QueueDeclareAsync(queue: QUEUE_NAME, durable: true, exclusive: false, + autoDelete: false, arguments: new Dictionary { { "x-queue-type", "quorum" } }); - try - { - var message = Encoding.UTF8.GetString(body); - int n = int.Parse(message); - Console.WriteLine(" [.] fib({0})", message); - response = fib(n).ToString(); - } - catch (Exception e) - { - Console.WriteLine(" [.] " + e.Message); - response = ""; - } - finally - { - var responseBytes = Encoding.UTF8.GetBytes(response); - channel.BasicPublish(exchange: "", routingKey: props.ReplyTo, basicProperties: replyProps, body: responseBytes); - channel.BasicAck(deliveryTag: ea.DeliveryTag, multiple: false); - } - }; +await channel.BasicQosAsync(prefetchSize: 0, prefetchCount: 1, global: false); - Console.WriteLine(" Press [enter] to exit."); - Console.ReadLine(); - } - } +var consumer = new AsyncEventingBasicConsumer(channel); +consumer.ReceivedAsync += async (object sender, BasicDeliverEventArgs ea) => +{ + AsyncEventingBasicConsumer cons = (AsyncEventingBasicConsumer)sender; + IChannel ch = cons.Channel; + string response = string.Empty; - /// - /// Assumes only valid positive integer input. - /// Don't expect this one to work for big numbers, and it's probably the slowest recursive implementation possible. - /// - private static int fib(int n) + byte[] body = ea.Body.ToArray(); + IReadOnlyBasicProperties props = ea.BasicProperties; + var replyProps = new BasicProperties { - if (n == 0 || n == 1) - { - return n; - } + CorrelationId = props.CorrelationId + }; - return fib(n - 1) + fib(n - 2); + try + { + var message = Encoding.UTF8.GetString(body); + int n = int.Parse(message); + Console.WriteLine($" [.] Fib({message})"); + response = Fib(n).ToString(); + } + catch (Exception e) + { + Console.WriteLine($" [.] {e.Message}"); + response = string.Empty; } + finally + { + var responseBytes = Encoding.UTF8.GetBytes(response); + await ch.BasicPublishAsync(exchange: string.Empty, routingKey: props.ReplyTo!, + mandatory: true, basicProperties: replyProps, body: responseBytes); + await ch.BasicAckAsync(deliveryTag: ea.DeliveryTag, multiple: false); + } +}; + +await channel.BasicConsumeAsync(QUEUE_NAME, false, consumer); +Console.WriteLine(" [x] Awaiting RPC requests"); +Console.WriteLine(" Press [enter] to exit."); +Console.ReadLine(); + +// Assumes only valid positive integer input. +// Don't expect this one to work for big numbers, +// and it's probably the slowest recursive implementation possible. +static int Fib(int n) +{ + if (n is 0 or 1) + { + return n; + } + + return Fib(n - 1) + Fib(n - 2); } diff --git a/dotnet/RPCServer/RPCServer.csproj b/dotnet/RPCServer/RPCServer.csproj old mode 100755 new mode 100644 index 749b7f56..871d4d85 --- a/dotnet/RPCServer/RPCServer.csproj +++ b/dotnet/RPCServer/RPCServer.csproj @@ -1,17 +1,14 @@  - netcoreapp1.0 - portable - RPCServer + net8.0 Exe - RPCServer - $(PackageTargetFallback);dnxcore50 - 1.0.4 + enable + enable - - + + diff --git a/dotnet/Receive/Receive.cs b/dotnet/Receive/Receive.cs index 034fa9df..21710731 100644 --- a/dotnet/Receive/Receive.cs +++ b/dotnet/Receive/Receive.cs @@ -1,29 +1,26 @@ using RabbitMQ.Client; using RabbitMQ.Client.Events; -using System; using System.Text; -class Receive +var factory = new ConnectionFactory { HostName = "localhost" }; +using var connection = await factory.CreateConnectionAsync(); +using var channel = await connection.CreateChannelAsync(); + +await channel.QueueDeclareAsync(queue: "hello", durable: true, exclusive: false, autoDelete: false, + arguments: new Dictionary { { "x-queue-type", "quorum" } }); + +Console.WriteLine(" [*] Waiting for messages."); + +var consumer = new AsyncEventingBasicConsumer(channel); +consumer.ReceivedAsync += (model, ea) => { - public static void Main() - { - var factory = new ConnectionFactory() { HostName = "localhost" }; - using(var connection = factory.CreateConnection()) - using(var channel = connection.CreateModel()) - { - channel.QueueDeclare(queue: "hello", durable: false, exclusive: false, autoDelete: false, arguments: null); + var body = ea.Body.ToArray(); + var message = Encoding.UTF8.GetString(body); + Console.WriteLine($" [x] Received {message}"); + return Task.CompletedTask; +}; - var consumer = new EventingBasicConsumer(channel); - consumer.Received += (model, ea) => - { - var body = ea.Body; - var message = Encoding.UTF8.GetString(body); - Console.WriteLine(" [x] Received {0}", message); - }; - channel.BasicConsume(queue: "hello", autoAck: true, consumer: consumer); +await channel.BasicConsumeAsync("hello", autoAck: true, consumer: consumer); - Console.WriteLine(" Press [enter] to exit."); - Console.ReadLine(); - } - } -} +Console.WriteLine(" Press [enter] to exit."); +Console.ReadLine(); diff --git a/dotnet/Receive/Receive.csproj b/dotnet/Receive/Receive.csproj old mode 100755 new mode 100644 index 90cf4040..871d4d85 --- a/dotnet/Receive/Receive.csproj +++ b/dotnet/Receive/Receive.csproj @@ -1,17 +1,14 @@  - netcoreapp1.0 - portable - Receive + net8.0 Exe - Receive - $(PackageTargetFallback);dnxcore50 - 1.0.4 + enable + enable - - + + diff --git a/dotnet/ReceiveLogs/ReceiveLogs.cs b/dotnet/ReceiveLogs/ReceiveLogs.cs index 67200ee2..1af450d6 100644 --- a/dotnet/ReceiveLogs/ReceiveLogs.cs +++ b/dotnet/ReceiveLogs/ReceiveLogs.cs @@ -1,34 +1,31 @@ -using System; using RabbitMQ.Client; using RabbitMQ.Client.Events; using System.Text; -class ReceiveLogs -{ - public static void Main() - { - var factory = new ConnectionFactory() { HostName = "localhost" }; - using(var connection = factory.CreateConnection()) - using(var channel = connection.CreateModel()) - { - channel.ExchangeDeclare(exchange: "logs", type: "fanout"); +var factory = new ConnectionFactory { HostName = "localhost" }; +using var connection = await factory.CreateConnectionAsync(); +using var channel = await connection.CreateChannelAsync(); + +await channel.ExchangeDeclareAsync(exchange: "logs", + type: ExchangeType.Fanout); - var queueName = channel.QueueDeclare().QueueName; - channel.QueueBind(queue: queueName, exchange: "logs", routingKey: ""); +// declare a server-named queue +QueueDeclareOk queueDeclareResult = await channel.QueueDeclareAsync(); +string queueName = queueDeclareResult.QueueName; +await channel.QueueBindAsync(queue: queueName, exchange: "logs", routingKey: string.Empty); - Console.WriteLine(" [*] Waiting for logs."); +Console.WriteLine(" [*] Waiting for logs."); + +var consumer = new AsyncEventingBasicConsumer(channel); +consumer.ReceivedAsync += (model, ea) => +{ + byte[] body = ea.Body.ToArray(); + var message = Encoding.UTF8.GetString(body); + Console.WriteLine($" [x] {message}"); + return Task.CompletedTask; +}; - var consumer = new EventingBasicConsumer(channel); - consumer.Received += (model, ea) => - { - var body = ea.Body; - var message = Encoding.UTF8.GetString(body); - Console.WriteLine(" [x] {0}", message); - }; - channel.BasicConsume(queue: queueName, autoAck: true, consumer: consumer); +await channel.BasicConsumeAsync(queueName, autoAck: true, consumer: consumer); - Console.WriteLine(" Press [enter] to exit."); - Console.ReadLine(); - } - } -} +Console.WriteLine(" Press [enter] to exit."); +Console.ReadLine(); diff --git a/dotnet/ReceiveLogs/ReceiveLogs.csproj b/dotnet/ReceiveLogs/ReceiveLogs.csproj old mode 100755 new mode 100644 index f91282bb..4af64e5f --- a/dotnet/ReceiveLogs/ReceiveLogs.csproj +++ b/dotnet/ReceiveLogs/ReceiveLogs.csproj @@ -1,17 +1,14 @@  - netcoreapp1.0 - portable - ReceiveLogs + net8.0 Exe - ReceiveLogs - $(PackageTargetFallback);dnxcore50 - 1.0.4 + enable + enable - - + + diff --git a/dotnet/ReceiveLogsDirect/ReceiveLogsDirect.cs b/dotnet/ReceiveLogsDirect/ReceiveLogsDirect.cs index ae78cfa4..5c50aebb 100644 --- a/dotnet/ReceiveLogsDirect/ReceiveLogsDirect.cs +++ b/dotnet/ReceiveLogsDirect/ReceiveLogsDirect.cs @@ -1,47 +1,46 @@ -using System; using RabbitMQ.Client; using RabbitMQ.Client.Events; using System.Text; -class ReceiveLogsDirect +if (args.Length < 1) { - public static int Main(string[] args) - { - var factory = new ConnectionFactory() { HostName = "localhost" }; - using(var connection = factory.CreateConnection()) - using(var channel = connection.CreateModel()) - { - channel.ExchangeDeclare(exchange: "direct_logs", type: "direct"); - var queueName = channel.QueueDeclare().QueueName; - - if(args.Length < 1) - { - Console.Error.WriteLine("Usage: {0} [info] [warning] [error]", Environment.GetCommandLineArgs()[0]); - Console.WriteLine(" Press [enter] to exit."); - Console.ReadLine(); - return 1; - } - - foreach(var severity in args) - { - channel.QueueBind(queue: queueName, exchange: "direct_logs", routingKey: severity); - } - - Console.WriteLine(" [*] Waiting for messages."); - - var consumer = new EventingBasicConsumer(channel); - consumer.Received += (model, ea) => - { - var body = ea.Body; - var message = Encoding.UTF8.GetString(body); - var routingKey = ea.RoutingKey; - Console.WriteLine(" [x] Received '{0}':'{1}'", routingKey, message); - }; - channel.BasicConsume(queue: queueName, autoAck: true, consumer: consumer); - - Console.WriteLine(" Press [enter] to exit."); - Console.ReadLine(); - return 0; - } - } + Console.Error.WriteLine("Usage: {0} [info] [warning] [error]", + Environment.GetCommandLineArgs()[0]); + Console.WriteLine(" Press [enter] to exit."); + Console.ReadLine(); + Environment.ExitCode = 1; + return; } + +var factory = new ConnectionFactory { HostName = "localhost" }; + +using var connection = await factory.CreateConnectionAsync(); +using var channel = await connection.CreateChannelAsync(); + +await channel.ExchangeDeclareAsync(exchange: "direct_logs", type: ExchangeType.Direct); + +// declare a server-named queue +var queueDeclareResult = await channel.QueueDeclareAsync(); +string queueName = queueDeclareResult.QueueName; + +foreach (string? severity in args) +{ + await channel.QueueBindAsync(queue: queueName, exchange: "direct_logs", routingKey: severity); +} + +Console.WriteLine(" [*] Waiting for messages."); + +var consumer = new AsyncEventingBasicConsumer(channel); +consumer.ReceivedAsync += (model, ea) => +{ + var body = ea.Body.ToArray(); + var message = Encoding.UTF8.GetString(body); + var routingKey = ea.RoutingKey; + Console.WriteLine($" [x] Received '{routingKey}':'{message}'"); + return Task.CompletedTask; +}; + +await channel.BasicConsumeAsync(queueName, autoAck: true, consumer: consumer); + +Console.WriteLine(" Press [enter] to exit."); +Console.ReadLine(); diff --git a/dotnet/ReceiveLogsDirect/ReceiveLogsDirect.csproj b/dotnet/ReceiveLogsDirect/ReceiveLogsDirect.csproj old mode 100755 new mode 100644 index 251221fb..c97f8ed9 --- a/dotnet/ReceiveLogsDirect/ReceiveLogsDirect.csproj +++ b/dotnet/ReceiveLogsDirect/ReceiveLogsDirect.csproj @@ -1,17 +1,14 @@  - netcoreapp1.0 - portable - ReceiveLogsDirect + net8.0 Exe - ReceiveLogsDirect - $(PackageTargetFallback);dnxcore50 - 1.0.4 + enable + enable - - + + diff --git a/dotnet/ReceiveLogsTopic/ReceiveLogsTopic.cs b/dotnet/ReceiveLogsTopic/ReceiveLogsTopic.cs index b848c483..97feb8dc 100644 --- a/dotnet/ReceiveLogsTopic/ReceiveLogsTopic.cs +++ b/dotnet/ReceiveLogsTopic/ReceiveLogsTopic.cs @@ -1,47 +1,46 @@ -using System; -using RabbitMQ.Client; -using RabbitMQ.Client.Events; -using System.Text; - -class ReceiveLogsTopic -{ - public static int Main(string[] args) - { - var factory = new ConnectionFactory() { HostName = "localhost" }; - using(var connection = factory.CreateConnection()) - using(var channel = connection.CreateModel()) - { - channel.ExchangeDeclare(exchange: "topic_logs", type: "topic"); - var queueName = channel.QueueDeclare().QueueName; - - if(args.Length < 1) - { - Console.Error.WriteLine("Usage: {0} [binding_key...]", Environment.GetCommandLineArgs()[0]); - Console.WriteLine(" Press [enter] to exit."); - Console.ReadLine(); - return 1; - } - - foreach(var bindingKey in args) - { - channel.QueueBind(queue: queueName, exchange: "topic_logs", routingKey: bindingKey); - } - - Console.WriteLine(" [*] Waiting for messages. To exit press CTRL+C"); - - var consumer = new EventingBasicConsumer(channel); - consumer.Received += (model, ea) => - { - var body = ea.Body; - var message = Encoding.UTF8.GetString(body); - var routingKey = ea.RoutingKey; - Console.WriteLine(" [x] Received '{0}':'{1}'", routingKey, message); - }; - channel.BasicConsume(queue: queueName, autoAck: true, consumer: consumer); - - Console.WriteLine(" Press [enter] to exit."); - Console.ReadLine(); - return 0; - } - } -} +using RabbitMQ.Client; +using RabbitMQ.Client.Events; +using System.Text; + +if (args.Length < 1) +{ + Console.Error.WriteLine("Usage: {0} [binding_key...]", + Environment.GetCommandLineArgs()[0]); + Console.WriteLine(" Press [enter] to exit."); + Console.ReadLine(); + Environment.ExitCode = 1; + return; +} + +var factory = new ConnectionFactory { HostName = "localhost" }; + +using var connection = await factory.CreateConnectionAsync(); +using var channel = await connection.CreateChannelAsync(); + +await channel.ExchangeDeclareAsync(exchange: "topic_logs", type: ExchangeType.Topic); + +// declare a server-named queue +QueueDeclareOk queueDeclareResult = await channel.QueueDeclareAsync(); +string queueName = queueDeclareResult.QueueName; + +foreach (string? bindingKey in args) +{ + await channel.QueueBindAsync(queue: queueName, exchange: "topic_logs", routingKey: bindingKey); +} + +Console.WriteLine(" [*] Waiting for messages. To exit press CTRL+C"); + +var consumer = new AsyncEventingBasicConsumer(channel); +consumer.ReceivedAsync += (model, ea) => +{ + var body = ea.Body.ToArray(); + var message = Encoding.UTF8.GetString(body); + var routingKey = ea.RoutingKey; + Console.WriteLine($" [x] Received '{routingKey}':'{message}'"); + return Task.CompletedTask; +}; + +await channel.BasicConsumeAsync(queueName, autoAck: true, consumer: consumer); + +Console.WriteLine(" Press [enter] to exit."); +Console.ReadLine(); diff --git a/dotnet/ReceiveLogsTopic/ReceiveLogsTopic.csproj b/dotnet/ReceiveLogsTopic/ReceiveLogsTopic.csproj old mode 100755 new mode 100644 index b1545ba1..c97f8ed9 --- a/dotnet/ReceiveLogsTopic/ReceiveLogsTopic.csproj +++ b/dotnet/ReceiveLogsTopic/ReceiveLogsTopic.csproj @@ -1,17 +1,14 @@  - netcoreapp1.0 - portable - ReceiveLogsTopic + net8.0 Exe - ReceiveLogsTopic - $(PackageTargetFallback);dnxcore50 - 1.0.4 + enable + enable - - + + diff --git a/dotnet/Receiver b/dotnet/Receiver deleted file mode 100755 index a05ac538..00000000 --- a/dotnet/Receiver +++ /dev/null @@ -1,20 +0,0 @@ -{ - "version": "1.0.0-*", - "buildOptions": { - "debugType": "portable", - "emitEntryPoint": true - }, - "dependencies": {}, - "frameworks": { - "netcoreapp1.0": { - "dependencies": { - "Microsoft.NETCore.App": { - "type": "platform", - "version": "1.0.0" - }, - "RabbitMQ.Client": "5.0.*" - }, - "imports": "dnxcore50" - } - } -} diff --git a/dotnet/Send/Send.cs b/dotnet/Send/Send.cs index cf2144f1..e1bb6a9c 100644 --- a/dotnet/Send/Send.cs +++ b/dotnet/Send/Send.cs @@ -1,25 +1,18 @@ -using System; using RabbitMQ.Client; using System.Text; -class Send -{ - public static void Main() - { - var factory = new ConnectionFactory() { HostName = "localhost" }; - using(var connection = factory.CreateConnection()) - using(var channel = connection.CreateModel()) - { - channel.QueueDeclare(queue: "hello", durable: false, exclusive: false, autoDelete: false, arguments: null); +var factory = new ConnectionFactory { HostName = "localhost" }; +using var connection = await factory.CreateConnectionAsync(); +using var channel = await connection.CreateChannelAsync(); - string message = "Hello World!"; - var body = Encoding.UTF8.GetBytes(message); +await channel.QueueDeclareAsync(queue: "hello", durable: true, exclusive: false, autoDelete: false, + arguments: new Dictionary { { "x-queue-type", "quorum" } }); - channel.BasicPublish(exchange: "", routingKey: "hello", basicProperties: null, body: body); - Console.WriteLine(" [x] Sent {0}", message); - } +const string message = "Hello World!"; +var body = Encoding.UTF8.GetBytes(message); - Console.WriteLine(" Press [enter] to exit."); - Console.ReadLine(); - } -} +await channel.BasicPublishAsync(exchange: string.Empty, routingKey: "hello", body: body); +Console.WriteLine($" [x] Sent {message}"); + +Console.WriteLine(" Press [enter] to exit."); +Console.ReadLine(); diff --git a/dotnet/Send/Send.csproj b/dotnet/Send/Send.csproj old mode 100755 new mode 100644 index 1755ffcf..2f9b2487 --- a/dotnet/Send/Send.csproj +++ b/dotnet/Send/Send.csproj @@ -1,17 +1,14 @@  - netcoreapp1.0 - portable - Send + net8.0 Exe - Send - $(PackageTargetFallback);dnxcore50 - 1.0.4 + enable + enable - - + + diff --git a/dotnet/Worker/Worker.cs b/dotnet/Worker/Worker.cs index 5773df61..647508d4 100644 --- a/dotnet/Worker/Worker.cs +++ b/dotnet/Worker/Worker.cs @@ -1,41 +1,35 @@ -using System; using RabbitMQ.Client; using RabbitMQ.Client.Events; using System.Text; -using System.Threading; -class Worker +var factory = new ConnectionFactory { HostName = "localhost" }; +using var connection = await factory.CreateConnectionAsync(); +using var channel = await connection.CreateChannelAsync(); + +await channel.QueueDeclareAsync(queue: "task_queue", durable: true, exclusive: false, + autoDelete: false, arguments: new Dictionary { { "x-queue-type", "quorum" } }); + +await channel.BasicQosAsync(prefetchSize: 0, prefetchCount: 1, global: false); + +Console.WriteLine(" [*] Waiting for messages."); + +var consumer = new AsyncEventingBasicConsumer(channel); +consumer.ReceivedAsync += async (model, ea) => { - public static void Main() - { - var factory = new ConnectionFactory() { HostName = "localhost" }; - using(var connection = factory.CreateConnection()) - using(var channel = connection.CreateModel()) - { - channel.QueueDeclare(queue: "task_queue", durable: true, exclusive: false, autoDelete: false, arguments: null); - - channel.BasicQos(prefetchSize: 0, prefetchCount: 1, global: false); - - Console.WriteLine(" [*] Waiting for messages."); - - var consumer = new EventingBasicConsumer(channel); - consumer.Received += (model, ea) => - { - var body = ea.Body; - var message = Encoding.UTF8.GetString(body); - Console.WriteLine(" [x] Received {0}", message); - - int dots = message.Split('.').Length - 1; - Thread.Sleep(dots * 1000); - - Console.WriteLine(" [x] Done"); - - channel.BasicAck(deliveryTag: ea.DeliveryTag, multiple: false); - }; - channel.BasicConsume(queue: "task_queue", autoAck: false, consumer: consumer); - - Console.WriteLine(" Press [enter] to exit."); - Console.ReadLine(); - } - } -} + byte[] body = ea.Body.ToArray(); + var message = Encoding.UTF8.GetString(body); + Console.WriteLine($" [x] Received {message}"); + + int dots = message.Split('.').Length - 1; + await Task.Delay(dots * 1000); + + Console.WriteLine(" [x] Done"); + + // here channel could also be accessed as ((AsyncEventingBasicConsumer)sender).Channel + await channel.BasicAckAsync(deliveryTag: ea.DeliveryTag, multiple: false); +}; + +await channel.BasicConsumeAsync("task_queue", autoAck: false, consumer: consumer); + +Console.WriteLine(" Press [enter] to exit."); +Console.ReadLine(); diff --git a/dotnet/Worker/Worker.csproj b/dotnet/Worker/Worker.csproj old mode 100755 new mode 100644 index 4b72257b..3e47c5b7 --- a/dotnet/Worker/Worker.csproj +++ b/dotnet/Worker/Worker.csproj @@ -1,17 +1,14 @@  - netcoreapp1.0 - portable - Worker + net8.0 Exe - Worker - $(PackageTargetFallback);dnxcore50 - 1.0.4 + enable + enable - - + + diff --git a/dotnet/dotnet.sln b/dotnet/dotnet.sln new file mode 100644 index 00000000..9b3c5c16 --- /dev/null +++ b/dotnet/dotnet.sln @@ -0,0 +1,103 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.0.31717.71 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EmitLog", "EmitLog\EmitLog.csproj", "{38520F1F-5F59-4D6C-815A-BCB811B8335E}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EmitLogDirect", "EmitLogDirect\EmitLogDirect.csproj", "{303D712F-9107-41D9-A855-46A281DA250C}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EmitLogTopic", "EmitLogTopic\EmitLogTopic.csproj", "{E1A0AF83-F505-483E-9F22-C0A137E4B190}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NewTask", "NewTask\NewTask.csproj", "{7575E7F7-4C5E-42A2-894D-DDCD16D964DB}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PublisherConfirms", "PublisherConfirms\PublisherConfirms.csproj", "{0BBAF99D-B711-4201-B30B-114D88EDD276}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Receive", "Receive\Receive.csproj", "{BE7FEFA8-EA23-496F-B772-8EF9CC9A21D9}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ReceiveLogs", "ReceiveLogs\ReceiveLogs.csproj", "{63E5ECF3-C78E-4461-8247-33F3BF019EF1}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ReceiveLogsDirect", "ReceiveLogsDirect\ReceiveLogsDirect.csproj", "{D16666BC-C69F-49D4-96B9-3DC562CC11C4}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RPCClient", "RPCClient\RPCClient.csproj", "{6161C019-58A6-4A72-B3F8-D1A1FD2681E8}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Send", "Send\Send.csproj", "{1BA2D501-C1AC-40B4-B540-2EBC9482BA2C}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Worker", "Worker\Worker.csproj", "{0B7F6DF9-E9E5-454F-B8F4-3CE332ECB983}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RPCServer", "RPCServer\RPCServer.csproj", "{7317B766-BBD8-402B-82E3-1DBAA376FA64}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ReceiveLogsTopic", "ReceiveLogsTopic\ReceiveLogsTopic.csproj", "{497698C7-9D59-478F-9394-265915455F00}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Folder", "Solution Folder", "{853BA576-BFDF-4547-BA70-BC7781EDAB10}" + ProjectSection(SolutionItems) = preProject + .gitignore = .gitignore + README.md = README.md + EndProjectSection +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {38520F1F-5F59-4D6C-815A-BCB811B8335E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {38520F1F-5F59-4D6C-815A-BCB811B8335E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {38520F1F-5F59-4D6C-815A-BCB811B8335E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {38520F1F-5F59-4D6C-815A-BCB811B8335E}.Release|Any CPU.Build.0 = Release|Any CPU + {303D712F-9107-41D9-A855-46A281DA250C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {303D712F-9107-41D9-A855-46A281DA250C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {303D712F-9107-41D9-A855-46A281DA250C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {303D712F-9107-41D9-A855-46A281DA250C}.Release|Any CPU.Build.0 = Release|Any CPU + {E1A0AF83-F505-483E-9F22-C0A137E4B190}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E1A0AF83-F505-483E-9F22-C0A137E4B190}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E1A0AF83-F505-483E-9F22-C0A137E4B190}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E1A0AF83-F505-483E-9F22-C0A137E4B190}.Release|Any CPU.Build.0 = Release|Any CPU + {7575E7F7-4C5E-42A2-894D-DDCD16D964DB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7575E7F7-4C5E-42A2-894D-DDCD16D964DB}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7575E7F7-4C5E-42A2-894D-DDCD16D964DB}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7575E7F7-4C5E-42A2-894D-DDCD16D964DB}.Release|Any CPU.Build.0 = Release|Any CPU + {0BBAF99D-B711-4201-B30B-114D88EDD276}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0BBAF99D-B711-4201-B30B-114D88EDD276}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0BBAF99D-B711-4201-B30B-114D88EDD276}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0BBAF99D-B711-4201-B30B-114D88EDD276}.Release|Any CPU.Build.0 = Release|Any CPU + {BE7FEFA8-EA23-496F-B772-8EF9CC9A21D9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {BE7FEFA8-EA23-496F-B772-8EF9CC9A21D9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {BE7FEFA8-EA23-496F-B772-8EF9CC9A21D9}.Release|Any CPU.ActiveCfg = Release|Any CPU + {BE7FEFA8-EA23-496F-B772-8EF9CC9A21D9}.Release|Any CPU.Build.0 = Release|Any CPU + {63E5ECF3-C78E-4461-8247-33F3BF019EF1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {63E5ECF3-C78E-4461-8247-33F3BF019EF1}.Debug|Any CPU.Build.0 = Debug|Any CPU + {63E5ECF3-C78E-4461-8247-33F3BF019EF1}.Release|Any CPU.ActiveCfg = Release|Any CPU + {63E5ECF3-C78E-4461-8247-33F3BF019EF1}.Release|Any CPU.Build.0 = Release|Any CPU + {D16666BC-C69F-49D4-96B9-3DC562CC11C4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D16666BC-C69F-49D4-96B9-3DC562CC11C4}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D16666BC-C69F-49D4-96B9-3DC562CC11C4}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D16666BC-C69F-49D4-96B9-3DC562CC11C4}.Release|Any CPU.Build.0 = Release|Any CPU + {6161C019-58A6-4A72-B3F8-D1A1FD2681E8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6161C019-58A6-4A72-B3F8-D1A1FD2681E8}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6161C019-58A6-4A72-B3F8-D1A1FD2681E8}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6161C019-58A6-4A72-B3F8-D1A1FD2681E8}.Release|Any CPU.Build.0 = Release|Any CPU + {1BA2D501-C1AC-40B4-B540-2EBC9482BA2C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1BA2D501-C1AC-40B4-B540-2EBC9482BA2C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1BA2D501-C1AC-40B4-B540-2EBC9482BA2C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1BA2D501-C1AC-40B4-B540-2EBC9482BA2C}.Release|Any CPU.Build.0 = Release|Any CPU + {0B7F6DF9-E9E5-454F-B8F4-3CE332ECB983}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0B7F6DF9-E9E5-454F-B8F4-3CE332ECB983}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0B7F6DF9-E9E5-454F-B8F4-3CE332ECB983}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0B7F6DF9-E9E5-454F-B8F4-3CE332ECB983}.Release|Any CPU.Build.0 = Release|Any CPU + {7317B766-BBD8-402B-82E3-1DBAA376FA64}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7317B766-BBD8-402B-82E3-1DBAA376FA64}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7317B766-BBD8-402B-82E3-1DBAA376FA64}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7317B766-BBD8-402B-82E3-1DBAA376FA64}.Release|Any CPU.Build.0 = Release|Any CPU + {497698C7-9D59-478F-9394-265915455F00}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {497698C7-9D59-478F-9394-265915455F00}.Debug|Any CPU.Build.0 = Debug|Any CPU + {497698C7-9D59-478F-9394-265915455F00}.Release|Any CPU.ActiveCfg = Release|Any CPU + {497698C7-9D59-478F-9394-265915455F00}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {22E2792A-85EF-4497-AA49-1A9C62A33CAC} + EndGlobalSection +EndGlobal diff --git a/dotnet/recompile.sh b/dotnet/recompile.sh deleted file mode 100755 index d67be133..00000000 --- a/dotnet/recompile.sh +++ /dev/null @@ -1,6 +0,0 @@ -#!/bin/sh - -for f in */; do \ - dotnet restore $f - dotnet build $f -done diff --git a/elixir-stream/README.md b/elixir-stream/README.md new file mode 100644 index 00000000..9ea32398 --- /dev/null +++ b/elixir-stream/README.md @@ -0,0 +1,25 @@ +# Elixir code for RabbitMQ tutorials + +Here you can find Elixir code examples from [RabbitMQ tutorials](https://www.rabbitmq.com/getstarted.html). + +## Requirements + +These examples use the [`VictorGaiva/rabbitmq-stream`](https://github.com/VictorGaiva/rabbitmq-stream) client library. + +The dependencies are installed during the exection of the examples using `Mix.install/1` + +## Code + +Code examples are executed via `elixir`: + +Tutorial one: "Hello World!": + +``` shell +# run the publisher +elixir publish.exs + +# run the consumer +elixir consume.exs +``` + +To learn more, see [`VictorGaiva/rabbitmq-stream`](https://github.com/VictorGaiva/rabbitmq-stream). diff --git a/elixir-stream/consume.exs b/elixir-stream/consume.exs new file mode 100755 index 00000000..42fea4c2 --- /dev/null +++ b/elixir-stream/consume.exs @@ -0,0 +1,27 @@ +#! /usr/bin/env elixir +require Logger + +# Installing the rabbitmq_stream Library +Mix.install([ + {:rabbitmq_stream, "~> 0.4.1"} +]) + +# First we start a Connection to the RabbitMQ Server +{:ok, connection} = RabbitMQStream.Connection.start_link() + +# We can assume the stream doesn't exist yet, and attempt to create it. If it already exists, +# it should be still be good to go. +RabbitMQStream.Connection.create_stream(connection, "my_stream") + +# Now we can subscribe to the stream, receiving up to 1 chunk. +{:ok, subscription_id} = + RabbitMQStream.Connection.subscribe(connection, "my_stream", self(), :first, 1) + +# Now we can consume the messages +receive do + # Each 'deliver' data comes inside a Chunk, which may contain multiple messages + {:deliver, %{subscription_id: ^subscription_id, osiris_chunk: chunk}} -> + for message <- chunk.data_entries do + Logger.info("Received: #{inspect(message)}") + end +end diff --git a/elixir-stream/publish.exs b/elixir-stream/publish.exs new file mode 100755 index 00000000..820347fa --- /dev/null +++ b/elixir-stream/publish.exs @@ -0,0 +1,31 @@ +#! /usr/bin/env elixir +require Logger + +# Installing the rabbitmq_stream Library +Mix.install([ + {:rabbitmq_stream, "~> 0.4.1"} +]) + +# First we start a Connection to the RabbitMQ Server +{:ok, connection} = RabbitMQStream.Connection.start_link() + +# We can assume the stream doesn't exist yet, and attempt to create it. If it already exists, +# it should be still be good to go. +RabbitMQStream.Connection.create_stream(connection, "my_stream") + +# Before publishing a message, we need to declare a producer. It is required by the +# RabbitMQ Sever to prevent message duplication. +{:ok, producer_id} = + RabbitMQStream.Connection.declare_producer(connection, "my_stream", "my_producer") + +# Each producer has a sequence number, that must must be published with the message, and +# incremented after each message. +{:ok, sequence_number} = + RabbitMQStream.Connection.query_producer_sequence(connection, "my_stream", "my_producer") + +# Now we can publish a message. Note that we only specify the producer_id and sequence number. +# The target Stream is already tracked by the server based on the producer_id. +:ok = + RabbitMQStream.Connection.publish(connection, producer_id, sequence_number + 1, "Hello, World!") + +Logger.info("Published: \"Hello, World!\"") diff --git a/elixir/README.md b/elixir/README.md index 5ed63119..e7a4350f 100644 --- a/elixir/README.md +++ b/elixir/README.md @@ -1,12 +1,12 @@ # Elixir code for RabbitMQ tutorials -Here you can find an [Elixir](http://elixir-lang.org) port of -[RabbitMQ tutorials](http://www.rabbitmq.com/getstarted.html). +Here you can find an [Elixir](https://elixir-lang.org) port of +[RabbitMQ tutorials](https://www.rabbitmq.com/getstarted.html). ## Requirements -To run this code you need a [recent Elixir version installed](http://elixir-lang.org/install.html), -which should include [Mix, the Elixir build tool](http://elixir-lang.org/docs/stable/mix/Mix.html). +To run this code you need a [recent Elixir version installed](https://elixir-lang.org/install.html), +which should include [Mix, the Elixir build tool](https://elixir-lang.org/docs/stable/mix/Mix.html). These tutorials use [Elixir AMQP 0-9-1 client](https://github.com/pma/amqp) built on top of the official [RabbitMQ Erlang client](https://www.rabbitmq.com/erlang-client-user-guide.html). diff --git a/elixir/config/config.exs b/elixir/config/config.exs index b5025a60..becde769 100644 --- a/elixir/config/config.exs +++ b/elixir/config/config.exs @@ -1,6 +1 @@ -use Mix.Config - -config :lager, - handlers: [ - lager_console_backend: [{:level, :warning}] - ] +import Config diff --git a/elixir/mix.exs b/elixir/mix.exs index e6ac1b0f..43455894 100644 --- a/elixir/mix.exs +++ b/elixir/mix.exs @@ -3,8 +3,8 @@ defmodule RabbitmqTutorials.Mixfile do def project do [app: :rabbitmq_tutorials, - version: "0.0.1", - elixir: "~> 1.1", + version: "1.0.0", + elixir: "~> 1.5", build_embedded: Mix.env == :prod, start_permanent: Mix.env == :prod, deps: deps()] @@ -28,7 +28,7 @@ defmodule RabbitmqTutorials.Mixfile do # Type "mix help deps" for more examples and options defp deps do [ - {:amqp, "~> 1.0"}, + {:amqp, "~> 4.1"}, ] end end diff --git a/elixir/new_task.exs b/elixir/new_task.exs index 6bb70db2..f9a7ce80 100644 --- a/elixir/new_task.exs +++ b/elixir/new_task.exs @@ -1,7 +1,7 @@ {:ok, connection} = AMQP.Connection.open {:ok, channel} = AMQP.Channel.open(connection) -AMQP.Queue.declare(channel, "task_queue", durable: true) +AMQP.Queue.declare(channel, "task_queue", durable: true, arguments: [{"x-queue-type", :longstr, "quorum"}]) message = case System.argv do diff --git a/elixir/receive.exs b/elixir/receive.exs index 692f1359..a9fd44d2 100644 --- a/elixir/receive.exs +++ b/elixir/receive.exs @@ -10,7 +10,7 @@ end {:ok, connection} = AMQP.Connection.open {:ok, channel} = AMQP.Channel.open(connection) -AMQP.Queue.declare(channel, "hello") +AMQP.Queue.declare(channel, "hello", durable: true, arguments: [{"x-queue-type", :longstr, "quorum"}]) AMQP.Basic.consume(channel, "hello", nil, no_ack: true) IO.puts " [*] Waiting for messages. To exit press CTRL+C, CTRL+C" diff --git a/elixir/rpc_server.exs b/elixir/rpc_server.exs index 8ac68b82..f698867b 100644 --- a/elixir/rpc_server.exs +++ b/elixir/rpc_server.exs @@ -21,7 +21,7 @@ end {:ok, connection} = AMQP.Connection.open {:ok, channel} = AMQP.Channel.open(connection) -AMQP.Queue.declare(channel, "rpc_queue") +AMQP.Queue.declare(channel, "rpc_queue", durable: true, arguments: [{"x-queue-type", :longstr, "quorum"}]) AMQP.Basic.qos(channel, prefetch_count: 1) diff --git a/elixir/send.exs b/elixir/send.exs index 4a84a10f..1845a781 100644 --- a/elixir/send.exs +++ b/elixir/send.exs @@ -1,6 +1,6 @@ {:ok, connection} = AMQP.Connection.open {:ok, channel} = AMQP.Channel.open(connection) -AMQP.Queue.declare(channel, "hello") +AMQP.Queue.declare(channel, "hello", durable: true, arguments: [{"x-queue-type", :longstr, "quorum"}]) AMQP.Basic.publish(channel, "", "hello", "Hello World!") IO.puts " [x] Sent 'Hello World!'" AMQP.Connection.close(connection) diff --git a/elixir/worker.exs b/elixir/worker.exs index f59b1ec2..556fbd0f 100644 --- a/elixir/worker.exs +++ b/elixir/worker.exs @@ -19,7 +19,7 @@ end {:ok, connection} = AMQP.Connection.open {:ok, channel} = AMQP.Channel.open(connection) -AMQP.Queue.declare(channel, "task_queue", durable: true) +AMQP.Queue.declare(channel, "task_queue", durable: true, arguments: [{"x-queue-type", :longstr, "quorum"}]) AMQP.Basic.qos(channel, prefetch_count: 1) AMQP.Basic.consume(channel, "task_queue") diff --git a/erlang/.gitignore b/erlang/.gitignore new file mode 100644 index 00000000..df53f7d9 --- /dev/null +++ b/erlang/.gitignore @@ -0,0 +1,20 @@ +.rebar3 +_build +_checkouts +_vendor +.eunit +*.o +*.beam +*.plt +*.swp +*.swo +.erlang.cookie +ebin +log +erl_crash.dump +.rebar +logs +.idea +*.iml +rebar3.crashdump +*~ diff --git a/erlang/README.md b/erlang/README.md index 666a4121..9d2a5ca7 100644 --- a/erlang/README.md +++ b/erlang/README.md @@ -1,58 +1,54 @@ # Erlang code for RabbitMQ tutorials # Here you can find a Erlang code examples from [RabbitMQ -tutorials](http://www.rabbitmq.com/getstarted.html). +tutorials](https://www.rabbitmq.com/getstarted.html). This code is using [RabbitMQ Erlang -Client](http://hg.rabbitmq.com/rabbitmq-erlang-client/) ([User -Guide](http://www.rabbitmq.com/erlang-client-user-guide.html)). +Client](https://github.com/rabbitmq/rabbitmq-server/tree/main/deps/amqp_client) ([User +Guide](https://www.rabbitmq.com/erlang-client-user-guide.html)). ## Requirements To run this code you need at least [Erlang -R13B03](http://erlang.org/download.html), on Ubuntu you can get it +R13B03](https://www.erlang.org/downloads), on Ubuntu you can get it using apt: sudo apt-get install erlang -You need Erlang Client binaries: +You also need rebar3: https://www.rebar3.org/docs/getting-started/ - wget https://www.rabbitmq.com/releases/rabbitmq-erlang-client/v3.6.14/rabbit_common-3.6.14.ez - unzip rabbit_common-3.6.14.ez - ln -s rabbit_common-3.6.14 rabbit_common +You need Erlang Client binaries: - wget https://www.rabbitmq.com/releases/rabbitmq-erlang-client/v3.6.14/amqp_client-3.6.14.ez - unzip amqp_client-3.6.14.ez - ln -s amqp_client-3.6.14 amqp_client + rebar3 compile - wget https://www.rabbitmq.com/releases/rabbitmq-erlang-client/v3.6.14/recon-2.3.2.ez - unzip recon-2.3.2.ez - ln -s recon-2.3.2 recon +## Code +[Tutorial one: "Hello World!"](https://www.rabbitmq.com/tutorials/tutorial-one-python.html): -## Code + rebar3 send + rebar3 recv -[Tutorial one: "Hello World!"](http://www.rabbitmq.com/tutorial-one-python.html): +[Tutorial two: Work Queues](https://www.rabbitmq.com/tutorials/tutorial-two-python.html): - ./send.erl - ./receive.erl + rebar3 new_task + rebar3 worker -[Tutorial two: Work Queues](http://www.rabbitmq.com/tutorial-two-python.html): +[Tutorial three: Publish/Subscribe](https://www.rabbitmq.com/tutorials/tutorial-three-python.html): - ./new_task.erl "A very hard task which takes two seconds.." - ./worker.erl + rebar3 receive_logs + rebar3 emit_log -[Tutorial three: Publish/Subscribe](http://www.rabbitmq.com/tutorial-three-python.html): +[Tutorial four: Routing](https://www.rabbitmq.com/tutorials/tutorial-four-python.html): - ./receive_logs.erl - ./emit_log.erl "info: This is the log message" + rebar3 receive_logs_direct + rebar3 emit_log_direct -[Tutorial four: Routing](http://www.rabbitmq.com/tutorial-four-python.html): +[Tutorial five: Topics](https://www.rabbitmq.com/tutorials/tutorial-five-python.html): - ./receive_logs_direct.erl info - ./emit_log_direct.erl info Hello + rebar3 receive_logs_topic + rebar3 emit_log_topic -[Tutorial five: Topics](http://www.rabbitmq.com/tutorial-five-python.html): +[Tutorial Six: RPC](https://www.rabbitmq.com/tutorials/tutorial-six-python.html): - ./receive_logs_topic.erl "*.rabbit" - ./emit_log_topic.erl red.rabbit Hello + rebar3 rpc_server + rebar3 rpc_client diff --git a/erlang/rebar.config b/erlang/rebar.config new file mode 100644 index 00000000..c49b4e0e --- /dev/null +++ b/erlang/rebar.config @@ -0,0 +1,34 @@ +{erl_opts, [debug_info]}. +{deps, [ + {amqp_client, "4.2.1"}, + {uuid, {git, "https://github.com/okeuday/uuid.git", {tag, "v2.0.7.1"}}} +]}. + +{plugins, [rebar_alias]}. + +{alias, [ + {send, [compile, {shell, + "--eval 'send:start(), init:stop().'"}]}, + {recv, [compile, {shell, + "--eval 'recv:start(), init:stop().'"}]}, + {new_task, [compile, {shell, + "--eval 'new_task:start([\"A very hard task which takes two seconds\"]), init:stop().'"}]}, + {worker, [compile, {shell, + "--eval 'worker:start(), init:stop().'"}]}, + {receive_logs, [compile, + {shell, "--eval 'receive_logs:start(), init:stop().'"}]}, + {emit_log, [compile, {shell, + "--eval 'emit_log:start([\"Info: This is the log message\"]), init:stop().'"}]}, + {receive_logs_direct, [compile, {shell, + "--eval 'receive_logs_direct:start([\"Info\"]), init:stop().'"}]}, + {emit_log_direct, [compile, {shell, + "--eval 'emit_log_direct:start([\"Info\", \"Hello\"]), init:stop().'"}]}, + {receive_logs_topic, [compile, {shell, + "--eval 'receive_logs_topic:start([\"*.rabbit\"]), init:stop().'"}]}, + {emit_log_topic, [compile, {shell, + "--eval 'emit_log_topic:start([\"red.rabbit\", \"Hello\"]), init:stop().'"}]}, + {rpc_server, [compile, {shell, + "--eval 'rpc_server:start(), init:stop().'"}]}, + {rpc_client, [compile, {shell, + "--eval 'rpc_client:start([\"10\"]), init:stop().'"}]} +]}. diff --git a/erlang/rebar.lock b/erlang/rebar.lock new file mode 100644 index 00000000..29c7c4fe --- /dev/null +++ b/erlang/rebar.lock @@ -0,0 +1,33 @@ +{"1.2.0", +[{<<"amqp_client">>,{pkg,<<"amqp_client">>,<<"4.2.1">>},0}, + {<<"credentials_obfuscation">>, + {pkg,<<"credentials_obfuscation">>,<<"3.5.0">>}, + 1}, + {<<"quickrand">>, + {git,"https://github.com/okeuday/quickrand.git", + {ref,"65332de501998764f437c3ffe05d744f582d7622"}}, + 1}, + {<<"rabbit_common">>,{pkg,<<"rabbit_common">>,<<"4.2.1">>},1}, + {<<"ranch">>,{pkg,<<"ranch">>,<<"2.2.0">>},2}, + {<<"recon">>,{pkg,<<"recon">>,<<"2.5.6">>},2}, + {<<"thoas">>,{pkg,<<"thoas">>,<<"1.2.1">>},2}, + {<<"uuid">>, + {git,"https://github.com/okeuday/uuid.git", + {ref,"63e32cdad70693495163ab131456905e827a5e36"}}, + 0}]}. +[ +{pkg_hash,[ + {<<"amqp_client">>, <<"CFF0CC13186E57457DC5745F1B3A4127C6857717CB8F5920DC457C84D0AD00A2">>}, + {<<"credentials_obfuscation">>, <<"61E282ADFB4439486B3994FAAEC69543C7EE6CC7E70C6340E8853FD9DEAF8219">>}, + {<<"rabbit_common">>, <<"1D64E391E12116B76B1425EB96B7552DE51F0301093EBA669B5334F4759CC1E8">>}, + {<<"ranch">>, <<"25528F82BC8D7C6152C57666CA99EC716510FE0925CB188172F41CE93117B1B0">>}, + {<<"recon">>, <<"9052588E83BFEDFD9B72E1034532AEE2A5369D9D9343B61AEB7FBCE761010741">>}, + {<<"thoas">>, <<"19A25F31177A17E74004D4840F66D791D4298C5738790FA2CC73731EB911F195">>}]}, +{pkg_hash_ext,[ + {<<"amqp_client">>, <<"8AE00B055A58500E0557F73D9C0FFE257487131E603F7F84FE72CBFAAF03838A">>}, + {<<"credentials_obfuscation">>, <<"843ADBE3246861CE0F1A0FA3222F384834EB31DEFD8D6B9CBA7AFD2977C957BC">>}, + {<<"rabbit_common">>, <<"FF509B07E639B1784898C28031E5204FEA14260172E4FC339F94405586037E40">>}, + {<<"ranch">>, <<"FA0B99A1780C80218A4197A59EA8D3BDAE32FBFF7E88527D7D8A4787EFF4F8E7">>}, + {<<"recon">>, <<"96C6799792D735CC0F0FD0F86267E9D351E63339CBE03DF9D162010CEFC26BB0">>}, + {<<"thoas">>, <<"E38697EDFFD6E91BD12CEA41B155115282630075C2A727E7A6B2947F5408B86A">>}]} +]. diff --git a/erlang/emit_log.erl b/erlang/src/emit_log.erl old mode 100755 new mode 100644 similarity index 74% rename from erlang/emit_log.erl rename to erlang/src/emit_log.erl index 2c29e00d..3fdd272e --- a/erlang/emit_log.erl +++ b/erlang/src/emit_log.erl @@ -1,9 +1,9 @@ -#!/usr/bin/env escript -%%! -pz ./amqp_client ./rabbit_common ./amqp_client/ebin ./rabbit_common/ebin ./recon/ebin +-module(emit_log). +-export([start/1]). -include_lib("amqp_client/include/amqp_client.hrl"). -main(Argv) -> +start(Argv) -> {ok, Connection} = amqp_connection:start(#amqp_params_network{host = "localhost"}), {ok, Channel} = amqp_connection:open_channel(Connection), @@ -12,9 +12,9 @@ main(Argv) -> type = <<"fanout">>}), Message = case Argv of - [] -> <<"info: Hello World!">>; - Msg -> list_to_binary(string:join(Msg, " ")) - end, + [] -> <<"info: Hello World!">>; + Msg -> list_to_binary(string:join(Msg, " ")) + end, amqp_channel:cast(Channel, #'basic.publish'{exchange = <<"logs">>}, #amqp_msg{payload = Message}), diff --git a/erlang/emit_log_direct.erl b/erlang/src/emit_log_direct.erl old mode 100755 new mode 100644 similarity index 82% rename from erlang/emit_log_direct.erl rename to erlang/src/emit_log_direct.erl index 721e984f..a0518f69 --- a/erlang/emit_log_direct.erl +++ b/erlang/src/emit_log_direct.erl @@ -1,9 +1,9 @@ -#!/usr/bin/env escript -%%! -pz ./amqp_client ./rabbit_common ./amqp_client/ebin ./rabbit_common/ebin ./recon/ebin +-module(emit_log_direct). +-export([start/1]). -include_lib("amqp_client/include/amqp_client.hrl"). -main(Argv) -> +start(Argv) -> {ok, Connection} = amqp_connection:start(#amqp_params_network{host = "localhost"}), {ok, Channel} = amqp_connection:open_channel(Connection), @@ -21,8 +21,8 @@ main(Argv) -> end, amqp_channel:cast(Channel, #'basic.publish'{ - exchange = <<"direct_logs">>, - routing_key = Severity}, + exchange = <<"direct_logs">>, + routing_key = Severity}, #amqp_msg{payload = Message}), io:format(" [x] Sent ~p:~p~n", [Severity, Message]), ok = amqp_channel:close(Channel), diff --git a/erlang/emit_log_topic.erl b/erlang/src/emit_log_topic.erl old mode 100755 new mode 100644 similarity index 82% rename from erlang/emit_log_topic.erl rename to erlang/src/emit_log_topic.erl index 4fcdd15a..860fdbb8 --- a/erlang/emit_log_topic.erl +++ b/erlang/src/emit_log_topic.erl @@ -1,9 +1,9 @@ -#!/usr/bin/env escript -%%! -pz ./amqp_client ./rabbit_common ./amqp_client/ebin ./rabbit_common/ebin ./recon/ebin +-module(emit_log_topic). +-export([start/1]). -include_lib("amqp_client/include/amqp_client.hrl"). -main(Argv) -> +start(Argv) -> {ok, Connection} = amqp_connection:start(#amqp_params_network{host = "localhost"}), {ok, Channel} = amqp_connection:open_channel(Connection), @@ -21,8 +21,8 @@ main(Argv) -> end, amqp_channel:cast(Channel, #'basic.publish'{ - exchange = <<"topic_logs">>, - routing_key = RoutingKey}, + exchange = <<"topic_logs">>, + routing_key = RoutingKey}, #amqp_msg{payload = Message}), io:format(" [x] Sent ~p:~p~n", [RoutingKey, Message]), ok = amqp_channel:close(Channel), diff --git a/erlang/src/erlang.app.src b/erlang/src/erlang.app.src new file mode 100644 index 00000000..ec71d3f9 --- /dev/null +++ b/erlang/src/erlang.app.src @@ -0,0 +1,13 @@ +{application, erlang, [ + {description, "An OTP library"}, + {vsn, "0.1.0"}, + {registered, []}, + {applications, [ + kernel, + stdlib + ]}, + {env, []}, + {modules, []}, + {licenses, ["Apache-2.0"]}, + {links, []} + ]}. diff --git a/erlang/new_task.erl b/erlang/src/new_task.erl old mode 100755 new mode 100644 similarity index 78% rename from erlang/new_task.erl rename to erlang/src/new_task.erl index 8b5ad0f9..31056451 --- a/erlang/new_task.erl +++ b/erlang/src/new_task.erl @@ -1,9 +1,9 @@ -#!/usr/bin/env escript -%%! -pz ./amqp_client ./rabbit_common ./amqp_client/ebin ./rabbit_common/ebin ./recon/ebin +-module(new_task). +-export([start/1]). -include_lib("amqp_client/include/amqp_client.hrl"). -main(Argv) -> +start(Argv) -> {ok, Connection} = amqp_connection:start(#amqp_params_network{host = "localhost"}), {ok, Channel} = amqp_connection:open_channel(Connection), @@ -17,8 +17,8 @@ main(Argv) -> end, amqp_channel:cast(Channel, #'basic.publish'{ - exchange = <<"">>, - routing_key = <<"task_queue">>}, + exchange = <<"">>, + routing_key = <<"task_queue">>}, #amqp_msg{props = #'P_basic'{delivery_mode = 2}, payload = Message}), io:format(" [x] Sent ~p~n", [Message]), diff --git a/erlang/receive_logs.erl b/erlang/src/receive_logs.erl old mode 100755 new mode 100644 similarity index 89% rename from erlang/receive_logs.erl rename to erlang/src/receive_logs.erl index 25b2b852..522222f0 --- a/erlang/receive_logs.erl +++ b/erlang/src/receive_logs.erl @@ -1,9 +1,9 @@ -#!/usr/bin/env escript -%%! -pz ./amqp_client ./rabbit_common ./amqp_client/ebin ./rabbit_common/ebin ./recon/ebin +-module(receive_logs). +-export([start/0]). -include_lib("amqp_client/include/amqp_client.hrl"). -main(_) -> +start() -> {ok, Connection} = amqp_connection:start(#amqp_params_network{host = "localhost"}), {ok, Channel} = amqp_connection:open_channel(Connection), diff --git a/erlang/receive_logs_direct.erl b/erlang/src/receive_logs_direct.erl old mode 100755 new mode 100644 similarity index 90% rename from erlang/receive_logs_direct.erl rename to erlang/src/receive_logs_direct.erl index 956653f6..f9730e63 --- a/erlang/receive_logs_direct.erl +++ b/erlang/src/receive_logs_direct.erl @@ -1,9 +1,9 @@ -#!/usr/bin/env escript -%%! -pz ./amqp_client ./rabbit_common ./amqp_client/ebin ./rabbit_common/ebin ./recon/ebin +-module(receive_logs_direct). +-export([start/1]). -include_lib("amqp_client/include/amqp_client.hrl"). -main(Argv) -> +start(Argv) -> {ok, Connection} = amqp_connection:start(#amqp_params_network{host = "localhost"}), {ok, Channel} = amqp_connection:open_channel(Connection), diff --git a/erlang/receive_logs_topic.erl b/erlang/src/receive_logs_topic.erl old mode 100755 new mode 100644 similarity index 90% rename from erlang/receive_logs_topic.erl rename to erlang/src/receive_logs_topic.erl index 3c01fe70..153cc6ae --- a/erlang/receive_logs_topic.erl +++ b/erlang/src/receive_logs_topic.erl @@ -1,9 +1,9 @@ -#!/usr/bin/env escript -%%! -pz ./amqp_client ./rabbit_common ./amqp_client/ebin ./rabbit_common/ebin ./recon/ebin +-module(receive_logs_topic). +-export([start/1]). -include_lib("amqp_client/include/amqp_client.hrl"). -main(Argv) -> +start(Argv) -> {ok, Connection} = amqp_connection:start(#amqp_params_network{host = "localhost"}), {ok, Channel} = amqp_connection:open_channel(Connection), diff --git a/erlang/receive.erl b/erlang/src/recv.erl old mode 100755 new mode 100644 similarity index 61% rename from erlang/receive.erl rename to erlang/src/recv.erl index 640b4295..55fe0126 --- a/erlang/receive.erl +++ b/erlang/src/recv.erl @@ -1,26 +1,25 @@ -#!/usr/bin/env escript -%%! -pz ./amqp_client ./rabbit_common ./amqp_client/ebin ./rabbit_common/ebin ./recon/ebin +-module(recv). +-export([start/0]). -include_lib("amqp_client/include/amqp_client.hrl"). -main(_) -> +start() -> {ok, Connection} = - amqp_connection:start(#amqp_params_network{host = "localhost"}), + amqp_connection:start(#amqp_params_network{host = "localhost", heartbeat = 30}), {ok, Channel} = amqp_connection:open_channel(Connection), amqp_channel:call(Channel, #'queue.declare'{queue = <<"hello">>}), io:format(" [*] Waiting for messages. To exit press CTRL+C~n"), - amqp_channel:subscribe(Channel, #'basic.consume'{queue = <<"hello">>, - no_ack = true}, self()), - receive - #'basic.consume_ok'{} -> ok - end, + Method = #'basic.consume'{queue = <<"hello">>, no_ack = true}, + amqp_channel:subscribe(Channel, Method, self()), loop(Channel). - loop(Channel) -> receive + #'basic.consume_ok'{} -> + io:format(" [x] Saw basic.consume_ok~n"), + loop(Channel); {#'basic.deliver'{}, #amqp_msg{payload = Body}} -> io:format(" [x] Received ~p~n", [Body]), loop(Channel) diff --git a/erlang/src/rpc_client.erl b/erlang/src/rpc_client.erl new file mode 100644 index 00000000..8e1f8ea4 --- /dev/null +++ b/erlang/src/rpc_client.erl @@ -0,0 +1,58 @@ +-module(rpc_client). +-export([start/1]). + +-include_lib("amqp_client/include/amqp_client.hrl"). + +start(Argv) -> + Num = case Argv of + [] -> 10; + [Arg] -> list_to_integer(Arg) + end, + io:format(" [x] Requesting fib(~p)~n", [Num]), + Response = call(Num), + io:format(" [.] Got ~p~n", [Response]), + ok. + +call(Num) -> + {ok, Connection} = + amqp_connection:start(#amqp_params_network{host = "localhost"}), + {ok, Channel} = amqp_connection:open_channel(Connection), + RequestQueue = <<"rpc_queue">>, + CorrelationId = uuid:get_v4(), + + amqp_channel:call(Channel, #'queue.declare'{queue = RequestQueue}), + + #'queue.declare_ok'{queue = ReplyQueue} = + amqp_channel:call(Channel, #'queue.declare'{exclusive = true}), + + Method = #'basic.consume'{queue = ReplyQueue, no_ack = true}, + amqp_channel:subscribe(Channel, Method, self()), + + amqp_channel:cast(Channel, + #'basic.publish'{ + exchange = <<>>, + routing_key = RequestQueue}, + #amqp_msg{ + payload = integer_to_binary(Num), + props = #'P_basic'{ + reply_to = ReplyQueue, + correlation_id = CorrelationId} + }), + + Response = wait_for_messages(CorrelationId), + + amqp_channel:close(Channel), + amqp_connection:close(Connection), + + Response. + +wait_for_messages(CorrelationId) -> + receive + {#'basic.deliver'{}, #amqp_msg{payload = Body, props = Props}} -> + #'P_basic'{correlation_id = CorrId} = Props, + if CorrelationId == CorrId -> + binary_to_integer(Body); + true -> + -1 + end + end. diff --git a/erlang/src/rpc_server.erl b/erlang/src/rpc_server.erl new file mode 100644 index 00000000..0a568d6f --- /dev/null +++ b/erlang/src/rpc_server.erl @@ -0,0 +1,49 @@ +-module(rpc_server). +-export([start/0]). + +-include_lib("amqp_client/include/amqp_client.hrl"). + +start() -> + {ok, Connection} = + amqp_connection:start(#amqp_params_network{host = "localhost", heartbeat = 30}), + {ok, Channel} = amqp_connection:open_channel(Connection), + + amqp_channel:call(Channel, #'queue.declare'{queue = <<"rpc_queue">>}), + io:format(" [*] Waiting for messages. To exit press CTRL+C~n"), + + amqp_channel:call(Channel, #'basic.qos'{prefetch_count = 1}), + + Method = #'basic.consume'{queue = <<"rpc_queue">>}, + amqp_channel:subscribe(Channel, Method, self()), + loop(Channel). + +loop(Channel) -> + receive + {#'basic.deliver'{delivery_tag = DeliveryTag}, #amqp_msg{payload = Body, props = Props}} -> + #'P_basic'{reply_to = ReplyTo, correlation_id = CorrelationId} = Props, + Num = binary_to_integer(Body), + io:format(" [.] fib(~p)~n", [Num]), + Response = fib(Num), + + amqp_channel:cast(Channel, + #'basic.publish'{ + exchange = <<>>, + routing_key = ReplyTo}, + #amqp_msg{ + payload = integer_to_binary(Response), + props = #'P_basic'{ + correlation_id = CorrelationId} + }), + + amqp_channel:cast(Channel, + #'basic.ack'{ + delivery_tag = DeliveryTag + }), + + loop(Channel) + end. + +fib(0) -> 0; +fib(1) -> 1; +fib(N) when N > 1 -> fib(N-1) + fib(N-2); +fib(N) when N =< 0 -> -1. \ No newline at end of file diff --git a/erlang/send.erl b/erlang/src/send.erl old mode 100755 new mode 100644 similarity index 71% rename from erlang/send.erl rename to erlang/src/send.erl index f5a0f4e2..078ec173 --- a/erlang/send.erl +++ b/erlang/src/send.erl @@ -1,9 +1,9 @@ -#!/usr/bin/env escript -%%! -pz ./amqp_client ./rabbit_common ./amqp_client/ebin ./rabbit_common/ebin ./recon/ebin +-module(send). +-export([start/0]). -include_lib("amqp_client/include/amqp_client.hrl"). -main(_) -> +start() -> {ok, Connection} = amqp_connection:start(#amqp_params_network{host = "localhost"}), {ok, Channel} = amqp_connection:open_channel(Connection), @@ -12,8 +12,8 @@ main(_) -> amqp_channel:cast(Channel, #'basic.publish'{ - exchange = <<"">>, - routing_key = <<"hello">>}, + exchange = <<"">>, + routing_key = <<"hello">>}, #amqp_msg{payload = <<"Hello World!">>}), io:format(" [x] Sent 'Hello World!'~n"), ok = amqp_channel:close(Channel), diff --git a/erlang/worker.erl b/erlang/src/worker.erl old mode 100755 new mode 100644 similarity index 88% rename from erlang/worker.erl rename to erlang/src/worker.erl index 866397a9..ffbb01a2 --- a/erlang/worker.erl +++ b/erlang/src/worker.erl @@ -1,9 +1,9 @@ -#!/usr/bin/env escript -%%! -pz ./amqp_client ./rabbit_common ./amqp_client/ebin ./rabbit_common/ebin ./recon/ebin +-module(worker). +-export([start/0]). -include_lib("amqp_client/include/amqp_client.hrl"). -main(_) -> +start() -> {ok, Connection} = amqp_connection:start(#amqp_params_network{host = "localhost"}), {ok, Channel} = amqp_connection:open_channel(Connection), @@ -18,7 +18,8 @@ main(_) -> receive #'basic.consume_ok'{} -> ok end, - loop(Channel). + loop(Channel), + io:format("GOOD BYE~n"). loop(Channel) -> receive diff --git a/go-amqp/.gitignore b/go-amqp/.gitignore new file mode 100644 index 00000000..8af6f160 --- /dev/null +++ b/go-amqp/.gitignore @@ -0,0 +1,2 @@ +pkg/* +src/* diff --git a/go-amqp/README.md b/go-amqp/README.md new file mode 100644 index 00000000..76b0f9d1 --- /dev/null +++ b/go-amqp/README.md @@ -0,0 +1,58 @@ +# Go code for RabbitMQ tutorials (AMQP 1.0) + + +Here you can find Go code examples from [RabbitMQ tutorials](https://www.rabbitmq.com/getstarted.html), using the AMQP 1.0 client. + + +## Requirements + +These examples use the [`rabbitmq-amqp-go-client`](https://github.com/rabbitmq/rabbitmq-amqp-go-client) library for RabbitMQ 4.x. Get it with + + go get github.com/rabbitmq/rabbitmq-amqp-go-client + +A RabbitMQ node must be running on `localhost` with the default port (`5672`) and credentials (`guest` / `guest`). + + +## Code + +Run each example from this directory with `go run`: + +[Tutorial one: "Hello World!"](https://www.rabbitmq.com/tutorials/tutorial-one-go.html): + + go run send.go + go run receive.go + +[Tutorial two: Work Queues](https://www.rabbitmq.com/tutorials/tutorial-two-go.html): + + go run new_task.go hello world + go run worker.go + +[Tutorial three: Publish/Subscribe](https://www.rabbitmq.com/tutorials/tutorial-three-go.html) + + go run receive_logs.go + go run emit_log.go hello world + +[Tutorial four: Routing](https://www.rabbitmq.com/tutorials/tutorial-four-go.html) + + go run receive_logs_direct.go info warn + go run emit_log_direct.go warn "a warning" + +[Tutorial five: Topics](https://www.rabbitmq.com/tutorials/tutorial-five-go.html) + + go run receive_logs_topic.go "kern.*" "*.critical" + go run emit_log_topic.go kern.critical "A critical kernel error" + +[Tutorial six: RPC](https://www.rabbitmq.com/tutorials/tutorial-six-go.html) + + go run rpc_server.go + go run rpc_client.go 10 + +[Publisher confirms](https://www.rabbitmq.com/tutorials/tutorial-seven-java.html) (AMQP 1.0 publish outcomes) + + go run publisher_confirms.go + +[AMQP 1.0 Direct Reply-To RPC](https://www.rabbitmq.com/docs/next/direct-reply-to) + + go run rpc_amqp10.go + +To learn more, see the [package documentation](https://pkg.go.dev/github.com/rabbitmq/rabbitmq-amqp-go-client) and [AMQP in RabbitMQ](https://www.rabbitmq.com/docs/amqp). diff --git a/go-amqp/emit_log.go b/go-amqp/emit_log.go new file mode 100644 index 00000000..4beb174b --- /dev/null +++ b/go-amqp/emit_log.go @@ -0,0 +1,63 @@ +package main + +import ( + "context" + "log" + "os" + "strings" + + rmq "github.com/rabbitmq/rabbitmq-amqp-go-client/pkg/rabbitmqamqp" +) + +const brokerURI = "amqp://guest:guest@localhost:5672/" + +func main() { + ctx := context.Background() + env := rmq.NewEnvironment(brokerURI, nil) + conn, err := env.NewConnection(ctx) + if err != nil { + log.Panicf("Failed to connect to RabbitMQ: %v", err) + } + defer func() { + _ = env.CloseConnections(context.Background()) + }() + + _, err = conn.Management().DeclareExchange(ctx, &rmq.FanOutExchangeSpecification{Name: "logs"}) + if err != nil { + log.Panicf("Failed to declare an exchange: %v", err) + } + + publisher, err := conn.NewPublisher(ctx, &rmq.ExchangeAddress{Exchange: "logs", Key: ""}, nil) + if err != nil { + log.Panicf("Failed to create publisher: %v", err) + } + defer func() { _ = publisher.Close(context.Background()) }() + + body := bodyFrom(os.Args) + res, err := publisher.Publish(ctx, rmq.NewMessage([]byte(body))) + if err != nil { + log.Panicf("Failed to publish a message: %v", err) + } + switch res.Outcome.(type) { + case *rmq.StateAccepted: + case *rmq.StateRejected: + log.Fatalf("Message was rejected: %v", res.Outcome) + case *rmq.StateReleased: + log.Fatalf("Message was released: %v", res.Outcome) + case *rmq.StateModified: + log.Fatalf("Message was modified: %v", res.Outcome) + default: + log.Fatalf("Unexpected publish outcome: %v", res.Outcome) + } + log.Printf(" [x] Sent %s", body) +} + +func bodyFrom(args []string) string { + var s string + if (len(args) < 2) || args[1] == "" { + s = "hello" + } else { + s = strings.Join(args[1:], " ") + } + return s +} diff --git a/go-amqp/emit_log_direct.go b/go-amqp/emit_log_direct.go new file mode 100644 index 00000000..28e54dc9 --- /dev/null +++ b/go-amqp/emit_log_direct.go @@ -0,0 +1,76 @@ +package main + +import ( + "context" + "log" + "os" + "strings" + + rmq "github.com/rabbitmq/rabbitmq-amqp-go-client/pkg/rabbitmqamqp" +) + +const brokerURI = "amqp://guest:guest@localhost:5672/" + +func main() { + ctx := context.Background() + env := rmq.NewEnvironment(brokerURI, nil) + conn, err := env.NewConnection(ctx) + if err != nil { + log.Panicf("Failed to connect to RabbitMQ: %v", err) + } + defer func() { + _ = env.CloseConnections(context.Background()) + }() + + _, err = conn.Management().DeclareExchange(ctx, &rmq.DirectExchangeSpecification{Name: "logs_direct"}) + if err != nil { + log.Panicf("Failed to declare an exchange: %v", err) + } + + publisher, err := conn.NewPublisher(ctx, &rmq.ExchangeAddress{ + Exchange: "logs_direct", + Key: severityFrom(os.Args), + }, nil) + if err != nil { + log.Panicf("Failed to create publisher: %v", err) + } + defer func() { _ = publisher.Close(context.Background()) }() + + body := bodyFrom(os.Args) + res, err := publisher.Publish(ctx, rmq.NewMessage([]byte(body))) + if err != nil { + log.Panicf("Failed to publish a message: %v", err) + } + switch res.Outcome.(type) { + case *rmq.StateAccepted: + case *rmq.StateRejected: + log.Fatalf("Message was rejected: %v", res.Outcome) + case *rmq.StateReleased: + log.Fatalf("Message was released: %v", res.Outcome) + case *rmq.StateModified: + log.Fatalf("Message was modified: %v", res.Outcome) + default: + log.Fatalf("Unexpected publish outcome: %v", res.Outcome) + } + log.Printf(" [x] Sent %s", body) +} + +func bodyFrom(args []string) string { + var s string + if (len(args) < 3) || args[2] == "" { + s = "hello" + } else { + s = strings.Join(args[2:], " ") + } + return s +} + +func severityFrom(args []string) string { + var s string + if (len(args) < 2) || args[1] == "" { + s = "info" + } else { + s = args[1] + } + return s +} diff --git a/go-amqp/emit_log_topic.go b/go-amqp/emit_log_topic.go new file mode 100644 index 00000000..824c463c --- /dev/null +++ b/go-amqp/emit_log_topic.go @@ -0,0 +1,76 @@ +package main + +import ( + "context" + "log" + "os" + "strings" + + rmq "github.com/rabbitmq/rabbitmq-amqp-go-client/pkg/rabbitmqamqp" +) + +const brokerURI = "amqp://guest:guest@localhost:5672/" + +func main() { + ctx := context.Background() + env := rmq.NewEnvironment(brokerURI, nil) + conn, err := env.NewConnection(ctx) + if err != nil { + log.Panicf("Failed to connect to RabbitMQ: %v", err) + } + defer func() { + _ = env.CloseConnections(context.Background()) + }() + + _, err = conn.Management().DeclareExchange(ctx, &rmq.TopicExchangeSpecification{Name: "logs_topic"}) + if err != nil { + log.Panicf("Failed to declare an exchange: %v", err) + } + + publisher, err := conn.NewPublisher(ctx, &rmq.ExchangeAddress{ + Exchange: "logs_topic", + Key: severityFrom(os.Args), + }, nil) + if err != nil { + log.Panicf("Failed to create publisher: %v", err) + } + defer func() { _ = publisher.Close(context.Background()) }() + + body := bodyFrom(os.Args) + res, err := publisher.Publish(ctx, rmq.NewMessage([]byte(body))) + if err != nil { + log.Panicf("Failed to publish a message: %v", err) + } + switch res.Outcome.(type) { + case *rmq.StateAccepted: + case *rmq.StateRejected: + log.Fatalf("Message was rejected: %v", res.Outcome) + case *rmq.StateReleased: + log.Fatalf("Message was released: %v", res.Outcome) + case *rmq.StateModified: + log.Fatalf("Message was modified: %v", res.Outcome) + default: + log.Fatalf("Unexpected publish outcome: %v", res.Outcome) + } + log.Printf(" [x] Sent %s", body) +} + +func bodyFrom(args []string) string { + var s string + if (len(args) < 3) || args[2] == "" { + s = "hello" + } else { + s = strings.Join(args[2:], " ") + } + return s +} + +func severityFrom(args []string) string { + var s string + if (len(args) < 2) || args[1] == "" { + s = "anonymous.info" + } else { + s = args[1] + } + return s +} diff --git a/go-amqp/go.mod b/go-amqp/go.mod new file mode 100644 index 00000000..dfb38625 --- /dev/null +++ b/go-amqp/go.mod @@ -0,0 +1,16 @@ +module github.com/rabbitmq/rabbitmq-tutorials/go-amqp + +go 1.24.0 + +require ( + github.com/Azure/go-amqp v1.5.1 + github.com/rabbitmq/rabbitmq-amqp-go-client v1.0.0 +) + +require ( + github.com/cespare/xxhash/v2 v2.3.0 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/gorilla/websocket v1.5.3 // indirect + go.opentelemetry.io/otel v1.41.0 // indirect + go.opentelemetry.io/otel/metric v1.41.0 // indirect +) diff --git a/go-amqp/go.sum b/go-amqp/go.sum new file mode 100644 index 00000000..5386da58 --- /dev/null +++ b/go-amqp/go.sum @@ -0,0 +1,54 @@ +github.com/Azure/go-amqp v1.5.1 h1:WyiPTz2C3zVvDL7RLAqwWdeoYhMtX62MZzQoP09fzsU= +github.com/Azure/go-amqp v1.5.1/go.mod h1:vZAogwdrkbyK3Mla8m/CxSc/aKdnTZ4IbPxl51Y5WZE= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw= +github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g= +github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= +github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= +github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= +github.com/golang-jwt/jwt/v5 v5.2.2 h1:Rl4B7itRWVtYIHFrSNd7vhTiz9UpLdi6gZhZ3wEeDy8= +github.com/golang-jwt/jwt/v5 v5.2.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 h1:BHT72Gu3keYf3ZEu2J0b1vyeLSOYI8bm5wbJM/8yDe8= +github.com/google/pprof v0.0.0-20250403155104-27863c87afa6/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= +github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/onsi/ginkgo/v2 v2.23.4 h1:ktYTpKJAVZnDT4VjxSbiBenUjmlL/5QkBEocaWXiQus= +github.com/onsi/ginkgo/v2 v2.23.4/go.mod h1:Bt66ApGPBFzHyR+JO10Zbt0Gsp4uWxu5mIOTusL46e8= +github.com/onsi/gomega v1.38.0 h1:c/WX+w8SLAinvuKKQFh77WEucCnPk4j2OTUr7lt7BeY= +github.com/onsi/gomega v1.38.0/go.mod h1:OcXcwId0b9QsE7Y49u+BTrL4IdKOBOKnD6VQNTJEB6o= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rabbitmq/rabbitmq-amqp-go-client v1.0.0 h1:VvrVB+Fz7g0hg0ZcxDXf+WHShQsfQ1iI5FbT5ZnJ3lY= +github.com/rabbitmq/rabbitmq-amqp-go-client v1.0.0/go.mod h1:u2HAJ0fUFyayNcIvTGCTlpmdPKHrKO9Xl81FbiEzYO4= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= +go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= +go.opentelemetry.io/otel v1.41.0 h1:YlEwVsGAlCvczDILpUXpIpPSL/VPugt7zHThEMLce1c= +go.opentelemetry.io/otel v1.41.0/go.mod h1:Yt4UwgEKeT05QbLwbyHXEwhnjxNO6D8L5PQP51/46dE= +go.opentelemetry.io/otel/metric v1.41.0 h1:rFnDcs4gRzBcsO9tS8LCpgR0dxg4aaxWlJxCno7JlTQ= +go.opentelemetry.io/otel/metric v1.41.0/go.mod h1:xPvCwd9pU0VN8tPZYzDZV/BMj9CM9vs00GuBjeKhJps= +go.opentelemetry.io/otel/trace v1.41.0 h1:Vbk2co6bhj8L59ZJ6/xFTskY+tGAbOnCtQGVVa9TIN0= +go.opentelemetry.io/otel/trace v1.41.0/go.mod h1:U1NU4ULCoxeDKc09yCWdWe+3QoyweJcISEVa1RBzOis= +go.uber.org/automaxprocs v1.6.0 h1:O3y2/QNTOdbF+e/dpXNNW7Rx2hZ4sTIPyybbxyNqTUs= +go.uber.org/automaxprocs v1.6.0/go.mod h1:ifeIMSnPZuznNm6jmdzmU3/bfk01Fe2fotchwEFJ8r8= +golang.org/x/net v0.41.0 h1:vBTly1HeNPEn3wtREYfy4GZ/NECgw2Cnl+nK6Nz3uvw= +golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA= +golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ= +golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/text v0.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M= +golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA= +golang.org/x/tools v0.33.0 h1:4qz2S3zmRxbGIhDIAgjxvFutSvH5EfnsYrRBj0UI0bc= +golang.org/x/tools v0.33.0/go.mod h1:CIJMaWEY88juyUfo7UbgPqbC8rU2OqfAV1h2Qp0oMYI= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/go-amqp/new_task.go b/go-amqp/new_task.go new file mode 100644 index 00000000..dbf07792 --- /dev/null +++ b/go-amqp/new_task.go @@ -0,0 +1,63 @@ +package main + +import ( + "context" + "log" + "os" + "strings" + + rmq "github.com/rabbitmq/rabbitmq-amqp-go-client/pkg/rabbitmqamqp" +) + +const brokerURI = "amqp://guest:guest@localhost:5672/" + +func main() { + ctx := context.Background() + env := rmq.NewEnvironment(brokerURI, nil) + conn, err := env.NewConnection(ctx) + if err != nil { + log.Panicf("Failed to connect to RabbitMQ: %v", err) + } + defer func() { + _ = env.CloseConnections(context.Background()) + }() + + _, err = conn.Management().DeclareQueue(ctx, &rmq.QuorumQueueSpecification{Name: "task_queue"}) + if err != nil { + log.Panicf("Failed to declare a queue: %v", err) + } + + publisher, err := conn.NewPublisher(ctx, &rmq.QueueAddress{Queue: "task_queue"}, nil) + if err != nil { + log.Panicf("Failed to create publisher: %v", err) + } + defer func() { _ = publisher.Close(context.Background()) }() + + body := bodyFrom(os.Args) + res, err := publisher.Publish(ctx, rmq.NewMessage([]byte(body))) + if err != nil { + log.Panicf("Failed to publish a message: %v", err) + } + switch res.Outcome.(type) { + case *rmq.StateAccepted: + case *rmq.StateRejected: + log.Fatalf("Message was rejected: %v", res.Outcome) + case *rmq.StateReleased: + log.Fatalf("Message was released: %v", res.Outcome) + case *rmq.StateModified: + log.Fatalf("Message was modified: %v", res.Outcome) + default: + log.Fatalf("Unexpected publish outcome: %v", res.Outcome) + } + log.Printf(" [x] Sent %s", body) +} + +func bodyFrom(args []string) string { + var s string + if (len(args) < 2) || args[1] == "" { + s = "hello" + } else { + s = strings.Join(args[1:], " ") + } + return s +} diff --git a/go-amqp/publisher_confirms.go b/go-amqp/publisher_confirms.go new file mode 100644 index 00000000..765b0530 --- /dev/null +++ b/go-amqp/publisher_confirms.go @@ -0,0 +1,87 @@ +package main + +import ( + "context" + "log" + + rmq "github.com/rabbitmq/rabbitmq-amqp-go-client/pkg/rabbitmqamqp" +) + +const brokerURI = "amqp://guest:guest@localhost:5672/" + +func main() { + ctx := context.Background() + env := rmq.NewEnvironment(brokerURI, nil) + conn, err := env.NewConnection(ctx) + if err != nil { + log.Panicf("Failed to connect to RabbitMQ: %v", err) + } + defer func() { + _ = env.CloseConnections(context.Background()) + }() + + qInfo, err := conn.Management().DeclareQueue(ctx, &rmq.AutoGeneratedQueueSpecification{ + IsExclusive: false, + IsAutoDelete: false, + }) + if err != nil { + log.Panicf("Failed to declare a queue: %v", err) + } + + consume(conn, qInfo.Name()) + publish(conn, qInfo.Name(), "hello") + + log.Printf(" [*] Waiting for messages. To exit press CTRL+C") + select {} +} + +func consume(conn *rmq.AmqpConnection, qName string) { + ctx := context.Background() + consumer, err := conn.NewConsumer(ctx, qName, nil) + if err != nil { + log.Panicf("Failed to create consumer: %v", err) + } + + go func() { + for { + delivery, err := consumer.Receive(ctx) + if err != nil { + log.Printf("consumer receive: %v", err) + return + } + msg := delivery.Message() + var body string + if len(msg.Data) > 0 { + body = string(msg.Data[0]) + } + log.Printf("Received a message: %s", body) + _ = delivery.Accept(ctx) + } + }() +} + +func publish(conn *rmq.AmqpConnection, qName, text string) { + ctx := context.Background() + publisher, err := conn.NewPublisher(ctx, &rmq.QueueAddress{Queue: qName}, nil) + if err != nil { + log.Panicf("Failed to create publisher: %v", err) + } + defer func() { _ = publisher.Close(context.Background()) }() + + res, err := publisher.Publish(ctx, rmq.NewMessage([]byte(text))) + if err != nil { + log.Panicf("Failed to publish a message: %v", err) + } + switch res.Outcome.(type) { + case *rmq.StateAccepted: + log.Printf("Confirmed") + case *rmq.StateRejected: + log.Fatalf("Message was rejected: %v", res.Outcome) + case *rmq.StateReleased: + log.Fatalf("Message was released: %v", res.Outcome) + case *rmq.StateModified: + log.Fatalf("Message was modified: %v", res.Outcome) + default: + log.Fatalf("Unexpected publish outcome: %v", res.Outcome) + } +} diff --git a/go-amqp/receive.go b/go-amqp/receive.go new file mode 100644 index 00000000..40b32344 --- /dev/null +++ b/go-amqp/receive.go @@ -0,0 +1,55 @@ +package main + +import ( + "context" + "errors" + "log" + + rmq "github.com/rabbitmq/rabbitmq-amqp-go-client/pkg/rabbitmqamqp" +) + +const brokerURI = "amqp://guest:guest@localhost:5672/" + +func main() { + ctx := context.Background() + env := rmq.NewEnvironment(brokerURI, nil) + conn, err := env.NewConnection(ctx) + if err != nil { + log.Panicf("Failed to connect to RabbitMQ: %v", err) + } + defer func() { + _ = env.CloseConnections(context.Background()) + }() + + _, err = conn.Management().DeclareQueue(ctx, &rmq.QuorumQueueSpecification{Name: "hello"}) + if err != nil { + log.Panicf("Failed to declare a queue: %v", err) + } + + consumer, err := conn.NewConsumer(ctx, "hello", nil) + if err != nil { + log.Panicf("Failed to create consumer: %v", err) + } + defer func() { _ = consumer.Close(context.Background()) }() + + log.Printf(" [*] Waiting for messages. To exit press CTRL+C") + for { + delivery, err := consumer.Receive(ctx) + if err != nil { + if errors.Is(err, context.Canceled) { + return + } + log.Panicf("Failed to receive a message: %v", err) + } + msg := delivery.Message() + var body string + if len(msg.Data) > 0 { + body = string(msg.Data[0]) + } + log.Printf("Received a message: %s", body) + err = delivery.Accept(ctx) + if err != nil { + log.Panicf("Failed to accept message: %v", err) + } + } +} diff --git a/go-amqp/receive_logs.go b/go-amqp/receive_logs.go new file mode 100644 index 00000000..1842c341 --- /dev/null +++ b/go-amqp/receive_logs.go @@ -0,0 +1,72 @@ +package main + +import ( + "context" + "errors" + "log" + + rmq "github.com/rabbitmq/rabbitmq-amqp-go-client/pkg/rabbitmqamqp" +) + +const brokerURI = "amqp://guest:guest@localhost:5672/" + +func main() { + ctx := context.Background() + env := rmq.NewEnvironment(brokerURI, nil) + conn, err := env.NewConnection(ctx) + if err != nil { + log.Panicf("Failed to connect to RabbitMQ: %v", err) + } + defer func() { + _ = env.CloseConnections(context.Background()) + }() + + _, err = conn.Management().DeclareExchange(ctx, &rmq.FanOutExchangeSpecification{Name: "logs"}) + if err != nil { + log.Panicf("Failed to declare an exchange: %v", err) + } + + qInfo, err := conn.Management().DeclareQueue(ctx, &rmq.AutoGeneratedQueueSpecification{ + IsExclusive: true, + IsAutoDelete: true, + }) + if err != nil { + log.Panicf("Failed to declare a queue: %v", err) + } + + _, err = conn.Management().Bind(ctx, &rmq.ExchangeToQueueBindingSpecification{ + SourceExchange: "logs", + DestinationQueue: qInfo.Name(), + BindingKey: "", + }) + if err != nil { + log.Panicf("Failed to bind a queue: %v", err) + } + + consumer, err := conn.NewConsumer(ctx, qInfo.Name(), nil) + if err != nil { + log.Panicf("Failed to create consumer: %v", err) + } + defer func() { _ = consumer.Close(context.Background()) }() + + log.Printf(" [*] Waiting for logs. To exit press CTRL+C") + for { + delivery, err := consumer.Receive(ctx) + if err != nil { + if errors.Is(err, context.Canceled) { + return + } + log.Panicf("Failed to receive a message: %v", err) + } + msg := delivery.Message() + var body string + if len(msg.Data) > 0 { + body = string(msg.Data[0]) + } + log.Printf(" [x] %s", body) + err = delivery.Accept(ctx) + if err != nil { + log.Panicf("Failed to accept message: %v", err) + } + } +} diff --git a/go-amqp/receive_logs_direct.go b/go-amqp/receive_logs_direct.go new file mode 100644 index 00000000..eaaf6b72 --- /dev/null +++ b/go-amqp/receive_logs_direct.go @@ -0,0 +1,80 @@ +package main + +import ( + "context" + "errors" + "log" + "os" + + rmq "github.com/rabbitmq/rabbitmq-amqp-go-client/pkg/rabbitmqamqp" +) + +const brokerURI = "amqp://guest:guest@localhost:5672/" + +func main() { + ctx := context.Background() + env := rmq.NewEnvironment(brokerURI, nil) + conn, err := env.NewConnection(ctx) + if err != nil { + log.Panicf("Failed to connect to RabbitMQ: %v", err) + } + defer func() { + _ = env.CloseConnections(context.Background()) + }() + + _, err = conn.Management().DeclareExchange(ctx, &rmq.DirectExchangeSpecification{Name: "logs_direct"}) + if err != nil { + log.Panicf("Failed to declare an exchange: %v", err) + } + + qInfo, err := conn.Management().DeclareQueue(ctx, &rmq.AutoGeneratedQueueSpecification{ + IsExclusive: true, + IsAutoDelete: true, + }) + if err != nil { + log.Panicf("Failed to declare a queue: %v", err) + } + + if len(os.Args) < 2 { + log.Printf("Usage: %s [info] [warning] [error]", os.Args[0]) + os.Exit(0) + } + for _, s := range os.Args[1:] { + log.Printf("Binding queue %s to exchange %s with routing key %s", qInfo.Name(), "logs_direct", s) + _, err = conn.Management().Bind(ctx, &rmq.ExchangeToQueueBindingSpecification{ + SourceExchange: "logs_direct", + DestinationQueue: qInfo.Name(), + BindingKey: s, + }) + if err != nil { + log.Panicf("Failed to bind a queue: %v", err) + } + } + + consumer, err := conn.NewConsumer(ctx, qInfo.Name(), nil) + if err != nil { + log.Panicf("Failed to create consumer: %v", err) + } + defer func() { _ = consumer.Close(context.Background()) }() + + log.Printf(" [*] Waiting for logs. To exit press CTRL+C") + for { + delivery, err := consumer.Receive(ctx) + if err != nil { + if errors.Is(err, context.Canceled) { + return + } + log.Panicf("Failed to receive a message: %v", err) + } + msg := delivery.Message() + var body string + if len(msg.Data) > 0 { + body = string(msg.Data[0]) + } + log.Printf(" [x] %s", body) + err = delivery.Accept(ctx) + if err != nil { + log.Panicf("Failed to accept message: %v", err) + } + } +} diff --git a/go-amqp/receive_logs_topic.go b/go-amqp/receive_logs_topic.go new file mode 100644 index 00000000..ea33b5ec --- /dev/null +++ b/go-amqp/receive_logs_topic.go @@ -0,0 +1,80 @@ +package main + +import ( + "context" + "errors" + "log" + "os" + + rmq "github.com/rabbitmq/rabbitmq-amqp-go-client/pkg/rabbitmqamqp" +) + +const brokerURI = "amqp://guest:guest@localhost:5672/" + +func main() { + ctx := context.Background() + env := rmq.NewEnvironment(brokerURI, nil) + conn, err := env.NewConnection(ctx) + if err != nil { + log.Panicf("Failed to connect to RabbitMQ: %v", err) + } + defer func() { + _ = env.CloseConnections(context.Background()) + }() + + _, err = conn.Management().DeclareExchange(ctx, &rmq.TopicExchangeSpecification{Name: "logs_topic"}) + if err != nil { + log.Panicf("Failed to declare an exchange: %v", err) + } + + qInfo, err := conn.Management().DeclareQueue(ctx, &rmq.AutoGeneratedQueueSpecification{ + IsExclusive: true, + IsAutoDelete: true, + }) + if err != nil { + log.Panicf("Failed to declare a queue: %v", err) + } + + if len(os.Args) < 2 { + log.Printf("Usage: %s [binding_key]...", os.Args[0]) + os.Exit(0) + } + for _, s := range os.Args[1:] { + log.Printf("Binding queue %s to exchange %s with routing key %s", qInfo.Name(), "logs_topic", s) + _, err = conn.Management().Bind(ctx, &rmq.ExchangeToQueueBindingSpecification{ + SourceExchange: "logs_topic", + DestinationQueue: qInfo.Name(), + BindingKey: s, + }) + if err != nil { + log.Panicf("Failed to bind a queue: %v", err) + } + } + + consumer, err := conn.NewConsumer(ctx, qInfo.Name(), nil) + if err != nil { + log.Panicf("Failed to create consumer: %v", err) + } + defer func() { _ = consumer.Close(context.Background()) }() + + log.Printf(" [*] Waiting for logs. To exit press CTRL+C") + for { + delivery, err := consumer.Receive(ctx) + if err != nil { + if errors.Is(err, context.Canceled) { + return + } + log.Panicf("Failed to receive a message: %v", err) + } + msg := delivery.Message() + var body string + if len(msg.Data) > 0 { + body = string(msg.Data[0]) + } + log.Printf(" [x] %s", body) + err = delivery.Accept(ctx) + if err != nil { + log.Panicf("Failed to accept message: %v", err) + } + } +} diff --git a/go-amqp/rpc_amqp10.go b/go-amqp/rpc_amqp10.go new file mode 100644 index 00000000..e586edc6 --- /dev/null +++ b/go-amqp/rpc_amqp10.go @@ -0,0 +1,151 @@ +package main + +import ( + "context" + "fmt" + "log" + "os" + "os/signal" + "syscall" + "time" + + amqp "github.com/Azure/go-amqp" + rmq "github.com/rabbitmq/rabbitmq-amqp-go-client/pkg/rabbitmqamqp" +) + +const ( + amqpURL = "amqp://guest:guest@localhost:5672/" + requestQueue = "rpc-requests" +) + +func rpcServer(ctx context.Context) error { + env := rmq.NewEnvironment(amqpURL, nil) + conn, err := env.NewConnection(ctx) + if err != nil { + return fmt.Errorf("failed to connect to RabbitMQ: %w", err) + } + defer func() { _ = env.CloseConnections(context.Background()) }() + + _, err = conn.Management().DeclareQueue(ctx, &rmq.QuorumQueueSpecification{Name: requestQueue}) + if err != nil { + return fmt.Errorf("failed to declare queue: %w", err) + } + + responder, err := conn.NewResponder(ctx, rmq.ResponderOptions{ + RequestQueue: requestQueue, + Handler: func(_ context.Context, request *amqp.Message) (*amqp.Message, error) { + var payload string + if len(request.Data) > 0 { + payload = string(request.Data[0]) + } + if request.Properties != nil && request.Properties.MessageID != nil { + log.Printf("RPC Server: Received %s request (ID: %v)", payload, request.Properties.MessageID) + } else { + log.Printf("RPC Server: Received %s request", payload) + } + return amqp.NewMessage([]byte("pong")), nil + }, + }) + if err != nil { + return fmt.Errorf("failed to create responder: %w", err) + } + defer func() { _ = responder.Close(context.Background()) }() + + log.Println("RPC Server: Started and listening for requests...") + + <-ctx.Done() + log.Println("RPC Server: Shutting down...") + return nil +} + +func rpcClient(ctx context.Context) error { + env := rmq.NewEnvironment(amqpURL, nil) + conn, err := env.NewConnection(ctx) + if err != nil { + return fmt.Errorf("failed to connect to RabbitMQ: %w", err) + } + defer func() { _ = env.CloseConnections(context.Background()) }() + + requester, err := conn.NewRequester(ctx, &rmq.RequesterOptions{ + RequestQueueName: requestQueue, + SettleStrategy: rmq.DirectReplyTo, + }) + if err != nil { + return fmt.Errorf("failed to create requester: %w", err) + } + defer func() { _ = requester.Close(context.Background()) }() + + replyAddr, err := requester.GetReplyQueue() + if err != nil { + return err + } + log.Printf("RPC Client: Reply address: %s", replyAddr) + + log.Println("RPC Client: Started and ready to send requests...") + + requestID := 0 + ticker := time.NewTicker(1 * time.Second) + defer ticker.Stop() + + for { + select { + case <-ctx.Done(): + log.Println("RPC Client: Shutting down...") + return nil + case <-ticker.C: + requestID++ + replyCh, err := requester.Publish(ctx, requester.Message([]byte("ping"))) + if err != nil { + log.Printf("RPC Client: Error sending request: %v", err) + continue + } + log.Printf("RPC Client: Sent ping request (%d)", requestID) + + reply := <-replyCh + if reply == nil { + log.Printf("RPC Client: No reply for request %d", requestID) + continue + } + var payload string + if len(reply.Data) > 0 { + payload = string(reply.Data[0]) + } + log.Printf("RPC Client: Received reply - %s", payload) + } + } +} + +func main() { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + log.Println("Starting Direct Reply-To RPC example...") + log.Println("Make sure RabbitMQ is running on localhost:5672") + + go func() { + if err := rpcServer(ctx); err != nil { + log.Printf("RPC Server error: %v", err) + cancel() + } + }() + + time.Sleep(500 * time.Millisecond) + + go func() { + if err := rpcClient(ctx); err != nil { + log.Printf("RPC Client error: %v", err) + cancel() + } + }() + + sigs := make(chan os.Signal, 1) + signal.Notify(sigs, os.Interrupt, syscall.SIGTERM) + go func() { + <-sigs + log.Println("\nReceived interrupt, shutting down...") + cancel() + }() + + <-ctx.Done() + log.Println("Application shutting down...") +} diff --git a/go-amqp/rpc_client.go b/go-amqp/rpc_client.go new file mode 100644 index 00000000..c901a40f --- /dev/null +++ b/go-amqp/rpc_client.go @@ -0,0 +1,74 @@ +package main + +import ( + "context" + "errors" + "log" + "os" + "strconv" + "strings" + + rmq "github.com/rabbitmq/rabbitmq-amqp-go-client/pkg/rabbitmqamqp" +) + +const brokerURI = "amqp://guest:guest@localhost:5672/" + +func fibonacciRPC(n int) (res int, err error) { + ctx := context.Background() + env := rmq.NewEnvironment(brokerURI, nil) + conn, err := env.NewConnection(ctx) + if err != nil { + return 0, err + } + defer func() { + _ = env.CloseConnections(context.Background()) + }() + + requester, err := conn.NewRequester(ctx, &rmq.RequesterOptions{ + RequestQueueName: "rpc_queue", + }) + if err != nil { + return 0, err + } + defer func() { _ = requester.Close(context.Background()) }() + + replyCh, err := requester.Publish(ctx, requester.Message([]byte(strconv.Itoa(n)))) + if err != nil { + return 0, err + } + reply := <-replyCh + if reply == nil { + return 0, errors.New("no reply received") + } + var payload []byte + if len(reply.Data) > 0 { + payload = reply.Data[0] + } + return strconv.Atoi(string(payload)) +} + +func main() { + n := bodyFrom(os.Args) + + log.Printf(" [x] Requesting fib(%d)", n) + res, err := fibonacciRPC(n) + if err != nil { + log.Panicf("Failed to handle RPC request: %v", err) + } + + log.Printf(" [.] Got %d", res) +} + +func bodyFrom(args []string) int { + var s string + if (len(args) < 2) || args[1] == "" { + s = "30" + } else { + s = strings.Join(args[1:], " ") + } + n, err := strconv.Atoi(s) + if err != nil { + log.Panicf("Failed to convert arg to integer: %v", err) + } + return n +} diff --git a/go-amqp/rpc_server.go b/go-amqp/rpc_server.go new file mode 100644 index 00000000..199cbd75 --- /dev/null +++ b/go-amqp/rpc_server.go @@ -0,0 +1,66 @@ +package main + +import ( + "context" + "log" + "strconv" + + amqp "github.com/Azure/go-amqp" + rmq "github.com/rabbitmq/rabbitmq-amqp-go-client/pkg/rabbitmqamqp" +) + +const brokerURI = "amqp://guest:guest@localhost:5672/" + +func fib(n int) int { + if n == 0 { + return 0 + } else if n == 1 { + return 1 + } + return fib(n-1) + fib(n-2) +} + +func main() { + ctx := context.Background() + env := rmq.NewEnvironment(brokerURI, nil) + conn, err := env.NewConnection(ctx) + if err != nil { + log.Panicf("Failed to connect to RabbitMQ: %v", err) + } + defer func() { + _ = env.CloseConnections(context.Background()) + }() + + _, err = conn.Management().DeclareQueue(ctx, &rmq.QuorumQueueSpecification{Name: "rpc_queue"}) + if err != nil { + log.Panicf("Failed to declare a queue: %v", err) + } + _, err = conn.Management().PurgeQueue(ctx, "rpc_queue") + if err != nil { + log.Printf("purge: %v", err) + } + + responder, err := conn.NewResponder(ctx, rmq.ResponderOptions{ + RequestQueue: "rpc_queue", + Handler: func(_ context.Context, request *amqp.Message) (*amqp.Message, error) { + var payload []byte + if len(request.Data) > 0 { + payload = request.Data[0] + } + n, err := strconv.Atoi(string(payload)) + if err != nil { + return nil, err + } + log.Printf(" [.] fib(%d)", n) + response := fib(n) + return amqp.NewMessage([]byte(strconv.Itoa(response))), nil + }, + }) + if err != nil { + log.Panicf("Failed to create responder: %v", err) + } + defer func() { _ = responder.Close(context.Background()) }() + + log.Printf(" [*] Awaiting RPC requests") + select {} +} diff --git a/go-amqp/send.go b/go-amqp/send.go new file mode 100644 index 00000000..5948c9b2 --- /dev/null +++ b/go-amqp/send.go @@ -0,0 +1,51 @@ +package main + +import ( + "context" + "log" + + rmq "github.com/rabbitmq/rabbitmq-amqp-go-client/pkg/rabbitmqamqp" +) + +const brokerURI = "amqp://guest:guest@localhost:5672/" + +func main() { + ctx := context.Background() + env := rmq.NewEnvironment(brokerURI, nil) + conn, err := env.NewConnection(ctx) + if err != nil { + log.Panicf("Failed to connect to RabbitMQ: %v", err) + } + defer func() { + _ = env.CloseConnections(context.Background()) + }() + + _, err = conn.Management().DeclareQueue(ctx, &rmq.QuorumQueueSpecification{Name: "hello"}) + if err != nil { + log.Panicf("Failed to declare a queue: %v", err) + } + + publisher, err := conn.NewPublisher(ctx, &rmq.QueueAddress{Queue: "hello"}, nil) + if err != nil { + log.Panicf("Failed to create publisher: %v", err) + } + defer func() { _ = publisher.Close(context.Background()) }() + + body := "Hello World!" + res, err := publisher.Publish(ctx, rmq.NewMessage([]byte(body))) + if err != nil { + log.Panicf("Failed to publish a message: %v", err) + } + switch res.Outcome.(type) { + case *rmq.StateAccepted: + case *rmq.StateRejected: + log.Fatalf("Message was rejected: %v", res.Outcome) + case *rmq.StateReleased: + log.Fatalf("Message was released: %v", res.Outcome) + case *rmq.StateModified: + log.Fatalf("Message was modified: %v", res.Outcome) + default: + log.Fatalf("Unexpected publish outcome: %v", res.Outcome) + } + log.Printf(" [x] Sent %s\n", body) +} diff --git a/go-amqp/test-tutorials.sh b/go-amqp/test-tutorials.sh new file mode 100755 index 00000000..25f3d057 --- /dev/null +++ b/go-amqp/test-tutorials.sh @@ -0,0 +1,221 @@ +#!/usr/bin/env bash +# End-to-end smoke tests for go-amqp tutorials. +# Requires: bash, go, RabbitMQ 4.x on localhost:5672 (guest/guest). +# +# Usage: +# ./go-amqp/test-tutorials.sh +# cd go-amqp && ./test-tutorials.sh +# +# Environment: +# SKIP_BROKER_CHECK=1 Skip TCP check on port 5672 (tests still need a broker). + +set -euo pipefail + +ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +cd "$ROOT" + +PASS=0 +FAIL=0 + +pass() { + printf 'ok\t%s\n' "$1" + PASS=$((PASS + 1)) +} + +fail() { + printf 'FAIL\t%s\n' "$1" >&2 + FAIL=$((FAIL + 1)) +} + +require_go() { + command -v go >/dev/null 2>&1 || { + echo "go is not on PATH" >&2 + exit 1 + } +} + +check_broker() { + if command -v nc >/dev/null 2>&1; then + nc -z localhost 5672 2>/dev/null + return + fi + { echo >/dev/tcp/127.0.0.1/5672; } 2>/dev/null +} + +require_broker() { + if [[ "${SKIP_BROKER_CHECK:-}" == "1" ]]; then + return 0 + fi + if ! check_broker; then + echo "RabbitMQ does not appear to be reachable on localhost:5672" >&2 + echo "Start the broker or set SKIP_BROKER_CHECK=1 to skip this check." >&2 + exit 1 + fi +} + +assert_file_contains() { + local file=$1 + local needle=$2 + local msg=$3 + if [[ -f "$file" ]] && grep -qF -- "$needle" "$file"; then + pass "$msg" + else + fail "$msg (log should contain: $needle)" + if [[ -f "$file" ]]; then + echo "--- $file (first 40 lines) ---" >&2 + head -n 40 "$file" >&2 + fi + fi +} + +# Run command in background; sleep $secs; kill. Captures stdout/stderr to $log. +run_timed() { + local log=$1 + local secs=$2 + shift 2 + : >"$log" + "$@" >"$log" 2>&1 & + local pid=$! + sleep "$secs" + kill "$pid" 2>/dev/null || true + wait "$pid" 2>/dev/null || true +} + +test_t1_hello() { + local log recv_log + log=$(mktemp) + recv_log=$(mktemp) + go run receive.go >"$recv_log" 2>&1 & + local rpid=$! + sleep 2 + go run send.go >"$log" 2>&1 + sleep 1 + kill "$rpid" 2>/dev/null || true + wait "$rpid" 2>/dev/null || true + assert_file_contains "$log" "Sent" "T1 send publishes" + assert_file_contains "$recv_log" "Hello World" "T1 receive gets message" + rm -f "$log" "$recv_log" +} + +test_t2_work_queues() { + local log recv_log + log=$(mktemp) + recv_log=$(mktemp) + go run worker.go >"$recv_log" 2>&1 & + local rpid=$! + sleep 2 + go run new_task.go e2e.task.test >"$log" 2>&1 + sleep 3 + kill "$rpid" 2>/dev/null || true + wait "$rpid" 2>/dev/null || true + assert_file_contains "$log" "Sent" "T2 new_task publishes" + assert_file_contains "$recv_log" "e2e.task.test" "T2 worker receives task" + rm -f "$log" "$recv_log" +} + +test_t3_pubsub() { + local log recv_log + log=$(mktemp) + recv_log=$(mktemp) + go run receive_logs.go >"$recv_log" 2>&1 & + local rpid=$! + sleep 2 + go run emit_log.go e2e pubsub hello >"$log" 2>&1 + sleep 2 + kill "$rpid" 2>/dev/null || true + wait "$rpid" 2>/dev/null || true + assert_file_contains "$log" "Sent" "T3 emit_log publishes" + assert_file_contains "$recv_log" "e2e pubsub hello" "T3 receive_logs receives broadcast" + rm -f "$log" "$recv_log" +} + +test_t4_routing() { + local log recv_log + log=$(mktemp) + recv_log=$(mktemp) + go run receive_logs_direct.go warn >"$recv_log" 2>&1 & + local rpid=$! + sleep 2 + go run emit_log_direct.go warn "e2e routing" >"$log" 2>&1 + sleep 2 + kill "$rpid" 2>/dev/null || true + wait "$rpid" 2>/dev/null || true + assert_file_contains "$log" "Sent" "T4 emit_log_direct publishes" + assert_file_contains "$recv_log" "e2e routing" "T4 receive_logs_direct gets routed message" + rm -f "$log" "$recv_log" +} + +test_t5_topics() { + local log recv_log + log=$(mktemp) + recv_log=$(mktemp) + go run receive_logs_topic.go "kern.*" >"$recv_log" 2>&1 & + local rpid=$! + sleep 2 + go run emit_log_topic.go kern.info "e2e topic" >"$log" 2>&1 + sleep 2 + kill "$rpid" 2>/dev/null || true + wait "$rpid" 2>/dev/null || true + assert_file_contains "$log" "Sent" "T5 emit_log_topic publishes" + assert_file_contains "$recv_log" "e2e topic" "T5 receive_logs_topic matches binding" + rm -f "$log" "$recv_log" +} + +test_t6_rpc() { + local log srv_log + log=$(mktemp) + srv_log=$(mktemp) + go run rpc_server.go >"$srv_log" 2>&1 & + local spid=$! + sleep 2 + if ! go run rpc_client.go 10 >"$log" 2>&1; then + kill "$spid" 2>/dev/null || true + wait "$spid" 2>/dev/null || true + fail "T6 rpc_client process failed" + rm -f "$log" "$srv_log" + return + fi + kill "$spid" 2>/dev/null || true + wait "$spid" 2>/dev/null || true + assert_file_contains "$log" "Requesting fib(10)" "T6 rpc_client sends request" + assert_file_contains "$log" "Got 55" "T6 rpc_client gets fib(10)==55" + rm -f "$log" "$srv_log" +} + +test_publisher_confirms() { + local log + log=$(mktemp) + run_timed "$log" 4 go run publisher_confirms.go + assert_file_contains "$log" "Confirmed" "publisher_confirms sees accepted publish" + assert_file_contains "$log" "Received a message" "publisher_confirms consumer sees message" + rm -f "$log" +} + +test_rpc_amqp10() { + local log + log=$(mktemp) + run_timed "$log" 5 go run rpc_amqp10.go + assert_file_contains "$log" "pong" "rpc_amqp10 ping/pong (Direct Reply-To)" + rm -f "$log" +} + +main() { + require_go + require_broker + echo "Running go-amqp tutorial smoke tests from $ROOT" + test_t1_hello + test_t2_work_queues + test_t3_pubsub + test_t4_routing + test_t5_topics + test_t6_rpc + test_publisher_confirms + test_rpc_amqp10 + echo "----" + echo "Passed: $PASS Failed: $FAIL" + if [[ "$FAIL" -gt 0 ]]; then + exit 1 + fi +} + +main "$@" diff --git a/go-amqp/worker.go b/go-amqp/worker.go new file mode 100644 index 00000000..f3f52531 --- /dev/null +++ b/go-amqp/worker.go @@ -0,0 +1,61 @@ +package main + +import ( + "bytes" + "context" + "errors" + "log" + "time" + + rmq "github.com/rabbitmq/rabbitmq-amqp-go-client/pkg/rabbitmqamqp" +) + +const brokerURI = "amqp://guest:guest@localhost:5672/" + +func main() { + ctx := context.Background() + env := rmq.NewEnvironment(brokerURI, nil) + conn, err := env.NewConnection(ctx) + if err != nil { + log.Panicf("Failed to connect to RabbitMQ: %v", err) + } + defer func() { + _ = env.CloseConnections(context.Background()) + }() + + _, err = conn.Management().DeclareQueue(ctx, &rmq.QuorumQueueSpecification{Name: "task_queue"}) + if err != nil { + log.Panicf("Failed to declare a queue: %v", err) + } + + consumer, err := conn.NewConsumer(ctx, "task_queue", &rmq.ConsumerOptions{InitialCredits: 1}) + if err != nil { + log.Panicf("Failed to create consumer: %v", err) + } + defer func() { _ = consumer.Close(context.Background()) }() + + log.Printf(" [*] Waiting for messages. To exit press CTRL+C") + for { + delivery, err := consumer.Receive(ctx) + if err != nil { + if errors.Is(err, context.Canceled) { + return + } + log.Panicf("Failed to receive a message: %v", err) + } + msg := delivery.Message() + var payload []byte + if len(msg.Data) > 0 { + payload = msg.Data[0] + } + log.Printf("Received a message: %s", payload) + dotCount := bytes.Count(payload, []byte(".")) + t := time.Duration(dotCount) + time.Sleep(t * time.Second) + log.Printf("Done") + err = delivery.Accept(ctx) + if err != nil { + log.Panicf("Failed to accept message: %v", err) + } + } +} diff --git a/go-stream/README.md b/go-stream/README.md new file mode 100644 index 00000000..cc5653ab --- /dev/null +++ b/go-stream/README.md @@ -0,0 +1,31 @@ +# Go code for RabbitMQ tutorials + +Here you can find Go code examples from [RabbitMQ tutorials](https://www.rabbitmq.com/getstarted.html). + +To successfully use the examples you will need a running RabbitMQ server with the [stream plugin enabled](https://www.rabbitmq.com/docs/stream#enabling-plugin). + +See [First Application With RabbitMQ Streams](https://www.rabbitmq.com/blog/2021/07/19/rabbitmq-streams-first-application), [Stream plugin documentation](https://www.rabbitmq.com/docs/stream) and [how to preconfigure plugins](https://www.rabbitmq.com/docs/plugins#enabled-plugins-file). + +## Requirements + +These examples use the [`rabbitmq/rabbitmq-stream-go-client`](https://github.com/rabbitmq/rabbitmq-stream-go-client) client library. +Get it first with + + go get -u github.com/rabbitmq/rabbitmq-stream-go-client + +## Code + +Code examples are executed via `go run`: + +[Tutorial one: "Hello World!"](https://www.rabbitmq.com/tutorials/tutorial-one-go-stream.html): + + go run send.go + go run receive.go + + +[Tutorial two: Offset Tracking](https://www.rabbitmq.com/tutorials/tutorial-two-go-stream.html): + + go run offset_tracking_send.go + go run offset_tracking_receive.go + +To learn more, see [`rabbitmq/rabbitmq-stream-go-client`](https://github.com/rabbitmq/rabbitmq-stream-go-client). diff --git a/go-stream/go.mod b/go-stream/go.mod new file mode 100644 index 00000000..e9b232b7 --- /dev/null +++ b/go-stream/go.mod @@ -0,0 +1,13 @@ +module github.com/rabbitmq/rabbitmq-tutorials + +go 1.22.0 + +require github.com/rabbitmq/rabbitmq-stream-go-client v1.4.6 + +require ( + github.com/golang/snappy v0.0.4 // indirect + github.com/klauspost/compress v1.17.9 // indirect + github.com/pierrec/lz4 v2.6.1+incompatible // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/spaolacci/murmur3 v1.1.0 // indirect +) diff --git a/go-stream/go.sum b/go-stream/go.sum new file mode 100644 index 00000000..0dee265f --- /dev/null +++ b/go-stream/go.sum @@ -0,0 +1,16 @@ +github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= +github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/klauspost/compress v1.17.8 h1:YcnTYrq7MikUT7k0Yb5eceMmALQPYBW/Xltxn0NAMnU= +github.com/klauspost/compress v1.17.8/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= +github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA= +github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= +github.com/pierrec/lz4 v2.6.1+incompatible h1:9UY3+iC23yxF0UfGaYrGplQ+79Rg+h/q9FV9ix19jjM= +github.com/pierrec/lz4 v2.6.1+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/rabbitmq/rabbitmq-stream-go-client v1.4.1 h1:QWHiXPio2rhQU98TKFhyooGWH2Xviwwz9RFGdIpIlUg= +github.com/rabbitmq/rabbitmq-stream-go-client v1.4.1/go.mod h1:CbFPhxC2+ctPq0FmWD3CiZWWGiP7sP4joRpw4YTlyL4= +github.com/rabbitmq/rabbitmq-stream-go-client v1.4.6 h1:aStxoALHUFdUouWWtG6f82rW0VE6885q2SGLKGOCZ50= +github.com/rabbitmq/rabbitmq-stream-go-client v1.4.6/go.mod h1:CDeYQ8E+cC96SWnkmdDc5NVEWjmtRBA9JAQrMG/y+MI= +github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI= +github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= diff --git a/go-stream/offset_tracking_receive.go b/go-stream/offset_tracking_receive.go new file mode 100644 index 00000000..48f8aa36 --- /dev/null +++ b/go-stream/offset_tracking_receive.go @@ -0,0 +1,73 @@ +package main + +import ( + "errors" + "fmt" + "github.com/rabbitmq/rabbitmq-stream-go-client/pkg/amqp" + "github.com/rabbitmq/rabbitmq-stream-go-client/pkg/stream" + "os" + "sync/atomic" +) + +func main() { + env, err := stream.NewEnvironment( + stream.NewEnvironmentOptions(). + SetHost("localhost"). + SetPort(5552). + SetUser("guest"). + SetPassword("guest")) + CheckErrReceive(err) + + streamName := "stream-offset-tracking-go" + err = env.DeclareStream(streamName, + &stream.StreamOptions{ + MaxLengthBytes: stream.ByteCapacity{}.GB(2), + }, + ) + CheckErrReceive(err) + + var firstOffset int64 = -1 + var messageCount int64 = -1 + var lastOffset atomic.Int64 + ch := make(chan bool) + messagesHandler := func(consumerContext stream.ConsumerContext, message *amqp.Message) { + if atomic.CompareAndSwapInt64(&firstOffset, -1, consumerContext.Consumer.GetOffset()) { + fmt.Println("First message received.") + } + if atomic.AddInt64(&messageCount, 1)%10 == 0 { + _ = consumerContext.Consumer.StoreOffset() + } + if string(message.GetData()) == "marker" { + lastOffset.Store(consumerContext.Consumer.GetOffset()) + _ = consumerContext.Consumer.StoreOffset() + _ = consumerContext.Consumer.Close() + ch <- true + } + } + + var offsetSpecification stream.OffsetSpecification + consumerName := "offset-tracking-tutorial" + storedOffset, err := env.QueryOffset(consumerName, streamName) + if errors.Is(err, stream.OffsetNotFoundError) { + offsetSpecification = stream.OffsetSpecification{}.First() + } else { + offsetSpecification = stream.OffsetSpecification{}.Offset(storedOffset + 1) + } + + _, err = env.NewConsumer(streamName, messagesHandler, + stream.NewConsumerOptions(). + SetManualCommit(). + SetConsumerName(consumerName). + SetOffset(offsetSpecification)) + CheckErrReceive(err) + fmt.Println("Started consuming...") + _ = <-ch + fmt.Printf("Done consuming, first offset %d, last offset %d.\n", firstOffset, lastOffset.Load()) +} + +func CheckErrReceive(err error) { + if err != nil { + fmt.Printf("%s ", err) + os.Exit(1) + } +} diff --git a/go-stream/offset_tracking_send.go b/go-stream/offset_tracking_send.go new file mode 100644 index 00000000..84d23583 --- /dev/null +++ b/go-stream/offset_tracking_send.go @@ -0,0 +1,74 @@ +package main + +import ( + "fmt" + "github.com/rabbitmq/rabbitmq-stream-go-client/pkg/amqp" + "github.com/rabbitmq/rabbitmq-stream-go-client/pkg/stream" + "os" +) + +func main() { + env, err := stream.NewEnvironment( + stream.NewEnvironmentOptions(). + SetHost("localhost"). + SetPort(5552). + SetUser("guest"). + SetPassword("guest")) + CheckErrSend(err) + + streamName := "stream-offset-tracking-go" + err = env.DeclareStream(streamName, + &stream.StreamOptions{ + MaxLengthBytes: stream.ByteCapacity{}.GB(2), + }, + ) + CheckErrSend(err) + + producer, err := env.NewProducer(streamName, stream.NewProducerOptions()) + CheckErrSend(err) + + messageCount := 100 + chPublishConfirm := producer.NotifyPublishConfirmation() + ch := make(chan bool) + handlePublishConfirm(chPublishConfirm, messageCount, ch) + + fmt.Printf("Publishing %d messages...\n", messageCount) + for i := 0; i < messageCount; i++ { + var body string + if i == messageCount-1 { + body = "marker" + } else { + body = "hello" + } + err = producer.Send(amqp.NewMessage([]byte(body))) + CheckErrSend(err) + } + _ = <-ch + fmt.Println("Messages confirmed.") + + err = producer.Close() + CheckErrSend(err) +} + +func handlePublishConfirm(confirms stream.ChannelPublishConfirm, messageCount int, ch chan bool) { + go func() { + confirmedCount := 0 + for confirmed := range confirms { + for _, msg := range confirmed { + if msg.IsConfirmed() { + confirmedCount++ + if confirmedCount == messageCount { + ch <- true + } + } + } + } + }() +} + +func CheckErrSend(err error) { + if err != nil { + fmt.Printf("%s ", err) + os.Exit(1) + } +} diff --git a/go-stream/receive.go b/go-stream/receive.go new file mode 100644 index 00000000..4a1db036 --- /dev/null +++ b/go-stream/receive.go @@ -0,0 +1,50 @@ +package main + +import ( + "bufio" + "fmt" + "github.com/rabbitmq/rabbitmq-stream-go-client/pkg/amqp" + "github.com/rabbitmq/rabbitmq-stream-go-client/pkg/stream" + "os" +) + +func CheckErrReceive(err error) { + if err != nil { + fmt.Printf("%s ", err) + os.Exit(1) + } +} +func main() { + + env, err := stream.NewEnvironment( + stream.NewEnvironmentOptions(). + SetHost("localhost"). + SetPort(5552). + SetUser("guest"). + SetPassword("guest")) + CheckErrReceive(err) + + streamName := "hello-go-stream" + err = env.DeclareStream(streamName, + &stream.StreamOptions{ + MaxLengthBytes: stream.ByteCapacity{}.GB(2), + }, + ) + CheckErrReceive(err) + + messagesHandler := func(consumerContext stream.ConsumerContext, message *amqp.Message) { + fmt.Printf("Stream: %s - Received message: %s\n", consumerContext.Consumer.GetStreamName(), + message.Data) + } + + consumer, err := env.NewConsumer(streamName, messagesHandler, + stream.NewConsumerOptions().SetOffset(stream.OffsetSpecification{}.First())) + CheckErrReceive(err) + + reader := bufio.NewReader(os.Stdin) + fmt.Println(" [x] Waiting for messages. enter to close the consumer") + _, _ = reader.ReadString('\n') + err = consumer.Close() + CheckErrReceive(err) + +} diff --git a/go-stream/send.go b/go-stream/send.go new file mode 100644 index 00000000..3d41b18b --- /dev/null +++ b/go-stream/send.go @@ -0,0 +1,47 @@ +package main + +import ( + "bufio" + "fmt" + "github.com/rabbitmq/rabbitmq-stream-go-client/pkg/amqp" + "github.com/rabbitmq/rabbitmq-stream-go-client/pkg/stream" + "os" +) + +func CheckErrSend(err error) { + if err != nil { + fmt.Printf("%s ", err) + os.Exit(1) + } +} +func main() { + env, err := stream.NewEnvironment( + stream.NewEnvironmentOptions(). + SetHost("localhost"). + SetPort(5552). + SetUser("guest"). + SetPassword("guest")) + CheckErrSend(err) + + streamName := "hello-go-stream" + err = env.DeclareStream(streamName, + &stream.StreamOptions{ + MaxLengthBytes: stream.ByteCapacity{}.GB(2), + }, + ) + CheckErrSend(err) + + producer, err := env.NewProducer(streamName, stream.NewProducerOptions()) + CheckErrSend(err) + + // Send a message + err = producer.Send(amqp.NewMessage([]byte("Hello world"))) + CheckErrSend(err) + fmt.Printf(" [x] 'Hello world' Message sent\n") + + reader := bufio.NewReader(os.Stdin) + fmt.Println(" [x] Press enter to close the producer") + _, _ = reader.ReadString('\n') + err = producer.Close() + CheckErrSend(err) +} diff --git a/go/README.md b/go/README.md index de103646..149e8afd 100644 --- a/go/README.md +++ b/go/README.md @@ -1,48 +1,52 @@ # Go code for RabbitMQ tutorials -Here you can find Go code examples from [RabbitMQ tutorials](http://www.rabbitmq.com/getstarted.html). +Here you can find Go code examples from [RabbitMQ tutorials](https://www.rabbitmq.com/getstarted.html). ## Requirements -To run this code you need [Go RabbitMQ client](https://github.com/streadway/amqp): - - go get github.com/streadway/amqp +These examples use the [`rabbitmq/amqp091-go`](https://github.com/rabbitmq/amqp091-go) client library. +Get it first with + go get github.com/rabbitmq/amqp091-go ## Code Code examples are executed via `go run`: -[Tutorial one: "Hello World!"](http://www.rabbitmq.com/tutorial-one-go.html): +[Tutorial one: "Hello World!"](https://www.rabbitmq.com/tutorials/tutorial-one-go.html): go run send.go go run receive.go -[Tutorial two: Work Queues](http://www.rabbitmq.com/tutorial-two-go.html): +[Tutorial two: Work Queues](https://www.rabbitmq.com/tutorials/tutorial-two-go.html): go run new_task.go hello world go run worker.go -[Tutorial three: Publish/Subscribe](http://www.rabbitmq.com/tutorial-three-go.html) +[Tutorial three: Publish/Subscribe](https://www.rabbitmq.com/tutorials/tutorial-three-go.html) go run receive_logs.go go run emit_log.go hello world -[Tutorial four: Routing](http://www.rabbitmq.com/tutorial-four-go.html) +[Tutorial four: Routing](https://www.rabbitmq.com/tutorials/tutorial-four-go.html) go run receive_logs_direct.go info warn go run emit_log_direct.go warn "a warning" -[Tutorial five: Topics](http://www.rabbitmq.com/tutorial-five-go.html) +[Tutorial five: Topics](https://www.rabbitmq.com/tutorials/tutorial-five-go.html) go run receive_logs_topic.go "kern.*" "*.critical" go run emit_log_topic.go kern.critical "A critical kernel error" -[Tutorial six: RPC](http://www.rabbitmq.com/tutorial-six-go.html) +[Tutorial six: RPC](https://www.rabbitmq.com/tutorials/tutorial-six-go.html) go run rpc_server.go go run rpc_client.go 10 -To learn more, see [Go RabbitMQ client](https://github.com/streadway/amqp). +[AMQP 1.0 Direct Reply-To RPC](https://www.rabbitmq.com/docs/next/direct-reply-to) + + go run rpc_amqp10.go + +To learn more, see [`rabbitmq/amqp091-go`](https://github.com/rabbitmq/amqp091-go). diff --git a/go/emit_log.go b/go/emit_log.go index 86ed19db..de30493e 100644 --- a/go/emit_log.go +++ b/go/emit_log.go @@ -1,16 +1,18 @@ package main import ( + "context" "log" "os" "strings" + "time" - "github.com/streadway/amqp" + amqp "github.com/rabbitmq/amqp091-go" ) func failOnError(err error, msg string) { if err != nil { - log.Fatalf("%s: %s", msg, err) + log.Panicf("%s: %s", msg, err) } } @@ -26,7 +28,7 @@ func main() { err = ch.ExchangeDeclare( "logs", // name "fanout", // type - true, // durable + false, // durability false, // auto-deleted false, // internal false, // no-wait @@ -34,8 +36,11 @@ func main() { ) failOnError(err, "Failed to declare an exchange") + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + body := bodyFrom(os.Args) - err = ch.Publish( + err = ch.PublishWithContext(ctx, "logs", // exchange "", // routing key false, // mandatory diff --git a/go/emit_log_direct.go b/go/emit_log_direct.go index ef3075d6..03b10263 100644 --- a/go/emit_log_direct.go +++ b/go/emit_log_direct.go @@ -1,16 +1,18 @@ package main import ( + "context" "log" "os" "strings" + "time" - "github.com/streadway/amqp" + amqp "github.com/rabbitmq/amqp091-go" ) func failOnError(err error, msg string) { if err != nil { - log.Fatalf("%s: %s", msg, err) + log.Panicf("%s: %s", msg, err) } } @@ -26,7 +28,7 @@ func main() { err = ch.ExchangeDeclare( "logs_direct", // name "direct", // type - true, // durable + false, // durability false, // auto-deleted false, // internal false, // no-wait @@ -34,12 +36,15 @@ func main() { ) failOnError(err, "Failed to declare an exchange") + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + body := bodyFrom(os.Args) - err = ch.Publish( + err = ch.PublishWithContext(ctx, "logs_direct", // exchange severityFrom(os.Args), // routing key - false, // mandatory - false, // immediate + false, // mandatory + false, // immediate amqp.Publishing{ ContentType: "text/plain", Body: []byte(body), diff --git a/go/emit_log_topic.go b/go/emit_log_topic.go index f75d719a..32eb4693 100644 --- a/go/emit_log_topic.go +++ b/go/emit_log_topic.go @@ -1,16 +1,18 @@ package main import ( + "context" "log" "os" "strings" + "time" - "github.com/streadway/amqp" + amqp "github.com/rabbitmq/amqp091-go" ) func failOnError(err error, msg string) { if err != nil { - log.Fatalf("%s: %s", msg, err) + log.Panicf("%s: %s", msg, err) } } @@ -26,7 +28,7 @@ func main() { err = ch.ExchangeDeclare( "logs_topic", // name "topic", // type - true, // durable + false, // durability false, // auto-deleted false, // internal false, // no-wait @@ -34,12 +36,15 @@ func main() { ) failOnError(err, "Failed to declare an exchange") + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + body := bodyFrom(os.Args) - err = ch.Publish( + err = ch.PublishWithContext(ctx, "logs_topic", // exchange severityFrom(os.Args), // routing key - false, // mandatory - false, // immediate + false, // mandatory + false, // immediate amqp.Publishing{ ContentType: "text/plain", Body: []byte(body), diff --git a/go/go.mod b/go/go.mod new file mode 100644 index 00000000..5f1bef65 --- /dev/null +++ b/go/go.mod @@ -0,0 +1,7 @@ +module github.com/rabbitmq/rabbitmq-tutorials + +go 1.17 + +require github.com/rabbitmq/amqp091-go v1.10.0 + +require github.com/Azure/go-amqp v1.5.0 diff --git a/go/go.sum b/go/go.sum new file mode 100644 index 00000000..209c06d2 --- /dev/null +++ b/go/go.sum @@ -0,0 +1,30 @@ +github.com/Azure/go-amqp v1.5.0 h1:GRiQK1VhrNFbyx5VlmI6BsA1FCp27W5rb9kxOZScnTo= +github.com/Azure/go-amqp v1.5.0/go.mod h1:vZAogwdrkbyK3Mla8m/CxSc/aKdnTZ4IbPxl51Y5WZE= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw= +github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g= +github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rabbitmq/amqp091-go v1.10.0 h1:STpn5XsHlHGcecLmMFCtg7mqq0RnD+zFr4uzukfVhBw= +github.com/rabbitmq/amqp091-go v1.10.0/go.mod h1:Hy4jKW5kQART1u+JkDTF9YYOQUHXqMuhrgxOEeS7G4o= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/go/new_task.go b/go/new_task.go index d6838a66..30e1a1a6 100644 --- a/go/new_task.go +++ b/go/new_task.go @@ -1,16 +1,18 @@ package main import ( + "context" "log" "os" "strings" + "time" - "github.com/streadway/amqp" + amqp "github.com/rabbitmq/amqp091-go" ) func failOnError(err error, msg string) { if err != nil { - log.Fatalf("%s: %s", msg, err) + log.Panicf("%s: %s", msg, err) } } @@ -25,16 +27,21 @@ func main() { q, err := ch.QueueDeclare( "task_queue", // name - true, // durable + true, // durability false, // delete when unused false, // exclusive false, // no-wait - nil, // arguments + amqp.Table{ + amqp.QueueTypeArg: amqp.QueueTypeQuorum, + }, ) failOnError(err, "Failed to declare a queue") + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + body := bodyFrom(os.Args) - err = ch.Publish( + err = ch.PublishWithContext(ctx, "", // exchange q.Name, // routing key false, // mandatory diff --git a/go/publisher_confirms.go b/go/publisher_confirms.go new file mode 100644 index 00000000..3205f1df --- /dev/null +++ b/go/publisher_confirms.go @@ -0,0 +1,88 @@ +package main + +import ( + "context" + "log" + "time" + + amqp "github.com/rabbitmq/amqp091-go" +) + +func failOnError(err error, msg string) { + if err != nil { + log.Panicf("%s: %s", msg, err) + } +} + +func main() { + conn, err := amqp.Dial("amqp://guest:guest@localhost:5672/") + failOnError(err, "Failed to connect to RabbitMQ") + defer conn.Close() + + ch, err := conn.Channel() + failOnError(err, "Failed to open a channel") + defer ch.Close() + + confirms := make(chan amqp.Confirmation) + ch.NotifyPublish(confirms) + go func() { + for confirm := range confirms { + if confirm.Ack { + // code when messages is confirmed + log.Printf("Confirmed") + } else { + // code when messages is nack-ed + log.Printf("Nacked") + } + } + }() + + err = ch.Confirm(false) + failOnError(err, "Failed to confirm") + + q, err := ch.QueueDeclare( + "", // name + false, // durability + false, // delete when unused + false, // exclusive + false, // no-wait + nil, // arguments + ) + failOnError(err, "Failed to declare a queue") + + consume(ch, q.Name) + publish(ch, q.Name, "hello") + + log.Printf(" [*] Waiting for messages. To exit press CTRL+C") + var forever chan struct{} + <-forever +} + +func consume(ch *amqp.Channel, qName string) { + msgs, err := ch.Consume( + qName, // queue + "", // consumer + true, // auto-ack + false, // exclusive + false, // no-local + false, // no-wait + nil, // args + ) + failOnError(err, "Failed to register a consumer") + + go func() { + for d := range msgs { + log.Printf("Received a message: %s", d.Body) + } + }() +} + +func publish(ch *amqp.Channel, qName, text string) { + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + err := ch.PublishWithContext(ctx, "", qName, false, false, amqp.Publishing{ + ContentType: "text/plain", + Body: []byte(text), + }) + failOnError(err, "Failed to publish a message") +} diff --git a/go/receive.go b/go/receive.go index 54c25610..77f357c2 100644 --- a/go/receive.go +++ b/go/receive.go @@ -3,12 +3,12 @@ package main import ( "log" - "github.com/streadway/amqp" + amqp "github.com/rabbitmq/amqp091-go" ) func failOnError(err error, msg string) { if err != nil { - log.Fatalf("%s: %s", msg, err) + log.Panicf("%s: %s", msg, err) } } @@ -23,11 +23,13 @@ func main() { q, err := ch.QueueDeclare( "hello", // name - false, // durable + true, // durability false, // delete when unused false, // exclusive false, // no-wait - nil, // arguments + amqp.Table{ + amqp.QueueTypeArg: amqp.QueueTypeQuorum, + }, ) failOnError(err, "Failed to declare a queue") @@ -42,7 +44,7 @@ func main() { ) failOnError(err, "Failed to register a consumer") - forever := make(chan bool) + var forever chan struct{} go func() { for d := range msgs { diff --git a/go/receive_logs.go b/go/receive_logs.go index fa6fd131..f1590191 100644 --- a/go/receive_logs.go +++ b/go/receive_logs.go @@ -3,12 +3,12 @@ package main import ( "log" - "github.com/streadway/amqp" + amqp "github.com/rabbitmq/amqp091-go" ) func failOnError(err error, msg string) { if err != nil { - log.Fatalf("%s: %s", msg, err) + log.Panicf("%s: %s", msg, err) } } @@ -24,7 +24,7 @@ func main() { err = ch.ExchangeDeclare( "logs", // name "fanout", // type - true, // durable + false, // durability false, // auto-deleted false, // internal false, // no-wait @@ -34,7 +34,7 @@ func main() { q, err := ch.QueueDeclare( "", // name - false, // durable + false, // durability false, // delete when unused true, // exclusive false, // no-wait @@ -61,7 +61,7 @@ func main() { ) failOnError(err, "Failed to register a consumer") - forever := make(chan bool) + var forever chan struct{} go func() { for d := range msgs { diff --git a/go/receive_logs_direct.go b/go/receive_logs_direct.go index 953f43c3..100978f2 100644 --- a/go/receive_logs_direct.go +++ b/go/receive_logs_direct.go @@ -4,12 +4,12 @@ import ( "log" "os" - "github.com/streadway/amqp" + amqp "github.com/rabbitmq/amqp091-go" ) func failOnError(err error, msg string) { if err != nil { - log.Fatalf("%s: %s", msg, err) + log.Panicf("%s: %s", msg, err) } } @@ -25,7 +25,7 @@ func main() { err = ch.ExchangeDeclare( "logs_direct", // name "direct", // type - true, // durable + false, // durability false, // auto-deleted false, // internal false, // no-wait @@ -35,7 +35,7 @@ func main() { q, err := ch.QueueDeclare( "", // name - false, // durable + false, // durability false, // delete when unused true, // exclusive false, // no-wait @@ -69,7 +69,7 @@ func main() { ) failOnError(err, "Failed to register a consumer") - forever := make(chan bool) + var forever chan struct{} go func() { for d := range msgs { diff --git a/go/receive_logs_topic.go b/go/receive_logs_topic.go index 2d189a2c..fd68bcf2 100644 --- a/go/receive_logs_topic.go +++ b/go/receive_logs_topic.go @@ -4,12 +4,12 @@ import ( "log" "os" - "github.com/streadway/amqp" + amqp "github.com/rabbitmq/amqp091-go" ) func failOnError(err error, msg string) { if err != nil { - log.Fatalf("%s: %s", msg, err) + log.Panicf("%s: %s", msg, err) } } @@ -25,7 +25,7 @@ func main() { err = ch.ExchangeDeclare( "logs_topic", // name "topic", // type - true, // durable + false, // durability false, // auto-deleted false, // internal false, // no-wait @@ -35,7 +35,7 @@ func main() { q, err := ch.QueueDeclare( "", // name - false, // durable + false, // durability false, // delete when unused true, // exclusive false, // no-wait @@ -69,7 +69,7 @@ func main() { ) failOnError(err, "Failed to register a consumer") - forever := make(chan bool) + var forever chan struct{} go func() { for d := range msgs { diff --git a/go/rpc_amqp10.go b/go/rpc_amqp10.go new file mode 100644 index 00000000..3e89ca49 --- /dev/null +++ b/go/rpc_amqp10.go @@ -0,0 +1,232 @@ +package main + +import ( + "context" + "fmt" + "log" + "time" + + "github.com/Azure/go-amqp" +) + +const ( + amqpURL = "amqp://guest:guest@localhost:5672/" + // Request queue name for RPC requests + requestQueue = "rpc-requests" +) + +// RPC Server - handles ping requests and sends pong replies +func rpcServer(ctx context.Context) error { + // Connect to RabbitMQ + conn, err := amqp.Dial(ctx, amqpURL, nil) + if err != nil { + return fmt.Errorf("failed to connect to RabbitMQ: %w", err) + } + defer conn.Close() + + // Create a session + session, err := conn.NewSession(ctx, nil) + if err != nil { + return fmt.Errorf("failed to create session: %w", err) + } + defer session.Close(ctx) + + // Create a receiver for the request queue + receiver, err := session.NewReceiver(ctx, requestQueue, nil) + if err != nil { + return fmt.Errorf("failed to create receiver: %w", err) + } + defer receiver.Close(ctx) + + log.Println("RPC Server: Started and listening for requests...") + + for { + select { + case <-ctx.Done(): + log.Println("RPC Server: Shutting down...") + return nil + default: + // Receive a request message + msg, err := receiver.Receive(ctx, nil) + if err != nil { + log.Printf("RPC Server: Error receiving message: %v", err) + continue + } + + // Accept the message + err = receiver.AcceptMessage(ctx, msg) + if err != nil { + log.Printf("RPC Server: Error accepting message: %v", err) + continue + } + + // Extract message properties + + messageID := msg.Properties.MessageID.(string) + replyTo := *msg.Properties.ReplyTo + + log.Printf("RPC Server: Received ping request (ID: %s, ReplyTo: %s)", messageID, replyTo) + + // Check if we have a reply-to address + if replyTo == "" { + log.Println("RPC Server: No reply-to address, skipping reply") + continue + } + + // Create a sender for the reply + sender, err := session.NewSender(ctx, replyTo, nil) + if err != nil { + log.Printf("RPC Server: Error creating sender for reply: %v", err) + continue + } + + // Create the pong reply message + replyMsg := &amqp.Message{ + Properties: &amqp.MessageProperties{ + CorrelationID: messageID, + }, + Data: [][]byte{[]byte("pong")}, + } + + // Send the reply + err = sender.Send(ctx, replyMsg, nil) + if err != nil { + log.Printf("RPC Server: Error sending reply: %v", err) + } else { + log.Printf("RPC Server: Sent pong reply (CorrelationID: %s)", messageID) + } + + // Close the sender + sender.Close(ctx) + } + } +} + +// RPC Client - sends ping requests using Direct Reply-To +func rpcClient(ctx context.Context) error { + // Connect to RabbitMQ + conn, err := amqp.Dial(ctx, amqpURL, nil) + if err != nil { + return fmt.Errorf("failed to connect to RabbitMQ: %w", err) + } + defer conn.Close() + + // Create a session + session, err := conn.NewSession(ctx, nil) + if err != nil { + return fmt.Errorf("failed to create session: %w", err) + } + defer session.Close(ctx) + + // Create a receiver for Direct Reply-To + receiver, err := session.NewReceiver(ctx, "", &amqp.ReceiverOptions{ + SourceCapabilities: []string{"rabbitmq:volatile-queue"}, + SourceExpiryPolicy: amqp.ExpiryPolicyLinkDetach, + DynamicAddress: true, + RequestedSenderSettleMode: amqp.SenderSettleModeSettled.Ptr(), + }) + if err != nil { + return fmt.Errorf("failed to create Direct Reply-To receiver: %w", err) + } + defer receiver.Close(ctx) + + // Get the generated reply address from the receiver + replyAddress := receiver.Address() + log.Printf("RPC Client: Generated reply address: %s", replyAddress) + + // Create a sender for requests + sender, err := session.NewSender(ctx, requestQueue, nil) + if err != nil { + return fmt.Errorf("failed to create sender: %w", err) + } + defer sender.Close(ctx) + + log.Println("RPC Client: Started and ready to send requests...") + + requestID := 0 + ticker := time.NewTicker(1 * time.Second) + defer ticker.Stop() + + for { + select { + case <-ctx.Done(): + log.Println("RPC Client: Shutting down...") + return nil + case <-ticker.C: + requestID++ + messageID := fmt.Sprintf("ping-%d", requestID) + + // Create the ping request message + requestMsg := &amqp.Message{ + Properties: &amqp.MessageProperties{ + MessageID: messageID, + ReplyTo: &replyAddress, + }, + Data: [][]byte{[]byte("ping")}, + } + + // Send the request + err = sender.Send(ctx, requestMsg, nil) + if err != nil { + log.Printf("RPC Client: Error sending request: %v", err) + continue + } + + log.Printf("RPC Client: Sent ping request (ID: %s)", messageID) + + // Try to receive the reply with a timeout + replyCtx, cancel := context.WithTimeout(ctx, 5*time.Second) + replyMsg, err := receiver.Receive(replyCtx, nil) + cancel() + + if err != nil { + log.Printf("RPC Client: Error receiving reply for %s: %v", messageID, err) + continue + } + + // Accept the reply message + err = receiver.AcceptMessage(ctx, replyMsg) + if err != nil { + log.Printf("RPC Client: Error accepting reply: %v", err) + continue + } + + // Extract correlation ID and payload + correlationID := "" + if replyMsg.Properties != nil && replyMsg.Properties.CorrelationID != nil { + correlationID = replyMsg.Properties.CorrelationID.(string) + } + + replyPayload := string(replyMsg.Data[0]) + log.Printf("RPC Client: Received reply - %s (CorrelationID: %s)", replyPayload, correlationID) + } + } +} + +func main() { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + log.Println("Starting Direct Reply-To RPC example...") + log.Println("Make sure RabbitMQ is running on localhost:5672") + + // Start the RPC server in a goroutine + go func() { + if err := rpcServer(ctx); err != nil { + log.Printf("RPC Server error: %v", err) + cancel() // Cancel context to stop the client as well + } + }() + + // Start the RPC client in a goroutine + go func() { + if err := rpcClient(ctx); err != nil { + log.Printf("RPC Client error: %v", err) + cancel() // Cancel context to stop the server as well + } + }() + + // Wait for context cancellation (Ctrl+C or error) + <-ctx.Done() + log.Println("Application shutting down...") +} diff --git a/go/rpc_client.go b/go/rpc_client.go index dfa23882..7507b271 100644 --- a/go/rpc_client.go +++ b/go/rpc_client.go @@ -1,6 +1,7 @@ package main import ( + "context" "log" "math/rand" "os" @@ -8,12 +9,12 @@ import ( "strings" "time" - "github.com/streadway/amqp" + amqp "github.com/rabbitmq/amqp091-go" ) func failOnError(err error, msg string) { if err != nil { - log.Fatalf("%s: %s", msg, err) + log.Panicf("%s: %s", msg, err) } } @@ -40,7 +41,7 @@ func fibonacciRPC(n int) (res int, err error) { q, err := ch.QueueDeclare( "", // name - false, // durable + false, // durability false, // delete when unused true, // exclusive false, // noWait @@ -61,7 +62,10 @@ func fibonacciRPC(n int) (res int, err error) { corrId := randomString(32) - err = ch.Publish( + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + + err = ch.PublishWithContext(ctx, "", // exchange "rpc_queue", // routing key false, // mandatory diff --git a/go/rpc_server.go b/go/rpc_server.go index 017715e0..ec1ba39a 100644 --- a/go/rpc_server.go +++ b/go/rpc_server.go @@ -1,15 +1,17 @@ package main import ( + "context" "log" "strconv" + "time" - "github.com/streadway/amqp" + amqp "github.com/rabbitmq/amqp091-go" ) func failOnError(err error, msg string) { if err != nil { - log.Fatalf("%s: %s", msg, err) + log.Panicf("%s: %s", msg, err) } } @@ -34,11 +36,13 @@ func main() { q, err := ch.QueueDeclare( "rpc_queue", // name - false, // durable + true, // durability false, // delete when unused false, // exclusive false, // no-wait - nil, // arguments + amqp.Table{ + amqp.QueueTypeArg: amqp.QueueTypeQuorum, + }, ) failOnError(err, "Failed to declare a queue") @@ -60,9 +64,11 @@ func main() { ) failOnError(err, "Failed to register a consumer") - forever := make(chan bool) + var forever chan struct{} go func() { + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() for d := range msgs { n, err := strconv.Atoi(string(d.Body)) failOnError(err, "Failed to convert body to integer") @@ -70,7 +76,7 @@ func main() { log.Printf(" [.] fib(%d)", n) response := fib(n) - err = ch.Publish( + err = ch.PublishWithContext(ctx, "", // exchange d.ReplyTo, // routing key false, // mandatory diff --git a/go/send.go b/go/send.go index c1d23939..3d3fd32b 100644 --- a/go/send.go +++ b/go/send.go @@ -1,14 +1,16 @@ package main import ( + "context" "log" + "time" - "github.com/streadway/amqp" + amqp "github.com/rabbitmq/amqp091-go" ) func failOnError(err error, msg string) { if err != nil { - log.Fatalf("%s: %s", msg, err) + log.Panicf("%s: %s", msg, err) } } @@ -23,16 +25,20 @@ func main() { q, err := ch.QueueDeclare( "hello", // name - false, // durable + true, // durability false, // delete when unused false, // exclusive false, // no-wait - nil, // arguments + amqp.Table{ + amqp.QueueTypeArg: amqp.QueueTypeQuorum, + }, ) failOnError(err, "Failed to declare a queue") + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() body := "Hello World!" - err = ch.Publish( + err = ch.PublishWithContext(ctx, "", // exchange q.Name, // routing key false, // mandatory @@ -41,6 +47,6 @@ func main() { ContentType: "text/plain", Body: []byte(body), }) - log.Printf(" [x] Sent %s", body) failOnError(err, "Failed to publish a message") + log.Printf(" [x] Sent %s\n", body) } diff --git a/go/worker.go b/go/worker.go index 6b37ea1c..c4f0198e 100644 --- a/go/worker.go +++ b/go/worker.go @@ -5,12 +5,12 @@ import ( "log" "time" - "github.com/streadway/amqp" + amqp "github.com/rabbitmq/amqp091-go" ) func failOnError(err error, msg string) { if err != nil { - log.Fatalf("%s: %s", msg, err) + log.Panicf("%s: %s", msg, err) } } @@ -25,11 +25,13 @@ func main() { q, err := ch.QueueDeclare( "task_queue", // name - true, // durable + true, // durability false, // delete when unused false, // exclusive false, // no-wait - nil, // arguments + amqp.Table{ + amqp.QueueTypeArg: amqp.QueueTypeQuorum, + }, ) failOnError(err, "Failed to declare a queue") @@ -51,13 +53,13 @@ func main() { ) failOnError(err, "Failed to register a consumer") - forever := make(chan bool) + var forever chan struct{} go func() { for d := range msgs { log.Printf("Received a message: %s", d.Body) - dot_count := bytes.Count(d.Body, []byte(".")) - t := time.Duration(dot_count) + dotCount := bytes.Count(d.Body, []byte(".")) + t := time.Duration(dotCount) time.Sleep(t * time.Second) log.Printf("Done") d.Ack(false) diff --git a/haskell/README.md b/haskell/README.md index ae2a0feb..62b91407 100644 --- a/haskell/README.md +++ b/haskell/README.md @@ -1,11 +1,11 @@ # Haskell code for RabbitMQ tutorials Here you can find Haskell code examples from -[RabbitMQ tutorials](http://www.rabbitmq.com/getstarted.html). +[RabbitMQ tutorials](https://www.rabbitmq.com/getstarted.html). ## Requirements -To run this code you need [Network.AMQP](http://hackage.haskell.org/package/amqp). +To run this code you need [Network.AMQP](https://hackage.haskell.org/package/amqp). ### Running the examples with `stack` @@ -14,36 +14,48 @@ To run this code you need [Network.AMQP](http://hackage.haskell.org/package/amqp ## Code -Code examples are executed via `runhaskell`: +Code examples are executed via `runhaskell`. -[Tutorial one: "Hello World!"](http://www.rabbitmq.com/tutorial-one-python.html): +Tutorial one: - runhaskell send.hs - runhaskell receive.hs +``` shell +runhaskell send.hs +runhaskell receive.hs +``` -[Tutorial two: Work Queues](http://www.rabbitmq.com/tutorial-two-python.html): +Tutorial two: - runhaskell newTask.hs hello world - runhaskell worker.hs +``` shell +runhaskell newTask.hs hello world +runhaskell worker.hs +``` -[Tutorial three: Publish/Subscribe](http://www.rabbitmq.com/tutorial-three-python.html) +Tutorial three: Publish/Subscribe - runhaskell receiveLogs.hs - runhaskell emitLog.hs hello world +``` shell +runhaskell receiveLogs.hs +runhaskell emitLog.hs hello world +``` -[Tutorial four: Routing](http://www.rabbitmq.com/tutorial-four-python.html) +Tutorial four: Routing - runhaskell receiveLogsDirect.hs info warn - runhaskell emitLogDirect.hs warn "a warning" +``` shell +runhaskell receiveLogsDirect.hs info warn +runhaskell emitLogDirect.hs warn "a warning" +``` -[Tutorial five: Topics](http://www.rabbitmq.com/tutorial-five-python.html) +Tutorial five: Topics - runhaskell receiveLogsTopic.hs info warn - runhaskell emitLogTopic.hs warn "a warning" +``` shell +runhaskell receiveLogsTopic.hs info warn +runhaskell emitLogTopic.hs warn "a warning" +``` -[Tutorial six: RPC](http://www.rabbitmq.com/tutorial-six-python.html) +Tutorial six: RPC - runhaskell rpcServer.hs - runhaskell rpcClient.hs +``` shell +runhaskell rpcServer.hs +runhaskell rpcClient.hs +``` To learn more, see [Network.AMQP](https://github.com/hreinhardt/amqp). diff --git a/java-amqp/.mvn/wrapper/maven-wrapper.jar b/java-amqp/.mvn/wrapper/maven-wrapper.jar new file mode 100644 index 00000000..cb28b0e3 Binary files /dev/null and b/java-amqp/.mvn/wrapper/maven-wrapper.jar differ diff --git a/java-amqp/.mvn/wrapper/maven-wrapper.properties b/java-amqp/.mvn/wrapper/maven-wrapper.properties new file mode 100644 index 00000000..346d645f --- /dev/null +++ b/java-amqp/.mvn/wrapper/maven-wrapper.properties @@ -0,0 +1,18 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.6/apache-maven-3.9.6-bin.zip +wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar diff --git a/java-amqp/README.md b/java-amqp/README.md new file mode 100644 index 00000000..99a3c0e7 --- /dev/null +++ b/java-amqp/README.md @@ -0,0 +1,103 @@ +# Java code for RabbitMQ tutorials (AMQP 1.0 client) + +Here you can find Java examples for the [RabbitMQ tutorials](https://www.rabbitmq.com/getstarted.html) using the [RabbitMQ AMQP 1.0 Java client](https://rabbitmq.github.io/rabbitmq-amqp-java-client/stable/htmlsingle/). + +You need a RabbitMQ node running locally. The client requires **RabbitMQ 4.0 or newer** with AMQP 1.0 (enabled by default on port **5672**). The examples use the default `guest` user. See also the [AMQP 1.0 client libraries overview](https://www.rabbitmq.com/client-libraries/amqp-client-libraries). + +This directory is a self-contained [Maven](https://maven.apache.org/) project. Use `./mvnw` on Unix-like systems or `mvnw.cmd` on Windows. + +Run integration tests with `./mvnw test` (they expect a broker on `localhost`). + +## Code + +#### Tutorial one: "Hello World!" + +```shell +# terminal tab 1 +./mvnw compile exec:java -Dexec.mainClass=Recv + +# terminal tab 2 +./mvnw compile exec:java -Dexec.mainClass=Send +``` + +#### Tutorial two: Work Queues + +```shell +# terminal tab 1 +./mvnw compile exec:java -Dexec.mainClass=Worker + +# terminal tab 2 +./mvnw compile exec:java -Dexec.mainClass=Worker + +# terminal tab 3 +./mvnw compile exec:java -Dexec.mainClass=NewTask -Dexec.args="First Message" +./mvnw compile exec:java -Dexec.mainClass=NewTask -Dexec.args="Second Message" +./mvnw compile exec:java -Dexec.mainClass=NewTask -Dexec.args="Third Message" +./mvnw compile exec:java -Dexec.mainClass=NewTask -Dexec.args="Fourth Message" +./mvnw compile exec:java -Dexec.mainClass=NewTask -Dexec.args="Fifth Message" +``` + +#### Tutorial three: Publish/Subscribe + +```shell +# terminal tab 1 +./mvnw compile exec:java -Dexec.mainClass=ReceiveLogs + +# terminal tab 2 +./mvnw compile exec:java -Dexec.mainClass=ReceiveLogs + +# terminal tab 3 +./mvnw compile exec:java -Dexec.mainClass=EmitLog +``` + +#### Tutorial four: Routing + +```shell +# terminal tab 1 +./mvnw compile exec:java -Dexec.mainClass=ReceiveLogsDirect -Dexec.args="warning error" + +# terminal tab 2 +./mvnw compile exec:java -Dexec.mainClass=ReceiveLogsDirect -Dexec.args="info warning error" + +# terminal tab 3 +./mvnw compile exec:java -Dexec.mainClass=EmitLogDirect -Dexec.args="info Run. Run. Or it will explode." +./mvnw compile exec:java -Dexec.mainClass=EmitLogDirect -Dexec.args="warning Run. Run. Or it will explode." +./mvnw compile exec:java -Dexec.mainClass=EmitLogDirect -Dexec.args="error Run. Run. Or it will explode." +``` + +#### Tutorial five: Topics + +```shell +# terminal tab 1 +# To receive all the logs: +./mvnw compile exec:java -Dexec.mainClass=ReceiveLogsTopic -Dexec.args="#" + +# To receive all logs from the facility "kern": +./mvnw compile exec:java -Dexec.mainClass=ReceiveLogsTopic -Dexec.args="kern.*" + +# Or if you want to hear only about "critical" logs: +./mvnw compile exec:java -Dexec.mainClass=ReceiveLogsTopic -Dexec.args="*.critical" + +# You can create multiple bindings: +./mvnw compile exec:java -Dexec.mainClass=ReceiveLogsTopic -Dexec.args="kern.* *.critical" + +# terminal tab 2 +# And to emit a log with a routing key "kern.critical" type: +./mvnw compile exec:java -Dexec.mainClass=EmitLogTopic -Dexec.args="kern.critical A critical kernel error" +``` + +#### Tutorial six: RPC + +```shell +# terminal tab 1 +./mvnw compile exec:java -Dexec.mainClass=RPCServer + +# terminal tab 2 +./mvnw compile exec:java -Dexec.mainClass=RPCClient +``` + +#### Tutorial seven: Publisher Confirms + +```shell +./mvnw compile exec:java -Dexec.mainClass=PublisherConfirms +``` diff --git a/java-amqp/mvnw b/java-amqp/mvnw new file mode 100755 index 00000000..8d937f4c --- /dev/null +++ b/java-amqp/mvnw @@ -0,0 +1,308 @@ +#!/bin/sh +# ---------------------------------------------------------------------------- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# ---------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------- +# Apache Maven Wrapper startup batch script, version 3.2.0 +# +# Required ENV vars: +# ------------------ +# JAVA_HOME - location of a JDK home dir +# +# Optional ENV vars +# ----------------- +# MAVEN_OPTS - parameters passed to the Java VM when running Maven +# e.g. to debug Maven itself, use +# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +# MAVEN_SKIP_RC - flag to disable loading of mavenrc files +# ---------------------------------------------------------------------------- + +if [ -z "$MAVEN_SKIP_RC" ] ; then + + if [ -f /usr/local/etc/mavenrc ] ; then + . /usr/local/etc/mavenrc + fi + + if [ -f /etc/mavenrc ] ; then + . /etc/mavenrc + fi + + if [ -f "$HOME/.mavenrc" ] ; then + . "$HOME/.mavenrc" + fi + +fi + +# OS specific support. $var _must_ be set to either true or false. +cygwin=false; +darwin=false; +mingw=false +case "$(uname)" in + CYGWIN*) cygwin=true ;; + MINGW*) mingw=true;; + Darwin*) darwin=true + # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home + # See https://developer.apple.com/library/mac/qa/qa1170/_index.html + if [ -z "$JAVA_HOME" ]; then + if [ -x "/usr/libexec/java_home" ]; then + JAVA_HOME="$(/usr/libexec/java_home)"; export JAVA_HOME + else + JAVA_HOME="/Library/Java/Home"; export JAVA_HOME + fi + fi + ;; +esac + +if [ -z "$JAVA_HOME" ] ; then + if [ -r /etc/gentoo-release ] ; then + JAVA_HOME=$(java-config --jre-home) + fi +fi + +# For Cygwin, ensure paths are in UNIX format before anything is touched +if $cygwin ; then + [ -n "$JAVA_HOME" ] && + JAVA_HOME=$(cygpath --unix "$JAVA_HOME") + [ -n "$CLASSPATH" ] && + CLASSPATH=$(cygpath --path --unix "$CLASSPATH") +fi + +# For Mingw, ensure paths are in UNIX format before anything is touched +if $mingw ; then + [ -n "$JAVA_HOME" ] && [ -d "$JAVA_HOME" ] && + JAVA_HOME="$(cd "$JAVA_HOME" || (echo "cannot cd into $JAVA_HOME."; exit 1); pwd)" +fi + +if [ -z "$JAVA_HOME" ]; then + javaExecutable="$(which javac)" + if [ -n "$javaExecutable" ] && ! [ "$(expr "\"$javaExecutable\"" : '\([^ ]*\)')" = "no" ]; then + # readlink(1) is not available as standard on Solaris 10. + readLink=$(which readlink) + if [ ! "$(expr "$readLink" : '\([^ ]*\)')" = "no" ]; then + if $darwin ; then + javaHome="$(dirname "\"$javaExecutable\"")" + javaExecutable="$(cd "\"$javaHome\"" && pwd -P)/javac" + else + javaExecutable="$(readlink -f "\"$javaExecutable\"")" + fi + javaHome="$(dirname "\"$javaExecutable\"")" + javaHome=$(expr "$javaHome" : '\(.*\)/bin') + JAVA_HOME="$javaHome" + export JAVA_HOME + fi + fi +fi + +if [ -z "$JAVACMD" ] ; then + if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + else + JAVACMD="$(\unset -f command 2>/dev/null; \command -v java)" + fi +fi + +if [ ! -x "$JAVACMD" ] ; then + echo "Error: JAVA_HOME is not defined correctly." >&2 + echo " We cannot execute $JAVACMD" >&2 + exit 1 +fi + +if [ -z "$JAVA_HOME" ] ; then + echo "Warning: JAVA_HOME environment variable is not set." +fi + +# traverses directory structure from process work directory to filesystem root +# first directory with .mvn subdirectory is considered project base directory +find_maven_basedir() { + if [ -z "$1" ] + then + echo "Path not specified to find_maven_basedir" + return 1 + fi + + basedir="$1" + wdir="$1" + while [ "$wdir" != '/' ] ; do + if [ -d "$wdir"/.mvn ] ; then + basedir=$wdir + break + fi + # workaround for JBEAP-8937 (on Solaris 10/Sparc) + if [ -d "${wdir}" ]; then + wdir=$(cd "$wdir/.." || exit 1; pwd) + fi + # end of workaround + done + printf '%s' "$(cd "$basedir" || exit 1; pwd)" +} + +# concatenates all lines of a file +concat_lines() { + if [ -f "$1" ]; then + # Remove \r in case we run on Windows within Git Bash + # and check out the repository with auto CRLF management + # enabled. Otherwise, we may read lines that are delimited with + # \r\n and produce $'-Xarg\r' rather than -Xarg due to word + # splitting rules. + tr -s '\r\n' ' ' < "$1" + fi +} + +log() { + if [ "$MVNW_VERBOSE" = true ]; then + printf '%s\n' "$1" + fi +} + +BASE_DIR=$(find_maven_basedir "$(dirname "$0")") +if [ -z "$BASE_DIR" ]; then + exit 1; +fi + +MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}; export MAVEN_PROJECTBASEDIR +log "$MAVEN_PROJECTBASEDIR" + +########################################################################################## +# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +# This allows using the maven wrapper in projects that prohibit checking in binary data. +########################################################################################## +wrapperJarPath="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" +if [ -r "$wrapperJarPath" ]; then + log "Found $wrapperJarPath" +else + log "Couldn't find $wrapperJarPath, downloading it ..." + + if [ -n "$MVNW_REPOURL" ]; then + wrapperUrl="$MVNW_REPOURL/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" + else + wrapperUrl="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" + fi + while IFS="=" read -r key value; do + # Remove '\r' from value to allow usage on windows as IFS does not consider '\r' as a separator ( considers space, tab, new line ('\n'), and custom '=' ) + safeValue=$(echo "$value" | tr -d '\r') + case "$key" in (wrapperUrl) wrapperUrl="$safeValue"; break ;; + esac + done < "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties" + log "Downloading from: $wrapperUrl" + + if $cygwin; then + wrapperJarPath=$(cygpath --path --windows "$wrapperJarPath") + fi + + if command -v wget > /dev/null; then + log "Found wget ... using wget" + [ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--quiet" + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + wget $QUIET "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath" + else + wget $QUIET --http-user="$MVNW_USERNAME" --http-password="$MVNW_PASSWORD" "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath" + fi + elif command -v curl > /dev/null; then + log "Found curl ... using curl" + [ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--silent" + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + curl $QUIET -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath" + else + curl $QUIET --user "$MVNW_USERNAME:$MVNW_PASSWORD" -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath" + fi + else + log "Falling back to using Java to download" + javaSource="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.java" + javaClass="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.class" + # For Cygwin, switch paths to Windows format before running javac + if $cygwin; then + javaSource=$(cygpath --path --windows "$javaSource") + javaClass=$(cygpath --path --windows "$javaClass") + fi + if [ -e "$javaSource" ]; then + if [ ! -e "$javaClass" ]; then + log " - Compiling MavenWrapperDownloader.java ..." + ("$JAVA_HOME/bin/javac" "$javaSource") + fi + if [ -e "$javaClass" ]; then + log " - Running MavenWrapperDownloader.java ..." + ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$wrapperUrl" "$wrapperJarPath") || rm -f "$wrapperJarPath" + fi + fi + fi +fi +########################################################################################## +# End of extension +########################################################################################## + +# If specified, validate the SHA-256 sum of the Maven wrapper jar file +wrapperSha256Sum="" +while IFS="=" read -r key value; do + case "$key" in (wrapperSha256Sum) wrapperSha256Sum=$value; break ;; + esac +done < "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties" +if [ -n "$wrapperSha256Sum" ]; then + wrapperSha256Result=false + if command -v sha256sum > /dev/null; then + if echo "$wrapperSha256Sum $wrapperJarPath" | sha256sum -c > /dev/null 2>&1; then + wrapperSha256Result=true + fi + elif command -v shasum > /dev/null; then + if echo "$wrapperSha256Sum $wrapperJarPath" | shasum -a 256 -c > /dev/null 2>&1; then + wrapperSha256Result=true + fi + else + echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available." + echo "Please install either command, or disable validation by removing 'wrapperSha256Sum' from your maven-wrapper.properties." + exit 1 + fi + if [ $wrapperSha256Result = false ]; then + echo "Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised." >&2 + echo "Investigate or delete $wrapperJarPath to attempt a clean download." >&2 + echo "If you updated your Maven version, you need to update the specified wrapperSha256Sum property." >&2 + exit 1 + fi +fi + +MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" + +# For Cygwin, switch paths to Windows format before running java +if $cygwin; then + [ -n "$JAVA_HOME" ] && + JAVA_HOME=$(cygpath --path --windows "$JAVA_HOME") + [ -n "$CLASSPATH" ] && + CLASSPATH=$(cygpath --path --windows "$CLASSPATH") + [ -n "$MAVEN_PROJECTBASEDIR" ] && + MAVEN_PROJECTBASEDIR=$(cygpath --path --windows "$MAVEN_PROJECTBASEDIR") +fi + +# Provide a "standardized" way to retrieve the CLI args that will +# work with both Windows and non-Windows executions. +MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $*" +export MAVEN_CMD_LINE_ARGS + +WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +# shellcheck disable=SC2086 # safe args +exec "$JAVACMD" \ + $MAVEN_OPTS \ + $MAVEN_DEBUG_OPTS \ + -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ + "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ + ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" diff --git a/java-amqp/mvnw.cmd b/java-amqp/mvnw.cmd new file mode 100644 index 00000000..c4586b56 --- /dev/null +++ b/java-amqp/mvnw.cmd @@ -0,0 +1,205 @@ +@REM ---------------------------------------------------------------------------- +@REM Licensed to the Apache Software Foundation (ASF) under one +@REM or more contributor license agreements. See the NOTICE file +@REM distributed with this work for additional information +@REM regarding copyright ownership. The ASF licenses this file +@REM to you under the Apache License, Version 2.0 (the +@REM "License"); you may not use this file except in compliance +@REM with the License. You may obtain a copy of the License at +@REM +@REM http://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, +@REM software distributed under the License is distributed on an +@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +@REM KIND, either express or implied. See the License for the +@REM specific language governing permissions and limitations +@REM under the License. +@REM ---------------------------------------------------------------------------- + +@REM ---------------------------------------------------------------------------- +@REM Apache Maven Wrapper startup batch script, version 3.2.0 +@REM +@REM Required ENV vars: +@REM JAVA_HOME - location of a JDK home dir +@REM +@REM Optional ENV vars +@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands +@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending +@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven +@REM e.g. to debug Maven itself, use +@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files +@REM ---------------------------------------------------------------------------- + +@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' +@echo off +@REM set title of command window +title %0 +@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on' +@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% + +@REM set %HOME% to equivalent of $HOME +if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") + +@REM Execute a user defined script before this one +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre +@REM check for pre script, once with legacy .bat ending and once with .cmd ending +if exist "%USERPROFILE%\mavenrc_pre.bat" call "%USERPROFILE%\mavenrc_pre.bat" %* +if exist "%USERPROFILE%\mavenrc_pre.cmd" call "%USERPROFILE%\mavenrc_pre.cmd" %* +:skipRcPre + +@setlocal + +set ERROR_CODE=0 + +@REM To isolate internal variables from possible post scripts, we use another setlocal +@setlocal + +@REM ==== START VALIDATION ==== +if not "%JAVA_HOME%" == "" goto OkJHome + +echo. +echo Error: JAVA_HOME not found in your environment. >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +:OkJHome +if exist "%JAVA_HOME%\bin\java.exe" goto init + +echo. +echo Error: JAVA_HOME is set to an invalid directory. >&2 +echo JAVA_HOME = "%JAVA_HOME%" >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +@REM ==== END VALIDATION ==== + +:init + +@REM Find the project base dir, i.e. the directory that contains the folder ".mvn". +@REM Fallback to current working directory if not found. + +set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% +IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir + +set EXEC_DIR=%CD% +set WDIR=%EXEC_DIR% +:findBaseDir +IF EXIST "%WDIR%"\.mvn goto baseDirFound +cd .. +IF "%WDIR%"=="%CD%" goto baseDirNotFound +set WDIR=%CD% +goto findBaseDir + +:baseDirFound +set MAVEN_PROJECTBASEDIR=%WDIR% +cd "%EXEC_DIR%" +goto endDetectBaseDir + +:baseDirNotFound +set MAVEN_PROJECTBASEDIR=%EXEC_DIR% +cd "%EXEC_DIR%" + +:endDetectBaseDir + +IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig + +@setlocal EnableExtensions EnableDelayedExpansion +for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a +@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% + +:endReadAdditionalConfig + +SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" +set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" +set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +set WRAPPER_URL="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" + +FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( + IF "%%A"=="wrapperUrl" SET WRAPPER_URL=%%B +) + +@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +@REM This allows using the maven wrapper in projects that prohibit checking in binary data. +if exist %WRAPPER_JAR% ( + if "%MVNW_VERBOSE%" == "true" ( + echo Found %WRAPPER_JAR% + ) +) else ( + if not "%MVNW_REPOURL%" == "" ( + SET WRAPPER_URL="%MVNW_REPOURL%/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" + ) + if "%MVNW_VERBOSE%" == "true" ( + echo Couldn't find %WRAPPER_JAR%, downloading it ... + echo Downloading from: %WRAPPER_URL% + ) + + powershell -Command "&{"^ + "$webclient = new-object System.Net.WebClient;"^ + "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ + "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ + "}"^ + "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%WRAPPER_URL%', '%WRAPPER_JAR%')"^ + "}" + if "%MVNW_VERBOSE%" == "true" ( + echo Finished downloading %WRAPPER_JAR% + ) +) +@REM End of extension + +@REM If specified, validate the SHA-256 sum of the Maven wrapper jar file +SET WRAPPER_SHA_256_SUM="" +FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( + IF "%%A"=="wrapperSha256Sum" SET WRAPPER_SHA_256_SUM=%%B +) +IF NOT %WRAPPER_SHA_256_SUM%=="" ( + powershell -Command "&{"^ + "$hash = (Get-FileHash \"%WRAPPER_JAR%\" -Algorithm SHA256).Hash.ToLower();"^ + "If('%WRAPPER_SHA_256_SUM%' -ne $hash){"^ + " Write-Output 'Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised.';"^ + " Write-Output 'Investigate or delete %WRAPPER_JAR% to attempt a clean download.';"^ + " Write-Output 'If you updated your Maven version, you need to update the specified wrapperSha256Sum property.';"^ + " exit 1;"^ + "}"^ + "}" + if ERRORLEVEL 1 goto error +) + +@REM Provide a "standardized" way to retrieve the CLI args that will +@REM work with both Windows and non-Windows executions. +set MAVEN_CMD_LINE_ARGS=%* + +%MAVEN_JAVA_EXE% ^ + %JVM_CONFIG_MAVEN_PROPS% ^ + %MAVEN_OPTS% ^ + %MAVEN_DEBUG_OPTS% ^ + -classpath %WRAPPER_JAR% ^ + "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" ^ + %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* +if ERRORLEVEL 1 goto error +goto end + +:error +set ERROR_CODE=1 + +:end +@endlocal & set ERROR_CODE=%ERROR_CODE% + +if not "%MAVEN_SKIP_RC%"=="" goto skipRcPost +@REM check for post script, once with legacy .bat ending and once with .cmd ending +if exist "%USERPROFILE%\mavenrc_post.bat" call "%USERPROFILE%\mavenrc_post.bat" +if exist "%USERPROFILE%\mavenrc_post.cmd" call "%USERPROFILE%\mavenrc_post.cmd" +:skipRcPost + +@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' +if "%MAVEN_BATCH_PAUSE%"=="on" pause + +if "%MAVEN_TERMINATE_CMD%"=="on" exit %ERROR_CODE% + +cmd /C exit /B %ERROR_CODE% diff --git a/java-amqp/pom.xml b/java-amqp/pom.xml new file mode 100644 index 00000000..26146602 --- /dev/null +++ b/java-amqp/pom.xml @@ -0,0 +1,110 @@ + + 4.0.0 + + com.rabbitmq + rabbitmq-tutorial-amqp + 1.0-SNAPSHOT + jar + + rabbitmq-tutorial-amqp + https://github.com/rabbitmq/rabbitmq-tutorials + + + UTF-8 + 11 + 11 + + + + + com.rabbitmq.client + amqp-client + 1.0.0 + + + + org.slf4j + slf4j-api + 2.0.17 + + + + org.slf4j + slf4j-simple + 2.0.17 + + + + org.junit.jupiter + junit-jupiter + 5.14.4 + test + + + + org.assertj + assertj-core + 3.27.7 + test + + + + + + + + maven-compiler-plugin + 3.15.0 + + 11 + + + + maven-surefire-plugin + 3.5.5 + + ${test-arguments} + + + + org.apache.maven.plugins + maven-clean-plugin + 3.5.0 + + + + org.apache.maven.plugins + maven-resources-plugin + 3.5.0 + + ${project.build.sourceEncoding} + + + + + org.codehaus.mojo + exec-maven-plugin + 3.6.3 + + false + + + + + + + + + jvm-test-arguments-java-21-and-more + + [21,) + + + -XX:+EnableDynamicAgentLoading + + + + + + diff --git a/java-amqp/src/main/java/EmitLog.java b/java-amqp/src/main/java/EmitLog.java new file mode 100644 index 00000000..f7c28ff5 --- /dev/null +++ b/java-amqp/src/main/java/EmitLog.java @@ -0,0 +1,39 @@ +import com.rabbitmq.client.amqp.Connection; +import com.rabbitmq.client.amqp.Environment; +import com.rabbitmq.client.amqp.Management; +import com.rabbitmq.client.amqp.Message; +import com.rabbitmq.client.amqp.Publisher; + +import java.nio.charset.StandardCharsets; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +public class EmitLog { + + private static final String EXCHANGE_NAME = "logs"; + + public static void main(String[] argv) throws Exception { + Environment environment = TutorialSupport.newEnvironment(); + try (Connection connection = environment.connectionBuilder().uri(TutorialSupport.BROKER_URI).build()) { + try (Management management = connection.management()) { + management.exchange().name(EXCHANGE_NAME).type(Management.ExchangeType.FANOUT).declare(); + } + try (Publisher publisher = connection.publisherBuilder().exchange(EXCHANGE_NAME).build()) { + String message = argv.length < 1 ? "info: Hello World!" : String.join(" ", argv); + Message msg = publisher.message(message.getBytes(StandardCharsets.UTF_8)); + CountDownLatch confirmed = new CountDownLatch(1); + publisher.publish(msg, ctx -> { + if (ctx.status() == Publisher.Status.ACCEPTED) { + confirmed.countDown(); + } + }); + if (!confirmed.await(30, TimeUnit.SECONDS)) { + throw new IllegalStateException("Publish was not confirmed in time"); + } + System.out.println(" [x] Sent '" + message + "'"); + } + } finally { + environment.close(); + } + } +} diff --git a/java-amqp/src/main/java/EmitLogDirect.java b/java-amqp/src/main/java/EmitLogDirect.java new file mode 100644 index 00000000..1e053ea9 --- /dev/null +++ b/java-amqp/src/main/java/EmitLogDirect.java @@ -0,0 +1,69 @@ +import com.rabbitmq.client.amqp.Connection; +import com.rabbitmq.client.amqp.Environment; +import com.rabbitmq.client.amqp.Management; +import com.rabbitmq.client.amqp.Message; +import com.rabbitmq.client.amqp.Publisher; + +import java.nio.charset.StandardCharsets; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +public class EmitLogDirect { + + private static final String EXCHANGE_NAME = "direct_logs"; + + public static void main(String[] argv) throws Exception { + Environment environment = TutorialSupport.newEnvironment(); + try (Connection connection = environment.connectionBuilder().uri(TutorialSupport.BROKER_URI).build()) { + try (Management management = connection.management()) { + management.exchange().name(EXCHANGE_NAME).type(Management.ExchangeType.DIRECT).declare(); + } + String severity = getSeverity(argv); + try (Publisher publisher = connection.publisherBuilder().exchange(EXCHANGE_NAME).key(severity).build()) { + String message = getMessage(argv); + Message msg = publisher.message(message.getBytes(StandardCharsets.UTF_8)); + CountDownLatch confirmed = new CountDownLatch(1); + publisher.publish(msg, ctx -> { + if (ctx.status() == Publisher.Status.ACCEPTED) { + confirmed.countDown(); + } + }); + if (!confirmed.await(30, TimeUnit.SECONDS)) { + throw new IllegalStateException("Publish was not confirmed in time"); + } + System.out.println(" [x] Sent '" + severity + "':'" + message + "'"); + } + } finally { + environment.close(); + } + } + + private static String getSeverity(String[] strings) { + if (strings.length < 1) { + return "info"; + } + return strings[0]; + } + + private static String getMessage(String[] strings) { + if (strings.length < 2) { + return "Hello World!"; + } + return joinStrings(strings, " ", 1); + } + + private static String joinStrings(String[] strings, String delimiter, int startIndex) { + int length = strings.length; + if (length == 0) { + return ""; + } + if (length <= startIndex) { + return ""; + } + StringBuilder words = new StringBuilder(strings[startIndex]); + for (int i = startIndex + 1; i < length; i++) { + words.append(delimiter).append(strings[i]); + } + return words.toString(); + } +} diff --git a/java-amqp/src/main/java/EmitLogTopic.java b/java-amqp/src/main/java/EmitLogTopic.java new file mode 100644 index 00000000..c8001017 --- /dev/null +++ b/java-amqp/src/main/java/EmitLogTopic.java @@ -0,0 +1,69 @@ +import com.rabbitmq.client.amqp.Connection; +import com.rabbitmq.client.amqp.Environment; +import com.rabbitmq.client.amqp.Management; +import com.rabbitmq.client.amqp.Message; +import com.rabbitmq.client.amqp.Publisher; + +import java.nio.charset.StandardCharsets; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +public class EmitLogTopic { + + private static final String EXCHANGE_NAME = "topic_logs"; + + public static void main(String[] argv) throws Exception { + Environment environment = TutorialSupport.newEnvironment(); + try (Connection connection = environment.connectionBuilder().uri(TutorialSupport.BROKER_URI).build()) { + try (Management management = connection.management()) { + management.exchange().name(EXCHANGE_NAME).type(Management.ExchangeType.TOPIC).declare(); + } + String routingKey = getRouting(argv); + try (Publisher publisher = connection.publisherBuilder().exchange(EXCHANGE_NAME).key(routingKey).build()) { + String message = getMessage(argv); + Message msg = publisher.message(message.getBytes(StandardCharsets.UTF_8)); + CountDownLatch confirmed = new CountDownLatch(1); + publisher.publish(msg, ctx -> { + if (ctx.status() == Publisher.Status.ACCEPTED) { + confirmed.countDown(); + } + }); + if (!confirmed.await(30, TimeUnit.SECONDS)) { + throw new IllegalStateException("Publish was not confirmed in time"); + } + System.out.println(" [x] Sent '" + routingKey + "':'" + message + "'"); + } + } finally { + environment.close(); + } + } + + private static String getRouting(String[] strings) { + if (strings.length < 1) { + return "anonymous.info"; + } + return strings[0]; + } + + private static String getMessage(String[] strings) { + if (strings.length < 2) { + return "Hello World!"; + } + return joinStrings(strings, " ", 1); + } + + private static String joinStrings(String[] strings, String delimiter, int startIndex) { + int length = strings.length; + if (length == 0) { + return ""; + } + if (length < startIndex) { + return ""; + } + StringBuilder words = new StringBuilder(strings[startIndex]); + for (int i = startIndex + 1; i < length; i++) { + words.append(delimiter).append(strings[i]); + } + return words.toString(); + } +} diff --git a/java-amqp/src/main/java/NewTask.java b/java-amqp/src/main/java/NewTask.java new file mode 100644 index 00000000..80aee9fb --- /dev/null +++ b/java-amqp/src/main/java/NewTask.java @@ -0,0 +1,42 @@ +import com.rabbitmq.client.amqp.Connection; +import com.rabbitmq.client.amqp.Environment; +import com.rabbitmq.client.amqp.Management; +import com.rabbitmq.client.amqp.Message; +import com.rabbitmq.client.amqp.Publisher; + +import java.nio.charset.StandardCharsets; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +public class NewTask { + + private static final String TASK_QUEUE_NAME = "task_queue"; + + public static void main(String[] argv) throws Exception { + Environment environment = TutorialSupport.newEnvironment(); + try (Connection connection = environment.connectionBuilder().uri(TutorialSupport.BROKER_URI).build()) { + try (Management management = connection.management()) { + management.queue(TASK_QUEUE_NAME).quorum().queue().declare(); + // Un-comment the following line to declare a Classic Queue + // Comment the previous queue declaration, if you un-comment below + // management.queue(TASK_QUEUE_NAME).classic().queue().declare(); + } + try (Publisher publisher = connection.publisherBuilder().queue(TASK_QUEUE_NAME).build()) { + String message = String.join(" ", argv); + Message msg = publisher.message(message.getBytes(StandardCharsets.UTF_8)).durable(true); + CountDownLatch confirmed = new CountDownLatch(1); + publisher.publish(msg, ctx -> { + if (ctx.status() == Publisher.Status.ACCEPTED) { + confirmed.countDown(); + } + }); + if (!confirmed.await(30, TimeUnit.SECONDS)) { + throw new IllegalStateException("Publish was not confirmed in time"); + } + System.out.println(" [x] Sent '" + message + "'"); + } + } finally { + environment.close(); + } + } +} diff --git a/java-amqp/src/main/java/PublisherConfirms.java b/java-amqp/src/main/java/PublisherConfirms.java new file mode 100644 index 00000000..ca707c1b --- /dev/null +++ b/java-amqp/src/main/java/PublisherConfirms.java @@ -0,0 +1,132 @@ +import com.rabbitmq.client.amqp.Connection; +import com.rabbitmq.client.amqp.Environment; +import com.rabbitmq.client.amqp.Management; +import com.rabbitmq.client.amqp.Message; +import com.rabbitmq.client.amqp.Publisher; + +import java.time.Duration; +import java.util.UUID; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * AMQP 1.0 clients publish with broker feedback in the {@link Publisher.Callback} (accepted / rejected / released). + * This is the counterpart to AMQP 0.9.1 publisher confirms. + */ +public class PublisherConfirms { + + static final int MESSAGE_COUNT = 50_000; + + public static void main(String[] args) throws Exception { + publishMessagesIndividually(); + publishMessagesInBatch(); + publishMessagesWithOutstandingTracking(); + } + + static void publishMessagesIndividually() throws Exception { + Environment environment = TutorialSupport.newEnvironment(); + try (Connection connection = environment.connectionBuilder().uri(TutorialSupport.BROKER_URI).build()) { + String queue = UUID.randomUUID().toString(); + try (Management management = connection.management()) { + management.queue().name(queue).exclusive(true).autoDelete(true).declare(); + } + try (Publisher publisher = connection.publisherBuilder().queue(queue).build()) { + long start = System.nanoTime(); + for (int i = 0; i < MESSAGE_COUNT; i++) { + String body = String.valueOf(i); + Message msg = publisher.message(body.getBytes()); + CountDownLatch latch = new CountDownLatch(1); + publisher.publish(msg, ctx -> { + if (ctx.status() == Publisher.Status.ACCEPTED) { + latch.countDown(); + } + }); + if (!latch.await(30, TimeUnit.SECONDS)) { + throw new IllegalStateException("Confirm not received for message " + i); + } + } + long end = System.nanoTime(); + System.out.format("Published %,d messages individually in %,d ms%n", MESSAGE_COUNT, + Duration.ofNanos(end - start).toMillis()); + } + } finally { + environment.close(); + } + } + + static void publishMessagesInBatch() throws Exception { + Environment environment = TutorialSupport.newEnvironment(); + try (Connection connection = environment.connectionBuilder().uri(TutorialSupport.BROKER_URI).build()) { + String queue = UUID.randomUUID().toString(); + try (Management management = connection.management()) { + management.queue().name(queue).exclusive(true).autoDelete(true).declare(); + } + try (Publisher publisher = connection.publisherBuilder().queue(queue).build()) { + int batchSize = 100; + long start = System.nanoTime(); + int i = 0; + while (i < MESSAGE_COUNT) { + int n = Math.min(batchSize, MESSAGE_COUNT - i); + CountDownLatch batchLatch = new CountDownLatch(n); + for (int j = 0; j < n; j++) { + String body = String.valueOf(i + j); + Message msg = publisher.message(body.getBytes()); + publisher.publish(msg, ctx -> { + if (ctx.status() == Publisher.Status.ACCEPTED) { + batchLatch.countDown(); + } + }); + } + if (!batchLatch.await(60, TimeUnit.SECONDS)) { + throw new IllegalStateException("Batch confirm incomplete at offset " + i); + } + i += n; + } + long end = System.nanoTime(); + System.out.format("Published %,d messages in batch in %,d ms%n", MESSAGE_COUNT, + Duration.ofNanos(end - start).toMillis()); + } + } finally { + environment.close(); + } + } + + static void publishMessagesWithOutstandingTracking() throws Exception { + Environment environment = TutorialSupport.newEnvironment(); + try (Connection connection = environment.connectionBuilder().uri(TutorialSupport.BROKER_URI).build()) { + String queue = UUID.randomUUID().toString(); + try (Management management = connection.management()) { + management.queue().name(queue).exclusive(true).autoDelete(true).declare(); + } + final int maxOutstanding = 1000; + try (Publisher publisher = connection.publisherBuilder().queue(queue).build()) { + AtomicInteger outstanding = new AtomicInteger(0); + CountDownLatch allDone = new CountDownLatch(MESSAGE_COUNT); + long start = System.nanoTime(); + for (int i = 0; i < MESSAGE_COUNT; i++) { + while (outstanding.get() >= maxOutstanding) { + Thread.sleep(1); + } + outstanding.incrementAndGet(); + String body = String.valueOf(i); + Message msg = publisher.message(body.getBytes()); + publisher.publish(msg, ctx -> { + if (ctx.status() == Publisher.Status.ACCEPTED) { + outstanding.decrementAndGet(); + allDone.countDown(); + } + }); + } + if (!allDone.await(120, TimeUnit.SECONDS)) { + throw new IllegalStateException("Not all messages confirmed"); + } + long end = System.nanoTime(); + System.out.format("Published %,d messages with outstanding window in %,d ms%n", MESSAGE_COUNT, + Duration.ofNanos(end - start).toMillis()); + } + } finally { + environment.close(); + } + } +} diff --git a/java-amqp/src/main/java/RPCClient.java b/java-amqp/src/main/java/RPCClient.java new file mode 100644 index 00000000..c1093404 --- /dev/null +++ b/java-amqp/src/main/java/RPCClient.java @@ -0,0 +1,30 @@ +import com.rabbitmq.client.amqp.Connection; +import com.rabbitmq.client.amqp.Environment; +import com.rabbitmq.client.amqp.Message; +import com.rabbitmq.client.amqp.Requester; + +import java.nio.charset.StandardCharsets; +import java.util.concurrent.TimeUnit; + +public class RPCClient { + + public static void main(String[] argv) throws Exception { + Environment environment = TutorialSupport.newEnvironment(); + try (Connection connection = environment.connectionBuilder().uri(TutorialSupport.BROKER_URI).build(); + Requester requester = connection.requesterBuilder() + .requestAddress().queue("rpc_queue") + .requester() + .build()) { + for (int i = 0; i < 32; i++) { + String iStr = Integer.toString(i); + System.out.println(" [x] Requesting fib(" + iStr + ")"); + Message request = requester.message(iStr.getBytes(StandardCharsets.UTF_8)); + Message reply = requester.publish(request).get(30, TimeUnit.SECONDS); + String response = new String(reply.body(), StandardCharsets.UTF_8); + System.out.println(" [.] Got '" + response + "'"); + } + } finally { + environment.close(); + } + } +} diff --git a/java-amqp/src/main/java/RPCServer.java b/java-amqp/src/main/java/RPCServer.java new file mode 100644 index 00000000..718ba625 --- /dev/null +++ b/java-amqp/src/main/java/RPCServer.java @@ -0,0 +1,56 @@ +import com.rabbitmq.client.amqp.Connection; +import com.rabbitmq.client.amqp.Environment; +import com.rabbitmq.client.amqp.Management; +import com.rabbitmq.client.amqp.Responder; + +import java.nio.charset.StandardCharsets; + +public class RPCServer { + + private static final String RPC_QUEUE_NAME = "rpc_queue"; + + private static int fib(int n) { + if (n == 0) { + return 0; + } + if (n == 1) { + return 1; + } + return fib(n - 1) + fib(n - 2); + } + + public static void main(String[] argv) throws Exception { + Environment environment = TutorialSupport.newEnvironment(); + Connection connection = environment.connectionBuilder().uri(TutorialSupport.BROKER_URI).build(); + try (Management management = connection.management()) { + management.queue().name(RPC_QUEUE_NAME).quorum().queue().declare(); + management.queuePurge(RPC_QUEUE_NAME); + } + + System.out.println(" [x] Awaiting RPC requests"); + + Responder responder = connection.responderBuilder() + .requestQueue(RPC_QUEUE_NAME) + .handler((ctx, req) -> { + String response = ""; + try { + String message = new String(req.body(), StandardCharsets.UTF_8); + int n = Integer.parseInt(message); + System.out.println(" [.] fib(" + message + ")"); + response += fib(n); + } catch (RuntimeException e) { + System.out.println(" [.] " + e); + } + return ctx.message(response.getBytes(StandardCharsets.UTF_8)); + }) + .build(); + + Runtime.getRuntime().addShutdownHook(new Thread(() -> { + responder.close(); + connection.close(); + environment.close(); + })); + + Thread.sleep(Long.MAX_VALUE); + } +} diff --git a/java-amqp/src/main/java/ReceiveLogs.java b/java-amqp/src/main/java/ReceiveLogs.java new file mode 100644 index 00000000..6c596427 --- /dev/null +++ b/java-amqp/src/main/java/ReceiveLogs.java @@ -0,0 +1,48 @@ +import com.rabbitmq.client.amqp.Connection; +import com.rabbitmq.client.amqp.Consumer; +import com.rabbitmq.client.amqp.Environment; +import com.rabbitmq.client.amqp.Management; +import com.rabbitmq.client.amqp.Management.QueueInfo; + +import java.nio.charset.StandardCharsets; + +public class ReceiveLogs { + + private static final String EXCHANGE_NAME = "logs"; + + public static void main(String[] argv) throws Exception { + Environment environment = TutorialSupport.newEnvironment(); + Connection connection = environment.connectionBuilder().uri(TutorialSupport.BROKER_URI).build(); + + String queueName; + try (Management management = connection.management()) { + management.exchange().name(EXCHANGE_NAME).type(Management.ExchangeType.FANOUT).declare(); + QueueInfo q = management.queue().exclusive(true).autoDelete(true).declare(); + queueName = q.name(); + management.binding() + .sourceExchange(EXCHANGE_NAME) + .destinationQueue(queueName) + .key("") + .bind(); + } + + System.out.println(" [*] Waiting for messages. To exit press CTRL+C"); + + Consumer consumer = connection.consumerBuilder() + .queue(queueName) + .messageHandler((ctx, message) -> { + String body = new String(message.body(), StandardCharsets.UTF_8); + System.out.println(" [x] Received '" + body + "'"); + ctx.accept(); + }) + .build(); + + Runtime.getRuntime().addShutdownHook(new Thread(() -> { + consumer.close(); + connection.close(); + environment.close(); + })); + + Thread.sleep(Long.MAX_VALUE); + } +} diff --git a/java-amqp/src/main/java/ReceiveLogsDirect.java b/java-amqp/src/main/java/ReceiveLogsDirect.java new file mode 100644 index 00000000..5591baea --- /dev/null +++ b/java-amqp/src/main/java/ReceiveLogsDirect.java @@ -0,0 +1,66 @@ +import com.rabbitmq.client.amqp.Connection; +import com.rabbitmq.client.amqp.Consumer; +import com.rabbitmq.client.amqp.Environment; +import com.rabbitmq.client.amqp.Management; +import com.rabbitmq.client.amqp.Management.QueueInfo; +import com.rabbitmq.client.amqp.Message; + +import java.nio.charset.StandardCharsets; + +public class ReceiveLogsDirect { + + private static final String EXCHANGE_NAME = "direct_logs"; + + public static void main(String[] argv) throws Exception { + if (argv.length < 1) { + System.err.println("Usage: ReceiveLogsDirect [info] [warning] [error]"); + System.exit(1); + } + + Environment environment = TutorialSupport.newEnvironment(); + Connection connection = environment.connectionBuilder().uri(TutorialSupport.BROKER_URI).build(); + + String queueName; + try (Management management = connection.management()) { + management.exchange().name(EXCHANGE_NAME).type(Management.ExchangeType.DIRECT).declare(); + QueueInfo q = management.queue().exclusive(true).autoDelete(true).declare(); + queueName = q.name(); + for (String severity : argv) { + management.binding() + .sourceExchange(EXCHANGE_NAME) + .destinationQueue(queueName) + .key(severity) + .bind(); + } + } + + System.out.println(" [*] Waiting for messages. To exit press CTRL+C"); + + Consumer consumer = connection.consumerBuilder() + .queue(queueName) + .messageHandler((ctx, message) -> { + String body = new String(message.body(), StandardCharsets.UTF_8); + String routingKey = routingKey(message); + System.out.println(" [x] Received '" + routingKey + "':'" + body + "'"); + ctx.accept(); + }) + .build(); + + Runtime.getRuntime().addShutdownHook(new Thread(() -> { + consumer.close(); + connection.close(); + environment.close(); + })); + + Thread.sleep(Long.MAX_VALUE); + } + + private static String routingKey(Message message) { + Object rk = message.annotation("x-routing-key"); + if (rk != null) { + return rk.toString(); + } + String subject = message.subject(); + return subject != null ? subject : ""; + } +} diff --git a/java-amqp/src/main/java/ReceiveLogsTopic.java b/java-amqp/src/main/java/ReceiveLogsTopic.java new file mode 100644 index 00000000..306baabd --- /dev/null +++ b/java-amqp/src/main/java/ReceiveLogsTopic.java @@ -0,0 +1,66 @@ +import com.rabbitmq.client.amqp.Connection; +import com.rabbitmq.client.amqp.Consumer; +import com.rabbitmq.client.amqp.Environment; +import com.rabbitmq.client.amqp.Management; +import com.rabbitmq.client.amqp.Management.QueueInfo; +import com.rabbitmq.client.amqp.Message; + +import java.nio.charset.StandardCharsets; + +public class ReceiveLogsTopic { + + private static final String EXCHANGE_NAME = "topic_logs"; + + public static void main(String[] argv) throws Exception { + if (argv.length < 1) { + System.err.println("Usage: ReceiveLogsTopic [binding_key]..."); + System.exit(1); + } + + Environment environment = TutorialSupport.newEnvironment(); + Connection connection = environment.connectionBuilder().uri(TutorialSupport.BROKER_URI).build(); + + String queueName; + try (Management management = connection.management()) { + management.exchange().name(EXCHANGE_NAME).type(Management.ExchangeType.TOPIC).declare(); + QueueInfo q = management.queue().exclusive(true).autoDelete(true).declare(); + queueName = q.name(); + for (String bindingKey : argv) { + management.binding() + .sourceExchange(EXCHANGE_NAME) + .destinationQueue(queueName) + .key(bindingKey) + .bind(); + } + } + + System.out.println(" [*] Waiting for messages. To exit press CTRL+C"); + + Consumer consumer = connection.consumerBuilder() + .queue(queueName) + .messageHandler((ctx, message) -> { + String body = new String(message.body(), StandardCharsets.UTF_8); + String routingKey = routingKey(message); + System.out.println(" [x] Received '" + routingKey + "':'" + body + "'"); + ctx.accept(); + }) + .build(); + + Runtime.getRuntime().addShutdownHook(new Thread(() -> { + consumer.close(); + connection.close(); + environment.close(); + })); + + Thread.sleep(Long.MAX_VALUE); + } + + private static String routingKey(Message message) { + Object rk = message.annotation("x-routing-key"); + if (rk != null) { + return rk.toString(); + } + String subject = message.subject(); + return subject != null ? subject : ""; + } +} diff --git a/java-amqp/src/main/java/Recv.java b/java-amqp/src/main/java/Recv.java new file mode 100644 index 00000000..7bea54a2 --- /dev/null +++ b/java-amqp/src/main/java/Recv.java @@ -0,0 +1,38 @@ +import com.rabbitmq.client.amqp.Connection; +import com.rabbitmq.client.amqp.Consumer; +import com.rabbitmq.client.amqp.Environment; +import com.rabbitmq.client.amqp.Management; + +import java.nio.charset.StandardCharsets; + +public class Recv { + + private static final String QUEUE_NAME = "hello"; + + public static void main(String[] argv) throws Exception { + Environment environment = TutorialSupport.newEnvironment(); + Connection connection = environment.connectionBuilder().uri(TutorialSupport.BROKER_URI).build(); + try (Management management = connection.management()) { + management.queue().name(QUEUE_NAME).quorum().queue().declare(); + } + + System.out.println(" [*] Waiting for messages. To exit press CTRL+C"); + + Consumer consumer = connection.consumerBuilder() + .queue(QUEUE_NAME) + .messageHandler((ctx, message) -> { + String body = new String(message.body(), StandardCharsets.UTF_8); + System.out.println(" [x] Received '" + body + "'"); + ctx.accept(); + }) + .build(); + + Runtime.getRuntime().addShutdownHook(new Thread(() -> { + consumer.close(); + connection.close(); + environment.close(); + })); + + Thread.sleep(Long.MAX_VALUE); + } +} diff --git a/java-amqp/src/main/java/Send.java b/java-amqp/src/main/java/Send.java new file mode 100644 index 00000000..3a3410ca --- /dev/null +++ b/java-amqp/src/main/java/Send.java @@ -0,0 +1,39 @@ +import com.rabbitmq.client.amqp.Connection; +import com.rabbitmq.client.amqp.Environment; +import com.rabbitmq.client.amqp.Management; +import com.rabbitmq.client.amqp.Message; +import com.rabbitmq.client.amqp.Publisher; + +import java.nio.charset.StandardCharsets; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +public class Send { + + private static final String QUEUE_NAME = "hello"; + + public static void main(String[] argv) throws Exception { + Environment environment = TutorialSupport.newEnvironment(); + try (Connection connection = environment.connectionBuilder().uri(TutorialSupport.BROKER_URI).build()) { + try (Management management = connection.management()) { + management.queue().name(QUEUE_NAME).quorum().queue().declare(); + } + try (Publisher publisher = connection.publisherBuilder().queue(QUEUE_NAME).build()) { + String message = "Hello World!"; + Message msg = publisher.message(message.getBytes(StandardCharsets.UTF_8)); + CountDownLatch confirmed = new CountDownLatch(1); + publisher.publish(msg, ctx -> { + if (ctx.status() == Publisher.Status.ACCEPTED) { + confirmed.countDown(); + } + }); + if (!confirmed.await(30, TimeUnit.SECONDS)) { + throw new IllegalStateException("Publish was not confirmed in time"); + } + System.out.println(" [x] Sent '" + message + "'"); + } + } finally { + environment.close(); + } + } +} diff --git a/java-amqp/src/main/java/TutorialSupport.java b/java-amqp/src/main/java/TutorialSupport.java new file mode 100644 index 00000000..5eb5fcd8 --- /dev/null +++ b/java-amqp/src/main/java/TutorialSupport.java @@ -0,0 +1,17 @@ +import com.rabbitmq.client.amqp.Environment; +import com.rabbitmq.client.amqp.impl.AmqpEnvironmentBuilder; + +/** + * Shared connection defaults for the AMQP 1.0 tutorials (guest on localhost, default port). + */ +final class TutorialSupport { + + static final String BROKER_URI = "amqp://guest:guest@localhost:5672/%2f"; + + private TutorialSupport() { + } + + static Environment newEnvironment() { + return new AmqpEnvironmentBuilder().build(); + } +} diff --git a/java-amqp/src/main/java/Worker.java b/java-amqp/src/main/java/Worker.java new file mode 100644 index 00000000..164d7d4f --- /dev/null +++ b/java-amqp/src/main/java/Worker.java @@ -0,0 +1,56 @@ +import com.rabbitmq.client.amqp.Connection; +import com.rabbitmq.client.amqp.Consumer; +import com.rabbitmq.client.amqp.Environment; +import com.rabbitmq.client.amqp.Management; + +import java.nio.charset.StandardCharsets; + +public class Worker { + + private static final String TASK_QUEUE_NAME = "task_queue"; + + public static void main(String[] argv) throws Exception { + Environment environment = TutorialSupport.newEnvironment(); + Connection connection = environment.connectionBuilder().uri(TutorialSupport.BROKER_URI).build(); + try (Management management = connection.management()) { + management.queue().name(TASK_QUEUE_NAME).quorum().queue().declare(); + } + + System.out.println(" [*] Waiting for messages. To exit press CTRL+C"); + + Consumer consumer = connection.consumerBuilder() + .queue(TASK_QUEUE_NAME) + .initialCredits(1) + .messageHandler((ctx, message) -> { + String body = new String(message.body(), StandardCharsets.UTF_8); + System.out.println(" [x] Received '" + body + "'"); + try { + doWork(body); + } finally { + System.out.println(" [x] Done"); + ctx.accept(); + } + }) + .build(); + + Runtime.getRuntime().addShutdownHook(new Thread(() -> { + consumer.close(); + connection.close(); + environment.close(); + })); + + Thread.sleep(Long.MAX_VALUE); + } + + private static void doWork(String task) { + for (char ch : task.toCharArray()) { + if (ch == '.') { + try { + Thread.sleep(1000); + } catch (InterruptedException ignored) { + Thread.currentThread().interrupt(); + } + } + } + } +} diff --git a/java-amqp/src/test/java/Amqp10TutorialIntegrationTest.java b/java-amqp/src/test/java/Amqp10TutorialIntegrationTest.java new file mode 100644 index 00000000..a1b3c461 --- /dev/null +++ b/java-amqp/src/test/java/Amqp10TutorialIntegrationTest.java @@ -0,0 +1,306 @@ +import com.rabbitmq.client.amqp.Connection; +import com.rabbitmq.client.amqp.Consumer; +import com.rabbitmq.client.amqp.Environment; +import com.rabbitmq.client.amqp.Management; +import com.rabbitmq.client.amqp.Management.QueueInfo; +import com.rabbitmq.client.amqp.Message; +import com.rabbitmq.client.amqp.Publisher; +import com.rabbitmq.client.amqp.Requester; +import com.rabbitmq.client.amqp.Responder; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.Timeout; + +import java.nio.charset.StandardCharsets; +import java.util.UUID; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.TimeUnit; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Integration tests against a RabbitMQ node on localhost (same requirement as running the tutorials). + */ +class Amqp10TutorialIntegrationTest { + + @Test + @Timeout(60) + void tutorial1_helloWorld() throws Exception { + Environment environment = TutorialSupport.newEnvironment(); + try (Connection connection = environment.connectionBuilder().uri(TutorialSupport.BROKER_URI).build()) { + String queue = "hello_it_" + UUID.randomUUID(); + try (Management management = connection.management()) { + management.queue().name(queue).quorum().queue().declare(); + } + BlockingQueue received = new LinkedBlockingQueue<>(); + Consumer consumer = connection.consumerBuilder() + .queue(queue) + .messageHandler((ctx, msg) -> { + received.offer(new String(msg.body(), StandardCharsets.UTF_8)); + ctx.accept(); + }) + .build(); + try (Publisher publisher = connection.publisherBuilder().queue(queue).build()) { + CountDownLatch ok = new CountDownLatch(1); + Message m = publisher.message("Hello World!".getBytes(StandardCharsets.UTF_8)); + publisher.publish(m, ctx -> { + if (ctx.status() == Publisher.Status.ACCEPTED) { + ok.countDown(); + } + }); + assertThat(ok.await(30, TimeUnit.SECONDS)).isTrue(); + } + assertThat(received.poll(30, TimeUnit.SECONDS)).isEqualTo("Hello World!"); + consumer.close(); + try (Management management = connection.management()) { + management.queueDelete(queue); + } + } finally { + environment.close(); + } + } + + @Test + @Timeout(120) + void tutorial2_workQueueFairDispatch() throws Exception { + Environment environment = TutorialSupport.newEnvironment(); + try (Connection connection = environment.connectionBuilder().uri(TutorialSupport.BROKER_URI).build()) { + String queue = "task_it_" + UUID.randomUUID(); + try (Management management = connection.management()) { + management.queue().name(queue).quorum().queue().declare(); + } + CountDownLatch processed = new CountDownLatch(3); + Consumer consumer = connection.consumerBuilder() + .queue(queue) + .initialCredits(1) + .messageHandler((ctx, msg) -> { + processed.countDown(); + ctx.accept(); + }) + .build(); + try (Publisher publisher = connection.publisherBuilder().queue(queue).build()) { + for (int k = 0; k < 3; k++) { + CountDownLatch ok = new CountDownLatch(1); + Message m = publisher.message(("m" + k).getBytes(StandardCharsets.UTF_8)); + publisher.publish(m, ctx -> { + if (ctx.status() == Publisher.Status.ACCEPTED) { + ok.countDown(); + } + }); + assertThat(ok.await(30, TimeUnit.SECONDS)).isTrue(); + } + } + assertThat(processed.await(30, TimeUnit.SECONDS)).isTrue(); + consumer.close(); + try (Management management = connection.management()) { + management.queueDelete(queue); + } + } finally { + environment.close(); + } + } + + @Test + @Timeout(60) + void tutorial3_publishSubscribe() throws Exception { + String ex = "logs_it_" + UUID.randomUUID(); + Environment environment = TutorialSupport.newEnvironment(); + try (Connection connection = environment.connectionBuilder().uri(TutorialSupport.BROKER_URI).build()) { + String queue; + try (Management management = connection.management()) { + management.exchange().name(ex).type(Management.ExchangeType.FANOUT).declare(); + QueueInfo q = management.queue().exclusive(true).autoDelete(true).declare(); + queue = q.name(); + management.binding().sourceExchange(ex).destinationQueue(queue).key("").bind(); + } + BlockingQueue received = new LinkedBlockingQueue<>(); + Consumer consumer = connection.consumerBuilder() + .queue(queue) + .messageHandler((ctx, msg) -> { + received.offer(new String(msg.body(), StandardCharsets.UTF_8)); + ctx.accept(); + }) + .build(); + try (Publisher publisher = connection.publisherBuilder().exchange(ex).build()) { + CountDownLatch ok = new CountDownLatch(1); + Message m = publisher.message("broadcast".getBytes(StandardCharsets.UTF_8)); + publisher.publish(m, ctx -> { + if (ctx.status() == Publisher.Status.ACCEPTED) { + ok.countDown(); + } + }); + assertThat(ok.await(30, TimeUnit.SECONDS)).isTrue(); + } + assertThat(received.poll(30, TimeUnit.SECONDS)).isEqualTo("broadcast"); + consumer.close(); + try (Management management = connection.management()) { + management.exchangeDelete(ex); + } + } finally { + environment.close(); + } + } + + @Test + @Timeout(60) + void tutorial4_routing() throws Exception { + String ex = "direct_it_" + UUID.randomUUID(); + Environment environment = TutorialSupport.newEnvironment(); + try (Connection connection = environment.connectionBuilder().uri(TutorialSupport.BROKER_URI).build()) { + String queue; + try (Management management = connection.management()) { + management.exchange().name(ex).type(Management.ExchangeType.DIRECT).declare(); + QueueInfo q = management.queue().exclusive(true).autoDelete(true).declare(); + queue = q.name(); + management.binding().sourceExchange(ex).destinationQueue(queue).key("info").bind(); + } + BlockingQueue keys = new LinkedBlockingQueue<>(); + BlockingQueue bodies = new LinkedBlockingQueue<>(); + Consumer consumer = connection.consumerBuilder() + .queue(queue) + .messageHandler((ctx, msg) -> { + Object ann = msg.annotation("x-routing-key"); + String rk = ann != null ? ann.toString() : (msg.subject() != null ? msg.subject() : ""); + keys.offer(rk); + bodies.offer(new String(msg.body(), StandardCharsets.UTF_8)); + ctx.accept(); + }) + .build(); + try (Publisher publisher = connection.publisherBuilder().exchange(ex).key("info").build()) { + CountDownLatch ok = new CountDownLatch(1); + Message m = publisher.message("Hello".getBytes(StandardCharsets.UTF_8)); + publisher.publish(m, ctx -> { + if (ctx.status() == Publisher.Status.ACCEPTED) { + ok.countDown(); + } + }); + assertThat(ok.await(30, TimeUnit.SECONDS)).isTrue(); + } + assertThat(keys.poll(30, TimeUnit.SECONDS)).isEqualTo("info"); + assertThat(bodies.poll(30, TimeUnit.SECONDS)).isEqualTo("Hello"); + consumer.close(); + try (Management management = connection.management()) { + management.exchangeDelete(ex); + } + } finally { + environment.close(); + } + } + + @Test + @Timeout(60) + void tutorial5_topics() throws Exception { + String ex = "topic_it_" + UUID.randomUUID(); + Environment environment = TutorialSupport.newEnvironment(); + try (Connection connection = environment.connectionBuilder().uri(TutorialSupport.BROKER_URI).build()) { + String queue; + try (Management management = connection.management()) { + management.exchange().name(ex).type(Management.ExchangeType.TOPIC).declare(); + QueueInfo q = management.queue().exclusive(true).autoDelete(true).declare(); + queue = q.name(); + management.binding().sourceExchange(ex).destinationQueue(queue).key("*.critical").bind(); + } + BlockingQueue keys = new LinkedBlockingQueue<>(); + Consumer consumer = connection.consumerBuilder() + .queue(queue) + .messageHandler((ctx, msg) -> { + Object ann = msg.annotation("x-routing-key"); + keys.offer(ann != null ? ann.toString() : (msg.subject() != null ? msg.subject() : "")); + ctx.accept(); + }) + .build(); + try (Publisher publisher = connection.publisherBuilder().exchange(ex).key("kern.critical").build()) { + CountDownLatch ok = new CountDownLatch(1); + Message m = publisher.message("A critical kernel error".getBytes(StandardCharsets.UTF_8)); + publisher.publish(m, ctx -> { + if (ctx.status() == Publisher.Status.ACCEPTED) { + ok.countDown(); + } + }); + assertThat(ok.await(30, TimeUnit.SECONDS)).isTrue(); + } + assertThat(keys.poll(30, TimeUnit.SECONDS)).isEqualTo("kern.critical"); + consumer.close(); + try (Management management = connection.management()) { + management.exchangeDelete(ex); + } + } finally { + environment.close(); + } + } + + @Test + @Timeout(60) + void tutorial6_rpc() throws Exception { + String queue = "rpc_it_" + UUID.randomUUID(); + Environment environment = TutorialSupport.newEnvironment(); + try (Connection connection = environment.connectionBuilder().uri(TutorialSupport.BROKER_URI).build()) { + try (Management management = connection.management()) { + management.queue().name(queue).quorum().queue().declare(); + } + Responder responder = connection.responderBuilder() + .requestQueue(queue) + .handler((ctx, req) -> { + int n = Integer.parseInt(new String(req.body(), StandardCharsets.UTF_8)); + int fib = fib(n); + return ctx.message(String.valueOf(fib).getBytes(StandardCharsets.UTF_8)); + }) + .build(); + try (Requester requester = connection.requesterBuilder() + .requestAddress().queue(queue) + .requester() + .build()) { + Message reply = requester.publish(requester.message("10".getBytes(StandardCharsets.UTF_8))) + .get(30, TimeUnit.SECONDS); + assertThat(new String(reply.body(), StandardCharsets.UTF_8)).isEqualTo("55"); + } finally { + responder.close(); + } + try (Management management = connection.management()) { + management.queueDelete(queue); + } + } finally { + environment.close(); + } + } + + private static int fib(int n) { + if (n == 0) { + return 0; + } + if (n == 1) { + return 1; + } + return fib(n - 1) + fib(n - 2); + } + + @Test + @Timeout(120) + void tutorial7_publisherConfirms() throws Exception { + Environment environment = TutorialSupport.newEnvironment(); + try (Connection connection = environment.connectionBuilder().uri(TutorialSupport.BROKER_URI).build()) { + String queue = UUID.randomUUID().toString(); + try (Management management = connection.management()) { + management.queue().name(queue).exclusive(true).autoDelete(true).declare(); + } + int nMsg = 500; + CountDownLatch confirmed = new CountDownLatch(nMsg); + try (Publisher publisher = connection.publisherBuilder().queue(queue).build()) { + for (int i = 0; i < nMsg; i++) { + String body = String.valueOf(i); + Message m = publisher.message(body.getBytes(StandardCharsets.UTF_8)); + publisher.publish(m, ctx -> { + if (ctx.status() == Publisher.Status.ACCEPTED) { + confirmed.countDown(); + } + }); + } + assertThat(confirmed.await(60, TimeUnit.SECONDS)).isTrue(); + } + } finally { + environment.close(); + } + } +} diff --git a/java-gradle/.gitignore b/java-gradle/.gitignore new file mode 100644 index 00000000..581bf51f --- /dev/null +++ b/java-gradle/.gitignore @@ -0,0 +1,39 @@ +.gradle +build/ +!gradle/wrapper/gradle-wrapper.jar +!**/src/main/**/build/ +!**/src/test/**/build/ + +### IntelliJ IDEA ### +.idea/ +*.iws +*.iml +*.ipr +out/ +!**/src/main/**/out/ +!**/src/test/**/out/ + +### Eclipse ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache +bin/ +!**/src/main/**/bin/ +!**/src/test/**/bin/ + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ + +### VS Code ### +.vscode/ + +### Mac OS ### +.DS_Store diff --git a/java-gradle/README.md b/java-gradle/README.md new file mode 100644 index 00000000..1d0319f2 --- /dev/null +++ b/java-gradle/README.md @@ -0,0 +1,119 @@ +# Java code for RabbitMQ tutorials + +Here you can find the Java code examples from [RabbitMQ +tutorials](https://www.rabbitmq.com/getstarted.html). + +To successfully use the examples you will need a RabbitMQ node running locally. + +You can easily set this up by [installing RabbitMQ](https://www.rabbitmq.com/docs/download). + +## Requirements + +### Linux + +- Note the source files are symbolic links to the [java](https://github.com/rabbitmq/rabbitmq-tutorials/tree/main/java) directory. + +### Windows + +- Run pull-source-files.bat to replace symbolic link to the actual source file. + +```shell +./pull-source-files.bat +``` + +## Code + +#### [Tutorial one: "Hello World!"](https://www.rabbitmq.com/tutorials/tutorial-one-java.html): + +```shell +# terminal tab 1 +./gradlew -Pmain=Recv run + +# terminal tab 2 +./gradlew -Pmain=Send run +``` + +#### [Tutorial two: Work Queues](https://www.rabbitmq.com/tutorials/tutorial-two-java.html): + +```shell +# terminal tab 1 +./gradlew -Pmain=Worker run + +# terminal tab 2 +./gradlew -Pmain=Worker run + +# terminal tab 3 +./gradlew -Pmain=NewTask run --args "First Message" +./gradlew -Pmain=NewTask run --args "Second Message" +./gradlew -Pmain=NewTask run --args "Third Message" +./gradlew -Pmain=NewTask run --args "Fourth Message" +./gradlew -Pmain=NewTask run --args "Fifth Message" +``` + +#### [Tutorial three: Publish/Subscribe](https://www.rabbitmq.com/tutorials/tutorial-three-java.html) + +```shell +# terminal tab 1 +./gradlew -Pmain=ReceiveLogs run + +# terminal tab 2 +./gradlew -Pmain=ReceiveLogs run + +# terminal tab 3 +./gradlew -Pmain=EmitLog run +``` + +#### [Tutorial four: Routing](https://www.rabbitmq.com/tutorials/tutorial-four-java.html) + +```shell +# terminal tab 1 +./gradlew -Pmain=ReceiveLogsDirect run --args "warning error" + +# terminal tab 2 +./gradlew -Pmain=ReceiveLogsDirect run --args "info warning error" + +# terminal tab 3 +./gradlew -Pmain=EmitLogDirect run --args "info 'Run. Run. Or it will explode'" +./gradlew -Pmain=EmitLogDirect run --args "warning 'Run. Run. Or it will explode'" +./gradlew -Pmain=EmitLogDirect run --args "error 'Run. Run. Or it will explode'" +``` + +#### [Tutorial five: Topics](https://www.rabbitmq.com/tutorials/tutorial-five-java.html) + +```shell +# terminal tab 1 +# To receive all the logs: +./gradlew -Pmain=ReceiveLogsTopic run --args "#" + +# To receive all logs from the facility "kern": +./gradlew -Pmain=ReceiveLogsTopic run --args "kern.*" + +# Or if you want to hear only about "critical" logs: +./gradlew -Pmain=ReceiveLogsTopic run --args "*.critical" + +# You can create multiple bindings: +./gradlew -Pmain=ReceiveLogsTopic run --args "kern.* *.critical" + +# terminal tab 2 +# And to emit a log with a routing key "kern.critical" type: +./gradlew -Pmain=EmitLogTopic run --args "kern.critical A critical kernel error" +``` + +#### [Tutorial six: RPC](https://www.rabbitmq.com/tutorials/tutorial-six-java.html) + +```shell +# terminal tab 1 +# Our RPC service is now ready. We can start the server: +./gradlew -Pmain=RPCServer run + +# terminal tab 2 +# To request a fibonacci number run the client: +./gradlew -Pmain=RPCClient run +``` + +#### [Tutorial seven: Publisher Confirms](https://www.rabbitmq.com/tutorials/tutorial-seven-java.html) + +```shell +# terminal tab 1 +./gradlew -Pmain=PublisherConfirms run +``` diff --git a/java-gradle/build.gradle b/java-gradle/build.gradle new file mode 100644 index 00000000..07b93456 --- /dev/null +++ b/java-gradle/build.gradle @@ -0,0 +1,29 @@ +plugins { + id 'application' +} + +group 'com.rabbitmq.client' +version '1.0-SNAPSHOT' + +repositories { + mavenCentral() +} + +dependencies { + implementation 'com.rabbitmq:amqp-client:5.30.0' + implementation 'org.slf4j:slf4j-simple:2.0.17' + testImplementation 'org.assertj:assertj-core:3.27.7' + testImplementation 'org.mockito:mockito-core:5.23.0' + testImplementation 'io.dropwizard.metrics:metrics-core:4.2.38' + testImplementation 'org.junit.jupiter:junit-jupiter-api:6.0.0' + testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:6.0.0' +} + +test { + useJUnitPlatform() +} + +application { + mainClassName = project.hasProperty("main") ? + project.getProperty("main") : "NULL" +} \ No newline at end of file diff --git a/java-gradle/gradle/wrapper/gradle-wrapper.jar b/java-gradle/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 00000000..b1b8ef56 Binary files /dev/null and b/java-gradle/gradle/wrapper/gradle-wrapper.jar differ diff --git a/java-gradle/gradle/wrapper/gradle-wrapper.properties b/java-gradle/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 00000000..743f4870 --- /dev/null +++ b/java-gradle/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,8 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-9.5.0-bin.zip +networkTimeout=10000 +retries=0 +retryBackOffMs=500 +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/java-gradle/gradlew b/java-gradle/gradlew new file mode 100755 index 00000000..b9bb139f --- /dev/null +++ b/java-gradle/gradlew @@ -0,0 +1,248 @@ +#!/bin/sh + +# +# Copyright © 2015 the original authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# SPDX-License-Identifier: Apache-2.0 +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/3d91ce3b8caaf77ad09f381f43615b715b53f72c/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/java-gradle/gradlew.bat b/java-gradle/gradlew.bat new file mode 100644 index 00000000..aa5f10b0 --- /dev/null +++ b/java-gradle/gradlew.bat @@ -0,0 +1,82 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables, and ensure extensions are enabled +setlocal EnableExtensions + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +"%COMSPEC%" /c exit 1 + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +"%COMSPEC%" /c exit 1 + +:execute +@rem Setup the command line + + + +@rem Execute Gradle +@rem endlocal doesn't take effect until after the line is parsed and variables are expanded +@rem which allows us to clear the local environment before executing the java command +endlocal & "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* & call :exitWithErrorLevel + +:exitWithErrorLevel +@rem Use "%COMSPEC%" /c exit to allow operators to work properly in scripts +"%COMSPEC%" /c exit %ERRORLEVEL% diff --git a/java-gradle/pull-source-files.bat b/java-gradle/pull-source-files.bat new file mode 100644 index 00000000..f414b47a --- /dev/null +++ b/java-gradle/pull-source-files.bat @@ -0,0 +1 @@ +copy ..\java\*.java src\main\java\ \ No newline at end of file diff --git a/java-gradle/settings.gradle b/java-gradle/settings.gradle new file mode 100644 index 00000000..7e47a7e9 --- /dev/null +++ b/java-gradle/settings.gradle @@ -0,0 +1,2 @@ +rootProject.name = 'rabbitmq-tutorials' + diff --git a/java-gradle/src/main/java/EmitLog.java b/java-gradle/src/main/java/EmitLog.java new file mode 120000 index 00000000..f65914c3 --- /dev/null +++ b/java-gradle/src/main/java/EmitLog.java @@ -0,0 +1 @@ +../../../../java/EmitLog.java \ No newline at end of file diff --git a/java-gradle/src/main/java/EmitLogDirect.java b/java-gradle/src/main/java/EmitLogDirect.java new file mode 120000 index 00000000..ab4bd7ba --- /dev/null +++ b/java-gradle/src/main/java/EmitLogDirect.java @@ -0,0 +1 @@ +../../../../java/EmitLogDirect.java \ No newline at end of file diff --git a/java-gradle/src/main/java/EmitLogHeader.java b/java-gradle/src/main/java/EmitLogHeader.java new file mode 120000 index 00000000..0c818354 --- /dev/null +++ b/java-gradle/src/main/java/EmitLogHeader.java @@ -0,0 +1 @@ +../../../../java/EmitLogHeader.java \ No newline at end of file diff --git a/java-gradle/src/main/java/EmitLogTopic.java b/java-gradle/src/main/java/EmitLogTopic.java new file mode 120000 index 00000000..b4662bb9 --- /dev/null +++ b/java-gradle/src/main/java/EmitLogTopic.java @@ -0,0 +1 @@ +../../../../java/EmitLogTopic.java \ No newline at end of file diff --git a/java-gradle/src/main/java/NewTask.java b/java-gradle/src/main/java/NewTask.java new file mode 120000 index 00000000..160b6504 --- /dev/null +++ b/java-gradle/src/main/java/NewTask.java @@ -0,0 +1 @@ +../../../../java/NewTask.java \ No newline at end of file diff --git a/java-gradle/src/main/java/PublisherConfirms.java b/java-gradle/src/main/java/PublisherConfirms.java new file mode 120000 index 00000000..543c9ceb --- /dev/null +++ b/java-gradle/src/main/java/PublisherConfirms.java @@ -0,0 +1 @@ +../../../../java/PublisherConfirms.java \ No newline at end of file diff --git a/java-gradle/src/main/java/RPCClient.java b/java-gradle/src/main/java/RPCClient.java new file mode 120000 index 00000000..41a0d676 --- /dev/null +++ b/java-gradle/src/main/java/RPCClient.java @@ -0,0 +1 @@ +../../../../java/RPCClient.java \ No newline at end of file diff --git a/java-gradle/src/main/java/RPCServer.java b/java-gradle/src/main/java/RPCServer.java new file mode 120000 index 00000000..40dd9654 --- /dev/null +++ b/java-gradle/src/main/java/RPCServer.java @@ -0,0 +1 @@ +../../../../java/RPCServer.java \ No newline at end of file diff --git a/java-gradle/src/main/java/ReceiveLogHeader.java b/java-gradle/src/main/java/ReceiveLogHeader.java new file mode 120000 index 00000000..96653bbc --- /dev/null +++ b/java-gradle/src/main/java/ReceiveLogHeader.java @@ -0,0 +1 @@ +../../../../java/ReceiveLogHeader.java \ No newline at end of file diff --git a/java-gradle/src/main/java/ReceiveLogs.java b/java-gradle/src/main/java/ReceiveLogs.java new file mode 120000 index 00000000..c37ec06b --- /dev/null +++ b/java-gradle/src/main/java/ReceiveLogs.java @@ -0,0 +1 @@ +../../../../java/ReceiveLogs.java \ No newline at end of file diff --git a/java-gradle/src/main/java/ReceiveLogsDirect.java b/java-gradle/src/main/java/ReceiveLogsDirect.java new file mode 120000 index 00000000..00a7cf55 --- /dev/null +++ b/java-gradle/src/main/java/ReceiveLogsDirect.java @@ -0,0 +1 @@ +../../../../java/ReceiveLogsDirect.java \ No newline at end of file diff --git a/java-gradle/src/main/java/ReceiveLogsTopic.java b/java-gradle/src/main/java/ReceiveLogsTopic.java new file mode 120000 index 00000000..b1dbe53a --- /dev/null +++ b/java-gradle/src/main/java/ReceiveLogsTopic.java @@ -0,0 +1 @@ +../../../../java/ReceiveLogsTopic.java \ No newline at end of file diff --git a/java-gradle/src/main/java/Recv.java b/java-gradle/src/main/java/Recv.java new file mode 120000 index 00000000..8e871235 --- /dev/null +++ b/java-gradle/src/main/java/Recv.java @@ -0,0 +1 @@ +../../../../java/Recv.java \ No newline at end of file diff --git a/java-gradle/src/main/java/Send.java b/java-gradle/src/main/java/Send.java new file mode 120000 index 00000000..16b12d58 --- /dev/null +++ b/java-gradle/src/main/java/Send.java @@ -0,0 +1 @@ +../../../../java/Send.java \ No newline at end of file diff --git a/java-gradle/src/main/java/Worker.java b/java-gradle/src/main/java/Worker.java new file mode 120000 index 00000000..39830cc6 --- /dev/null +++ b/java-gradle/src/main/java/Worker.java @@ -0,0 +1 @@ +../../../../java/Worker.java \ No newline at end of file diff --git a/java-idea/.idea/.name b/java-idea/.idea/.name deleted file mode 100644 index 8378ebc3..00000000 --- a/java-idea/.idea/.name +++ /dev/null @@ -1 +0,0 @@ -rabbitmq-java-tutorials \ No newline at end of file diff --git a/java-idea/.idea/codeStyleSettings.xml b/java-idea/.idea/codeStyleSettings.xml deleted file mode 100644 index 01899622..00000000 --- a/java-idea/.idea/codeStyleSettings.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/java-idea/.idea/compiler.xml b/java-idea/.idea/compiler.xml deleted file mode 100644 index 96cc43ef..00000000 --- a/java-idea/.idea/compiler.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/java-idea/.idea/emacs.xml b/java-idea/.idea/emacs.xml deleted file mode 100644 index a1e816fc..00000000 --- a/java-idea/.idea/emacs.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - \ No newline at end of file diff --git a/java-idea/.idea/libraries/com_rabbitmq_amqp_client_3_5_4.xml b/java-idea/.idea/libraries/com_rabbitmq_amqp_client_3_5_4.xml deleted file mode 100644 index 06a4135a..00000000 --- a/java-idea/.idea/libraries/com_rabbitmq_amqp_client_3_5_4.xml +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - - - - - - - \ No newline at end of file diff --git a/java-idea/.idea/misc.xml b/java-idea/.idea/misc.xml deleted file mode 100644 index 045159c3..00000000 --- a/java-idea/.idea/misc.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/java-idea/.idea/modules.xml b/java-idea/.idea/modules.xml deleted file mode 100644 index 4449b62d..00000000 --- a/java-idea/.idea/modules.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/java-idea/.idea/vcs.xml b/java-idea/.idea/vcs.xml deleted file mode 100644 index 6c0b8635..00000000 --- a/java-idea/.idea/vcs.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/java-idea/lib/amqp-client-3.5.4-javadoc.jar b/java-idea/lib/amqp-client-3.5.4-javadoc.jar deleted file mode 100644 index 91bb82c7..00000000 Binary files a/java-idea/lib/amqp-client-3.5.4-javadoc.jar and /dev/null differ diff --git a/java-idea/lib/amqp-client-3.5.4-sources.jar b/java-idea/lib/amqp-client-3.5.4-sources.jar deleted file mode 100644 index ed8f4cd8..00000000 Binary files a/java-idea/lib/amqp-client-3.5.4-sources.jar and /dev/null differ diff --git a/java-idea/lib/amqp-client-3.5.4.jar b/java-idea/lib/amqp-client-3.5.4.jar deleted file mode 100644 index 97aa2914..00000000 Binary files a/java-idea/lib/amqp-client-3.5.4.jar and /dev/null differ diff --git a/java-idea/src/EmitLog.java b/java-idea/src/EmitLog.java deleted file mode 120000 index 32bc0191..00000000 --- a/java-idea/src/EmitLog.java +++ /dev/null @@ -1 +0,0 @@ -../../java/EmitLog.java \ No newline at end of file diff --git a/java-idea/src/EmitLogDirect.java b/java-idea/src/EmitLogDirect.java deleted file mode 120000 index bc74d66c..00000000 --- a/java-idea/src/EmitLogDirect.java +++ /dev/null @@ -1 +0,0 @@ -../../java/EmitLogDirect.java \ No newline at end of file diff --git a/java-idea/src/EmitLogHeader.java b/java-idea/src/EmitLogHeader.java deleted file mode 120000 index 0f9a266f..00000000 --- a/java-idea/src/EmitLogHeader.java +++ /dev/null @@ -1 +0,0 @@ -../../java/EmitLogHeader.java \ No newline at end of file diff --git a/java-idea/src/EmitLogTopic.java b/java-idea/src/EmitLogTopic.java deleted file mode 120000 index 40c49659..00000000 --- a/java-idea/src/EmitLogTopic.java +++ /dev/null @@ -1 +0,0 @@ -../../java/EmitLogTopic.java \ No newline at end of file diff --git a/java-idea/src/NewTask.java b/java-idea/src/NewTask.java deleted file mode 120000 index 8e189155..00000000 --- a/java-idea/src/NewTask.java +++ /dev/null @@ -1 +0,0 @@ -../../java/NewTask.java \ No newline at end of file diff --git a/java-idea/src/RPCClient.java b/java-idea/src/RPCClient.java deleted file mode 120000 index 4f1d2e4d..00000000 --- a/java-idea/src/RPCClient.java +++ /dev/null @@ -1 +0,0 @@ -../../java/RPCClient.java \ No newline at end of file diff --git a/java-idea/src/RPCServer.java b/java-idea/src/RPCServer.java deleted file mode 120000 index 5559abc4..00000000 --- a/java-idea/src/RPCServer.java +++ /dev/null @@ -1 +0,0 @@ -../../java/RPCServer.java \ No newline at end of file diff --git a/java-idea/src/ReceiveLogHeader.java b/java-idea/src/ReceiveLogHeader.java deleted file mode 120000 index fd5d5b34..00000000 --- a/java-idea/src/ReceiveLogHeader.java +++ /dev/null @@ -1 +0,0 @@ -../../java/ReceiveLogHeader.java \ No newline at end of file diff --git a/java-idea/src/ReceiveLogs.java b/java-idea/src/ReceiveLogs.java deleted file mode 120000 index 7eaab189..00000000 --- a/java-idea/src/ReceiveLogs.java +++ /dev/null @@ -1 +0,0 @@ -../../java/ReceiveLogs.java \ No newline at end of file diff --git a/java-idea/src/ReceiveLogsDirect.java b/java-idea/src/ReceiveLogsDirect.java deleted file mode 120000 index 033cd067..00000000 --- a/java-idea/src/ReceiveLogsDirect.java +++ /dev/null @@ -1 +0,0 @@ -../../java/ReceiveLogsDirect.java \ No newline at end of file diff --git a/java-idea/src/ReceiveLogsTopic.java b/java-idea/src/ReceiveLogsTopic.java deleted file mode 120000 index 943ea267..00000000 --- a/java-idea/src/ReceiveLogsTopic.java +++ /dev/null @@ -1 +0,0 @@ -../../java/ReceiveLogsTopic.java \ No newline at end of file diff --git a/java-idea/src/Recv.java b/java-idea/src/Recv.java deleted file mode 120000 index bd95286c..00000000 --- a/java-idea/src/Recv.java +++ /dev/null @@ -1 +0,0 @@ -../../java/Recv.java \ No newline at end of file diff --git a/java-idea/src/Send.java b/java-idea/src/Send.java deleted file mode 120000 index 38371d99..00000000 --- a/java-idea/src/Send.java +++ /dev/null @@ -1 +0,0 @@ -../../java/Send.java \ No newline at end of file diff --git a/java-idea/src/Worker.java b/java-idea/src/Worker.java deleted file mode 120000 index 35bbea07..00000000 --- a/java-idea/src/Worker.java +++ /dev/null @@ -1 +0,0 @@ -../../java/Worker.java \ No newline at end of file diff --git a/java-mvn/.mvn/wrapper/maven-wrapper.jar b/java-mvn/.mvn/wrapper/maven-wrapper.jar new file mode 100644 index 00000000..cb28b0e3 Binary files /dev/null and b/java-mvn/.mvn/wrapper/maven-wrapper.jar differ diff --git a/java-mvn/.mvn/wrapper/maven-wrapper.properties b/java-mvn/.mvn/wrapper/maven-wrapper.properties new file mode 100644 index 00000000..346d645f --- /dev/null +++ b/java-mvn/.mvn/wrapper/maven-wrapper.properties @@ -0,0 +1,18 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.6/apache-maven-3.9.6-bin.zip +wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar diff --git a/java-mvn/README.md b/java-mvn/README.md new file mode 100644 index 00000000..1786eecb --- /dev/null +++ b/java-mvn/README.md @@ -0,0 +1,119 @@ +# Java code for RabbitMQ tutorials + +Here you can find the Java code examples from [RabbitMQ +tutorials](https://www.rabbitmq.com/getstarted.html). + +To successfully use the examples you will need a RabbitMQ node running locally. + +You can easily set this up by [installing RabbitMQ](https://www.rabbitmq.com/docs/download). + +## Requirements + +### Linux + +- Note the source files are symbolic links to the [java](https://github.com/rabbitmq/rabbitmq-tutorials/tree/main/java) directory. + +### Windows + +- Run pull-source-files.bat to replace symbolic link to the actual source file. + +```shell +./pull-source-files.bat +``` + +## Code + +#### [Tutorial one: "Hello World!"](https://www.rabbitmq.com/tutorials/tutorial-one-java.html): + +```shell +# terminal tab 1 +./mvnw compile exec:java -D"exec.mainClass=Recv" + +# terminal tab 2 +./mvnw compile exec:java -D"exec.mainClass=Send" +``` + +#### [Tutorial two: Work Queues](https://www.rabbitmq.com/tutorials/tutorial-two-java.html): + +```shell +# terminal tab 1 +./mvnw compile exec:java -D"exec.mainClass=Worker" + +# terminal tab 2 +./mvnw compile exec:java -D"exec.mainClass=Worker" + +# terminal tab 3 +./mvnw compile exec:java -D"exec.mainClass=NewTask" -D"exec.args='First Message'" +./mvnw compile exec:java -D"exec.mainClass=NewTask" -D"exec.args='Second Message'" +./mvnw compile exec:java -D"exec.mainClass=NewTask" -D"exec.args='Third Message'" +./mvnw compile exec:java -D"exec.mainClass=NewTask" -D"exec.args='Fourth Message'" +./mvnw compile exec:java -D"exec.mainClass=NewTask" -D"exec.args='Fifth Message'" +``` + +#### [Tutorial three: Publish/Subscribe](https://www.rabbitmq.com/tutorials/tutorial-three-java.html) + +```shell +# terminal tab 1 +./mvnw compile exec:java -D"exec.mainClass=ReceiveLogs" + +# terminal tab 2 +./mvnw compile exec:java -D"exec.mainClass=ReceiveLogs" + +# terminal tab 3 +./mvnw compile exec:java -D"exec.mainClass=EmitLog" +``` + +#### [Tutorial four: Routing](https://www.rabbitmq.com/tutorials/tutorial-four-java.html) + +```shell +# terminal tab 1 +./mvnw compile exec:java -D"exec.mainClass=ReceiveLogsDirect" -D"exec.args=warning error" + +# terminal tab 2 +./mvnw compile exec:java -D"exec.mainClass=ReceiveLogsDirect" -D"exec.args=info warning error" + +# terminal tab 3 +./mvnw compile exec:java -D"exec.mainClass=EmitLogDirect" -D"exec.args=info 'Run. Run. Or it will explode'" +./mvnw compile exec:java -D"exec.mainClass=EmitLogDirect" -D"exec.args=warning 'Run. Run. Or it will explode'" +./mvnw compile exec:java -D"exec.mainClass=EmitLogDirect" -D"exec.args=error 'Run. Run. Or it will explode'" +``` + +#### [Tutorial five: Topics](https://www.rabbitmq.com/tutorials/tutorial-five-java.html) + +```shell +# terminal tab 1 +# To receive all the logs: +./mvnw compile exec:java -D"exec.mainClass=ReceiveLogsTopic" -D"exec.args=#" + +# To receive all logs from the facility "kern": +./mvnw compile exec:java -D"exec.mainClass=ReceiveLogsTopic" -D"exec.args=kern.*" + +# Or if you want to hear only about "critical" logs: +./mvnw compile exec:java -D"exec.mainClass=ReceiveLogsTopic" -D"exec.args=*.critical" + +# You can create multiple bindings: +./mvnw compile exec:java -D"exec.mainClass=ReceiveLogsTopic" -D"exec.args=kern.* *.critical" + +# terminal tab 2 +# And to emit a log with a routing key "kern.critical" type: +./mvnw compile exec:java -D"exec.mainClass=EmitLogTopic" -D"exec.args=kern.critical A critical kernel error" +``` + +#### [Tutorial six: RPC](https://www.rabbitmq.com/tutorials/tutorial-six-java.html) + +```shell +# terminal tab 1 +# Our RPC service is now ready. We can start the server: +./mvnw compile exec:java -D"exec.mainClass=RPCServer" + +# terminal tab 2 +# To request a fibonacci number run the client: +./mvnw compile exec:java -D"exec.mainClass=RPCClient" +``` + +#### [Tutorial seven: Publisher Confirms](https://www.rabbitmq.com/tutorials/tutorial-seven-java.html) + +```shell +# terminal tab 1 +./mvnw compile exec:java -D"exec.mainClass=PublisherConfirms" +``` diff --git a/java-mvn/mvnw b/java-mvn/mvnw new file mode 100755 index 00000000..8d937f4c --- /dev/null +++ b/java-mvn/mvnw @@ -0,0 +1,308 @@ +#!/bin/sh +# ---------------------------------------------------------------------------- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# ---------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------- +# Apache Maven Wrapper startup batch script, version 3.2.0 +# +# Required ENV vars: +# ------------------ +# JAVA_HOME - location of a JDK home dir +# +# Optional ENV vars +# ----------------- +# MAVEN_OPTS - parameters passed to the Java VM when running Maven +# e.g. to debug Maven itself, use +# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +# MAVEN_SKIP_RC - flag to disable loading of mavenrc files +# ---------------------------------------------------------------------------- + +if [ -z "$MAVEN_SKIP_RC" ] ; then + + if [ -f /usr/local/etc/mavenrc ] ; then + . /usr/local/etc/mavenrc + fi + + if [ -f /etc/mavenrc ] ; then + . /etc/mavenrc + fi + + if [ -f "$HOME/.mavenrc" ] ; then + . "$HOME/.mavenrc" + fi + +fi + +# OS specific support. $var _must_ be set to either true or false. +cygwin=false; +darwin=false; +mingw=false +case "$(uname)" in + CYGWIN*) cygwin=true ;; + MINGW*) mingw=true;; + Darwin*) darwin=true + # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home + # See https://developer.apple.com/library/mac/qa/qa1170/_index.html + if [ -z "$JAVA_HOME" ]; then + if [ -x "/usr/libexec/java_home" ]; then + JAVA_HOME="$(/usr/libexec/java_home)"; export JAVA_HOME + else + JAVA_HOME="/Library/Java/Home"; export JAVA_HOME + fi + fi + ;; +esac + +if [ -z "$JAVA_HOME" ] ; then + if [ -r /etc/gentoo-release ] ; then + JAVA_HOME=$(java-config --jre-home) + fi +fi + +# For Cygwin, ensure paths are in UNIX format before anything is touched +if $cygwin ; then + [ -n "$JAVA_HOME" ] && + JAVA_HOME=$(cygpath --unix "$JAVA_HOME") + [ -n "$CLASSPATH" ] && + CLASSPATH=$(cygpath --path --unix "$CLASSPATH") +fi + +# For Mingw, ensure paths are in UNIX format before anything is touched +if $mingw ; then + [ -n "$JAVA_HOME" ] && [ -d "$JAVA_HOME" ] && + JAVA_HOME="$(cd "$JAVA_HOME" || (echo "cannot cd into $JAVA_HOME."; exit 1); pwd)" +fi + +if [ -z "$JAVA_HOME" ]; then + javaExecutable="$(which javac)" + if [ -n "$javaExecutable" ] && ! [ "$(expr "\"$javaExecutable\"" : '\([^ ]*\)')" = "no" ]; then + # readlink(1) is not available as standard on Solaris 10. + readLink=$(which readlink) + if [ ! "$(expr "$readLink" : '\([^ ]*\)')" = "no" ]; then + if $darwin ; then + javaHome="$(dirname "\"$javaExecutable\"")" + javaExecutable="$(cd "\"$javaHome\"" && pwd -P)/javac" + else + javaExecutable="$(readlink -f "\"$javaExecutable\"")" + fi + javaHome="$(dirname "\"$javaExecutable\"")" + javaHome=$(expr "$javaHome" : '\(.*\)/bin') + JAVA_HOME="$javaHome" + export JAVA_HOME + fi + fi +fi + +if [ -z "$JAVACMD" ] ; then + if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + else + JAVACMD="$(\unset -f command 2>/dev/null; \command -v java)" + fi +fi + +if [ ! -x "$JAVACMD" ] ; then + echo "Error: JAVA_HOME is not defined correctly." >&2 + echo " We cannot execute $JAVACMD" >&2 + exit 1 +fi + +if [ -z "$JAVA_HOME" ] ; then + echo "Warning: JAVA_HOME environment variable is not set." +fi + +# traverses directory structure from process work directory to filesystem root +# first directory with .mvn subdirectory is considered project base directory +find_maven_basedir() { + if [ -z "$1" ] + then + echo "Path not specified to find_maven_basedir" + return 1 + fi + + basedir="$1" + wdir="$1" + while [ "$wdir" != '/' ] ; do + if [ -d "$wdir"/.mvn ] ; then + basedir=$wdir + break + fi + # workaround for JBEAP-8937 (on Solaris 10/Sparc) + if [ -d "${wdir}" ]; then + wdir=$(cd "$wdir/.." || exit 1; pwd) + fi + # end of workaround + done + printf '%s' "$(cd "$basedir" || exit 1; pwd)" +} + +# concatenates all lines of a file +concat_lines() { + if [ -f "$1" ]; then + # Remove \r in case we run on Windows within Git Bash + # and check out the repository with auto CRLF management + # enabled. Otherwise, we may read lines that are delimited with + # \r\n and produce $'-Xarg\r' rather than -Xarg due to word + # splitting rules. + tr -s '\r\n' ' ' < "$1" + fi +} + +log() { + if [ "$MVNW_VERBOSE" = true ]; then + printf '%s\n' "$1" + fi +} + +BASE_DIR=$(find_maven_basedir "$(dirname "$0")") +if [ -z "$BASE_DIR" ]; then + exit 1; +fi + +MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}; export MAVEN_PROJECTBASEDIR +log "$MAVEN_PROJECTBASEDIR" + +########################################################################################## +# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +# This allows using the maven wrapper in projects that prohibit checking in binary data. +########################################################################################## +wrapperJarPath="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" +if [ -r "$wrapperJarPath" ]; then + log "Found $wrapperJarPath" +else + log "Couldn't find $wrapperJarPath, downloading it ..." + + if [ -n "$MVNW_REPOURL" ]; then + wrapperUrl="$MVNW_REPOURL/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" + else + wrapperUrl="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" + fi + while IFS="=" read -r key value; do + # Remove '\r' from value to allow usage on windows as IFS does not consider '\r' as a separator ( considers space, tab, new line ('\n'), and custom '=' ) + safeValue=$(echo "$value" | tr -d '\r') + case "$key" in (wrapperUrl) wrapperUrl="$safeValue"; break ;; + esac + done < "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties" + log "Downloading from: $wrapperUrl" + + if $cygwin; then + wrapperJarPath=$(cygpath --path --windows "$wrapperJarPath") + fi + + if command -v wget > /dev/null; then + log "Found wget ... using wget" + [ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--quiet" + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + wget $QUIET "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath" + else + wget $QUIET --http-user="$MVNW_USERNAME" --http-password="$MVNW_PASSWORD" "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath" + fi + elif command -v curl > /dev/null; then + log "Found curl ... using curl" + [ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--silent" + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + curl $QUIET -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath" + else + curl $QUIET --user "$MVNW_USERNAME:$MVNW_PASSWORD" -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath" + fi + else + log "Falling back to using Java to download" + javaSource="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.java" + javaClass="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.class" + # For Cygwin, switch paths to Windows format before running javac + if $cygwin; then + javaSource=$(cygpath --path --windows "$javaSource") + javaClass=$(cygpath --path --windows "$javaClass") + fi + if [ -e "$javaSource" ]; then + if [ ! -e "$javaClass" ]; then + log " - Compiling MavenWrapperDownloader.java ..." + ("$JAVA_HOME/bin/javac" "$javaSource") + fi + if [ -e "$javaClass" ]; then + log " - Running MavenWrapperDownloader.java ..." + ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$wrapperUrl" "$wrapperJarPath") || rm -f "$wrapperJarPath" + fi + fi + fi +fi +########################################################################################## +# End of extension +########################################################################################## + +# If specified, validate the SHA-256 sum of the Maven wrapper jar file +wrapperSha256Sum="" +while IFS="=" read -r key value; do + case "$key" in (wrapperSha256Sum) wrapperSha256Sum=$value; break ;; + esac +done < "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties" +if [ -n "$wrapperSha256Sum" ]; then + wrapperSha256Result=false + if command -v sha256sum > /dev/null; then + if echo "$wrapperSha256Sum $wrapperJarPath" | sha256sum -c > /dev/null 2>&1; then + wrapperSha256Result=true + fi + elif command -v shasum > /dev/null; then + if echo "$wrapperSha256Sum $wrapperJarPath" | shasum -a 256 -c > /dev/null 2>&1; then + wrapperSha256Result=true + fi + else + echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available." + echo "Please install either command, or disable validation by removing 'wrapperSha256Sum' from your maven-wrapper.properties." + exit 1 + fi + if [ $wrapperSha256Result = false ]; then + echo "Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised." >&2 + echo "Investigate or delete $wrapperJarPath to attempt a clean download." >&2 + echo "If you updated your Maven version, you need to update the specified wrapperSha256Sum property." >&2 + exit 1 + fi +fi + +MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" + +# For Cygwin, switch paths to Windows format before running java +if $cygwin; then + [ -n "$JAVA_HOME" ] && + JAVA_HOME=$(cygpath --path --windows "$JAVA_HOME") + [ -n "$CLASSPATH" ] && + CLASSPATH=$(cygpath --path --windows "$CLASSPATH") + [ -n "$MAVEN_PROJECTBASEDIR" ] && + MAVEN_PROJECTBASEDIR=$(cygpath --path --windows "$MAVEN_PROJECTBASEDIR") +fi + +# Provide a "standardized" way to retrieve the CLI args that will +# work with both Windows and non-Windows executions. +MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $*" +export MAVEN_CMD_LINE_ARGS + +WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +# shellcheck disable=SC2086 # safe args +exec "$JAVACMD" \ + $MAVEN_OPTS \ + $MAVEN_DEBUG_OPTS \ + -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ + "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ + ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" diff --git a/java-mvn/mvnw.cmd b/java-mvn/mvnw.cmd new file mode 100644 index 00000000..f80fbad3 --- /dev/null +++ b/java-mvn/mvnw.cmd @@ -0,0 +1,205 @@ +@REM ---------------------------------------------------------------------------- +@REM Licensed to the Apache Software Foundation (ASF) under one +@REM or more contributor license agreements. See the NOTICE file +@REM distributed with this work for additional information +@REM regarding copyright ownership. The ASF licenses this file +@REM to you under the Apache License, Version 2.0 (the +@REM "License"); you may not use this file except in compliance +@REM with the License. You may obtain a copy of the License at +@REM +@REM http://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, +@REM software distributed under the License is distributed on an +@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +@REM KIND, either express or implied. See the License for the +@REM specific language governing permissions and limitations +@REM under the License. +@REM ---------------------------------------------------------------------------- + +@REM ---------------------------------------------------------------------------- +@REM Apache Maven Wrapper startup batch script, version 3.2.0 +@REM +@REM Required ENV vars: +@REM JAVA_HOME - location of a JDK home dir +@REM +@REM Optional ENV vars +@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands +@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending +@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven +@REM e.g. to debug Maven itself, use +@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files +@REM ---------------------------------------------------------------------------- + +@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' +@echo off +@REM set title of command window +title %0 +@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on' +@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% + +@REM set %HOME% to equivalent of $HOME +if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") + +@REM Execute a user defined script before this one +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre +@REM check for pre script, once with legacy .bat ending and once with .cmd ending +if exist "%USERPROFILE%\mavenrc_pre.bat" call "%USERPROFILE%\mavenrc_pre.bat" %* +if exist "%USERPROFILE%\mavenrc_pre.cmd" call "%USERPROFILE%\mavenrc_pre.cmd" %* +:skipRcPre + +@setlocal + +set ERROR_CODE=0 + +@REM To isolate internal variables from possible post scripts, we use another setlocal +@setlocal + +@REM ==== START VALIDATION ==== +if not "%JAVA_HOME%" == "" goto OkJHome + +echo. +echo Error: JAVA_HOME not found in your environment. >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +:OkJHome +if exist "%JAVA_HOME%\bin\java.exe" goto init + +echo. +echo Error: JAVA_HOME is set to an invalid directory. >&2 +echo JAVA_HOME = "%JAVA_HOME%" >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +@REM ==== END VALIDATION ==== + +:init + +@REM Find the project base dir, i.e. the directory that contains the folder ".mvn". +@REM Fallback to current working directory if not found. + +set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% +IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir + +set EXEC_DIR=%CD% +set WDIR=%EXEC_DIR% +:findBaseDir +IF EXIST "%WDIR%"\.mvn goto baseDirFound +cd .. +IF "%WDIR%"=="%CD%" goto baseDirNotFound +set WDIR=%CD% +goto findBaseDir + +:baseDirFound +set MAVEN_PROJECTBASEDIR=%WDIR% +cd "%EXEC_DIR%" +goto endDetectBaseDir + +:baseDirNotFound +set MAVEN_PROJECTBASEDIR=%EXEC_DIR% +cd "%EXEC_DIR%" + +:endDetectBaseDir + +IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig + +@setlocal EnableExtensions EnableDelayedExpansion +for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a +@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% + +:endReadAdditionalConfig + +SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" +set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" +set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +set WRAPPER_URL="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" + +FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( + IF "%%A"=="wrapperUrl" SET WRAPPER_URL=%%B +) + +@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +@REM This allows using the maven wrapper in projects that prohibit checking in binary data. +if exist %WRAPPER_JAR% ( + if "%MVNW_VERBOSE%" == "true" ( + echo Found %WRAPPER_JAR% + ) +) else ( + if not "%MVNW_REPOURL%" == "" ( + SET WRAPPER_URL="%MVNW_REPOURL%/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" + ) + if "%MVNW_VERBOSE%" == "true" ( + echo Couldn't find %WRAPPER_JAR%, downloading it ... + echo Downloading from: %WRAPPER_URL% + ) + + powershell -Command "&{"^ + "$webclient = new-object System.Net.WebClient;"^ + "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ + "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ + "}"^ + "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%WRAPPER_URL%', '%WRAPPER_JAR%')"^ + "}" + if "%MVNW_VERBOSE%" == "true" ( + echo Finished downloading %WRAPPER_JAR% + ) +) +@REM End of extension + +@REM If specified, validate the SHA-256 sum of the Maven wrapper jar file +SET WRAPPER_SHA_256_SUM="" +FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( + IF "%%A"=="wrapperSha256Sum" SET WRAPPER_SHA_256_SUM=%%B +) +IF NOT %WRAPPER_SHA_256_SUM%=="" ( + powershell -Command "&{"^ + "$hash = (Get-FileHash \"%WRAPPER_JAR%\" -Algorithm SHA256).Hash.ToLower();"^ + "If('%WRAPPER_SHA_256_SUM%' -ne $hash){"^ + " Write-Output 'Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised.';"^ + " Write-Output 'Investigate or delete %WRAPPER_JAR% to attempt a clean download.';"^ + " Write-Output 'If you updated your Maven version, you need to update the specified wrapperSha256Sum property.';"^ + " exit 1;"^ + "}"^ + "}" + if ERRORLEVEL 1 goto error +) + +@REM Provide a "standardized" way to retrieve the CLI args that will +@REM work with both Windows and non-Windows executions. +set MAVEN_CMD_LINE_ARGS=%* + +%MAVEN_JAVA_EXE% ^ + %JVM_CONFIG_MAVEN_PROPS% ^ + %MAVEN_OPTS% ^ + %MAVEN_DEBUG_OPTS% ^ + -classpath %WRAPPER_JAR% ^ + "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" ^ + %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* +if ERRORLEVEL 1 goto error +goto end + +:error +set ERROR_CODE=1 + +:end +@endlocal & set ERROR_CODE=%ERROR_CODE% + +if not "%MAVEN_SKIP_RC%"=="" goto skipRcPost +@REM check for post script, once with legacy .bat ending and once with .cmd ending +if exist "%USERPROFILE%\mavenrc_post.bat" call "%USERPROFILE%\mavenrc_post.bat" +if exist "%USERPROFILE%\mavenrc_post.cmd" call "%USERPROFILE%\mavenrc_post.cmd" +:skipRcPost + +@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' +if "%MAVEN_BATCH_PAUSE%"=="on" pause + +if "%MAVEN_TERMINATE_CMD%"=="on" exit %ERROR_CODE% + +cmd /C exit /B %ERROR_CODE% diff --git a/java-mvn/pom.xml b/java-mvn/pom.xml index 6cbdcd97..7809ee2f 100644 --- a/java-mvn/pom.xml +++ b/java-mvn/pom.xml @@ -1,26 +1,117 @@ - 4.0.0 - - com.rabbitmq - rabbitmq-tutorial - 1.0-SNAPSHOT - jar - - rabbitmq-tutorial - https://github.com/rabbitmq/rabbitmq-tutorials - - - UTF-8 - 1.8 - 1.8 - - - - - com.rabbitmq - amqp-client - LATEST - - + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> + 4.0.0 + + com.rabbitmq + rabbitmq-tutorial + 1.0-SNAPSHOT + jar + + rabbitmq-tutorial + https://github.com/rabbitmq/rabbitmq-tutorials + + + UTF-8 + 11 + 11 + + + + + com.rabbitmq + amqp-client + 5.30.0 + + + + org.slf4j + slf4j-api + 2.0.17 + + + + org.slf4j + slf4j-simple + 2.0.17 + + + + org.junit.jupiter + junit-jupiter + 5.14.4 + test + + + + io.dropwizard.metrics + metrics-core + 4.2.38 + test + + + + org.assertj + assertj-core + 3.27.7 + test + + + + org.mockito + mockito-core + 5.23.0 + test + + + + + + + + maven-compiler-plugin + 3.15.0 + + 11 + + + + maven-surefire-plugin + 3.5.5 + + ${test-arguments} + + + + org.apache.maven.plugins + maven-clean-plugin + 3.5.0 + + + + org.apache.maven.plugins + maven-resources-plugin + 3.5.0 + + ${project.build.sourceEncoding} + + + + + + + + + + jvm-test-arguments-java-21-and-more + + [21,) + + + -XX:+EnableDynamicAgentLoading + + + + + + diff --git a/java-mvn/pull-source-files.bat b/java-mvn/pull-source-files.bat new file mode 100644 index 00000000..f414b47a --- /dev/null +++ b/java-mvn/pull-source-files.bat @@ -0,0 +1 @@ +copy ..\java\*.java src\main\java\ \ No newline at end of file diff --git a/java-mvn/src/main/java/PublisherConfirms.java b/java-mvn/src/main/java/PublisherConfirms.java new file mode 120000 index 00000000..543c9ceb --- /dev/null +++ b/java-mvn/src/main/java/PublisherConfirms.java @@ -0,0 +1 @@ +../../../../java/PublisherConfirms.java \ No newline at end of file diff --git a/java-mvn/src/test/java/PublisherConfirmsTest.java b/java-mvn/src/test/java/PublisherConfirmsTest.java new file mode 100644 index 00000000..e92c3eea --- /dev/null +++ b/java-mvn/src/test/java/PublisherConfirmsTest.java @@ -0,0 +1,404 @@ +import com.codahale.metrics.Meter; +import com.codahale.metrics.MetricRegistry; +import com.rabbitmq.client.Channel; +import com.rabbitmq.client.ConfirmCallback; +import com.rabbitmq.client.Connection; +import com.rabbitmq.client.ConnectionFactory; +import org.junit.jupiter.api.*; + +import java.io.IOException; +import java.time.Duration; +import java.util.*; +import java.util.concurrent.*; +import java.util.concurrent.atomic.AtomicLong; +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.BooleanSupplier; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.*; + +/** + * Tests to illustrate different ways to handle publisher confirms. + */ +public class PublisherConfirmsTest { + + private final MetricRegistry metrics = new MetricRegistry(); + private final Meter meter = metrics.meter("outbound-messages"); + Connection connection; + String queue; + int messageCount = 10_000; + + static boolean waitUntil(Duration timeout, BooleanSupplier condition) throws InterruptedException { + int waited = 0; + while (!condition.getAsBoolean() && waited < timeout.toMillis()) { + Thread.sleep(100L); + waited = +100; + } + return condition.getAsBoolean(); + } + + @BeforeEach + void init() throws Exception { + ConnectionFactory cf = new ConnectionFactory(); + cf.setHost("localhost"); + cf.setUsername("guest"); + cf.setPassword("guest"); + connection = cf.newConnection(); + queue = UUID.randomUUID().toString(); + try (Channel ch = connection.createChannel()) { + ch.queueDeclare(queue, false, false, true, null); + } + } + + @AfterEach + void tearDown(TestInfo testInfo) throws Exception { + System.out.println(String.format("%s: %.0f messages/second", testInfo.getDisplayName(), meter.getMeanRate())); + connection.close(10_000); + } + + @Test + @DisplayName("publish messages individually") + void publishMessagesIndividually() throws Exception { + Channel ch = connection.createChannel(); + ch.confirmSelect(); + for (int i = 0; i < messageCount; i++) { + String body = String.valueOf(i); + ch.basicPublish("", queue, null, body.getBytes()); + ch.waitForConfirmsOrDie(5_000); + meter.mark(); + } + ch.close(); + + CountDownLatch latch = new CountDownLatch(messageCount); + ch = connection.createChannel(); + ch.basicConsume(queue, true, ((consumerTag, message) -> latch.countDown()), consumerTag -> { + }); + + assertThat(latch.await(60, TimeUnit.SECONDS)).isTrue(); + } + + @Test + @DisplayName("publish messages in batch") + void publishMessagesInBatch() throws Exception { + Channel ch = connection.createChannel(); + ch.confirmSelect(); + int batchSize = 100; + int outstandingMessageCount = 0; + for (int i = 0; i < messageCount; i++) { + String body = String.valueOf(i); + ch.basicPublish("", queue, null, body.getBytes()); + outstandingMessageCount++; + + if (outstandingMessageCount == batchSize) { + ch.waitForConfirmsOrDie(5_000); + outstandingMessageCount = 0; + } + meter.mark(); + } + + if (outstandingMessageCount > 0) { + ch.waitForConfirmsOrDie(5_000); + } + + ch.close(); + + CountDownLatch latch = new CountDownLatch(messageCount); + ch = connection.createChannel(); + ch.basicConsume(queue, true, ((consumerTag, message) -> latch.countDown()), consumerTag -> { + }); + + assertThat(latch.await(60, TimeUnit.SECONDS)).isTrue(); + } + + @Test + @DisplayName("allow max of outstanding confirms") + void allowMaxOfOutstandingConfirms() throws Exception { + int maxOutstandingConfirms = 100; + Semaphore semaphore = new Semaphore(maxOutstandingConfirms); + Channel ch = connection.createChannel(); + ch.confirmSelect(); + ConcurrentSkipListSet outstandingConfirms = new ConcurrentSkipListSet<>(); + ConfirmCallback handleConfirm = (deliveryTag, multiple) -> { + if (multiple) { + NavigableSet confirmed = outstandingConfirms.headSet(deliveryTag, true); + int confirmedSize = confirmed.size(); + confirmed.clear(); + semaphore.release(confirmedSize); + } else { + outstandingConfirms.remove(deliveryTag); + semaphore.release(); + } + }; + ch.addConfirmListener(handleConfirm, handleConfirm); + for (int i = 0; i < messageCount; i++) { + String body = String.valueOf(i); + boolean acquired = semaphore.tryAcquire(10, TimeUnit.SECONDS); + if (!acquired) { + throw new IllegalStateException("Could not publish because of too many outstanding publisher confirms"); + } + outstandingConfirms.add(ch.getNextPublishSeqNo()); + ch.basicPublish("", queue, null, body.getBytes()); + meter.mark(); + } + assertThat(waitUntil(Duration.ofSeconds(5), () -> outstandingConfirms.isEmpty())).isTrue(); + ch.close(); + + CountDownLatch latch = new CountDownLatch(messageCount); + ch = connection.createChannel(); + ch.basicConsume(queue, true, ((consumerTag, message) -> latch.countDown()), consumerTag -> { + }); + + assertThat(latch.await(60, TimeUnit.SECONDS)).isTrue(); + } + + @Test + @DisplayName("resend unconfirmed messages") + void resendUnconfirmedMessagesIntegration() throws Exception { + Channel ch = connection.createChannel(); + ch.confirmSelect(); + Map outstandingConfirms = resendUnconfirmedMessages(ch); + assertThat(waitUntil(Duration.ofSeconds(5), () -> outstandingConfirms.isEmpty())).isTrue(); + ch.close(); + + Set receivedMessages = ConcurrentHashMap.newKeySet(messageCount); + CountDownLatch latch = new CountDownLatch(messageCount); + ch = connection.createChannel(); + ch.basicConsume(queue, true, ((consumerTag, message) -> { + receivedMessages.add(new String(message.getBody())); + latch.countDown(); + }), consumerTag -> { + }); + + assertThat(latch.await(60, TimeUnit.SECONDS)).isTrue(); + assertThat(receivedMessages).hasSize(messageCount); + } + + @Test + @DisplayName("resend unconfirmed messages (mock)") + void resendUnconfirmedMessagesMock() throws Exception { + Channel ch = mock(Channel.class); + + Set receivedMessages = configureMockServer(ch); + + resendUnconfirmedMessages(ch); + + assertThat(receivedMessages).hasSize(messageCount); + } + + @Test + @DisplayName("resend unconfirmed messages with lower bound and map confirm handling") + void resendUnconfirmedMessagesUseLowerBoundAndConcurrentMapIntegration() throws Exception { + Channel ch = connection.createChannel(); + ch.confirmSelect(); + Map outstandingConfirms = resendUnconfirmedMessagesUseLowerBoundAndConcurrentMap(ch); + assertThat(waitUntil(Duration.ofSeconds(5), () -> outstandingConfirms.isEmpty())).isTrue(); + ch.close(); + + Set receivedMessages = ConcurrentHashMap.newKeySet(messageCount); + CountDownLatch latch = new CountDownLatch(messageCount); + ch = connection.createChannel(); + ch.basicConsume(queue, true, ((consumerTag, message) -> { + receivedMessages.add(new String(message.getBody())); + latch.countDown(); + }), consumerTag -> { + }); + + assertThat(latch.await(60, TimeUnit.SECONDS)).isTrue(); + assertThat(receivedMessages).hasSize(messageCount); + } + + @Test + @DisplayName("resend unconfirmed messages with lower bound and map confirm handling (mock)") + void resendUnconfirmedMessagesUseLowerBoundAndConcurrentMapMock() throws Exception { + Channel ch = mock(Channel.class); + + Set receivedMessages = configureMockServer(ch); + + resendUnconfirmedMessagesUseLowerBoundAndConcurrentMap(ch); + + assertThat(receivedMessages).hasSize(messageCount); + } + + /** + * Configure a mock channel that will publish to a server-like threads. + * The fake server will send publisher confirms notification (ack, nack, multiple + * or not) randomly. + * Allows to test publisher confirms handling in a reliable way. + */ + private Set configureMockServer(Channel ch) throws IOException { + AtomicLong clientPublishingSequence = new AtomicLong(0); + when(ch.getNextPublishSeqNo()).thenAnswer(invocationOnMock -> clientPublishingSequence.getAndIncrement()); + BlockingQueue messagesSentToServer = new LinkedBlockingQueue<>(); + doAnswer(invocation -> { + messagesSentToServer.offer(new String(invocation.getArgument(3, byte[].class))); + return null; + }).when(ch).basicPublish(anyString(), anyString(), isNull(), any(byte[].class)); + AtomicReference handleAckReference = new AtomicReference<>(); + AtomicReference handleNackReference = new AtomicReference<>(); + doAnswer(invocation -> { + handleAckReference.set(invocation.getArgument(0, ConfirmCallback.class)); + handleNackReference.set(invocation.getArgument(1, ConfirmCallback.class)); + return null; + }).when(ch).addConfirmListener(any(ConfirmCallback.class), any(ConfirmCallback.class)); + + Set receivedMessages = ConcurrentHashMap.newKeySet(messageCount); + new Thread(() -> { + AtomicLong serverPublishingSequence = new AtomicLong(0); + Random random = new Random(); + int outstandingMultiple = 0; + while (true) { + try { + String body = messagesSentToServer.poll(10, TimeUnit.SECONDS); + receivedMessages.add(body); + long messageSequenceNumber = serverPublishingSequence.getAndIncrement(); + if (outstandingMultiple == 0) { + // no outstanding multiple + boolean multiple = random.nextBoolean(); + if (multiple) { + outstandingMultiple = random.nextInt(10) + 1; + } else { + if (random.nextInt(100) < 5) { + handleNackReference.get().handle(messageSequenceNumber, false); + } else { + handleAckReference.get().handle(messageSequenceNumber, false); + } + } + } else if (outstandingMultiple == 1) { + // last outstanding acking/nacking + if (random.nextInt(100) < 5) { + handleNackReference.get().handle(messageSequenceNumber, true); + } else { + handleAckReference.get().handle(messageSequenceNumber, true); + } + outstandingMultiple = 0; + } else { + outstandingMultiple--; + } + } catch (Exception e) { + e.printStackTrace(); + } + } + }).start(); + return receivedMessages; + } + + Map resendUnconfirmedMessages(Channel ch) throws Exception { + ConcurrentNavigableMap outstandingConfirms = new ConcurrentSkipListMap<>(); + ch.confirmSelect(); + + ConfirmCallback handleAck = (deliveryTag, multiple) -> { + if (multiple) { + ConcurrentNavigableMap confirmed = outstandingConfirms.headMap(deliveryTag, true); + confirmed.clear(); + } else { + outstandingConfirms.remove(deliveryTag); + } + }; + + Queue nackedMessages = new ConcurrentLinkedQueue<>(); + ConfirmCallback handleNack = (deliveryTag, multiple) -> { + if (multiple) { + ConcurrentNavigableMap nacked = outstandingConfirms.headMap(deliveryTag, true); + for (String body : nacked.values()) { + nackedMessages.offer(body); + } + nacked.clear(); + } else { + nackedMessages.add(outstandingConfirms.get(deliveryTag)); + outstandingConfirms.remove(deliveryTag); + } + }; + + ch.addConfirmListener(handleAck, handleNack); + + for (int i = 0; i < messageCount; i++) { + String body = String.valueOf(i); + outstandingConfirms.put(ch.getNextPublishSeqNo(), body); + ch.basicPublish("", queue, null, body.getBytes()); + meter.mark(); + if (!nackedMessages.isEmpty()) { + while ((body = nackedMessages.poll()) != null) { + outstandingConfirms.put(ch.getNextPublishSeqNo(), body); + ch.basicPublish("", queue, null, body.getBytes()); + } + } + } + while (!outstandingConfirms.isEmpty() && !nackedMessages.isEmpty()) { + String body; + while ((body = nackedMessages.poll()) != null) { + outstandingConfirms.put(ch.getNextPublishSeqNo(), body); + ch.basicPublish("", queue, null, body.getBytes()); + } + } + return outstandingConfirms; + } + + Map resendUnconfirmedMessagesUseLowerBoundAndConcurrentMap(Channel ch) throws Exception { + Map outstandingConfirms = new ConcurrentHashMap<>(); + AtomicLong multipleLowerBound = new AtomicLong(1); + ch.confirmSelect(); + + ConfirmCallback handleAck = (deliveryTag, multiple) -> { + Long lowerBound = multipleLowerBound.get(); + if (multiple) { + for (long i = lowerBound; i <= deliveryTag; i++) { + outstandingConfirms.remove(i); + } + multipleLowerBound.compareAndSet(lowerBound, deliveryTag); + } else { + outstandingConfirms.remove(deliveryTag); + if (deliveryTag == lowerBound + 1) { + multipleLowerBound.compareAndSet(lowerBound, deliveryTag); + } + } + }; + + Queue nackedMessages = new ConcurrentLinkedQueue<>(); + ConfirmCallback handleNack = (deliveryTag, multiple) -> { + Long lowerBound = multipleLowerBound.get(); + if (multiple) { + for (long i = lowerBound; i <= deliveryTag; i++) { + String body = outstandingConfirms.remove(i); + if (body != null) { + nackedMessages.offer(body); + } + } + } else { + String body = outstandingConfirms.remove(deliveryTag); + if (body != null) { + nackedMessages.offer(body); + } + if (deliveryTag == lowerBound + 1) { + multipleLowerBound.compareAndSet(lowerBound, deliveryTag); + } + } + }; + + ch.addConfirmListener(handleAck, handleNack); + + for (int i = 0; i < messageCount; i++) { + String body = String.valueOf(i); + outstandingConfirms.put(ch.getNextPublishSeqNo(), body); + ch.basicPublish("", queue, null, body.getBytes()); + meter.mark(); + if (!nackedMessages.isEmpty()) { + while ((body = nackedMessages.poll()) != null) { + outstandingConfirms.put(ch.getNextPublishSeqNo(), body); + ch.basicPublish("", queue, null, body.getBytes()); + } + } + } + while (!outstandingConfirms.isEmpty() && !nackedMessages.isEmpty()) { + String body; + while ((body = nackedMessages.poll()) != null) { + outstandingConfirms.put(ch.getNextPublishSeqNo(), body); + ch.basicPublish("", queue, null, body.getBytes()); + } + } + return outstandingConfirms; + } + + +} diff --git a/java-stream-mvn/.gitignore b/java-stream-mvn/.gitignore new file mode 100644 index 00000000..454d7ea5 --- /dev/null +++ b/java-stream-mvn/.gitignore @@ -0,0 +1,40 @@ +target/ +rmq-conf/ + +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/**/target/ +!**/src/test/**/target/ + +### IntelliJ IDEA ### +.idea/modules.xml +.idea/jarRepositories.xml +.idea/compiler.xml +.idea/libraries/ +*.iws +*.iml +*.ipr + +### Eclipse ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ +!**/src/main/**/build/ +!**/src/test/**/build/ + +### VS Code ### +.vscode/ + +### Mac OS ### +.DS_Store diff --git a/java-stream-mvn/.mvn/wrapper/maven-wrapper.jar b/java-stream-mvn/.mvn/wrapper/maven-wrapper.jar new file mode 100644 index 00000000..cb28b0e3 Binary files /dev/null and b/java-stream-mvn/.mvn/wrapper/maven-wrapper.jar differ diff --git a/java-stream-mvn/.mvn/wrapper/maven-wrapper.properties b/java-stream-mvn/.mvn/wrapper/maven-wrapper.properties new file mode 100644 index 00000000..10cbcb5e --- /dev/null +++ b/java-stream-mvn/.mvn/wrapper/maven-wrapper.properties @@ -0,0 +1,18 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.8/apache-maven-3.9.8-bin.zip +wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar diff --git a/java-stream-mvn/README.md b/java-stream-mvn/README.md new file mode 100644 index 00000000..b609efcb --- /dev/null +++ b/java-stream-mvn/README.md @@ -0,0 +1,26 @@ +# Java code for RabbitMQ tutorials + +Here you can find Java code examples from [RabbitMQ tutorials](https://www.rabbitmq.com/getstarted.html). + +To successfully use the examples you will need a running RabbitMQ server with the [stream plugin enabled](https://www.rabbitmq.com/docs/stream#enabling-plugin). + +See [First Application With RabbitMQ Streams](https://www.rabbitmq.com/blog/2021/07/19/rabbitmq-streams-first-application), [Stream plugin documentation](https://www.rabbitmq.com/docs/stream) and [how to preconfigure plugins](https://www.rabbitmq.com/docs/plugins#enabled-plugins-file). + +## Requirements + +These examples use the [`rabbitmq-stream-java-client`](https://github.com/rabbitmq/rabbitmq-stream-java-client) client library. +This example uses Maven to manage dependencies. + +## Code + +Code examples are executed via the Maven wrapper `./mvnw`. +Remove the `-q` flag to get more information in case an example does not behave as expected. + +[Tutorial one: "Hello World!"](https://www.rabbitmq.com/tutorials/tutorial-one-java-stream.html): + +``` +./mvnw -q compile exec:java '-Dexec.mainClass=Send' +./mvnw -q compile exec:java '-Dexec.mainClass=Receive' +``` + +To learn more, see [`rabbitmq/rabbitmq-stream-java-client`](https://github.com/rabbitmq/rabbitmq-stream-java-client). diff --git a/java-stream-mvn/mvnw b/java-stream-mvn/mvnw new file mode 100755 index 00000000..8d937f4c --- /dev/null +++ b/java-stream-mvn/mvnw @@ -0,0 +1,308 @@ +#!/bin/sh +# ---------------------------------------------------------------------------- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# ---------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------- +# Apache Maven Wrapper startup batch script, version 3.2.0 +# +# Required ENV vars: +# ------------------ +# JAVA_HOME - location of a JDK home dir +# +# Optional ENV vars +# ----------------- +# MAVEN_OPTS - parameters passed to the Java VM when running Maven +# e.g. to debug Maven itself, use +# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +# MAVEN_SKIP_RC - flag to disable loading of mavenrc files +# ---------------------------------------------------------------------------- + +if [ -z "$MAVEN_SKIP_RC" ] ; then + + if [ -f /usr/local/etc/mavenrc ] ; then + . /usr/local/etc/mavenrc + fi + + if [ -f /etc/mavenrc ] ; then + . /etc/mavenrc + fi + + if [ -f "$HOME/.mavenrc" ] ; then + . "$HOME/.mavenrc" + fi + +fi + +# OS specific support. $var _must_ be set to either true or false. +cygwin=false; +darwin=false; +mingw=false +case "$(uname)" in + CYGWIN*) cygwin=true ;; + MINGW*) mingw=true;; + Darwin*) darwin=true + # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home + # See https://developer.apple.com/library/mac/qa/qa1170/_index.html + if [ -z "$JAVA_HOME" ]; then + if [ -x "/usr/libexec/java_home" ]; then + JAVA_HOME="$(/usr/libexec/java_home)"; export JAVA_HOME + else + JAVA_HOME="/Library/Java/Home"; export JAVA_HOME + fi + fi + ;; +esac + +if [ -z "$JAVA_HOME" ] ; then + if [ -r /etc/gentoo-release ] ; then + JAVA_HOME=$(java-config --jre-home) + fi +fi + +# For Cygwin, ensure paths are in UNIX format before anything is touched +if $cygwin ; then + [ -n "$JAVA_HOME" ] && + JAVA_HOME=$(cygpath --unix "$JAVA_HOME") + [ -n "$CLASSPATH" ] && + CLASSPATH=$(cygpath --path --unix "$CLASSPATH") +fi + +# For Mingw, ensure paths are in UNIX format before anything is touched +if $mingw ; then + [ -n "$JAVA_HOME" ] && [ -d "$JAVA_HOME" ] && + JAVA_HOME="$(cd "$JAVA_HOME" || (echo "cannot cd into $JAVA_HOME."; exit 1); pwd)" +fi + +if [ -z "$JAVA_HOME" ]; then + javaExecutable="$(which javac)" + if [ -n "$javaExecutable" ] && ! [ "$(expr "\"$javaExecutable\"" : '\([^ ]*\)')" = "no" ]; then + # readlink(1) is not available as standard on Solaris 10. + readLink=$(which readlink) + if [ ! "$(expr "$readLink" : '\([^ ]*\)')" = "no" ]; then + if $darwin ; then + javaHome="$(dirname "\"$javaExecutable\"")" + javaExecutable="$(cd "\"$javaHome\"" && pwd -P)/javac" + else + javaExecutable="$(readlink -f "\"$javaExecutable\"")" + fi + javaHome="$(dirname "\"$javaExecutable\"")" + javaHome=$(expr "$javaHome" : '\(.*\)/bin') + JAVA_HOME="$javaHome" + export JAVA_HOME + fi + fi +fi + +if [ -z "$JAVACMD" ] ; then + if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + else + JAVACMD="$(\unset -f command 2>/dev/null; \command -v java)" + fi +fi + +if [ ! -x "$JAVACMD" ] ; then + echo "Error: JAVA_HOME is not defined correctly." >&2 + echo " We cannot execute $JAVACMD" >&2 + exit 1 +fi + +if [ -z "$JAVA_HOME" ] ; then + echo "Warning: JAVA_HOME environment variable is not set." +fi + +# traverses directory structure from process work directory to filesystem root +# first directory with .mvn subdirectory is considered project base directory +find_maven_basedir() { + if [ -z "$1" ] + then + echo "Path not specified to find_maven_basedir" + return 1 + fi + + basedir="$1" + wdir="$1" + while [ "$wdir" != '/' ] ; do + if [ -d "$wdir"/.mvn ] ; then + basedir=$wdir + break + fi + # workaround for JBEAP-8937 (on Solaris 10/Sparc) + if [ -d "${wdir}" ]; then + wdir=$(cd "$wdir/.." || exit 1; pwd) + fi + # end of workaround + done + printf '%s' "$(cd "$basedir" || exit 1; pwd)" +} + +# concatenates all lines of a file +concat_lines() { + if [ -f "$1" ]; then + # Remove \r in case we run on Windows within Git Bash + # and check out the repository with auto CRLF management + # enabled. Otherwise, we may read lines that are delimited with + # \r\n and produce $'-Xarg\r' rather than -Xarg due to word + # splitting rules. + tr -s '\r\n' ' ' < "$1" + fi +} + +log() { + if [ "$MVNW_VERBOSE" = true ]; then + printf '%s\n' "$1" + fi +} + +BASE_DIR=$(find_maven_basedir "$(dirname "$0")") +if [ -z "$BASE_DIR" ]; then + exit 1; +fi + +MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}; export MAVEN_PROJECTBASEDIR +log "$MAVEN_PROJECTBASEDIR" + +########################################################################################## +# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +# This allows using the maven wrapper in projects that prohibit checking in binary data. +########################################################################################## +wrapperJarPath="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" +if [ -r "$wrapperJarPath" ]; then + log "Found $wrapperJarPath" +else + log "Couldn't find $wrapperJarPath, downloading it ..." + + if [ -n "$MVNW_REPOURL" ]; then + wrapperUrl="$MVNW_REPOURL/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" + else + wrapperUrl="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" + fi + while IFS="=" read -r key value; do + # Remove '\r' from value to allow usage on windows as IFS does not consider '\r' as a separator ( considers space, tab, new line ('\n'), and custom '=' ) + safeValue=$(echo "$value" | tr -d '\r') + case "$key" in (wrapperUrl) wrapperUrl="$safeValue"; break ;; + esac + done < "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties" + log "Downloading from: $wrapperUrl" + + if $cygwin; then + wrapperJarPath=$(cygpath --path --windows "$wrapperJarPath") + fi + + if command -v wget > /dev/null; then + log "Found wget ... using wget" + [ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--quiet" + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + wget $QUIET "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath" + else + wget $QUIET --http-user="$MVNW_USERNAME" --http-password="$MVNW_PASSWORD" "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath" + fi + elif command -v curl > /dev/null; then + log "Found curl ... using curl" + [ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--silent" + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + curl $QUIET -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath" + else + curl $QUIET --user "$MVNW_USERNAME:$MVNW_PASSWORD" -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath" + fi + else + log "Falling back to using Java to download" + javaSource="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.java" + javaClass="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.class" + # For Cygwin, switch paths to Windows format before running javac + if $cygwin; then + javaSource=$(cygpath --path --windows "$javaSource") + javaClass=$(cygpath --path --windows "$javaClass") + fi + if [ -e "$javaSource" ]; then + if [ ! -e "$javaClass" ]; then + log " - Compiling MavenWrapperDownloader.java ..." + ("$JAVA_HOME/bin/javac" "$javaSource") + fi + if [ -e "$javaClass" ]; then + log " - Running MavenWrapperDownloader.java ..." + ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$wrapperUrl" "$wrapperJarPath") || rm -f "$wrapperJarPath" + fi + fi + fi +fi +########################################################################################## +# End of extension +########################################################################################## + +# If specified, validate the SHA-256 sum of the Maven wrapper jar file +wrapperSha256Sum="" +while IFS="=" read -r key value; do + case "$key" in (wrapperSha256Sum) wrapperSha256Sum=$value; break ;; + esac +done < "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties" +if [ -n "$wrapperSha256Sum" ]; then + wrapperSha256Result=false + if command -v sha256sum > /dev/null; then + if echo "$wrapperSha256Sum $wrapperJarPath" | sha256sum -c > /dev/null 2>&1; then + wrapperSha256Result=true + fi + elif command -v shasum > /dev/null; then + if echo "$wrapperSha256Sum $wrapperJarPath" | shasum -a 256 -c > /dev/null 2>&1; then + wrapperSha256Result=true + fi + else + echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available." + echo "Please install either command, or disable validation by removing 'wrapperSha256Sum' from your maven-wrapper.properties." + exit 1 + fi + if [ $wrapperSha256Result = false ]; then + echo "Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised." >&2 + echo "Investigate or delete $wrapperJarPath to attempt a clean download." >&2 + echo "If you updated your Maven version, you need to update the specified wrapperSha256Sum property." >&2 + exit 1 + fi +fi + +MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" + +# For Cygwin, switch paths to Windows format before running java +if $cygwin; then + [ -n "$JAVA_HOME" ] && + JAVA_HOME=$(cygpath --path --windows "$JAVA_HOME") + [ -n "$CLASSPATH" ] && + CLASSPATH=$(cygpath --path --windows "$CLASSPATH") + [ -n "$MAVEN_PROJECTBASEDIR" ] && + MAVEN_PROJECTBASEDIR=$(cygpath --path --windows "$MAVEN_PROJECTBASEDIR") +fi + +# Provide a "standardized" way to retrieve the CLI args that will +# work with both Windows and non-Windows executions. +MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $*" +export MAVEN_CMD_LINE_ARGS + +WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +# shellcheck disable=SC2086 # safe args +exec "$JAVACMD" \ + $MAVEN_OPTS \ + $MAVEN_DEBUG_OPTS \ + -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ + "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ + ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" diff --git a/java-stream-mvn/mvnw.cmd b/java-stream-mvn/mvnw.cmd new file mode 100644 index 00000000..f80fbad3 --- /dev/null +++ b/java-stream-mvn/mvnw.cmd @@ -0,0 +1,205 @@ +@REM ---------------------------------------------------------------------------- +@REM Licensed to the Apache Software Foundation (ASF) under one +@REM or more contributor license agreements. See the NOTICE file +@REM distributed with this work for additional information +@REM regarding copyright ownership. The ASF licenses this file +@REM to you under the Apache License, Version 2.0 (the +@REM "License"); you may not use this file except in compliance +@REM with the License. You may obtain a copy of the License at +@REM +@REM http://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, +@REM software distributed under the License is distributed on an +@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +@REM KIND, either express or implied. See the License for the +@REM specific language governing permissions and limitations +@REM under the License. +@REM ---------------------------------------------------------------------------- + +@REM ---------------------------------------------------------------------------- +@REM Apache Maven Wrapper startup batch script, version 3.2.0 +@REM +@REM Required ENV vars: +@REM JAVA_HOME - location of a JDK home dir +@REM +@REM Optional ENV vars +@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands +@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending +@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven +@REM e.g. to debug Maven itself, use +@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files +@REM ---------------------------------------------------------------------------- + +@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' +@echo off +@REM set title of command window +title %0 +@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on' +@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% + +@REM set %HOME% to equivalent of $HOME +if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") + +@REM Execute a user defined script before this one +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre +@REM check for pre script, once with legacy .bat ending and once with .cmd ending +if exist "%USERPROFILE%\mavenrc_pre.bat" call "%USERPROFILE%\mavenrc_pre.bat" %* +if exist "%USERPROFILE%\mavenrc_pre.cmd" call "%USERPROFILE%\mavenrc_pre.cmd" %* +:skipRcPre + +@setlocal + +set ERROR_CODE=0 + +@REM To isolate internal variables from possible post scripts, we use another setlocal +@setlocal + +@REM ==== START VALIDATION ==== +if not "%JAVA_HOME%" == "" goto OkJHome + +echo. +echo Error: JAVA_HOME not found in your environment. >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +:OkJHome +if exist "%JAVA_HOME%\bin\java.exe" goto init + +echo. +echo Error: JAVA_HOME is set to an invalid directory. >&2 +echo JAVA_HOME = "%JAVA_HOME%" >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +@REM ==== END VALIDATION ==== + +:init + +@REM Find the project base dir, i.e. the directory that contains the folder ".mvn". +@REM Fallback to current working directory if not found. + +set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% +IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir + +set EXEC_DIR=%CD% +set WDIR=%EXEC_DIR% +:findBaseDir +IF EXIST "%WDIR%"\.mvn goto baseDirFound +cd .. +IF "%WDIR%"=="%CD%" goto baseDirNotFound +set WDIR=%CD% +goto findBaseDir + +:baseDirFound +set MAVEN_PROJECTBASEDIR=%WDIR% +cd "%EXEC_DIR%" +goto endDetectBaseDir + +:baseDirNotFound +set MAVEN_PROJECTBASEDIR=%EXEC_DIR% +cd "%EXEC_DIR%" + +:endDetectBaseDir + +IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig + +@setlocal EnableExtensions EnableDelayedExpansion +for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a +@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% + +:endReadAdditionalConfig + +SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" +set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" +set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +set WRAPPER_URL="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" + +FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( + IF "%%A"=="wrapperUrl" SET WRAPPER_URL=%%B +) + +@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +@REM This allows using the maven wrapper in projects that prohibit checking in binary data. +if exist %WRAPPER_JAR% ( + if "%MVNW_VERBOSE%" == "true" ( + echo Found %WRAPPER_JAR% + ) +) else ( + if not "%MVNW_REPOURL%" == "" ( + SET WRAPPER_URL="%MVNW_REPOURL%/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" + ) + if "%MVNW_VERBOSE%" == "true" ( + echo Couldn't find %WRAPPER_JAR%, downloading it ... + echo Downloading from: %WRAPPER_URL% + ) + + powershell -Command "&{"^ + "$webclient = new-object System.Net.WebClient;"^ + "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ + "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ + "}"^ + "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%WRAPPER_URL%', '%WRAPPER_JAR%')"^ + "}" + if "%MVNW_VERBOSE%" == "true" ( + echo Finished downloading %WRAPPER_JAR% + ) +) +@REM End of extension + +@REM If specified, validate the SHA-256 sum of the Maven wrapper jar file +SET WRAPPER_SHA_256_SUM="" +FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( + IF "%%A"=="wrapperSha256Sum" SET WRAPPER_SHA_256_SUM=%%B +) +IF NOT %WRAPPER_SHA_256_SUM%=="" ( + powershell -Command "&{"^ + "$hash = (Get-FileHash \"%WRAPPER_JAR%\" -Algorithm SHA256).Hash.ToLower();"^ + "If('%WRAPPER_SHA_256_SUM%' -ne $hash){"^ + " Write-Output 'Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised.';"^ + " Write-Output 'Investigate or delete %WRAPPER_JAR% to attempt a clean download.';"^ + " Write-Output 'If you updated your Maven version, you need to update the specified wrapperSha256Sum property.';"^ + " exit 1;"^ + "}"^ + "}" + if ERRORLEVEL 1 goto error +) + +@REM Provide a "standardized" way to retrieve the CLI args that will +@REM work with both Windows and non-Windows executions. +set MAVEN_CMD_LINE_ARGS=%* + +%MAVEN_JAVA_EXE% ^ + %JVM_CONFIG_MAVEN_PROPS% ^ + %MAVEN_OPTS% ^ + %MAVEN_DEBUG_OPTS% ^ + -classpath %WRAPPER_JAR% ^ + "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" ^ + %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* +if ERRORLEVEL 1 goto error +goto end + +:error +set ERROR_CODE=1 + +:end +@endlocal & set ERROR_CODE=%ERROR_CODE% + +if not "%MAVEN_SKIP_RC%"=="" goto skipRcPost +@REM check for post script, once with legacy .bat ending and once with .cmd ending +if exist "%USERPROFILE%\mavenrc_post.bat" call "%USERPROFILE%\mavenrc_post.bat" +if exist "%USERPROFILE%\mavenrc_post.cmd" call "%USERPROFILE%\mavenrc_post.cmd" +:skipRcPost + +@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' +if "%MAVEN_BATCH_PAUSE%"=="on" pause + +if "%MAVEN_TERMINATE_CMD%"=="on" exit %ERROR_CODE% + +cmd /C exit /B %ERROR_CODE% diff --git a/java-stream-mvn/pom.xml b/java-stream-mvn/pom.xml new file mode 100644 index 00000000..69645401 --- /dev/null +++ b/java-stream-mvn/pom.xml @@ -0,0 +1,57 @@ + + + 4.0.0 + + stream + java-stream-mvn + 1.0-SNAPSHOT + + + + com.rabbitmq + stream-client + 1.6.0 + + + org.slf4j + slf4j-api + 2.0.17 + + + ch.qos.logback + logback-classic + 1.5.32 + + + + + + + + maven-compiler-plugin + 3.15.0 + + 11 + 11 + 11 + + + + maven-clean-plugin + 3.5.0 + + + maven-resources-plugin + 3.5.0 + + + + + + + UTF-8 + UTF-8 + + diff --git a/java-stream-mvn/src/main/java/OffsetTrackingReceive.java b/java-stream-mvn/src/main/java/OffsetTrackingReceive.java new file mode 100644 index 00000000..7471cd83 --- /dev/null +++ b/java-stream-mvn/src/main/java/OffsetTrackingReceive.java @@ -0,0 +1,55 @@ +import com.rabbitmq.stream.ByteCapacity; +import com.rabbitmq.stream.Environment; +import com.rabbitmq.stream.OffsetSpecification; + +import java.time.Duration; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicLong; + +import static java.nio.charset.StandardCharsets.UTF_8; + +public class OffsetTrackingReceive { + + public static void main(String[] args) throws InterruptedException { + try (Environment environment = + Environment.builder().requestedHeartbeat(Duration.ofSeconds(5)).build()) { + String stream = "stream-offset-tracking-java"; + environment.streamCreator().stream(stream).maxLengthBytes(ByteCapacity.GB(1)).create(); + + OffsetSpecification offsetSpecification = OffsetSpecification.first(); + AtomicLong firstOffset = new AtomicLong(-1); + AtomicLong lastOffset = new AtomicLong(0); + AtomicLong messageCount = new AtomicLong(0); + CountDownLatch consumedLatch = new CountDownLatch(1); + environment.consumerBuilder().stream(stream) + .offset(offsetSpecification) + .name("offset-tracking-tutorial") + .manualTrackingStrategy().builder() + .messageHandler( + (ctx, msg) -> { + if (firstOffset.compareAndSet(-1, ctx.offset())) { + System.out.println("First message received."); + } + if (messageCount.incrementAndGet() % 10 == 0) { + ctx.storeOffset(); + } + String body = new String(msg.getBodyAsBinary(), UTF_8); + if ("marker".equals(body)) { + lastOffset.set(ctx.offset()); + ctx.storeOffset(); + ctx.consumer().close(); + consumedLatch.countDown(); + } + }) + .build(); + System.out.println("Started consuming..."); + + consumedLatch.await(60, TimeUnit.MINUTES); + + System.out.printf("Done consuming, first offset %d, last offset %d.%n", + firstOffset.get(), lastOffset.get()); + } + } + +} diff --git a/java-stream-mvn/src/main/java/OffsetTrackingSend.java b/java-stream-mvn/src/main/java/OffsetTrackingSend.java new file mode 100644 index 00000000..11ec162e --- /dev/null +++ b/java-stream-mvn/src/main/java/OffsetTrackingSend.java @@ -0,0 +1,37 @@ +import com.rabbitmq.stream.ByteCapacity; +import com.rabbitmq.stream.Environment; +import com.rabbitmq.stream.Producer; + +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.stream.IntStream; + +import static java.nio.charset.StandardCharsets.UTF_8; + +public class OffsetTrackingSend { + + public static void main(String[] args) throws InterruptedException { + try (Environment environment = Environment.builder().build()) { + String stream = "stream-offset-tracking-java"; + environment.streamCreator().stream(stream).maxLengthBytes(ByteCapacity.GB(1)).create(); + + Producer producer = environment.producerBuilder().stream(stream).build(); + + int messageCount = 100; + CountDownLatch confirmedLatch = new CountDownLatch(messageCount); + System.out.printf("Publishing %d messages...%n", messageCount); + IntStream.range(0, messageCount).forEach(i -> { + String body = i == messageCount - 1 ? "marker" : "hello"; + producer.send(producer.messageBuilder().addData(body.getBytes(UTF_8)).build(), + ctx -> { + if (ctx.isConfirmed()) { + confirmedLatch.countDown(); + } + }); + }); + + boolean completed = confirmedLatch.await(60, TimeUnit.SECONDS); + System.out.printf("Messages confirmed: %b.%n", completed); + } + } +} diff --git a/java-stream-mvn/src/main/java/Receive.java b/java-stream-mvn/src/main/java/Receive.java new file mode 100644 index 00000000..003b7788 --- /dev/null +++ b/java-stream-mvn/src/main/java/Receive.java @@ -0,0 +1,27 @@ +import com.rabbitmq.stream.ByteCapacity; +import com.rabbitmq.stream.Consumer; +import com.rabbitmq.stream.Environment; +import com.rabbitmq.stream.OffsetSpecification; + +import java.io.IOException; + +public class Receive { + + public static void main(String[] args) throws IOException { + Environment environment = Environment.builder().build(); + String stream = "hello-java-stream"; + environment.streamCreator().stream(stream).maxLengthBytes(ByteCapacity.GB(5)).create(); + + Consumer consumer = environment.consumerBuilder() + .stream(stream) + .offset(OffsetSpecification.first()) + .messageHandler((unused, message) -> { + System.out.println("Received message: " + new String(message.getBodyAsBinary())); + }).build(); + + System.out.println(" [x] Press Enter to close the consumer..."); + System.in.read(); + consumer.close(); + environment.close(); + } +} diff --git a/java-stream-mvn/src/main/java/Send.java b/java-stream-mvn/src/main/java/Send.java new file mode 100644 index 00000000..fd235919 --- /dev/null +++ b/java-stream-mvn/src/main/java/Send.java @@ -0,0 +1,20 @@ +import com.rabbitmq.stream.*; +import java.io.IOException; + +public class Send { + + public static void main(String[] args) throws IOException { + Environment environment = Environment.builder().build(); + String stream = "hello-java-stream"; + environment.streamCreator().stream(stream).maxLengthBytes(ByteCapacity.GB(5)).create(); + + Producer producer = environment.producerBuilder().stream(stream).build(); + producer.send(producer.messageBuilder().addData("Hello, World!".getBytes()).build(), null); + System.out.println(" [x] 'Hello, World!' message sent"); + + System.out.println(" [x] Press Enter to close the producer..."); + System.in.read(); + producer.close(); + environment.close(); + } +} diff --git a/java-stream-mvn/src/main/resources/logback.xml b/java-stream-mvn/src/main/resources/logback.xml new file mode 100644 index 00000000..83114f84 --- /dev/null +++ b/java-stream-mvn/src/main/resources/logback.xml @@ -0,0 +1,13 @@ + + + + %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n + + + + + + + + + diff --git a/java-stream-mvn/start-broker.sh b/java-stream-mvn/start-broker.sh new file mode 100755 index 00000000..a5294f7b --- /dev/null +++ b/java-stream-mvn/start-broker.sh @@ -0,0 +1,77 @@ +#!/usr/bin/env bash + +set -o errexit +set -o pipefail +# set -o xtrace + +script_dir="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" +readonly script_dir +echo "[INFO] script_dir: '$script_dir'" + +readonly docker_name_prefix='java-stream-mvn' +readonly rmq_docker_name="$docker_name_prefix-rabbitmq" +readonly rmq_image=${RABBITMQ_IMAGE:-rabbitmq:3-management} +readonly rmq_config_dir="$script_dir/rmq-conf" + +if [[ $1 == 'pull' ]] +then + readonly docker_pull_args='--pull always' +else + readonly docker_pull_args='' +fi + +set -o nounset + +function config_rabbitmq +{ + mkdir -p "$rmq_config_dir" + echo '[rabbitmq_top,rabbitmq_management,rabbitmq_stream,rabbitmq_stream_management,rabbitmq_amqp1_0].' > "$rmq_config_dir/enabled_plugins" + echo 'loopback_users = none' > "$rmq_config_dir/rabbitmq.conf" +} + +function start_rabbitmq +{ + echo "[INFO] starting RabbitMQ server docker container" + docker rm --force "$rmq_docker_name" 2>/dev/null || echo "[INFO] $rmq_docker_name was not running" + # shellcheck disable=SC2086 + docker run --detach $docker_pull_args \ + --name "$rmq_docker_name" \ + --env 'RABBITMQ_SERVER_ADDITIONAL_ERL_ARGS=-rabbitmq_stream advertised_host localhost' \ + --network host \ + --volume "$rmq_config_dir:/etc/rabbitmq:ro" \ + "$rmq_image" +} + +function wait_rabbitmq +{ + set +o errexit + + declare -i count=12 + while (( count > 0 )) && [[ "$(docker inspect --format='{{.State.Running}}' "$rmq_docker_name")" != 'true' ]] + do + echo '[WARNING] RabbitMQ container is not yet running...' + sleep 5 + (( count-- )) + done + + declare -i count=12 + while (( count > 0 )) && ! docker exec "$rmq_docker_name" epmd -names | grep -F 'name rabbit' + do + echo '[WARNING] epmd is not reporting rabbit name just yet...' + sleep 5 + (( count-- )) + done + + docker exec "$rmq_docker_name" rabbitmqctl await_startup + + set -o errexit +} + +config_rabbitmq + +start_rabbitmq + +wait_rabbitmq + +docker exec "$rmq_docker_name" rabbitmq-diagnostics erlang_version +docker exec "$rmq_docker_name" rabbitmqctl version diff --git a/java/.idea/.name b/java/.idea/.name deleted file mode 100644 index 85863977..00000000 --- a/java/.idea/.name +++ /dev/null @@ -1 +0,0 @@ -java \ No newline at end of file diff --git a/java/.idea/compiler.xml b/java/.idea/compiler.xml deleted file mode 100644 index 96cc43ef..00000000 --- a/java/.idea/compiler.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/java/.idea/misc.xml b/java/.idea/misc.xml deleted file mode 100644 index de08bfa2..00000000 --- a/java/.idea/misc.xml +++ /dev/null @@ -1,40 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - 1.8 - - - - - - - - \ No newline at end of file diff --git a/java/.idea/modules.xml b/java/.idea/modules.xml deleted file mode 100644 index a127731f..00000000 --- a/java/.idea/modules.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/java/.idea/vcs.xml b/java/.idea/vcs.xml deleted file mode 100644 index 6c0b8635..00000000 --- a/java/.idea/vcs.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/java/.idea/workspace.xml b/java/.idea/workspace.xml deleted file mode 100644 index 7883c086..00000000 --- a/java/.idea/workspace.xml +++ /dev/null @@ -1,248 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/java/EmitLog.java b/java/EmitLog.java index 748355c6..84620577 100644 --- a/java/EmitLog.java +++ b/java/EmitLog.java @@ -1,43 +1,26 @@ import com.rabbitmq.client.BuiltinExchangeType; -import com.rabbitmq.client.ConnectionFactory; -import com.rabbitmq.client.Connection; import com.rabbitmq.client.Channel; +import com.rabbitmq.client.Connection; +import com.rabbitmq.client.ConnectionFactory; public class EmitLog { - private static final String EXCHANGE_NAME = "logs"; - - public static void main(String[] argv) throws Exception { - ConnectionFactory factory = new ConnectionFactory(); - factory.setHost("localhost"); - Connection connection = factory.newConnection(); - Channel channel = connection.createChannel(); + private static final String EXCHANGE_NAME = "logs"; - channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.FANOUT); + public static void main(String[] argv) throws Exception { + ConnectionFactory factory = new ConnectionFactory(); + factory.setHost("localhost"); + try (Connection connection = factory.newConnection(); + Channel channel = connection.createChannel()) { + channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.FANOUT); - String message = getMessage(argv); + String message = argv.length < 1 ? "info: Hello World!" : + String.join(" ", argv); - channel.basicPublish(EXCHANGE_NAME, "", null, message.getBytes("UTF-8")); - System.out.println(" [x] Sent '" + message + "'"); - - channel.close(); - connection.close(); - } - - private static String getMessage(String[] strings){ - if (strings.length < 1) - return "info: Hello World!"; - return joinStrings(strings, " "); - } - - private static String joinStrings(String[] strings, String delimiter) { - int length = strings.length; - if (length == 0) return ""; - StringBuilder words = new StringBuilder(strings[0]); - for (int i = 1; i < length; i++) { - words.append(delimiter).append(strings[i]); + channel.basicPublish(EXCHANGE_NAME, "", null, message.getBytes("UTF-8")); + System.out.println(" [x] Sent '" + message + "'"); + } } - return words.toString(); - } + } diff --git a/java/EmitLogDirect.java b/java/EmitLogDirect.java index 7edce177..653d646c 100644 --- a/java/EmitLogDirect.java +++ b/java/EmitLogDirect.java @@ -1,52 +1,48 @@ import com.rabbitmq.client.BuiltinExchangeType; -import com.rabbitmq.client.ConnectionFactory; -import com.rabbitmq.client.Connection; import com.rabbitmq.client.Channel; +import com.rabbitmq.client.Connection; +import com.rabbitmq.client.ConnectionFactory; public class EmitLogDirect { - private static final String EXCHANGE_NAME = "direct_logs"; - - public static void main(String[] argv) throws Exception { + private static final String EXCHANGE_NAME = "direct_logs"; - ConnectionFactory factory = new ConnectionFactory(); - factory.setHost("localhost"); - Connection connection = factory.newConnection(); - Channel channel = connection.createChannel(); + public static void main(String[] argv) throws Exception { + ConnectionFactory factory = new ConnectionFactory(); + factory.setHost("localhost"); + try (Connection connection = factory.newConnection(); + Channel channel = connection.createChannel()) { + channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.DIRECT); - channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.DIRECT); + String severity = getSeverity(argv); + String message = getMessage(argv); - String severity = getSeverity(argv); - String message = getMessage(argv); - - channel.basicPublish(EXCHANGE_NAME, severity, null, message.getBytes("UTF-8")); - System.out.println(" [x] Sent '" + severity + "':'" + message + "'"); - - channel.close(); - connection.close(); - } + channel.basicPublish(EXCHANGE_NAME, severity, null, message.getBytes("UTF-8")); + System.out.println(" [x] Sent '" + severity + "':'" + message + "'"); + } + } - private static String getSeverity(String[] strings){ - if (strings.length < 1) - return "info"; - return strings[0]; - } + private static String getSeverity(String[] strings) { + if (strings.length < 1) + return "info"; + return strings[0]; + } - private static String getMessage(String[] strings){ - if (strings.length < 2) - return "Hello World!"; - return joinStrings(strings, " ", 1); - } + private static String getMessage(String[] strings) { + if (strings.length < 2) + return "Hello World!"; + return joinStrings(strings, " ", 1); + } - private static String joinStrings(String[] strings, String delimiter, int startIndex) { - int length = strings.length; - if (length == 0 ) return ""; - if (length < startIndex ) return ""; - StringBuilder words = new StringBuilder(strings[startIndex]); - for (int i = startIndex + 1; i < length; i++) { - words.append(delimiter).append(strings[i]); + private static String joinStrings(String[] strings, String delimiter, int startIndex) { + int length = strings.length; + if (length == 0) return ""; + if (length <= startIndex) return ""; + StringBuilder words = new StringBuilder(strings[startIndex]); + for (int i = startIndex + 1; i < length; i++) { + words.append(delimiter).append(strings[i]); + } + return words.toString(); } - return words.toString(); - } } diff --git a/java/EmitLogHeader.java b/java/EmitLogHeader.java index ca181003..b96cfa3c 100644 --- a/java/EmitLogHeader.java +++ b/java/EmitLogHeader.java @@ -5,57 +5,57 @@ public class EmitLogHeader { - private static final String EXCHANGE_NAME = "header_test"; - - public static void main(String[] argv) throws Exception { - if (argv.length < 1) { - System.err.println("Usage: EmitLogHeader message queueName [headers]..."); - System.exit(1); + private static final String EXCHANGE_NAME = "header_test"; + + public static void main(String[] argv) throws Exception { + if (argv.length < 1) { + System.err.println("Usage: EmitLogHeader message queueName [headers]..."); + System.exit(1); + } + + // The API requires a routing key, but in fact if you are using a header exchange the + // value of the routing key is not used in the routing. You can store information + // for the receiver here as the routing key is still available in the received message. + String routingKey = "ourTestRoutingKey"; + + // Argument processing: the first arg is the message, the rest are + // key value pairs for headers. + String message = argv[0]; + + // The map for the headers. + Map headers = new HashMap(); + + // The rest of the arguments are key value header pairs. For the purpose of this + // example, we are assuming they are all strings, but that is not required by RabbitMQ + for (int i = 1; i < argv.length; i++) { + System.out.println("Adding header " + argv[i] + " with value " + argv[i + 1] + " to Map"); + headers.put(argv[i], argv[i + 1]); + i++; + } + + ConnectionFactory factory = new ConnectionFactory(); + factory.setHost("localhost"); + try (Connection connection = factory.newConnection(); + Channel channel = connection.createChannel()) { + channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.HEADERS); + + AMQP.BasicProperties.Builder builder = new AMQP.BasicProperties.Builder(); + + // MessageProperties.PERSISTENT_TEXT_PLAIN is a static instance of AMQP.BasicProperties + // that contains a delivery mode and a priority. So we pass them to the builder. + builder.deliveryMode(MessageProperties.PERSISTENT_TEXT_PLAIN.getDeliveryMode()); + builder.priority(MessageProperties.PERSISTENT_TEXT_PLAIN.getPriority()); + + // Add the headers to the builder. + builder.headers(headers); + + // Use the builder to create the BasicProperties object. + AMQP.BasicProperties theProps = builder.build(); + + // Now we add the headers. This example only uses string headers, but they can also be integers + channel.basicPublish(EXCHANGE_NAME, routingKey, theProps, message.getBytes("UTF-8")); + System.out.println(" [x] Sent message: '" + message + "'"); + } } - - // The API requires a routing key, but in fact if you are using a header exchange the - // value of the routing key is not used in the routing. You can store information - // for the receiver here as the routing key is still available in the received message. - String routingKey = "ourTestRoutingKey"; - - // Argument processing: the first arg is the message, the rest are - // key value pairs for headers. - String message = argv[0]; - - // The map for the headers. - Map headers = new HashMap(); - - // The rest of the arguments are key value header pairs. For the purpose of this - // example, we are assuming they are all strings, but that is not required by RabbitMQ - for (int i = 1; i < argv.length; i++) { - System.out.println("Adding header " + argv[i] + " with value " + argv[i + 1] + " to Map"); - headers.put(argv[i], argv[i + 1]); - i++; - } - - ConnectionFactory factory = new ConnectionFactory(); - factory.setHost("localhost"); - Connection connection = factory.newConnection(); - Channel channel = connection.createChannel(); - - channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.HEADERS); - - AMQP.BasicProperties.Builder builder = new AMQP.BasicProperties.Builder(); - - // MessageProperties.PERSISTENT_TEXT_PLAIN is a static instance of AMQP.BasicProperties - // that contains a delivery mode and a priority. So we pass them to the builder. - builder.deliveryMode(MessageProperties.PERSISTENT_TEXT_PLAIN.getDeliveryMode()); - builder.priority(MessageProperties.PERSISTENT_TEXT_PLAIN.getPriority()); - - // Add the headers to the builder. - builder.headers(headers); - - // Use the builder to create the BasicProperties object. - AMQP.BasicProperties theProps = builder.build(); - - // Now we add the headers. This example only uses string headers, but they can also be integers - channel.basicPublish(EXCHANGE_NAME, routingKey, theProps, message.getBytes("UTF-8")); - System.out.println(" [x] Sent message: '" + message + "'"); - } } diff --git a/java/EmitLogTopic.java b/java/EmitLogTopic.java index 646652e3..ad14701c 100644 --- a/java/EmitLogTopic.java +++ b/java/EmitLogTopic.java @@ -1,65 +1,49 @@ import com.rabbitmq.client.BuiltinExchangeType; -import com.rabbitmq.client.ConnectionFactory; -import com.rabbitmq.client.Connection; import com.rabbitmq.client.Channel; +import com.rabbitmq.client.Connection; +import com.rabbitmq.client.ConnectionFactory; public class EmitLogTopic { - private static final String EXCHANGE_NAME = "topic_logs"; - - public static void main(String[] argv) { - Connection connection = null; - Channel channel = null; - try { - ConnectionFactory factory = new ConnectionFactory(); - factory.setHost("localhost"); - - connection = factory.newConnection(); - channel = connection.createChannel(); + private static final String EXCHANGE_NAME = "topic_logs"; - channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.TOPIC); + public static void main(String[] argv) throws Exception { + ConnectionFactory factory = new ConnectionFactory(); + factory.setHost("localhost"); + try (Connection connection = factory.newConnection(); + Channel channel = connection.createChannel()) { - String routingKey = getRouting(argv); - String message = getMessage(argv); + channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.TOPIC); - channel.basicPublish(EXCHANGE_NAME, routingKey, null, message.getBytes("UTF-8")); - System.out.println(" [x] Sent '" + routingKey + "':'" + message + "'"); + String routingKey = getRouting(argv); + String message = getMessage(argv); - } - catch (Exception e) { - e.printStackTrace(); - } - finally { - if (connection != null) { - try { - connection.close(); + channel.basicPublish(EXCHANGE_NAME, routingKey, null, message.getBytes("UTF-8")); + System.out.println(" [x] Sent '" + routingKey + "':'" + message + "'"); } - catch (Exception ignore) {} - } } - } - private static String getRouting(String[] strings){ - if (strings.length < 1) - return "anonymous.info"; - return strings[0]; - } + private static String getRouting(String[] strings) { + if (strings.length < 1) + return "anonymous.info"; + return strings[0]; + } - private static String getMessage(String[] strings){ - if (strings.length < 2) - return "Hello World!"; - return joinStrings(strings, " ", 1); - } + private static String getMessage(String[] strings) { + if (strings.length < 2) + return "Hello World!"; + return joinStrings(strings, " ", 1); + } - private static String joinStrings(String[] strings, String delimiter, int startIndex) { - int length = strings.length; - if (length == 0 ) return ""; - if (length < startIndex ) return ""; - StringBuilder words = new StringBuilder(strings[startIndex]); - for (int i = startIndex + 1; i < length; i++) { - words.append(delimiter).append(strings[i]); + private static String joinStrings(String[] strings, String delimiter, int startIndex) { + int length = strings.length; + if (length == 0) return ""; + if (length < startIndex) return ""; + StringBuilder words = new StringBuilder(strings[startIndex]); + for (int i = startIndex + 1; i < length; i++) { + words.append(delimiter).append(strings[i]); + } + return words.toString(); } - return words.toString(); - } } diff --git a/java/NewTask.java b/java/NewTask.java index 7e5f68be..924812cc 100644 --- a/java/NewTask.java +++ b/java/NewTask.java @@ -2,43 +2,26 @@ import com.rabbitmq.client.Connection; import com.rabbitmq.client.ConnectionFactory; import com.rabbitmq.client.MessageProperties; +import java.util.Map; public class NewTask { - private static final String TASK_QUEUE_NAME = "task_queue"; + private static final String TASK_QUEUE_NAME = "task_queue"; - public static void main(String[] argv) throws Exception { - ConnectionFactory factory = new ConnectionFactory(); - factory.setHost("localhost"); - Connection connection = factory.newConnection(); - Channel channel = connection.createChannel(); + public static void main(String[] argv) throws Exception { + ConnectionFactory factory = new ConnectionFactory(); + factory.setHost("localhost"); + try (Connection connection = factory.newConnection(); + Channel channel = connection.createChannel()) { + channel.queueDeclare(TASK_QUEUE_NAME, true, false, false, Map.of("x-queue-type", "quorum")); - channel.queueDeclare(TASK_QUEUE_NAME, true, false, false, null); + String message = String.join(" ", argv); - String message = getMessage(argv); - - channel.basicPublish("", TASK_QUEUE_NAME, - MessageProperties.PERSISTENT_TEXT_PLAIN, - message.getBytes("UTF-8")); - System.out.println(" [x] Sent '" + message + "'"); - - channel.close(); - connection.close(); - } - - private static String getMessage(String[] strings) { - if (strings.length < 1) - return "Hello World!"; - return joinStrings(strings, " "); - } - - private static String joinStrings(String[] strings, String delimiter) { - int length = strings.length; - if (length == 0) return ""; - StringBuilder words = new StringBuilder(strings[0]); - for (int i = 1; i < length; i++) { - words.append(delimiter).append(strings[i]); + channel.basicPublish("", TASK_QUEUE_NAME, + MessageProperties.PERSISTENT_TEXT_PLAIN, + message.getBytes("UTF-8")); + System.out.println(" [x] Sent '" + message + "'"); + } } - return words.toString(); - } + } diff --git a/java/PublisherConfirms.java b/java/PublisherConfirms.java new file mode 100644 index 00000000..ba9a8c67 --- /dev/null +++ b/java/PublisherConfirms.java @@ -0,0 +1,261 @@ +import com.rabbitmq.client.Channel; +import com.rabbitmq.client.ConfirmCallback; +import com.rabbitmq.client.Connection; +import com.rabbitmq.client.ConnectionFactory; + +import java.time.Duration; +import java.util.LinkedList; +import java.util.UUID; +import java.util.concurrent.ConcurrentNavigableMap; +import java.util.concurrent.ConcurrentSkipListMap; +import java.util.function.BooleanSupplier; + +public class PublisherConfirms { + + static final int MESSAGE_COUNT = 50_000; + + static Connection createConnection() throws Exception { + ConnectionFactory cf = new ConnectionFactory(); + cf.setHost("localhost"); + cf.setUsername("guest"); + cf.setPassword("guest"); + return cf.newConnection(); + } + + static final int MAX_OUTSTANDING = 1000; // Confirmation window + static final int THROTTLING_PERCENTAGE = 50; // Start throttling at 50% capacity + static final int MAX_DELAY_MS = 1000; // Maximum delay in milliseconds + + public static void main(String[] args) throws Exception { + publishMessagesIndividually(); + publishMessagesInBatch(); + handlePublishConfirmsAsynchronously(); + handlePublishConfirmsWithWindow(); + handlePublishConfirmsWithAdaptiveThrottling(); + } + + static void publishMessagesIndividually() throws Exception { + try (Connection connection = createConnection()) { + Channel ch = connection.createChannel(); + + String queue = UUID.randomUUID().toString(); + ch.queueDeclare(queue, false, false, true, null); + + ch.confirmSelect(); + long start = System.nanoTime(); + for (int i = 0; i < MESSAGE_COUNT; i++) { + String body = String.valueOf(i); + ch.basicPublish("", queue, null, body.getBytes()); + ch.waitForConfirmsOrDie(5_000); + } + long end = System.nanoTime(); + System.out.format("Published %,d messages individually in %,d ms%n", MESSAGE_COUNT, Duration.ofNanos(end - start).toMillis()); + } + } + + static void publishMessagesInBatch() throws Exception { + try (Connection connection = createConnection()) { + Channel ch = connection.createChannel(); + + String queue = UUID.randomUUID().toString(); + ch.queueDeclare(queue, false, false, true, null); + + ch.confirmSelect(); + + int batchSize = 100; + int outstandingMessageCount = 0; + + long start = System.nanoTime(); + for (int i = 0; i < MESSAGE_COUNT; i++) { + String body = String.valueOf(i); + ch.basicPublish("", queue, null, body.getBytes()); + outstandingMessageCount++; + + if (outstandingMessageCount == batchSize) { + ch.waitForConfirmsOrDie(5_000); + outstandingMessageCount = 0; + } + } + + if (outstandingMessageCount > 0) { + ch.waitForConfirmsOrDie(5_000); + } + long end = System.nanoTime(); + System.out.format("Published %,d messages in batch in %,d ms%n", MESSAGE_COUNT, Duration.ofNanos(end - start).toMillis()); + } + } + + static void handlePublishConfirmsAsynchronously() throws Exception { + try (Connection connection = createConnection()) { + Channel ch = connection.createChannel(); + + String queue = UUID.randomUUID().toString(); + ch.queueDeclare(queue, false, false, true, null); + + ch.confirmSelect(); + + ConcurrentNavigableMap outstandingConfirms = new ConcurrentSkipListMap<>(); + + ConfirmCallback cleanOutstandingConfirms = (sequenceNumber, multiple) -> { + if (multiple) { + ConcurrentNavigableMap confirmed = outstandingConfirms.headMap( + sequenceNumber, true + ); + confirmed.clear(); + } else { + outstandingConfirms.remove(sequenceNumber); + } + }; + + ch.addConfirmListener(cleanOutstandingConfirms, (sequenceNumber, multiple) -> { + String body = outstandingConfirms.get(sequenceNumber); + System.err.format( + "Message with body %s has been nack-ed. Sequence number: %d, multiple: %b%n", + body, sequenceNumber, multiple + ); + cleanOutstandingConfirms.handle(sequenceNumber, multiple); + }); + + long start = System.nanoTime(); + for (int i = 0; i < MESSAGE_COUNT; i++) { + String body = String.valueOf(i); + outstandingConfirms.put(ch.getNextPublishSeqNo(), body); + ch.basicPublish("", queue, null, body.getBytes()); + } + + if (!waitUntil(Duration.ofSeconds(60), () -> outstandingConfirms.isEmpty())) { + throw new IllegalStateException("All messages could not be confirmed in 60 seconds"); + } + + long end = System.nanoTime(); + System.out.format("Published %,d messages and handled confirms asynchronously in %,d ms%n", MESSAGE_COUNT, Duration.ofNanos(end - start).toMillis()); + } + } + + static void handlePublishConfirmsWithWindow() throws Exception { + try (Connection connection = createConnection()) { + Channel ch = connection.createChannel(); + + String queue = UUID.randomUUID().toString(); + ch.queueDeclare(queue, false, false, true, null); + ch.confirmSelect(); + + ConcurrentNavigableMap outstandingConfirms = new ConcurrentSkipListMap<>(); + + ConfirmCallback cleanOutstandingConfirms = (sequenceNumber, multiple) -> { + if (multiple) { + outstandingConfirms.headMap(sequenceNumber, true).clear(); + } else { + outstandingConfirms.remove(sequenceNumber); + } + synchronized (outstandingConfirms) { + outstandingConfirms.notifyAll(); + } + }; + + ch.addConfirmListener(cleanOutstandingConfirms, (sequenceNumber, multiple) -> { + System.err.format("Message nacked. Sequence: %d, multiple: %b%n", sequenceNumber, multiple); + cleanOutstandingConfirms.handle(sequenceNumber, multiple); + }); + + long start = System.nanoTime(); + for (int i = 0; i < MESSAGE_COUNT; i++) { + // Wait if window is full + synchronized (outstandingConfirms) { + while (outstandingConfirms.size() >= MAX_OUTSTANDING) { + outstandingConfirms.wait(); + } + } + + String body = String.valueOf(i); + outstandingConfirms.put(ch.getNextPublishSeqNo(), body); + ch.basicPublish("", queue, null, body.getBytes()); + } + + // Wait for remaining confirmations + synchronized (outstandingConfirms) { + while (!outstandingConfirms.isEmpty()) { + outstandingConfirms.wait(); + } + } + + long end = System.nanoTime(); + System.out.format("Published %,d messages with confirmation window in %,d ms%n", + MESSAGE_COUNT, Duration.ofNanos(end - start).toMillis()); + } + } + + static void handlePublishConfirmsWithAdaptiveThrottling() throws Exception { + try (Connection connection = createConnection()) { + Channel ch = connection.createChannel(); + + String queue = UUID.randomUUID().toString(); + ch.queueDeclare(queue, false, false, true, null); + ch.confirmSelect(); + + LinkedList outstandingConfirms = new LinkedList<>(); + int throttlingThreshold = MAX_OUTSTANDING * THROTTLING_PERCENTAGE / 100; + + ConfirmCallback cleanOutstandingConfirms = (sequenceNumber, multiple) -> { + synchronized (outstandingConfirms) { + if (multiple) { + outstandingConfirms.removeIf(seqNo -> seqNo <= sequenceNumber); + } else { + outstandingConfirms.removeFirstOccurrence(sequenceNumber); + } + outstandingConfirms.notifyAll(); + } + }; + + ch.addConfirmListener(cleanOutstandingConfirms, (sequenceNumber, multiple) -> { + System.err.format("Message nacked. Sequence: %d, multiple: %b%n", sequenceNumber, multiple); + cleanOutstandingConfirms.handle(sequenceNumber, multiple); + }); + + long start = System.nanoTime(); + for (int i = 0; i < MESSAGE_COUNT; i++) { + String body = String.valueOf(i); + + synchronized (outstandingConfirms) { + while (outstandingConfirms.size() >= MAX_OUTSTANDING) { + outstandingConfirms.wait(); + } + + int availablePermits = MAX_OUTSTANDING - outstandingConfirms.size(); + if (availablePermits < throttlingThreshold) { + double percentageUsed = 1.0 - (availablePermits / (double) MAX_OUTSTANDING); + int delay = (int) (percentageUsed * MAX_DELAY_MS); + if (delay > 0) { + outstandingConfirms.wait(delay); + } + } + + long seqNo = ch.getNextPublishSeqNo(); + outstandingConfirms.addLast(seqNo); + } + + ch.basicPublish("", queue, null, body.getBytes()); + } + + synchronized (outstandingConfirms) { + while (!outstandingConfirms.isEmpty()) { + outstandingConfirms.wait(); + } + } + + long end = System.nanoTime(); + System.out.format("Published %,d messages with adaptive throttling in %,d ms%n", + MESSAGE_COUNT, Duration.ofNanos(end - start).toMillis()); + } + } + + static boolean waitUntil(Duration timeout, BooleanSupplier condition) throws InterruptedException { + int waited = 0; + while (!condition.getAsBoolean() && waited < timeout.toMillis()) { + Thread.sleep(100L); + waited += 100; + } + return condition.getAsBoolean(); + } + +} diff --git a/java/README.md b/java/README.md index bcdbc3f6..0896b9ef 100644 --- a/java/README.md +++ b/java/README.md @@ -1,59 +1,137 @@ # Java code for RabbitMQ tutorials Here you can find the Java code examples from [RabbitMQ -tutorials](http://www.rabbitmq.com/getstarted.html). +tutorials](https://www.rabbitmq.com/getstarted.html). -To successfully use the examples you will need a running RabbitMQ server. +To successfully use the examples you will need a RabbitMQ node running locally. ## Requirements You'll need to download the following JAR files from Maven Central: - - * [RabbitMQ Java Client](http://central.maven.org/maven2/com/rabbitmq/amqp-client/4.0.2/amqp-client-4.0.2.jar) - * [SLF4J API](http://central.maven.org/maven2/org/slf4j/slf4j-api/1.7.21/slf4j-api-1.7.21.jar) - * [SLF4J Simple](http://central.maven.org/maven2/org/slf4j/slf4j-simple/1.7.22/slf4j-simple-1.7.22.jar) -Copy those files in your working directory, along the tutorials Java files. + * [RabbitMQ Java Client](https://repo1.maven.org/maven2/com/rabbitmq/amqp-client/5.21.0/amqp-client-5.21.0.jar) + * [SLF4J API](https://repo1.maven.org/maven2/org/slf4j/slf4j-api/2.0.13/slf4j-api-2.0.13.jar) + * [SLF4J Simple](https://repo1.maven.org/maven2/org/slf4j/slf4j-simple/2.0.13/slf4j-simple-2.0.13.jar) + +For example, with `wget`: -To compile you only need the Rabbitmq Java Client jar on the classpath. +``` shell +wget https://repo1.maven.org/maven2/com/rabbitmq/amqp-client/5.21.0/amqp-client-5.21.0.jar +wget https://repo1.maven.org/maven2/org/slf4j/slf4j-api/2.0.13/slf4j-api-2.0.13.jar +wget https://repo1.maven.org/maven2/org/slf4j/slf4j-simple/2.0.13/slf4j-simple-2.0.13.jar +``` + +Copy those files in your working directory, along the tutorials Java files. +To compile you only need the Rabbitmq Java client jar on the classpath. To run them you'll need all the dependencies, see examples below. -Note: If you're on Windows, -use a semicolon instead of a colon to separate items in the classpath. +You can set an environment variable for the jar files on the classpath e.g. -> You can set an environment variable for the jar files on the classpath e.g. -> -> $ export CP=.:amqp-client-4.0.2.jar:slf4j-api-1.7.21.jar:slf4j-simple-1.7.22.jar -> $ java -cp $CP Send -> -> or on Windows: -> -> > set CP=.;amqp-client-4.0.2.jar;slf4j-api-1.7.21.jar;slf4j-simple-1.7.22.jar -> > java -cp %CP% Send +``` +export CP=.:amqp-client-5.21.0.jar:slf4j-api-2.0.13.jar:slf4j-simple-2.0.13.jar +java -cp $CP Send +``` + +On Windows, use a semicolon instead of a colon to separate items in the classpath: + +``` +set CP=.;amqp-client-5.21.0.jar;slf4j-api-2.0.13.jar;slf4j-simple-2.0.13.jar +java -cp %CP% Send +``` ## Code -[Tutorial one: "Hello World!"](http://www.rabbitmq.com/tutorial-one-java.html): +#### [Tutorial one: "Hello World!"](https://www.rabbitmq.com/tutorials/tutorial-one-java.html): + +``` +javac -cp amqp-client-5.21.0.jar Send.java Recv.java + +# terminal tab 1 +java -cp .:amqp-client-5.21.0.jar:slf4j-api-2.0.13.jar:slf4j-simple-2.0.13.jar Recv + +# terminal tab 2 +java -cp .:amqp-client-5.21.0.jar:slf4j-api-2.0.13.jar:slf4j-simple-2.0.13.jar Send +``` + +#### [Tutorial two: Work Queues](https://www.rabbitmq.com/tutorials/tutorial-two-java.html): + +``` +javac -cp $CP NewTask.java Worker.java + +# terminal tab 1 +java -cp $CP NewTask + +# terminal tab 2 +java -cp $CP Worker +``` + +#### [Tutorial three: Publish/Subscribe](https://www.rabbitmq.com/tutorials/tutorial-three-java.html) + +``` shell +javac -cp $CP EmitLog.java ReceiveLogs.java + +# terminal tab 1 +java -cp $CP ReceiveLogs + +# terminal tab 2 +java -cp $CP EmitLog +``` + +#### [Tutorial four: Routing](https://www.rabbitmq.com/tutorials/tutorial-four-java.html) + +``` +javac -cp $CP ReceiveLogsDirect.java EmitLogDirect.java + +# terminal tab 1 +java -cp $CP ReceiveLogsDirect warning error + +# terminal tab 2 +java -cp $CP ReceiveLogsDirect info warning error + +# terminal tab 3 +java -cp $CP EmitLogDirect error "Run. Run. Or it will explode." +``` + +#### [Tutorial five: Topics](https://www.rabbitmq.com/tutorials/tutorial-five-java.html) + +``` +# To compile: +javac -cp $CP ReceiveLogsTopic.java EmitLogTopic.java + +# To receive all the logs: +java -cp $CP ReceiveLogsTopic "#" - $ javac -cp amqp-client-4.0.2.jar Send.java Recv.java +# To receive all logs from the facility "kern": +java -cp $CP ReceiveLogsTopic "kern.*" - $ java -cp .:amqp-client-4.0.2.jar:slf4j-api-1.7.21.jar:slf4j-simple-1.7.22.jar Send - $ java -cp .:amqp-client-4.0.2.jar:slf4j-api-1.7.21.jar:slf4j-simple-1.7.22.jar Recv +# Or if you want to hear only about "critical" logs: +java -cp $CP ReceiveLogsTopic "*.critical" -[Tutorial two: Work Queues](http://www.rabbitmq.com/tutorial-two-java.html): +# You can create multiple bindings: +java -cp $CP ReceiveLogsTopic "kern.*" "*.critical" - $ javac -cp amqp-client-4.0.2.jar NewTask.java Worker.java +# And to emit a log with a routing key "kern.critical" type: +java -cp $CP EmitLogTopic "kern.critical" "A critical kernel error" +``` - $ java -cp $CP NewTask - $ java -cp $CP Worker +#### [Tutorial six: RPC](https://www.rabbitmq.com/tutorials/tutorial-six-java.html) -[Tutorial three: Publish/Subscribe](http://www.rabbitmq.com/tutorial-three-java.html) +``` +# Compile and set up the classpath as usual (see tutorial one): +javac -cp $CP RPCClient.java RPCServer.java - $ javac -cp amqp-client-4.0.2.jar EmitLog.java ReceiveLogs.java +# Our RPC service is now ready. We can start the server: +java -cp $CP RPCServer - $ java -cp $CP ReceiveLogs - $ java -cp $CP EmitLog +# To request a fibonacci number run the client: +java -cp $CP RPCClient +``` +#### [Tutorial seven: Publisher Confirms](https://www.rabbitmq.com/tutorials/tutorial-seven-java.html) +``` +javac -cp $CP PublisherConfirms.java +java -cp $CP PublisherConfirms +``` diff --git a/java/RPCClient.java b/java/RPCClient.java index e7d2802c..6c72dd4e 100644 --- a/java/RPCClient.java +++ b/java/RPCClient.java @@ -1,86 +1,67 @@ -import com.rabbitmq.client.ConnectionFactory; -import com.rabbitmq.client.Connection; -import com.rabbitmq.client.Channel; -import com.rabbitmq.client.DefaultConsumer; import com.rabbitmq.client.AMQP; -import com.rabbitmq.client.Envelope; +import com.rabbitmq.client.Channel; +import com.rabbitmq.client.Connection; +import com.rabbitmq.client.ConnectionFactory; import java.io.IOException; import java.util.UUID; -import java.util.concurrent.ArrayBlockingQueue; -import java.util.concurrent.BlockingQueue; -import java.util.concurrent.TimeoutException; - -public class RPCClient { - - private Connection connection; - private Channel channel; - private String requestQueueName = "rpc_queue"; +import java.util.concurrent.*; - public RPCClient() throws IOException, TimeoutException { - ConnectionFactory factory = new ConnectionFactory(); - factory.setHost("localhost"); +public class RPCClient implements AutoCloseable { - connection = factory.newConnection(); - channel = connection.createChannel(); - } + private Connection connection; + private Channel channel; + private String requestQueueName = "rpc_queue"; - public String call(String message) throws IOException, InterruptedException { - final String corrId = UUID.randomUUID().toString(); + public RPCClient() throws IOException, TimeoutException { + ConnectionFactory factory = new ConnectionFactory(); + factory.setHost("localhost"); - String replyQueueName = channel.queueDeclare().getQueue(); - AMQP.BasicProperties props = new AMQP.BasicProperties - .Builder() - .correlationId(corrId) - .replyTo(replyQueueName) - .build(); + connection = factory.newConnection(); + channel = connection.createChannel(); + } - channel.basicPublish("", requestQueueName, props, message.getBytes("UTF-8")); + public static void main(String[] argv) { + try (RPCClient fibonacciRpc = new RPCClient()) { + for (int i = 0; i < 32; i++) { + String i_str = Integer.toString(i); + System.out.println(" [x] Requesting fib(" + i_str + ")"); + String response = fibonacciRpc.call(i_str); + System.out.println(" [.] Got '" + response + "'"); + } + } catch (IOException | TimeoutException | InterruptedException | ExecutionException e) { + e.printStackTrace(); + } + } - final BlockingQueue response = new ArrayBlockingQueue(1); + public String call(String message) throws IOException, InterruptedException, ExecutionException { + final String corrId = UUID.randomUUID().toString(); - String ctag = channel.basicConsume(replyQueueName, true, new DefaultConsumer(channel) { - @Override - public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException { - if (properties.getCorrelationId().equals(corrId)) { - response.offer(new String(body, "UTF-8")); - } - } - }); + String replyQueueName = channel.queueDeclare().getQueue(); + AMQP.BasicProperties props = new AMQP.BasicProperties + .Builder() + .correlationId(corrId) + .replyTo(replyQueueName) + .build(); - String result = response.take(); - channel.basicCancel(ctag); - return result; - } + channel.basicPublish("", requestQueueName, props, message.getBytes("UTF-8")); - public void close() throws IOException { - connection.close(); - } + final CompletableFuture response = new CompletableFuture<>(); - public static void main(String[] argv) { - RPCClient fibonacciRpc = null; - String response = null; - try { - fibonacciRpc = new RPCClient(); + String ctag = channel.basicConsume(replyQueueName, true, (consumerTag, delivery) -> { + if (delivery.getProperties().getCorrelationId().equals(corrId)) { + response.complete(new String(delivery.getBody(), "UTF-8")); + } + }, consumerTag -> { + }); - for (int i = 0; i < 32; i++) { - String i_str = Integer.toString(i); - System.out.println(" [x] Requesting fib(" + i_str + ")"); - response = fibonacciRpc.call(i_str); - System.out.println(" [.] Got '" + response + "'"); - } + String result = response.get(); + channel.basicCancel(ctag); + return result; } - catch (IOException | TimeoutException | InterruptedException e) { - e.printStackTrace(); - } - finally { - if (fibonacciRpc!= null) { - try { - fibonacciRpc.close(); - } - catch (IOException _ignore) {} - } + + public void close() throws IOException { + connection.close(); } - } } diff --git a/java/RPCServer.java b/java/RPCServer.java index 0575e601..fa03f60d 100644 --- a/java/RPCServer.java +++ b/java/RPCServer.java @@ -1,90 +1,50 @@ -import com.rabbitmq.client.ConnectionFactory; -import com.rabbitmq.client.Connection; -import com.rabbitmq.client.Channel; -import com.rabbitmq.client.Consumer; -import com.rabbitmq.client.DefaultConsumer; -import com.rabbitmq.client.AMQP; -import com.rabbitmq.client.Envelope; - -import java.io.IOException; -import java.util.concurrent.TimeoutException; +import com.rabbitmq.client.*; +import java.util.Map; public class RPCServer { - private static final String RPC_QUEUE_NAME = "rpc_queue"; - - private static int fib(int n) { - if (n ==0) return 0; - if (n == 1) return 1; - return fib(n-1) + fib(n-2); - } - - public static void main(String[] argv) { - ConnectionFactory factory = new ConnectionFactory(); - factory.setHost("localhost"); - - Connection connection = null; - try { - connection = factory.newConnection(); - final Channel channel = connection.createChannel(); - - channel.queueDeclare(RPC_QUEUE_NAME, false, false, false, null); - channel.queuePurge(RPC_QUEUE_NAME); - - channel.basicQos(1); + private static final String RPC_QUEUE_NAME = "rpc_queue"; - System.out.println(" [x] Awaiting RPC requests"); - - Consumer consumer = new DefaultConsumer(channel) { - @Override - public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException { - AMQP.BasicProperties replyProps = new AMQP.BasicProperties - .Builder() - .correlationId(properties.getCorrelationId()) - .build(); - - String response = ""; - - try { - String message = new String(body,"UTF-8"); - int n = Integer.parseInt(message); + private static int fib(int n) { + if (n == 0) return 0; + if (n == 1) return 1; + return fib(n - 1) + fib(n - 2); + } - System.out.println(" [.] fib(" + message + ")"); - response += fib(n); - } - catch (RuntimeException e){ - System.out.println(" [.] " + e.toString()); - } - finally { - channel.basicPublish( "", properties.getReplyTo(), replyProps, response.getBytes("UTF-8")); - channel.basicAck(envelope.getDeliveryTag(), false); - // RabbitMq consumer worker thread notifies the RPC server owner thread - synchronized(this) { - this.notify(); + public static void main(String[] argv) throws Exception { + ConnectionFactory factory = new ConnectionFactory(); + factory.setHost("localhost"); + + Connection connection = factory.newConnection(); + Channel channel = connection.createChannel(); + channel.queueDeclare(RPC_QUEUE_NAME, true, false, false, Map.of("x-queue-type", "quorum")); + channel.queuePurge(RPC_QUEUE_NAME); + + channel.basicQos(1); + + System.out.println(" [x] Awaiting RPC requests"); + + DeliverCallback deliverCallback = (consumerTag, delivery) -> { + AMQP.BasicProperties replyProps = new AMQP.BasicProperties + .Builder() + .correlationId(delivery.getProperties().getCorrelationId()) + .build(); + + String response = ""; + try { + String message = new String(delivery.getBody(), "UTF-8"); + int n = Integer.parseInt(message); + + System.out.println(" [.] fib(" + message + ")"); + response += fib(n); + } catch (RuntimeException e) { + System.out.println(" [.] " + e); + } finally { + channel.basicPublish("", delivery.getProperties().getReplyTo(), replyProps, response.getBytes("UTF-8")); + channel.basicAck(delivery.getEnvelope().getDeliveryTag(), false); } - } - } - }; + }; - channel.basicConsume(RPC_QUEUE_NAME, false, consumer); - // Wait and be prepared to consume the message from RPC client. - while (true) { - synchronized(consumer) { - try { - consumer.wait(); - } catch (InterruptedException e) { - e.printStackTrace(); - } - } - } - } catch (IOException | TimeoutException e) { - e.printStackTrace(); - } - finally { - if (connection != null) - try { - connection.close(); - } catch (IOException _ignore) {} + channel.basicConsume(RPC_QUEUE_NAME, false, deliverCallback, (consumerTag -> {})); } - } } \ No newline at end of file diff --git a/java/ReceiveLogHeader.java b/java/ReceiveLogHeader.java index b6a4eda5..96c9d17a 100644 --- a/java/ReceiveLogHeader.java +++ b/java/ReceiveLogHeader.java @@ -1,62 +1,57 @@ import com.rabbitmq.client.*; -import java.io.IOException; import java.util.HashMap; import java.util.Map; public class ReceiveLogHeader { - private static final String EXCHANGE_NAME = "header_test"; - - public static void main(String[] argv) throws Exception { - if (argv.length < 1) { - System.err.println("Usage: ReceiveLogsHeader queueName [headers]..."); - System.exit(1); - } - - ConnectionFactory factory = new ConnectionFactory(); - factory.setHost("localhost"); - Connection connection = factory.newConnection(); - Channel channel = connection.createChannel(); - - channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.HEADERS); - - // The API requires a routing key, but in fact if you are using a header exchange the - // value of the routing key is not used in the routing. You can receive information - // from the sender here as the routing key is still available in the received message. - String routingKeyFromUser = "ourTestRoutingKey"; - - // Argument processing: the first arg is the local queue name, the rest are - // key value pairs for headers. - String queueInputName = argv[0]; - - // The map for the headers. - Map headers = new HashMap(); - - // The rest of the arguments are key value header pairs. For the purpose of this - // example, we are assuming they are all strings, but that is not required by RabbitMQ - // Note that when you run this code you should include the x-match header on the command - // line. Example: - // java -cp $CP ReceiveLogsHeader testQueue1 x-match any header1 value1 - for (int i = 1; i < argv.length; i++) { - headers.put(argv[i], argv[i + 1]); - System.out.println("Binding header " + argv[i] + " and value " + argv[i + 1] + " to queue " + queueInputName); - i++; + private static final String EXCHANGE_NAME = "header_test"; + + public static void main(String[] argv) throws Exception { + if (argv.length < 1) { + System.err.println("Usage: ReceiveLogsHeader queueName [headers]..."); + System.exit(1); + } + + ConnectionFactory factory = new ConnectionFactory(); + factory.setHost("localhost"); + Connection connection = factory.newConnection(); + Channel channel = connection.createChannel(); + + channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.HEADERS); + + // The API requires a routing key, but in fact if you are using a header exchange the + // value of the routing key is not used in the routing. You can receive information + // from the sender here as the routing key is still available in the received message. + String routingKeyFromUser = "ourTestRoutingKey"; + + // Argument processing: the first arg is the local queue name, the rest are + // key value pairs for headers. + String queueInputName = argv[0]; + + // The map for the headers. + Map headers = new HashMap<>(); + + // The rest of the arguments are key value header pairs. For the purpose of this + // example, we are assuming they are all strings, but that is not required by RabbitMQ + // Note that when you run this code you should include the x-match header on the command + // line. Example: + // java -cp $CP ReceiveLogsHeader testQueue1 x-match any header1 value1 + for (int i = 1; i < argv.length; i++) { + headers.put(argv[i], argv[i + 1]); + System.out.println("Binding header " + argv[i] + " and value " + argv[i + 1] + " to queue " + queueInputName); + i++; + } + + String queueName = channel.queueDeclare(queueInputName, true, false, false, null).getQueue(); + channel.queueBind(queueName, EXCHANGE_NAME, routingKeyFromUser, headers); + + System.out.println(" [*] Waiting for messages. To exit press CTRL+C"); + + DeliverCallback deliverCallback = (consumerTag, delivery) -> { + String message = new String(delivery.getBody(), "UTF-8"); + System.out.println(" [x] Received '" + delivery.getEnvelope().getRoutingKey() + "':'" + message + "'"); + }; + channel.basicConsume(queueName, true, deliverCallback, consumerTag -> { }); } - - String queueName = channel.queueDeclare(queueInputName, true, false, false, null).getQueue(); - channel.queueBind(queueName, EXCHANGE_NAME, routingKeyFromUser, headers); - - System.out.println(" [*] Waiting for messages. To exit press CTRL+C"); - - Consumer consumer = new DefaultConsumer(channel) { - @Override - public void handleDelivery(String consumerTag, Envelope envelope, - AMQP.BasicProperties properties, byte[] body) throws IOException { - String message = new String(body, "UTF-8"); - System.out.println(" [x] Received '" + envelope.getRoutingKey() + "':'" + message + "'"); - } - }; - channel.basicConsume(queueName, true, consumer); - } } diff --git a/java/ReceiveLogs.java b/java/ReceiveLogs.java index fd807a6b..5fdaa78a 100644 --- a/java/ReceiveLogs.java +++ b/java/ReceiveLogs.java @@ -1,31 +1,25 @@ import com.rabbitmq.client.*; -import java.io.IOException; - public class ReceiveLogs { - private static final String EXCHANGE_NAME = "logs"; + private static final String EXCHANGE_NAME = "logs"; - public static void main(String[] argv) throws Exception { - ConnectionFactory factory = new ConnectionFactory(); - factory.setHost("localhost"); - Connection connection = factory.newConnection(); - Channel channel = connection.createChannel(); + public static void main(String[] argv) throws Exception { + ConnectionFactory factory = new ConnectionFactory(); + factory.setHost("localhost"); + Connection connection = factory.newConnection(); + Channel channel = connection.createChannel(); - channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.FANOUT); - String queueName = channel.queueDeclare().getQueue(); - channel.queueBind(queueName, EXCHANGE_NAME, ""); + channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.FANOUT); + String queueName = channel.queueDeclare().getQueue(); + channel.queueBind(queueName, EXCHANGE_NAME, ""); - System.out.println(" [*] Waiting for messages. To exit press CTRL+C"); + System.out.println(" [*] Waiting for messages. To exit press CTRL+C"); - Consumer consumer = new DefaultConsumer(channel) { - @Override - public void handleDelivery(String consumerTag, Envelope envelope, - AMQP.BasicProperties properties, byte[] body) throws IOException { - String message = new String(body, "UTF-8"); - System.out.println(" [x] Received '" + message + "'"); - } - }; - channel.basicConsume(queueName, true, consumer); - } + DeliverCallback deliverCallback = (consumerTag, delivery) -> { + String message = new String(delivery.getBody(), "UTF-8"); + System.out.println(" [x] Received '" + message + "'"); + }; + channel.basicConsume(queueName, true, deliverCallback, consumerTag -> { }); + } } diff --git a/java/ReceiveLogsDirect.java b/java/ReceiveLogsDirect.java index 0a13f8d1..b505165f 100644 --- a/java/ReceiveLogsDirect.java +++ b/java/ReceiveLogsDirect.java @@ -1,39 +1,34 @@ import com.rabbitmq.client.*; -import java.io.IOException; - public class ReceiveLogsDirect { - private static final String EXCHANGE_NAME = "direct_logs"; - - public static void main(String[] argv) throws Exception { - ConnectionFactory factory = new ConnectionFactory(); - factory.setHost("localhost"); - Connection connection = factory.newConnection(); - Channel channel = connection.createChannel(); - - channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.DIRECT); - String queueName = channel.queueDeclare().getQueue(); - - if (argv.length < 1){ - System.err.println("Usage: ReceiveLogsDirect [info] [warning] [error]"); - System.exit(1); - } - - for(String severity : argv){ - channel.queueBind(queueName, EXCHANGE_NAME, severity); + private static final String EXCHANGE_NAME = "direct_logs"; + + public static void main(String[] argv) throws Exception { + ConnectionFactory factory = new ConnectionFactory(); + factory.setHost("localhost"); + Connection connection = factory.newConnection(); + Channel channel = connection.createChannel(); + + channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.DIRECT); + String queueName = channel.queueDeclare().getQueue(); + + if (argv.length < 1) { + System.err.println("Usage: ReceiveLogsDirect [info] [warning] [error]"); + System.exit(1); + } + + for (String severity : argv) { + channel.queueBind(queueName, EXCHANGE_NAME, severity); + } + System.out.println(" [*] Waiting for messages. To exit press CTRL+C"); + + DeliverCallback deliverCallback = (consumerTag, delivery) -> { + String message = new String(delivery.getBody(), "UTF-8"); + System.out.println(" [x] Received '" + delivery.getEnvelope().getRoutingKey() + "':'" + message + "'"); + }; + channel.basicConsume(queueName, true, deliverCallback, consumerTag -> { + }); } - System.out.println(" [*] Waiting for messages. To exit press CTRL+C"); - - Consumer consumer = new DefaultConsumer(channel) { - @Override - public void handleDelivery(String consumerTag, Envelope envelope, - AMQP.BasicProperties properties, byte[] body) throws IOException { - String message = new String(body, "UTF-8"); - System.out.println(" [x] Received '" + envelope.getRoutingKey() + "':'" + message + "'"); - } - }; - channel.basicConsume(queueName, true, consumer); - } } diff --git a/java/ReceiveLogsTopic.java b/java/ReceiveLogsTopic.java index f100d34f..0f484bb1 100644 --- a/java/ReceiveLogsTopic.java +++ b/java/ReceiveLogsTopic.java @@ -1,40 +1,34 @@ import com.rabbitmq.client.*; -import java.io.IOException; - public class ReceiveLogsTopic { - private static final String EXCHANGE_NAME = "topic_logs"; + private static final String EXCHANGE_NAME = "topic_logs"; - public static void main(String[] argv) throws Exception { - ConnectionFactory factory = new ConnectionFactory(); - factory.setHost("localhost"); - Connection connection = factory.newConnection(); - Channel channel = connection.createChannel(); + public static void main(String[] argv) throws Exception { + ConnectionFactory factory = new ConnectionFactory(); + factory.setHost("localhost"); + Connection connection = factory.newConnection(); + Channel channel = connection.createChannel(); - channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.TOPIC); - String queueName = channel.queueDeclare().getQueue(); + channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.TOPIC); + String queueName = channel.queueDeclare().getQueue(); - if (argv.length < 1) { - System.err.println("Usage: ReceiveLogsTopic [binding_key]..."); - System.exit(1); - } + if (argv.length < 1) { + System.err.println("Usage: ReceiveLogsTopic [binding_key]..."); + System.exit(1); + } - for (String bindingKey : argv) { - channel.queueBind(queueName, EXCHANGE_NAME, bindingKey); - } + for (String bindingKey : argv) { + channel.queueBind(queueName, EXCHANGE_NAME, bindingKey); + } - System.out.println(" [*] Waiting for messages. To exit press CTRL+C"); - - Consumer consumer = new DefaultConsumer(channel) { - @Override - public void handleDelivery(String consumerTag, Envelope envelope, - AMQP.BasicProperties properties, byte[] body) throws IOException { - String message = new String(body, "UTF-8"); - System.out.println(" [x] Received '" + envelope.getRoutingKey() + "':'" + message + "'"); - } - }; - channel.basicConsume(queueName, true, consumer); - } + System.out.println(" [*] Waiting for messages. To exit press CTRL+C"); + + DeliverCallback deliverCallback = (consumerTag, delivery) -> { + String message = new String(delivery.getBody(), "UTF-8"); + System.out.println(" [x] Received '" + delivery.getEnvelope().getRoutingKey() + "':'" + message + "'"); + }; + channel.basicConsume(queueName, true, deliverCallback, consumerTag -> { }); + } } diff --git a/java/Recv.java b/java/Recv.java index 355397c5..2c0fe65d 100644 --- a/java/Recv.java +++ b/java/Recv.java @@ -1,28 +1,27 @@ -import com.rabbitmq.client.*; - -import java.io.IOException; +import com.rabbitmq.client.Channel; +import com.rabbitmq.client.Connection; +import com.rabbitmq.client.ConnectionFactory; +import com.rabbitmq.client.DeliverCallback; +import java.nio.charset.StandardCharsets; +import java.util.Map; public class Recv { - private final static String QUEUE_NAME = "hello"; + private final static String QUEUE_NAME = "hello"; - public static void main(String[] argv) throws Exception { - ConnectionFactory factory = new ConnectionFactory(); - factory.setHost("localhost"); - Connection connection = factory.newConnection(); - Channel channel = connection.createChannel(); + public static void main(String[] argv) throws Exception { + ConnectionFactory factory = new ConnectionFactory(); + factory.setHost("localhost"); + Connection connection = factory.newConnection(); + Channel channel = connection.createChannel(); - channel.queueDeclare(QUEUE_NAME, false, false, false, null); - System.out.println(" [*] Waiting for messages. To exit press CTRL+C"); + channel.queueDeclare(QUEUE_NAME, true, false, false, Map.of("x-queue-type", "quorum")); + System.out.println(" [*] Waiting for messages. To exit press CTRL+C"); - Consumer consumer = new DefaultConsumer(channel) { - @Override - public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) - throws IOException { - String message = new String(body, "UTF-8"); - System.out.println(" [x] Received '" + message + "'"); - } - }; - channel.basicConsume(QUEUE_NAME, true, consumer); - } + DeliverCallback deliverCallback = (consumerTag, delivery) -> { + String message = new String(delivery.getBody(), StandardCharsets.UTF_8); + System.out.println(" [x] Received '" + message + "'"); + }; + channel.basicConsume(QUEUE_NAME, true, deliverCallback, consumerTag -> { }); + } } diff --git a/java/Send.java b/java/Send.java index 111f78a3..8f1d4df7 100644 --- a/java/Send.java +++ b/java/Send.java @@ -2,22 +2,22 @@ import com.rabbitmq.client.Connection; import com.rabbitmq.client.ConnectionFactory; -public class Send { - - private final static String QUEUE_NAME = "hello"; +import java.nio.charset.StandardCharsets; +import java.util.Map; - public static void main(String[] argv) throws Exception { - ConnectionFactory factory = new ConnectionFactory(); - factory.setHost("localhost"); - Connection connection = factory.newConnection(); - Channel channel = connection.createChannel(); +public class Send { - channel.queueDeclare(QUEUE_NAME, false, false, false, null); - String message = "Hello World!"; - channel.basicPublish("", QUEUE_NAME, null, message.getBytes("UTF-8")); - System.out.println(" [x] Sent '" + message + "'"); + private final static String QUEUE_NAME = "hello"; - channel.close(); - connection.close(); - } + public static void main(String[] argv) throws Exception { + ConnectionFactory factory = new ConnectionFactory(); + factory.setHost("localhost"); + try (Connection connection = factory.newConnection(); + Channel channel = connection.createChannel()) { + channel.queueDeclare(QUEUE_NAME, true, false, false, Map.of("x-queue-type", "quorum")); + String message = "Hello World!"; + channel.basicPublish("", QUEUE_NAME, null, message.getBytes(StandardCharsets.UTF_8)); + System.out.println(" [x] Sent '" + message + "'"); + } + } } diff --git a/java/Worker.java b/java/Worker.java index ca679d93..cd5be75e 100644 --- a/java/Worker.java +++ b/java/Worker.java @@ -1,49 +1,48 @@ -import com.rabbitmq.client.*; - -import java.io.IOException; +import com.rabbitmq.client.Channel; +import com.rabbitmq.client.Connection; +import com.rabbitmq.client.ConnectionFactory; +import com.rabbitmq.client.DeliverCallback; +import java.util.Map; public class Worker { - private static final String TASK_QUEUE_NAME = "task_queue"; + private static final String TASK_QUEUE_NAME = "task_queue"; - public static void main(String[] argv) throws Exception { - ConnectionFactory factory = new ConnectionFactory(); - factory.setHost("localhost"); - final Connection connection = factory.newConnection(); - final Channel channel = connection.createChannel(); + public static void main(String[] argv) throws Exception { + ConnectionFactory factory = new ConnectionFactory(); + factory.setHost("localhost"); + final Connection connection = factory.newConnection(); + final Channel channel = connection.createChannel(); - channel.queueDeclare(TASK_QUEUE_NAME, true, false, false, null); - System.out.println(" [*] Waiting for messages. To exit press CTRL+C"); + channel.queueDeclare(TASK_QUEUE_NAME, true, false, false, Map.of("x-queue-type", "quorum")); + System.out.println(" [*] Waiting for messages. To exit press CTRL+C"); - channel.basicQos(1); + channel.basicQos(1); - final Consumer consumer = new DefaultConsumer(channel) { - @Override - public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException { - String message = new String(body, "UTF-8"); + DeliverCallback deliverCallback = (consumerTag, delivery) -> { + String message = new String(delivery.getBody(), "UTF-8"); - System.out.println(" [x] Received '" + message + "'"); - try { - doWork(message); - } finally { - System.out.println(" [x] Done"); - channel.basicAck(envelope.getDeliveryTag(), false); - } - } - }; - channel.basicConsume(TASK_QUEUE_NAME, false, consumer); - } - - private static void doWork(String task) { - for (char ch : task.toCharArray()) { - if (ch == '.') { - try { - Thread.sleep(1000); - } catch (InterruptedException _ignored) { - Thread.currentThread().interrupt(); + System.out.println(" [x] Received '" + message + "'"); + try { + doWork(message); + } finally { + System.out.println(" [x] Done"); + channel.basicAck(delivery.getEnvelope().getDeliveryTag(), false); + } + }; + channel.basicConsume(TASK_QUEUE_NAME, false, deliverCallback, consumerTag -> { }); + } + + private static void doWork(String task) { + for (char ch : task.toCharArray()) { + if (ch == '.') { + try { + Thread.sleep(1000); + } catch (InterruptedException _ignored) { + Thread.currentThread().interrupt(); + } + } } - } } - } } diff --git a/java/recompile.sh b/java/recompile.sh index 504548fa..eb5509c6 100755 --- a/java/recompile.sh +++ b/java/recompile.sh @@ -1,3 +1,3 @@ #!/bin/sh -javac -cp .:amqp-client-4.0.2.jar:slf4j-api-1.7.21.jar:slf4j-simple-1.7.22.jar *.java +javac -cp .:amqp-client-5.21.0.jar:slf4j-api-2.0.13.jar:slf4j-simple-2.0.13.jar *.java diff --git a/java8-maven/pom.xml b/java8-maven/pom.xml deleted file mode 100644 index 6cbdcd97..00000000 --- a/java8-maven/pom.xml +++ /dev/null @@ -1,26 +0,0 @@ - - 4.0.0 - - com.rabbitmq - rabbitmq-tutorial - 1.0-SNAPSHOT - jar - - rabbitmq-tutorial - https://github.com/rabbitmq/rabbitmq-tutorials - - - UTF-8 - 1.8 - 1.8 - - - - - com.rabbitmq - amqp-client - LATEST - - - diff --git a/java8-maven/src/main/java/EmitLog.java b/java8-maven/src/main/java/EmitLog.java deleted file mode 100644 index 301d6918..00000000 --- a/java8-maven/src/main/java/EmitLog.java +++ /dev/null @@ -1,35 +0,0 @@ -import com.rabbitmq.client.BuiltinExchangeType; -import com.rabbitmq.client.ConnectionFactory; -import com.rabbitmq.client.Connection; -import com.rabbitmq.client.Channel; - -import java.util.stream.Collectors; -import java.util.stream.Stream; - -public class EmitLog { - - private static final String EXCHANGE_NAME = "logs"; - - public static void main(String[] argv) throws Exception { - ConnectionFactory factory = new ConnectionFactory(); - factory.setHost("localhost"); - try (Connection connection = factory.newConnection(); - Channel channel = connection.createChannel()) { - - channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.FANOUT); - - String message = getMessage(argv); - - channel.basicPublish(EXCHANGE_NAME, "", null, message.getBytes("UTF-8")); - System.out.println(" [x] Sent '" + message + "'"); - } - } - - private static String getMessage(String[] strings) { - if (strings.length < 1) - return "info: Hello World!"; - return String.join(" ", strings); - } - -} - diff --git a/java8-maven/src/main/java/EmitLogDirect.java b/java8-maven/src/main/java/EmitLogDirect.java deleted file mode 100644 index cb3418a4..00000000 --- a/java8-maven/src/main/java/EmitLogDirect.java +++ /dev/null @@ -1,44 +0,0 @@ -import com.rabbitmq.client.BuiltinExchangeType; -import com.rabbitmq.client.ConnectionFactory; -import com.rabbitmq.client.Connection; -import com.rabbitmq.client.Channel; - -import java.util.Arrays; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -public class EmitLogDirect { - - private static final String EXCHANGE_NAME = "direct_logs"; - - public static void main(String[] argv) throws Exception { - - ConnectionFactory factory = new ConnectionFactory(); - factory.setHost("localhost"); - try (Connection connection = factory.newConnection(); - Channel channel = connection.createChannel()) { - - channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.DIRECT); - - String severity = getSeverity(argv); - String message = getMessage(argv); - - channel.basicPublish(EXCHANGE_NAME, severity, null, message.getBytes("UTF-8")); - System.out.println(" [x] Sent '" + severity + "':'" + message + "'"); - } - } - - private static String getSeverity(String[] strings) { - if (strings.length < 1) - return "info"; - return strings[0]; - } - - private static String getMessage(String[] strings) { - if (strings.length < 2) - return "Hello World!"; - return String.join(" ",Arrays.copyOfRange(strings, 1, strings.length)); - } - -} - diff --git a/java8-maven/src/main/java/EmitLogHeader.java b/java8-maven/src/main/java/EmitLogHeader.java deleted file mode 100644 index ca181003..00000000 --- a/java8-maven/src/main/java/EmitLogHeader.java +++ /dev/null @@ -1,61 +0,0 @@ -import com.rabbitmq.client.*; - -import java.util.HashMap; -import java.util.Map; - -public class EmitLogHeader { - - private static final String EXCHANGE_NAME = "header_test"; - - public static void main(String[] argv) throws Exception { - if (argv.length < 1) { - System.err.println("Usage: EmitLogHeader message queueName [headers]..."); - System.exit(1); - } - - // The API requires a routing key, but in fact if you are using a header exchange the - // value of the routing key is not used in the routing. You can store information - // for the receiver here as the routing key is still available in the received message. - String routingKey = "ourTestRoutingKey"; - - // Argument processing: the first arg is the message, the rest are - // key value pairs for headers. - String message = argv[0]; - - // The map for the headers. - Map headers = new HashMap(); - - // The rest of the arguments are key value header pairs. For the purpose of this - // example, we are assuming they are all strings, but that is not required by RabbitMQ - for (int i = 1; i < argv.length; i++) { - System.out.println("Adding header " + argv[i] + " with value " + argv[i + 1] + " to Map"); - headers.put(argv[i], argv[i + 1]); - i++; - } - - ConnectionFactory factory = new ConnectionFactory(); - factory.setHost("localhost"); - Connection connection = factory.newConnection(); - Channel channel = connection.createChannel(); - - channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.HEADERS); - - AMQP.BasicProperties.Builder builder = new AMQP.BasicProperties.Builder(); - - // MessageProperties.PERSISTENT_TEXT_PLAIN is a static instance of AMQP.BasicProperties - // that contains a delivery mode and a priority. So we pass them to the builder. - builder.deliveryMode(MessageProperties.PERSISTENT_TEXT_PLAIN.getDeliveryMode()); - builder.priority(MessageProperties.PERSISTENT_TEXT_PLAIN.getPriority()); - - // Add the headers to the builder. - builder.headers(headers); - - // Use the builder to create the BasicProperties object. - AMQP.BasicProperties theProps = builder.build(); - - // Now we add the headers. This example only uses string headers, but they can also be integers - channel.basicPublish(EXCHANGE_NAME, routingKey, theProps, message.getBytes("UTF-8")); - System.out.println(" [x] Sent message: '" + message + "'"); - } -} - diff --git a/java8-maven/src/main/java/EmitLogTopic.java b/java8-maven/src/main/java/EmitLogTopic.java deleted file mode 100644 index cf5f2697..00000000 --- a/java8-maven/src/main/java/EmitLogTopic.java +++ /dev/null @@ -1,47 +0,0 @@ -import com.rabbitmq.client.BuiltinExchangeType; -import com.rabbitmq.client.ConnectionFactory; -import com.rabbitmq.client.Connection; -import com.rabbitmq.client.Channel; - -import java.util.Arrays; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -public class EmitLogTopic { - - private static final String EXCHANGE_NAME = "topic_logs"; - - public static void main(String[] argv) { - - ConnectionFactory factory = new ConnectionFactory(); - factory.setHost("localhost"); - try (Connection connection = factory.newConnection(); - Channel channel = connection.createChannel()) { - - - channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.TOPIC); - - String routingKey = getRouting(argv); - String message = getMessage(argv); - - channel.basicPublish(EXCHANGE_NAME, routingKey, null, message.getBytes("UTF-8")); - System.out.println(" [x] Sent '" + routingKey + "':'" + message + "'"); - - } catch (Exception e) { - e.printStackTrace(); - } - } - - private static String getRouting(String[] strings) { - if (strings.length < 1) - return "anonymous.info"; - return strings[0]; - } - - private static String getMessage(String[] strings) { - if (strings.length < 2) - return "Hello World!"; - return String.join(" ", Arrays.copyOfRange(strings, 1, strings.length)); - } -} - diff --git a/java8-maven/src/main/java/NewTask.java b/java8-maven/src/main/java/NewTask.java deleted file mode 100644 index 868067ea..00000000 --- a/java8-maven/src/main/java/NewTask.java +++ /dev/null @@ -1,36 +0,0 @@ -import com.rabbitmq.client.Channel; -import com.rabbitmq.client.Connection; -import com.rabbitmq.client.ConnectionFactory; -import com.rabbitmq.client.MessageProperties; - -import java.util.stream.Collectors; -import java.util.stream.Stream; - -public class NewTask { - - private static final String TASK_QUEUE_NAME = "task_queue"; - - public static void main(String[] argv) throws Exception { - ConnectionFactory factory = new ConnectionFactory(); - factory.setHost("localhost"); - try (Connection connection = factory.newConnection(); - Channel channel = connection.createChannel()) { - - channel.queueDeclare(TASK_QUEUE_NAME, true, false, false, null); - - String message = getMessage(argv); - - channel.basicPublish("", TASK_QUEUE_NAME, - MessageProperties.PERSISTENT_TEXT_PLAIN, - message.getBytes("UTF-8")); - System.out.println(" [x] Sent '" + message + "'"); - } - } - - private static String getMessage(String[] strings) { - if (strings.length < 1) - return "Hello World!"; - return String.join(" ", strings); - } - -} diff --git a/java8-maven/src/main/java/RPCClient.java b/java8-maven/src/main/java/RPCClient.java deleted file mode 100644 index 1eafea05..00000000 --- a/java8-maven/src/main/java/RPCClient.java +++ /dev/null @@ -1,72 +0,0 @@ -import com.rabbitmq.client.ConnectionFactory; -import com.rabbitmq.client.Connection; -import com.rabbitmq.client.Channel; -import com.rabbitmq.client.DefaultConsumer; -import com.rabbitmq.client.AMQP; -import com.rabbitmq.client.Envelope; - -import java.io.IOException; -import java.util.UUID; -import java.util.concurrent.ArrayBlockingQueue; -import java.util.concurrent.BlockingQueue; -import java.util.concurrent.TimeoutException; - -public class RPCClient implements AutoCloseable { - - private Connection connection; - private Channel channel; - private String requestQueueName = "rpc_queue"; - private String replyQueueName; - - public RPCClient() throws IOException, TimeoutException { - ConnectionFactory factory = new ConnectionFactory(); - factory.setHost("localhost"); - - connection = factory.newConnection(); - channel = connection.createChannel(); - - replyQueueName = channel.queueDeclare().getQueue(); - } - - public String call(String message) throws IOException, InterruptedException { - final String corrId = UUID.randomUUID().toString(); - - AMQP.BasicProperties props = new AMQP.BasicProperties - .Builder() - .correlationId(corrId) - .replyTo(replyQueueName) - .build(); - - channel.basicPublish("", requestQueueName, props, message.getBytes("UTF-8")); - - final BlockingQueue response = new ArrayBlockingQueue(1); - - channel.basicConsume(replyQueueName, true, new DefaultConsumer(channel) { - @Override - public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException { - if (properties.getCorrelationId().equals(corrId)) { - response.offer(new String(body, "UTF-8")); - } - } - }); - - return response.take(); - } - - public void close() throws IOException { - connection.close(); - } - - public static void main(String[] argv) { - try (RPCClient fibonacciRpc = new RPCClient()) { - System.out.println(" [x] Requesting fib(30)"); - - final String response = fibonacciRpc.call("30"); - System.out.println(" [.] Got '" + response + "'"); - - } catch (IOException | TimeoutException | InterruptedException e) { - e.printStackTrace(); - } - } -} - diff --git a/java8-maven/src/main/java/RPCServer.java b/java8-maven/src/main/java/RPCServer.java deleted file mode 100644 index a9a76bd1..00000000 --- a/java8-maven/src/main/java/RPCServer.java +++ /dev/null @@ -1,80 +0,0 @@ -import com.rabbitmq.client.ConnectionFactory; -import com.rabbitmq.client.Connection; -import com.rabbitmq.client.Channel; -import com.rabbitmq.client.Consumer; -import com.rabbitmq.client.DefaultConsumer; -import com.rabbitmq.client.AMQP; -import com.rabbitmq.client.Envelope; - -import java.io.IOException; -import java.util.concurrent.TimeoutException; - -public class RPCServer { - - private static final String RPC_QUEUE_NAME = "rpc_queue"; - - private static int fib(int n) { - if (n == 0) return 0; - if (n == 1) return 1; - return fib(n - 1) + fib(n - 2); - } - - public static void main(String[] argv) { - ConnectionFactory factory = new ConnectionFactory(); - factory.setHost("localhost"); - - - try (Connection connection = factory.newConnection(); - Channel channel = connection.createChannel()) { - - channel.queueDeclare(RPC_QUEUE_NAME, false, false, false, null); - - channel.basicQos(1); - - System.out.println(" [x] Awaiting RPC requests"); - - Consumer consumer = new DefaultConsumer(channel) { - @Override - public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException { - AMQP.BasicProperties replyProps = new AMQP.BasicProperties - .Builder() - .correlationId(properties.getCorrelationId()) - .build(); - - String response = ""; - - try { - String message = new String(body, "UTF-8"); - int n = Integer.parseInt(message); - - System.out.println(" [.] fib(" + message + ")"); - response += fib(n); - } catch (RuntimeException e) { - System.out.println(" [.] " + e.toString()); - } finally { - channel.basicPublish("", properties.getReplyTo(), replyProps, response.getBytes("UTF-8")); - channel.basicAck(envelope.getDeliveryTag(), false); - // RabbitMq consumer worker thread notifies the RPC server owner thread - synchronized (this) { - this.notify(); - } - } - } - }; - - channel.basicConsume(RPC_QUEUE_NAME, false, consumer); - // Wait and be prepared to consume the message from RPC client. - while (true) { - synchronized (consumer) { - try { - consumer.wait(); - } catch (InterruptedException e) { - e.printStackTrace(); - } - } - } - } catch (IOException | TimeoutException e) { - e.printStackTrace(); - } - } -} \ No newline at end of file diff --git a/java8-maven/src/main/java/ReceiveLogHeader.java b/java8-maven/src/main/java/ReceiveLogHeader.java deleted file mode 100644 index b6a4eda5..00000000 --- a/java8-maven/src/main/java/ReceiveLogHeader.java +++ /dev/null @@ -1,62 +0,0 @@ -import com.rabbitmq.client.*; - -import java.io.IOException; -import java.util.HashMap; -import java.util.Map; - -public class ReceiveLogHeader { - private static final String EXCHANGE_NAME = "header_test"; - - public static void main(String[] argv) throws Exception { - if (argv.length < 1) { - System.err.println("Usage: ReceiveLogsHeader queueName [headers]..."); - System.exit(1); - } - - ConnectionFactory factory = new ConnectionFactory(); - factory.setHost("localhost"); - Connection connection = factory.newConnection(); - Channel channel = connection.createChannel(); - - channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.HEADERS); - - // The API requires a routing key, but in fact if you are using a header exchange the - // value of the routing key is not used in the routing. You can receive information - // from the sender here as the routing key is still available in the received message. - String routingKeyFromUser = "ourTestRoutingKey"; - - // Argument processing: the first arg is the local queue name, the rest are - // key value pairs for headers. - String queueInputName = argv[0]; - - // The map for the headers. - Map headers = new HashMap(); - - // The rest of the arguments are key value header pairs. For the purpose of this - // example, we are assuming they are all strings, but that is not required by RabbitMQ - // Note that when you run this code you should include the x-match header on the command - // line. Example: - // java -cp $CP ReceiveLogsHeader testQueue1 x-match any header1 value1 - for (int i = 1; i < argv.length; i++) { - headers.put(argv[i], argv[i + 1]); - System.out.println("Binding header " + argv[i] + " and value " + argv[i + 1] + " to queue " + queueInputName); - i++; - } - - String queueName = channel.queueDeclare(queueInputName, true, false, false, null).getQueue(); - channel.queueBind(queueName, EXCHANGE_NAME, routingKeyFromUser, headers); - - System.out.println(" [*] Waiting for messages. To exit press CTRL+C"); - - Consumer consumer = new DefaultConsumer(channel) { - @Override - public void handleDelivery(String consumerTag, Envelope envelope, - AMQP.BasicProperties properties, byte[] body) throws IOException { - String message = new String(body, "UTF-8"); - System.out.println(" [x] Received '" + envelope.getRoutingKey() + "':'" + message + "'"); - } - }; - channel.basicConsume(queueName, true, consumer); - } -} - diff --git a/java8-maven/src/main/java/ReceiveLogs.java b/java8-maven/src/main/java/ReceiveLogs.java deleted file mode 100644 index fd807a6b..00000000 --- a/java8-maven/src/main/java/ReceiveLogs.java +++ /dev/null @@ -1,31 +0,0 @@ -import com.rabbitmq.client.*; - -import java.io.IOException; - -public class ReceiveLogs { - private static final String EXCHANGE_NAME = "logs"; - - public static void main(String[] argv) throws Exception { - ConnectionFactory factory = new ConnectionFactory(); - factory.setHost("localhost"); - Connection connection = factory.newConnection(); - Channel channel = connection.createChannel(); - - channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.FANOUT); - String queueName = channel.queueDeclare().getQueue(); - channel.queueBind(queueName, EXCHANGE_NAME, ""); - - System.out.println(" [*] Waiting for messages. To exit press CTRL+C"); - - Consumer consumer = new DefaultConsumer(channel) { - @Override - public void handleDelivery(String consumerTag, Envelope envelope, - AMQP.BasicProperties properties, byte[] body) throws IOException { - String message = new String(body, "UTF-8"); - System.out.println(" [x] Received '" + message + "'"); - } - }; - channel.basicConsume(queueName, true, consumer); - } -} - diff --git a/java8-maven/src/main/java/ReceiveLogsDirect.java b/java8-maven/src/main/java/ReceiveLogsDirect.java deleted file mode 100644 index 0a13f8d1..00000000 --- a/java8-maven/src/main/java/ReceiveLogsDirect.java +++ /dev/null @@ -1,39 +0,0 @@ -import com.rabbitmq.client.*; - -import java.io.IOException; - -public class ReceiveLogsDirect { - - private static final String EXCHANGE_NAME = "direct_logs"; - - public static void main(String[] argv) throws Exception { - ConnectionFactory factory = new ConnectionFactory(); - factory.setHost("localhost"); - Connection connection = factory.newConnection(); - Channel channel = connection.createChannel(); - - channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.DIRECT); - String queueName = channel.queueDeclare().getQueue(); - - if (argv.length < 1){ - System.err.println("Usage: ReceiveLogsDirect [info] [warning] [error]"); - System.exit(1); - } - - for(String severity : argv){ - channel.queueBind(queueName, EXCHANGE_NAME, severity); - } - System.out.println(" [*] Waiting for messages. To exit press CTRL+C"); - - Consumer consumer = new DefaultConsumer(channel) { - @Override - public void handleDelivery(String consumerTag, Envelope envelope, - AMQP.BasicProperties properties, byte[] body) throws IOException { - String message = new String(body, "UTF-8"); - System.out.println(" [x] Received '" + envelope.getRoutingKey() + "':'" + message + "'"); - } - }; - channel.basicConsume(queueName, true, consumer); - } -} - diff --git a/java8-maven/src/main/java/ReceiveLogsTopic.java b/java8-maven/src/main/java/ReceiveLogsTopic.java deleted file mode 100644 index f100d34f..00000000 --- a/java8-maven/src/main/java/ReceiveLogsTopic.java +++ /dev/null @@ -1,40 +0,0 @@ -import com.rabbitmq.client.*; - -import java.io.IOException; - -public class ReceiveLogsTopic { - - private static final String EXCHANGE_NAME = "topic_logs"; - - public static void main(String[] argv) throws Exception { - ConnectionFactory factory = new ConnectionFactory(); - factory.setHost("localhost"); - Connection connection = factory.newConnection(); - Channel channel = connection.createChannel(); - - channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.TOPIC); - String queueName = channel.queueDeclare().getQueue(); - - if (argv.length < 1) { - System.err.println("Usage: ReceiveLogsTopic [binding_key]..."); - System.exit(1); - } - - for (String bindingKey : argv) { - channel.queueBind(queueName, EXCHANGE_NAME, bindingKey); - } - - System.out.println(" [*] Waiting for messages. To exit press CTRL+C"); - - Consumer consumer = new DefaultConsumer(channel) { - @Override - public void handleDelivery(String consumerTag, Envelope envelope, - AMQP.BasicProperties properties, byte[] body) throws IOException { - String message = new String(body, "UTF-8"); - System.out.println(" [x] Received '" + envelope.getRoutingKey() + "':'" + message + "'"); - } - }; - channel.basicConsume(queueName, true, consumer); - } -} - diff --git a/java8-maven/src/main/java/Recv.java b/java8-maven/src/main/java/Recv.java deleted file mode 100644 index 355397c5..00000000 --- a/java8-maven/src/main/java/Recv.java +++ /dev/null @@ -1,28 +0,0 @@ -import com.rabbitmq.client.*; - -import java.io.IOException; - -public class Recv { - - private final static String QUEUE_NAME = "hello"; - - public static void main(String[] argv) throws Exception { - ConnectionFactory factory = new ConnectionFactory(); - factory.setHost("localhost"); - Connection connection = factory.newConnection(); - Channel channel = connection.createChannel(); - - channel.queueDeclare(QUEUE_NAME, false, false, false, null); - System.out.println(" [*] Waiting for messages. To exit press CTRL+C"); - - Consumer consumer = new DefaultConsumer(channel) { - @Override - public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) - throws IOException { - String message = new String(body, "UTF-8"); - System.out.println(" [x] Received '" + message + "'"); - } - }; - channel.basicConsume(QUEUE_NAME, true, consumer); - } -} diff --git a/java8-maven/src/main/java/Send.java b/java8-maven/src/main/java/Send.java deleted file mode 100644 index e0d470c5..00000000 --- a/java8-maven/src/main/java/Send.java +++ /dev/null @@ -1,21 +0,0 @@ -import com.rabbitmq.client.Channel; -import com.rabbitmq.client.Connection; -import com.rabbitmq.client.ConnectionFactory; - -public class Send { - - private final static String QUEUE_NAME = "hello"; - - public static void main(String[] argv) throws Exception { - ConnectionFactory factory = new ConnectionFactory(); - factory.setHost("localhost"); - try (Connection connection = factory.newConnection(); - Channel channel = connection.createChannel()) { - - channel.queueDeclare(QUEUE_NAME, false, false, false, null); - String message = "Hello World!"; - channel.basicPublish("", QUEUE_NAME, null, message.getBytes("UTF-8")); - System.out.println(" [x] Sent '" + message + "'"); - } - } -} diff --git a/java8-maven/src/main/java/Worker.java b/java8-maven/src/main/java/Worker.java deleted file mode 100644 index ca679d93..00000000 --- a/java8-maven/src/main/java/Worker.java +++ /dev/null @@ -1,49 +0,0 @@ -import com.rabbitmq.client.*; - -import java.io.IOException; - -public class Worker { - - private static final String TASK_QUEUE_NAME = "task_queue"; - - public static void main(String[] argv) throws Exception { - ConnectionFactory factory = new ConnectionFactory(); - factory.setHost("localhost"); - final Connection connection = factory.newConnection(); - final Channel channel = connection.createChannel(); - - channel.queueDeclare(TASK_QUEUE_NAME, true, false, false, null); - System.out.println(" [*] Waiting for messages. To exit press CTRL+C"); - - channel.basicQos(1); - - final Consumer consumer = new DefaultConsumer(channel) { - @Override - public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException { - String message = new String(body, "UTF-8"); - - System.out.println(" [x] Received '" + message + "'"); - try { - doWork(message); - } finally { - System.out.println(" [x] Done"); - channel.basicAck(envelope.getDeliveryTag(), false); - } - } - }; - channel.basicConsume(TASK_QUEUE_NAME, false, consumer); - } - - private static void doWork(String task) { - for (char ch : task.toCharArray()) { - if (ch == '.') { - try { - Thread.sleep(1000); - } catch (InterruptedException _ignored) { - Thread.currentThread().interrupt(); - } - } - } - } -} - diff --git a/javascript-nodejs-stream/.gitignore b/javascript-nodejs-stream/.gitignore new file mode 100644 index 00000000..30bc1627 --- /dev/null +++ b/javascript-nodejs-stream/.gitignore @@ -0,0 +1 @@ +/node_modules \ No newline at end of file diff --git a/javascript-nodejs-stream/README.md b/javascript-nodejs-stream/README.md new file mode 100644 index 00000000..dde542f2 --- /dev/null +++ b/javascript-nodejs-stream/README.md @@ -0,0 +1,31 @@ +# Node.js code for RabbitMQ stream tutorials + +Here you can find JavaScript (Node) code examples from [RabbitMQ tutorials](https://www.rabbitmq.com/getstarted.html) related to the [Stream plugin](https://www.rabbitmq.com/docs/stream). + +To successfully use the examples you will need a running RabbitMQ server with the [stream plugin enabled](https://www.rabbitmq.com/docs/stream#enabling-plugin). + +See [First Application With RabbitMQ Streams](https://www.rabbitmq.com/blog/2021/07/19/rabbitmq-streams-first-application), [Stream plugin documentation](https://www.rabbitmq.com/docs/stream) and [how to preconfigure plugins](https://www.rabbitmq.com/docs/plugins#enabled-plugins-file). + +## Requirements + +Apart from [Node.js](https://nodejs.org/en/download/), these examples use the [`rabbitmq-stream-js-client`](https://github.com/coders51/rabbitmq-stream-js-client) client library. + +## Code + +Code examples are executed via `npm`: + +[Tutorial one: "Hello World!"](https://www.rabbitmq.com/tutorials/tutorial-one-javascript-stream): + +```shell +npm run send +npm run receive +``` + +[Tutorial two: Offset Tracking](https://www.rabbitmq.com/tutorials/tutorial-two-javascript-stream): + +```shell +npm run offset-tracking-publish +npm run offset-tracking-receive +``` + +To learn more, see [`coders51/rabbitmq-stream-js-client`](https://github.com/coders51/rabbitmq-stream-js-client). diff --git a/javascript-nodejs-stream/offset_tracking_receive.js b/javascript-nodejs-stream/offset_tracking_receive.js new file mode 100644 index 00000000..9663d3b9 --- /dev/null +++ b/javascript-nodejs-stream/offset_tracking_receive.js @@ -0,0 +1,59 @@ +const rabbit = require("rabbitmq-stream-js-client"); + +const sleep = (ms) => new Promise((r) => setTimeout(r, ms)); + +async function main() { + console.log("Connecting..."); + const client = await rabbit.connect({ + hostname: "localhost", + port: 5552, + username: "guest", + password: "guest", + vhost: "/", + }); + + console.log("Making sure the stream exists..."); + const streamName = "stream-offset-tracking-javascript"; + await client.createStream({ stream: streamName, arguments: {} }); + + const consumerRef = "offset-tracking-tutorial"; + let firstOffset = undefined; + let offsetSpecification = rabbit.Offset.first(); + try { + const offset = await client.queryOffset({ reference: consumerRef, stream: streamName }); + offsetSpecification = rabbit.Offset.offset(offset + 1n); + } catch (e) {} + + let lastOffset = offsetSpecification.value; + let messageCount = 0; + const consumer = await client.declareConsumer( + { stream: streamName, offset: offsetSpecification, consumerRef }, + async (message) => { + messageCount++; + if (!firstOffset && messageCount === 1) { + firstOffset = message.offset; + console.log("First message received"); + } + if (messageCount % 10 === 0) { + await consumer.storeOffset(message.offset); + } + if (message.content.toString() === "marker") { + console.log("Marker found"); + lastOffset = message.offset; + await consumer.storeOffset(message.offset); + await consumer.close(true); + } + } + ); + + console.log(`Start consuming...`); + await sleep(2000); + console.log(`Done consuming, first offset was ${firstOffset}, last offset was ${lastOffset}`); +} + +main() + .then(async () => process.exit(0)) + .catch((res) => { + console.log("Error while receiving message!", res); + process.exit(-1); + }); diff --git a/javascript-nodejs-stream/offset_tracking_send.js b/javascript-nodejs-stream/offset_tracking_send.js new file mode 100644 index 00000000..52113878 --- /dev/null +++ b/javascript-nodejs-stream/offset_tracking_send.js @@ -0,0 +1,36 @@ +const rabbit = require("rabbitmq-stream-js-client"); + +async function main() { + console.log("Connecting..."); + const client = await rabbit.connect({ + vhost: "/", + port: 5552, + hostname: "localhost", + username: "guest", + password: "guest", + }); + + console.log("Making sure the stream exists..."); + const streamName = "stream-offset-tracking-javascript"; + await client.createStream({ stream: streamName, arguments: {} }); + + console.log("Creating the publisher..."); + const publisher = await client.declarePublisher({ stream: streamName }); + + const messageCount = 100; + console.log(`Publishing ${messageCount} messages`); + for (let i = 0; i < messageCount; i++) { + const body = i === messageCount - 1 ? "marker" : `hello ${i}`; + await publisher.send(Buffer.from(body)); + } + + console.log("Closing the connection..."); + await client.close(); +} + +main() + .then(() => console.log("done!")) + .catch((res) => { + console.log("Error in publishing message!", res); + process.exit(-1); + }); diff --git a/javascript-nodejs-stream/package-lock.json b/javascript-nodejs-stream/package-lock.json new file mode 100644 index 00000000..b0effd4e --- /dev/null +++ b/javascript-nodejs-stream/package-lock.json @@ -0,0 +1,84 @@ +{ + "name": "rabbitmq-stream-node-tutorial", + "version": "1.0.0", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "name": "rabbitmq-stream-node-tutorial", + "version": "1.0.0", + "dependencies": { + "rabbitmq-stream-js-client": "^0.4.2" + } + }, + "node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/rabbitmq-stream-js-client": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/rabbitmq-stream-js-client/-/rabbitmq-stream-js-client-0.4.2.tgz", + "integrity": "sha512-/hcTDZJ8oUnVZoWFwGbD278qZ7F2Yb4mSDmzUQ1kpZh+82C0xiCDa0+nYotGauwZXsXddjnyc5C6q6qaP2OU1A==", + "dependencies": { + "semver": "^7.5.4" + } + }, + "node_modules/semver": { + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + } + }, + "dependencies": { + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "requires": { + "yallist": "^4.0.0" + } + }, + "rabbitmq-stream-js-client": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/rabbitmq-stream-js-client/-/rabbitmq-stream-js-client-0.4.2.tgz", + "integrity": "sha512-/hcTDZJ8oUnVZoWFwGbD278qZ7F2Yb4mSDmzUQ1kpZh+82C0xiCDa0+nYotGauwZXsXddjnyc5C6q6qaP2OU1A==", + "requires": { + "semver": "^7.5.4" + } + }, + "semver": { + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", + "requires": { + "lru-cache": "^6.0.0" + } + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + } + } +} diff --git a/javascript-nodejs-stream/package.json b/javascript-nodejs-stream/package.json new file mode 100644 index 00000000..95689ee4 --- /dev/null +++ b/javascript-nodejs-stream/package.json @@ -0,0 +1,14 @@ +{ + "name": "rabbitmq-stream-node-tutorial", + "version": "1.0.0", + "description": "Tutorial for the nodejs RabbitMQ stream client", + "scripts": { + "offset-tracking-publish": "node offset_tracking_send.js", + "offset-tracking-receive": "node offset_tracking_receive.js", + "send": "node send.js", + "receive": "node receive.js" + }, + "dependencies": { + "rabbitmq-stream-js-client": "^0.4.2" + } +} diff --git a/javascript-nodejs-stream/receive.js b/javascript-nodejs-stream/receive.js new file mode 100644 index 00000000..83ff1377 --- /dev/null +++ b/javascript-nodejs-stream/receive.js @@ -0,0 +1,33 @@ +const rabbit = require("rabbitmq-stream-js-client") + +async function main() { + const streamName = "hello-nodejs-stream" + + console.log("Connecting..."); + const client = await rabbit.connect({ + hostname: "localhost", + port: 5552, + username: "guest", + password: "guest", + vhost: "/", + }) + + console.log("Making sure the stream exists..."); + const streamSizeRetention = 5 * 1e9 + await client.createStream({ stream: streamName, arguments: { "max-length-bytes": streamSizeRetention } }); + + console.log("Declaring the consumer with offset..."); + await client.declareConsumer({ stream: streamName, offset: rabbit.Offset.first() }, (message) => { + console.log(`Received message ${message.content.toString()}`) + }) + +} + +main() + .then(async () => { + await new Promise(function () { }) + }) + .catch((res) => { + console.log("Error while receiving message!", res) + process.exit(-1) + }) diff --git a/javascript-nodejs-stream/send.js b/javascript-nodejs-stream/send.js new file mode 100644 index 00000000..b86e02e0 --- /dev/null +++ b/javascript-nodejs-stream/send.js @@ -0,0 +1,34 @@ +const rabbit = require("rabbitmq-stream-js-client"); + +async function main() { + const streamName = "hello-nodejs-stream"; + + console.log("Connecting..."); + const client = await rabbit.connect({ + vhost: "/", + port: 5552, + hostname: "localhost", + username: "guest", + password: "guest", + }); + + console.log("Making sure the stream exists..."); + const streamSizeRetention = 5 * 1e9 + await client.createStream({ stream: streamName, arguments: { "max-length-bytes": streamSizeRetention } }); + + console.log("Creating the publisher..."); + const publisher = await client.declarePublisher({ stream: streamName }); + + console.log("Sending a message..."); + await publisher.send(Buffer.from("Test message")); + + console.log("Closing the connection..."); + await client.close(); +} + +main() + .then(() => console.log("done!")) + .catch((res) => { + console.log("Error in publishing message!", res); + process.exit(-1); + }); diff --git a/javascript-nodejs/README.md b/javascript-nodejs/README.md index d1e76043..5491b45f 100644 --- a/javascript-nodejs/README.md +++ b/javascript-nodejs/README.md @@ -1,7 +1,7 @@ # Node.js code for RabbitMQ tutorials Here you can find JavaScript (Node) code examples from [RabbitMQ -tutorials](http://www.rabbitmq.com/getstarted.html). +tutorials](https://www.rabbitmq.com/getstarted.html). To successfully use the examples you will need a running RabbitMQ server. @@ -9,47 +9,59 @@ To successfully use the examples you will need a running RabbitMQ server. ### Node.js -You need [Node.js](https://nodejs.org/en/download/) and [amqp.node](https://github.com/squaremo/amqp.node) +You need [Node.js](https://nodejs.org/en/download/) and [amqplib](https://github.com/amqp-node/amqplib) to run these tutorials. ### Client Library -To install `amqp.node` using npm: +To install `amqplib` using npm: - npm install amqplib -g +``` shell +npm install amqplib -g +``` ## Code -[Tutorial one: "Hello World!"](http://www.rabbitmq.com/tutorials/tutorial-one-javascript.html): +[Tutorial one: "Hello World!"](https://www.rabbitmq.com/tutorials/tutorial-one-javascript.html): - node src/send.js - node src/receive.js +``` shell +node src/send.js +node src/receive.js +``` +[Tutorial two: Work Queues](https://www.rabbitmq.com/tutorials/tutorial-two-javascript.html): -[Tutorial two: Work Queues](http://www.rabbitmq.com/tutorials/tutorial-two-javascript.html): +``` shell +node src/new_task.js "A very hard task which takes two seconds.." +node src/worker.js +``` - node src/new_task.js "A very hard task which takes two seconds.." - node src/worker.js +[Tutorial three: Publish/Subscribe](https://www.rabbitmq.com/tutorials/tutorial-three-javascript.html) +``` shell +node src/receive_logs.js +node src/emit_log.js "info: This is the log message" +``` -[Tutorial three: Publish/Subscribe](http://www.rabbitmq.com/tutorials/tutorial-three-javascript.html) +[Tutorial four: Routing](https://www.rabbitmq.com/tutorials/tutorial-four-javascript.html): - node src/receive_logs.js - node src/emit_log.js "info: This is the log message" +``` shell +node src/receive_logs_direct.js info +node src/emit_log_direct.js info "The message" +``` -[Tutorial four: Routing](http://www.rabbitmq.com/tutorials/tutorial-four-javascript.html): - node src/receive_logs_direct.js info - node src/emit_log_direct.js info "The message" +[Tutorial five: Topics](https://www.rabbitmq.com/tutorials/tutorial-five-javascript.html): +``` shell +node src/receive_logs_topic.js "*.rabbit" +node src/emit_log_topic.js red.rabbit Hello +``` -[Tutorial five: Topics](http://www.rabbitmq.com/tutorials/tutorial-five-javascript.html): +[Tutorial six: RPC](https://www.rabbitmq.com/tutorials/tutorial-six-javascript.html): - node src/receive_logs_topic.js "*.rabbit" - node src/emit_log_topic.js red.rabbit Hello - -[Tutorial six: RPC](http://www.rabbitmq.com/tutorials/tutorial-six-javascript.html): - - node src/rpc_server.js - node src/rpc_client.js +``` shell +node src/rpc_server.js +node src/rpc_client.js 30 +``` diff --git a/javascript-nodejs/package-lock.json b/javascript-nodejs/package-lock.json index 85d71454..14f5846f 100644 --- a/javascript-nodejs/package-lock.json +++ b/javascript-nodejs/package-lock.json @@ -1,68 +1,658 @@ { "name": "rabbitmq-node-tutorial", "version": "1.0.0", - "lockfileVersion": 1, + "lockfileVersion": 3, "requires": true, - "dependencies": { - "amqplib": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/amqplib/-/amqplib-0.3.2.tgz", - "integrity": "sha1-532Ps4QuvK54zxyTDEkNHpVPQpc=", - "requires": { - "bitsyntax": "0.0.4", - "buffer-more-ints": "0.0.2", - "readable-stream": "1.1.14", - "when": "3.6.4" - } - }, - "bitsyntax": { - "version": "0.0.4", - "resolved": "https://registry.npmjs.org/bitsyntax/-/bitsyntax-0.0.4.tgz", - "integrity": "sha1-6xDMb4K4xJDj6FaY8H6D1G4MuoI=", - "requires": { - "buffer-more-ints": "0.0.2" - } - }, - "buffer-more-ints": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/buffer-more-ints/-/buffer-more-ints-0.0.2.tgz", - "integrity": "sha1-JrOIXRD6E9t/wBquOquHAZngEkw=" - }, - "core-util-is": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" - }, - "inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" - }, - "isarray": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", - "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=" - }, - "readable-stream": { - "version": "1.1.14", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", - "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", - "requires": { - "core-util-is": "1.0.2", - "inherits": "2.0.3", - "isarray": "0.0.1", - "string_decoder": "0.10.31" - } - }, - "string_decoder": { - "version": "0.10.31", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", - "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=" - }, - "when": { - "version": "3.6.4", - "resolved": "https://registry.npmjs.org/when/-/when-3.6.4.tgz", - "integrity": "sha1-RztRfsFZ4rhQBUl6E5g/CVQS404=" + "packages": { + "": { + "name": "rabbitmq-node-tutorial", + "version": "1.0.0", + "dependencies": { + "amqplib": "0.10.9", + "js-beautify": "^1.15.4", + "url-parse": ">=1.5.9" + } + }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "license": "ISC", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@one-ini/wasm": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@one-ini/wasm/-/wasm-0.1.1.tgz", + "integrity": "sha512-XuySG1E38YScSJoMlqovLru4KTUNSjgVTIjyh7qMX6aNN5HY5Ct5LhRJdxO79JtTzKfzV/bnWpz+zquYrISsvw==", + "license": "MIT" + }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/abbrev": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-2.0.0.tgz", + "integrity": "sha512-6/mh1E2u2YgEsCHdY0Yx5oW+61gZU+1vXaoiHHrpKeuRNNgFvS+/jrwHiQhB5apAf5oB7UB7E19ol2R2LKH8hQ==", + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/amqplib": { + "version": "0.10.9", + "resolved": "https://registry.npmjs.org/amqplib/-/amqplib-0.10.9.tgz", + "integrity": "sha512-jwSftI4QjS3mizvnSnOrPGYiUnm1vI2OP1iXeOUz5pb74Ua0nbf6nPyyTzuiCLEE3fMpaJORXh2K/TQ08H5xGA==", + "license": "MIT", + "dependencies": { + "buffer-more-ints": "~1.0.0", + "url-parse": "~1.5.10" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/balanced-match": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.3.tgz", + "integrity": "sha512-1pHv8LX9CpKut1Zp4EXey7Z8OfH11ONNH6Dhi2WDUt31VVZFXZzKwXcysBgqSumFCmR+0dqjMK5v5JiFHzi0+g==", + "license": "MIT", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/brace-expansion": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz", + "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==", + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/buffer-more-ints": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/buffer-more-ints/-/buffer-more-ints-1.0.0.tgz", + "integrity": "sha512-EMetuGFz5SLsT0QTnXzINh4Ksr+oo4i+UGTXEshiGCQWnsgSs7ZhJ8fzlwQ+OzEMs0MpDAMr1hxnblp5a4vcHg==", + "license": "MIT" + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "license": "MIT" + }, + "node_modules/commander": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz", + "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==", + "license": "MIT", + "engines": { + "node": ">=14" + } + }, + "node_modules/config-chain": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.13.tgz", + "integrity": "sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==", + "license": "MIT", + "dependencies": { + "ini": "^1.3.4", + "proto-list": "~1.2.1" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "license": "MIT" + }, + "node_modules/editorconfig": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/editorconfig/-/editorconfig-1.0.4.tgz", + "integrity": "sha512-L9Qe08KWTlqYMVvMcTIvMAdl1cDUubzRNYL+WfA4bLDMHe4nemKkpmYzkznE1FwLKu0EEmy6obgQKzMJrg4x9Q==", + "license": "MIT", + "dependencies": { + "@one-ini/wasm": "0.1.1", + "commander": "^10.0.0", + "minimatch": "9.0.1", + "semver": "^7.5.3" + }, + "bin": { + "editorconfig": "bin/editorconfig" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "license": "MIT" + }, + "node_modules/foreground-child": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", + "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "license": "ISC" + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "license": "ISC" + }, + "node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "node_modules/js-beautify": { + "version": "1.15.4", + "resolved": "https://registry.npmjs.org/js-beautify/-/js-beautify-1.15.4.tgz", + "integrity": "sha512-9/KXeZUKKJwqCXUdBxFJ3vPh467OCckSBmYDwSK/EtV090K+iMJ7zx2S3HLVDIWFQdqMIsZWbnaGiba18aWhaA==", + "license": "MIT", + "dependencies": { + "config-chain": "^1.1.13", + "editorconfig": "^1.0.4", + "glob": "^10.4.2", + "js-cookie": "^3.0.5", + "nopt": "^7.2.1" + }, + "bin": { + "css-beautify": "js/bin/css-beautify.js", + "html-beautify": "js/bin/html-beautify.js", + "js-beautify": "js/bin/js-beautify.js" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/js-cookie": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-3.0.5.tgz", + "integrity": "sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==", + "license": "MIT", + "engines": { + "node": ">=14" + } + }, + "node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "license": "ISC" + }, + "node_modules/minimatch": { + "version": "10.2.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.4.tgz", + "integrity": "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==", + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/minipass": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", + "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/nopt": { + "version": "7.2.1", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-7.2.1.tgz", + "integrity": "sha512-taM24ViiimT/XntxbPyJQzCG+p4EKOpgD3mxFwW38mGjVUrfERQOeY4EDHjdnptttfHuHQXFx+lTP08Q+mLa/w==", + "license": "ISC", + "dependencies": { + "abbrev": "^2.0.0" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "license": "BlueOak-1.0.0" + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/proto-list": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz", + "integrity": "sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==", + "license": "ISC" + }, + "node_modules/querystringify": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", + "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==", + "license": "MIT" + }, + "node_modules/requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", + "license": "MIT" + }, + "node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" + }, + "node_modules/string-width-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", + "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/url-parse": { + "version": "1.5.10", + "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", + "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", + "license": "MIT", + "dependencies": { + "querystringify": "^2.1.1", + "requires-port": "^1.0.0" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" + }, + "node_modules/wrap-ansi-cjs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } } } } diff --git a/javascript-nodejs/package.json b/javascript-nodejs/package.json index e5c72ac2..2c2f71c8 100644 --- a/javascript-nodejs/package.json +++ b/javascript-nodejs/package.json @@ -1,8 +1,13 @@ { "name": "rabbitmq-node-tutorial", "version": "1.0.0", - "description": "RabbitMQ amqp.node tutorial ", + "description": "RabbitMQ amqplib tutorial ", "dependencies": { - "amqplib": "^0.3.2" + "amqplib": "0.10.9", + "js-beautify": "^1.15.4", + "url-parse": ">=1.5.9" + }, + "overrides": { + "minimatch": ">=10.2.1" } } diff --git a/javascript-nodejs/src/emit_log.js b/javascript-nodejs/src/emit_log.js index fcb8ccba..5ebadac2 100755 --- a/javascript-nodejs/src/emit_log.js +++ b/javascript-nodejs/src/emit_log.js @@ -1,16 +1,24 @@ #!/usr/bin/env node -var amqp = require('amqplib/callback_api'); +const amqp = require('amqplib'); -amqp.connect('amqp://localhost', function(err, conn) { - conn.createChannel(function(err, ch) { - var ex = 'logs'; - var msg = process.argv.slice(2).join(' ') || 'Hello World!'; +async function main() { + const connection = await amqp.connect('amqp://localhost'); + const channel = await connection.createChannel(); - ch.assertExchange(ex, 'fanout', {durable: false}); - ch.publish(ex, '', new Buffer(msg)); + const exchange = 'logs'; + const msg = process.argv.slice(2).join(' ') || 'Hello World!'; + + await channel.assertExchange(exchange, 'fanout', { + durable: false + }); + channel.publish(exchange, '', Buffer.from(msg)); console.log(" [x] Sent %s", msg); - }); - setTimeout(function() { conn.close(); process.exit(0) }, 500); -}); + setTimeout(function() { + connection.close(); + process.exit(0); + }, 500); +} + +main(); diff --git a/javascript-nodejs/src/emit_log_direct.js b/javascript-nodejs/src/emit_log_direct.js index 37abf341..fe7a9532 100755 --- a/javascript-nodejs/src/emit_log_direct.js +++ b/javascript-nodejs/src/emit_log_direct.js @@ -1,18 +1,26 @@ #!/usr/bin/env node -var amqp = require('amqplib/callback_api'); +const amqp = require('amqplib'); -amqp.connect('amqp://localhost', function(err, conn) { - conn.createChannel(function(err, ch) { - var ex = 'direct_logs'; - var args = process.argv.slice(2); - var msg = args.slice(1).join(' ') || 'Hello World!'; - var severity = (args.length > 0) ? args[0] : 'info'; +async function main() { + const connection = await amqp.connect('amqp://localhost'); + const channel = await connection.createChannel(); - ch.assertExchange(ex, 'direct', {durable: false}); - ch.publish(ex, severity, new Buffer(msg)); + const exchange = 'direct_logs'; + const args = process.argv.slice(2); + const msg = args.slice(1).join(' ') || 'Hello World!'; + const severity = (args.length > 0) ? args[0] : 'info'; + + await channel.assertExchange(exchange, 'direct', { + durable: false + }); + channel.publish(exchange, severity, Buffer.from(msg)); console.log(" [x] Sent %s: '%s'", severity, msg); - }); - setTimeout(function() { conn.close(); process.exit(0) }, 500); -}); + setTimeout(function() { + connection.close(); + process.exit(0); + }, 500); +} + +main(); diff --git a/javascript-nodejs/src/emit_log_topic.js b/javascript-nodejs/src/emit_log_topic.js index 2418c107..c27e98be 100755 --- a/javascript-nodejs/src/emit_log_topic.js +++ b/javascript-nodejs/src/emit_log_topic.js @@ -1,18 +1,26 @@ #!/usr/bin/env node -var amqp = require('amqplib/callback_api'); +const amqp = require('amqplib'); -amqp.connect('amqp://localhost', function(err, conn) { - conn.createChannel(function(err, ch) { - var ex = 'topic_logs'; - var args = process.argv.slice(2); - var key = (args.length > 0) ? args[0] : 'anonymous.info'; - var msg = args.slice(1).join(' ') || 'Hello World!'; +async function main() { + const connection = await amqp.connect('amqp://localhost'); + const channel = await connection.createChannel(); - ch.assertExchange(ex, 'topic', {durable: false}); - ch.publish(ex, key, new Buffer(msg)); + const exchange = 'topic_logs'; + const args = process.argv.slice(2); + const key = (args.length > 0) ? args[0] : 'anonymous.info'; + const msg = args.slice(1).join(' ') || 'Hello World!'; + + await channel.assertExchange(exchange, 'topic', { + durable: false + }); + channel.publish(exchange, key, Buffer.from(msg)); console.log(" [x] Sent %s: '%s'", key, msg); - }); - setTimeout(function() { conn.close(); process.exit(0) }, 500); -}); + setTimeout(function() { + connection.close(); + process.exit(0); + }, 500); +} + +main(); diff --git a/javascript-nodejs/src/new_task.js b/javascript-nodejs/src/new_task.js index b6739127..33afec9f 100755 --- a/javascript-nodejs/src/new_task.js +++ b/javascript-nodejs/src/new_task.js @@ -1,15 +1,27 @@ #!/usr/bin/env node -var amqp = require('amqplib/callback_api'); +const amqp = require('amqplib'); -amqp.connect('amqp://localhost', function(err, conn) { - conn.createChannel(function(err, ch) { - var q = 'task_queue'; - var msg = process.argv.slice(2).join(' ') || "Hello World!"; +async function main() { + const connection = await amqp.connect('amqp://localhost'); + const channel = await connection.createChannel(); - ch.assertQueue(q, {durable: true}); - ch.sendToQueue(q, new Buffer(msg), {persistent: true}); + const queue = 'task_queue'; + const msg = process.argv.slice(2).join(' ') || "Hello World!"; + + await channel.assertQueue(queue, { + durable: true, + arguments: { 'x-queue-type': 'quorum' } + }); + channel.sendToQueue(queue, Buffer.from(msg), { + persistent: true + }); console.log(" [x] Sent '%s'", msg); - }); - setTimeout(function() { conn.close(); process.exit(0) }, 500); -}); + + setTimeout(function() { + connection.close(); + process.exit(0); + }, 500); +} + +main(); diff --git a/javascript-nodejs/src/receive.js b/javascript-nodejs/src/receive.js index ef0419b2..5d28fb7a 100755 --- a/javascript-nodejs/src/receive.js +++ b/javascript-nodejs/src/receive.js @@ -1,15 +1,25 @@ #!/usr/bin/env node -var amqp = require('amqplib/callback_api'); - -amqp.connect('amqp://localhost', function(err, conn) { - conn.createChannel(function(err, ch) { - var q = 'hello'; - - ch.assertQueue(q, {durable: false}); - console.log(" [*] Waiting for messages in %s. To exit press CTRL+C", q); - ch.consume(q, function(msg) { - console.log(" [x] Received %s", msg.content.toString()); - }, {noAck: true}); - }); -}); +const amqp = require('amqplib'); + +async function main() { + const connection = await amqp.connect('amqp://localhost'); + const channel = await connection.createChannel(); + + const queue = 'hello'; + + await channel.assertQueue(queue, { + durable: true, + arguments: { 'x-queue-type': 'quorum' } + }); + + console.log(" [*] Waiting for messages in %s. To exit press CTRL+C", queue); + + channel.consume(queue, function(msg) { + console.log(" [x] Received %s", msg.content.toString()); + }, { + noAck: true + }); +} + +main(); diff --git a/javascript-nodejs/src/receive_logs.js b/javascript-nodejs/src/receive_logs.js index f56e904b..899f919e 100755 --- a/javascript-nodejs/src/receive_logs.js +++ b/javascript-nodejs/src/receive_logs.js @@ -1,20 +1,30 @@ #!/usr/bin/env node -var amqp = require('amqplib/callback_api'); +const amqp = require('amqplib'); -amqp.connect('amqp://localhost', function(err, conn) { - conn.createChannel(function(err, ch) { - var ex = 'logs'; +async function main() { + const connection = await amqp.connect('amqp://localhost'); + const channel = await connection.createChannel(); - ch.assertExchange(ex, 'fanout', {durable: false}); + const exchange = 'logs'; - ch.assertQueue('', {exclusive: true}, function(err, q) { - console.log(" [*] Waiting for messages in %s. To exit press CTRL+C", q.queue); - ch.bindQueue(q.queue, ex, ''); + await channel.assertExchange(exchange, 'fanout', { + durable: false + }); + + const q = await channel.assertQueue('', { + exclusive: true + }); + console.log(" [*] Waiting for messages in %s. To exit press CTRL+C", q.queue); + channel.bindQueue(q.queue, exchange, ''); - ch.consume(q.queue, function(msg) { - console.log(" [x] %s", msg.content.toString()); - }, {noAck: true}); + channel.consume(q.queue, function(msg) { + if (msg.content) { + console.log(" [x] %s", msg.content.toString()); + } + }, { + noAck: true }); - }); -}); +} + +main(); diff --git a/javascript-nodejs/src/receive_logs_direct.js b/javascript-nodejs/src/receive_logs_direct.js index bccf0d14..58795ce5 100755 --- a/javascript-nodejs/src/receive_logs_direct.js +++ b/javascript-nodejs/src/receive_logs_direct.js @@ -1,30 +1,38 @@ #!/usr/bin/env node -var amqp = require('amqplib/callback_api'); +const amqp = require('amqplib'); -var args = process.argv.slice(2); +const args = process.argv.slice(2); -if (args.length == 0) { - console.log("Usage: receive_logs_direct.js [info] [warning] [error]"); - process.exit(1); +if (args.length === 0) { + console.log("Usage: receive_logs_direct.js [info] [warning] [error]"); + process.exit(1); } -amqp.connect('amqp://localhost', function(err, conn) { - conn.createChannel(function(err, ch) { - var ex = 'direct_logs'; +async function main() { + const connection = await amqp.connect('amqp://localhost'); + const channel = await connection.createChannel(); - ch.assertExchange(ex, 'direct', {durable: false}); + const exchange = 'direct_logs'; - ch.assertQueue('', {exclusive: true}, function(err, q) { - console.log(' [*] Waiting for logs. To exit press CTRL+C'); + await channel.assertExchange(exchange, 'direct', { + durable: false + }); + + const q = await channel.assertQueue('', { + exclusive: true + }); + console.log(' [*] Waiting for logs. To exit press CTRL+C'); - args.forEach(function(severity) { - ch.bindQueue(q.queue, ex, severity); - }); + for (const severity of args) { + await channel.bindQueue(q.queue, exchange, severity); + } - ch.consume(q.queue, function(msg) { + channel.consume(q.queue, function(msg) { console.log(" [x] %s: '%s'", msg.fields.routingKey, msg.content.toString()); - }, {noAck: true}); + }, { + noAck: true }); - }); -}); +} + +main(); diff --git a/javascript-nodejs/src/receive_logs_topic.js b/javascript-nodejs/src/receive_logs_topic.js index 4b837f19..556b065f 100755 --- a/javascript-nodejs/src/receive_logs_topic.js +++ b/javascript-nodejs/src/receive_logs_topic.js @@ -1,30 +1,38 @@ #!/usr/bin/env node -var amqp = require('amqplib/callback_api'); +const amqp = require('amqplib'); -var args = process.argv.slice(2); +const args = process.argv.slice(2); -if (args.length == 0) { - console.log("Usage: receive_logs_topic.js ."); - process.exit(1); +if (args.length === 0) { + console.log("Usage: receive_logs_topic.js ."); + process.exit(1); } -amqp.connect('amqp://localhost', function(err, conn) { - conn.createChannel(function(err, ch) { - var ex = 'topic_logs'; +async function main() { + const connection = await amqp.connect('amqp://localhost'); + const channel = await connection.createChannel(); - ch.assertExchange(ex, 'topic', {durable: false}); + const exchange = 'topic_logs'; - ch.assertQueue('', {exclusive: true}, function(err, q) { - console.log(' [*] Waiting for logs. To exit press CTRL+C'); + await channel.assertExchange(exchange, 'topic', { + durable: false + }); + + const q = await channel.assertQueue('', { + exclusive: true + }); + console.log(' [*] Waiting for logs. To exit press CTRL+C'); - args.forEach(function(key) { - ch.bindQueue(q.queue, ex, key); - }); + for (const key of args) { + await channel.bindQueue(q.queue, exchange, key); + } - ch.consume(q.queue, function(msg) { + channel.consume(q.queue, function(msg) { console.log(" [x] %s:'%s'", msg.fields.routingKey, msg.content.toString()); - }, {noAck: true}); + }, { + noAck: true }); - }); -}); +} + +main(); diff --git a/javascript-nodejs/src/rpc_client.js b/javascript-nodejs/src/rpc_client.js index f6fd89ec..8e42bcd1 100755 --- a/javascript-nodejs/src/rpc_client.js +++ b/javascript-nodejs/src/rpc_client.js @@ -1,38 +1,46 @@ #!/usr/bin/env node -var amqp = require('amqplib/callback_api'); +const amqp = require('amqplib'); -var args = process.argv.slice(2); +const args = process.argv.slice(2); if (args.length === 0) { - console.log("Usage: rpc_client.js num"); - process.exit(1); + console.log("Usage: rpc_client.js num"); + process.exit(1); } -amqp.connect('amqp://localhost', function(err, conn) { - conn.createChannel(function(err, ch) { - ch.assertQueue('', {exclusive: true}, function(err, q) { - var corr = generateUuid(); - var num = parseInt(args[0]); +async function main() { + const connection = await amqp.connect('amqp://localhost'); + const channel = await connection.createChannel(); - console.log(' [x] Requesting fib(%d)', num); + const correlationId = generateUuid(); + const num = parseInt(args[0]); - ch.consume(q.queue, function(msg) { - if (msg.properties.correlationId === corr) { - console.log(' [.] Got %s', msg.content.toString()); - setTimeout(function() { conn.close(); process.exit(0) }, 500); - } - }, {noAck: true}); + console.log(' [x] Requesting fib(%d)', num); - ch.sendToQueue('rpc_queue', - new Buffer(num.toString()), - { correlationId: corr, replyTo: q.queue }); + const result = await new Promise((resolve) => { + // Consume from the Direct Reply-to pseudo-queue (noAck is mandatory) + channel.consume('amq.rabbitmq.reply-to', (msg) => { + if (msg.properties.correlationId === correlationId) { + resolve(msg.content.toString()); + } + }, { noAck: true }); + + channel.sendToQueue('rpc_queue', + Buffer.from(num.toString()), { + correlationId: correlationId, + replyTo: 'amq.rabbitmq.reply-to' + }); }); - }); -}); + + console.log(' [.] Got %s', result); + await connection.close(); +} function generateUuid() { - return Math.random().toString() + - Math.random().toString() + - Math.random().toString(); + return Math.random().toString() + + Math.random().toString() + + Math.random().toString(); } + +main(); diff --git a/javascript-nodejs/src/rpc_server.js b/javascript-nodejs/src/rpc_server.js index c9e0f121..71bb5227 100755 --- a/javascript-nodejs/src/rpc_server.js +++ b/javascript-nodejs/src/rpc_server.js @@ -1,33 +1,40 @@ #!/usr/bin/env node -var amqp = require('amqplib/callback_api'); +const amqp = require('amqplib'); -amqp.connect('amqp://localhost', function(err, conn) { - conn.createChannel(function(err, ch) { - var q = 'rpc_queue'; +async function main() { + const connection = await amqp.connect('amqp://localhost'); + const channel = await connection.createChannel(); - ch.assertQueue(q, {durable: false}); - ch.prefetch(1); + const queue = 'rpc_queue'; + + await channel.assertQueue(queue, { + durable: true, + arguments: { 'x-queue-type': 'quorum' } + }); + channel.prefetch(1); console.log(' [x] Awaiting RPC requests'); - ch.consume(q, function reply(msg) { - var n = parseInt(msg.content.toString()); + channel.consume(queue, function reply(msg) { + const n = parseInt(msg.content.toString()); - console.log(" [.] fib(%d)", n); + console.log(" [.] fib(%d)", n); - var r = fibonacci(n); + const r = fibonacci(n); - ch.sendToQueue(msg.properties.replyTo, - new Buffer(r.toString()), - {correlationId: msg.properties.correlationId}); + channel.sendToQueue(msg.properties.replyTo, + Buffer.from(r.toString()), { + correlationId: msg.properties.correlationId + }); - ch.ack(msg); + channel.ack(msg); }); - }); -}); +} function fibonacci(n) { - if (n === 0 || n === 1) - return n; - else - return fibonacci(n - 1) + fibonacci(n - 2); + if (n === 0 || n === 1) + return n; + else + return fibonacci(n - 1) + fibonacci(n - 2); } + +main(); diff --git a/javascript-nodejs/src/send.js b/javascript-nodejs/src/send.js index 92da2ad6..9a5bb5b4 100755 --- a/javascript-nodejs/src/send.js +++ b/javascript-nodejs/src/send.js @@ -1,15 +1,26 @@ #!/usr/bin/env node -var amqp = require('amqplib/callback_api'); +const amqp = require('amqplib'); -amqp.connect('amqp://localhost', function(err, conn) { - conn.createChannel(function(err, ch) { - var q = 'hello'; - var msg = 'Hello World!'; +async function main() { + const connection = await amqp.connect('amqp://localhost'); + const channel = await connection.createChannel(); + + const queue = 'hello'; + const msg = 'Hello World!'; + + await channel.assertQueue(queue, { + durable: true, + arguments: { 'x-queue-type': 'quorum' } + }); + channel.sendToQueue(queue, Buffer.from(msg)); - ch.assertQueue(q, {durable: false}); - ch.sendToQueue(q, Buffer.from(msg)); console.log(" [x] Sent %s", msg); - }); - setTimeout(function() { conn.close(); process.exit(0) }, 500); -}); + + setTimeout(function() { + connection.close(); + process.exit(0); + }, 500); +} + +main(); diff --git a/javascript-nodejs/src/worker.js b/javascript-nodejs/src/worker.js index 40c391fe..bf15eee2 100755 --- a/javascript-nodejs/src/worker.js +++ b/javascript-nodejs/src/worker.js @@ -1,22 +1,32 @@ #!/usr/bin/env node -var amqp = require('amqplib/callback_api'); +const amqp = require('amqplib'); -amqp.connect('amqp://localhost', function(err, conn) { - conn.createChannel(function(err, ch) { - var q = 'task_queue'; +async function main() { + const connection = await amqp.connect('amqp://localhost'); + const channel = await connection.createChannel(); - ch.assertQueue(q, {durable: true}); - ch.prefetch(1); - console.log(" [*] Waiting for messages in %s. To exit press CTRL+C", q); - ch.consume(q, function(msg) { - var secs = msg.content.toString().split('.').length - 1; + const queue = 'task_queue'; - console.log(" [x] Received %s", msg.content.toString()); - setTimeout(function() { - console.log(" [x] Done"); - ch.ack(msg); - }, secs * 1000); - }, {noAck: false}); - }); -}); + await channel.assertQueue(queue, { + durable: true, + arguments: { 'x-queue-type': 'quorum' } + }); + channel.prefetch(1); + console.log(" [*] Waiting for messages in %s. To exit press CTRL+C", queue); + channel.consume(queue, function(msg) { + const secs = msg.content.toString().split('.').length - 1; + + console.log(" [x] Received %s", msg.content.toString()); + setTimeout(function() { + console.log(" [x] Done"); + channel.ack(msg); + }, secs * 1000); + }, { + // manual acknowledgment mode, + // see /docs/confirms for details + noAck: false + }); +} + +main(); diff --git a/julia/README.md b/julia/README.md new file mode 100644 index 00000000..cc6ca9c6 --- /dev/null +++ b/julia/README.md @@ -0,0 +1,63 @@ +# Julia code for RabbitMQ tutorials + +Here you can find Julia code examples from [RabbitMQ +tutorials](https://www.rabbitmq.com/getstarted.html). + +To successfully use the examples you will need a running RabbitMQ server. + +## Requirements + +To run this code you need to install the `AMQPClient` and `JSON`. To install it, run + +``` julia +julia> using Pkg + +julia> Pkg.add("AMQPClient") + Updating registry at `~/.julia/registries/General` + Resolving package versions... + No Changes to `~/.julia/environments/v1.6/Project.toml` + No Changes to `~/.julia/environments/v1.6/Manifest.toml` + +julia> Pkg.add("JSON") + Resolving package versions... + No Changes to `~/.julia/environments/v1.6/Project.toml` + No Changes to `~/.julia/environments/v1.6/Manifest.toml` + +``` + +## Code + +Tutorial one: "Hello World!" + + julia send.jl + julia receive.jl + + +Tutorial two: Work Queues: + + julia new_task.jl "A very hard task which takes two seconds.." + julia worker.jl + + +Tutorial three: Publish/Subscribe + + julia receive_logs.jl + julia emit_log.jl "info: This is the log message" + + +Tutorial four: Routing + + julia receive_logs_direct.jl info + julia emit_log_direct.jl info "The message" + + +Tutorial five: Topics + + julia receive_logs_topic.jl "*.rabbit" + julia emit_log_topic.jl red.rabbit Hello + + +Tutorial six: RPC + + julia rpc_server.jl + julia rpc_client.jl diff --git a/julia/emit_log.jl b/julia/emit_log.jl new file mode 100644 index 00000000..64666287 --- /dev/null +++ b/julia/emit_log.jl @@ -0,0 +1,29 @@ +using AMQPClient +const VIRTUALHOST = "/" +const HOST = "127.0.0.1" + +function send() + # 1. Create a connection to the localhost or 127.0.0.1 of virtualhost '/' + connection(; virtualhost=VIRTUALHOST, host=HOST) do conn + # 2. Create a channel to send messages + channel(conn, AMQPClient.UNUSED_CHANNEL, true) do chan + exchange = "logs" + # 3. Declare the exchange + exchange_declare(chan, exchange, EXCHANGE_TYPE_FANOUT) + if length(Base.ARGS) >= 1 + received = join(Base.ARGS, ' ') + else + received = "info: Hello World" + end + + data = convert(Vector{UInt8}, codeunits(received)) + msg = Message(data, content_type="text/plain", delivery_mode=PERSISTENT) + + # 4. Publish message + basic_publish(chan, msg; exchange=exchange, routing_key="") + println("Message sent: $received") + end + end +end + +send() diff --git a/julia/emit_log_direct.jl b/julia/emit_log_direct.jl new file mode 100644 index 00000000..adf65c7d --- /dev/null +++ b/julia/emit_log_direct.jl @@ -0,0 +1,31 @@ +using AMQPClient +const VIRTUALHOST = "/" +const HOST = "127.0.0.1" + + +function send() + # 1. Create a connection to the localhost or 127.0.0.1 of virtualhost '/' + connection(; virtualhost=VIRTUALHOST, host=HOST) do conn + # 2. Create a channel to send messages + channel(conn, AMQPClient.UNUSED_CHANNEL, true) do chan + # 3. Declare exchange + exchange = "direct_logs" + exchange_declare(chan, exchange, EXCHANGE_TYPE_DIRECT) + # 4. Get severity and message + if length(Base.ARGS) >= 3 + severity = Base.ARGS[1] + received = join(Base.ARGS[2:end], ' ') + else + severity = "info" + received = "Hello World" + end + data = convert(Vector{UInt8}, codeunits(received)) + msg = Message(data, content_type="text/plain", delivery_mode=PERSISTENT) + # 5. Publish the message + basic_publish(chan, msg; exchange=exchange, routing_key=severity) + println("Message sent: $received, Severity: $severity") + end + end +end + +send() diff --git a/julia/emit_log_topic.jl b/julia/emit_log_topic.jl new file mode 100644 index 00000000..31be4964 --- /dev/null +++ b/julia/emit_log_topic.jl @@ -0,0 +1,31 @@ +using AMQPClient +const VIRTUALHOST = "/" +const HOST = "127.0.0.1" + +function send() + # 1. Create a connection to the localhost or 127.0.0.1 of virtualhost '/' + connection(; virtualhost=VIRTUALHOST, host=HOST) do conn + # 2. Create a channel to send messages + channel(conn, AMQPClient.UNUSED_CHANNEL, true) do chan + # 3. Declare Exchange + exchange = "topic_logs" + exchange_topic = "topic" + exchange_declare(chan, exchange, EXCHANGE_TYPE_TOPIC) + # 4. Get input data + if length(Base.ARGS) >= 2 + routing_key = Base.ARGS[1] + received = join(Base.ARGS[2:end], ' ') + else + routing_key = "info" + received = "Hello World" + end + # 5. Prepare and send data + data = convert(Vector{UInt8}, codeunits(received)) + msg = Message(data, content_type="text/plain", delivery_mode=PERSISTENT) + basic_publish(chan, msg; exchange=exchange, routing_key=routing_key) + println("Message sent: $received, routing key: $routing_key") + end + end +end + +send() diff --git a/julia/new_task.jl b/julia/new_task.jl new file mode 100644 index 00000000..000bd22c --- /dev/null +++ b/julia/new_task.jl @@ -0,0 +1,32 @@ +using AMQPClient +const VIRTUALHOST = "/" +const HOST = "127.0.0.1" + +function send() + # 1. Create a connection to the localhost or 127.0.0.1 of virtualhost '/' + connection(; virtualhost=VIRTUALHOST, host=HOST) do conn + # 2. Create a channel to send messages + channel(conn, AMQPClient.UNUSED_CHANNEL, true) do chan + queue = "task_queue" + # 3. Configure the queue + success, queue_name, message_count, consumer_count = queue_declare(chan, queue, durable=true) + + # 4. Prepare the message text + if length(Base.ARGS) >= 1 + received = Base.ARGS[1] + else + received = "Hello World" + end + + # 5. Prepare the payload + data = convert(Vector{UInt8}, codeunits(received)) + msg = Message(data, content_type="text/plain", delivery_mode=PERSISTENT) + + # 6. Send the payload + basic_publish(chan, msg; exchange="", routing_key=queue) + println("Message sent: $received") + end + end +end + +send() diff --git a/julia/receive.jl b/julia/receive.jl new file mode 100644 index 00000000..02b7fe05 --- /dev/null +++ b/julia/receive.jl @@ -0,0 +1,48 @@ +using AMQPClient +const VIRTUALHOST = "/" +const HOST = "127.0.0.1" + + +function receive() + # 1. Create a connection to the localhost or 127.0.0.1 of virtualhost '/' + connection(; virtualhost=VIRTUALHOST, host=HOST) do conn + println("Connection established") + # 2. Create a channel to send messages + channel(conn, AMQPClient.UNUSED_CHANNEL, true) do chan + # 3. Declare a queue + println("Channel created") + queue = "hello" + success, queue_name, message_count, consumer_count = queue_declare(chan, queue) + + # 4. Setup to receive + on_receive = (msg) -> begin + # 4.1 Receive message is Vector{UInt8} + data = String(msg.data) + println("Received the message: $data") + # 4.2 Acknowledge the message + basic_ack(chan, msg.delivery_tag) + end + + success, consumer_tag = basic_consume(chan, queue, on_receive) + @assert success == true + + # 5. Run for-ever + # listen to new messages + while true + sleep(1) + end + end + end +end + +# Don't exit on Ctrl-C +Base.exit_on_sigint(false) +try + receive() +catch ex + if ex isa InterruptException + println("Interrupted") + else + println("Exception: $ex") + end +end diff --git a/julia/receive_logs.jl b/julia/receive_logs.jl new file mode 100644 index 00000000..819a52c9 --- /dev/null +++ b/julia/receive_logs.jl @@ -0,0 +1,44 @@ +using AMQPClient +const VIRTUALHOST = "/" +const HOST = "127.0.0.1" + + +function receive() + # 1. Create a connection to the localhost or 127.0.0.1 of virtualhost '/' + connection(; virtualhost=VIRTUALHOST, host=HOST) do conn + # 2. Create a channel to send messages + channel(conn, AMQPClient.UNUSED_CHANNEL, true) do chan + # 3. Declare a exchange + exchange = "logs" + exchange_declare(chan, exchange, EXCHANGE_TYPE_FANOUT) + result, queue, _, _ = queue_declare(chan, "", durable=true) + # 4. Bind the queue + queue_bind(chan, queue, exchange, EXCHANGE_TYPE_FANOUT) + + println(" [*] Waiting for messages. To exit press CTRL+C") + on_receive = (msg) -> begin + data = String(msg.data) + println("Received the message: $data") + basic_ack(chan, msg.delivery_tag) + end + + success, consumer_tag = basic_consume(chan, queue, on_receive) + + while true + sleep(1) + end + end + end +end + +# Don't exit on Ctrl-C +Base.exit_on_sigint(false) +try + receive() +catch ex + if ex isa InterruptException + println("Interrupted") + else + println("Exception: $ex") + end +end diff --git a/julia/receive_logs_direct.jl b/julia/receive_logs_direct.jl new file mode 100644 index 00000000..01f92f43 --- /dev/null +++ b/julia/receive_logs_direct.jl @@ -0,0 +1,55 @@ +using AMQPClient +const VIRTUALHOST = "/" +const HOST = "127.0.0.1" + + +function receive() + # 1. Create a connection to the localhost or 127.0.0.1 of virtualhost '/' + connection(; virtualhost=VIRTUALHOST, host=HOST) do conn + # 2. Create a channel to send messages + channel(conn, AMQPClient.UNUSED_CHANNEL, true) do chan + # 3. Declare a exchange + exchange = "direct_logs" + exchange_declare(chan, exchange, EXCHANGE_TYPE_DIRECT) + + result, queue_name, _, _ = queue_declare(chan, "", exclusive=true) + + # 4. Receive queues to bind + if length(Base.ARGS) <= 0 + println(Base.stdout, "Usage: [info] [warning] [error]\n") + Base.exit(1) + end + + # 4.1 Bind queues + for severity in Base.ARGS[1:end] + queue_bind(chan, queue_name, exchange, + severity) + end + + println(" [*] Waiting for messages. To exit press CTRL+C") + on_receive = (msg) -> begin + data = String(msg.data) + println("Received the message: $data") + basic_ack(chan, msg.delivery_tag) + end + + success, consumer_tag = basic_consume(chan, queue_name, on_receive) + + while true + sleep(1) + end + end + end +end + +# Don't exit on Ctrl-C +Base.exit_on_sigint(false) +try + receive() +catch ex + if ex isa InterruptException + println("Interrupted") + else + println("Exception: $ex") + end +end diff --git a/julia/receive_logs_topic.jl b/julia/receive_logs_topic.jl new file mode 100644 index 00000000..69ecc9ac --- /dev/null +++ b/julia/receive_logs_topic.jl @@ -0,0 +1,56 @@ +using AMQPClient +const VIRTUALHOST = "/" +const HOST = "127.0.0.1" + + +function receive() + # 1. Create a connection to the localhost or 127.0.0.1 of virtualhost '/' + connection(; virtualhost=VIRTUALHOST, host=HOST) do conn + # 2. Create a channel to send messages + channel(conn, AMQPClient.UNUSED_CHANNEL, true) do chan + # 3. Declare a exchange and queue + exchange = "topic_logs" + exchange_declare(chan, exchange, EXCHANGE_TYPE_TOPIC) + result, queue_name, _, _ = queue_declare(chan, "", exclusive=true) + + if length(Base.ARGS) <= 0 + println(Base.stdout, "Usage: [binding_key] \n") + Base.exit(1) + end + + # 3.1 Bind all queues + for binding_key in Base.ARGS[1:end] + queue_bind(chan, queue_name, exchange, + binding_key) + end + + println(" [*] Waiting for messages. To exit press CTRL+C") + + # 4. Receive messages + on_receive = (msg) -> begin + data = String(msg.data) + println("Received the message: $data") + basic_ack(chan, msg.delivery_tag) + end + + success, consumer_tag = basic_consume(chan, queue_name, on_receive) + + while true + sleep(1) + end + end + end +end + + +# Don't exit on Ctrl-C +Base.exit_on_sigint(false) +try + receive() +catch ex + if ex isa InterruptException + println("Interrupted") + else + println("Exception: $ex") + end +end diff --git a/julia/rpc_client.jl b/julia/rpc_client.jl new file mode 100644 index 00000000..da13e968 --- /dev/null +++ b/julia/rpc_client.jl @@ -0,0 +1,62 @@ +using AMQPClient +const VIRTUALHOST = "/" +const HOST = "127.0.0.1" +using UUIDs +using JSON + +function send(n) + # 1. Create Connection + connection(; virtualhost=VIRTUALHOST, host=HOST) do conn + println("Created Connection") + # 2. Create a channel to send messages + channel(conn, AMQPClient.UNUSED_CHANNEL, true) do chan + println("Created Channel") + # 3. Create queue for response + success, queue_name, message_count, consumer_count = queue_declare(chan, "", exclusive=true) + corr_id = string(uuid4()) + + # 4. Receive the response + on_receive = (msg) -> begin + try + response = String(msg.data) + msg_corr_id = convert(String, msg.properties[:correlation_id]) + println(msg_corr_id) + if msg_corr_id == corr_id + println("Received RPC response: $response") + end + basic_ack(chan, msg.delivery_tag) + catch err + println(err) + end + end + + success, consumer_tag = basic_consume(chan, queue_name, on_receive) + + # 5. Send RPC Request in JSON encoding since integer is disallowed + data = convert(Vector{UInt8}, codeunits(JSON.json(n))) + msg = Message(data, content_type="text/plain", delivery_mode=PERSISTENT, correlation_id=corr_id, + reply_to=queue_name) + routing_key = "rpc_queue" + basic_publish(chan, msg; exchange="", routing_key=routing_key) + println("RPC Request sent: $n, routing_key: $queue_name") + + # Listen for ever + while true + sleep(1) + end + + end + end +end + +# Don't exit on Ctrl-C +Base.exit_on_sigint(false) +try + send(30) +catch ex + if ex isa InterruptException + println("Interrupted") + else + println("Exception: $ex") + end +end diff --git a/julia/rpc_server.jl b/julia/rpc_server.jl new file mode 100644 index 00000000..db3f6816 --- /dev/null +++ b/julia/rpc_server.jl @@ -0,0 +1,71 @@ +using AMQPClient +const VIRTUALHOST = "/" +const HOST = "127.0.0.1" +using JSON + + +function fib(n) + if n == 0 + return 0 + elseif n == 1 + return 1 + else + return fib(n - 1) + fib(n - 2) + end +end + +function receive() + # 1. Create a connection to the localhost or 127.0.0.1 of virtualhost '/' + connection(; virtualhost=VIRTUALHOST, host=HOST) do conn + println("Connection established") + # 2. Create a channel to send messages + channel(conn, AMQPClient.UNUSED_CHANNEL, true) do chan + # 3. Declare a queue + println("Channel created") + queue = "rpc_queue" + success, queue_name, message_count, consumer_count = queue_declare(chan, queue; durable=true) + + # 4. Receiver requests and dispatch response + on_receive = (msg) -> begin + data = String(msg.data) + # 4.1 Deserialize + number = JSON.parse(data) + println("Received requets to calculate fib[$number]") + try + output = fib(number) + res = convert(Vector{UInt8}, codeunits(JSON.json(output))) + # 4.2 Ack the received request + basic_ack(chan, msg.delivery_tag) + # 4.3 Get the reply details from the message + reply_to = convert(String, msg.properties[:reply_to]) + corr_id = convert(String, msg.properties[:correlation_id]) + resp = Message(res, content_type="text/plain", delivery_mode=PERSISTENT, correlation_id=corr_id) + # Sent the response + basic_publish(chan, resp; exchange="", routing_key=reply_to) + println("Sent response for $number as $output") + catch err + println(err) + end + end + + + # 5. Consume requests for-ever + success, consumer_tag = basic_consume(chan, queue, on_receive) + + while true + sleep(1) + end + end + end +end +# Don't exit on Ctrl-C +Base.exit_on_sigint(false) +try + receive() +catch ex + if ex isa InterruptException + println("Interrupted") + else + println("Exception: $ex") + end +end diff --git a/julia/send.jl b/julia/send.jl new file mode 100755 index 00000000..a1d422b6 --- /dev/null +++ b/julia/send.jl @@ -0,0 +1,24 @@ +using AMQPClient +const VIRTUALHOST ="/" +const HOST = "127.0.0.1" + + +function send(message) + # 1. Create a connection to the localhost or 127.0.0.1 of virtualhost '/' + connection(; virtualhost=VIRTUALHOST, host=HOST) do conn + # 2. Create a channel to send messages + channel(conn, AMQPClient.UNUSED_CHANNEL, true) do chan + # 3. Declare a queue + success, queue_name, message_count, consumer_count = queue_declare(chan, "hello") + # 4.1 Create a message, AMQPCleint only accepts message in UInt8 + data = convert(Vector{UInt8}, codeunits(message)) + msg = Message(data, content_type="text/plain", delivery_mode=PERSISTENT) + # 4.2 Send a message + basic_publish(chan, msg; exchange="", routing_key="hello") + println("Message sent: $message") + # 5. Auto-closes the channel and connection + end + end +end + +send("Hello World!") diff --git a/julia/worker.jl b/julia/worker.jl new file mode 100644 index 00000000..d52e9c25 --- /dev/null +++ b/julia/worker.jl @@ -0,0 +1,48 @@ +using AMQPClient +const VIRTUALHOST = "/" +const HOST = "127.0.0.1" + + +function receive() + # 1. Create a connection to the localhost or 127.0.0.1 of virtualhost '/' + connection(; virtualhost=VIRTUALHOST, host=HOST) do conn + # 2. Create a channel to send messages + channel(conn, AMQPClient.UNUSED_CHANNEL, true) do chan + # 3. Declare a queue + queue = "task_queue" + success, queue_name, message_count, consumer_count = queue_declare(chan, queue, durable=true) + println(" [*] Waiting for messages. To exit press CTRL+C") + + # 4. Setup function to receive message + on_receive = (msg) -> begin + data = String(msg.data) + println("Received the message: $data") + basic_ack(chan, msg.delivery_tag) + end + + # 5. Configure Quality of Service + basic_qos(chan, 0, 1, false) + success, consumer_tag = basic_consume(chan, queue, on_receive) + + @assert success == true + + while true + sleep(1) + end + # 5. Close the connection + end + end +end + + +# Don't exit on Ctrl-C +Base.exit_on_sigint(false) +try + receive() +catch ex + if ex isa InterruptException + println("Interrupted") + else + println("Exception: $ex") + end +end diff --git a/kotlin-java-client/.gitignore b/kotlin-java-client/.gitignore new file mode 100644 index 00000000..e51bc30b --- /dev/null +++ b/kotlin-java-client/.gitignore @@ -0,0 +1,2 @@ +build/* +.gradle/* diff --git a/kotlin-java-client/README.md b/kotlin-java-client/README.md new file mode 100644 index 00000000..91b51dec --- /dev/null +++ b/kotlin-java-client/README.md @@ -0,0 +1,95 @@ +# RabbitMQ Tutorials in Kotlin + +This is a minimalistic Kotlin port of the [RabbitMQ tutorials in Java](https://www.rabbitmq.com/getstarted.html). +The port is admittedly quite close to Java in terms of code style. + + +## Compiling the Code + +``` shell +gradle clean compileKotlin +``` + +## Running the Tutorials + +### Tutorial 1 + +Execute the following command to start a Hello, world consumer + +``` shell +gradle run -P main=Recv +``` + +Execute the following in a separate shell to publish a Hello, world messge: + +``` shell +gradle run -P main=Send +``` + +### Tutorial 2 + +Send a task message. The task will be completed immediately + +``` shell +gradle run -P main=NewTask +``` + +To start a worker (run in a separate shell): + +``` shell +gradle run -P main=Worker +``` + +Send a task message. It will wait for 1 second for each dot in the payload. + +``` shell +gradle run -P main=NewTask -P argv="rabbit1 ...." +``` + +Add more workers to the same queue, message will be distributed in the +round robin manner. + +### Tutorial 3 + +``` shell +gradle run -P main=ReceiveLogs +``` + + +``` shell +gradle run -P main=EmitLog -P argv="rabbit1, msg1" +``` + +### Tutorial 4 + +``` shell +gradle run -P main="ReceiveLogsDirect" -P argv="info,error" +``` + +``` shell +gradle run -P main=EmitLogDirect" +``` + +### Tutorial 5 + +``` shell +gradle run -P main=ReceiveLogsTopic -P argv="anonymous.*" +``` + +``` shell +gradle run -P main=EmitLogTopic -P argv="anonymous.info" +``` + +### Tutorial 6 + +In one shell: + +``` shell +gradle run -P main=RPCServer +``` + +In another shell: + +``` shell +gradle run -P main=RPCClient +``` diff --git a/kotlin/build.gradle b/kotlin-java-client/build.gradle similarity index 74% rename from kotlin/build.gradle rename to kotlin-java-client/build.gradle index cc1b7245..82d1cf82 100644 --- a/kotlin/build.gradle +++ b/kotlin-java-client/build.gradle @@ -1,5 +1,5 @@ buildscript { - ext.kotlin_version = '1.2.31' + ext.kotlin_version = '1.8.10' repositories { mavenCentral() @@ -19,9 +19,9 @@ repositories { } dependencies { - compile "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" - compile "com.rabbitmq:amqp-client:latest.release" - compile "org.slf4j:slf4j-simple:1.7.25" + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" + implementation "com.rabbitmq:amqp-client:latest.release" + implementation "org.slf4j:slf4j-simple:1.7.25" } compileKotlin { diff --git a/kotlin-java-client/gradle.properties b/kotlin-java-client/gradle.properties new file mode 100644 index 00000000..19b3681c --- /dev/null +++ b/kotlin-java-client/gradle.properties @@ -0,0 +1,2 @@ +main = +argv = diff --git a/kotlin/settings.gradle b/kotlin-java-client/settings.gradle similarity index 100% rename from kotlin/settings.gradle rename to kotlin-java-client/settings.gradle diff --git a/kotlin/src/main/kotlin/EmitLog.kt b/kotlin-java-client/src/main/kotlin/EmitLog.kt similarity index 100% rename from kotlin/src/main/kotlin/EmitLog.kt rename to kotlin-java-client/src/main/kotlin/EmitLog.kt diff --git a/kotlin/src/main/kotlin/EmitLogDirect.kt b/kotlin-java-client/src/main/kotlin/EmitLogDirect.kt similarity index 100% rename from kotlin/src/main/kotlin/EmitLogDirect.kt rename to kotlin-java-client/src/main/kotlin/EmitLogDirect.kt diff --git a/kotlin/src/main/kotlin/EmitLogHeader.kt b/kotlin-java-client/src/main/kotlin/EmitLogHeader.kt similarity index 100% rename from kotlin/src/main/kotlin/EmitLogHeader.kt rename to kotlin-java-client/src/main/kotlin/EmitLogHeader.kt diff --git a/kotlin/src/main/kotlin/EmitLogTopic.kt b/kotlin-java-client/src/main/kotlin/EmitLogTopic.kt similarity index 100% rename from kotlin/src/main/kotlin/EmitLogTopic.kt rename to kotlin-java-client/src/main/kotlin/EmitLogTopic.kt diff --git a/kotlin/src/main/kotlin/NewTask.kt b/kotlin-java-client/src/main/kotlin/NewTask.kt similarity index 100% rename from kotlin/src/main/kotlin/NewTask.kt rename to kotlin-java-client/src/main/kotlin/NewTask.kt diff --git a/kotlin/src/main/kotlin/RPCClient.kt b/kotlin-java-client/src/main/kotlin/RPCClient.kt similarity index 100% rename from kotlin/src/main/kotlin/RPCClient.kt rename to kotlin-java-client/src/main/kotlin/RPCClient.kt diff --git a/kotlin/src/main/kotlin/RPCServer.kt b/kotlin-java-client/src/main/kotlin/RPCServer.kt similarity index 100% rename from kotlin/src/main/kotlin/RPCServer.kt rename to kotlin-java-client/src/main/kotlin/RPCServer.kt diff --git a/kotlin/src/main/kotlin/ReceiveLogHeader.kt b/kotlin-java-client/src/main/kotlin/ReceiveLogHeader.kt similarity index 100% rename from kotlin/src/main/kotlin/ReceiveLogHeader.kt rename to kotlin-java-client/src/main/kotlin/ReceiveLogHeader.kt diff --git a/kotlin/src/main/kotlin/ReceiveLogs.kt b/kotlin-java-client/src/main/kotlin/ReceiveLogs.kt similarity index 100% rename from kotlin/src/main/kotlin/ReceiveLogs.kt rename to kotlin-java-client/src/main/kotlin/ReceiveLogs.kt diff --git a/kotlin/src/main/kotlin/ReceiveLogsDirect.kt b/kotlin-java-client/src/main/kotlin/ReceiveLogsDirect.kt similarity index 100% rename from kotlin/src/main/kotlin/ReceiveLogsDirect.kt rename to kotlin-java-client/src/main/kotlin/ReceiveLogsDirect.kt diff --git a/kotlin/src/main/kotlin/ReceiveLogsTopic.kt b/kotlin-java-client/src/main/kotlin/ReceiveLogsTopic.kt similarity index 100% rename from kotlin/src/main/kotlin/ReceiveLogsTopic.kt rename to kotlin-java-client/src/main/kotlin/ReceiveLogsTopic.kt diff --git a/kotlin/src/main/kotlin/Recv.kt b/kotlin-java-client/src/main/kotlin/Recv.kt similarity index 100% rename from kotlin/src/main/kotlin/Recv.kt rename to kotlin-java-client/src/main/kotlin/Recv.kt diff --git a/kotlin/src/main/kotlin/Send.kt b/kotlin-java-client/src/main/kotlin/Send.kt similarity index 100% rename from kotlin/src/main/kotlin/Send.kt rename to kotlin-java-client/src/main/kotlin/Send.kt diff --git a/kotlin/src/main/kotlin/Worker.kt b/kotlin-java-client/src/main/kotlin/Worker.kt similarity index 100% rename from kotlin/src/main/kotlin/Worker.kt rename to kotlin-java-client/src/main/kotlin/Worker.kt diff --git a/kotlin/.gitignore b/kotlin/.gitignore index e51bc30b..ba8d33ea 100644 --- a/kotlin/.gitignore +++ b/kotlin/.gitignore @@ -1,2 +1,3 @@ build/* .gradle/* +.kotlin diff --git a/kotlin/README.md b/kotlin/README.md index aa52deb3..44e270ef 100644 --- a/kotlin/README.md +++ b/kotlin/README.md @@ -1,95 +1,116 @@ -# RabbitMQ Tutorials in Kotlin +# RabbitMQ Tutorials in Kotlin (JVM & Native) -This is a minimalistic Kotlin port of the [RabbitMQ tutorials in Java](http://www.rabbitmq.com/getstarted.html). -The port is admittedly quite close to Java in terms of code style. +This repository contains RabbitMQ tutorials implemented using [Kourier](https://kourier.dev), a pure Kotlin multiplatform AMQP client. +## About -## Compiling the Code +These tutorials demonstrate the core concepts of RabbitMQ using Kotlin and the Kourier AMQP client library. All examples work on JVM, macOS (ARM64), Linux (x64), and Windows (x64) platforms through Kotlin Multiplatform. -``` shell -gradle clean compileKotlin +## Prerequisites + +- RabbitMQ server running on localhost (default port 5672) +- Kotlin 2.1.21 +- Gradle + +## Building + +```bash +./gradlew build ``` -## Running the Tutorials +## Tutorials -### Tutorial 1 +### 1. Hello World (HelloWorld.kt) -Execute the following command to start a Hello, world consumer +The simplest thing that does something - sending and receiving messages from a named queue. -``` shell -gradle run -P main=Recv -``` +Functions: +- `send()` - Sends "Hello World!" message to the queue +- `receive()` - Receives and prints messages from the queue -Execute the following in a separate shell to publish a Hello, world messge: +### 2. Work Queues (WorkQueues.kt) -``` shell -gradle run -P main=Send -``` +Distributing time-consuming tasks among multiple workers. -### Tutorial 2 +Functions: +- `newTask(message)` - Sends a task to the work queue (dots represent work time) +- `worker(workerName)` - Processes tasks with fair dispatch and manual acknowledgment -Send a task message. The task will be completed immediately +Key concepts: +- Message durability +- Fair dispatch with `basicQos` +- Manual acknowledgments -``` shell -gradle run -P main=NewTask -``` +### 3. Publish/Subscribe (PublishSubscribe.kt) -To start a worker (run in a separate shell): +Sending messages to many consumers at once using fanout exchanges. -``` shell -gradle run -P main=Worker -``` +Functions: +- `emitLog(message)` - Publishes log messages to all subscribers +- `receiveLogs(subscriberName)` - Subscribes to all log messages -Send a task message. It will wait for 1 second for each dot in the payload. +Key concepts: +- Fanout exchanges +- Temporary queues +- Broadcasting messages -``` shell -gradle run -P main=NewTask -P argv="rabbit1 ...." -``` +### 4. Routing (Routing.kt) -Add more workers to the same queue, message will be distributed in the -round robin manner. +Receiving messages selectively using direct exchanges and routing keys. -### Tutorial 3 +Functions: +- `emitLogDirect(severity, message)` - Publishes log with specific severity +- `receiveLogsDirect(subscriberName, severities)` - Subscribes to specific severities -``` shell -gradle run -P main=ReceiveLogs -``` +Key concepts: +- Direct exchanges +- Routing keys +- Multiple bindings per queue +### 5. Topics (Topics.kt) -``` shell -gradle run -P main=EmitLog -P argv="rabbit1, msg1" -``` +Receiving messages based on patterns using topic exchanges. -### Tutorial 4 +Functions: +- `emitLogTopic(routingKey, message)` - Publishes with topic routing key +- `receiveLogsTopic(subscriberName, bindingKeys)` - Subscribes using patterns -``` shell -gradle run -P main="ReceiveLogsDirect" -P argv="info,error" -``` +Key concepts: +- Topic exchanges +- Wildcard patterns (`*` = one word, `#` = zero or more words) +- Pattern-based routing -``` shell -gradle run -P main=EmitLogDirect" -``` +### 6. RPC (RPC.kt) -### Tutorial 5 +Request/reply pattern for remote procedure calls. -``` shell -gradle run -P main=ReceiveLogsTopic -P argv="anonymous.*" -``` +Functions: +- `rpcServer()` - Processes Fibonacci number requests +- `rpcClient(n)` - Sends RPC request and waits for response -``` shell -gradle run -P main=EmitLogTopic -P argv="anonymous.info" -``` +Key concepts: +- Callback queues +- Correlation IDs +- Reply-to pattern -### Tutorial 6 +## Running Examples -In one shell: +These tutorials are designed as library functions. You can call them from your own code or tests. For example: -``` shell -gradle run -P main=RPCServer -``` +```kotlin +import kotlinx.coroutines.runBlocking + +fun main() = runBlocking { + // Example: Run work queue tutorial + launch { worker(this, "Worker-1") } + launch { worker(this, "Worker-2") } -In another shell: + delay(1000) // Give workers time to start -``` shell -gradle run -P main=RPCClient + newTask(this, "Task with work...") +} ``` + +## More Information + +For detailed explanations of each tutorial, see the [Kourier documentation](https://kourier.dev/tutorials/). diff --git a/kotlin/build.gradle.kts b/kotlin/build.gradle.kts new file mode 100644 index 00000000..da84fddf --- /dev/null +++ b/kotlin/build.gradle.kts @@ -0,0 +1,34 @@ +plugins { + kotlin("multiplatform") version "2.1.21" +} + +repositories { + mavenCentral() +} + +kotlin { + // Examples can be executed with JVM or natively for macOS, Linux, or Windows + jvm() + macosArm64() + linuxX64() + mingwX64() + + applyDefaultHierarchyTemplate() + sourceSets { + all { + languageSettings.apply { + optIn("kotlin.uuid.ExperimentalUuidApi") + } + } + val commonMain by getting { + dependencies { + api("dev.kourier:amqp-client:0.4.2") + } + } + val commonTest by getting { + dependencies { + implementation(kotlin("test")) + } + } + } +} diff --git a/kotlin/gradle/wrapper/gradle-wrapper.jar b/kotlin/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 00000000..1b33c55b Binary files /dev/null and b/kotlin/gradle/wrapper/gradle-wrapper.jar differ diff --git a/kotlin/gradle/wrapper/gradle-wrapper.properties b/kotlin/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 00000000..ca025c83 --- /dev/null +++ b/kotlin/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,7 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-bin.zip +networkTimeout=10000 +validateDistributionUrl=true +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/kotlin/gradlew b/kotlin/gradlew new file mode 100755 index 00000000..23d15a93 --- /dev/null +++ b/kotlin/gradlew @@ -0,0 +1,251 @@ +#!/bin/sh + +# +# Copyright © 2015-2021 the original authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# SPDX-License-Identifier: Apache-2.0 +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH="\\\"\\\"" + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/kotlin/gradlew.bat b/kotlin/gradlew.bat new file mode 100644 index 00000000..db3a6ac2 --- /dev/null +++ b/kotlin/gradlew.bat @@ -0,0 +1,94 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH= + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* + +:end +@rem End local scope for the variables with windows NT shell +if %ERRORLEVEL% equ 0 goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/kotlin/settings.gradle.kts b/kotlin/settings.gradle.kts new file mode 100644 index 00000000..fc6d81fb --- /dev/null +++ b/kotlin/settings.gradle.kts @@ -0,0 +1 @@ +rootProject.name = "rabbitmq-tutorial" diff --git a/kotlin/src/commonMain/kotlin/HelloWorld.kt b/kotlin/src/commonMain/kotlin/HelloWorld.kt new file mode 100644 index 00000000..c1807f1a --- /dev/null +++ b/kotlin/src/commonMain/kotlin/HelloWorld.kt @@ -0,0 +1,61 @@ +import dev.kourier.amqp.Field +import dev.kourier.amqp.Properties +import dev.kourier.amqp.connection.amqpConfig +import dev.kourier.amqp.connection.createAMQPConnection +import io.ktor.utils.io.core.* +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch +import kotlinx.coroutines.runBlocking + +val queueName = "hello" + +suspend fun send(coroutineScope: CoroutineScope) { + val config = amqpConfig { + server { + host = "localhost" + } + } + val connection = createAMQPConnection(coroutineScope, config) + val channel = connection.openChannel() + + channel.queueDeclare(queueName, durable = true, exclusive = false, autoDelete = false, arguments = mapOf("x-queue-type" to Field.LongString("quorum"))) + val message = "Hello World!" + channel.basicPublish(message.toByteArray(), exchange = "", routingKey = queueName, properties = Properties()) + println("[x] Sent '$message'") + + channel.close() + connection.close() +} + +suspend fun receive(coroutineScope: CoroutineScope) { + val config = amqpConfig { + server { + host = "localhost" + } + } + val connection = createAMQPConnection(coroutineScope, config) + val channel = connection.openChannel() + + channel.queueDeclare(queueName, durable = true, exclusive = false, autoDelete = false, arguments = mapOf("x-queue-type" to Field.LongString("quorum"))) + println("[*] Waiting for messages. To exit press CTRL+C") + + val consumer = channel.basicConsume(queueName, noAck = true) + for (delivery in consumer) { + val message: String = delivery.message.body.decodeToString() + println("[x] Received '$message'") + } + + channel.close() + connection.close() +} + +fun main() = runBlocking { + val coroutineScope = this + + launch { send(coroutineScope) } + launch { receive(coroutineScope) } + + delay(Long.MAX_VALUE) // Keep the main thread alive to allow the consumer to run +} + diff --git a/kotlin/src/commonMain/kotlin/PublishSubscribe.kt b/kotlin/src/commonMain/kotlin/PublishSubscribe.kt new file mode 100644 index 00000000..fcd04eba --- /dev/null +++ b/kotlin/src/commonMain/kotlin/PublishSubscribe.kt @@ -0,0 +1,89 @@ +import dev.kourier.amqp.BuiltinExchangeType +import dev.kourier.amqp.Properties +import dev.kourier.amqp.connection.amqpConfig +import dev.kourier.amqp.connection.createAMQPConnection +import io.ktor.utils.io.core.* +import kotlinx.coroutines.* + +suspend fun emitLog(coroutineScope: CoroutineScope, message: String) { + val config = amqpConfig { + server { + host = "localhost" + } + } + val connection = createAMQPConnection(coroutineScope, config) + val channel = connection.openChannel() + + // Declare a fanout exchange - broadcasts to all bound queues + channel.exchangeDeclare( + "logs", + BuiltinExchangeType.FANOUT, + durable = false, + autoDelete = false, + internal = false, + arguments = emptyMap() + ) + + // Publish to the exchange (routing key is ignored for fanout) + channel.basicPublish( + message.toByteArray(), + exchange = "logs", + routingKey = "", // Routing key is ignored by fanout exchanges + properties = Properties() + ) + println(" [x] Sent '$message'") + + channel.close() + connection.close() +} + +suspend fun receiveLogs(coroutineScope: CoroutineScope, subscriberName: String) { + val config = amqpConfig { + server { + host = "localhost" + } + } + val connection = createAMQPConnection(coroutineScope, config) + val channel = connection.openChannel() + + // Declare the same fanout exchange + channel.exchangeDeclare( + "logs", + BuiltinExchangeType.FANOUT, + durable = false, + autoDelete = false, + internal = false, + arguments = emptyMap() + ) + + // Declare a temporary, exclusive, auto-delete queue + // The server generates a unique name for us + val queueDeclared = channel.queueDeclare( + name = "", // Empty name = server generates a unique name + durable = false, + exclusive = true, // Queue is deleted when connection closes + autoDelete = true, // Queue is deleted when no consumers + arguments = emptyMap() + ) + val queueName = queueDeclared.queueName + println(" [$subscriberName] Created temporary queue: $queueName") + + // Bind the queue to the exchange + channel.queueBind( + queue = queueName, + exchange = "logs", + routingKey = "" // Routing key is ignored for fanout + ) + println(" [$subscriberName] Waiting for logs. To exit press CTRL+C") + + // Consume messages with auto-ack (since these are just logs) + val consumer = channel.basicConsume(queueName, noAck = true) + + for (delivery in consumer) { + val message = delivery.message.body.decodeToString() + println(" [$subscriberName] $message") + } + + channel.close() + connection.close() +} diff --git a/kotlin/src/commonMain/kotlin/RPC.kt b/kotlin/src/commonMain/kotlin/RPC.kt new file mode 100644 index 00000000..ef63bd75 --- /dev/null +++ b/kotlin/src/commonMain/kotlin/RPC.kt @@ -0,0 +1,162 @@ +import dev.kourier.amqp.Field +import dev.kourier.amqp.connection.amqpConfig +import dev.kourier.amqp.connection.createAMQPConnection +import dev.kourier.amqp.properties +import io.ktor.utils.io.core.* +import kotlinx.coroutines.* +import kotlin.uuid.Uuid + +/** + * Fibonacci function - calculates Fibonacci number recursively. + * Note: This is a simple recursive implementation for demonstration. + * Not suitable for large numbers in production. + */ +private fun fib(n: Int): Int { + return when { + n == 0 -> 0 + n == 1 -> 1 + else -> fib(n - 1) + fib(n - 2) + } +} + +/** + * RPC Server - Processes Fibonacci requests and sends responses. + */ +suspend fun rpcServer(coroutineScope: CoroutineScope) { + val config = amqpConfig { + server { + host = "localhost" + } + } + val connection = createAMQPConnection(coroutineScope, config) + val channel = connection.openChannel() + + try { + // Declare the RPC queue + channel.queueDeclare( + "rpc_queue", + durable = true, + exclusive = false, + autoDelete = false, + arguments = mapOf("x-queue-type" to Field.LongString("quorum")) + ) + + // Fair dispatch - don't give more than one message at a time + channel.basicQos(count = 1u, global = false) + + println(" [x] Awaiting RPC requests") + + // Consume RPC requests + val consumer = channel.basicConsume("rpc_queue", noAck = false) + + for (delivery in consumer) { + try { + val props = delivery.message.properties + val correlationId = props.correlationId + val replyTo = props.replyTo + + // Parse request (expecting an integer) + val requestMessage = delivery.message.body.decodeToString() + val n = requestMessage.toIntOrNull() ?: 0 + + println(" [.] fib($n)") + + // Calculate Fibonacci + val response = fib(n) + + // Build response properties with correlation ID + val replyProps = properties { + this.correlationId = correlationId + } + + // Send response to the callback queue + if (replyTo != null) { + channel.basicPublish( + response.toString().toByteArray(), + exchange = "", + routingKey = replyTo, + properties = replyProps + ) + } + + // Acknowledge the request + channel.basicAck(delivery.message, multiple = false) + } catch (e: Exception) { + println(" [.] Error processing request: ${e.message}") + e.printStackTrace() + channel.basicAck(delivery.message, multiple = false) + } + } + } finally { + // Ensure cleanup happens even when coroutine is cancelled + channel.close() + connection.close() + } +} + +/** + * RPC Client - Sends Fibonacci requests and waits for responses. + */ +suspend fun rpcClient(coroutineScope: CoroutineScope, n: Int): Int { + val config = amqpConfig { + server { + host = "localhost" + } + } + val connection = createAMQPConnection(coroutineScope, config) + val channel = connection.openChannel() + + try { + // Create an exclusive callback queue for receiving responses + val callbackQueueDeclared = channel.queueDeclare( + name = "", + durable = false, + exclusive = true, // Exclusive to this connection + autoDelete = true, + arguments = emptyMap() + ) + val callbackQueueName = callbackQueueDeclared.queueName + + // Generate a unique correlation ID for this request + val correlationId = Uuid.random().toString() + + // Start consuming BEFORE sending the request to avoid race condition + val consumer = channel.basicConsume(callbackQueueName, noAck = true) + var result = 0 + + // Build request properties + val requestProps = properties { + this.correlationId = correlationId + this.replyTo = callbackQueueName + } + + // Send the RPC request + channel.basicPublish( + n.toString().toByteArray(), + exchange = "", + routingKey = "rpc_queue", + properties = requestProps + ) + println(" [x] Requesting fib($n)") + + withTimeout(10000) { // 10 second timeout + for (delivery in consumer) { + val responseCorrelationId = delivery.message.properties.correlationId + + if (responseCorrelationId == correlationId) { + // Found matching response + val responseMessage = delivery.message.body.decodeToString() + result = responseMessage.toInt() + println(" [.] Got $result") + break + } + } + } + + return result + } finally { + // Ensure cleanup happens even on timeout or error + channel.close() + connection.close() + } +} diff --git a/kotlin/src/commonMain/kotlin/Routing.kt b/kotlin/src/commonMain/kotlin/Routing.kt new file mode 100644 index 00000000..df33a1bb --- /dev/null +++ b/kotlin/src/commonMain/kotlin/Routing.kt @@ -0,0 +1,92 @@ +import dev.kourier.amqp.BuiltinExchangeType +import dev.kourier.amqp.Properties +import dev.kourier.amqp.connection.amqpConfig +import dev.kourier.amqp.connection.createAMQPConnection +import io.ktor.utils.io.core.* +import kotlinx.coroutines.* + +suspend fun emitLogDirect(coroutineScope: CoroutineScope, severity: String, message: String) { + val config = amqpConfig { + server { + host = "localhost" + } + } + val connection = createAMQPConnection(coroutineScope, config) + val channel = connection.openChannel() + + // Declare a direct exchange - routes based on exact routing key match + channel.exchangeDeclare( + "direct_logs", + BuiltinExchangeType.DIRECT, + durable = false, + autoDelete = false, + internal = false, + arguments = emptyMap() + ) + + // Publish with severity as the routing key + channel.basicPublish( + message.toByteArray(), + exchange = "direct_logs", + routingKey = severity, // Routing key determines which queues receive the message + properties = Properties() + ) + println(" [x] Sent '$severity':'$message'") + + channel.close() + connection.close() +} + +suspend fun receiveLogsDirect(coroutineScope: CoroutineScope, subscriberName: String, severities: List) { + val config = amqpConfig { + server { + host = "localhost" + } + } + val connection = createAMQPConnection(coroutineScope, config) + val channel = connection.openChannel() + + // Declare the same direct exchange + channel.exchangeDeclare( + "direct_logs", + BuiltinExchangeType.DIRECT, + durable = false, + autoDelete = false, + internal = false, + arguments = emptyMap() + ) + + // Declare a temporary queue + val queueDeclared = channel.queueDeclare( + name = "", + durable = false, + exclusive = true, + autoDelete = true, + arguments = emptyMap() + ) + val queueName = queueDeclared.queueName + println(" [$subscriberName] Created temporary queue: $queueName") + + // Bind the queue to the exchange with each severity + for (severity in severities) { + channel.queueBind( + queue = queueName, + exchange = "direct_logs", + routingKey = severity // Only receive messages with this routing key + ) + println(" [$subscriberName] Binding queue to '$severity'") + } + println(" [$subscriberName] Waiting for ${severities.joinToString(", ")} logs. To exit press CTRL+C") + + // Consume messages + val consumer = channel.basicConsume(queueName, noAck = true) + + for (delivery in consumer) { + val routingKey = delivery.message.routingKey + val message = delivery.message.body.decodeToString() + println(" [$subscriberName] Received '$routingKey':'$message'") + } + + channel.close() + connection.close() +} diff --git a/kotlin/src/commonMain/kotlin/Topics.kt b/kotlin/src/commonMain/kotlin/Topics.kt new file mode 100644 index 00000000..aa158ee7 --- /dev/null +++ b/kotlin/src/commonMain/kotlin/Topics.kt @@ -0,0 +1,92 @@ +import dev.kourier.amqp.BuiltinExchangeType +import dev.kourier.amqp.Properties +import dev.kourier.amqp.connection.amqpConfig +import dev.kourier.amqp.connection.createAMQPConnection +import io.ktor.utils.io.core.* +import kotlinx.coroutines.* + +suspend fun emitLogTopic(coroutineScope: CoroutineScope, routingKey: String, message: String) { + val config = amqpConfig { + server { + host = "localhost" + } + } + val connection = createAMQPConnection(coroutineScope, config) + val channel = connection.openChannel() + + // Declare a topic exchange - routes based on pattern matching + channel.exchangeDeclare( + "topic_logs", + BuiltinExchangeType.TOPIC, + durable = false, + autoDelete = false, + internal = false, + arguments = emptyMap() + ) + + // Publish with a topic routing key (dot-separated words) + channel.basicPublish( + message.toByteArray(), + exchange = "topic_logs", + routingKey = routingKey, // Format: . or similar + properties = Properties() + ) + println(" [x] Sent '$routingKey':'$message'") + + channel.close() + connection.close() +} + +suspend fun receiveLogsTopic(coroutineScope: CoroutineScope, subscriberName: String, bindingKeys: List) { + val config = amqpConfig { + server { + host = "localhost" + } + } + val connection = createAMQPConnection(coroutineScope, config) + val channel = connection.openChannel() + + // Declare the same topic exchange + channel.exchangeDeclare( + "topic_logs", + BuiltinExchangeType.TOPIC, + durable = false, + autoDelete = false, + internal = false, + arguments = emptyMap() + ) + + // Declare a temporary queue + val queueDeclared = channel.queueDeclare( + name = "", + durable = false, + exclusive = true, + autoDelete = true, + arguments = emptyMap() + ) + val queueName = queueDeclared.queueName + println(" [$subscriberName] Created temporary queue: $queueName") + + // Bind the queue with topic patterns + for (bindingKey in bindingKeys) { + channel.queueBind( + queue = queueName, + exchange = "topic_logs", + routingKey = bindingKey // Pattern with wildcards: * or # + ) + println(" [$subscriberName] Binding queue to pattern '$bindingKey'") + } + println(" [$subscriberName] Waiting for messages matching ${bindingKeys.joinToString(", ")}") + + // Consume messages + val consumer = channel.basicConsume(queueName, noAck = true) + + for (delivery in consumer) { + val routingKey = delivery.message.routingKey + val message = delivery.message.body.decodeToString() + println(" [$subscriberName] Received '$routingKey':'$message'") + } + + channel.close() + connection.close() +} diff --git a/kotlin/src/commonMain/kotlin/WorkQueues.kt b/kotlin/src/commonMain/kotlin/WorkQueues.kt new file mode 100644 index 00000000..633a90af --- /dev/null +++ b/kotlin/src/commonMain/kotlin/WorkQueues.kt @@ -0,0 +1,96 @@ +import dev.kourier.amqp.Field +import dev.kourier.amqp.connection.amqpConfig +import dev.kourier.amqp.connection.createAMQPConnection +import dev.kourier.amqp.properties +import io.ktor.utils.io.core.* +import kotlinx.coroutines.* + +suspend fun newTask(coroutineScope: CoroutineScope, message: String) { + val config = amqpConfig { + server { + host = "localhost" + } + } + val connection = createAMQPConnection(coroutineScope, config) + val channel = connection.openChannel() + + // Declare a durable queue to survive broker restarts + channel.queueDeclare( + "task_queue", + durable = true, // Queue survives broker restart + exclusive = false, + autoDelete = false, + arguments = mapOf("x-queue-type" to Field.LongString("quorum")) + ) + + // Mark messages as persistent (deliveryMode = 2) + val properties = properties { + deliveryMode = 2u // Persistent message + } + + channel.basicPublish( + message.toByteArray(), + exchange = "", + routingKey = "task_queue", + properties = properties + ) + println(" [x] Sent '$message'") + + channel.close() + connection.close() +} + +suspend fun worker(coroutineScope: CoroutineScope, workerName: String) { + val config = amqpConfig { + server { + host = "localhost" + } + } + val connection = createAMQPConnection(coroutineScope, config) + val channel = connection.openChannel() + + // Declare the same durable queue + channel.queueDeclare( + "task_queue", + durable = true, + exclusive = false, + autoDelete = false, + arguments = mapOf("x-queue-type" to Field.LongString("quorum")) + ) + println(" [$workerName] Waiting for messages. To exit press CTRL+C") + + // Fair dispatch: don't give more than one message to a worker at a time + channel.basicQos(count = 1u, global = false) + + // Consume with manual acknowledgment (noAck = false) + val consumer = channel.basicConsume("task_queue", noAck = false) + + for (delivery in consumer) { + val message = delivery.message.body.decodeToString() + println(" [$workerName] Received '$message'") + + try { + // Simulate work - each dot represents 1 second of work + doWork(message) + println(" [$workerName] Done") + } finally { + // Manual acknowledgment - message is removed from queue + channel.basicAck(delivery.message, multiple = false) + } + } + + channel.close() + connection.close() +} + +/** + * Simulates time-consuming work. + * Each dot in the task string represents 1 second of work. + */ +private suspend fun doWork(task: String) { + for (ch in task) { + if (ch == '.') { + delay(1000) // Sleep for 1 second per dot + } + } +} diff --git a/objective-c/.gitignore b/objective-c/.gitignore deleted file mode 100644 index 3170b5fd..00000000 --- a/objective-c/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -tutorial?/Carthage/Build -tutorial?/Carthage/Checkouts -*xcuserdata* diff --git a/objective-c/README.md b/objective-c/README.md deleted file mode 100644 index 4914383e..00000000 --- a/objective-c/README.md +++ /dev/null @@ -1,52 +0,0 @@ -# Objective-C code for RabbitMQ tutorials - -Objective-C code examples for the [RabbitMQ tutorials](http://www.rabbitmq.com/getstarted.html). - -## Requirements - -To run this code you need -[Carthage](https://github.com/Carthage/Carthage) to pull down dependencies, -which include the -[Objective-C client](https://github.com/rabbitmq/rabbitmq-objc-client) itself. - -If you have Homebrew installed, simply: - -```sh -brew install carthage -``` - -You also need a running RabbitMQ server on localhost. - -## Installation - -Each tutorial has its own Xcode project. Before the projects can be run, you -need to download and build their dependencies. - -For example, to install tutorial 1: - -```sh -cd tutorial1 -carthage bootstrap --platform iOS -``` - -You should then be able to open [the project](tutorial1/tutorial1.xcodeproj) in Xcode and hit Run. Output is -NSLogged. - -See [ViewController.m](tutorial1/tutorial1/ViewController.m) for the -implementation (each tutorial has its own `ViewController.m`). - -## Running the tutorials on master - -If you're QAing a change, or just want to run these tutorials on the master version of the client, follow these steps. - -### Edit `Cartfile` - -Change the version number to the word "master" - -### Clear Carthage cache and update - -`rm -rf ~/Library/Caches/org.carthage.CarthageKit && carthage update --platform iOS` - -### Rebuild the project in Xcode - -If there have been breaking changes, you might now need to make changes to the tutorial. diff --git a/objective-c/bump_dependencies.sh b/objective-c/bump_dependencies.sh deleted file mode 100755 index 03579651..00000000 --- a/objective-c/bump_dependencies.sh +++ /dev/null @@ -1,12 +0,0 @@ -#!/bin/bash - -set -ex - -rm -rf ~/Library/Caches/org.carthage.CarthageKit - -for tutdir in `ls | grep "tutorial[1-9]"` -do - cd $tutdir - carthage update --platform iOS - cd .. -done diff --git a/objective-c/tutorial1/Cartfile b/objective-c/tutorial1/Cartfile deleted file mode 100644 index 38483e08..00000000 --- a/objective-c/tutorial1/Cartfile +++ /dev/null @@ -1 +0,0 @@ -github "rabbitmq/rabbitmq-objc-client" "v0.10.0" diff --git a/objective-c/tutorial1/Cartfile.resolved b/objective-c/tutorial1/Cartfile.resolved deleted file mode 100644 index 750d6791..00000000 --- a/objective-c/tutorial1/Cartfile.resolved +++ /dev/null @@ -1,3 +0,0 @@ -github "robbiehanson/CocoaAsyncSocket" "7.5.1" -github "jeffh/JKVValue" "v1.3.2" -github "rabbitmq/rabbitmq-objc-client" "v0.10.0" diff --git a/objective-c/tutorial1/tutorial1.xcodeproj/project.pbxproj b/objective-c/tutorial1/tutorial1.xcodeproj/project.pbxproj deleted file mode 100644 index 9b602cb2..00000000 --- a/objective-c/tutorial1/tutorial1.xcodeproj/project.pbxproj +++ /dev/null @@ -1,341 +0,0 @@ -// !$*UTF8*$! -{ - archiveVersion = 1; - classes = { - }; - objectVersion = 46; - objects = { - -/* Begin PBXBuildFile section */ - AE40C7301CB2719500CC7B97 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = AE40C72F1CB2719500CC7B97 /* main.m */; }; - AE40C7331CB2719500CC7B97 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = AE40C7321CB2719500CC7B97 /* AppDelegate.m */; }; - AE40C7361CB2719500CC7B97 /* ViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = AE40C7351CB2719500CC7B97 /* ViewController.m */; }; - AE40C7391CB2719500CC7B97 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = AE40C7371CB2719500CC7B97 /* Main.storyboard */; }; - AE40C73B1CB2719500CC7B97 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = AE40C73A1CB2719500CC7B97 /* Assets.xcassets */; }; - AE40C73E1CB2719500CC7B97 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = AE40C73C1CB2719500CC7B97 /* LaunchScreen.storyboard */; }; - AE40C7931CB504BF00CC7B97 /* RMQClient.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AE40C7921CB504BF00CC7B97 /* RMQClient.framework */; }; - AE40C7941CB504C700CC7B97 /* RMQClient.framework in CopyFiles */ = {isa = PBXBuildFile; fileRef = AE40C7921CB504BF00CC7B97 /* RMQClient.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; -/* End PBXBuildFile section */ - -/* Begin PBXCopyFilesBuildPhase section */ - AE40C7471CB272C000CC7B97 /* CopyFiles */ = { - isa = PBXCopyFilesBuildPhase; - buildActionMask = 2147483647; - dstPath = ""; - dstSubfolderSpec = 10; - files = ( - AE40C7941CB504C700CC7B97 /* RMQClient.framework in CopyFiles */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXCopyFilesBuildPhase section */ - -/* Begin PBXFileReference section */ - AE40C72B1CB2719500CC7B97 /* tutorial1.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = tutorial1.app; sourceTree = BUILT_PRODUCTS_DIR; }; - AE40C72F1CB2719500CC7B97 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; - AE40C7311CB2719500CC7B97 /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; - AE40C7321CB2719500CC7B97 /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; - AE40C7341CB2719500CC7B97 /* ViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ViewController.h; sourceTree = ""; }; - AE40C7351CB2719500CC7B97 /* ViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ViewController.m; sourceTree = ""; }; - AE40C7381CB2719500CC7B97 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; - AE40C73A1CB2719500CC7B97 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; - AE40C73D1CB2719500CC7B97 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; - AE40C73F1CB2719500CC7B97 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - AE40C7921CB504BF00CC7B97 /* RMQClient.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = RMQClient.framework; path = Carthage/Build/iOS/RMQClient.framework; sourceTree = ""; }; -/* End PBXFileReference section */ - -/* Begin PBXFrameworksBuildPhase section */ - AE40C7281CB2719500CC7B97 /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - AE40C7931CB504BF00CC7B97 /* RMQClient.framework in Frameworks */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXFrameworksBuildPhase section */ - -/* Begin PBXGroup section */ - AE40C7221CB2719500CC7B97 = { - isa = PBXGroup; - children = ( - AE40C7921CB504BF00CC7B97 /* RMQClient.framework */, - AE40C72D1CB2719500CC7B97 /* tutorial1 */, - AE40C72C1CB2719500CC7B97 /* Products */, - ); - sourceTree = ""; - }; - AE40C72C1CB2719500CC7B97 /* Products */ = { - isa = PBXGroup; - children = ( - AE40C72B1CB2719500CC7B97 /* tutorial1.app */, - ); - name = Products; - sourceTree = ""; - }; - AE40C72D1CB2719500CC7B97 /* tutorial1 */ = { - isa = PBXGroup; - children = ( - AE40C7311CB2719500CC7B97 /* AppDelegate.h */, - AE40C7321CB2719500CC7B97 /* AppDelegate.m */, - AE40C7341CB2719500CC7B97 /* ViewController.h */, - AE40C7351CB2719500CC7B97 /* ViewController.m */, - AE40C7371CB2719500CC7B97 /* Main.storyboard */, - AE40C73A1CB2719500CC7B97 /* Assets.xcassets */, - AE40C73C1CB2719500CC7B97 /* LaunchScreen.storyboard */, - AE40C73F1CB2719500CC7B97 /* Info.plist */, - AE40C72E1CB2719500CC7B97 /* Supporting Files */, - ); - path = tutorial1; - sourceTree = ""; - }; - AE40C72E1CB2719500CC7B97 /* Supporting Files */ = { - isa = PBXGroup; - children = ( - AE40C72F1CB2719500CC7B97 /* main.m */, - ); - name = "Supporting Files"; - sourceTree = ""; - }; -/* End PBXGroup section */ - -/* Begin PBXNativeTarget section */ - AE40C72A1CB2719500CC7B97 /* tutorial1 */ = { - isa = PBXNativeTarget; - buildConfigurationList = AE40C7421CB2719500CC7B97 /* Build configuration list for PBXNativeTarget "tutorial1" */; - buildPhases = ( - AE40C7271CB2719500CC7B97 /* Sources */, - AE40C7281CB2719500CC7B97 /* Frameworks */, - AE40C7291CB2719500CC7B97 /* Resources */, - AE40C7471CB272C000CC7B97 /* CopyFiles */, - ); - buildRules = ( - ); - dependencies = ( - ); - name = tutorial1; - productName = tutorial1; - productReference = AE40C72B1CB2719500CC7B97 /* tutorial1.app */; - productType = "com.apple.product-type.application"; - }; -/* End PBXNativeTarget section */ - -/* Begin PBXProject section */ - AE40C7231CB2719500CC7B97 /* Project object */ = { - isa = PBXProject; - attributes = { - LastUpgradeCheck = 0820; - ORGANIZATIONNAME = RabbitMQ; - TargetAttributes = { - AE40C72A1CB2719500CC7B97 = { - CreatedOnToolsVersion = 7.3; - }; - }; - }; - buildConfigurationList = AE40C7261CB2719500CC7B97 /* Build configuration list for PBXProject "tutorial1" */; - compatibilityVersion = "Xcode 3.2"; - developmentRegion = English; - hasScannedForEncodings = 0; - knownRegions = ( - en, - Base, - ); - mainGroup = AE40C7221CB2719500CC7B97; - productRefGroup = AE40C72C1CB2719500CC7B97 /* Products */; - projectDirPath = ""; - projectRoot = ""; - targets = ( - AE40C72A1CB2719500CC7B97 /* tutorial1 */, - ); - }; -/* End PBXProject section */ - -/* Begin PBXResourcesBuildPhase section */ - AE40C7291CB2719500CC7B97 /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - AE40C73E1CB2719500CC7B97 /* LaunchScreen.storyboard in Resources */, - AE40C73B1CB2719500CC7B97 /* Assets.xcassets in Resources */, - AE40C7391CB2719500CC7B97 /* Main.storyboard in Resources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXResourcesBuildPhase section */ - -/* Begin PBXSourcesBuildPhase section */ - AE40C7271CB2719500CC7B97 /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - AE40C7361CB2719500CC7B97 /* ViewController.m in Sources */, - AE40C7331CB2719500CC7B97 /* AppDelegate.m in Sources */, - AE40C7301CB2719500CC7B97 /* main.m in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXSourcesBuildPhase section */ - -/* Begin PBXVariantGroup section */ - AE40C7371CB2719500CC7B97 /* Main.storyboard */ = { - isa = PBXVariantGroup; - children = ( - AE40C7381CB2719500CC7B97 /* Base */, - ); - name = Main.storyboard; - sourceTree = ""; - }; - AE40C73C1CB2719500CC7B97 /* LaunchScreen.storyboard */ = { - isa = PBXVariantGroup; - children = ( - AE40C73D1CB2719500CC7B97 /* Base */, - ); - name = LaunchScreen.storyboard; - sourceTree = ""; - }; -/* End PBXVariantGroup section */ - -/* Begin XCBuildConfiguration section */ - AE40C7401CB2719500CC7B97 /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_ANALYZER_NONNULL = YES; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - COPY_PHASE_STRIP = NO; - DEBUG_INFORMATION_FORMAT = dwarf; - ENABLE_STRICT_OBJC_MSGSEND = YES; - ENABLE_TESTABILITY = YES; - GCC_C_LANGUAGE_STANDARD = gnu99; - GCC_DYNAMIC_NO_PIC = NO; - GCC_NO_COMMON_BLOCKS = YES; - GCC_OPTIMIZATION_LEVEL = 0; - GCC_PREPROCESSOR_DEFINITIONS = ( - "DEBUG=1", - "$(inherited)", - ); - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 10.0; - MTL_ENABLE_DEBUG_INFO = YES; - ONLY_ACTIVE_ARCH = YES; - SDKROOT = iphoneos; - TARGETED_DEVICE_FAMILY = "1,2"; - }; - name = Debug; - }; - AE40C7411CB2719500CC7B97 /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_ANALYZER_NONNULL = YES; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - COPY_PHASE_STRIP = NO; - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - ENABLE_NS_ASSERTIONS = NO; - ENABLE_STRICT_OBJC_MSGSEND = YES; - GCC_C_LANGUAGE_STANDARD = gnu99; - GCC_NO_COMMON_BLOCKS = YES; - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 10.0; - MTL_ENABLE_DEBUG_INFO = NO; - SDKROOT = iphoneos; - TARGETED_DEVICE_FAMILY = "1,2"; - VALIDATE_PRODUCT = YES; - }; - name = Release; - }; - AE40C7431CB2719500CC7B97 /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - FRAMEWORK_SEARCH_PATHS = ( - "$(inherited)", - "$(PROJECT_DIR)/Carthage/Build/iOS", - ); - INFOPLIST_FILE = tutorial1/Info.plist; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; - PRODUCT_BUNDLE_IDENTIFIER = com.rabbitmq.tutorial1; - PRODUCT_NAME = "$(TARGET_NAME)"; - }; - name = Debug; - }; - AE40C7441CB2719500CC7B97 /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - FRAMEWORK_SEARCH_PATHS = ( - "$(inherited)", - "$(PROJECT_DIR)/Carthage/Build/iOS", - ); - INFOPLIST_FILE = tutorial1/Info.plist; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; - PRODUCT_BUNDLE_IDENTIFIER = com.rabbitmq.tutorial1; - PRODUCT_NAME = "$(TARGET_NAME)"; - }; - name = Release; - }; -/* End XCBuildConfiguration section */ - -/* Begin XCConfigurationList section */ - AE40C7261CB2719500CC7B97 /* Build configuration list for PBXProject "tutorial1" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - AE40C7401CB2719500CC7B97 /* Debug */, - AE40C7411CB2719500CC7B97 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - AE40C7421CB2719500CC7B97 /* Build configuration list for PBXNativeTarget "tutorial1" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - AE40C7431CB2719500CC7B97 /* Debug */, - AE40C7441CB2719500CC7B97 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; -/* End XCConfigurationList section */ - }; - rootObject = AE40C7231CB2719500CC7B97 /* Project object */; -} diff --git a/objective-c/tutorial1/tutorial1.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/objective-c/tutorial1/tutorial1.xcodeproj/project.xcworkspace/contents.xcworkspacedata deleted file mode 100644 index fc07c512..00000000 --- a/objective-c/tutorial1/tutorial1.xcodeproj/project.xcworkspace/contents.xcworkspacedata +++ /dev/null @@ -1,7 +0,0 @@ - - - - - diff --git a/objective-c/tutorial1/tutorial1/AppDelegate.h b/objective-c/tutorial1/tutorial1/AppDelegate.h deleted file mode 100644 index 3621106c..00000000 --- a/objective-c/tutorial1/tutorial1/AppDelegate.h +++ /dev/null @@ -1,9 +0,0 @@ -#import - -@interface AppDelegate : UIResponder - -@property (strong, nonatomic) UIWindow *window; - - -@end - diff --git a/objective-c/tutorial1/tutorial1/AppDelegate.m b/objective-c/tutorial1/tutorial1/AppDelegate.m deleted file mode 100644 index dd0b8824..00000000 --- a/objective-c/tutorial1/tutorial1/AppDelegate.m +++ /dev/null @@ -1,37 +0,0 @@ -#import "AppDelegate.h" - -@interface AppDelegate () - -@end - -@implementation AppDelegate - - -- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { - // Override point for customization after application launch. - return YES; -} - -- (void)applicationWillResignActive:(UIApplication *)application { - // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. - // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. -} - -- (void)applicationDidEnterBackground:(UIApplication *)application { - // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. - // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. -} - -- (void)applicationWillEnterForeground:(UIApplication *)application { - // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background. -} - -- (void)applicationDidBecomeActive:(UIApplication *)application { - // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. -} - -- (void)applicationWillTerminate:(UIApplication *)application { - // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. -} - -@end diff --git a/objective-c/tutorial1/tutorial1/Assets.xcassets/AppIcon.appiconset/Contents.json b/objective-c/tutorial1/tutorial1/Assets.xcassets/AppIcon.appiconset/Contents.json deleted file mode 100644 index 1d060ed2..00000000 --- a/objective-c/tutorial1/tutorial1/Assets.xcassets/AppIcon.appiconset/Contents.json +++ /dev/null @@ -1,93 +0,0 @@ -{ - "images" : [ - { - "idiom" : "iphone", - "size" : "20x20", - "scale" : "2x" - }, - { - "idiom" : "iphone", - "size" : "20x20", - "scale" : "3x" - }, - { - "idiom" : "iphone", - "size" : "29x29", - "scale" : "2x" - }, - { - "idiom" : "iphone", - "size" : "29x29", - "scale" : "3x" - }, - { - "idiom" : "iphone", - "size" : "40x40", - "scale" : "2x" - }, - { - "idiom" : "iphone", - "size" : "40x40", - "scale" : "3x" - }, - { - "idiom" : "iphone", - "size" : "60x60", - "scale" : "2x" - }, - { - "idiom" : "iphone", - "size" : "60x60", - "scale" : "3x" - }, - { - "idiom" : "ipad", - "size" : "20x20", - "scale" : "1x" - }, - { - "idiom" : "ipad", - "size" : "20x20", - "scale" : "2x" - }, - { - "idiom" : "ipad", - "size" : "29x29", - "scale" : "1x" - }, - { - "idiom" : "ipad", - "size" : "29x29", - "scale" : "2x" - }, - { - "idiom" : "ipad", - "size" : "40x40", - "scale" : "1x" - }, - { - "idiom" : "ipad", - "size" : "40x40", - "scale" : "2x" - }, - { - "idiom" : "ipad", - "size" : "76x76", - "scale" : "1x" - }, - { - "idiom" : "ipad", - "size" : "76x76", - "scale" : "2x" - }, - { - "idiom" : "ipad", - "size" : "83.5x83.5", - "scale" : "2x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/objective-c/tutorial1/tutorial1/Base.lproj/LaunchScreen.storyboard b/objective-c/tutorial1/tutorial1/Base.lproj/LaunchScreen.storyboard deleted file mode 100644 index 323bd431..00000000 --- a/objective-c/tutorial1/tutorial1/Base.lproj/LaunchScreen.storyboard +++ /dev/null @@ -1,27 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/objective-c/tutorial1/tutorial1/Base.lproj/Main.storyboard b/objective-c/tutorial1/tutorial1/Base.lproj/Main.storyboard deleted file mode 100644 index 598e7a45..00000000 --- a/objective-c/tutorial1/tutorial1/Base.lproj/Main.storyboard +++ /dev/null @@ -1,30 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/objective-c/tutorial1/tutorial1/Info.plist b/objective-c/tutorial1/tutorial1/Info.plist deleted file mode 100644 index fd284d97..00000000 --- a/objective-c/tutorial1/tutorial1/Info.plist +++ /dev/null @@ -1,49 +0,0 @@ - - - - - CFBundleDevelopmentRegion - en - CFBundleDisplayName - tutorial1 - CFBundleExecutable - $(EXECUTABLE_NAME) - CFBundleIdentifier - $(PRODUCT_BUNDLE_IDENTIFIER) - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - $(PRODUCT_NAME) - CFBundlePackageType - APPL - CFBundleShortVersionString - 1.0 - CFBundleSignature - ???? - CFBundleVersion - 1 - LSRequiresIPhoneOS - - UILaunchStoryboardName - LaunchScreen - UIMainStoryboardFile - Main - UIRequiredDeviceCapabilities - - armv7 - - UISupportedInterfaceOrientations - - UIInterfaceOrientationPortrait - UIInterfaceOrientationLandscapeLeft - UIInterfaceOrientationLandscapeRight - - UISupportedInterfaceOrientations~ipad - - UIInterfaceOrientationPortrait - UIInterfaceOrientationPortraitUpsideDown - UIInterfaceOrientationLandscapeLeft - UIInterfaceOrientationLandscapeRight - - - diff --git a/objective-c/tutorial1/tutorial1/ViewController.h b/objective-c/tutorial1/tutorial1/ViewController.h deleted file mode 100644 index a79652b5..00000000 --- a/objective-c/tutorial1/tutorial1/ViewController.h +++ /dev/null @@ -1,7 +0,0 @@ -#import - -@interface ViewController : UIViewController - - -@end - diff --git a/objective-c/tutorial1/tutorial1/ViewController.m b/objective-c/tutorial1/tutorial1/ViewController.m deleted file mode 100644 index 9355135e..00000000 --- a/objective-c/tutorial1/tutorial1/ViewController.m +++ /dev/null @@ -1,44 +0,0 @@ -#import "ViewController.h" -@import RMQClient; - -@interface ViewController () -@end - -@implementation ViewController - -- (void)viewDidLoad { - [super viewDidLoad]; - [self send]; - [self receive]; -} - -- (void)send { - NSLog(@"Attempting to connect to local RabbitMQ broker"); - RMQConnection *conn = [[RMQConnection alloc] initWithDelegate:[RMQConnectionDelegateLogger new]]; - [conn start]; - - id ch = [conn createChannel]; - - RMQQueue *q = [ch queue:@"hello"]; - - [ch.defaultExchange publish:[@"Hello World!" dataUsingEncoding:NSUTF8StringEncoding] routingKey:q.name]; - NSLog(@"Sent 'Hello World!'"); - - [conn close]; -} - -- (void)receive { - NSLog(@"Attempting to connect to local RabbitMQ broker"); - RMQConnection *conn = [[RMQConnection alloc] initWithDelegate:[RMQConnectionDelegateLogger new]]; - [conn start]; - - id ch = [conn createChannel]; - - RMQQueue *q = [ch queue:@"hello"]; - NSLog(@"Waiting for messages."); - [q subscribe:^(RMQMessage * _Nonnull message) { - NSLog(@"Received %@", [[NSString alloc] initWithData:message.body encoding:NSUTF8StringEncoding]); - }]; -} - -@end diff --git a/objective-c/tutorial1/tutorial1/main.m b/objective-c/tutorial1/tutorial1/main.m deleted file mode 100644 index 2d94b33f..00000000 --- a/objective-c/tutorial1/tutorial1/main.m +++ /dev/null @@ -1,16 +0,0 @@ -// -// main.m -// tutorial1 -// -// Created by Pivotal on 04/04/2016. -// Copyright © 2016 RabbitMQ. All rights reserved. -// - -#import -#import "AppDelegate.h" - -int main(int argc, char * argv[]) { - @autoreleasepool { - return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); - } -} diff --git a/objective-c/tutorial2/Cartfile b/objective-c/tutorial2/Cartfile deleted file mode 100644 index 38483e08..00000000 --- a/objective-c/tutorial2/Cartfile +++ /dev/null @@ -1 +0,0 @@ -github "rabbitmq/rabbitmq-objc-client" "v0.10.0" diff --git a/objective-c/tutorial2/Cartfile.resolved b/objective-c/tutorial2/Cartfile.resolved deleted file mode 100644 index 750d6791..00000000 --- a/objective-c/tutorial2/Cartfile.resolved +++ /dev/null @@ -1,3 +0,0 @@ -github "robbiehanson/CocoaAsyncSocket" "7.5.1" -github "jeffh/JKVValue" "v1.3.2" -github "rabbitmq/rabbitmq-objc-client" "v0.10.0" diff --git a/objective-c/tutorial2/tutorial2.xcodeproj/project.pbxproj b/objective-c/tutorial2/tutorial2.xcodeproj/project.pbxproj deleted file mode 100644 index 02afd417..00000000 --- a/objective-c/tutorial2/tutorial2.xcodeproj/project.pbxproj +++ /dev/null @@ -1,341 +0,0 @@ -// !$*UTF8*$! -{ - archiveVersion = 1; - classes = { - }; - objectVersion = 48; - objects = { - -/* Begin PBXBuildFile section */ - AE40C7661CB2B50B00CC7B97 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = AE40C7651CB2B50B00CC7B97 /* main.m */; }; - AE40C7691CB2B50B00CC7B97 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = AE40C7681CB2B50B00CC7B97 /* AppDelegate.m */; }; - AE40C76C1CB2B50B00CC7B97 /* ViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = AE40C76B1CB2B50B00CC7B97 /* ViewController.m */; }; - AE40C76F1CB2B50B00CC7B97 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = AE40C76D1CB2B50B00CC7B97 /* Main.storyboard */; }; - AE40C7711CB2B50B00CC7B97 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = AE40C7701CB2B50B00CC7B97 /* Assets.xcassets */; }; - AE40C7741CB2B50B00CC7B97 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = AE40C7721CB2B50B00CC7B97 /* LaunchScreen.storyboard */; }; - AE40C77C1CB2B5CD00CC7B97 /* RMQClient.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AE40C77B1CB2B5CD00CC7B97 /* RMQClient.framework */; }; - AE40C77E1CB2B5E200CC7B97 /* RMQClient.framework in CopyFiles */ = {isa = PBXBuildFile; fileRef = AE40C77B1CB2B5CD00CC7B97 /* RMQClient.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; -/* End PBXBuildFile section */ - -/* Begin PBXCopyFilesBuildPhase section */ - AE40C77D1CB2B5D800CC7B97 /* CopyFiles */ = { - isa = PBXCopyFilesBuildPhase; - buildActionMask = 2147483647; - dstPath = ""; - dstSubfolderSpec = 10; - files = ( - AE40C77E1CB2B5E200CC7B97 /* RMQClient.framework in CopyFiles */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXCopyFilesBuildPhase section */ - -/* Begin PBXFileReference section */ - AE40C7611CB2B50B00CC7B97 /* tutorial2.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = tutorial2.app; sourceTree = BUILT_PRODUCTS_DIR; }; - AE40C7651CB2B50B00CC7B97 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; - AE40C7671CB2B50B00CC7B97 /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; - AE40C7681CB2B50B00CC7B97 /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; - AE40C76A1CB2B50B00CC7B97 /* ViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ViewController.h; sourceTree = ""; }; - AE40C76B1CB2B50B00CC7B97 /* ViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ViewController.m; sourceTree = ""; }; - AE40C76E1CB2B50B00CC7B97 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; - AE40C7701CB2B50B00CC7B97 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; - AE40C7731CB2B50B00CC7B97 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; - AE40C7751CB2B50B00CC7B97 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - AE40C77B1CB2B5CD00CC7B97 /* RMQClient.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = RMQClient.framework; path = Carthage/Build/iOS/RMQClient.framework; sourceTree = ""; }; -/* End PBXFileReference section */ - -/* Begin PBXFrameworksBuildPhase section */ - AE40C75E1CB2B50B00CC7B97 /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - AE40C77C1CB2B5CD00CC7B97 /* RMQClient.framework in Frameworks */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXFrameworksBuildPhase section */ - -/* Begin PBXGroup section */ - AE40C7581CB2B50B00CC7B97 = { - isa = PBXGroup; - children = ( - AE40C77B1CB2B5CD00CC7B97 /* RMQClient.framework */, - AE40C7631CB2B50B00CC7B97 /* tutorial2 */, - AE40C7621CB2B50B00CC7B97 /* Products */, - ); - sourceTree = ""; - }; - AE40C7621CB2B50B00CC7B97 /* Products */ = { - isa = PBXGroup; - children = ( - AE40C7611CB2B50B00CC7B97 /* tutorial2.app */, - ); - name = Products; - sourceTree = ""; - }; - AE40C7631CB2B50B00CC7B97 /* tutorial2 */ = { - isa = PBXGroup; - children = ( - AE40C7671CB2B50B00CC7B97 /* AppDelegate.h */, - AE40C7681CB2B50B00CC7B97 /* AppDelegate.m */, - AE40C76A1CB2B50B00CC7B97 /* ViewController.h */, - AE40C76B1CB2B50B00CC7B97 /* ViewController.m */, - AE40C76D1CB2B50B00CC7B97 /* Main.storyboard */, - AE40C7701CB2B50B00CC7B97 /* Assets.xcassets */, - AE40C7721CB2B50B00CC7B97 /* LaunchScreen.storyboard */, - AE40C7751CB2B50B00CC7B97 /* Info.plist */, - AE40C7641CB2B50B00CC7B97 /* Supporting Files */, - ); - path = tutorial2; - sourceTree = ""; - }; - AE40C7641CB2B50B00CC7B97 /* Supporting Files */ = { - isa = PBXGroup; - children = ( - AE40C7651CB2B50B00CC7B97 /* main.m */, - ); - name = "Supporting Files"; - sourceTree = ""; - }; -/* End PBXGroup section */ - -/* Begin PBXNativeTarget section */ - AE40C7601CB2B50B00CC7B97 /* tutorial2 */ = { - isa = PBXNativeTarget; - buildConfigurationList = AE40C7781CB2B50B00CC7B97 /* Build configuration list for PBXNativeTarget "tutorial2" */; - buildPhases = ( - AE40C75D1CB2B50B00CC7B97 /* Sources */, - AE40C75E1CB2B50B00CC7B97 /* Frameworks */, - AE40C75F1CB2B50B00CC7B97 /* Resources */, - AE40C77D1CB2B5D800CC7B97 /* CopyFiles */, - ); - buildRules = ( - ); - dependencies = ( - ); - name = tutorial2; - productName = tutorial2; - productReference = AE40C7611CB2B50B00CC7B97 /* tutorial2.app */; - productType = "com.apple.product-type.application"; - }; -/* End PBXNativeTarget section */ - -/* Begin PBXProject section */ - AE40C7591CB2B50B00CC7B97 /* Project object */ = { - isa = PBXProject; - attributes = { - LastUpgradeCheck = 0820; - ORGANIZATIONNAME = RabbitMQ; - TargetAttributes = { - AE40C7601CB2B50B00CC7B97 = { - CreatedOnToolsVersion = 7.3; - }; - }; - }; - buildConfigurationList = AE40C75C1CB2B50B00CC7B97 /* Build configuration list for PBXProject "tutorial2" */; - compatibilityVersion = "Xcode 8.0"; - developmentRegion = English; - hasScannedForEncodings = 0; - knownRegions = ( - en, - Base, - ); - mainGroup = AE40C7581CB2B50B00CC7B97; - productRefGroup = AE40C7621CB2B50B00CC7B97 /* Products */; - projectDirPath = ""; - projectRoot = ""; - targets = ( - AE40C7601CB2B50B00CC7B97 /* tutorial2 */, - ); - }; -/* End PBXProject section */ - -/* Begin PBXResourcesBuildPhase section */ - AE40C75F1CB2B50B00CC7B97 /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - AE40C7741CB2B50B00CC7B97 /* LaunchScreen.storyboard in Resources */, - AE40C7711CB2B50B00CC7B97 /* Assets.xcassets in Resources */, - AE40C76F1CB2B50B00CC7B97 /* Main.storyboard in Resources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXResourcesBuildPhase section */ - -/* Begin PBXSourcesBuildPhase section */ - AE40C75D1CB2B50B00CC7B97 /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - AE40C76C1CB2B50B00CC7B97 /* ViewController.m in Sources */, - AE40C7691CB2B50B00CC7B97 /* AppDelegate.m in Sources */, - AE40C7661CB2B50B00CC7B97 /* main.m in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXSourcesBuildPhase section */ - -/* Begin PBXVariantGroup section */ - AE40C76D1CB2B50B00CC7B97 /* Main.storyboard */ = { - isa = PBXVariantGroup; - children = ( - AE40C76E1CB2B50B00CC7B97 /* Base */, - ); - name = Main.storyboard; - sourceTree = ""; - }; - AE40C7721CB2B50B00CC7B97 /* LaunchScreen.storyboard */ = { - isa = PBXVariantGroup; - children = ( - AE40C7731CB2B50B00CC7B97 /* Base */, - ); - name = LaunchScreen.storyboard; - sourceTree = ""; - }; -/* End PBXVariantGroup section */ - -/* Begin XCBuildConfiguration section */ - AE40C7761CB2B50B00CC7B97 /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_ANALYZER_NONNULL = YES; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - COPY_PHASE_STRIP = NO; - DEBUG_INFORMATION_FORMAT = dwarf; - ENABLE_STRICT_OBJC_MSGSEND = YES; - ENABLE_TESTABILITY = YES; - GCC_C_LANGUAGE_STANDARD = gnu99; - GCC_DYNAMIC_NO_PIC = NO; - GCC_NO_COMMON_BLOCKS = YES; - GCC_OPTIMIZATION_LEVEL = 0; - GCC_PREPROCESSOR_DEFINITIONS = ( - "DEBUG=1", - "$(inherited)", - ); - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 10.0; - MTL_ENABLE_DEBUG_INFO = YES; - ONLY_ACTIVE_ARCH = YES; - SDKROOT = iphoneos; - TARGETED_DEVICE_FAMILY = "1,2"; - }; - name = Debug; - }; - AE40C7771CB2B50B00CC7B97 /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_ANALYZER_NONNULL = YES; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - COPY_PHASE_STRIP = NO; - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - ENABLE_NS_ASSERTIONS = NO; - ENABLE_STRICT_OBJC_MSGSEND = YES; - GCC_C_LANGUAGE_STANDARD = gnu99; - GCC_NO_COMMON_BLOCKS = YES; - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 10.0; - MTL_ENABLE_DEBUG_INFO = NO; - SDKROOT = iphoneos; - TARGETED_DEVICE_FAMILY = "1,2"; - VALIDATE_PRODUCT = YES; - }; - name = Release; - }; - AE40C7791CB2B50B00CC7B97 /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - FRAMEWORK_SEARCH_PATHS = ( - "$(inherited)", - "$(PROJECT_DIR)/Carthage/Build/iOS", - ); - INFOPLIST_FILE = tutorial2/Info.plist; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; - PRODUCT_BUNDLE_IDENTIFIER = com.rabbitmq.tutorial2; - PRODUCT_NAME = "$(TARGET_NAME)"; - }; - name = Debug; - }; - AE40C77A1CB2B50B00CC7B97 /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - FRAMEWORK_SEARCH_PATHS = ( - "$(inherited)", - "$(PROJECT_DIR)/Carthage/Build/iOS", - ); - INFOPLIST_FILE = tutorial2/Info.plist; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; - PRODUCT_BUNDLE_IDENTIFIER = com.rabbitmq.tutorial2; - PRODUCT_NAME = "$(TARGET_NAME)"; - }; - name = Release; - }; -/* End XCBuildConfiguration section */ - -/* Begin XCConfigurationList section */ - AE40C75C1CB2B50B00CC7B97 /* Build configuration list for PBXProject "tutorial2" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - AE40C7761CB2B50B00CC7B97 /* Debug */, - AE40C7771CB2B50B00CC7B97 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - AE40C7781CB2B50B00CC7B97 /* Build configuration list for PBXNativeTarget "tutorial2" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - AE40C7791CB2B50B00CC7B97 /* Debug */, - AE40C77A1CB2B50B00CC7B97 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; -/* End XCConfigurationList section */ - }; - rootObject = AE40C7591CB2B50B00CC7B97 /* Project object */; -} diff --git a/objective-c/tutorial2/tutorial2.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/objective-c/tutorial2/tutorial2.xcodeproj/project.xcworkspace/contents.xcworkspacedata deleted file mode 100644 index 7424db3d..00000000 --- a/objective-c/tutorial2/tutorial2.xcodeproj/project.xcworkspace/contents.xcworkspacedata +++ /dev/null @@ -1,7 +0,0 @@ - - - - - diff --git a/objective-c/tutorial2/tutorial2/AppDelegate.h b/objective-c/tutorial2/tutorial2/AppDelegate.h deleted file mode 100644 index 3621106c..00000000 --- a/objective-c/tutorial2/tutorial2/AppDelegate.h +++ /dev/null @@ -1,9 +0,0 @@ -#import - -@interface AppDelegate : UIResponder - -@property (strong, nonatomic) UIWindow *window; - - -@end - diff --git a/objective-c/tutorial2/tutorial2/AppDelegate.m b/objective-c/tutorial2/tutorial2/AppDelegate.m deleted file mode 100644 index dd0b8824..00000000 --- a/objective-c/tutorial2/tutorial2/AppDelegate.m +++ /dev/null @@ -1,37 +0,0 @@ -#import "AppDelegate.h" - -@interface AppDelegate () - -@end - -@implementation AppDelegate - - -- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { - // Override point for customization after application launch. - return YES; -} - -- (void)applicationWillResignActive:(UIApplication *)application { - // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. - // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. -} - -- (void)applicationDidEnterBackground:(UIApplication *)application { - // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. - // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. -} - -- (void)applicationWillEnterForeground:(UIApplication *)application { - // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background. -} - -- (void)applicationDidBecomeActive:(UIApplication *)application { - // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. -} - -- (void)applicationWillTerminate:(UIApplication *)application { - // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. -} - -@end diff --git a/objective-c/tutorial2/tutorial2/Assets.xcassets/AppIcon.appiconset/Contents.json b/objective-c/tutorial2/tutorial2/Assets.xcassets/AppIcon.appiconset/Contents.json deleted file mode 100644 index eeea76c2..00000000 --- a/objective-c/tutorial2/tutorial2/Assets.xcassets/AppIcon.appiconset/Contents.json +++ /dev/null @@ -1,73 +0,0 @@ -{ - "images" : [ - { - "idiom" : "iphone", - "size" : "29x29", - "scale" : "2x" - }, - { - "idiom" : "iphone", - "size" : "29x29", - "scale" : "3x" - }, - { - "idiom" : "iphone", - "size" : "40x40", - "scale" : "2x" - }, - { - "idiom" : "iphone", - "size" : "40x40", - "scale" : "3x" - }, - { - "idiom" : "iphone", - "size" : "60x60", - "scale" : "2x" - }, - { - "idiom" : "iphone", - "size" : "60x60", - "scale" : "3x" - }, - { - "idiom" : "ipad", - "size" : "29x29", - "scale" : "1x" - }, - { - "idiom" : "ipad", - "size" : "29x29", - "scale" : "2x" - }, - { - "idiom" : "ipad", - "size" : "40x40", - "scale" : "1x" - }, - { - "idiom" : "ipad", - "size" : "40x40", - "scale" : "2x" - }, - { - "idiom" : "ipad", - "size" : "76x76", - "scale" : "1x" - }, - { - "idiom" : "ipad", - "size" : "76x76", - "scale" : "2x" - }, - { - "idiom" : "ipad", - "size" : "83.5x83.5", - "scale" : "2x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/objective-c/tutorial2/tutorial2/Base.lproj/LaunchScreen.storyboard b/objective-c/tutorial2/tutorial2/Base.lproj/LaunchScreen.storyboard deleted file mode 100644 index 323bd431..00000000 --- a/objective-c/tutorial2/tutorial2/Base.lproj/LaunchScreen.storyboard +++ /dev/null @@ -1,27 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/objective-c/tutorial2/tutorial2/Base.lproj/Main.storyboard b/objective-c/tutorial2/tutorial2/Base.lproj/Main.storyboard deleted file mode 100644 index d5b34779..00000000 --- a/objective-c/tutorial2/tutorial2/Base.lproj/Main.storyboard +++ /dev/null @@ -1,26 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/objective-c/tutorial2/tutorial2/Info.plist b/objective-c/tutorial2/tutorial2/Info.plist deleted file mode 100644 index 40c6215d..00000000 --- a/objective-c/tutorial2/tutorial2/Info.plist +++ /dev/null @@ -1,47 +0,0 @@ - - - - - CFBundleDevelopmentRegion - en - CFBundleExecutable - $(EXECUTABLE_NAME) - CFBundleIdentifier - $(PRODUCT_BUNDLE_IDENTIFIER) - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - $(PRODUCT_NAME) - CFBundlePackageType - APPL - CFBundleShortVersionString - 1.0 - CFBundleSignature - ???? - CFBundleVersion - 1 - LSRequiresIPhoneOS - - UILaunchStoryboardName - LaunchScreen - UIMainStoryboardFile - Main - UIRequiredDeviceCapabilities - - armv7 - - UISupportedInterfaceOrientations - - UIInterfaceOrientationPortrait - UIInterfaceOrientationLandscapeLeft - UIInterfaceOrientationLandscapeRight - - UISupportedInterfaceOrientations~ipad - - UIInterfaceOrientationPortrait - UIInterfaceOrientationPortraitUpsideDown - UIInterfaceOrientationLandscapeLeft - UIInterfaceOrientationLandscapeRight - - - diff --git a/objective-c/tutorial2/tutorial2/ViewController.h b/objective-c/tutorial2/tutorial2/ViewController.h deleted file mode 100644 index a79652b5..00000000 --- a/objective-c/tutorial2/tutorial2/ViewController.h +++ /dev/null @@ -1,7 +0,0 @@ -#import - -@interface ViewController : UIViewController - - -@end - diff --git a/objective-c/tutorial2/tutorial2/ViewController.m b/objective-c/tutorial2/tutorial2/ViewController.m deleted file mode 100644 index 8752c779..00000000 --- a/objective-c/tutorial2/tutorial2/ViewController.m +++ /dev/null @@ -1,60 +0,0 @@ -#import "ViewController.h" -@import RMQClient; - -@interface ViewController () -@end - -@implementation ViewController - -- (void)viewDidLoad { - [super viewDidLoad]; - [self workerNamed:@"Jack"]; - [self workerNamed:@"Jill"]; - sleep(1); - [self newTask:@"Hello World..."]; - [self newTask:@"Just one this time."]; - [self newTask:@"Five....."]; - [self newTask:@"None"]; - [self newTask:@"Two..dots"]; -} - -- (void)newTask:(NSString *)msg { - RMQConnection *conn = [[RMQConnection alloc] initWithDelegate:[RMQConnectionDelegateLogger new]]; - [conn start]; - - id ch = [conn createChannel]; - - RMQQueue *q = [ch queue:@"task_queue" options:RMQQueueDeclareDurable]; - - NSData *msgData = [msg dataUsingEncoding:NSUTF8StringEncoding]; - [ch.defaultExchange publish:msgData routingKey:q.name persistent:YES]; - NSLog(@"Sent %@", msg); - - [conn close]; -} - -- (void)workerNamed:(NSString *)name { - RMQConnection *conn = [[RMQConnection alloc] initWithDelegate:[RMQConnectionDelegateLogger new]]; - [conn start]; - - id ch = [conn createChannel]; - - RMQQueue *q = [ch queue:@"task_queue" options:RMQQueueDeclareDurable]; - - [ch basicQos:@1 global:NO]; - NSLog(@"%@: Waiting for messages", name); - - RMQBasicConsumeOptions manualAck = RMQBasicConsumeNoOptions; - [q subscribe:manualAck handler:^(RMQMessage * _Nonnull message) { - NSString *messageText = [[NSString alloc] initWithData:message.body encoding:NSUTF8StringEncoding]; - NSLog(@"%@: Received %@", name, messageText); - // imitate some work - unsigned int sleepTime = (unsigned int)[messageText componentsSeparatedByString:@"."].count - 1; - NSLog(@"%@: Sleeping for %u seconds", name, sleepTime); - sleep(sleepTime); - - [ch ack:message.deliveryTag]; - }]; -} - -@end diff --git a/objective-c/tutorial2/tutorial2/main.m b/objective-c/tutorial2/tutorial2/main.m deleted file mode 100644 index 7d088aa4..00000000 --- a/objective-c/tutorial2/tutorial2/main.m +++ /dev/null @@ -1,16 +0,0 @@ -// -// main.m -// tutorial2 -// -// Created by Pivotal on 04/04/2016. -// Copyright © 2016 RabbitMQ. All rights reserved. -// - -#import -#import "AppDelegate.h" - -int main(int argc, char * argv[]) { - @autoreleasepool { - return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); - } -} diff --git a/objective-c/tutorial3/Cartfile b/objective-c/tutorial3/Cartfile deleted file mode 100644 index 38483e08..00000000 --- a/objective-c/tutorial3/Cartfile +++ /dev/null @@ -1 +0,0 @@ -github "rabbitmq/rabbitmq-objc-client" "v0.10.0" diff --git a/objective-c/tutorial3/Cartfile.resolved b/objective-c/tutorial3/Cartfile.resolved deleted file mode 100644 index 750d6791..00000000 --- a/objective-c/tutorial3/Cartfile.resolved +++ /dev/null @@ -1,3 +0,0 @@ -github "robbiehanson/CocoaAsyncSocket" "7.5.1" -github "jeffh/JKVValue" "v1.3.2" -github "rabbitmq/rabbitmq-objc-client" "v0.10.0" diff --git a/objective-c/tutorial3/tutorial3.xcodeproj/project.pbxproj b/objective-c/tutorial3/tutorial3.xcodeproj/project.pbxproj deleted file mode 100644 index d649a941..00000000 --- a/objective-c/tutorial3/tutorial3.xcodeproj/project.pbxproj +++ /dev/null @@ -1,341 +0,0 @@ -// !$*UTF8*$! -{ - archiveVersion = 1; - classes = { - }; - objectVersion = 46; - objects = { - -/* Begin PBXBuildFile section */ - AE09C8141CCE315E00FA6915 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = AE09C8131CCE315E00FA6915 /* main.m */; }; - AE09C8171CCE315E00FA6915 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = AE09C8161CCE315E00FA6915 /* AppDelegate.m */; }; - AE09C81A1CCE315E00FA6915 /* ViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = AE09C8191CCE315E00FA6915 /* ViewController.m */; }; - AE09C81D1CCE315E00FA6915 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = AE09C81B1CCE315E00FA6915 /* Main.storyboard */; }; - AE09C81F1CCE315E00FA6915 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = AE09C81E1CCE315E00FA6915 /* Assets.xcassets */; }; - AE09C8221CCE315E00FA6915 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = AE09C8201CCE315E00FA6915 /* LaunchScreen.storyboard */; }; - AE09C82A1CCE33D400FA6915 /* RMQClient.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AE09C8291CCE33D400FA6915 /* RMQClient.framework */; }; - AE09C82C1CCE33EA00FA6915 /* RMQClient.framework in CopyFiles */ = {isa = PBXBuildFile; fileRef = AE09C8291CCE33D400FA6915 /* RMQClient.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; -/* End PBXBuildFile section */ - -/* Begin PBXCopyFilesBuildPhase section */ - AE09C82B1CCE33DE00FA6915 /* CopyFiles */ = { - isa = PBXCopyFilesBuildPhase; - buildActionMask = 2147483647; - dstPath = ""; - dstSubfolderSpec = 10; - files = ( - AE09C82C1CCE33EA00FA6915 /* RMQClient.framework in CopyFiles */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXCopyFilesBuildPhase section */ - -/* Begin PBXFileReference section */ - AE09C80F1CCE315E00FA6915 /* tutorial3.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = tutorial3.app; sourceTree = BUILT_PRODUCTS_DIR; }; - AE09C8131CCE315E00FA6915 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; - AE09C8151CCE315E00FA6915 /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; - AE09C8161CCE315E00FA6915 /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; - AE09C8181CCE315E00FA6915 /* ViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ViewController.h; sourceTree = ""; }; - AE09C8191CCE315E00FA6915 /* ViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ViewController.m; sourceTree = ""; }; - AE09C81C1CCE315E00FA6915 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; - AE09C81E1CCE315E00FA6915 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; - AE09C8211CCE315E00FA6915 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; - AE09C8231CCE315E00FA6915 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - AE09C8291CCE33D400FA6915 /* RMQClient.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = RMQClient.framework; path = Carthage/Build/iOS/RMQClient.framework; sourceTree = ""; }; -/* End PBXFileReference section */ - -/* Begin PBXFrameworksBuildPhase section */ - AE09C80C1CCE315E00FA6915 /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - AE09C82A1CCE33D400FA6915 /* RMQClient.framework in Frameworks */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXFrameworksBuildPhase section */ - -/* Begin PBXGroup section */ - AE09C8061CCE315E00FA6915 = { - isa = PBXGroup; - children = ( - AE09C8291CCE33D400FA6915 /* RMQClient.framework */, - AE09C8111CCE315E00FA6915 /* tutorial3 */, - AE09C8101CCE315E00FA6915 /* Products */, - ); - sourceTree = ""; - }; - AE09C8101CCE315E00FA6915 /* Products */ = { - isa = PBXGroup; - children = ( - AE09C80F1CCE315E00FA6915 /* tutorial3.app */, - ); - name = Products; - sourceTree = ""; - }; - AE09C8111CCE315E00FA6915 /* tutorial3 */ = { - isa = PBXGroup; - children = ( - AE09C8151CCE315E00FA6915 /* AppDelegate.h */, - AE09C8161CCE315E00FA6915 /* AppDelegate.m */, - AE09C8181CCE315E00FA6915 /* ViewController.h */, - AE09C8191CCE315E00FA6915 /* ViewController.m */, - AE09C81B1CCE315E00FA6915 /* Main.storyboard */, - AE09C81E1CCE315E00FA6915 /* Assets.xcassets */, - AE09C8201CCE315E00FA6915 /* LaunchScreen.storyboard */, - AE09C8231CCE315E00FA6915 /* Info.plist */, - AE09C8121CCE315E00FA6915 /* Supporting Files */, - ); - path = tutorial3; - sourceTree = ""; - }; - AE09C8121CCE315E00FA6915 /* Supporting Files */ = { - isa = PBXGroup; - children = ( - AE09C8131CCE315E00FA6915 /* main.m */, - ); - name = "Supporting Files"; - sourceTree = ""; - }; -/* End PBXGroup section */ - -/* Begin PBXNativeTarget section */ - AE09C80E1CCE315E00FA6915 /* tutorial3 */ = { - isa = PBXNativeTarget; - buildConfigurationList = AE09C8261CCE315E00FA6915 /* Build configuration list for PBXNativeTarget "tutorial3" */; - buildPhases = ( - AE09C80B1CCE315E00FA6915 /* Sources */, - AE09C80C1CCE315E00FA6915 /* Frameworks */, - AE09C80D1CCE315E00FA6915 /* Resources */, - AE09C82B1CCE33DE00FA6915 /* CopyFiles */, - ); - buildRules = ( - ); - dependencies = ( - ); - name = tutorial3; - productName = tutorial3; - productReference = AE09C80F1CCE315E00FA6915 /* tutorial3.app */; - productType = "com.apple.product-type.application"; - }; -/* End PBXNativeTarget section */ - -/* Begin PBXProject section */ - AE09C8071CCE315E00FA6915 /* Project object */ = { - isa = PBXProject; - attributes = { - LastUpgradeCheck = 0820; - ORGANIZATIONNAME = RabbitMQ; - TargetAttributes = { - AE09C80E1CCE315E00FA6915 = { - CreatedOnToolsVersion = 7.3; - }; - }; - }; - buildConfigurationList = AE09C80A1CCE315E00FA6915 /* Build configuration list for PBXProject "tutorial3" */; - compatibilityVersion = "Xcode 3.2"; - developmentRegion = English; - hasScannedForEncodings = 0; - knownRegions = ( - en, - Base, - ); - mainGroup = AE09C8061CCE315E00FA6915; - productRefGroup = AE09C8101CCE315E00FA6915 /* Products */; - projectDirPath = ""; - projectRoot = ""; - targets = ( - AE09C80E1CCE315E00FA6915 /* tutorial3 */, - ); - }; -/* End PBXProject section */ - -/* Begin PBXResourcesBuildPhase section */ - AE09C80D1CCE315E00FA6915 /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - AE09C8221CCE315E00FA6915 /* LaunchScreen.storyboard in Resources */, - AE09C81F1CCE315E00FA6915 /* Assets.xcassets in Resources */, - AE09C81D1CCE315E00FA6915 /* Main.storyboard in Resources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXResourcesBuildPhase section */ - -/* Begin PBXSourcesBuildPhase section */ - AE09C80B1CCE315E00FA6915 /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - AE09C81A1CCE315E00FA6915 /* ViewController.m in Sources */, - AE09C8171CCE315E00FA6915 /* AppDelegate.m in Sources */, - AE09C8141CCE315E00FA6915 /* main.m in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXSourcesBuildPhase section */ - -/* Begin PBXVariantGroup section */ - AE09C81B1CCE315E00FA6915 /* Main.storyboard */ = { - isa = PBXVariantGroup; - children = ( - AE09C81C1CCE315E00FA6915 /* Base */, - ); - name = Main.storyboard; - sourceTree = ""; - }; - AE09C8201CCE315E00FA6915 /* LaunchScreen.storyboard */ = { - isa = PBXVariantGroup; - children = ( - AE09C8211CCE315E00FA6915 /* Base */, - ); - name = LaunchScreen.storyboard; - sourceTree = ""; - }; -/* End PBXVariantGroup section */ - -/* Begin XCBuildConfiguration section */ - AE09C8241CCE315E00FA6915 /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_ANALYZER_NONNULL = YES; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - COPY_PHASE_STRIP = NO; - DEBUG_INFORMATION_FORMAT = dwarf; - ENABLE_STRICT_OBJC_MSGSEND = YES; - ENABLE_TESTABILITY = YES; - GCC_C_LANGUAGE_STANDARD = gnu99; - GCC_DYNAMIC_NO_PIC = NO; - GCC_NO_COMMON_BLOCKS = YES; - GCC_OPTIMIZATION_LEVEL = 0; - GCC_PREPROCESSOR_DEFINITIONS = ( - "DEBUG=1", - "$(inherited)", - ); - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 9.3; - MTL_ENABLE_DEBUG_INFO = YES; - ONLY_ACTIVE_ARCH = YES; - SDKROOT = iphoneos; - TARGETED_DEVICE_FAMILY = "1,2"; - }; - name = Debug; - }; - AE09C8251CCE315E00FA6915 /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_ANALYZER_NONNULL = YES; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - COPY_PHASE_STRIP = NO; - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - ENABLE_NS_ASSERTIONS = NO; - ENABLE_STRICT_OBJC_MSGSEND = YES; - GCC_C_LANGUAGE_STANDARD = gnu99; - GCC_NO_COMMON_BLOCKS = YES; - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 9.3; - MTL_ENABLE_DEBUG_INFO = NO; - SDKROOT = iphoneos; - TARGETED_DEVICE_FAMILY = "1,2"; - VALIDATE_PRODUCT = YES; - }; - name = Release; - }; - AE09C8271CCE315E00FA6915 /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - FRAMEWORK_SEARCH_PATHS = ( - "$(inherited)", - "$(PROJECT_DIR)/Carthage/Build/iOS", - ); - INFOPLIST_FILE = tutorial3/Info.plist; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; - PRODUCT_BUNDLE_IDENTIFIER = com.rabbitmq.tutorial3; - PRODUCT_NAME = "$(TARGET_NAME)"; - }; - name = Debug; - }; - AE09C8281CCE315E00FA6915 /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - FRAMEWORK_SEARCH_PATHS = ( - "$(inherited)", - "$(PROJECT_DIR)/Carthage/Build/iOS", - ); - INFOPLIST_FILE = tutorial3/Info.plist; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; - PRODUCT_BUNDLE_IDENTIFIER = com.rabbitmq.tutorial3; - PRODUCT_NAME = "$(TARGET_NAME)"; - }; - name = Release; - }; -/* End XCBuildConfiguration section */ - -/* Begin XCConfigurationList section */ - AE09C80A1CCE315E00FA6915 /* Build configuration list for PBXProject "tutorial3" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - AE09C8241CCE315E00FA6915 /* Debug */, - AE09C8251CCE315E00FA6915 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - AE09C8261CCE315E00FA6915 /* Build configuration list for PBXNativeTarget "tutorial3" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - AE09C8271CCE315E00FA6915 /* Debug */, - AE09C8281CCE315E00FA6915 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; -/* End XCConfigurationList section */ - }; - rootObject = AE09C8071CCE315E00FA6915 /* Project object */; -} diff --git a/objective-c/tutorial3/tutorial3.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/objective-c/tutorial3/tutorial3.xcodeproj/project.xcworkspace/contents.xcworkspacedata deleted file mode 100644 index 22bcdc83..00000000 --- a/objective-c/tutorial3/tutorial3.xcodeproj/project.xcworkspace/contents.xcworkspacedata +++ /dev/null @@ -1,7 +0,0 @@ - - - - - diff --git a/objective-c/tutorial3/tutorial3/AppDelegate.h b/objective-c/tutorial3/tutorial3/AppDelegate.h deleted file mode 100644 index 216cd39e..00000000 --- a/objective-c/tutorial3/tutorial3/AppDelegate.h +++ /dev/null @@ -1,17 +0,0 @@ -// -// AppDelegate.h -// tutorial3 -// -// Created by Pivotal on 25/04/2016. -// Copyright © 2016 RabbitMQ. All rights reserved. -// - -#import - -@interface AppDelegate : UIResponder - -@property (strong, nonatomic) UIWindow *window; - - -@end - diff --git a/objective-c/tutorial3/tutorial3/AppDelegate.m b/objective-c/tutorial3/tutorial3/AppDelegate.m deleted file mode 100644 index 7ecfdc8b..00000000 --- a/objective-c/tutorial3/tutorial3/AppDelegate.m +++ /dev/null @@ -1,45 +0,0 @@ -// -// AppDelegate.m -// tutorial3 -// -// Created by Pivotal on 25/04/2016. -// Copyright © 2016 RabbitMQ. All rights reserved. -// - -#import "AppDelegate.h" - -@interface AppDelegate () - -@end - -@implementation AppDelegate - - -- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { - // Override point for customization after application launch. - return YES; -} - -- (void)applicationWillResignActive:(UIApplication *)application { - // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. - // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. -} - -- (void)applicationDidEnterBackground:(UIApplication *)application { - // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. - // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. -} - -- (void)applicationWillEnterForeground:(UIApplication *)application { - // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background. -} - -- (void)applicationDidBecomeActive:(UIApplication *)application { - // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. -} - -- (void)applicationWillTerminate:(UIApplication *)application { - // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. -} - -@end diff --git a/objective-c/tutorial3/tutorial3/Assets.xcassets/AppIcon.appiconset/Contents.json b/objective-c/tutorial3/tutorial3/Assets.xcassets/AppIcon.appiconset/Contents.json deleted file mode 100644 index 36d2c80d..00000000 --- a/objective-c/tutorial3/tutorial3/Assets.xcassets/AppIcon.appiconset/Contents.json +++ /dev/null @@ -1,68 +0,0 @@ -{ - "images" : [ - { - "idiom" : "iphone", - "size" : "29x29", - "scale" : "2x" - }, - { - "idiom" : "iphone", - "size" : "29x29", - "scale" : "3x" - }, - { - "idiom" : "iphone", - "size" : "40x40", - "scale" : "2x" - }, - { - "idiom" : "iphone", - "size" : "40x40", - "scale" : "3x" - }, - { - "idiom" : "iphone", - "size" : "60x60", - "scale" : "2x" - }, - { - "idiom" : "iphone", - "size" : "60x60", - "scale" : "3x" - }, - { - "idiom" : "ipad", - "size" : "29x29", - "scale" : "1x" - }, - { - "idiom" : "ipad", - "size" : "29x29", - "scale" : "2x" - }, - { - "idiom" : "ipad", - "size" : "40x40", - "scale" : "1x" - }, - { - "idiom" : "ipad", - "size" : "40x40", - "scale" : "2x" - }, - { - "idiom" : "ipad", - "size" : "76x76", - "scale" : "1x" - }, - { - "idiom" : "ipad", - "size" : "76x76", - "scale" : "2x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/objective-c/tutorial3/tutorial3/Base.lproj/LaunchScreen.storyboard b/objective-c/tutorial3/tutorial3/Base.lproj/LaunchScreen.storyboard deleted file mode 100644 index 2e721e18..00000000 --- a/objective-c/tutorial3/tutorial3/Base.lproj/LaunchScreen.storyboard +++ /dev/null @@ -1,27 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/objective-c/tutorial3/tutorial3/Base.lproj/Main.storyboard b/objective-c/tutorial3/tutorial3/Base.lproj/Main.storyboard deleted file mode 100644 index d5b34779..00000000 --- a/objective-c/tutorial3/tutorial3/Base.lproj/Main.storyboard +++ /dev/null @@ -1,26 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/objective-c/tutorial3/tutorial3/Info.plist b/objective-c/tutorial3/tutorial3/Info.plist deleted file mode 100644 index 40c6215d..00000000 --- a/objective-c/tutorial3/tutorial3/Info.plist +++ /dev/null @@ -1,47 +0,0 @@ - - - - - CFBundleDevelopmentRegion - en - CFBundleExecutable - $(EXECUTABLE_NAME) - CFBundleIdentifier - $(PRODUCT_BUNDLE_IDENTIFIER) - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - $(PRODUCT_NAME) - CFBundlePackageType - APPL - CFBundleShortVersionString - 1.0 - CFBundleSignature - ???? - CFBundleVersion - 1 - LSRequiresIPhoneOS - - UILaunchStoryboardName - LaunchScreen - UIMainStoryboardFile - Main - UIRequiredDeviceCapabilities - - armv7 - - UISupportedInterfaceOrientations - - UIInterfaceOrientationPortrait - UIInterfaceOrientationLandscapeLeft - UIInterfaceOrientationLandscapeRight - - UISupportedInterfaceOrientations~ipad - - UIInterfaceOrientationPortrait - UIInterfaceOrientationPortraitUpsideDown - UIInterfaceOrientationLandscapeLeft - UIInterfaceOrientationLandscapeRight - - - diff --git a/objective-c/tutorial3/tutorial3/ViewController.h b/objective-c/tutorial3/tutorial3/ViewController.h deleted file mode 100644 index d40beeaa..00000000 --- a/objective-c/tutorial3/tutorial3/ViewController.h +++ /dev/null @@ -1,15 +0,0 @@ -// -// ViewController.h -// tutorial3 -// -// Created by Pivotal on 25/04/2016. -// Copyright © 2016 RabbitMQ. All rights reserved. -// - -#import - -@interface ViewController : UIViewController - - -@end - diff --git a/objective-c/tutorial3/tutorial3/ViewController.m b/objective-c/tutorial3/tutorial3/ViewController.m deleted file mode 100644 index b5ce0a7a..00000000 --- a/objective-c/tutorial3/tutorial3/ViewController.m +++ /dev/null @@ -1,50 +0,0 @@ -#import "ViewController.h" -@import RMQClient; - -@interface ViewController () - -@end - -@implementation ViewController - -- (void)viewDidLoad { - [super viewDidLoad]; - [self receiveLogs]; - [self receiveLogs]; - sleep(1); - [self emitLog]; -} - -- (void)emitLog { - RMQConnection *conn = [[RMQConnection alloc] initWithDelegate:[RMQConnectionDelegateLogger new]]; - [conn start]; - - id ch = [conn createChannel]; - RMQExchange *x = [ch fanout:@"logs"]; - - NSString *msg = @"Hello World!"; - - [x publish:[msg dataUsingEncoding:NSUTF8StringEncoding]]; - NSLog(@"Sent %@", msg); - - [conn close]; -} - -- (void)receiveLogs { - RMQConnection *conn = [[RMQConnection alloc] initWithDelegate:[RMQConnectionDelegateLogger new]]; - [conn start]; - - id ch = [conn createChannel]; - RMQExchange *x = [ch fanout:@"logs"]; - RMQQueue *q = [ch queue:@"" options:RMQQueueDeclareExclusive]; - - [q bind:x]; - - NSLog(@"Waiting for logs."); - - [q subscribe:^(RMQMessage * _Nonnull message) { - NSLog(@"Received %@", [[NSString alloc] initWithData:message.body encoding:NSUTF8StringEncoding]); - }]; -} - -@end diff --git a/objective-c/tutorial3/tutorial3/main.m b/objective-c/tutorial3/tutorial3/main.m deleted file mode 100644 index 03556db4..00000000 --- a/objective-c/tutorial3/tutorial3/main.m +++ /dev/null @@ -1,16 +0,0 @@ -// -// main.m -// tutorial3 -// -// Created by Pivotal on 25/04/2016. -// Copyright © 2016 RabbitMQ. All rights reserved. -// - -#import -#import "AppDelegate.h" - -int main(int argc, char * argv[]) { - @autoreleasepool { - return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); - } -} diff --git a/objective-c/tutorial4/Cartfile b/objective-c/tutorial4/Cartfile deleted file mode 100644 index 38483e08..00000000 --- a/objective-c/tutorial4/Cartfile +++ /dev/null @@ -1 +0,0 @@ -github "rabbitmq/rabbitmq-objc-client" "v0.10.0" diff --git a/objective-c/tutorial4/Cartfile.resolved b/objective-c/tutorial4/Cartfile.resolved deleted file mode 100644 index 750d6791..00000000 --- a/objective-c/tutorial4/Cartfile.resolved +++ /dev/null @@ -1,3 +0,0 @@ -github "robbiehanson/CocoaAsyncSocket" "7.5.1" -github "jeffh/JKVValue" "v1.3.2" -github "rabbitmq/rabbitmq-objc-client" "v0.10.0" diff --git a/objective-c/tutorial4/tutorial4.xcodeproj/project.pbxproj b/objective-c/tutorial4/tutorial4.xcodeproj/project.pbxproj deleted file mode 100644 index ce8b9036..00000000 --- a/objective-c/tutorial4/tutorial4.xcodeproj/project.pbxproj +++ /dev/null @@ -1,341 +0,0 @@ -// !$*UTF8*$! -{ - archiveVersion = 1; - classes = { - }; - objectVersion = 46; - objects = { - -/* Begin PBXBuildFile section */ - AEF0F7AB1CCEC50A007DAF85 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = AEF0F7AA1CCEC50A007DAF85 /* main.m */; }; - AEF0F7AE1CCEC50A007DAF85 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = AEF0F7AD1CCEC50A007DAF85 /* AppDelegate.m */; }; - AEF0F7B11CCEC50A007DAF85 /* ViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = AEF0F7B01CCEC50A007DAF85 /* ViewController.m */; }; - AEF0F7B41CCEC50A007DAF85 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = AEF0F7B21CCEC50A007DAF85 /* Main.storyboard */; }; - AEF0F7B61CCEC50A007DAF85 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = AEF0F7B51CCEC50A007DAF85 /* Assets.xcassets */; }; - AEF0F7B91CCEC50A007DAF85 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = AEF0F7B71CCEC50A007DAF85 /* LaunchScreen.storyboard */; }; - AEF0F7C11CCEC5BE007DAF85 /* RMQClient.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AEF0F7C01CCEC5BE007DAF85 /* RMQClient.framework */; }; - AEF0F7C31CCEC5CB007DAF85 /* RMQClient.framework in CopyFiles */ = {isa = PBXBuildFile; fileRef = AEF0F7C01CCEC5BE007DAF85 /* RMQClient.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; -/* End PBXBuildFile section */ - -/* Begin PBXCopyFilesBuildPhase section */ - AEF0F7C21CCEC5C2007DAF85 /* CopyFiles */ = { - isa = PBXCopyFilesBuildPhase; - buildActionMask = 2147483647; - dstPath = ""; - dstSubfolderSpec = 10; - files = ( - AEF0F7C31CCEC5CB007DAF85 /* RMQClient.framework in CopyFiles */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXCopyFilesBuildPhase section */ - -/* Begin PBXFileReference section */ - AEF0F7A61CCEC50A007DAF85 /* tutorial4.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = tutorial4.app; sourceTree = BUILT_PRODUCTS_DIR; }; - AEF0F7AA1CCEC50A007DAF85 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; - AEF0F7AC1CCEC50A007DAF85 /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; - AEF0F7AD1CCEC50A007DAF85 /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; - AEF0F7AF1CCEC50A007DAF85 /* ViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ViewController.h; sourceTree = ""; }; - AEF0F7B01CCEC50A007DAF85 /* ViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ViewController.m; sourceTree = ""; }; - AEF0F7B31CCEC50A007DAF85 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; - AEF0F7B51CCEC50A007DAF85 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; - AEF0F7B81CCEC50A007DAF85 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; - AEF0F7BA1CCEC50A007DAF85 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - AEF0F7C01CCEC5BE007DAF85 /* RMQClient.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = RMQClient.framework; path = Carthage/Build/iOS/RMQClient.framework; sourceTree = ""; }; -/* End PBXFileReference section */ - -/* Begin PBXFrameworksBuildPhase section */ - AEF0F7A31CCEC50A007DAF85 /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - AEF0F7C11CCEC5BE007DAF85 /* RMQClient.framework in Frameworks */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXFrameworksBuildPhase section */ - -/* Begin PBXGroup section */ - AEF0F79D1CCEC50A007DAF85 = { - isa = PBXGroup; - children = ( - AEF0F7C01CCEC5BE007DAF85 /* RMQClient.framework */, - AEF0F7A81CCEC50A007DAF85 /* tutorial4 */, - AEF0F7A71CCEC50A007DAF85 /* Products */, - ); - sourceTree = ""; - }; - AEF0F7A71CCEC50A007DAF85 /* Products */ = { - isa = PBXGroup; - children = ( - AEF0F7A61CCEC50A007DAF85 /* tutorial4.app */, - ); - name = Products; - sourceTree = ""; - }; - AEF0F7A81CCEC50A007DAF85 /* tutorial4 */ = { - isa = PBXGroup; - children = ( - AEF0F7AC1CCEC50A007DAF85 /* AppDelegate.h */, - AEF0F7AD1CCEC50A007DAF85 /* AppDelegate.m */, - AEF0F7AF1CCEC50A007DAF85 /* ViewController.h */, - AEF0F7B01CCEC50A007DAF85 /* ViewController.m */, - AEF0F7B21CCEC50A007DAF85 /* Main.storyboard */, - AEF0F7B51CCEC50A007DAF85 /* Assets.xcassets */, - AEF0F7B71CCEC50A007DAF85 /* LaunchScreen.storyboard */, - AEF0F7BA1CCEC50A007DAF85 /* Info.plist */, - AEF0F7A91CCEC50A007DAF85 /* Supporting Files */, - ); - path = tutorial4; - sourceTree = ""; - }; - AEF0F7A91CCEC50A007DAF85 /* Supporting Files */ = { - isa = PBXGroup; - children = ( - AEF0F7AA1CCEC50A007DAF85 /* main.m */, - ); - name = "Supporting Files"; - sourceTree = ""; - }; -/* End PBXGroup section */ - -/* Begin PBXNativeTarget section */ - AEF0F7A51CCEC50A007DAF85 /* tutorial4 */ = { - isa = PBXNativeTarget; - buildConfigurationList = AEF0F7BD1CCEC50A007DAF85 /* Build configuration list for PBXNativeTarget "tutorial4" */; - buildPhases = ( - AEF0F7A21CCEC50A007DAF85 /* Sources */, - AEF0F7A31CCEC50A007DAF85 /* Frameworks */, - AEF0F7A41CCEC50A007DAF85 /* Resources */, - AEF0F7C21CCEC5C2007DAF85 /* CopyFiles */, - ); - buildRules = ( - ); - dependencies = ( - ); - name = tutorial4; - productName = tutorial4; - productReference = AEF0F7A61CCEC50A007DAF85 /* tutorial4.app */; - productType = "com.apple.product-type.application"; - }; -/* End PBXNativeTarget section */ - -/* Begin PBXProject section */ - AEF0F79E1CCEC50A007DAF85 /* Project object */ = { - isa = PBXProject; - attributes = { - LastUpgradeCheck = 0820; - ORGANIZATIONNAME = RabbitMQ; - TargetAttributes = { - AEF0F7A51CCEC50A007DAF85 = { - CreatedOnToolsVersion = 7.3; - }; - }; - }; - buildConfigurationList = AEF0F7A11CCEC50A007DAF85 /* Build configuration list for PBXProject "tutorial4" */; - compatibilityVersion = "Xcode 3.2"; - developmentRegion = English; - hasScannedForEncodings = 0; - knownRegions = ( - en, - Base, - ); - mainGroup = AEF0F79D1CCEC50A007DAF85; - productRefGroup = AEF0F7A71CCEC50A007DAF85 /* Products */; - projectDirPath = ""; - projectRoot = ""; - targets = ( - AEF0F7A51CCEC50A007DAF85 /* tutorial4 */, - ); - }; -/* End PBXProject section */ - -/* Begin PBXResourcesBuildPhase section */ - AEF0F7A41CCEC50A007DAF85 /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - AEF0F7B91CCEC50A007DAF85 /* LaunchScreen.storyboard in Resources */, - AEF0F7B61CCEC50A007DAF85 /* Assets.xcassets in Resources */, - AEF0F7B41CCEC50A007DAF85 /* Main.storyboard in Resources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXResourcesBuildPhase section */ - -/* Begin PBXSourcesBuildPhase section */ - AEF0F7A21CCEC50A007DAF85 /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - AEF0F7B11CCEC50A007DAF85 /* ViewController.m in Sources */, - AEF0F7AE1CCEC50A007DAF85 /* AppDelegate.m in Sources */, - AEF0F7AB1CCEC50A007DAF85 /* main.m in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXSourcesBuildPhase section */ - -/* Begin PBXVariantGroup section */ - AEF0F7B21CCEC50A007DAF85 /* Main.storyboard */ = { - isa = PBXVariantGroup; - children = ( - AEF0F7B31CCEC50A007DAF85 /* Base */, - ); - name = Main.storyboard; - sourceTree = ""; - }; - AEF0F7B71CCEC50A007DAF85 /* LaunchScreen.storyboard */ = { - isa = PBXVariantGroup; - children = ( - AEF0F7B81CCEC50A007DAF85 /* Base */, - ); - name = LaunchScreen.storyboard; - sourceTree = ""; - }; -/* End PBXVariantGroup section */ - -/* Begin XCBuildConfiguration section */ - AEF0F7BB1CCEC50A007DAF85 /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_ANALYZER_NONNULL = YES; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - COPY_PHASE_STRIP = NO; - DEBUG_INFORMATION_FORMAT = dwarf; - ENABLE_STRICT_OBJC_MSGSEND = YES; - ENABLE_TESTABILITY = YES; - GCC_C_LANGUAGE_STANDARD = gnu99; - GCC_DYNAMIC_NO_PIC = NO; - GCC_NO_COMMON_BLOCKS = YES; - GCC_OPTIMIZATION_LEVEL = 0; - GCC_PREPROCESSOR_DEFINITIONS = ( - "DEBUG=1", - "$(inherited)", - ); - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 10.0; - MTL_ENABLE_DEBUG_INFO = YES; - ONLY_ACTIVE_ARCH = YES; - SDKROOT = iphoneos; - TARGETED_DEVICE_FAMILY = "1,2"; - }; - name = Debug; - }; - AEF0F7BC1CCEC50A007DAF85 /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_ANALYZER_NONNULL = YES; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - COPY_PHASE_STRIP = NO; - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - ENABLE_NS_ASSERTIONS = NO; - ENABLE_STRICT_OBJC_MSGSEND = YES; - GCC_C_LANGUAGE_STANDARD = gnu99; - GCC_NO_COMMON_BLOCKS = YES; - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 10.0; - MTL_ENABLE_DEBUG_INFO = NO; - SDKROOT = iphoneos; - TARGETED_DEVICE_FAMILY = "1,2"; - VALIDATE_PRODUCT = YES; - }; - name = Release; - }; - AEF0F7BE1CCEC50A007DAF85 /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - FRAMEWORK_SEARCH_PATHS = ( - "$(inherited)", - "$(PROJECT_DIR)/Carthage/Build/iOS", - ); - INFOPLIST_FILE = tutorial4/Info.plist; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; - PRODUCT_BUNDLE_IDENTIFIER = com.rabbitmq.tutorial4; - PRODUCT_NAME = "$(TARGET_NAME)"; - }; - name = Debug; - }; - AEF0F7BF1CCEC50A007DAF85 /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - FRAMEWORK_SEARCH_PATHS = ( - "$(inherited)", - "$(PROJECT_DIR)/Carthage/Build/iOS", - ); - INFOPLIST_FILE = tutorial4/Info.plist; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; - PRODUCT_BUNDLE_IDENTIFIER = com.rabbitmq.tutorial4; - PRODUCT_NAME = "$(TARGET_NAME)"; - }; - name = Release; - }; -/* End XCBuildConfiguration section */ - -/* Begin XCConfigurationList section */ - AEF0F7A11CCEC50A007DAF85 /* Build configuration list for PBXProject "tutorial4" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - AEF0F7BB1CCEC50A007DAF85 /* Debug */, - AEF0F7BC1CCEC50A007DAF85 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - AEF0F7BD1CCEC50A007DAF85 /* Build configuration list for PBXNativeTarget "tutorial4" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - AEF0F7BE1CCEC50A007DAF85 /* Debug */, - AEF0F7BF1CCEC50A007DAF85 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; -/* End XCConfigurationList section */ - }; - rootObject = AEF0F79E1CCEC50A007DAF85 /* Project object */; -} diff --git a/objective-c/tutorial4/tutorial4.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/objective-c/tutorial4/tutorial4.xcodeproj/project.xcworkspace/contents.xcworkspacedata deleted file mode 100644 index a37c2d60..00000000 --- a/objective-c/tutorial4/tutorial4.xcodeproj/project.xcworkspace/contents.xcworkspacedata +++ /dev/null @@ -1,7 +0,0 @@ - - - - - diff --git a/objective-c/tutorial4/tutorial4/AppDelegate.h b/objective-c/tutorial4/tutorial4/AppDelegate.h deleted file mode 100644 index b9bf4868..00000000 --- a/objective-c/tutorial4/tutorial4/AppDelegate.h +++ /dev/null @@ -1,17 +0,0 @@ -// -// AppDelegate.h -// tutorial4 -// -// Created by Pivotal on 25/04/2016. -// Copyright © 2016 RabbitMQ. All rights reserved. -// - -#import - -@interface AppDelegate : UIResponder - -@property (strong, nonatomic) UIWindow *window; - - -@end - diff --git a/objective-c/tutorial4/tutorial4/AppDelegate.m b/objective-c/tutorial4/tutorial4/AppDelegate.m deleted file mode 100644 index 0b0d972d..00000000 --- a/objective-c/tutorial4/tutorial4/AppDelegate.m +++ /dev/null @@ -1,45 +0,0 @@ -// -// AppDelegate.m -// tutorial4 -// -// Created by Pivotal on 25/04/2016. -// Copyright © 2016 RabbitMQ. All rights reserved. -// - -#import "AppDelegate.h" - -@interface AppDelegate () - -@end - -@implementation AppDelegate - - -- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { - // Override point for customization after application launch. - return YES; -} - -- (void)applicationWillResignActive:(UIApplication *)application { - // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. - // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. -} - -- (void)applicationDidEnterBackground:(UIApplication *)application { - // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. - // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. -} - -- (void)applicationWillEnterForeground:(UIApplication *)application { - // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background. -} - -- (void)applicationDidBecomeActive:(UIApplication *)application { - // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. -} - -- (void)applicationWillTerminate:(UIApplication *)application { - // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. -} - -@end diff --git a/objective-c/tutorial4/tutorial4/Assets.xcassets/AppIcon.appiconset/Contents.json b/objective-c/tutorial4/tutorial4/Assets.xcassets/AppIcon.appiconset/Contents.json deleted file mode 100644 index 36d2c80d..00000000 --- a/objective-c/tutorial4/tutorial4/Assets.xcassets/AppIcon.appiconset/Contents.json +++ /dev/null @@ -1,68 +0,0 @@ -{ - "images" : [ - { - "idiom" : "iphone", - "size" : "29x29", - "scale" : "2x" - }, - { - "idiom" : "iphone", - "size" : "29x29", - "scale" : "3x" - }, - { - "idiom" : "iphone", - "size" : "40x40", - "scale" : "2x" - }, - { - "idiom" : "iphone", - "size" : "40x40", - "scale" : "3x" - }, - { - "idiom" : "iphone", - "size" : "60x60", - "scale" : "2x" - }, - { - "idiom" : "iphone", - "size" : "60x60", - "scale" : "3x" - }, - { - "idiom" : "ipad", - "size" : "29x29", - "scale" : "1x" - }, - { - "idiom" : "ipad", - "size" : "29x29", - "scale" : "2x" - }, - { - "idiom" : "ipad", - "size" : "40x40", - "scale" : "1x" - }, - { - "idiom" : "ipad", - "size" : "40x40", - "scale" : "2x" - }, - { - "idiom" : "ipad", - "size" : "76x76", - "scale" : "1x" - }, - { - "idiom" : "ipad", - "size" : "76x76", - "scale" : "2x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/objective-c/tutorial4/tutorial4/Base.lproj/LaunchScreen.storyboard b/objective-c/tutorial4/tutorial4/Base.lproj/LaunchScreen.storyboard deleted file mode 100644 index 2e721e18..00000000 --- a/objective-c/tutorial4/tutorial4/Base.lproj/LaunchScreen.storyboard +++ /dev/null @@ -1,27 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/objective-c/tutorial4/tutorial4/Base.lproj/Main.storyboard b/objective-c/tutorial4/tutorial4/Base.lproj/Main.storyboard deleted file mode 100644 index f56d2f3b..00000000 --- a/objective-c/tutorial4/tutorial4/Base.lproj/Main.storyboard +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/objective-c/tutorial4/tutorial4/Info.plist b/objective-c/tutorial4/tutorial4/Info.plist deleted file mode 100644 index 40c6215d..00000000 --- a/objective-c/tutorial4/tutorial4/Info.plist +++ /dev/null @@ -1,47 +0,0 @@ - - - - - CFBundleDevelopmentRegion - en - CFBundleExecutable - $(EXECUTABLE_NAME) - CFBundleIdentifier - $(PRODUCT_BUNDLE_IDENTIFIER) - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - $(PRODUCT_NAME) - CFBundlePackageType - APPL - CFBundleShortVersionString - 1.0 - CFBundleSignature - ???? - CFBundleVersion - 1 - LSRequiresIPhoneOS - - UILaunchStoryboardName - LaunchScreen - UIMainStoryboardFile - Main - UIRequiredDeviceCapabilities - - armv7 - - UISupportedInterfaceOrientations - - UIInterfaceOrientationPortrait - UIInterfaceOrientationLandscapeLeft - UIInterfaceOrientationLandscapeRight - - UISupportedInterfaceOrientations~ipad - - UIInterfaceOrientationPortrait - UIInterfaceOrientationPortraitUpsideDown - UIInterfaceOrientationLandscapeLeft - UIInterfaceOrientationLandscapeRight - - - diff --git a/objective-c/tutorial4/tutorial4/ViewController.h b/objective-c/tutorial4/tutorial4/ViewController.h deleted file mode 100644 index a79652b5..00000000 --- a/objective-c/tutorial4/tutorial4/ViewController.h +++ /dev/null @@ -1,7 +0,0 @@ -#import - -@interface ViewController : UIViewController - - -@end - diff --git a/objective-c/tutorial4/tutorial4/ViewController.m b/objective-c/tutorial4/tutorial4/ViewController.m deleted file mode 100644 index 4b88b551..00000000 --- a/objective-c/tutorial4/tutorial4/ViewController.m +++ /dev/null @@ -1,53 +0,0 @@ -#import "ViewController.h" -@import RMQClient; - -@interface ViewController () - -@end - -@implementation ViewController - -- (void)viewDidLoad { - [super viewDidLoad]; - - [self receiveLogsDirect]; - sleep(2); - [self emitLogDirect:@"Hello World!" severity:@"info"]; - [self emitLogDirect:@"Missile button pressed" severity:@"warning"]; - [self emitLogDirect:@"Launch mechanism jammed" severity:@"error"]; -} - -- (void)receiveLogsDirect { - RMQConnection *conn = [[RMQConnection alloc] initWithDelegate:[RMQConnectionDelegateLogger new]]; - [conn start]; - - id ch = [conn createChannel]; - RMQExchange *x = [ch direct:@"direct_logs"]; - RMQQueue *q = [ch queue:@"" options:RMQQueueDeclareExclusive]; - - NSArray *severities = @[@"error", @"warning", @"info"]; - for (NSString *severity in severities) { - [q bind:x routingKey:severity]; - } - - NSLog(@"Waiting for logs."); - - [q subscribe:^(RMQMessage * _Nonnull message) { - NSLog(@"%@:%@", message.routingKey, [[NSString alloc] initWithData:message.body encoding:NSUTF8StringEncoding]); - }]; -} - -- (void)emitLogDirect:(NSString *)msg severity:(NSString *)severity { - RMQConnection *conn = [[RMQConnection alloc] initWithDelegate:[RMQConnectionDelegateLogger new]]; - [conn start]; - - id ch = [conn createChannel]; - RMQExchange *x = [ch direct:@"direct_logs"]; - - [x publish:[msg dataUsingEncoding:NSUTF8StringEncoding] routingKey:severity]; - NSLog(@"Sent '%@'", msg); - - [conn close]; -} - -@end diff --git a/objective-c/tutorial4/tutorial4/main.m b/objective-c/tutorial4/tutorial4/main.m deleted file mode 100644 index 59f23651..00000000 --- a/objective-c/tutorial4/tutorial4/main.m +++ /dev/null @@ -1,16 +0,0 @@ -// -// main.m -// tutorial4 -// -// Created by Pivotal on 25/04/2016. -// Copyright © 2016 RabbitMQ. All rights reserved. -// - -#import -#import "AppDelegate.h" - -int main(int argc, char * argv[]) { - @autoreleasepool { - return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); - } -} diff --git a/objective-c/tutorial5/Cartfile b/objective-c/tutorial5/Cartfile deleted file mode 100644 index 38483e08..00000000 --- a/objective-c/tutorial5/Cartfile +++ /dev/null @@ -1 +0,0 @@ -github "rabbitmq/rabbitmq-objc-client" "v0.10.0" diff --git a/objective-c/tutorial5/Cartfile.resolved b/objective-c/tutorial5/Cartfile.resolved deleted file mode 100644 index 750d6791..00000000 --- a/objective-c/tutorial5/Cartfile.resolved +++ /dev/null @@ -1,3 +0,0 @@ -github "robbiehanson/CocoaAsyncSocket" "7.5.1" -github "jeffh/JKVValue" "v1.3.2" -github "rabbitmq/rabbitmq-objc-client" "v0.10.0" diff --git a/objective-c/tutorial5/tutorial5.xcodeproj/project.pbxproj b/objective-c/tutorial5/tutorial5.xcodeproj/project.pbxproj deleted file mode 100644 index dabbfa02..00000000 --- a/objective-c/tutorial5/tutorial5.xcodeproj/project.pbxproj +++ /dev/null @@ -1,341 +0,0 @@ -// !$*UTF8*$! -{ - archiveVersion = 1; - classes = { - }; - objectVersion = 46; - objects = { - -/* Begin PBXBuildFile section */ - AE929C5E1CCF6FAF001A6524 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = AE929C5D1CCF6FAF001A6524 /* main.m */; }; - AE929C611CCF6FAF001A6524 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = AE929C601CCF6FAF001A6524 /* AppDelegate.m */; }; - AE929C641CCF6FAF001A6524 /* ViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = AE929C631CCF6FAF001A6524 /* ViewController.m */; }; - AE929C671CCF6FAF001A6524 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = AE929C651CCF6FAF001A6524 /* Main.storyboard */; }; - AE929C691CCF6FAF001A6524 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = AE929C681CCF6FAF001A6524 /* Assets.xcassets */; }; - AE929C6C1CCF6FAF001A6524 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = AE929C6A1CCF6FAF001A6524 /* LaunchScreen.storyboard */; }; - AE929C741CCF70A5001A6524 /* RMQClient.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AE929C731CCF70A5001A6524 /* RMQClient.framework */; }; - AE929C761CCF70C0001A6524 /* RMQClient.framework in CopyFiles */ = {isa = PBXBuildFile; fileRef = AE929C731CCF70A5001A6524 /* RMQClient.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; -/* End PBXBuildFile section */ - -/* Begin PBXCopyFilesBuildPhase section */ - AE929C751CCF70B8001A6524 /* CopyFiles */ = { - isa = PBXCopyFilesBuildPhase; - buildActionMask = 2147483647; - dstPath = ""; - dstSubfolderSpec = 10; - files = ( - AE929C761CCF70C0001A6524 /* RMQClient.framework in CopyFiles */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXCopyFilesBuildPhase section */ - -/* Begin PBXFileReference section */ - AE929C591CCF6FAF001A6524 /* tutorial5.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = tutorial5.app; sourceTree = BUILT_PRODUCTS_DIR; }; - AE929C5D1CCF6FAF001A6524 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; - AE929C5F1CCF6FAF001A6524 /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; - AE929C601CCF6FAF001A6524 /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; - AE929C621CCF6FAF001A6524 /* ViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ViewController.h; sourceTree = ""; }; - AE929C631CCF6FAF001A6524 /* ViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ViewController.m; sourceTree = ""; }; - AE929C661CCF6FAF001A6524 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; - AE929C681CCF6FAF001A6524 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; - AE929C6B1CCF6FAF001A6524 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; - AE929C6D1CCF6FAF001A6524 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - AE929C731CCF70A5001A6524 /* RMQClient.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = RMQClient.framework; path = Carthage/Build/iOS/RMQClient.framework; sourceTree = ""; }; -/* End PBXFileReference section */ - -/* Begin PBXFrameworksBuildPhase section */ - AE929C561CCF6FAF001A6524 /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - AE929C741CCF70A5001A6524 /* RMQClient.framework in Frameworks */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXFrameworksBuildPhase section */ - -/* Begin PBXGroup section */ - AE929C501CCF6FAF001A6524 = { - isa = PBXGroup; - children = ( - AE929C731CCF70A5001A6524 /* RMQClient.framework */, - AE929C5B1CCF6FAF001A6524 /* tutorial5 */, - AE929C5A1CCF6FAF001A6524 /* Products */, - ); - sourceTree = ""; - }; - AE929C5A1CCF6FAF001A6524 /* Products */ = { - isa = PBXGroup; - children = ( - AE929C591CCF6FAF001A6524 /* tutorial5.app */, - ); - name = Products; - sourceTree = ""; - }; - AE929C5B1CCF6FAF001A6524 /* tutorial5 */ = { - isa = PBXGroup; - children = ( - AE929C5F1CCF6FAF001A6524 /* AppDelegate.h */, - AE929C601CCF6FAF001A6524 /* AppDelegate.m */, - AE929C621CCF6FAF001A6524 /* ViewController.h */, - AE929C631CCF6FAF001A6524 /* ViewController.m */, - AE929C651CCF6FAF001A6524 /* Main.storyboard */, - AE929C681CCF6FAF001A6524 /* Assets.xcassets */, - AE929C6A1CCF6FAF001A6524 /* LaunchScreen.storyboard */, - AE929C6D1CCF6FAF001A6524 /* Info.plist */, - AE929C5C1CCF6FAF001A6524 /* Supporting Files */, - ); - path = tutorial5; - sourceTree = ""; - }; - AE929C5C1CCF6FAF001A6524 /* Supporting Files */ = { - isa = PBXGroup; - children = ( - AE929C5D1CCF6FAF001A6524 /* main.m */, - ); - name = "Supporting Files"; - sourceTree = ""; - }; -/* End PBXGroup section */ - -/* Begin PBXNativeTarget section */ - AE929C581CCF6FAF001A6524 /* tutorial5 */ = { - isa = PBXNativeTarget; - buildConfigurationList = AE929C701CCF6FAF001A6524 /* Build configuration list for PBXNativeTarget "tutorial5" */; - buildPhases = ( - AE929C551CCF6FAF001A6524 /* Sources */, - AE929C561CCF6FAF001A6524 /* Frameworks */, - AE929C571CCF6FAF001A6524 /* Resources */, - AE929C751CCF70B8001A6524 /* CopyFiles */, - ); - buildRules = ( - ); - dependencies = ( - ); - name = tutorial5; - productName = tutorial5; - productReference = AE929C591CCF6FAF001A6524 /* tutorial5.app */; - productType = "com.apple.product-type.application"; - }; -/* End PBXNativeTarget section */ - -/* Begin PBXProject section */ - AE929C511CCF6FAF001A6524 /* Project object */ = { - isa = PBXProject; - attributes = { - LastUpgradeCheck = 0820; - ORGANIZATIONNAME = RabbitMQ; - TargetAttributes = { - AE929C581CCF6FAF001A6524 = { - CreatedOnToolsVersion = 7.3; - }; - }; - }; - buildConfigurationList = AE929C541CCF6FAF001A6524 /* Build configuration list for PBXProject "tutorial5" */; - compatibilityVersion = "Xcode 3.2"; - developmentRegion = English; - hasScannedForEncodings = 0; - knownRegions = ( - en, - Base, - ); - mainGroup = AE929C501CCF6FAF001A6524; - productRefGroup = AE929C5A1CCF6FAF001A6524 /* Products */; - projectDirPath = ""; - projectRoot = ""; - targets = ( - AE929C581CCF6FAF001A6524 /* tutorial5 */, - ); - }; -/* End PBXProject section */ - -/* Begin PBXResourcesBuildPhase section */ - AE929C571CCF6FAF001A6524 /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - AE929C6C1CCF6FAF001A6524 /* LaunchScreen.storyboard in Resources */, - AE929C691CCF6FAF001A6524 /* Assets.xcassets in Resources */, - AE929C671CCF6FAF001A6524 /* Main.storyboard in Resources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXResourcesBuildPhase section */ - -/* Begin PBXSourcesBuildPhase section */ - AE929C551CCF6FAF001A6524 /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - AE929C641CCF6FAF001A6524 /* ViewController.m in Sources */, - AE929C611CCF6FAF001A6524 /* AppDelegate.m in Sources */, - AE929C5E1CCF6FAF001A6524 /* main.m in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXSourcesBuildPhase section */ - -/* Begin PBXVariantGroup section */ - AE929C651CCF6FAF001A6524 /* Main.storyboard */ = { - isa = PBXVariantGroup; - children = ( - AE929C661CCF6FAF001A6524 /* Base */, - ); - name = Main.storyboard; - sourceTree = ""; - }; - AE929C6A1CCF6FAF001A6524 /* LaunchScreen.storyboard */ = { - isa = PBXVariantGroup; - children = ( - AE929C6B1CCF6FAF001A6524 /* Base */, - ); - name = LaunchScreen.storyboard; - sourceTree = ""; - }; -/* End PBXVariantGroup section */ - -/* Begin XCBuildConfiguration section */ - AE929C6E1CCF6FAF001A6524 /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_ANALYZER_NONNULL = YES; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - COPY_PHASE_STRIP = NO; - DEBUG_INFORMATION_FORMAT = dwarf; - ENABLE_STRICT_OBJC_MSGSEND = YES; - ENABLE_TESTABILITY = YES; - GCC_C_LANGUAGE_STANDARD = gnu99; - GCC_DYNAMIC_NO_PIC = NO; - GCC_NO_COMMON_BLOCKS = YES; - GCC_OPTIMIZATION_LEVEL = 0; - GCC_PREPROCESSOR_DEFINITIONS = ( - "DEBUG=1", - "$(inherited)", - ); - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 10.0; - MTL_ENABLE_DEBUG_INFO = YES; - ONLY_ACTIVE_ARCH = YES; - SDKROOT = iphoneos; - TARGETED_DEVICE_FAMILY = "1,2"; - }; - name = Debug; - }; - AE929C6F1CCF6FAF001A6524 /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_ANALYZER_NONNULL = YES; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - COPY_PHASE_STRIP = NO; - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - ENABLE_NS_ASSERTIONS = NO; - ENABLE_STRICT_OBJC_MSGSEND = YES; - GCC_C_LANGUAGE_STANDARD = gnu99; - GCC_NO_COMMON_BLOCKS = YES; - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 10.0; - MTL_ENABLE_DEBUG_INFO = NO; - SDKROOT = iphoneos; - TARGETED_DEVICE_FAMILY = "1,2"; - VALIDATE_PRODUCT = YES; - }; - name = Release; - }; - AE929C711CCF6FAF001A6524 /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - FRAMEWORK_SEARCH_PATHS = ( - "$(inherited)", - "$(PROJECT_DIR)/Carthage/Build/iOS", - ); - INFOPLIST_FILE = tutorial5/Info.plist; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; - PRODUCT_BUNDLE_IDENTIFIER = com.rabbitmq.tutorial5; - PRODUCT_NAME = "$(TARGET_NAME)"; - }; - name = Debug; - }; - AE929C721CCF6FAF001A6524 /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - FRAMEWORK_SEARCH_PATHS = ( - "$(inherited)", - "$(PROJECT_DIR)/Carthage/Build/iOS", - ); - INFOPLIST_FILE = tutorial5/Info.plist; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; - PRODUCT_BUNDLE_IDENTIFIER = com.rabbitmq.tutorial5; - PRODUCT_NAME = "$(TARGET_NAME)"; - }; - name = Release; - }; -/* End XCBuildConfiguration section */ - -/* Begin XCConfigurationList section */ - AE929C541CCF6FAF001A6524 /* Build configuration list for PBXProject "tutorial5" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - AE929C6E1CCF6FAF001A6524 /* Debug */, - AE929C6F1CCF6FAF001A6524 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - AE929C701CCF6FAF001A6524 /* Build configuration list for PBXNativeTarget "tutorial5" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - AE929C711CCF6FAF001A6524 /* Debug */, - AE929C721CCF6FAF001A6524 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; -/* End XCConfigurationList section */ - }; - rootObject = AE929C511CCF6FAF001A6524 /* Project object */; -} diff --git a/objective-c/tutorial5/tutorial5.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/objective-c/tutorial5/tutorial5.xcodeproj/project.xcworkspace/contents.xcworkspacedata deleted file mode 100644 index ac57ad93..00000000 --- a/objective-c/tutorial5/tutorial5.xcodeproj/project.xcworkspace/contents.xcworkspacedata +++ /dev/null @@ -1,7 +0,0 @@ - - - - - diff --git a/objective-c/tutorial5/tutorial5/AppDelegate.h b/objective-c/tutorial5/tutorial5/AppDelegate.h deleted file mode 100644 index 9e6ccbcd..00000000 --- a/objective-c/tutorial5/tutorial5/AppDelegate.h +++ /dev/null @@ -1,17 +0,0 @@ -// -// AppDelegate.h -// tutorial5 -// -// Created by Pivotal on 26/04/2016. -// Copyright © 2016 RabbitMQ. All rights reserved. -// - -#import - -@interface AppDelegate : UIResponder - -@property (strong, nonatomic) UIWindow *window; - - -@end - diff --git a/objective-c/tutorial5/tutorial5/AppDelegate.m b/objective-c/tutorial5/tutorial5/AppDelegate.m deleted file mode 100644 index efe93a65..00000000 --- a/objective-c/tutorial5/tutorial5/AppDelegate.m +++ /dev/null @@ -1,45 +0,0 @@ -// -// AppDelegate.m -// tutorial5 -// -// Created by Pivotal on 26/04/2016. -// Copyright © 2016 RabbitMQ. All rights reserved. -// - -#import "AppDelegate.h" - -@interface AppDelegate () - -@end - -@implementation AppDelegate - - -- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { - // Override point for customization after application launch. - return YES; -} - -- (void)applicationWillResignActive:(UIApplication *)application { - // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. - // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. -} - -- (void)applicationDidEnterBackground:(UIApplication *)application { - // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. - // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. -} - -- (void)applicationWillEnterForeground:(UIApplication *)application { - // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background. -} - -- (void)applicationDidBecomeActive:(UIApplication *)application { - // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. -} - -- (void)applicationWillTerminate:(UIApplication *)application { - // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. -} - -@end diff --git a/objective-c/tutorial5/tutorial5/Assets.xcassets/AppIcon.appiconset/Contents.json b/objective-c/tutorial5/tutorial5/Assets.xcassets/AppIcon.appiconset/Contents.json deleted file mode 100644 index 36d2c80d..00000000 --- a/objective-c/tutorial5/tutorial5/Assets.xcassets/AppIcon.appiconset/Contents.json +++ /dev/null @@ -1,68 +0,0 @@ -{ - "images" : [ - { - "idiom" : "iphone", - "size" : "29x29", - "scale" : "2x" - }, - { - "idiom" : "iphone", - "size" : "29x29", - "scale" : "3x" - }, - { - "idiom" : "iphone", - "size" : "40x40", - "scale" : "2x" - }, - { - "idiom" : "iphone", - "size" : "40x40", - "scale" : "3x" - }, - { - "idiom" : "iphone", - "size" : "60x60", - "scale" : "2x" - }, - { - "idiom" : "iphone", - "size" : "60x60", - "scale" : "3x" - }, - { - "idiom" : "ipad", - "size" : "29x29", - "scale" : "1x" - }, - { - "idiom" : "ipad", - "size" : "29x29", - "scale" : "2x" - }, - { - "idiom" : "ipad", - "size" : "40x40", - "scale" : "1x" - }, - { - "idiom" : "ipad", - "size" : "40x40", - "scale" : "2x" - }, - { - "idiom" : "ipad", - "size" : "76x76", - "scale" : "1x" - }, - { - "idiom" : "ipad", - "size" : "76x76", - "scale" : "2x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/objective-c/tutorial5/tutorial5/Base.lproj/LaunchScreen.storyboard b/objective-c/tutorial5/tutorial5/Base.lproj/LaunchScreen.storyboard deleted file mode 100644 index 2e721e18..00000000 --- a/objective-c/tutorial5/tutorial5/Base.lproj/LaunchScreen.storyboard +++ /dev/null @@ -1,27 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/objective-c/tutorial5/tutorial5/Base.lproj/Main.storyboard b/objective-c/tutorial5/tutorial5/Base.lproj/Main.storyboard deleted file mode 100644 index f56d2f3b..00000000 --- a/objective-c/tutorial5/tutorial5/Base.lproj/Main.storyboard +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/objective-c/tutorial5/tutorial5/Info.plist b/objective-c/tutorial5/tutorial5/Info.plist deleted file mode 100644 index 40c6215d..00000000 --- a/objective-c/tutorial5/tutorial5/Info.plist +++ /dev/null @@ -1,47 +0,0 @@ - - - - - CFBundleDevelopmentRegion - en - CFBundleExecutable - $(EXECUTABLE_NAME) - CFBundleIdentifier - $(PRODUCT_BUNDLE_IDENTIFIER) - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - $(PRODUCT_NAME) - CFBundlePackageType - APPL - CFBundleShortVersionString - 1.0 - CFBundleSignature - ???? - CFBundleVersion - 1 - LSRequiresIPhoneOS - - UILaunchStoryboardName - LaunchScreen - UIMainStoryboardFile - Main - UIRequiredDeviceCapabilities - - armv7 - - UISupportedInterfaceOrientations - - UIInterfaceOrientationPortrait - UIInterfaceOrientationLandscapeLeft - UIInterfaceOrientationLandscapeRight - - UISupportedInterfaceOrientations~ipad - - UIInterfaceOrientationPortrait - UIInterfaceOrientationPortraitUpsideDown - UIInterfaceOrientationLandscapeLeft - UIInterfaceOrientationLandscapeRight - - - diff --git a/objective-c/tutorial5/tutorial5/ViewController.h b/objective-c/tutorial5/tutorial5/ViewController.h deleted file mode 100644 index a79652b5..00000000 --- a/objective-c/tutorial5/tutorial5/ViewController.h +++ /dev/null @@ -1,7 +0,0 @@ -#import - -@interface ViewController : UIViewController - - -@end - diff --git a/objective-c/tutorial5/tutorial5/ViewController.m b/objective-c/tutorial5/tutorial5/ViewController.m deleted file mode 100644 index e5944ad0..00000000 --- a/objective-c/tutorial5/tutorial5/ViewController.m +++ /dev/null @@ -1,53 +0,0 @@ -#import "ViewController.h" -@import RMQClient; - -@interface ViewController () - -@end - -@implementation ViewController - -- (void)viewDidLoad { - [super viewDidLoad]; - - [self receiveLogsTopic:@[@"kern.*", @"*.critical"]]; - sleep(2); - [self emitLogTopic:@"Hello World!" routingKey:@"kern.info"]; - [self emitLogTopic:@"A critical kernel error" routingKey:@"kern.critical"]; - [self emitLogTopic:@"Critical module error" routingKey:@"somemod.critical"]; - [self emitLogTopic:@"Just some module info. You won't get this." routingKey:@"somemod.info"]; -} - -- (void)receiveLogsTopic:(NSArray *)routingKeys { - RMQConnection *conn = [[RMQConnection alloc] initWithDelegate:[RMQConnectionDelegateLogger new]]; - [conn start]; - - id ch = [conn createChannel]; - RMQExchange *x = [ch topic:@"topic_logs"]; - RMQQueue *q = [ch queue:@"" options:RMQQueueDeclareExclusive]; - - for (NSString *routingKey in routingKeys) { - [q bind:x routingKey:routingKey]; - } - - NSLog(@"Waiting for logs."); - - [q subscribe:^(RMQMessage * _Nonnull message) { - NSLog(@"%@:%@", message.routingKey, [[NSString alloc] initWithData:message.body encoding:NSUTF8StringEncoding]); - }]; -} - -- (void)emitLogTopic:(NSString *)msg routingKey:(NSString *)routingKey { - RMQConnection *conn = [[RMQConnection alloc] initWithDelegate:[RMQConnectionDelegateLogger new]]; - [conn start]; - - id ch = [conn createChannel]; - RMQExchange *x = [ch topic:@"topic_logs"]; - - [x publish:[msg dataUsingEncoding:NSUTF8StringEncoding] routingKey:routingKey]; - NSLog(@"Sent '%@'", msg); - - [conn close]; -} - -@end diff --git a/objective-c/tutorial5/tutorial5/main.m b/objective-c/tutorial5/tutorial5/main.m deleted file mode 100644 index 659f0d43..00000000 --- a/objective-c/tutorial5/tutorial5/main.m +++ /dev/null @@ -1,16 +0,0 @@ -// -// main.m -// tutorial5 -// -// Created by Pivotal on 26/04/2016. -// Copyright © 2016 RabbitMQ. All rights reserved. -// - -#import -#import "AppDelegate.h" - -int main(int argc, char * argv[]) { - @autoreleasepool { - return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); - } -} diff --git a/perl/README.md b/perl/README.md index 7f124199..c50ffbe2 100644 --- a/perl/README.md +++ b/perl/README.md @@ -1,7 +1,7 @@ # Perl code for RabbitMQ tutorials Here you can find Perl code examples from [RabbitMQ -tutorials](http://www.rabbitmq.com/getstarted.html). +tutorials](https://www.rabbitmq.com/getstarted.html). To successfully use the examples you will need a running RabbitMQ server. @@ -27,37 +27,37 @@ On Ubuntu: ## Code -[Tutorial one: "Hello World!"](http://www.rabbitmq.com/tutorial-one-python.html): +Tutorial one: "Hello World!" perl send.pl perl receive.pl -[Tutorial two: Work Queues](http://www.rabbitmq.com/tutorial-two-python.html): +Tutorial two: Work Queues perl new_task.pl "A very hard task which takes two seconds.." perl worker.pl -[Tutorial three: Publish/Subscribe](http://www.rabbitmq.com/tutorial-three-python.html): +Tutorial three: Publish/Subscribe perl receive_logs.pl perl emit_log.pl "info: This is the log message" -[Tutorial four: Routing](http://www.rabbitmq.com/tutorial-four-python.html): +Tutorial four: Routing perl receive_logs_direct.pl info perl emit_log_direct.pl info "The message" -[Tutorial five: Topics](http://www.rabbitmq.com/tutorial-five-python.html): +Tutorial five: Topics perl receive_logs_topic.pl "*.rabbit" perl emit_log_topic.pl red.rabbit Hello -[Tutorial six: RPC](http://www.rabbitmq.com/tutorial-six-python.html): +Tutorial six: RPC perl rpc_server.pl perl rpc_client.pl diff --git a/php-amqp/new_task.php b/php-amqp/new_task.php index 04bb3d48..2640ef3f 100644 --- a/php-amqp/new_task.php +++ b/php-amqp/new_task.php @@ -33,7 +33,7 @@ $message = "Hello World!"; $exchange = new AMQPExchange($channel); -$exchange->publish($message, $routing_key); +$exchange->publish($message, $routing_key, AMQP_NOPARAM, array('delivery_mode' => 2)); echo " [x] Sent {$message}", PHP_EOL; diff --git a/php-interop/README.md b/php-interop/README.md index c397f765..d5c2e834 100644 --- a/php-interop/README.md +++ b/php-interop/README.md @@ -21,7 +21,7 @@ Then [install Composer](https://getcomposer.org/download/) per instructions on t ### Client Library -Then you can, for example, install `enqueue/amqp-bunny` using [Composer](http://getcomposer.org). +Then you can, for example, install `enqueue/amqp-bunny` using [Composer](https://getcomposer.org). To do that install Composer and add it to your path, then run the following command inside this project folder: diff --git a/php-thesis/.gitignore b/php-thesis/.gitignore new file mode 100755 index 00000000..d1502b08 --- /dev/null +++ b/php-thesis/.gitignore @@ -0,0 +1,2 @@ +vendor/ +composer.lock diff --git a/php-thesis/Makefile b/php-thesis/Makefile new file mode 100755 index 00000000..aa4121d8 --- /dev/null +++ b/php-thesis/Makefile @@ -0,0 +1,50 @@ +up: + docker compose up -d + +down: + docker compose down + +php: + docker compose exec php bash + +run-send: + docker compose exec php php hello-world/send.php + +run-receive: + docker compose exec php php hello-world/receive.php + +run-new-task: + docker compose exec php php work-queue/new_task.php "A very hard task which takes two seconds.." + +run-worker: + docker compose exec php php work-queue/worker.php + +run-emit-log: + docker compose exec php php pub-sub/emit_log.php + +run-receive-logs: + docker compose exec php php pub-sub/receive_logs.php + +run-emit-log-direct: + docker compose exec php php routing/emit_log_direct.php warning "Something went wrong" + +run-receive-logs-direct: + docker compose exec php php routing/receive_logs_direct.php info warning error + +run-emit-log-topic: + docker compose exec php php topics/emit_log_topic.php "kern.critical" "A critical kernel error" + +run-receive-logs-topic: + docker compose exec php php topics/receive_logs_topic.php "#" + +run-rpc-server: + docker compose exec php php rpc/rpc_server.php + +run-rpc-client: + docker compose exec php php rpc/rpc_client.php + +run-confirms-message: + docker compose exec php php publisher-confirms/message.php + +run-confirms-batch: + docker compose exec php php publisher-confirms/batch.php diff --git a/php-thesis/README.md b/php-thesis/README.md new file mode 100755 index 00000000..51d21ba6 --- /dev/null +++ b/php-thesis/README.md @@ -0,0 +1,47 @@ +# Non-blocking php code for RabbitMQ tutorials based on thesis/amqp + +Run rabbitmq server and php container with `make up`. + +## Code + +[Tutorial one: "Hello World!"](https://www.rabbitmq.com/tutorials/tutorial-one-php): +```shell +make run-send +make run-receive +``` + +[Tutorial two: Work Queues](https://www.rabbitmq.com/tutorials/tutorial-two-php): +```shell +make run-new-task +make run-worker +``` + +[Tutorial three: Publish/Subscribe](https://www.rabbitmq.com/tutorials/tutorial-three-php) +```shell +make run-emit-log +make run-receive-logs +``` + +[Tutorial four: Routing](https://www.rabbitmq.com/tutorials/tutorial-four-php): +```shell +make run-emit-log-direct +make run-receive-logs-direct +``` + +[Tutorial five: Topics](https://www.rabbitmq.com/tutorials/tutorial-five-php): +```shell +make run-emit-log-topic +make run-receive-logs-topic +``` + +[Tutorial six: RPC](https://www.rabbitmq.com/tutorials/tutorial-six-php): +```shell +make run-rpc-server +make run-rpc-client +``` + +[Tutorial seven: Publisher Confirms](https://www.rabbitmq.com/tutorials/tutorial-seven-php): +```shell +make run-rpc-server +make run-rpc-client +``` diff --git a/php-thesis/composer.json b/php-thesis/composer.json new file mode 100755 index 00000000..7775cf83 --- /dev/null +++ b/php-thesis/composer.json @@ -0,0 +1,8 @@ +{ + "require": { + "thesis/amqp": "^1.0" + }, + "require-dev": { + "symfony/var-dumper": "^7.3" + } +} diff --git a/php-thesis/docker-compose.yaml b/php-thesis/docker-compose.yaml new file mode 100755 index 00000000..5fc57bde --- /dev/null +++ b/php-thesis/docker-compose.yaml @@ -0,0 +1,14 @@ +services: + php: + build: + dockerfile: docker/php/Dockerfile + volumes: + - .:/app:cached + command: sh -c 'trap "exit 0" TERM; tail -f /dev/null & wait' + + rabbitmq: + image: rabbitmq:4.0-management-alpine + restart: always + ports: + - "127.0.0.1:5672:5672" + - "127.0.0.1:15672:15672" diff --git a/php-thesis/docker/php/Dockerfile b/php-thesis/docker/php/Dockerfile new file mode 100755 index 00000000..79a5b210 --- /dev/null +++ b/php-thesis/docker/php/Dockerfile @@ -0,0 +1,35 @@ +FROM composer:2.8 AS composer +FROM mlocati/php-extension-installer:2.7 AS php-extension-installer +FROM php:8.3-cli-bookworm AS php-dev + +COPY --from=composer /usr/bin/composer /usr/bin/ +COPY --from=php-extension-installer /usr/bin/install-php-extensions /usr/bin/ + +ARG UID=10001 +ARG GID=10001 + +RUN <channel(); +$channel->queueDeclare('hello'); + +echo " [*] Waiting for messages. To exit press CTRL+C\n"; + +$channel->consume( + static function (DeliveryMessage $delivery): void { + echo " [x] Received {$delivery->message->body} \n"; + }, + queue: 'hello', + noAck: true, +); + +trapSignal([\SIGTERM, \SIGINT]); + +$client->disconnect(); diff --git a/php-thesis/hello-world/send.php b/php-thesis/hello-world/send.php new file mode 100755 index 00000000..3a45d984 --- /dev/null +++ b/php-thesis/hello-world/send.php @@ -0,0 +1,21 @@ +channel(); + +$channel->publish(new Message('Hello World!'), routingKey: 'hello'); + +$client->disconnect(); diff --git a/php-thesis/pub-sub/emit_log.php b/php-thesis/pub-sub/emit_log.php new file mode 100644 index 00000000..f1c9919c --- /dev/null +++ b/php-thesis/pub-sub/emit_log.php @@ -0,0 +1,27 @@ +channel(); +$channel->exchangeDeclare(exchange: 'logs', exchangeType: 'fanout'); + +$channel->publish( + new Message( + body: $data = implode(' ', array_slice($argv, 1)) ?: 'info: Hello World!', + ), + exchange: 'logs', +); + +$client->disconnect(); diff --git a/php-thesis/pub-sub/receive_logs.php b/php-thesis/pub-sub/receive_logs.php new file mode 100644 index 00000000..b49a237b --- /dev/null +++ b/php-thesis/pub-sub/receive_logs.php @@ -0,0 +1,36 @@ +channel(); + +$channel->exchangeDeclare(exchange: 'logs', exchangeType: 'fanout'); +$queue = $channel->queueDeclare(exclusive: true); +$channel->queueBind(queue: $queue->name, exchange: 'logs'); + +echo " [*] Waiting for logs. To exit press CTRL+C\n"; + +$channel->consume( + callback: static function (DeliveryMessage $delivery): void { + echo " [x] {$delivery->message->body} \n"; + }, + queue: $queue->name, + noAck: true, +); + +trapSignal([\SIGTERM, \SIGINT]); + +$client->disconnect(); diff --git a/php-thesis/publisher-confirms/batch.php b/php-thesis/publisher-confirms/batch.php new file mode 100644 index 00000000..2d95f7ba --- /dev/null +++ b/php-thesis/publisher-confirms/batch.php @@ -0,0 +1,34 @@ +channel(); +$channel->confirmSelect(); + +$queue = $channel->queueDeclare(exclusive: true); + +$channel + ->publishBatch(array_map( + static fn(int $number): PublishMessage => new PublishMessage(new Message("{$number}"), routingKey: $queue->name), + range(1, 100), + )) + ->await() + ->ensureAllPublished() +; + +echo "Messages sent\n"; + +$client->disconnect(); diff --git a/php-thesis/publisher-confirms/message.php b/php-thesis/publisher-confirms/message.php new file mode 100644 index 00000000..72eed709 --- /dev/null +++ b/php-thesis/publisher-confirms/message.php @@ -0,0 +1,31 @@ +channel(); +$channel->confirmSelect(); + +$queue = $channel->queueDeclare(exclusive: true); + +$channel + ->publish( + new Message('Hello World!'), + routingKey: $queue->name, + ) + ?->await(); + +echo "Message sent\n"; + +$client->disconnect(); diff --git a/php-thesis/routing/emit_log_direct.php b/php-thesis/routing/emit_log_direct.php new file mode 100644 index 00000000..226c4afa --- /dev/null +++ b/php-thesis/routing/emit_log_direct.php @@ -0,0 +1,33 @@ +channel(); + +$channel->exchangeDeclare(exchange: 'direct_logs', exchangeType: 'direct'); + +$severity = $argv[1] ?? 'info'; + +$channel->publish( + new Message( + body: $data = implode(' ', array_slice($argv, 2)) ?: 'Hello World!', + ), + exchange: 'direct_logs', + routingKey: $severity, +); + +echo " [x] Sent {$severity}: {$data} \n"; + +$client->disconnect(); diff --git a/php-thesis/routing/receive_logs_direct.php b/php-thesis/routing/receive_logs_direct.php new file mode 100644 index 00000000..e6bcc709 --- /dev/null +++ b/php-thesis/routing/receive_logs_direct.php @@ -0,0 +1,45 @@ +channel(); + +$channel->exchangeDeclare(exchange: 'direct_logs', exchangeType: 'direct'); +$queue = $channel->queueDeclare(exclusive: true); + +$severities = array_slice($argv, 1); +if ($severities === []) { + file_put_contents('php://stderr', "Usage: $argv[0] [info] [warning] [error]\n"); + exit(1); +} + +foreach ($severities as $severity) { + $channel->queueBind(queue: $queue->name, exchange: 'direct_logs', routingKey: $severity); +} + +echo " [*] Waiting for logs. To exit press CTRL+C\n"; + +$channel->consume( + callback: static function (DeliveryMessage $delivery): void { + echo " [x] {$delivery->routingKey}: {$delivery->message->body} \n"; + }, + queue: $queue->name, + noAck: true, +); + +trapSignal([\SIGTERM, \SIGINT]); + +$client->disconnect(); diff --git a/php-thesis/rpc/rpc_client.php b/php-thesis/rpc/rpc_client.php new file mode 100644 index 00000000..e24ec424 --- /dev/null +++ b/php-thesis/rpc/rpc_client.php @@ -0,0 +1,24 @@ +request( + new Message('30'), + routingKey: 'rpc_queue', +); + +echo " [.] Got {$response->body} \n"; diff --git a/php-thesis/rpc/rpc_server.php b/php-thesis/rpc/rpc_server.php new file mode 100644 index 00000000..5f9f993e --- /dev/null +++ b/php-thesis/rpc/rpc_server.php @@ -0,0 +1,53 @@ +channel(); +$channel->queueDeclare('rpc_queue'); + +echo " [x] Awaiting RPC requests\n"; + +$channel->qos(prefetchCount: 1); +$channel->consume( + callback: static function (DeliveryMessage $delivery): void { + $n = intval($delivery->message->body); + echo " [.] fib({$n})\n"; + + $delivery->reply( + new Message((string) fib($n)), + ); + }, + queue: 'rpc_queue', + noAck: true, +); + +trapSignal([\SIGTERM, \SIGINT]); + +$client->disconnect(); diff --git a/php-thesis/topics/emit_log_topic.php b/php-thesis/topics/emit_log_topic.php new file mode 100644 index 00000000..0cda74b9 --- /dev/null +++ b/php-thesis/topics/emit_log_topic.php @@ -0,0 +1,32 @@ +channel(); +$channel->exchangeDeclare(exchange: 'topics_logs', exchangeType: 'topic'); + +$routingKey = $argv[1] ?? 'anonymous.info'; + +$channel->publish( + message: new Message( + body: $data = implode(' ', array_slice($argv, 2)) ?: 'Hello World!', + ), + exchange: 'topics_logs', + routingKey: $routingKey, +); + +echo " [x] Sent {$routingKey}: {$data} \n"; + +$client->disconnect(); diff --git a/php-thesis/topics/receive_logs_topic.php b/php-thesis/topics/receive_logs_topic.php new file mode 100644 index 00000000..7e7a6ff2 --- /dev/null +++ b/php-thesis/topics/receive_logs_topic.php @@ -0,0 +1,45 @@ +channel(); + +$channel->exchangeDeclare(exchange: 'topics_logs', exchangeType: 'topic'); +$queue = $channel->queueDeclare(exclusive: true); + +$bindingKeys = array_slice($argv, 1); +if ($bindingKeys === []) { + file_put_contents('php://stderr', "Usage: $argv[0] [binding_key]\n"); + exit(1); +} + +foreach ($bindingKeys as $bindingKey) { + $channel->queueBind(queue: $queue->name, exchange: 'topics_logs', routingKey: $bindingKey); +} + +echo " [*] Waiting for logs. To exit press CTRL+C\n"; + +$channel->consume( + callback: static function (DeliveryMessage $delivery): void { + echo " [x] {$delivery->routingKey}: {$delivery->message->body} \n"; + }, + queue: $queue->name, + noAck: true, +); + +trapSignal([\SIGTERM, \SIGINT]); + +$client->disconnect(); diff --git a/php-thesis/work-queue/new_task.php b/php-thesis/work-queue/new_task.php new file mode 100644 index 00000000..50b2ca07 --- /dev/null +++ b/php-thesis/work-queue/new_task.php @@ -0,0 +1,29 @@ +channel(); +$channel->queueDeclare('task_queue', durable: true); + +$channel->publish( + new Message( + body: $data = implode(' ', array_slice($argv, 1)) ?: 'Hello World!', + deliveryMode: DeliveryMode::Persistent, + ), + routingKey: 'task_queue', +); + +echo " [x] Sent {$data} \n"; diff --git a/php-thesis/work-queue/worker.php b/php-thesis/work-queue/worker.php new file mode 100644 index 00000000..1658ba6d --- /dev/null +++ b/php-thesis/work-queue/worker.php @@ -0,0 +1,37 @@ +channel(); +$channel->queueDeclare('task_queue', durable: true); + +echo " [*] Waiting for messages. To exit press CTRL+C\n"; + +$channel->qos(prefetchCount: 1); +$channel->consume( + callback: static function (DeliveryMessage $delivery): void { + echo " [x] Received {$delivery->message->body} \n"; + delay(substr_count($delivery->message->body, '.')); + echo " [x] Done\n"; + $delivery->ack(); + }, + queue: 'task_queue', +); + +trapSignal([\SIGTERM, \SIGINT]); + +$client->disconnect(); diff --git a/php/README.md b/php/README.md index 0bdd70e2..e1dc73ff 100644 --- a/php/README.md +++ b/php/README.md @@ -1,18 +1,18 @@ # PHP code for RabbitMQ tutorials Here you can find PHP code examples from [RabbitMQ -tutorials](http://www.rabbitmq.com/getstarted.html). +tutorials](https://www.rabbitmq.com/getstarted.html). To successfully use the examples you will need a running RabbitMQ server. ## Requirements -### PHP 5.3+ +### PHP 7.2+ -You need `PHP 5.3` and `php-amqplib`. To get these +You need `PHP 7.2` and `php-amqplib`. To get these dependencies on Ubuntu type: - sudo apt-get install git-core php5-cli + sudo apt-get install git-core php-cli ### Composer @@ -22,44 +22,48 @@ Then [install Composer](https://getcomposer.org/download/) per instructions on t ### Client Library -Then you can install `php-amqplib` using [Composer](http://getcomposer.org). +Then you can install `php-amqplib` using [Composer](https://getcomposer.org). To do that install Composer and add it to your path, then run the following command inside this project folder: - composer.phar install + php composer.phar install + +Or you can require it to the existing project using a command: + + php composer.phar require php-amqplib/php-amqplib ## Code -[Tutorial one: "Hello World!"](http://www.rabbitmq.com/tutorials/tutorial-one-php.html): +[Tutorial one: "Hello World!"](https://www.rabbitmq.com/tutorials/tutorial-one-php.html): php send.php php receive.php -[Tutorial two: Work Queues](http://www.rabbitmq.com/tutorials/tutorial-two-php.html): +[Tutorial two: Work Queues](https://www.rabbitmq.com/tutorials/tutorial-two-php.html): php new_task.php "A very hard task which takes two seconds.." php worker.php -[Tutorial three: Publish/Subscribe](http://www.rabbitmq.com/tutorials/tutorial-three-php.html) +[Tutorial three: Publish/Subscribe](https://www.rabbitmq.com/tutorials/tutorial-three-php.html) php receive_logs.php php emit_log.php "info: This is the log message" -[Tutorial four: Routing](http://www.rabbitmq.com/tutorials/tutorial-four-php.html): +[Tutorial four: Routing](https://www.rabbitmq.com/tutorials/tutorial-four-php.html): php receive_logs_direct.php info php emit_log_direct.php info "The message" -[Tutorial five: Topics](http://www.rabbitmq.com/tutorials/tutorial-five-php.html): +[Tutorial five: Topics](https://www.rabbitmq.com/tutorials/tutorial-five-php.html): php receive_logs_topic.php "*.rabbit" php emit_log_topic.php red.rabbit Hello -[Tutorial six: RPC](http://www.rabbitmq.com/tutorials/tutorial-six-php.html): +[Tutorial six: RPC](https://www.rabbitmq.com/tutorials/tutorial-six-php.html): php rpc_server.php php rpc_client.php diff --git a/php/composer.json b/php/composer.json index 39288364..9745cdb9 100644 --- a/php/composer.json +++ b/php/composer.json @@ -1,5 +1,5 @@ { "require": { - "php-amqplib/php-amqplib": ">=2.6.1" + "php-amqplib/php-amqplib": "^3.7" } } diff --git a/php/emit_log.php b/php/emit_log.php index aafcd2d5..30002ccd 100644 --- a/php/emit_log.php +++ b/php/emit_log.php @@ -21,3 +21,4 @@ $channel->close(); $connection->close(); +?> diff --git a/php/emit_log_direct.php b/php/emit_log_direct.php index e68d662f..0288d7e8 100644 --- a/php/emit_log_direct.php +++ b/php/emit_log_direct.php @@ -24,3 +24,4 @@ $channel->close(); $connection->close(); +?> diff --git a/php/emit_log_topic.php b/php/emit_log_topic.php index c39e2ff2..14899466 100644 --- a/php/emit_log_topic.php +++ b/php/emit_log_topic.php @@ -23,3 +23,4 @@ $channel->close(); $connection->close(); +?> diff --git a/php/new_task.php b/php/new_task.php index 7be98db3..f6c1edc6 100644 --- a/php/new_task.php +++ b/php/new_task.php @@ -3,11 +3,12 @@ require_once __DIR__ . '/vendor/autoload.php'; use PhpAmqpLib\Connection\AMQPStreamConnection; use PhpAmqpLib\Message\AMQPMessage; +use PhpAmqpLib\Wire\AMQPTable; $connection = new AMQPStreamConnection('localhost', 5672, 'guest', 'guest'); $channel = $connection->channel(); -$channel->queue_declare('task_queue', false, true, false, false); +$channel->queue_declare('task_queue', false, true, false, false, false, new AMQPTable(['x-queue-type' => 'quorum'])); $data = implode(' ', array_slice($argv, 1)); if (empty($data)) { @@ -24,3 +25,4 @@ $channel->close(); $connection->close(); +?> diff --git a/php/receive.php b/php/receive.php index 047617ea..af993553 100644 --- a/php/receive.php +++ b/php/receive.php @@ -2,22 +2,25 @@ require_once __DIR__ . '/vendor/autoload.php'; use PhpAmqpLib\Connection\AMQPStreamConnection; +use PhpAmqpLib\Wire\AMQPTable; $connection = new AMQPStreamConnection('localhost', 5672, 'guest', 'guest'); $channel = $connection->channel(); -$channel->queue_declare('hello', false, false, false, false); +$channel->queue_declare('hello', false, true, false, false, false, new AMQPTable(['x-queue-type' => 'quorum'])); echo " [*] Waiting for messages. To exit press CTRL+C\n"; $callback = function ($msg) { - echo ' [x] Received ', $msg->body, "\n"; + echo ' [x] Received ', $msg->getBody(), "\n"; }; $channel->basic_consume('hello', '', false, true, false, false, $callback); -while (count($channel->callbacks)) { - $channel->wait(); +try { + $channel->consume(); +} catch (\Throwable $exception) { + echo $exception->getMessage(); } $channel->close(); diff --git a/php/receive_logs.php b/php/receive_logs.php index 34e3a257..00e5e4d8 100644 --- a/php/receive_logs.php +++ b/php/receive_logs.php @@ -15,13 +15,15 @@ echo " [*] Waiting for logs. To exit press CTRL+C\n"; $callback = function ($msg) { - echo ' [x] ', $msg->body, "\n"; + echo ' [x] ', $msg->getBody(), "\n"; }; $channel->basic_consume($queue_name, '', false, true, false, false, $callback); -while (count($channel->callbacks)) { - $channel->wait(); +try { + $channel->consume(); +} catch (\Throwable $exception) { + echo $exception->getMessage(); } $channel->close(); diff --git a/php/receive_logs_direct.php b/php/receive_logs_direct.php index a93547bf..6d5517d0 100644 --- a/php/receive_logs_direct.php +++ b/php/receive_logs_direct.php @@ -23,13 +23,15 @@ echo " [*] Waiting for logs. To exit press CTRL+C\n"; $callback = function ($msg) { - echo ' [x] ', $msg->delivery_info['routing_key'], ':', $msg->body, "\n"; + echo ' [x] ', $msg->getRoutingKey(), ':', $msg->getBody(), "\n"; }; $channel->basic_consume($queue_name, '', false, true, false, false, $callback); -while (count($channel->callbacks)) { - $channel->wait(); +try { + $channel->consume(); +} catch (\Throwable $exception) { + echo $exception->getMessage(); } $channel->close(); diff --git a/php/receive_logs_topic.php b/php/receive_logs_topic.php index 22266270..a198e7f4 100644 --- a/php/receive_logs_topic.php +++ b/php/receive_logs_topic.php @@ -23,13 +23,15 @@ echo " [*] Waiting for logs. To exit press CTRL+C\n"; $callback = function ($msg) { - echo ' [x] ', $msg->delivery_info['routing_key'], ':', $msg->body, "\n"; + echo ' [x] ', $msg->getRoutingKey(), ':', $msg->getBody(), "\n"; }; $channel->basic_consume($queue_name, '', false, true, false, false, $callback); -while (count($channel->callbacks)) { - $channel->wait(); +try { + $channel->consume(); +} catch (\Throwable $exception) { + echo $exception->getMessage(); } $channel->close(); diff --git a/php/rpc_client.php b/php/rpc_client.php index 8799c4f2..d967de1b 100644 --- a/php/rpc_client.php +++ b/php/rpc_client.php @@ -32,7 +32,7 @@ public function __construct() $this->callback_queue, '', false, - false, + true, false, false, array( @@ -45,7 +45,7 @@ public function __construct() public function onResponse($rep) { if ($rep->get('correlation_id') == $this->corr_id) { - $this->response = $rep->body; + $this->response = $rep->getBody(); } } @@ -72,3 +72,4 @@ public function call($n) $fibonacci_rpc = new FibonacciRpcClient(); $response = $fibonacci_rpc->call(30); echo ' [.] Got ', $response, "\n"; +?> diff --git a/php/rpc_server.php b/php/rpc_server.php index c8a5b0f8..962fc998 100644 --- a/php/rpc_server.php +++ b/php/rpc_server.php @@ -3,11 +3,12 @@ require_once __DIR__ . '/vendor/autoload.php'; use PhpAmqpLib\Connection\AMQPStreamConnection; use PhpAmqpLib\Message\AMQPMessage; +use PhpAmqpLib\Wire\AMQPTable; $connection = new AMQPStreamConnection('localhost', 5672, 'guest', 'guest'); $channel = $connection->channel(); -$channel->queue_declare('rpc_queue', false, false, false, false); +$channel->queue_declare('rpc_queue', false, true, false, false, false, new AMQPTable(['x-queue-type' => 'quorum'])); function fib($n) { @@ -22,7 +23,7 @@ function fib($n) echo " [x] Awaiting RPC requests\n"; $callback = function ($req) { - $n = intval($req->body); + $n = intval($req->getBody()); echo ' [.] fib(', $n, ")\n"; $msg = new AMQPMessage( @@ -30,21 +31,21 @@ function fib($n) array('correlation_id' => $req->get('correlation_id')) ); - $req->delivery_info['channel']->basic_publish( + $req->getChannel()->basic_publish( $msg, '', $req->get('reply_to') ); - $req->delivery_info['channel']->basic_ack( - $req->delivery_info['delivery_tag'] - ); + $req->ack(); }; -$channel->basic_qos(null, 1, null); +$channel->basic_qos(null, 1, false); $channel->basic_consume('rpc_queue', '', false, false, false, false, $callback); -while (count($channel->callbacks)) { - $channel->wait(); +try { + $channel->consume(); +} catch (\Throwable $exception) { + echo $exception->getMessage(); } $channel->close(); diff --git a/php/send.php b/php/send.php index 00c42b3a..5630d922 100644 --- a/php/send.php +++ b/php/send.php @@ -3,11 +3,12 @@ require_once __DIR__ . '/vendor/autoload.php'; use PhpAmqpLib\Connection\AMQPStreamConnection; use PhpAmqpLib\Message\AMQPMessage; +use PhpAmqpLib\Wire\AMQPTable; $connection = new AMQPStreamConnection('localhost', 5672, 'guest', 'guest'); $channel = $connection->channel(); -$channel->queue_declare('hello', false, false, false, false); +$channel->queue_declare('hello', false, true, false, false, false, new AMQPTable(['x-queue-type' => 'quorum'])); $msg = new AMQPMessage('Hello World!'); $channel->basic_publish($msg, '', 'hello'); @@ -16,3 +17,4 @@ $channel->close(); $connection->close(); +?> diff --git a/php/worker.php b/php/worker.php index 7f425a23..ea692940 100644 --- a/php/worker.php +++ b/php/worker.php @@ -2,26 +2,29 @@ require_once __DIR__ . '/vendor/autoload.php'; use PhpAmqpLib\Connection\AMQPStreamConnection; +use PhpAmqpLib\Wire\AMQPTable; $connection = new AMQPStreamConnection('localhost', 5672, 'guest', 'guest'); $channel = $connection->channel(); -$channel->queue_declare('task_queue', false, true, false, false); +$channel->queue_declare('task_queue', false, true, false, false, false, new AMQPTable(['x-queue-type' => 'quorum'])); echo " [*] Waiting for messages. To exit press CTRL+C\n"; $callback = function ($msg) { - echo ' [x] Received ', $msg->body, "\n"; - sleep(substr_count($msg->body, '.')); + echo ' [x] Received ', $msg->getBody(), "\n"; + sleep(substr_count($msg->getBody(), '.')); echo " [x] Done\n"; - $msg->delivery_info['channel']->basic_ack($msg->delivery_info['delivery_tag']); + $msg->ack(); }; -$channel->basic_qos(null, 1, null); +$channel->basic_qos(null, 1, false); $channel->basic_consume('task_queue', '', false, false, false, false, $callback); -while (count($channel->callbacks)) { - $channel->wait(); +try { + $channel->consume(); +} catch (\Throwable $exception) { + echo $exception->getMessage(); } $channel->close(); diff --git a/python-stream/README.md b/python-stream/README.md new file mode 100644 index 00000000..42c58642 --- /dev/null +++ b/python-stream/README.md @@ -0,0 +1,30 @@ +# Python code for RabbitMQ tutorials + + +Here you can find Python code examples from [RabbitMQ tutorials](https://www.rabbitmq.com/getstarted.html). + + +## Requirements + +These examples use the [`qweeze/rstream`](https://github.com/qweeze/rstream) client library. + +Get it first with the necessary dependencies: + + pip3 install -r requirements.txt + +## Code + +Code examples are executed via `python3`: + +[Tutorial one: "Hello World!"](https://www.rabbitmq.com/tutorials/tutorial-one-python-stream): + + python3 send.py + python3 receive.py + +[Tutorial two: "Offset tracking"](https://www.rabbitmq.com/tutorials/tutorial-two-python-stream): + + python3 offset_tracking_send.py + python3 offset_tracking_receive.py + + +To learn more, see [`rqweeze/rstream`](https://github.com/qweeze/rstream). \ No newline at end of file diff --git a/python-stream/offset_tracking_receive.py b/python-stream/offset_tracking_receive.py new file mode 100644 index 00000000..d3500270 --- /dev/null +++ b/python-stream/offset_tracking_receive.py @@ -0,0 +1,114 @@ +import asyncio + +from rstream import ( + AMQPMessage, + Consumer, + ConsumerOffsetSpecification, + MessageContext, + OffsetNotFound, + OffsetType, + ServerError, + amqp_decoder, +) + +message_count = -1 +first_offset = -1 +last_offset = -1 +STREAM_NAME = "stream-offset-tracking-python" +# 2GB +STREAM_RETENTION = 2000000000 + + +async def on_message(msg: AMQPMessage, message_context: MessageContext): + global message_count + global first_offset + global last_offset + + offset = message_context.offset + if first_offset == -1: + print("First message received") + first_offset = offset + + consumer = message_context.consumer + stream = message_context.stream + + # store the offset after every 10 messages received + message_count = message_count + 1 + + if message_count % 10 == 0: + await consumer.store_offset( + stream=stream, + offset=offset, + subscriber_name=message_context.subscriber_name, + ) + + if "marker" in str(msg): + await consumer.store_offset( + stream=stream, + offset=offset, + subscriber_name=message_context.subscriber_name, + ) + last_offset = offset + await consumer.close() + + +async def consume(): + stored_offset = -1 + global first_offset + global last_offset + + consumer = Consumer( + host="localhost", + port=5552, + username="guest", + password="guest", + ) + + await consumer.create_stream( + STREAM_NAME, exists_ok=True, arguments={"max-length-bytes": STREAM_RETENTION} + ) + + try: + await consumer.start() + print("Started consuming: Press control +C to close") + try: + # will raise an exception if store_offset wasn't invoked before + stored_offset = await consumer.query_offset( + stream=STREAM_NAME, subscriber_name="subscriber_1" + ) + except OffsetNotFound as offset_exception: + print(f"Offset not previously stored. {offset_exception}") + + except ServerError as server_error: + print(f"Server error: {server_error}") + exit(1) + + # if no offset was previously stored start from the first offset + stored_offset = stored_offset + 1 + await consumer.subscribe( + stream=STREAM_NAME, + subscriber_name="subscriber_1", + callback=on_message, + decoder=amqp_decoder, + offset_specification=ConsumerOffsetSpecification( + OffsetType.OFFSET, stored_offset + ), + ) + await consumer.run() + + except (KeyboardInterrupt, asyncio.exceptions.CancelledError): + await consumer.close() + + # give time to the consumer task to close the consumer + await asyncio.sleep(1) + + if first_offset != -1: + print( + "Done consuming first_offset: {} last_offset {} ".format( + first_offset, last_offset + ) + ) + + +with asyncio.Runner() as runner: + runner.run(consume()) diff --git a/python-stream/offset_tracking_send.py b/python-stream/offset_tracking_send.py new file mode 100644 index 00000000..8e0c624e --- /dev/null +++ b/python-stream/offset_tracking_send.py @@ -0,0 +1,58 @@ +import asyncio + +from rstream import AMQPMessage, ConfirmationStatus, Producer + +STREAM = "stream-offset-tracking-python" +MESSAGES = 100 +# 2GB +STREAM_RETENTION = 2000000000 +confirmed_messages = 0 +all_confirmed_messages_cond = asyncio.Condition() + + +async def _on_publish_confirm_client(confirmation: ConfirmationStatus) -> None: + global confirmed_messages + if confirmation.is_confirmed: + confirmed_messages = confirmed_messages + 1 + if confirmed_messages == 100: + async with all_confirmed_messages_cond: + all_confirmed_messages_cond.notify() + + +async def publish(): + async with Producer("localhost", username="guest", password="guest") as producer: + # create a stream if it doesn't already exist + await producer.create_stream( + STREAM, exists_ok=True, arguments={"max-length-bytes": STREAM_RETENTION} + ) + + print("Publishing {} messages".format(MESSAGES)) + # Send 99 hello message + for i in range(MESSAGES - 1): + amqp_message = AMQPMessage( + body=bytes("hello: {}".format(i), "utf-8"), + ) + + await producer.send( + stream=STREAM, + message=amqp_message, + on_publish_confirm=_on_publish_confirm_client, + ) + # Send a final marker message + amqp_message = AMQPMessage( + body=bytes("marker: {}".format(i + 1), "utf-8"), + ) + + await producer.send( + stream=STREAM, + message=amqp_message, + on_publish_confirm=_on_publish_confirm_client, + ) + + async with all_confirmed_messages_cond: + await all_confirmed_messages_cond.wait() + + print("Messages confirmed: true") + + +asyncio.run(publish()) diff --git a/python-stream/receive.py b/python-stream/receive.py new file mode 100644 index 00000000..847a0e93 --- /dev/null +++ b/python-stream/receive.py @@ -0,0 +1,40 @@ +import asyncio + +from rstream import ( + AMQPMessage, + Consumer, + ConsumerOffsetSpecification, + MessageContext, + OffsetType, +) + +STREAM_NAME = "hello-python-stream" +# 5GB +STREAM_RETENTION = 5000000000 + + +async def receive(): + async with Consumer(host="localhost", username="guest", password="guest") as consumer: + await consumer.create_stream( + STREAM_NAME, exists_ok=True, arguments={"max-length-bytes": STREAM_RETENTION} + ) + + async def on_message(msg: AMQPMessage, message_context: MessageContext): + print("Got message: {} from stream {}".format(msg, message_context.stream)) + + print("Press control + C to close") + await consumer.start() + await consumer.subscribe( + stream=STREAM_NAME, + callback=on_message, + offset_specification=ConsumerOffsetSpecification(OffsetType.FIRST, None), + ) + try: + await consumer.run() + except (KeyboardInterrupt, asyncio.CancelledError): + print("Closing Consumer...") + return + + +with asyncio.Runner() as runner: + runner.run(receive()) diff --git a/python-stream/requirements.txt b/python-stream/requirements.txt new file mode 100644 index 00000000..1230cd22 --- /dev/null +++ b/python-stream/requirements.txt @@ -0,0 +1 @@ +rstream diff --git a/python-stream/send.py b/python-stream/send.py new file mode 100644 index 00000000..801dce7c --- /dev/null +++ b/python-stream/send.py @@ -0,0 +1,27 @@ +import asyncio + +from rstream import Producer + +STREAM_NAME = "hello-python-stream" +# 5GB +STREAM_RETENTION = 5000000000 + + +async def send(): + async with Producer( + host="localhost", + username="guest", + password="guest", + ) as producer: + await producer.create_stream( + STREAM_NAME, exists_ok=True, arguments={"max-length-bytes": STREAM_RETENTION} + ) + + await producer.send(stream=STREAM_NAME, message=b"Hello, World!") + + print(" [x] Hello, World! message sent") + + input(" [x] Press Enter to close the producer ...") + +with asyncio.Runner() as runner: + runner.run(send()) diff --git a/python/README.md b/python/README.md index a607520a..201bbbec 100644 --- a/python/README.md +++ b/python/README.md @@ -1,15 +1,15 @@ # Python code for RabbitMQ tutorials Here you can find Python code examples from [RabbitMQ -tutorials](http://www.rabbitmq.com/getstarted.html). +tutorials](https://www.rabbitmq.com/getstarted.html). To successfully use the examples you will need a running RabbitMQ server. ## Requirements -To run this code you need to install the `pika` package version 0.10.0 or later. To install it, run +To run this code you need to install the `pika` package version `1.0.0` or later. To install it, run - pip install pika==0.11.0 + python -m pip install pika You may first need to run @@ -18,37 +18,37 @@ You may first need to run ## Code -[Tutorial one: "Hello World!"](http://www.rabbitmq.com/tutorial-one-python.html): +[Tutorial one: "Hello World!"](https://www.rabbitmq.com/tutorials/tutorial-one-python.html): python send.py python receive.py -[Tutorial two: Work Queues](http://www.rabbitmq.com/tutorial-two-python.html): +[Tutorial two: Work Queues](https://www.rabbitmq.com/tutorials/tutorial-two-python.html): python new_task.py "A very hard task which takes two seconds.." python worker.py -[Tutorial three: Publish/Subscribe](http://www.rabbitmq.com/tutorial-three-python.html): +[Tutorial three: Publish/Subscribe](https://www.rabbitmq.com/tutorials/tutorial-three-python.html): python receive_logs.py python emit_log.py "info: This is the log message" -[Tutorial four: Routing](http://www.rabbitmq.com/tutorial-four-python.html): +[Tutorial four: Routing](https://www.rabbitmq.com/tutorials/tutorial-four-python.html): python receive_logs_direct.py info python emit_log_direct.py info "The message" -[Tutorial five: Topics](http://www.rabbitmq.com/tutorial-five-python.html): +[Tutorial five: Topics](https://www.rabbitmq.com/tutorials/tutorial-five-python.html): python receive_logs_topic.py "*.rabbit" python emit_log_topic.py red.rabbit Hello -[Tutorial six: RPC](http://www.rabbitmq.com/tutorial-six-python.html): +[Tutorial six: RPC](https://www.rabbitmq.com/tutorials/tutorial-six-python.html): python rpc_server.py python rpc_client.py diff --git a/python/emit_log.py b/python/emit_log.py index 1d7a1c85..5bd35d1f 100755 --- a/python/emit_log.py +++ b/python/emit_log.py @@ -1,17 +1,17 @@ #!/usr/bin/env python -import pika import sys -connection = pika.BlockingConnection(pika.ConnectionParameters( - host='localhost')) +import pika + +connection = pika.BlockingConnection( + pika.ConnectionParameters(host="localhost"), +) channel = connection.channel() -channel.exchange_declare(exchange='logs', - exchange_type='fanout') +channel.exchange_declare(exchange="logs", exchange_type="fanout") + +message = " ".join(sys.argv[1:]) or "info: Hello World!" +channel.basic_publish(exchange="logs", routing_key="", body=message) +print(f" [x] Sent {message}") -message = ' '.join(sys.argv[1:]) or "info: Hello World!" -channel.basic_publish(exchange='logs', - routing_key='', - body=message) -print(" [x] Sent %r" % message) connection.close() diff --git a/python/emit_log_direct.py b/python/emit_log_direct.py index 1aec46de..dfeca104 100755 --- a/python/emit_log_direct.py +++ b/python/emit_log_direct.py @@ -1,18 +1,22 @@ #!/usr/bin/env python -import pika import sys -connection = pika.BlockingConnection(pika.ConnectionParameters( - host='localhost')) +import pika + +connection = pika.BlockingConnection( + pika.ConnectionParameters(host="localhost"), +) channel = connection.channel() -channel.exchange_declare(exchange='direct_logs', - exchange_type='direct') +channel.exchange_declare(exchange="direct_logs", exchange_type="direct") + +severity = sys.argv[1] if len(sys.argv) > 2 else "info" +message = " ".join(sys.argv[2:]) or "Hello World!" +channel.basic_publish( + exchange="direct_logs", + routing_key=severity, + body=message, +) +print(f" [x] Sent {severity}:{message}") -severity = sys.argv[1] if len(sys.argv) > 2 else 'info' -message = ' '.join(sys.argv[2:]) or 'Hello World!' -channel.basic_publish(exchange='direct_logs', - routing_key=severity, - body=message) -print(" [x] Sent %r:%r" % (severity, message)) connection.close() diff --git a/python/emit_log_topic.py b/python/emit_log_topic.py index 5161053f..8c9d0fc2 100755 --- a/python/emit_log_topic.py +++ b/python/emit_log_topic.py @@ -1,18 +1,22 @@ #!/usr/bin/env python -import pika import sys -connection = pika.BlockingConnection(pika.ConnectionParameters( - host='localhost')) +import pika + +connection = pika.BlockingConnection( + pika.ConnectionParameters(host="localhost"), +) channel = connection.channel() -channel.exchange_declare(exchange='topic_logs', - exchange_type='topic') +channel.exchange_declare(exchange="topic_logs", exchange_type="topic") + +routing_key = sys.argv[1] if len(sys.argv) > 2 else "anonymous.info" +message = " ".join(sys.argv[2:]) or "Hello World!" +channel.basic_publish( + exchange="topic_logs", + routing_key=routing_key, + body=message, +) +print(f" [x] Sent {routing_key}:{message}") -routing_key = sys.argv[1] if len(sys.argv) > 2 else 'anonymous.info' -message = ' '.join(sys.argv[2:]) or 'Hello World!' -channel.basic_publish(exchange='topic_logs', - routing_key=routing_key, - body=message) -print(" [x] Sent %r:%r" % (routing_key, message)) connection.close() diff --git a/python/new_task.py b/python/new_task.py index 2193d63a..4118bdb4 100755 --- a/python/new_task.py +++ b/python/new_task.py @@ -1,19 +1,24 @@ #!/usr/bin/env python -import pika import sys -connection = pika.BlockingConnection(pika.ConnectionParameters( - host='localhost')) +import pika + +connection = pika.BlockingConnection( + pika.ConnectionParameters(host="localhost"), +) channel = connection.channel() -channel.queue_declare(queue='task_queue', durable=True) +channel.queue_declare(queue="task_queue", durable=True, arguments={"x-queue-type": "quorum"}) + +message = " ".join(sys.argv[1:]) or "Hello World!" +channel.basic_publish( + exchange="", + routing_key="task_queue", + body=message, + properties=pika.BasicProperties( + delivery_mode=pika.DeliveryMode.Persistent, + ), +) +print(f" [x] Sent {message}") -message = ' '.join(sys.argv[1:]) or "Hello World!" -channel.basic_publish(exchange='', - routing_key='task_queue', - body=message, - properties=pika.BasicProperties( - delivery_mode = 2, # make message persistent - )) -print(" [x] Sent %r" % message) connection.close() diff --git a/python/receive.py b/python/receive.py index f7d1d2dd..874e9a19 100755 --- a/python/receive.py +++ b/python/receive.py @@ -1,19 +1,37 @@ #!/usr/bin/env python +import os +import sys + import pika -connection = pika.BlockingConnection(pika.ConnectionParameters( - host='localhost')) -channel = connection.channel() +def main(): + connection = pika.BlockingConnection( + pika.ConnectionParameters(host="localhost"), + ) + channel = connection.channel() + + channel.queue_declare(queue="hello", durable=True, arguments={"x-queue-type": "quorum"}) + + def callback(ch, method, properties, body): + print(f" [x] Received {body.decode()}") -channel.queue_declare(queue='hello') + channel.basic_consume( + queue="hello", + on_message_callback=callback, + auto_ack=True, + ) -def callback(ch, method, properties, body): - print(" [x] Received %r" % body) + print(" [*] Waiting for messages. To exit press CTRL+C") + channel.start_consuming() -channel.basic_consume(callback, - queue='hello', - no_ack=True) -print(' [*] Waiting for messages. To exit press CTRL+C') -channel.start_consuming() +if __name__ == "__main__": + try: + main() + except KeyboardInterrupt: + print("Interrupted") + try: + sys.exit(0) + except SystemExit: + os._exit(0) diff --git a/python/receive_logs.py b/python/receive_logs.py index 41808021..c48f3aac 100755 --- a/python/receive_logs.py +++ b/python/receive_logs.py @@ -1,26 +1,42 @@ #!/usr/bin/env python +import os +import sys + import pika -connection = pika.BlockingConnection(pika.ConnectionParameters( - host='localhost')) -channel = connection.channel() -channel.exchange_declare(exchange='logs', - exchange_type='fanout') +def main(): + connection = pika.BlockingConnection( + pika.ConnectionParameters(host="localhost"), + ) + channel = connection.channel() + + channel.exchange_declare(exchange="logs", exchange_type="fanout") + + result = channel.queue_declare(queue="", exclusive=True) + queue_name = result.method.queue -result = channel.queue_declare(exclusive=True) -queue_name = result.method.queue + channel.queue_bind(exchange="logs", queue=queue_name) -channel.queue_bind(exchange='logs', - queue=queue_name) + def callback(ch, method, properties, body): + print(f" [x] {body.decode()}") -print(' [*] Waiting for logs. To exit press CTRL+C') + print(" [*] Waiting for logs. To exit press CTRL+C") + channel.basic_consume( + queue=queue_name, + on_message_callback=callback, + auto_ack=True, + ) -def callback(ch, method, properties, body): - print(" [x] %r" % body) + channel.start_consuming() -channel.basic_consume(callback, - queue=queue_name, - no_ack=True) -channel.start_consuming() +if __name__ == "__main__": + try: + main() + except KeyboardInterrupt: + print("Interrupted") + try: + sys.exit(0) + except SystemExit: + os._exit(0) diff --git a/python/receive_logs_direct.py b/python/receive_logs_direct.py index cec68d4b..fbbce12f 100755 --- a/python/receive_logs_direct.py +++ b/python/receive_logs_direct.py @@ -1,34 +1,53 @@ #!/usr/bin/env python -import pika +import os import sys -connection = pika.BlockingConnection(pika.ConnectionParameters( - host='localhost')) -channel = connection.channel() +import pika + + +def main(): + connection = pika.BlockingConnection( + pika.ConnectionParameters(host="localhost"), + ) + channel = connection.channel() + + channel.exchange_declare(exchange="direct_logs", exchange_type="direct") + + result = channel.queue_declare(queue="", exclusive=True) + queue_name = result.method.queue -channel.exchange_declare(exchange='direct_logs', - exchange_type='direct') + severities = sys.argv[1:] + if not severities: + sys.stderr.write("Usage: %s [info] [warning] [error]\n" % sys.argv[0]) + sys.exit(1) -result = channel.queue_declare(exclusive=True) -queue_name = result.method.queue + for severity in severities: + channel.queue_bind( + exchange="direct_logs", + queue=queue_name, + routing_key=severity, + ) -severities = sys.argv[1:] -if not severities: - sys.stderr.write("Usage: %s [info] [warning] [error]\n" % sys.argv[0]) - sys.exit(1) + print(" [*] Waiting for logs. To exit press CTRL+C") -for severity in severities: - channel.queue_bind(exchange='direct_logs', - queue=queue_name, - routing_key=severity) + def callback(ch, method, properties, body): + print(f" [x] {method.routing_key}:{body.decode()}") -print(' [*] Waiting for logs. To exit press CTRL+C') + channel.basic_consume( + queue=queue_name, + on_message_callback=callback, + auto_ack=True, + ) -def callback(ch, method, properties, body): - print(" [x] %r:%r" % (method.routing_key, body)) + channel.start_consuming() -channel.basic_consume(callback, - queue=queue_name, - no_ack=True) -channel.start_consuming() +if __name__ == "__main__": + try: + main() + except KeyboardInterrupt: + print("Interrupted") + try: + sys.exit(0) + except SystemExit: + os._exit(0) diff --git a/python/receive_logs_topic.py b/python/receive_logs_topic.py index 05abcefe..bf5b3b4f 100755 --- a/python/receive_logs_topic.py +++ b/python/receive_logs_topic.py @@ -1,34 +1,53 @@ #!/usr/bin/env python -import pika +import os import sys -connection = pika.BlockingConnection(pika.ConnectionParameters( - host='localhost')) -channel = connection.channel() +import pika + + +def main(): + connection = pika.BlockingConnection( + pika.ConnectionParameters(host="localhost"), + ) + channel = connection.channel() + + channel.exchange_declare(exchange="topic_logs", exchange_type="topic") + + result = channel.queue_declare(queue="", exclusive=True) + queue_name = result.method.queue -channel.exchange_declare(exchange='topic_logs', - exchange_type='topic') + binding_keys = sys.argv[1:] + if not binding_keys: + sys.stderr.write("Usage: %s [binding_key]...\n" % sys.argv[0]) + sys.exit(1) -result = channel.queue_declare(exclusive=True) -queue_name = result.method.queue + for binding_key in binding_keys: + channel.queue_bind( + exchange="topic_logs", + queue=queue_name, + routing_key=binding_key, + ) -binding_keys = sys.argv[1:] -if not binding_keys: - sys.stderr.write("Usage: %s [binding_key]...\n" % sys.argv[0]) - sys.exit(1) + print(" [*] Waiting for logs. To exit press CTRL+C") -for binding_key in binding_keys: - channel.queue_bind(exchange='topic_logs', - queue=queue_name, - routing_key=binding_key) + def callback(ch, method, properties, body): + print(f" [x] {method.routing_key}:{body.decode()}") -print(' [*] Waiting for logs. To exit press CTRL+C') + channel.basic_consume( + queue=queue_name, + on_message_callback=callback, + auto_ack=True, + ) -def callback(ch, method, properties, body): - print(" [x] %r:%r" % (method.routing_key, body)) + channel.start_consuming() -channel.basic_consume(callback, - queue=queue_name, - no_ack=True) -channel.start_consuming() +if __name__ == "__main__": + try: + main() + except KeyboardInterrupt: + print("Interrupted") + try: + sys.exit(0) + except SystemExit: + os._exit(0) diff --git a/python/rpc_client.py b/python/rpc_client.py index bf8e1e75..2ccc2469 100755 --- a/python/rpc_client.py +++ b/python/rpc_client.py @@ -1,19 +1,28 @@ #!/usr/bin/env python -import pika import uuid +import pika + + class FibonacciRpcClient(object): def __init__(self): - self.connection = pika.BlockingConnection(pika.ConnectionParameters( - host='localhost')) + self.connection = pika.BlockingConnection( + pika.ConnectionParameters(host="localhost"), + ) self.channel = self.connection.channel() - result = self.channel.queue_declare(exclusive=True) + result = self.channel.queue_declare(queue="", exclusive=True) self.callback_queue = result.method.queue - self.channel.basic_consume(self.on_response, no_ack=True, - queue=self.callback_queue) + self.channel.basic_consume( + queue=self.callback_queue, + on_message_callback=self.on_response, + auto_ack=True, + ) + + self.response = None + self.corr_id = None def on_response(self, ch, method, props, body): if self.corr_id == props.correlation_id: @@ -22,19 +31,21 @@ def on_response(self, ch, method, props, body): def call(self, n): self.response = None self.corr_id = str(uuid.uuid4()) - self.channel.basic_publish(exchange='', - routing_key='rpc_queue', - properties=pika.BasicProperties( - reply_to = self.callback_queue, - correlation_id = self.corr_id, - ), - body=str(n)) - while self.response is None: - self.connection.process_data_events() + self.channel.basic_publish( + exchange="", + routing_key="rpc_queue", + properties=pika.BasicProperties( + reply_to=self.callback_queue, + correlation_id=self.corr_id, + ), + body=str(n), + ) + self.connection.process_data_events(time_limit=None) return int(self.response) + fibonacci_rpc = FibonacciRpcClient() print(" [x] Requesting fib(30)") response = fibonacci_rpc.call(30) -print(" [.] Got %r" % response) +print(f" [.] Got {response}") diff --git a/python/rpc_server.py b/python/rpc_server.py index bff5ab91..4b950501 100755 --- a/python/rpc_server.py +++ b/python/rpc_server.py @@ -1,12 +1,13 @@ #!/usr/bin/env python import pika -connection = pika.BlockingConnection(pika.ConnectionParameters( - host='localhost')) - +connection = pika.BlockingConnection( + pika.ConnectionParameters(host="localhost"), +) channel = connection.channel() -channel.queue_declare(queue='rpc_queue') +channel.queue_declare(queue="rpc_queue", durable=True, arguments={"x-queue-type": "quorum"}) + def fib(n): if n == 0: @@ -14,23 +15,26 @@ def fib(n): elif n == 1: return 1 else: - return fib(n-1) + fib(n-2) + return fib(n - 1) + fib(n - 2) + def on_request(ch, method, props, body): n = int(body) - print(" [.] fib(%s)" % n) + print(f" [.] fib({n})") response = fib(n) - ch.basic_publish(exchange='', - routing_key=props.reply_to, - properties=pika.BasicProperties(correlation_id = \ - props.correlation_id), - body=str(response)) - ch.basic_ack(delivery_tag = method.delivery_tag) + ch.basic_publish( + exchange="", + routing_key=props.reply_to, + properties=pika.BasicProperties(correlation_id=props.correlation_id), + body=str(response), + ) + ch.basic_ack(delivery_tag=method.delivery_tag) + channel.basic_qos(prefetch_count=1) -channel.basic_consume(on_request, queue='rpc_queue') +channel.basic_consume(queue="rpc_queue", on_message_callback=on_request) print(" [x] Awaiting RPC requests") channel.start_consuming() diff --git a/python/send.py b/python/send.py index 24332406..720151ec 100755 --- a/python/send.py +++ b/python/send.py @@ -1,15 +1,14 @@ #!/usr/bin/env python import pika -connection = pika.BlockingConnection(pika.ConnectionParameters( - host='localhost')) +connection = pika.BlockingConnection( + pika.ConnectionParameters(host="localhost"), +) channel = connection.channel() +channel.queue_declare(queue="hello", durable=True, arguments={"x-queue-type": "quorum"}) -channel.queue_declare(queue='hello') - -channel.basic_publish(exchange='', - routing_key='hello', - body='Hello World!') +channel.basic_publish(exchange="", routing_key="hello", body="Hello World!") print(" [x] Sent 'Hello World!'") + connection.close() diff --git a/python/worker.py b/python/worker.py index 5b26e214..a6729671 100755 --- a/python/worker.py +++ b/python/worker.py @@ -1,22 +1,25 @@ #!/usr/bin/env python -import pika import time -connection = pika.BlockingConnection(pika.ConnectionParameters( - host='localhost')) +import pika + +connection = pika.BlockingConnection( + pika.ConnectionParameters(host="localhost"), +) channel = connection.channel() -channel.queue_declare(queue='task_queue', durable=True) -print(' [*] Waiting for messages. To exit press CTRL+C') +channel.queue_declare(queue="task_queue", durable=True, arguments={"x-queue-type": "quorum"}) +print(" [*] Waiting for messages. To exit press CTRL+C") + def callback(ch, method, properties, body): - print(" [x] Received %r" % body) - time.sleep(body.count(b'.')) + print(f" [x] Received {body.decode()}") + time.sleep(body.count(b".")) print(" [x] Done") - ch.basic_ack(delivery_tag = method.delivery_tag) + ch.basic_ack(delivery_tag=method.delivery_tag) + channel.basic_qos(prefetch_count=1) -channel.basic_consume(callback, - queue='task_queue') +channel.basic_consume(queue="task_queue", on_message_callback=callback) channel.start_consuming() diff --git a/ruby/Gemfile b/ruby/Gemfile new file mode 100644 index 00000000..f601f176 --- /dev/null +++ b/ruby/Gemfile @@ -0,0 +1,5 @@ +source 'https://rubygems.org' + +ruby ">= 3.1.0" + +gem "bunny", "~> 3.1.0" diff --git a/ruby/Gemfile.lock b/ruby/Gemfile.lock new file mode 100644 index 00000000..4f29abb2 --- /dev/null +++ b/ruby/Gemfile.lock @@ -0,0 +1,24 @@ +GEM + remote: https://rubygems.org/ + specs: + amq-protocol (2.7.0) + bunny (3.1.0) + amq-protocol (~> 2.7) + logger (~> 1, >= 1.7) + sorted_set (~> 1, >= 1.0.2) + logger (1.7.0) + rbtree (0.4.6) + sorted_set (1.1.0) + rbtree + +PLATFORMS + ruby + +DEPENDENCIES + bunny (~> 3.1.0) + +RUBY VERSION + ruby 3.1.0 + +BUNDLED WITH + 2.5.23 diff --git a/ruby/README.md b/ruby/README.md index 638cdfaa..1f2bae8c 100644 --- a/ruby/README.md +++ b/ruby/README.md @@ -1,48 +1,67 @@ # Ruby code for RabbitMQ tutorials Here you can find Ruby code examples from -[RabbitMQ tutorials](http://www.rabbitmq.com/getstarted.html). +[RabbitMQ tutorials](https://www.rabbitmq.com/getstarted.html). ## Requirements -These tutorials require [Bunny](http://rubybunny.info) to be installed. +These tutorials require Ruby 3.1.0 or later, [Bundler](https://bundler.io/) and [Bunny](http://rubybunny.info) to be installed. -To install it with RubyGems: +To install Bunny with Bundler, do - gem install bunny --version ">= 2.10.0" +``` sh +bundle install +``` -Bunny supports Ruby 2.2 and later versions. ## Code -[Tutorial one: "Hello World!"](http://www.rabbitmq.com/tutorial-one-ruby.html): +To run [tutorial one: "Hello World!"](https://www.rabbitmq.com/tutorials/tutorial-one-ruby.html): - ruby send.rb - ruby receive.rb +``` sh +bundle exec ruby send.rb +bundle exec ruby receive.rb +``` -[Tutorial two: Work Queues](http://www.rabbitmq.com/tutorial-two-ruby.html): +[Tutorial two: Work Queues](https://www.rabbitmq.com/tutorials/tutorial-two-ruby.html): - ruby new_task.rb - ruby worker.rb +``` sh +bundle exec ruby new_task.rb +bundle exec ruby worker.rb +``` -[Tutorial three: Publish/Subscribe](http://www.rabbitmq.com/tutorial-three-ruby.html) +[Tutorial three: Publish/Subscribe](https://www.rabbitmq.com/tutorials/tutorial-three-ruby.html) - ruby receive_logs.rb - ruby emit_log.rb +``` sh +bundle exec ruby receive_logs.rb +bundle exec ruby emit_log.rb +``` -[Tutorial four: Routing](http://www.rabbitmq.com/tutorial-four-ruby.html) +[Tutorial four: Routing](https://www.rabbitmq.com/tutorials/tutorial-four-ruby.html) - ruby receive_logs_direct.rb - ruby emit_log_direct.rb +``` sh +bundle exec ruby receive_logs_direct.rb +bundle exec ruby emit_log_direct.rb +``` -[Tutorial five: Topics](http://www.rabbitmq.com/tutorial-five-ruby.html) +[Tutorial five: Topics](https://www.rabbitmq.com/tutorials/tutorial-five-ruby.html) - ruby receive_logs_topic.rb - ruby emit_log_topic.rb +``` sh +bundle exec ruby receive_logs_topic.rb +bundle exec ruby emit_log_topic.rb +``` -[Tutorial six: RPC](http://www.rabbitmq.com/tutorial-six-ruby.html) +[Tutorial six: RPC](https://www.rabbitmq.com/tutorials/tutorial-six-ruby.html) - ruby rpc_server.rb - ruby rpc_client.rb +``` sh +bundle exec ruby rpc_server.rb +bundle exec ruby rpc_client.rb +``` -To learn more, visit [Bunny documentation](http://rubybunny.info) site. +[Tutorial seven: Publisher Confirms](https://www.rabbitmq.com/tutorials/tutorial-seven-ruby.html) + +``` sh +bundle exec ruby publisher_confirms.rb +``` + +To learn more, see [Bunny documentation](http://rubybunny.info). diff --git a/ruby/new_task.rb b/ruby/new_task.rb index 743a35d5..7c3a3467 100644 --- a/ruby/new_task.rb +++ b/ruby/new_task.rb @@ -5,7 +5,7 @@ connection.start channel = connection.create_channel -queue = channel.queue('task_queue', durable: true) +queue = channel.quorum_queue('task_queue') message = ARGV.empty? ? 'Hello World!' : ARGV.join(' ') diff --git a/ruby/publisher_confirms.rb b/ruby/publisher_confirms.rb new file mode 100644 index 00000000..b1211e1f --- /dev/null +++ b/ruby/publisher_confirms.rb @@ -0,0 +1,101 @@ +#!/usr/bin/env ruby +require 'bunny' + +MESSAGE_COUNT = 50_000 + +def publish_messages_individually(connection) + channel = connection.create_channel + queue = channel.queue('', exclusive: true) + + channel.confirm_select(tracking: true) + + start_time = Time.now + MESSAGE_COUNT.times do |i| + body = i.to_s + channel.basic_publish(body, '', queue.name) + channel.wait_for_confirms + end + + end_time = Time.now + + puts "Published #{MESSAGE_COUNT} messages individually in #{((end_time - start_time) * 1000).to_i} ms" +ensure + channel.close if channel && channel.open? +end + +def publish_messages_in_batch(connection) + channel = connection.create_channel + queue = channel.queue('', exclusive: true) + + channel.confirm_select(tracking: true) + + batch_size = 1000 + start_time = Time.now + + (0...MESSAGE_COUNT).each_slice(batch_size) do |batch| + messages = batch.map { |i| i.to_s } + channel.basic_publish_batch(messages, '', queue.name) + end + + # Wait for any remaining confirmations + channel.wait_for_confirms + end_time = Time.now + + puts "Published #{MESSAGE_COUNT} messages in batch in #{((end_time - start_time) * 1000).to_i} ms" +ensure + channel.close if channel && channel.open? +end + +def handle_publish_confirms_asynchronously(connection) + channel = connection.create_channel + queue = channel.queue('', exclusive: true) + + outstanding_confirms = {} + # A mutex is necessary because the confirm callbacks are executed in a separate thread + confirms_mutex = Mutex.new + + channel.confirm_select do |delivery_tag, multiple, nack| + confirms_mutex.synchronize do + if multiple + outstanding_confirms.reject! { |k, _| k <= delivery_tag } + else + outstanding_confirms.delete(delivery_tag) + end + end + if nack + puts "Message with delivery tag #{delivery_tag} was nacked!" + end + end + + start_time = Time.now + MESSAGE_COUNT.times do |i| + body = i.to_s + seq_no = channel.next_publish_seq_no + confirms_mutex.synchronize do + outstanding_confirms[seq_no] = body + end + channel.basic_publish(body, '', queue.name) + end + + # Wait for any remaining confirmations + channel.wait_for_confirms + end_time = Time.now + + puts "Published #{MESSAGE_COUNT} messages and handled confirms asynchronously in #{((end_time - start_time) * 1000).to_i} ms" +ensure + channel.close if channel && channel.open? +end + +begin + connection = Bunny.new + connection.start + + publish_messages_individually(connection) + publish_messages_in_batch(connection) + handle_publish_confirms_asynchronously(connection) +rescue Interrupt => _ + connection.close + exit(0) +ensure + connection.close if connection +end diff --git a/ruby/receive.rb b/ruby/receive.rb index 684c2c2b..8992af89 100644 --- a/ruby/receive.rb +++ b/ruby/receive.rb @@ -5,10 +5,12 @@ connection.start channel = connection.create_channel -queue = channel.queue('hello') +queue = channel.quorum_queue('hello') begin puts ' [*] Waiting for messages. To exit press CTRL+C' + # block: true is only used to keep the main thread + # alive. Please avoid using it in real world applications. queue.subscribe(block: true) do |_delivery_info, _properties, body| puts " [x] Received #{body}" end diff --git a/ruby/receive_logs.rb b/ruby/receive_logs.rb index 4290020e..34062704 100644 --- a/ruby/receive_logs.rb +++ b/ruby/receive_logs.rb @@ -14,6 +14,8 @@ puts ' [*] Waiting for logs. To exit press CTRL+C' begin + # block: true is only used to keep the main thread + # alive. Please avoid using it in real world applications. queue.subscribe(block: true) do |_delivery_info, _properties, body| puts " [x] #{body}" end diff --git a/ruby/receive_logs_direct.rb b/ruby/receive_logs_direct.rb index da2ebbce..913b5fbf 100644 --- a/ruby/receive_logs_direct.rb +++ b/ruby/receive_logs_direct.rb @@ -17,6 +17,8 @@ puts ' [*] Waiting for logs. To exit press CTRL+C' begin + # block: true is only used to keep the main thread + # alive. Please avoid using it in real world applications. queue.subscribe(block: true) do |delivery_info, _properties, body| puts " [x] #{delivery_info.routing_key}:#{body}" end diff --git a/ruby/receive_logs_topic.rb b/ruby/receive_logs_topic.rb index 1028624d..ea8496a8 100644 --- a/ruby/receive_logs_topic.rb +++ b/ruby/receive_logs_topic.rb @@ -17,6 +17,8 @@ puts ' [*] Waiting for logs. To exit press CTRL+C' begin + # block: true is only used to keep the main thread + # alive. Please avoid using it in real world applications. queue.subscribe(block: true) do |delivery_info, _properties, body| puts " [x] #{delivery_info.routing_key}:#{body}" end diff --git a/ruby/rpc_client.rb b/ruby/rpc_client.rb index 9c6cd499..6a236946 100644 --- a/ruby/rpc_client.rb +++ b/ruby/rpc_client.rb @@ -62,8 +62,10 @@ def generate_uuid client = FibonacciClient.new('rpc_queue') -puts ' [x] Requesting fib(30)' -response = client.call(30) +n = (ARGV[0] || 30).to_i + +puts " [x] Requesting fib(#{n})" +response = client.call(n) puts " [.] Got #{response}" diff --git a/ruby/rpc_server.rb b/ruby/rpc_server.rb index 0f957084..d767b302 100644 --- a/ruby/rpc_server.rb +++ b/ruby/rpc_server.rb @@ -9,7 +9,7 @@ def initialize end def start(queue_name) - @queue = channel.queue(queue_name) + @queue = channel.quorum_queue(queue_name) @exchange = channel.default_exchange subscribe_to_queue end @@ -19,12 +19,18 @@ def stop connection.close end + def loop_forever + # This loop only exists to keep the main thread + # alive. Many real world apps won't need this. + loop { sleep 5 } + end + private attr_reader :channel, :exchange, :queue, :connection def subscribe_to_queue - queue.subscribe(block: true) do |_delivery_info, properties, payload| + queue.subscribe do |_delivery_info, properties, payload| result = fibonacci(payload.to_i) exchange.publish( @@ -47,6 +53,7 @@ def fibonacci(value) puts ' [x] Awaiting RPC requests' server.start('rpc_queue') + server.loop_forever rescue Interrupt => _ server.stop end diff --git a/ruby/send.rb b/ruby/send.rb index c20872f3..20552b5b 100644 --- a/ruby/send.rb +++ b/ruby/send.rb @@ -5,7 +5,7 @@ connection.start channel = connection.create_channel -queue = channel.queue('hello') +queue = channel.quorum_queue('hello') channel.default_exchange.publish('Hello World!', routing_key: queue.name) puts " [x] Sent 'Hello World!'" diff --git a/ruby/worker.rb b/ruby/worker.rb index fc3748f7..08812c95 100644 --- a/ruby/worker.rb +++ b/ruby/worker.rb @@ -5,12 +5,14 @@ connection.start channel = connection.create_channel -queue = channel.queue('task_queue', durable: true) +queue = channel.quorum_queue('task_queue') channel.prefetch(1) puts ' [*] Waiting for messages. To exit press CTRL+C' begin + # block: true is only used to keep the main thread + # alive. Please avoid using it in real world applications. queue.subscribe(manual_ack: true, block: true) do |delivery_info, _properties, body| puts " [x] Received '#{body}'" # imitate some work diff --git a/rust-amqprs/Cargo.lock b/rust-amqprs/Cargo.lock new file mode 100644 index 00000000..af2e5fd7 --- /dev/null +++ b/rust-amqprs/Cargo.lock @@ -0,0 +1,404 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "addr2line" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" + +[[package]] +name = "amqp_serde" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19659fab053d9b2cd4fa17649609a3241b181fcd14189931ea5c0976502fe381" +dependencies = [ + "bytes", + "serde", +] + +[[package]] +name = "amqprs" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b16f62ef427e90df82ba53de2bd2c9fdae10640b8b78538d8c63ac2bdf13dd80" +dependencies = [ + "amqp_serde", + "async-trait", + "bytes", + "serde", + "tokio", +] + +[[package]] +name = "async-trait" +version = "0.1.68" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9ccdd8f2a161be9bd5c023df56f1b2a0bd1d83872ae53b71a84a12c9bf6e842" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "backtrace" +version = "0.3.74" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" +dependencies = [ + "addr2line", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", + "windows-targets 0.52.6", +] + +[[package]] +name = "bytes" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "gimli" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" + +[[package]] +name = "hermit-abi" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7" +dependencies = [ + "libc", +] + +[[package]] +name = "libc" +version = "0.2.171" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c19937216e9d3aa9956d9bb8dfc0b0c8beb6058fc4f7a4dc4d850edf86a237d6" + +[[package]] +name = "memchr" +version = "2.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" + +[[package]] +name = "miniz_oxide" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff70ce3e48ae43fa075863cef62e8b43b71a4f2382229920e0df362592919430" +dependencies = [ + "adler2", +] + +[[package]] +name = "mio" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" +dependencies = [ + "libc", + "wasi", + "windows-sys 0.48.0", +] + +[[package]] +name = "num_cpus" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] +name = "object" +version = "0.36.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" +dependencies = [ + "memchr", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" + +[[package]] +name = "proc-macro2" +version = "1.0.94" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a31971752e70b8b2686d7e46ec17fb38dad4051d94024c88df49b667caea9c84" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f4f29d145265ec1c483c7c654450edde0bfe043d3938d6972630663356d9500" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rabbitmq-tutorials" +version = "1.0.0" +dependencies = [ + "amqp_serde", + "amqprs", + "tokio", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" + +[[package]] +name = "serde" +version = "1.0.163" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2113ab51b87a539ae008b5c6c02dc020ffa39afd2d83cffcb3f4eb2722cebec2" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.163" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c805777e3930c8883389c602315a24224bcc738b63905ef87cd1420353ea93e" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "socket2" +version = "0.5.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f5fd57c80058a56cf5c777ab8a126398ece8e442983605d280a44ce79d0edef" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "syn" +version = "2.0.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6f671d4b5ffdb8eadec19c0ae67fe2639df8684bd7bc4b83d986b8db549cf01" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "tokio" +version = "1.38.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68722da18b0fc4a05fdc1120b302b82051265792a1e1b399086e9b204b10ad3d" +dependencies = [ + "backtrace", + "bytes", + "libc", + "mio", + "num_cpus", + "pin-project-lite", + "socket2", + "tokio-macros", + "windows-sys 0.48.0", +] + +[[package]] +name = "tokio-macros" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f5ae998a069d4b5aba8ee9dad856af7d520c3699e6159b185c2acd48155d39a" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "unicode-ident" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4" + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.0", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b1eb6f0cd7c80c79759c929114ef071b87354ce476d9d94271031c0497adfd5" +dependencies = [ + "windows_aarch64_gnullvm 0.48.0", + "windows_aarch64_msvc 0.48.0", + "windows_i686_gnu 0.48.0", + "windows_i686_msvc 0.48.0", + "windows_x86_64_gnu 0.48.0", + "windows_x86_64_gnullvm 0.48.0", + "windows_x86_64_msvc 0.48.0", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" diff --git a/rust-amqprs/Cargo.toml b/rust-amqprs/Cargo.toml new file mode 100644 index 00000000..71c1c437 --- /dev/null +++ b/rust-amqprs/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "rabbitmq-tutorials" +version = "1.0.0" +authors = ["Michael Klishin "] +edition = "2021" + +[dependencies] +amqprs = "1.2" +amqp_serde = "0.4" +tokio = "1" \ No newline at end of file diff --git a/rust-amqprs/README.md b/rust-amqprs/README.md new file mode 100644 index 00000000..be51ff21 --- /dev/null +++ b/rust-amqprs/README.md @@ -0,0 +1,40 @@ +# Rust code for RabbitMQ tutorials (using amqprs) + +Here you can find the Rust code examples for [RabbitMQ +tutorials](https://www.rabbitmq.com/getstarted.html). + +The examples use [amqprs](https://github.com/gftea/amqprs) client library. + +These tutorials assume a RabbitMQ server node running locally using default ports. + +## Requirements + +* [Rust and Cargo](https://www.rust-lang.org/tools/install) + +## Code +Each cargo command should be launched in a separate shell. + +#### [Tutorial one: "Hello World!"](https://www.rabbitmq.com/tutorials/tutorial-one-python.html) + + cargo run --bin receive + cargo run --bin send + +#### [Tutorial two: Work Queues](https://www.rabbitmq.com/tutorials/tutorial-two-python.html) + + cargo run --bin worker + cargo run --bin new_task "hi" # specify a custom message + +#### [Tutorial three: Publish/Subscribe](https://www.rabbitmq.com/tutorials/tutorial-three-python.html) + + cargo run --bin receive_logs + cargo run --bin emit_log "hi" # specify a custom message + +#### [Tutorial four: Routing](https://www.rabbitmq.com/tutorials/tutorial-four-python.html) + + cargo run --bin receive_logs_direct info error # specify log levels + cargo run --bin emit_log_direct error "help!" # specify severity and custom message + +#### [Tutorial five: Topics](https://www.rabbitmq.com/tutorials/tutorial-five-python.html) + + cargo run --bin receive_logs_topic kern.* # specify topic filter + cargo run --bin emit_log_topic kern.mem "No memory left!" # specify topic and message diff --git a/rust-amqprs/src/bin/emit_log.rs b/rust-amqprs/src/bin/emit_log.rs new file mode 100644 index 00000000..36ac2564 --- /dev/null +++ b/rust-amqprs/src/bin/emit_log.rs @@ -0,0 +1,42 @@ +use amqprs::{ + connection::{Connection, OpenConnectionArguments}, + callbacks::{DefaultConnectionCallback, DefaultChannelCallback}, + channel::{ExchangeDeclareArguments, BasicPublishArguments}, BasicProperties +}; +use tokio::{io::Error as TError}; + +#[tokio::main] +async fn main() -> Result<(), Box> { + let conn = Connection::open(&OpenConnectionArguments::new( + "localhost", + 5672, + "guest", + "guest")).await.unwrap(); + conn.register_callback(DefaultConnectionCallback).await.unwrap(); + + let ch = conn.open_channel(None).await.unwrap(); + ch.register_callback(DefaultChannelCallback).await.unwrap(); + + let x_name = "logs"; + let x_type = "fanout"; + let x_args = ExchangeDeclareArguments::new(x_name, x_type).finish(); + ch.exchange_declare(x_args).await.unwrap(); + + let args: Vec<_> = std::env::args().skip(1).collect(); + let payload = match args.len() { + 0 => "hello".to_string(), + _ => args.join(" ").to_string(), + }; + + let publish_args = BasicPublishArguments::new(x_name, ""); + // publish messages as persistent + let props = BasicProperties::default().with_delivery_mode(2).finish(); + ch.basic_publish(props, payload.clone().into_bytes(), publish_args).await.unwrap(); + + println!(" [x] Sent {:?}", payload); + + ch.close().await.unwrap(); + conn.close().await.unwrap(); + + Ok(()) +} \ No newline at end of file diff --git a/rust-amqprs/src/bin/emit_log_direct.rs b/rust-amqprs/src/bin/emit_log_direct.rs new file mode 100644 index 00000000..88b5a108 --- /dev/null +++ b/rust-amqprs/src/bin/emit_log_direct.rs @@ -0,0 +1,48 @@ +use amqprs::{ + connection::{Connection, OpenConnectionArguments}, + callbacks::{DefaultConnectionCallback, DefaultChannelCallback}, + channel::{ExchangeDeclareArguments, BasicPublishArguments}, BasicProperties +}; +use tokio::{io::Error as TError}; +use std::str; + +#[tokio::main] +async fn main() -> Result<(), Box> { + let conn = Connection::open(&OpenConnectionArguments::new( + "localhost", + 5672, + "guest", + "guest")).await.unwrap(); + conn.register_callback(DefaultConnectionCallback).await.unwrap(); + + let ch = conn.open_channel(None).await.unwrap(); + ch.register_callback(DefaultChannelCallback).await.unwrap(); + + let x_name = "direct_logs"; + let x_type = "direct"; + let x_args = ExchangeDeclareArguments::new(x_name, x_type).finish(); + ch.exchange_declare(x_args).await.unwrap(); + + let args: Vec<_> = std::env::args().skip(1).collect(); + let routing_key = args.first().map(String::as_str).unwrap_or("anonymous.info"); + let payload = match args.len() { + x if x < 2 => "Hello, world!".to_string(), + _ => args[1..].join(" ").to_string(), + }; + + let publish_args = BasicPublishArguments::new(x_name, routing_key); + // publish messages as persistent + let props = BasicProperties::default().with_delivery_mode(2).finish(); + ch.basic_publish(props, payload.clone().into_bytes(), publish_args).await.unwrap(); + + println!( + " [x] Sent {}:{:?}", + routing_key, + str::from_utf8(payload.as_bytes()).unwrap() + ); + + ch.close().await.unwrap(); + conn.close().await.unwrap(); + + Ok(()) +} \ No newline at end of file diff --git a/rust-amqprs/src/bin/emit_log_topic.rs b/rust-amqprs/src/bin/emit_log_topic.rs new file mode 100644 index 00000000..01534779 --- /dev/null +++ b/rust-amqprs/src/bin/emit_log_topic.rs @@ -0,0 +1,43 @@ +use amqprs::{ + connection::{Connection, OpenConnectionArguments}, + callbacks::{DefaultConnectionCallback, DefaultChannelCallback}, + channel::{ExchangeDeclareArguments, BasicPublishArguments}, BasicProperties +}; +use tokio::{io::Error as TError}; + +#[tokio::main] +async fn main() -> Result<(), Box> { + let conn = Connection::open(&OpenConnectionArguments::new( + "localhost", + 5672, + "guest", + "guest")).await.unwrap(); + conn.register_callback(DefaultConnectionCallback).await.unwrap(); + + let ch = conn.open_channel(None).await.unwrap(); + ch.register_callback(DefaultChannelCallback).await.unwrap(); + + let x_name = "topic_logs"; + let x_type = "topic"; + let x_args = ExchangeDeclareArguments::new(x_name, x_type).finish(); + ch.exchange_declare(x_args).await.unwrap(); + + let args: Vec<_> = std::env::args().skip(1).collect(); + let routing_key = args.first().map(String::as_str).unwrap_or("anonymous.info"); + let payload = match args.len() { + x if x < 2 => "Hello, world!".to_string(), + _ => args[1..].join(" ").to_string(), + }; + + let publish_args = BasicPublishArguments::new(x_name, routing_key); + // publish messages as persistent + let props = BasicProperties::default().with_delivery_mode(2).finish(); + ch.basic_publish(props, payload.clone().into_bytes(), publish_args).await.unwrap(); + + println!(" [x] Sent {:?}", payload); + + ch.close().await.unwrap(); + conn.close().await.unwrap(); + + Ok(()) +} \ No newline at end of file diff --git a/rust-amqprs/src/bin/new_task.rs b/rust-amqprs/src/bin/new_task.rs new file mode 100644 index 00000000..ce62c65e --- /dev/null +++ b/rust-amqprs/src/bin/new_task.rs @@ -0,0 +1,48 @@ +use amqprs::{ + connection::{Connection, OpenConnectionArguments}, + callbacks::{DefaultConnectionCallback, DefaultChannelCallback}, + channel::{QueueDeclareArguments, BasicPublishArguments}, BasicProperties, + FieldTable, +}; +use amqp_serde::types::{FieldValue, ShortStr}; +use tokio::{io::Error as TError}; + +#[tokio::main] +async fn main() -> Result<(), Box> { + let conn = Connection::open(&OpenConnectionArguments::new( + "localhost", + 5672, + "guest", + "guest")).await.unwrap(); + conn.register_callback(DefaultConnectionCallback).await.unwrap(); + + let ch = conn.open_channel(None).await.unwrap(); + ch.register_callback(DefaultChannelCallback).await.unwrap(); + + let q_name = "task_queue"; + let mut args = FieldTable::new(); + args.insert( + ShortStr::try_from("x-queue-type").unwrap(), + FieldValue::S("quorum".try_into().unwrap()), + ); + let q_args = QueueDeclareArguments::new(q_name).durable(true).arguments(args).finish(); + let (_, _, _) = ch.queue_declare(q_args).await.unwrap().unwrap(); + + let args: Vec<_> = std::env::args().skip(1).collect(); + let payload = match args.len() { + 0 => "hello".to_string(), + _ => args.join(" ").to_string(), + }; + + let publish_args = BasicPublishArguments::new("", &q_name); + // publish messages as persistent + let props = BasicProperties::default().with_delivery_mode(2).finish(); + ch.basic_publish(props, payload.clone().into_bytes(), publish_args).await.unwrap(); + + println!(" [x] Sent {:?}", payload); + + ch.close().await.unwrap(); + conn.close().await.unwrap(); + + Ok(()) +} \ No newline at end of file diff --git a/rust-amqprs/src/bin/receive.rs b/rust-amqprs/src/bin/receive.rs new file mode 100644 index 00000000..122ff353 --- /dev/null +++ b/rust-amqprs/src/bin/receive.rs @@ -0,0 +1,54 @@ +use amqprs::{ + connection::{Connection, OpenConnectionArguments}, + callbacks::{DefaultConnectionCallback, DefaultChannelCallback}, channel::{QueueDeclareArguments, BasicConsumeArguments}, + FieldTable, +}; +use amqp_serde::types::{FieldValue, ShortStr}; +use tokio::{self, sync::Notify}; +use tokio::io::Error as TError; +use std::str; + +#[tokio::main] +async fn main() -> Result<(), Box> { + let conn = Connection::open(&OpenConnectionArguments::new( + "localhost", + 5672, + "guest", + "guest", + )) + .await.unwrap(); + conn.register_callback(DefaultConnectionCallback).await.unwrap(); + + let ch = conn.open_channel(None).await.unwrap(); + ch.register_callback(DefaultChannelCallback).await.unwrap(); + + let mut args = FieldTable::new(); + args.insert( + ShortStr::try_from("x-queue-type").unwrap(), + FieldValue::S("quorum".try_into().unwrap()), + ); + let q_args = QueueDeclareArguments::default() + .queue(String::from("hello")) + .durable(true) + .arguments(args) + .finish(); + let (queue_name, _, _) = ch.queue_declare(q_args).await.unwrap().unwrap(); + let consumer_args = BasicConsumeArguments::new(&queue_name, "receive.rs"); + let (_ctag, mut rx) = ch.basic_consume_rx(consumer_args).await.unwrap(); + + tokio::spawn(async move { + while let Some(msg) = rx.recv().await { + if let Some(payload) = msg.content { + println!(" [x] Received {:?}", str::from_utf8(&payload).unwrap()); + } + }; + + }); + + println!(" [*] Waiting for messages. To exit press CTRL+C"); + + let guard = Notify::new(); + guard.notified().await; + + Ok(()) +} diff --git a/rust-amqprs/src/bin/receive_logs.rs b/rust-amqprs/src/bin/receive_logs.rs new file mode 100644 index 00000000..f85e2f71 --- /dev/null +++ b/rust-amqprs/src/bin/receive_logs.rs @@ -0,0 +1,49 @@ +use amqprs::{ + connection::{Connection, OpenConnectionArguments}, + callbacks::{DefaultConnectionCallback, DefaultChannelCallback}, + channel::{QueueDeclareArguments, BasicConsumeArguments, BasicAckArguments, QueueBindArguments, ExchangeDeclareArguments} +}; +use tokio::{io::Error as TError, sync::Notify}; + +#[tokio::main] +async fn main() -> Result<(), Box> { + let conn = Connection::open(&OpenConnectionArguments::new( + "localhost", + 5672, + "guest", + "guest")).await.unwrap(); + conn.register_callback(DefaultConnectionCallback).await.unwrap(); + + let ch = conn.open_channel(None).await.unwrap(); + ch.register_callback(DefaultChannelCallback).await.unwrap(); + + let x_name = "logs"; + let x_type = "fanout"; + let x_args = ExchangeDeclareArguments::new(x_name, x_type).finish(); + ch.exchange_declare(x_args).await.unwrap(); + + let q_args = QueueDeclareArguments::new("").durable(false).exclusive(true).finish(); + let (q_name, _, _) = ch.queue_declare(q_args).await.unwrap().unwrap(); + + ch.queue_bind(QueueBindArguments::new(&q_name, &x_name, "")).await.unwrap(); + + let consumer_args = BasicConsumeArguments::default().queue(String::from(q_name)).finish(); + let (_ctag, mut rx) = ch.basic_consume_rx(consumer_args).await.unwrap(); + + println!(" [*] Waiting for logs. To exit press CTRL+C"); + + tokio::spawn(async move { + while let Some(msg) = rx.recv().await { + if let Some(payload) = msg.content { + println!(" [x] Received {:?}", std::str::from_utf8(&payload).unwrap()); + + ch.basic_ack(BasicAckArguments::new(msg.deliver.unwrap().delivery_tag(), false)).await.unwrap(); + } + } + }); + + let guard = Notify::new(); + guard.notified().await; + + Ok(()) +} \ No newline at end of file diff --git a/rust-amqprs/src/bin/receive_logs_direct.rs b/rust-amqprs/src/bin/receive_logs_direct.rs new file mode 100644 index 00000000..8f04d979 --- /dev/null +++ b/rust-amqprs/src/bin/receive_logs_direct.rs @@ -0,0 +1,60 @@ +use amqprs::{ + connection::{Connection, OpenConnectionArguments}, + callbacks::{DefaultConnectionCallback, DefaultChannelCallback}, + channel::{QueueDeclareArguments, BasicConsumeArguments, BasicAckArguments, QueueBindArguments, ExchangeDeclareArguments} +}; +use tokio::{io::Error as TError, sync::Notify}; + +#[tokio::main] +async fn main() -> Result<(), Box> { + let severities: Vec<_> = std::env::args().skip(1).collect(); + if severities.is_empty() { + eprintln!( + "Usage: {} [info] [warning] [error]\n", + std::env::args().next().unwrap_or_else(|| "receive-direct".into()) + ); + std::process::exit(1); + } + + let conn = Connection::open(&OpenConnectionArguments::new( + "localhost", + 5672, + "guest", + "guest")).await.unwrap(); + conn.register_callback(DefaultConnectionCallback).await.unwrap(); + + let ch = conn.open_channel(None).await.unwrap(); + ch.register_callback(DefaultChannelCallback).await.unwrap(); + + let x_name = "direct_logs"; + let x_type = "direct"; + let x_args = ExchangeDeclareArguments::new(x_name, x_type).finish(); + ch.exchange_declare(x_args).await.unwrap(); + + let q_args = QueueDeclareArguments::new("").durable(false).exclusive(true).finish(); + let (q_name, _, _) = ch.queue_declare(q_args).await.unwrap().unwrap(); + + for bk in severities { + ch.queue_bind(QueueBindArguments::new(&q_name, &x_name, &bk)).await.unwrap(); + } + + let consumer_args = BasicConsumeArguments::default().queue(String::from(q_name)).finish(); + let (_ctag, mut rx) = ch.basic_consume_rx(consumer_args).await.unwrap(); + + println!(" [*] Waiting for logs. To exit press CTRL+C"); + + tokio::spawn(async move { + while let Some(msg) = rx.recv().await { + if let Some(payload) = msg.content { + println!(" [x] Received {:?}", std::str::from_utf8(&payload).unwrap()); + + ch.basic_ack(BasicAckArguments::new(msg.deliver.unwrap().delivery_tag(), false)).await.unwrap(); + } + } + }); + + let guard = Notify::new(); + guard.notified().await; + + Ok(()) +} \ No newline at end of file diff --git a/rust-amqprs/src/bin/receive_logs_topic.rs b/rust-amqprs/src/bin/receive_logs_topic.rs new file mode 100644 index 00000000..5acb991b --- /dev/null +++ b/rust-amqprs/src/bin/receive_logs_topic.rs @@ -0,0 +1,60 @@ +use amqprs::{ + connection::{Connection, OpenConnectionArguments}, + callbacks::{DefaultConnectionCallback, DefaultChannelCallback}, + channel::{QueueDeclareArguments, BasicConsumeArguments, BasicAckArguments, QueueBindArguments, ExchangeDeclareArguments} +}; +use tokio::{io::Error as TError, sync::Notify}; + +#[tokio::main] +async fn main() -> Result<(), Box> { + let binding_keys: Vec<_> = std::env::args().skip(1).collect(); + if binding_keys.is_empty() { + eprintln!( + "Usage: {} [binding_key]...\n", + std::env::args().next().unwrap_or_else(|| "receive-topic".into()) + ); + std::process::exit(1); + } + + let conn = Connection::open(&OpenConnectionArguments::new( + "localhost", + 5672, + "guest", + "guest")).await.unwrap(); + conn.register_callback(DefaultConnectionCallback).await.unwrap(); + + let ch = conn.open_channel(None).await.unwrap(); + ch.register_callback(DefaultChannelCallback).await.unwrap(); + + let x_name = "topic_logs"; + let x_type = "topic"; + let x_args = ExchangeDeclareArguments::new(x_name, x_type).finish(); + ch.exchange_declare(x_args).await.unwrap(); + + let q_args = QueueDeclareArguments::new("").durable(false).exclusive(true).finish(); + let (q_name, _, _) = ch.queue_declare(q_args).await.unwrap().unwrap(); + + for bk in binding_keys { + ch.queue_bind(QueueBindArguments::new(&q_name, &x_name, &bk)).await.unwrap(); + } + + let consumer_args = BasicConsumeArguments::default().queue(String::from(q_name)).finish(); + let (_ctag, mut rx) = ch.basic_consume_rx(consumer_args).await.unwrap(); + + println!(" [*] Waiting for logs. To exit press CTRL+C"); + + tokio::spawn(async move { + while let Some(msg) = rx.recv().await { + if let Some(payload) = msg.content { + println!(" [x] Received {:?}", std::str::from_utf8(&payload).unwrap()); + + ch.basic_ack(BasicAckArguments::new(msg.deliver.unwrap().delivery_tag(), false)).await.unwrap(); + } + } + }); + + let guard = Notify::new(); + guard.notified().await; + + Ok(()) +} \ No newline at end of file diff --git a/rust-amqprs/src/bin/send.rs b/rust-amqprs/src/bin/send.rs new file mode 100644 index 00000000..29522b20 --- /dev/null +++ b/rust-amqprs/src/bin/send.rs @@ -0,0 +1,48 @@ +use amqprs::{ + connection::{Connection, OpenConnectionArguments}, + callbacks::{DefaultConnectionCallback, DefaultChannelCallback}, channel::{QueueDeclareArguments, BasicPublishArguments}, BasicProperties, + FieldTable, +}; +use amqp_serde::types::{FieldValue, ShortStr}; +use tokio; +use tokio::io::Error as TError; + +#[tokio::main] +async fn main() -> Result<(), Box> { + let conn = Connection::open(&OpenConnectionArguments::new( + "localhost", + 5672, + "guest", + "guest", + )) + .await.unwrap(); + conn.register_callback(DefaultConnectionCallback).await.unwrap(); + + let ch = conn.open_channel(None).await.unwrap(); + ch.register_callback(DefaultChannelCallback).await.unwrap(); + + let mut args = FieldTable::new(); + args.insert( + ShortStr::try_from("x-queue-type").unwrap(), + FieldValue::S("quorum".try_into().unwrap()), + ); + let q_args = QueueDeclareArguments::default() + .queue(String::from("hello")) + .durable(true) + .arguments(args) + .finish(); + let (queue_name, _, _) = ch.queue_declare(q_args).await.unwrap().unwrap(); + + let payload = String::from("Hello world!").into_bytes(); + let publish_args = BasicPublishArguments::new("", &queue_name); + // publish messages as persistent + let props = BasicProperties::default().with_delivery_mode(2).finish(); + ch.basic_publish(props, payload, publish_args).await.unwrap(); + + println!(" [x] Sent \"Hello World!\""); + + // in real applications connections are meant to be long lived + conn.close().await.unwrap(); + + Ok(()) +} diff --git a/rust-amqprs/src/bin/worker.rs b/rust-amqprs/src/bin/worker.rs new file mode 100644 index 00000000..813e2441 --- /dev/null +++ b/rust-amqprs/src/bin/worker.rs @@ -0,0 +1,53 @@ +use amqprs::{ + connection::{Connection, OpenConnectionArguments}, + callbacks::{DefaultConnectionCallback, DefaultChannelCallback}, + channel::{QueueDeclareArguments, BasicConsumeArguments, BasicAckArguments}, + FieldTable, +}; +use amqp_serde::types::{FieldValue, ShortStr}; +use tokio::{io::Error as TError, sync::Notify}; +use std::{time::Duration, thread}; + +#[tokio::main] +async fn main() -> Result<(), Box> { + let conn = Connection::open(&OpenConnectionArguments::new( + "localhost", + 5672, + "guest", + "guest")).await.unwrap(); + conn.register_callback(DefaultConnectionCallback).await.unwrap(); + + let ch = conn.open_channel(None).await.unwrap(); + ch.register_callback(DefaultChannelCallback).await.unwrap(); + + let q_name = "task_queue"; + let mut args = FieldTable::new(); + args.insert( + ShortStr::try_from("x-queue-type").unwrap(), + FieldValue::S("quorum".try_into().unwrap()), + ); + let q_args = QueueDeclareArguments::new(q_name).durable(true).arguments(args).finish(); + let (_, _, _) = ch.queue_declare(q_args).await.unwrap().unwrap(); + + let consumer_args = BasicConsumeArguments::default().queue(String::from(q_name)).finish(); + let (_ctag, mut rx) = ch.basic_consume_rx(consumer_args).await.unwrap(); + + println!(" [*] Waiting for messages. To exit press CTRL+C"); + + tokio::spawn(async move { + while let Some(msg) = rx.recv().await { + if let Some(payload) = msg.content { + println!(" [x] Received {:?}", std::str::from_utf8(&payload).unwrap()); + thread::sleep(Duration::from_secs(payload.len() as u64)); + println!(" [x] Done"); + + ch.basic_ack(BasicAckArguments::new(msg.deliver.unwrap().delivery_tag(), false)).await.unwrap(); + } + } + }); + + let guard = Notify::new(); + guard.notified().await; + + Ok(()) +} \ No newline at end of file diff --git a/rust-lapin/.gitignore b/rust-lapin/.gitignore new file mode 100644 index 00000000..c0094bb5 --- /dev/null +++ b/rust-lapin/.gitignore @@ -0,0 +1 @@ +!bin/ diff --git a/rust-lapin/Cargo.lock b/rust-lapin/Cargo.lock new file mode 100644 index 00000000..787dbb04 --- /dev/null +++ b/rust-lapin/Cargo.lock @@ -0,0 +1,2522 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "aes" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures", +] + +[[package]] +name = "amq-protocol" +version = "7.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "587d313f3a8b4a40f866cc84b6059fe83133bf172165ac3b583129dd211d8e1c" +dependencies = [ + "amq-protocol-tcp", + "amq-protocol-types", + "amq-protocol-uri", + "cookie-factory", + "nom", + "serde", +] + +[[package]] +name = "amq-protocol-tcp" +version = "7.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc707ab9aa964a85d9fc25908a3fdc486d2e619406883b3105b48bf304a8d606" +dependencies = [ + "amq-protocol-uri", + "tcp-stream", + "tracing", +] + +[[package]] +name = "amq-protocol-types" +version = "7.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf99351d92a161c61ec6ecb213bc7057f5b837dd4e64ba6cb6491358efd770c4" +dependencies = [ + "cookie-factory", + "nom", + "serde", + "serde_json", +] + +[[package]] +name = "amq-protocol-uri" +version = "7.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f89f8273826a676282208e5af38461a07fe939def57396af6ad5997fcf56577d" +dependencies = [ + "amq-protocol-types", + "percent-encoding", + "url", +] + +[[package]] +name = "anyhow" +version = "1.0.102" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" + +[[package]] +name = "asn1-rs" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56624a96882bb8c26d61312ae18cb45868e5a9992ea73c58e45c3101e56a1e60" +dependencies = [ + "asn1-rs-derive", + "asn1-rs-impl", + "displaydoc", + "nom", + "num-traits", + "rusticata-macros", + "thiserror", + "time", +] + +[[package]] +name = "asn1-rs-derive" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3109e49b1e4909e9db6515a30c633684d68cdeaa252f215214cb4fa1a5bfee2c" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "asn1-rs-impl" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b18050c2cd6fe86c3a76584ef5e0baf286d038cda203eb6223df2cc413565f7" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "async-channel" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "924ed96dd52d1b75e9c1a3e6275715fd320f5f9439fb5a4a11fa51f4221158d2" +dependencies = [ + "concurrent-queue", + "event-listener-strategy", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-executor" +version = "1.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c96bf972d85afc50bf5ab8fe2d54d1586b4e0b46c97c50a0c9e71e2f7bcd812a" +dependencies = [ + "async-task", + "concurrent-queue", + "fastrand 2.4.1", + "futures-lite 2.6.1", + "pin-project-lite", + "slab", +] + +[[package]] +name = "async-global-executor" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13f937e26114b93193065fd44f507aa2e9169ad0cdabbb996920b1fe1ddea7ba" +dependencies = [ + "async-channel", + "async-executor", + "async-io 2.6.0", + "async-lock 3.4.2", + "blocking", + "futures-lite 2.6.1", +] + +[[package]] +name = "async-global-executor-trait" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9af57045d58eeb1f7060e7025a1631cbc6399e0a1d10ad6735b3d0ea7f8346ce" +dependencies = [ + "async-global-executor", + "async-trait", + "executor-trait", +] + +[[package]] +name = "async-io" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fc5b45d93ef0529756f812ca52e44c221b35341892d3dcc34132ac02f3dd2af" +dependencies = [ + "async-lock 2.8.0", + "autocfg", + "cfg-if", + "concurrent-queue", + "futures-lite 1.13.0", + "log", + "parking", + "polling 2.8.0", + "rustix 0.37.28", + "slab", + "socket2 0.4.10", + "waker-fn", +] + +[[package]] +name = "async-io" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "456b8a8feb6f42d237746d4b3e9a178494627745c3c56c6ea55d92ba50d026fc" +dependencies = [ + "autocfg", + "cfg-if", + "concurrent-queue", + "futures-io", + "futures-lite 2.6.1", + "parking", + "polling 3.11.0", + "rustix 1.1.4", + "slab", + "windows-sys 0.61.2", +] + +[[package]] +name = "async-lock" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "287272293e9d8c41773cec55e365490fe034813a2f172f502d6ddcf75b2f582b" +dependencies = [ + "event-listener 2.5.3", +] + +[[package]] +name = "async-lock" +version = "3.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "290f7f2596bd5b78a9fec8088ccd89180d7f9f55b94b0576823bbbdc72ee8311" +dependencies = [ + "event-listener 5.4.1", + "event-listener-strategy", + "pin-project-lite", +] + +[[package]] +name = "async-reactor-trait" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a6012d170ad00de56c9ee354aef2e358359deb1ec504254e0e5a3774771de0e" +dependencies = [ + "async-io 1.13.0", + "async-trait", + "futures-core", + "reactor-trait", +] + +[[package]] +name = "async-task" +version = "4.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" + +[[package]] +name = "async-trait" +version = "0.1.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + +[[package]] +name = "base64ct" +version = "1.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3" + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "block-padding" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8894febbff9f758034a5b8e12d87918f56dfc64a8e1fe757d65e29041538d93" +dependencies = [ + "generic-array", +] + +[[package]] +name = "blocking" +version = "1.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e83f8d02be6967315521be875afa792a316e28d57b5a2d401897e2a7921b7f21" +dependencies = [ + "async-channel", + "async-task", + "futures-io", + "futures-lite 2.6.1", + "piper", +] + +[[package]] +name = "bumpalo" +version = "3.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" + +[[package]] +name = "bytes" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" + +[[package]] +name = "cbc" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26b52a9543ae338f279b96b0b9fed9c8093744685043739079ce85cd58f289a6" +dependencies = [ + "cipher", +] + +[[package]] +name = "cc" +version = "1.2.61" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d16d90359e986641506914ba71350897565610e87ce0ad9e6f28569db3dd5c6d" +dependencies = [ + "find-msvc-tools", + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "cipher" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common", + "inout", +] + +[[package]] +name = "cms" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b77c319abfd5219629c45c34c89ba945ed3c5e49fcde9d16b6c3885f118a730" +dependencies = [ + "const-oid", + "der", + "spki", + "x509-cert", +] + +[[package]] +name = "concurrent-queue" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "const-oid" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" + +[[package]] +name = "cookie-factory" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9885fa71e26b8ab7855e2ec7cae6e9b380edff76cd052e07c683a0319d51b3a2" + +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" + +[[package]] +name = "crypto-common" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "data-encoding" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4ae5f15dda3c708c0ade84bfee31ccab44a3da4f88015ed22f63732abe300c8" + +[[package]] +name = "der" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" +dependencies = [ + "const-oid", + "der_derive", + "flagset", + "pem-rfc7468", + "zeroize", +] + +[[package]] +name = "der-parser" +version = "10.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07da5016415d5a3c4dd39b11ed26f915f52fc4e0dc197d87908bc916e51bc1a6" +dependencies = [ + "asn1-rs", + "displaydoc", + "nom", + "num-bigint", + "num-traits", + "rusticata-macros", +] + +[[package]] +name = "der_derive" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8034092389675178f570469e6c3b0465d3d30b4505c294a6550db47f3c17ad18" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "deranged" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" +dependencies = [ + "powerfmt", +] + +[[package]] +name = "des" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffdd80ce8ce993de27e9f063a444a4d53ce8e8db4c1f00cc03af5ad5a9867a1e" +dependencies = [ + "cipher", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", + "subtle", +] + +[[package]] +name = "displaydoc" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "doc-comment" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "780955b8b195a21ab8e4ac6b60dd1dbdcec1dc6c51c0617964b08c81785e12c9" + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "event-listener" +version = "2.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" + +[[package]] +name = "event-listener" +version = "5.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13b66accf52311f30a0db42147dadea9850cb48cd070028831ae5f5d4b856ab" +dependencies = [ + "concurrent-queue", + "parking", + "pin-project-lite", +] + +[[package]] +name = "event-listener-strategy" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8be9f3dfaaffdae2972880079a491a1a8bb7cbed0b8dd7a347f668b4150a3b93" +dependencies = [ + "event-listener 5.4.1", + "pin-project-lite", +] + +[[package]] +name = "executor-trait" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c39dff9342e4e0e16ce96be751eb21a94e94a87bb2f6e63ad1961c2ce109bf" +dependencies = [ + "async-trait", +] + +[[package]] +name = "fastrand" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be" +dependencies = [ + "instant", +] + +[[package]] +name = "fastrand" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f1f227452a390804cdb637b74a86990f2a7d7ba4b7d5693aac9b4dd6defd8d6" + +[[package]] +name = "find-msvc-tools" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" + +[[package]] +name = "flagset" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7ac824320a75a52197e8f2d787f6a38b6718bb6897a35142d749af3c0e8f4fe" + +[[package]] +name = "flume" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da0e4dd2a88388a1f4ccc7c9ce104604dab68d9f408dc34cd45823d5a9069095" +dependencies = [ + "futures-core", + "futures-sink", + "spin", +] + +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + +[[package]] +name = "form_urlencoded" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "futures" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b147ee9d1f6d097cef9ce628cd2ee62288d963e16fb287bd9286455b241382d" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" + +[[package]] +name = "futures-executor" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf29c38818342a3b26b5b923639e7b1f4a61fc5e76102d4b1981c6dc7a7579d" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718" + +[[package]] +name = "futures-lite" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49a9d51ce47660b1e808d3c990b4709f2f415d928835a17dfd16991515c46bce" +dependencies = [ + "fastrand 1.9.0", + "futures-core", + "futures-io", + "memchr", + "parking", + "pin-project-lite", + "waker-fn", +] + +[[package]] +name = "futures-lite" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f78e10609fe0e0b3f4157ffab1876319b5b0db102a2c60dc4626306dc46b44ad" +dependencies = [ + "fastrand 2.4.1", + "futures-core", + "futures-io", + "parking", + "pin-project-lite", +] + +[[package]] +name = "futures-macro" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "futures-sink" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893" + +[[package]] +name = "futures-task" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" + +[[package]] +name = "futures-util" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "slab", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "getrandom" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +dependencies = [ + "cfg-if", + "libc", + "r-efi 5.3.0", + "wasip2", +] + +[[package]] +name = "getrandom" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555" +dependencies = [ + "cfg-if", + "libc", + "r-efi 6.0.0", + "wasip2", + "wasip3", +] + +[[package]] +name = "hashbrown" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +dependencies = [ + "foldhash", +] + +[[package]] +name = "hashbrown" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f467dd6dccf739c208452f8014c75c18bb8301b050ad1cfb27153803edb0f51" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "hermit-abi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" + +[[package]] +name = "hermit-abi" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] + +[[package]] +name = "icu_collections" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2984d1cd16c883d7935b9e07e44071dca8d917fd52ecc02c04d5fa0b5a3f191c" +dependencies = [ + "displaydoc", + "potential_utf", + "utf8_iter", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locale_core" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92219b62b3e2b4d88ac5119f8904c10f8f61bf7e95b640d25ba3075e6cac2c29" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_normalizer" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c56e5ee99d6e3d33bd91c5d85458b6005a22140021cc324cea84dd0e72cff3b4" +dependencies = [ + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da3be0ae77ea334f4da67c12f149704f19f81d1adf7c51cf482943e84a2bad38" + +[[package]] +name = "icu_properties" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bee3b67d0ea5c2cca5003417989af8996f8604e34fb9ddf96208a033901e70de" +dependencies = [ + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "zerotrie", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e2bbb201e0c04f7b4b3e14382af113e17ba4f63e2c9d2ee626b720cbce54a14" + +[[package]] +name = "icu_provider" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "139c4cf31c8b5f33d7e199446eff9c1e02decfc2f0eec2c8d71f65befa45b421" +dependencies = [ + "displaydoc", + "icu_locale_core", + "writeable", + "yoke", + "zerofrom", + "zerotrie", + "zerovec", +] + +[[package]] +name = "id-arena" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" + +[[package]] +name = "idna" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + +[[package]] +name = "indexmap" +version = "2.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" +dependencies = [ + "equivalent", + "hashbrown 0.17.0", + "serde", + "serde_core", +] + +[[package]] +name = "inout" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01" +dependencies = [ + "block-padding", + "generic-array", +] + +[[package]] +name = "instant" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "io-lifetimes" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2" +dependencies = [ + "hermit-abi 0.3.9", + "libc", + "windows-sys 0.48.0", +] + +[[package]] +name = "itoa" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" + +[[package]] +name = "js-sys" +version = "0.3.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2964e92d1d9dc3364cae4d718d93f227e3abb088e747d92e0395bfdedf1c12ca" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "lapin" +version = "2.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02d2aa4725b9607915fa1a73e940710a3be6af508ce700e56897cbe8847fbb07" +dependencies = [ + "amq-protocol", + "async-global-executor-trait", + "async-reactor-trait", + "async-trait", + "executor-trait", + "flume", + "futures-core", + "futures-io", + "parking_lot", + "pinky-swear", + "reactor-trait", + "serde", + "tracing", + "waker-fn", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "leb128fmt" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" + +[[package]] +name = "libc" +version = "0.2.186" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" + +[[package]] +name = "linux-raw-sys" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" + +[[package]] +name = "linux-raw-sys" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" + +[[package]] +name = "litemap" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0" + +[[package]] +name = "lock_api" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" + +[[package]] +name = "memchr" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "mio" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50b7e5b27aa02a74bac8c3f23f448f8d87ff11f92d3aac1a6ed369ee08cc56c1" +dependencies = [ + "libc", + "wasi", + "windows-sys 0.61.2", +] + +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "num-bigint" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-conv" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6673768db2d862beb9b39a78fdcb1a69439615d5794a1be50caa9bc92c81967" + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "oid-registry" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12f40cff3dde1b6087cc5d5f5d4d65712f34016a03ed60e9c08dcc392736b5b7" +dependencies = [ + "asn1-rs", +] + +[[package]] +name = "once_cell" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" + +[[package]] +name = "openssl-probe" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" + +[[package]] +name = "p12-keystore" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3cae83056e7cb770211494a0ecf66d9fa7eba7d00977e5bb91f0e925b40b937f" +dependencies = [ + "cbc", + "cms", + "der", + "des", + "hex", + "hmac", + "pkcs12", + "pkcs5", + "rand", + "rc2", + "sha1", + "sha2", + "thiserror", + "x509-parser", +] + +[[package]] +name = "parking" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" + +[[package]] +name = "parking_lot" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-link", +] + +[[package]] +name = "pbkdf2" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2" +dependencies = [ + "digest", + "hmac", +] + +[[package]] +name = "pem-rfc7468" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" +dependencies = [ + "base64ct", +] + +[[package]] +name = "percent-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" + +[[package]] +name = "pin-project-lite" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" + +[[package]] +name = "pinky-swear" +version = "6.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1ea6e230dd3a64d61bcb8b79e597d3ab6b4c94ec7a234ce687dd718b4f2e657" +dependencies = [ + "doc-comment", + "flume", + "parking_lot", + "tracing", +] + +[[package]] +name = "piper" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c835479a4443ded371d6c535cbfd8d31ad92c5d23ae9770a61bc155e4992a3c1" +dependencies = [ + "atomic-waker", + "fastrand 2.4.1", + "futures-io", +] + +[[package]] +name = "pkcs12" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "695b3df3d3cc1015f12d70235e35b6b79befc5fa7a9b95b951eab1dd07c9efc2" +dependencies = [ + "cms", + "const-oid", + "der", + "digest", + "spki", + "x509-cert", + "zeroize", +] + +[[package]] +name = "pkcs5" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e847e2c91a18bfa887dd028ec33f2fe6f25db77db3619024764914affe8b69a6" +dependencies = [ + "aes", + "cbc", + "der", + "pbkdf2", + "scrypt", + "sha2", + "spki", +] + +[[package]] +name = "polling" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b2d323e8ca7996b3e23126511a523f7e62924d93ecd5ae73b333815b0eb3dce" +dependencies = [ + "autocfg", + "bitflags 1.3.2", + "cfg-if", + "concurrent-queue", + "libc", + "log", + "pin-project-lite", + "windows-sys 0.48.0", +] + +[[package]] +name = "polling" +version = "3.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d0e4f59085d47d8241c88ead0f274e8a0cb551f3625263c05eb8dd897c34218" +dependencies = [ + "cfg-if", + "concurrent-queue", + "hermit-abi 0.5.2", + "pin-project-lite", + "rustix 1.1.4", + "windows-sys 0.61.2", +] + +[[package]] +name = "potential_utf" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0103b1cef7ec0cf76490e969665504990193874ea05c85ff9bab8b911d0a0564" +dependencies = [ + "zerovec", +] + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "prettyplease" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" +dependencies = [ + "proc-macro2", + "syn", +] + +[[package]] +name = "proc-macro2" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[package]] +name = "r-efi" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" + +[[package]] +name = "rabbitmq-tutorials" +version = "1.0.0" +dependencies = [ + "futures", + "lapin", + "tokio", + "tokio-stream", + "uuid", +] + +[[package]] +name = "rand" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44c5af06bb1b7d3216d91932aed5265164bf384dc89cd6ba05cf59a35f5f76ea" +dependencies = [ + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" +dependencies = [ + "getrandom 0.3.4", +] + +[[package]] +name = "rc2" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62c64daa8e9438b84aaae55010a93f396f8e60e3911590fcba770d04643fc1dd" +dependencies = [ + "cipher", +] + +[[package]] +name = "reactor-trait" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "438a4293e4d097556730f4711998189416232f009c137389e0f961d2bc0ddc58" +dependencies = [ + "async-trait", + "futures-core", + "futures-io", +] + +[[package]] +name = "redox_syscall" +version = "0.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +dependencies = [ + "bitflags 2.11.1", +] + +[[package]] +name = "ring" +version = "0.17.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" +dependencies = [ + "cc", + "cfg-if", + "getrandom 0.2.17", + "libc", + "untrusted", + "windows-sys 0.52.0", +] + +[[package]] +name = "rusticata-macros" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "faf0c4a6ece9950b9abdb62b1cfcf2a68b3b67a10ba445b3bb85be2a293d0632" +dependencies = [ + "nom", +] + +[[package]] +name = "rustix" +version = "0.37.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "519165d378b97752ca44bbe15047d5d3409e875f39327546b42ac81d7e18c1b6" +dependencies = [ + "bitflags 1.3.2", + "errno", + "io-lifetimes", + "libc", + "linux-raw-sys 0.3.8", + "windows-sys 0.48.0", +] + +[[package]] +name = "rustix" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" +dependencies = [ + "bitflags 2.11.1", + "errno", + "libc", + "linux-raw-sys 0.12.1", + "windows-sys 0.61.2", +] + +[[package]] +name = "rustls" +version = "0.23.39" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c2c118cb077cca2822033836dfb1b975355dfb784b5e8da48f7b6c5db74e60e" +dependencies = [ + "once_cell", + "ring", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-connector" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70cc376c6ba1823ae229bacf8ad93c136d93524eab0e4e5e0e4f96b9c4e5b212" +dependencies = [ + "log", + "rustls", + "rustls-native-certs", + "rustls-pki-types", + "rustls-webpki", +] + +[[package]] +name = "rustls-native-certs" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5bfb394eeed242e909609f56089eecfe5fda225042e8b171791b9c95f5931e5" +dependencies = [ + "openssl-probe", + "rustls-pemfile", + "rustls-pki-types", + "schannel", + "security-framework", +] + +[[package]] +name = "rustls-pemfile" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "rustls-pki-types" +version = "1.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30a7197ae7eb376e574fe940d068c30fe0462554a3ddbe4eca7838e049c937a9" +dependencies = [ + "zeroize", +] + +[[package]] +name = "rustls-webpki" +version = "0.103.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61c429a8649f110dddef65e2a5ad240f747e85f7758a6bccc7e5777bd33f756e" +dependencies = [ + "ring", + "rustls-pki-types", + "untrusted", +] + +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + +[[package]] +name = "salsa20" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97a22f5af31f73a954c10289c93e8a50cc23d971e80ee446f1f6f7137a088213" +dependencies = [ + "cipher", +] + +[[package]] +name = "schannel" +version = "0.1.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91c1b7e4904c873ef0710c1f407dde2e6287de2bebc1bbbf7d430bb7cbffd939" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "scrypt" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0516a385866c09368f0b5bcd1caff3366aace790fcd46e2bb032697bb172fd1f" +dependencies = [ + "pbkdf2", + "salsa20", + "sha2", +] + +[[package]] +name = "security-framework" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" +dependencies = [ + "bitflags 2.11.1", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2691df843ecc5d231c0b14ece2acc3efb62c0a398c7e1d875f3983ce020e3" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "semver" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" + +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.149" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" +dependencies = [ + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[package]] +name = "sha1" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sha2" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "signal-hook-registry" +version = "1.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" +dependencies = [ + "errno", + "libc", +] + +[[package]] +name = "slab" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" + +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" + +[[package]] +name = "socket2" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7916fc008ca5542385b89a3d3ce689953c143e9304a9bf8beec1de48994c0d" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "socket2" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +dependencies = [ + "lock_api", +] + +[[package]] +name = "spki" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" +dependencies = [ + "base64ct", + "der", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] +name = "syn" +version = "2.0.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "synstructure" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tcp-stream" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "495b0abdce3dc1f8fd27240651c9e68890c14e9d9c61527b1ce44d8a5a7bd3d5" +dependencies = [ + "cfg-if", + "p12-keystore", + "rustls-connector", + "rustls-pemfile", +] + +[[package]] +name = "thiserror" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "time" +version = "0.3.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c" +dependencies = [ + "deranged", + "itoa", + "num-conv", + "powerfmt", + "serde_core", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca" + +[[package]] +name = "time-macros" +version = "0.2.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e70e4c5a0e0a8a4823ad65dfe1a6930e4f4d756dcd9dd7939022b5e8c501215" +dependencies = [ + "num-conv", + "time-core", +] + +[[package]] +name = "tinystr" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8323304221c2a851516f22236c5722a72eaa19749016521d6dff0824447d96d" +dependencies = [ + "displaydoc", + "zerovec", +] + +[[package]] +name = "tokio" +version = "1.52.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b67dee974fe86fd92cc45b7a95fdd2f99a36a6d7b0d431a231178d3d670bbcc6" +dependencies = [ + "bytes", + "libc", + "mio", + "parking_lot", + "pin-project-lite", + "signal-hook-registry", + "socket2 0.6.3", + "tokio-macros", + "windows-sys 0.61.2", +] + +[[package]] +name = "tokio-macros" +version = "2.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "385a6cb71ab9ab790c5fe8d67f1645e6c450a7ce006a33de03daa956cf70a496" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tokio-stream" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32da49809aab5c3bc678af03902d4ccddea2a87d028d86392a4b1560c6906c70" +dependencies = [ + "futures-core", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tracing" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" +dependencies = [ + "pin-project-lite", + "tracing-core", +] + +[[package]] +name = "tracing-core" +version = "0.1.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" + +[[package]] +name = "typenum" +version = "1.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40ce102ab67701b8526c123c1bab5cbe42d7040ccfd0f64af1a385808d2f43de" + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + +[[package]] +name = "url" +version = "2.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", + "serde", +] + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + +[[package]] +name = "uuid" +version = "1.23.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddd74a9687298c6858e9b88ec8935ec45d22e8fd5e6394fa1bd4e99a87789c76" +dependencies = [ + "getrandom 0.4.2", + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "waker-fn" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "317211a0dc0ceedd78fb2ca9a44aed3d7b9b26f81870d485c07122b4350673b7" + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "wasip2" +version = "1.0.3+wasi-0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20064672db26d7cdc89c7798c48a0fdfac8213434a1186e5ef29fd560ae223d6" +dependencies = [ + "wit-bindgen 0.57.1", +] + +[[package]] +name = "wasip3" +version = "0.4.0+wasi-0.3.0-rc-2026-01-06" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" +dependencies = [ + "wit-bindgen 0.51.0", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.118" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf938a0bacb0469e83c1e148908bd7d5a6010354cf4fb73279b7447422e3a89" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.118" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eeff24f84126c0ec2db7a449f0c2ec963c6a49efe0698c4242929da037ca28ed" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.118" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d08065faf983b2b80a79fd87d8254c409281cf7de75fc4b773019824196c904" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.118" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fd04d9e306f1907bd13c6361b5c6bfc7b3b3c095ed3f8a9246390f8dbdee129" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "wasm-encoder" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319" +dependencies = [ + "leb128fmt", + "wasmparser", +] + +[[package]] +name = "wasm-metadata" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" +dependencies = [ + "anyhow", + "indexmap", + "wasm-encoder", + "wasmparser", +] + +[[package]] +name = "wasmparser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" +dependencies = [ + "bitflags 2.11.1", + "hashbrown 0.15.5", + "indexmap", + "semver", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "wit-bindgen" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" +dependencies = [ + "wit-bindgen-rust-macro", +] + +[[package]] +name = "wit-bindgen" +version = "0.57.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" + +[[package]] +name = "wit-bindgen-core" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc" +dependencies = [ + "anyhow", + "heck", + "wit-parser", +] + +[[package]] +name = "wit-bindgen-rust" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" +dependencies = [ + "anyhow", + "heck", + "indexmap", + "prettyplease", + "syn", + "wasm-metadata", + "wit-bindgen-core", + "wit-component", +] + +[[package]] +name = "wit-bindgen-rust-macro" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a" +dependencies = [ + "anyhow", + "prettyplease", + "proc-macro2", + "quote", + "syn", + "wit-bindgen-core", + "wit-bindgen-rust", +] + +[[package]] +name = "wit-component" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" +dependencies = [ + "anyhow", + "bitflags 2.11.1", + "indexmap", + "log", + "serde", + "serde_derive", + "serde_json", + "wasm-encoder", + "wasm-metadata", + "wasmparser", + "wit-parser", +] + +[[package]] +name = "wit-parser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" +dependencies = [ + "anyhow", + "id-arena", + "indexmap", + "log", + "semver", + "serde", + "serde_derive", + "serde_json", + "unicode-xid", + "wasmparser", +] + +[[package]] +name = "writeable" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4" + +[[package]] +name = "x509-cert" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1301e935010a701ae5f8655edc0ad17c44bad3ac5ce8c39185f75453b720ae94" +dependencies = [ + "const-oid", + "der", + "spki", +] + +[[package]] +name = "x509-parser" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4569f339c0c402346d4a75a9e39cf8dad310e287eef1ff56d4c68e5067f53460" +dependencies = [ + "asn1-rs", + "data-encoding", + "der-parser", + "lazy_static", + "nom", + "oid-registry", + "rusticata-macros", + "thiserror", + "time", +] + +[[package]] +name = "yoke" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abe8c5fda708d9ca3df187cae8bfb9ceda00dd96231bed36e445a1a48e66f9ca" +dependencies = [ + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "zerocopy" +version = "0.8.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eed437bf9d6692032087e337407a86f04cd8d6a16a37199ed57949d415bd68e9" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70e3cd084b1788766f53af483dd21f93881ff30d7320490ec3ef7526d203bad4" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zerofrom" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69faa1f2a1ea75661980b013019ed6687ed0e83d069bc1114e2cc74c6c04c4df" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "zeroize" +version = "1.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" + +[[package]] +name = "zerotrie" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f9152d31db0792fa83f70fb2f83148effb5c1f5b8c7686c3459e361d9bc20bf" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] + +[[package]] +name = "zerovec" +version = "0.11.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90f911cbc359ab6af17377d242225f4d75119aec87ea711a880987b18cd7b239" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zmij" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" diff --git a/rust-lapin/Cargo.toml b/rust-lapin/Cargo.toml new file mode 100644 index 00000000..83de91bb --- /dev/null +++ b/rust-lapin/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "rabbitmq-tutorials" +version = "1.0.0" +authors = ["Michal Malek "] +edition = "2018" + +[dependencies] +futures = "0.3.21" +lapin = "2.1.1" +tokio = { version = "1.43.1", features = ["full"] } +uuid = { version = "1.1.1", features = ["v4"] } +tokio-stream = "0.1" diff --git a/rust-lapin/README.md b/rust-lapin/README.md new file mode 100644 index 00000000..6299778a --- /dev/null +++ b/rust-lapin/README.md @@ -0,0 +1,45 @@ +# Rust code for RabbitMQ tutorials (using Lapin) + +Here you can find the Rust code examples for [RabbitMQ +tutorials](https://www.rabbitmq.com/getstarted.html). + +The examples use [lapin](https://github.com/CleverCloud/lapin) client library. + +These tutorials assume a RabbitMQ server node running locally using default ports. + +## Requirements + +* [Rust and Cargo](https://www.rust-lang.org/tools/install) + +## Code +Each cargo command should be launched in a separate shell. + +#### [Tutorial one: "Hello World!"](https://www.rabbitmq.com/tutorials/tutorial-one-python.html) + + cargo run --bin receive + cargo run --bin send + +#### [Tutorial two: Work Queues](https://www.rabbitmq.com/tutorials/tutorial-two-python.html) + + cargo run --bin worker + cargo run --bin new_task "hi" # specify a custom message + +#### [Tutorial three: Publish/Subscribe](https://www.rabbitmq.com/tutorials/tutorial-three-python.html) + + cargo run --bin receive_logs + cargo run --bin emit_log "hi" # specify a custom message + +#### [Tutorial four: Routing](https://www.rabbitmq.com/tutorials/tutorial-four-python.html) + + cargo run --bin receive_logs_direct info error # specify log levels + cargo run --bin emit_log_direct error "help!" # specify severity and custom message + +#### [Tutorial five: Topics](https://www.rabbitmq.com/tutorials/tutorial-five-python.html) + + cargo run --bin receive_logs_topic kern.* # specify topic filter + cargo run --bin emit_log_topic kern.mem "No memory left!" # specify topic and message + +#### [Tutorial six: RPC](https://www.rabbitmq.com/tutorials/tutorial-six-python.html) + + cargo run --bin rpc_server + cargo run --bin rpc_client diff --git a/rust-lapin/src/bin/emit_log.rs b/rust-lapin/src/bin/emit_log.rs new file mode 100644 index 00000000..060c48f3 --- /dev/null +++ b/rust-lapin/src/bin/emit_log.rs @@ -0,0 +1,41 @@ +use lapin::{ + options::*, types::FieldTable, BasicProperties, Connection, ConnectionProperties, ExchangeKind, +}; + +#[tokio::main] +async fn main() -> Result<(), Box> { + let args: Vec<_> = std::env::args().skip(1).collect(); + let message = match args.len() { + 0 => "hello".to_string(), + _ => args.join(" ").to_string(), + }; + + let addr = "amqp://127.0.0.1:5672"; + let conn = Connection::connect(addr, ConnectionProperties::default()).await?; + let channel = conn.create_channel().await?; + + channel + .exchange_declare( + "logs", + ExchangeKind::Fanout, + ExchangeDeclareOptions::default(), + FieldTable::default(), + ) + .await?; + + channel + .basic_publish( + "logs", + "", + BasicPublishOptions::default(), + message.as_bytes(), + BasicProperties::default(), + ) + .await?; + + println!(" [x] Sent {:?}", std::str::from_utf8(&message.as_bytes())?); + + conn.close(0, "").await?; + + Ok(()) +} diff --git a/rust-lapin/src/bin/emit_log_direct.rs b/rust-lapin/src/bin/emit_log_direct.rs new file mode 100644 index 00000000..a3d4161c --- /dev/null +++ b/rust-lapin/src/bin/emit_log_direct.rs @@ -0,0 +1,46 @@ +use lapin::{ + options::*, types::FieldTable, BasicProperties, Connection, ConnectionProperties, ExchangeKind, +}; + +#[tokio::main] +async fn main() -> Result<(), Box> { + let args: Vec<_> = std::env::args().skip(1).collect(); + let severity = args.first().map(String::as_str).unwrap_or("info"); + let message = match args.len() { + x if x < 2 => "Hello, world!".to_string(), + _ => args[1..].join(" ").to_string(), + }; + + let addr = "amqp://127.0.0.1:5672"; + let conn = Connection::connect(addr, ConnectionProperties::default()).await?; + let channel = conn.create_channel().await?; + + channel + .exchange_declare( + "direct_logs", + ExchangeKind::Direct, + ExchangeDeclareOptions::default(), + FieldTable::default(), + ) + .await?; + + channel + .basic_publish( + "direct_logs", + severity, + BasicPublishOptions::default(), + message.as_bytes(), + BasicProperties::default(), + ) + .await?; + + println!( + " [x] Sent {}:{:?}", + severity, + std::str::from_utf8(message.as_bytes())? + ); + + conn.close(0, "").await?; + + Ok(()) +} diff --git a/rust-lapin/src/bin/emit_log_topic.rs b/rust-lapin/src/bin/emit_log_topic.rs new file mode 100644 index 00000000..09b720a4 --- /dev/null +++ b/rust-lapin/src/bin/emit_log_topic.rs @@ -0,0 +1,46 @@ +use lapin::{ + options::*, types::FieldTable, BasicProperties, Connection, ConnectionProperties, ExchangeKind, +}; + +#[tokio::main] +async fn main() -> Result<(), Box> { + let args: Vec<_> = std::env::args().skip(1).collect(); + let routing_key = args.first().map(String::as_str).unwrap_or("anonymous.info"); + let message = match args.len() { + x if x < 2 => "Hello, world!".to_string(), + _ => args[1..].join(" ").to_string(), + }; + + let addr = "amqp://127.0.0.1:5672"; + let conn = Connection::connect(addr, ConnectionProperties::default()).await?; + let channel = conn.create_channel().await?; + + channel + .exchange_declare( + "topic_logs", + ExchangeKind::Topic, + ExchangeDeclareOptions::default(), + FieldTable::default(), + ) + .await?; + + channel + .basic_publish( + "topic_logs", + routing_key, + BasicPublishOptions::default(), + message.as_bytes(), + BasicProperties::default(), + ) + .await?; + + println!( + " [x] Sent {}:{:?}", + routing_key, + std::str::from_utf8(message.as_bytes())? + ); + + conn.close(0, "").await?; + + Ok(()) +} diff --git a/rust-lapin/src/bin/new_task.rs b/rust-lapin/src/bin/new_task.rs new file mode 100644 index 00000000..04b5808c --- /dev/null +++ b/rust-lapin/src/bin/new_task.rs @@ -0,0 +1,40 @@ +use lapin::{BasicProperties, Connection, ConnectionProperties, options::*, types::{AMQPValue, FieldTable}}; + +#[tokio::main] +async fn main() -> Result<(), Box> { + let args: Vec<_> = std::env::args().skip(1).collect(); + let message = match args.len() { + 0 => "hello".to_string(), + _ => args.join(" ").to_string(), + }; + + let addr = "amqp://127.0.0.1:5672"; + let conn = Connection::connect(addr, ConnectionProperties::default()).await?; + let channel = conn.create_channel().await?; + + let mut args = FieldTable::default(); + args.insert("x-queue-type".into(), AMQPValue::LongString("quorum".into())); + channel + .queue_declare( + "task_queue", + QueueDeclareOptions { durable: true, ..Default::default() }, + args, + ) + .await?; + + channel + .basic_publish( + "", + "task_queue", + BasicPublishOptions::default(), + message.as_bytes(), + BasicProperties::default(), + ) + .await?; + + println!(" [x] Sent {:?}", std::str::from_utf8(message.as_bytes())?); + + conn.close(0, "").await?; + + Ok(()) +} diff --git a/rust-lapin/src/bin/receive.rs b/rust-lapin/src/bin/receive.rs new file mode 100644 index 00000000..d592f999 --- /dev/null +++ b/rust-lapin/src/bin/receive.rs @@ -0,0 +1,43 @@ +use futures::StreamExt; +use lapin::{Connection, ConnectionProperties, options::*, types::{AMQPValue, FieldTable}}; + +#[tokio::main] +async fn main() -> Result<(), Box> { + let addr = "amqp://127.0.0.1:5672"; + let conn = Connection::connect(addr, ConnectionProperties::default()).await?; + let channel = conn.create_channel().await?; + + let mut args = FieldTable::default(); + args.insert("x-queue-type".into(), AMQPValue::LongString("quorum".into())); + channel + .queue_declare( + "hello", + QueueDeclareOptions { durable: true, ..Default::default() }, + args, + ) + .await?; + + let mut consumer = channel + .basic_consume( + "hello", + "consumer", + BasicConsumeOptions::default(), + FieldTable::default(), + ) + .await + .expect("basic_consume"); + + println!(" [*] Waiting for messages. To exit press CTRL+C"); + + + while let Some(delivery) = consumer.next().await { + if let Ok(delivery) = delivery { + println!(" [x] Received {:?}", std::str::from_utf8(&delivery.data)?); + delivery.ack(BasicAckOptions::default()) + .await + .expect("basic_ack"); + } + } + + Ok(()) +} diff --git a/rust-lapin/src/bin/receive_logs.rs b/rust-lapin/src/bin/receive_logs.rs new file mode 100644 index 00000000..65f9d8d5 --- /dev/null +++ b/rust-lapin/src/bin/receive_logs.rs @@ -0,0 +1,63 @@ +use futures::StreamExt; +use lapin::{options::*, types::FieldTable, Connection, ConnectionProperties, ExchangeKind}; + +#[tokio::main] +async fn main() -> Result<(), Box> { + let addr = "amqp://127.0.0.1:5672"; + let conn = Connection::connect(addr, ConnectionProperties::default()).await?; + let channel = conn.create_channel().await?; + + channel + .exchange_declare( + "logs", + ExchangeKind::Fanout, + ExchangeDeclareOptions::default(), + FieldTable::default(), + ) + .await?; + + let queue = channel + .queue_declare( + "", + QueueDeclareOptions { + exclusive: true, + ..Default::default() + }, + FieldTable::default(), + ) + .await?; + + channel + .queue_bind( + queue.name().as_str(), + "logs", + "", + QueueBindOptions::default(), + FieldTable::default(), + ) + .await?; + + let mut consumer = channel + .basic_consume( + queue.name().as_str(), + "consumer", + BasicConsumeOptions { + no_ack: true, + ..Default::default() + }, + FieldTable::default(), + ) + .await?; + + println!(" [*] Waiting for logs. To exit press CTRL+C"); + + + while let Some(delivery) = consumer.next().await { + if let Ok(delivery) = delivery { + println!(" [x] Received {:?}", std::str::from_utf8(&delivery.data)?); + } + } + + + Ok(()) +} diff --git a/rust-lapin/src/bin/receive_logs_direct.rs b/rust-lapin/src/bin/receive_logs_direct.rs new file mode 100644 index 00000000..67753baa --- /dev/null +++ b/rust-lapin/src/bin/receive_logs_direct.rs @@ -0,0 +1,74 @@ +use futures::StreamExt; +use lapin::{Connection, ConnectionProperties, ExchangeKind, options::*, types::FieldTable}; + +#[tokio::main] +async fn main() -> Result<(), Box> { + let severities: Vec<_> = std::env::args().skip(1).collect(); + if severities.is_empty() { + eprintln!( + "Usage: {} [info] [warning] [error]\n", + std::env::args().next().unwrap_or_else(|| "receive-direct".into()) + ); + std::process::exit(1); + } + + let addr = "amqp://127.0.0.1:5672"; + let conn = Connection::connect(addr, ConnectionProperties::default()).await?; + let channel = conn.create_channel().await?; + + channel + .exchange_declare( + "direct_logs", + ExchangeKind::Direct, + ExchangeDeclareOptions::default(), + FieldTable::default(), + ) + .await?; + + let queue = channel + .queue_declare( + "", + QueueDeclareOptions { + exclusive: true, + ..Default::default() + }, + FieldTable::default(), + ) + .await?; + + + futures::future::join_all(severities.iter().map(|severity| { + channel.queue_bind( + queue.name().as_str(), + "direct_logs", + &severity, + QueueBindOptions::default(), + FieldTable::default(), + ) + })).await; + + let mut consumer = channel + .basic_consume( + queue.name().as_str(), + "consumer", + BasicConsumeOptions { + no_ack: true, + ..Default::default() + }, + FieldTable::default(), + ) + .await?; + + println!(" [*] Waiting for logs. To exit press CTRL+C"); + + while let Some(delivery) = consumer.next().await { + if let Ok(delivery) = delivery { + println!( + " [x] {}:{:?}", + delivery.routing_key, + std::str::from_utf8(&delivery.data)? + ); + } + } + Ok(()) +} diff --git a/rust-lapin/src/bin/receive_logs_topic.rs b/rust-lapin/src/bin/receive_logs_topic.rs new file mode 100644 index 00000000..547aea71 --- /dev/null +++ b/rust-lapin/src/bin/receive_logs_topic.rs @@ -0,0 +1,77 @@ +use futures::StreamExt; +use lapin::{options::*, types::FieldTable, Connection, ConnectionProperties, ExchangeKind}; + +#[tokio::main] +async fn main() -> Result<(), Box> { + let binding_keys: Vec<_> = std::env::args().skip(1).collect(); + if binding_keys.is_empty() { + eprintln!( + "Usage: {} [binding_key]...\n", + std::env::args().next().unwrap_or_else(|| "receive-topic".into()) + ); + std::process::exit(1); + } + + let addr = "amqp://127.0.0.1:5672"; + let conn = Connection::connect(addr, ConnectionProperties::default()).await?; + let channel = conn.create_channel().await?; + + channel + .exchange_declare( + "topic_logs", + ExchangeKind::Topic, + ExchangeDeclareOptions::default(), + FieldTable::default(), + ) + .await?; + + let queue = channel + .queue_declare( + "", + QueueDeclareOptions { + exclusive: true, + ..Default::default() + }, + FieldTable::default(), + ) + .await?; + + futures::future::join_all(binding_keys.iter().map(|binding_key| { + channel.queue_bind( + queue.name().as_str(), + "topic_logs", + &binding_key, + QueueBindOptions::default(), + FieldTable::default(), + ) + })) + .await; + + let mut consumer = channel + .basic_consume( + queue.name().as_str(), + "consumer", + BasicConsumeOptions { + no_ack: true, + ..Default::default() + }, + FieldTable::default(), + ) + .await?; + + println!(" [*] Waiting for logs. To exit press CTRL+C"); + + + while let Some(delivery) = consumer.next().await { + if let Ok(delivery) = delivery { + println!( + " [x] {}:{:?}", + delivery.routing_key, + std::str::from_utf8(&delivery.data)? + ); + } + } + + + Ok(()) +} diff --git a/rust-lapin/src/bin/rpc_client.rs b/rust-lapin/src/bin/rpc_client.rs new file mode 100644 index 00000000..691a2cd9 --- /dev/null +++ b/rust-lapin/src/bin/rpc_client.rs @@ -0,0 +1,118 @@ +use futures::StreamExt; +use lapin::{ + options::*, types::FieldTable, types::ShortString, BasicProperties, Channel, Connection, + ConnectionProperties, Consumer, Queue, +}; +use std::convert::TryInto; +use std::fmt::Display; +use uuid::Uuid; + +#[derive(Debug)] +enum Error { + CannotDecodeReply, + NoReply, +} + +impl std::error::Error for Error {} + +impl Display for Error { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + match self { + Error::CannotDecodeReply => write!(f, "Cannot decode reply"), + Error::NoReply => write!(f, "No reply arrived"), + } + } +} + +struct FibonacciRpcClient { + conn: Connection, + channel: Channel, + callback_queue: Queue, + consumer: Consumer, + correlation_id: ShortString, +} + +impl FibonacciRpcClient { + async fn new() -> Result { + let addr = "amqp://127.0.0.1:5672"; + let conn = Connection::connect(addr, ConnectionProperties::default()).await?; + let channel = conn.create_channel().await?; + let callback_queue = channel + .queue_declare( + "", + QueueDeclareOptions { + exclusive: true, + ..Default::default() + }, + FieldTable::default(), + ) + .await?; + + let consumer = channel + .basic_consume( + callback_queue.name().as_str(), + "rpc_client", + BasicConsumeOptions { + no_ack: true, + ..Default::default() + }, + FieldTable::default(), + ) + .await?; + + let correlation_id = Uuid::new_v4().to_string().into(); + + Ok(Self { + conn, + channel, + callback_queue, + consumer, + correlation_id, + }) + } + + async fn call(&mut self, n: u64) -> Result> { + self.channel + .basic_publish( + "", + "rpc_queue", + BasicPublishOptions::default(), + &*n.to_le_bytes().to_vec(), + BasicProperties::default() + .with_reply_to(self.callback_queue.name().clone()) + .with_correlation_id(self.correlation_id.clone()), + ) + .await? + .await?; + + while let Some(delivery) = self.consumer.next().await { + if let Ok(delivery) = delivery { + if delivery.properties.correlation_id().as_ref() == Some(&self.correlation_id) { + return Ok(u64::from_le_bytes( + delivery + .data + .as_slice() + .try_into() + .map_err(|_| Error::CannotDecodeReply)?, + )); + } + } + } + + Err(Box::new(Error::NoReply)) + } + + async fn close(&self) -> Result<(), lapin::Error> { + self.conn.close(0, "").await + } +} + +#[tokio::main] +async fn main() -> Result<(), Box> { + let mut fibonacci_rpc = FibonacciRpcClient::new().await?; + println!(" [x] Requesting fib(30)"); + let response = fibonacci_rpc.call(30).await?; + println!(" [.] Got {}", response); + fibonacci_rpc.close().await?; + Ok(()) +} diff --git a/rust-lapin/src/bin/rpc_server.rs b/rust-lapin/src/bin/rpc_server.rs new file mode 100644 index 00000000..d7526082 --- /dev/null +++ b/rust-lapin/src/bin/rpc_server.rs @@ -0,0 +1,107 @@ +use std::convert::TryInto; +use std::fmt::Display; +use futures::StreamExt; +use lapin::{BasicProperties, Connection, ConnectionProperties, options::*, types::{AMQPValue, FieldTable}}; + +#[derive(Debug)] +enum Error { + CannotDecodeArg, + MissingReplyTo, + MissingCorrelationId, +} + +impl std::error::Error for Error {} + +impl Display for Error { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + match self { + Error::CannotDecodeArg => write!(f, "Cannot decode argument"), + Error::MissingReplyTo => write!(f, "Missing 'reply to' property"), + Error::MissingCorrelationId => write!(f, "Missing 'correlation id' property"), + } + } +} + +fn fib(n: u64) -> u64 { + if n < 2 { + n + } else { + fib(n - 1) + fib(n - 2) + } +} + +#[tokio::main] +async fn main() -> Result<(), Box> { + let addr = "amqp://127.0.0.1:5672"; + let conn = Connection::connect(addr, ConnectionProperties::default()).await?; + let channel = conn.create_channel().await?; + + let mut args = FieldTable::default(); + args.insert("x-queue-type".into(), AMQPValue::LongString("quorum".into())); + channel + .queue_declare( + "rpc_queue", + QueueDeclareOptions { durable: true, ..Default::default() }, + args, + ) + .await?; + + channel.basic_qos(1, BasicQosOptions::default()).await?; + + let mut consumer = channel + .basic_consume( + "rpc_queue", + "rpc_server", + BasicConsumeOptions::default(), + FieldTable::default(), + ) + .await?; + + println!(" [x] Awaiting RPC requests"); + + + while let Some(delivery) = consumer.next().await { + if let Ok(delivery) = delivery { + println!(" [x] Received {:?}", std::str::from_utf8(&delivery.data)?); + let n = u64::from_le_bytes( + delivery + .data + .as_slice() + .try_into() + .map_err(|_| Error::CannotDecodeArg)?, + ); + println!(" [.] fib({})", n); + let response = fib(n); + let payload = response.to_le_bytes(); + + let routing_key = delivery + .properties + .reply_to() + .as_ref() + .ok_or(Error::MissingReplyTo)? + .as_str(); + + let correlation_id = delivery + .properties + .correlation_id() + .clone() + .ok_or(Error::MissingCorrelationId)?; + + channel + .basic_publish( + "", + routing_key, + BasicPublishOptions::default(), + &payload, + BasicProperties::default().with_correlation_id(correlation_id), + ) + .await?; + + channel + .basic_ack(delivery.delivery_tag, BasicAckOptions::default()) + .await?; + } + } + + Ok(()) +} diff --git a/rust-lapin/src/bin/send.rs b/rust-lapin/src/bin/send.rs new file mode 100644 index 00000000..17205c4f --- /dev/null +++ b/rust-lapin/src/bin/send.rs @@ -0,0 +1,35 @@ +use lapin::{options::*, types::{AMQPValue, FieldTable}, BasicProperties, Connection, ConnectionProperties}; + +#[tokio::main] +async fn main() -> Result<(), Box> { + let addr = "amqp://127.0.0.1:5672"; + let conn = Connection::connect(addr, ConnectionProperties::default()).await?; + let channel = conn.create_channel().await?; + + let mut args = FieldTable::default(); + args.insert("x-queue-type".into(), AMQPValue::LongString("quorum".into())); + channel + .queue_declare( + "hello", + QueueDeclareOptions { durable: true, ..Default::default() }, + args, + ) + .await?; + + let payload = "Hello world!".as_bytes(); + channel + .basic_publish( + "", + "hello", + BasicPublishOptions::default(), + payload, + BasicProperties::default(), + ) + .await?; + + println!(" [x] Sent \"Hello World!\""); + + conn.close(0, "").await?; + + Ok(()) +} diff --git a/rust-lapin/src/bin/worker.rs b/rust-lapin/src/bin/worker.rs new file mode 100644 index 00000000..347e0fa9 --- /dev/null +++ b/rust-lapin/src/bin/worker.rs @@ -0,0 +1,46 @@ +use futures::StreamExt; +use std::thread; +use std::time::Duration; +use lapin::{Connection, ConnectionProperties, options::*, types::{AMQPValue, FieldTable}}; + +#[tokio::main] +async fn main() -> Result<(), Box> { + let addr = "amqp://127.0.0.1:5672"; + let conn = Connection::connect(addr, ConnectionProperties::default()).await?; + let channel = conn.create_channel().await?; + + let mut args = FieldTable::default(); + args.insert("x-queue-type".into(), AMQPValue::LongString("quorum".into())); + channel + .queue_declare( + "task_queue", + QueueDeclareOptions { durable: true, ..Default::default() }, + args, + ) + .await?; + + let mut consumer = channel + .basic_consume( + "task_queue", + "consumer", + BasicConsumeOptions::default(), + FieldTable::default(), + ) + .await + .expect("basic_consume"); + + println!(" [*] Waiting for messages. To exit press CTRL+C"); + + while let Some(delivery) = consumer.next().await { + if let Ok(delivery) = delivery { + println!(" [x] Received {:?}", std::str::from_utf8(&delivery.data)?); + thread::sleep(Duration::from_secs(delivery.data.len() as u64)); + println!(" [x] Done"); + delivery.ack(BasicAckOptions::default()) + .await + .expect("basic_ack"); + } + } + + Ok(()) +} diff --git a/rust-stream/Cargo.lock b/rust-stream/Cargo.lock new file mode 100644 index 00000000..f597b108 --- /dev/null +++ b/rust-stream/Cargo.lock @@ -0,0 +1,1718 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "anyhow" +version = "1.0.102" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" + +[[package]] +name = "async-trait" +version = "0.1.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + +[[package]] +name = "aws-lc-rs" +version = "1.16.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ec6fb3fe69024a75fa7e1bfb48aa6cf59706a101658ea01bfd33b2b248a038f" +dependencies = [ + "aws-lc-sys", + "zeroize", +] + +[[package]] +name = "aws-lc-sys" +version = "0.40.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f50037ee5e1e41e7b8f9d161680a725bd1626cb6f8c7e901f91f942850852fe7" +dependencies = [ + "cc", + "cmake", + "dunce", + "fs_extra", +] + +[[package]] +name = "bitflags" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3" + +[[package]] +name = "bumpalo" +version = "3.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "bytes" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" + +[[package]] +name = "cc" +version = "1.2.61" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d16d90359e986641506914ba71350897565610e87ce0ad9e6f28569db3dd5c6d" +dependencies = [ + "find-msvc-tools", + "jobserver", + "libc", + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "chacha20" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f8d983286843e49675a4b7a2d174efe136dc93a18d69130dd18198a6c167601" +dependencies = [ + "cfg-if", + "cpufeatures", + "rand_core", +] + +[[package]] +name = "chrono" +version = "0.4.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c673075a2e0e5f4a1dde27ce9dee1ea4558c7ffe648f576438a20ca1d2acc4b0" +dependencies = [ + "iana-time-zone", + "js-sys", + "num-traits", + "wasm-bindgen", + "windows-link", +] + +[[package]] +name = "cmake" +version = "0.1.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0f78a02292a74a88ac736019ab962ece0bc380e3f977bf72e376c5d78ff0678" +dependencies = [ + "cc", +] + +[[package]] +name = "convert_case" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "633458d4ef8c78b72454de2d54fd6ab2e60f9e02be22f3c6104cdc8a4e0fceb9" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "cpufeatures" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201" +dependencies = [ + "libc", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" + +[[package]] +name = "dashmap" +version = "6.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5041cc499144891f3790297212f32a74fb938e5136a14943f338ef9e0ae276cf" +dependencies = [ + "cfg-if", + "crossbeam-utils", + "hashbrown 0.14.5", + "lock_api", + "once_cell", + "parking_lot_core", +] + +[[package]] +name = "derive_more" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d751e9e49156b02b44f9c1815bcb94b984cdcc4396ecc32521c739452808b134" +dependencies = [ + "derive_more-impl", +] + +[[package]] +name = "derive_more-impl" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "799a97264921d8623a957f6c3b9011f3b5492f557bbb7a5a19b7fa6d06ba8dcb" +dependencies = [ + "convert_case", + "proc-macro2", + "quote", + "rustc_version", + "syn", + "unicode-xid", +] + +[[package]] +name = "displaydoc" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "dunce" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "find-msvc-tools" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" + +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + +[[package]] +name = "form_urlencoded" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "fs_extra" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" + +[[package]] +name = "futures" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b147ee9d1f6d097cef9ce628cd2ee62288d963e16fb287bd9286455b241382d" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" + +[[package]] +name = "futures-executor" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf29c38818342a3b26b5b923639e7b1f4a61fc5e76102d4b1981c6dc7a7579d" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718" + +[[package]] +name = "futures-macro" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "futures-sink" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893" + +[[package]] +name = "futures-task" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" + +[[package]] +name = "futures-util" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "slab", +] + +[[package]] +name = "getrandom" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "getrandom" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +dependencies = [ + "cfg-if", + "libc", + "r-efi 5.3.0", + "wasip2", +] + +[[package]] +name = "getrandom" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555" +dependencies = [ + "cfg-if", + "libc", + "r-efi 6.0.0", + "rand_core", + "wasip2", + "wasip3", +] + +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" + +[[package]] +name = "hashbrown" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +dependencies = [ + "foldhash", +] + +[[package]] +name = "hashbrown" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f467dd6dccf739c208452f8014c75c18bb8301b050ad1cfb27153803edb0f51" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "iana-time-zone" +version = "0.1.65" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "log", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "icu_collections" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2984d1cd16c883d7935b9e07e44071dca8d917fd52ecc02c04d5fa0b5a3f191c" +dependencies = [ + "displaydoc", + "potential_utf", + "utf8_iter", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locale_core" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92219b62b3e2b4d88ac5119f8904c10f8f61bf7e95b640d25ba3075e6cac2c29" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_normalizer" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c56e5ee99d6e3d33bd91c5d85458b6005a22140021cc324cea84dd0e72cff3b4" +dependencies = [ + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da3be0ae77ea334f4da67c12f149704f19f81d1adf7c51cf482943e84a2bad38" + +[[package]] +name = "icu_properties" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bee3b67d0ea5c2cca5003417989af8996f8604e34fb9ddf96208a033901e70de" +dependencies = [ + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "zerotrie", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e2bbb201e0c04f7b4b3e14382af113e17ba4f63e2c9d2ee626b720cbce54a14" + +[[package]] +name = "icu_provider" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "139c4cf31c8b5f33d7e199446eff9c1e02decfc2f0eec2c8d71f65befa45b421" +dependencies = [ + "displaydoc", + "icu_locale_core", + "writeable", + "yoke", + "zerofrom", + "zerotrie", + "zerovec", +] + +[[package]] +name = "id-arena" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" + +[[package]] +name = "idna" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + +[[package]] +name = "indexmap" +version = "2.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" +dependencies = [ + "equivalent", + "hashbrown 0.17.0", + "serde", + "serde_core", +] + +[[package]] +name = "itoa" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" + +[[package]] +name = "jobserver" +version = "0.1.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" +dependencies = [ + "getrandom 0.3.4", + "libc", +] + +[[package]] +name = "js-sys" +version = "0.3.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2964e92d1d9dc3364cae4d718d93f227e3abb088e747d92e0395bfdedf1c12ca" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "leb128fmt" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" + +[[package]] +name = "libc" +version = "0.2.186" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" + +[[package]] +name = "litemap" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0" + +[[package]] +name = "lock_api" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" + +[[package]] +name = "memchr" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" + +[[package]] +name = "mio" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50b7e5b27aa02a74bac8c3f23f448f8d87ff11f92d3aac1a6ed369ee08cc56c1" +dependencies = [ + "libc", + "wasi", + "windows-sys 0.61.2", +] + +[[package]] +name = "murmur3" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9252111cf132ba0929b6f8e030cac2a24b507f3a4d6db6fb2896f27b354c714b" + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "num_enum" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d0bca838442ec211fa11de3a8b0e0e8f3a4522575b5c4c06ed722e005036f26" +dependencies = [ + "num_enum_derive", + "rustversion", +] + +[[package]] +name = "num_enum_derive" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "680998035259dcfcafe653688bf2aa6d3e2dc05e98be6ab46afb089dc84f1df8" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "once_cell" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" + +[[package]] +name = "ordered-float" +version = "4.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7bb71e1b3fa6ca1c61f383464aaf2bb0e2f8e772a1f01d486832464de363b951" +dependencies = [ + "num-traits", +] + +[[package]] +name = "parking_lot" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-link", +] + +[[package]] +name = "percent-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" + +[[package]] +name = "pin-project" +version = "1.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1749c7ed4bcaf4c3d0a3efc28538844fb29bcdd7d2b67b2be7e20ba861ff517" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b20ed30f105399776b9c883e68e536ef602a16ae6f596d2c473591d6ad64c6" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" + +[[package]] +name = "potential_utf" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0103b1cef7ec0cf76490e969665504990193874ea05c85ff9bab8b911d0a0564" +dependencies = [ + "zerovec", +] + +[[package]] +name = "prettyplease" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" +dependencies = [ + "proc-macro2", + "syn", +] + +[[package]] +name = "proc-macro-crate" +version = "3.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f" +dependencies = [ + "toml_edit", +] + +[[package]] +name = "proc-macro2" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[package]] +name = "r-efi" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" + +[[package]] +name = "rabbitmq-stream-client" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7e766fd5cccfba5caa077d0b00de81840f0553a15325f22b59e87322f235d10" +dependencies = [ + "async-trait", + "bytes", + "dashmap", + "futures", + "murmur3", + "pin-project", + "rabbitmq-stream-protocol", + "rand", + "rustls-pemfile", + "thiserror", + "tokio", + "tokio-rustls", + "tokio-stream", + "tokio-util", + "tracing", + "url", +] + +[[package]] +name = "rabbitmq-stream-protocol" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97fedda81585bf2b5f8b91be49a592d2c91cb060c2c1927840617c52a67d1b7d" +dependencies = [ + "byteorder", + "chrono", + "derive_more", + "num_enum", + "ordered-float", + "uuid", +] + +[[package]] +name = "rand" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2e8e8bcc7961af1fdac401278c6a831614941f6164ee3bf4ce61b7edb162207" +dependencies = [ + "chacha20", + "getrandom 0.4.2", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69" + +[[package]] +name = "redox_syscall" +version = "0.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +dependencies = [ + "bitflags", +] + +[[package]] +name = "ring" +version = "0.17.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" +dependencies = [ + "cc", + "cfg-if", + "getrandom 0.2.17", + "libc", + "untrusted", + "windows-sys 0.52.0", +] + +[[package]] +name = "rust-stream" +version = "0.1.0" +dependencies = [ + "futures", + "rabbitmq-stream-client", + "rustls-webpki", + "tokio", +] + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + +[[package]] +name = "rustls" +version = "0.23.39" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c2c118cb077cca2822033836dfb1b975355dfb784b5e8da48f7b6c5db74e60e" +dependencies = [ + "aws-lc-rs", + "log", + "once_cell", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-pemfile" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "rustls-pki-types" +version = "1.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30a7197ae7eb376e574fe940d068c30fe0462554a3ddbe4eca7838e049c937a9" +dependencies = [ + "zeroize", +] + +[[package]] +name = "rustls-webpki" +version = "0.103.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61c429a8649f110dddef65e2a5ad240f747e85f7758a6bccc7e5777bd33f756e" +dependencies = [ + "aws-lc-rs", + "ring", + "rustls-pki-types", + "untrusted", +] + +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "semver" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" + +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.149" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" +dependencies = [ + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "signal-hook-registry" +version = "1.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" +dependencies = [ + "errno", + "libc", +] + +[[package]] +name = "slab" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" + +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" + +[[package]] +name = "socket2" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] +name = "syn" +version = "2.0.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "synstructure" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "thiserror" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tinystr" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8323304221c2a851516f22236c5722a72eaa19749016521d6dff0824447d96d" +dependencies = [ + "displaydoc", + "zerovec", +] + +[[package]] +name = "tokio" +version = "1.52.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b67dee974fe86fd92cc45b7a95fdd2f99a36a6d7b0d431a231178d3d670bbcc6" +dependencies = [ + "bytes", + "libc", + "mio", + "parking_lot", + "pin-project-lite", + "signal-hook-registry", + "socket2", + "tokio-macros", + "windows-sys 0.61.2", +] + +[[package]] +name = "tokio-macros" +version = "2.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "385a6cb71ab9ab790c5fe8d67f1645e6c450a7ce006a33de03daa956cf70a496" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tokio-rustls" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" +dependencies = [ + "rustls", + "tokio", +] + +[[package]] +name = "tokio-stream" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32da49809aab5c3bc678af03902d4ccddea2a87d028d86392a4b1560c6906c70" +dependencies = [ + "futures-core", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tokio-util" +version = "0.7.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "toml_datetime" +version = "1.1.1+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3165f65f62e28e0115a00b2ebdd37eb6f3b641855f9d636d3cd4103767159ad7" +dependencies = [ + "serde_core", +] + +[[package]] +name = "toml_edit" +version = "0.25.11+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b59c4d22ed448339746c59b905d24568fcbb3ab65a500494f7b8c3e97739f2b" +dependencies = [ + "indexmap", + "toml_datetime", + "toml_parser", + "winnow", +] + +[[package]] +name = "toml_parser" +version = "1.1.2+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2abe9b86193656635d2411dc43050282ca48aa31c2451210f4202550afb7526" +dependencies = [ + "winnow", +] + +[[package]] +name = "tracing" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" +dependencies = [ + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing-core" +version = "0.1.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" +dependencies = [ + "once_cell", +] + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "unicode-segmentation" +version = "1.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9629274872b2bfaf8d66f5f15725007f635594914870f65218920345aa11aa8c" + +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + +[[package]] +name = "url" +version = "2.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", + "serde", +] + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + +[[package]] +name = "uuid" +version = "1.23.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddd74a9687298c6858e9b88ec8935ec45d22e8fd5e6394fa1bd4e99a87789c76" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "wasip2" +version = "1.0.3+wasi-0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20064672db26d7cdc89c7798c48a0fdfac8213434a1186e5ef29fd560ae223d6" +dependencies = [ + "wit-bindgen 0.57.1", +] + +[[package]] +name = "wasip3" +version = "0.4.0+wasi-0.3.0-rc-2026-01-06" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" +dependencies = [ + "wit-bindgen 0.51.0", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.118" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf938a0bacb0469e83c1e148908bd7d5a6010354cf4fb73279b7447422e3a89" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.118" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eeff24f84126c0ec2db7a449f0c2ec963c6a49efe0698c4242929da037ca28ed" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.118" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d08065faf983b2b80a79fd87d8254c409281cf7de75fc4b773019824196c904" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.118" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fd04d9e306f1907bd13c6361b5c6bfc7b3b3c095ed3f8a9246390f8dbdee129" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "wasm-encoder" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319" +dependencies = [ + "leb128fmt", + "wasmparser", +] + +[[package]] +name = "wasm-metadata" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" +dependencies = [ + "anyhow", + "indexmap", + "wasm-encoder", + "wasmparser", +] + +[[package]] +name = "wasmparser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" +dependencies = [ + "bitflags", + "hashbrown 0.15.5", + "indexmap", + "semver", +] + +[[package]] +name = "windows-core" +version = "0.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-implement" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-interface" +version = "0.59.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-result" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "winnow" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ee1708bef14716a11bae175f579062d4554d95be2c6829f518df847b7b3fdd0" +dependencies = [ + "memchr", +] + +[[package]] +name = "wit-bindgen" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" +dependencies = [ + "wit-bindgen-rust-macro", +] + +[[package]] +name = "wit-bindgen" +version = "0.57.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" + +[[package]] +name = "wit-bindgen-core" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc" +dependencies = [ + "anyhow", + "heck", + "wit-parser", +] + +[[package]] +name = "wit-bindgen-rust" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" +dependencies = [ + "anyhow", + "heck", + "indexmap", + "prettyplease", + "syn", + "wasm-metadata", + "wit-bindgen-core", + "wit-component", +] + +[[package]] +name = "wit-bindgen-rust-macro" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a" +dependencies = [ + "anyhow", + "prettyplease", + "proc-macro2", + "quote", + "syn", + "wit-bindgen-core", + "wit-bindgen-rust", +] + +[[package]] +name = "wit-component" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" +dependencies = [ + "anyhow", + "bitflags", + "indexmap", + "log", + "serde", + "serde_derive", + "serde_json", + "wasm-encoder", + "wasm-metadata", + "wasmparser", + "wit-parser", +] + +[[package]] +name = "wit-parser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" +dependencies = [ + "anyhow", + "id-arena", + "indexmap", + "log", + "semver", + "serde", + "serde_derive", + "serde_json", + "unicode-xid", + "wasmparser", +] + +[[package]] +name = "writeable" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4" + +[[package]] +name = "yoke" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abe8c5fda708d9ca3df187cae8bfb9ceda00dd96231bed36e445a1a48e66f9ca" +dependencies = [ + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "zerofrom" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69faa1f2a1ea75661980b013019ed6687ed0e83d069bc1114e2cc74c6c04c4df" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "zeroize" +version = "1.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" + +[[package]] +name = "zerotrie" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f9152d31db0792fa83f70fb2f83148effb5c1f5b8c7686c3459e361d9bc20bf" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] + +[[package]] +name = "zerovec" +version = "0.11.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90f911cbc359ab6af17377d242225f4d75119aec87ea711a880987b18cd7b239" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zmij" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" diff --git a/rust-stream/Cargo.toml b/rust-stream/Cargo.toml new file mode 100644 index 00000000..13b9294f --- /dev/null +++ b/rust-stream/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "rust-stream" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +rabbitmq-stream-client = "0.11" +tokio = { version = "1.43.1", features = ["rt", "rt-multi-thread", "macros"] } +futures = "0.3.30" +rustls-webpki = "0.103.13" diff --git a/rust-stream/README.md b/rust-stream/README.md new file mode 100644 index 00000000..5ba82d6f --- /dev/null +++ b/rust-stream/README.md @@ -0,0 +1,27 @@ +# Rust code for RabbitMQ tutorials (using rabbitmq-stream-client) + +Here you can find the Rust code examples for [RabbitMQ +tutorials](https://www.rabbitmq.com/getstarted.html). + +The examples use [rabbitmq-stream-client](https://github.com/rabbitmq/rabbitmq-stream-rust-client) client library. + +These tutorials assume a RabbitMQ server node running locally using default ports and the [stream plugin enabled](https://www.rabbitmq.com/docs/stream#enabling-plugin). + +See [First Application With RabbitMQ Streams](https://www.rabbitmq.com/blog/2021/07/19/rabbitmq-streams-first-application), [Stream plugin documentation](https://www.rabbitmq.com/docs/stream) and [how to preconfigure plugins](https://www.rabbitmq.com/docs/plugins#enabled-plugins-file). + +## Requirements + +* [Rust and Cargo](https://www.rust-lang.org/tools/install) + +## Code +Each cargo command should be launched in a separate shell. + +#### [Tutorial one: "Hello World!"](https://www.rabbitmq.com/tutorials/tutorial-one-rust-stream.html) + + cargo run --bin receive + cargo run --bin send + +#### [Tutorial one: "Offset tracking!"](https://www.rabbitmq.com/tutorials/tutorial-two-rust-stream.html) + + cargo run --bin send_offset_tracking + cargo run --bin receive_offset_tracking \ No newline at end of file diff --git a/rust-stream/src/bin/receive.rs b/rust-stream/src/bin/receive.rs new file mode 100644 index 00000000..5a32fb2d --- /dev/null +++ b/rust-stream/src/bin/receive.rs @@ -0,0 +1,55 @@ +use std::io::stdin; +use rabbitmq_stream_client::error::StreamCreateError; +use rabbitmq_stream_client::types::{ByteCapacity, OffsetSpecification, ResponseCode}; +use futures::{StreamExt}; +use tokio::task; + +#[tokio::main] +async fn main() -> Result<(), Box> { + use rabbitmq_stream_client::Environment; + let environment = Environment::builder().build().await?; + let stream = "hello-rust-stream"; + let create_response = environment + .stream_creator() + .max_length(ByteCapacity::GB(5)) + .create(stream) + .await; + + if let Err(e) = create_response { + if let StreamCreateError::Create { stream, status } = e { + match status { + // we can ignore this error because the stream already exists + ResponseCode::StreamAlreadyExists => {} + err => { + println!("Error creating stream: {:?} {:?}", stream, err); + } + } + } + } + + let mut consumer = environment + .consumer() + .offset(OffsetSpecification::First) + .build(stream) + .await + .unwrap(); + + let handle = consumer.handle(); + task::spawn(async move { + while let Some(delivery) = consumer.next().await { + let d = delivery.unwrap(); + println!("Got message: {:#?} with offset: {}", + d.message().data().map(|data| String::from_utf8(data.to_vec()).unwrap()), + d.offset(),); + } + }); + + + println!("Press any key to close the consumer"); + _ = stdin().read_line(&mut "".to_string()); + + + handle.close().await?; + println!("consumer closed successfully"); + Ok(()) +} diff --git a/rust-stream/src/bin/receive_offset_tracking.rs b/rust-stream/src/bin/receive_offset_tracking.rs new file mode 100644 index 00000000..3774081a --- /dev/null +++ b/rust-stream/src/bin/receive_offset_tracking.rs @@ -0,0 +1,85 @@ +use futures::StreamExt; +use rabbitmq_stream_client::error::StreamCreateError; +use rabbitmq_stream_client::types::{ByteCapacity, OffsetSpecification, ResponseCode}; + +#[tokio::main] +async fn main() -> Result<(), Box> { + use rabbitmq_stream_client::Environment; + let environment = Environment::builder().build().await?; + let stream = "stream-offset-tracking-rust"; + let mut first_offset: Option = None; + let mut last_offset: Option = None; + let create_response = environment + .stream_creator() + .max_length(ByteCapacity::GB(2)) + .create(stream) + .await; + + if let Err(e) = create_response { + if let StreamCreateError::Create { stream, status } = e { + match status { + // we can ignore this error because the stream already exists + ResponseCode::StreamAlreadyExists => {} + err => { + println!("Error creating stream: {:?} {:?}", stream, err); + std::process::exit(1); + } + } + } + } + + let mut consumer = environment + .consumer() + .name("consumer-1") + .offset(OffsetSpecification::First) + .build(stream) + .await + .unwrap(); + + println!("Started consuming"); + + let mut stored_offset: u64 = consumer.query_offset().await.unwrap_or_else(|_| 0); + + if stored_offset > 0 { + stored_offset += 1; + } + consumer = environment + .consumer() + .name("consumer-1") + .offset(OffsetSpecification::Offset(stored_offset)) + .build(stream) + .await + .unwrap(); + + let mut received_messages: i64 = -1; + while let Some(delivery) = consumer.next().await { + let d = delivery.unwrap(); + + if !first_offset.is_some() { + println!("First message received"); + first_offset = Some(d.offset()); + } + received_messages = received_messages + 1; + if received_messages % 10 == 0 + || String::from_utf8_lossy(d.message().data().unwrap()).contains("marker") + { + let _ = consumer + .store_offset(d.offset()) + .await + .unwrap_or_else(|e| println!("Err: {}", e)); + if String::from_utf8_lossy(d.message().data().unwrap()).contains("marker") { + last_offset = Some(d.offset()); + let handle = consumer.handle(); + _ = handle.close().await; + break; + } + } + } + + if first_offset.is_some() { + println!( + "Done consuming first_offset: {:?} last_offset: {:?} ", first_offset.unwrap(), last_offset.unwrap()) + } + + Ok(()) +} diff --git a/rust-stream/src/bin/send.rs b/rust-stream/src/bin/send.rs new file mode 100644 index 00000000..a842415c --- /dev/null +++ b/rust-stream/src/bin/send.rs @@ -0,0 +1,35 @@ +use rabbitmq_stream_client::error::StreamCreateError; +use rabbitmq_stream_client::types::{ByteCapacity, Message, ResponseCode}; + +#[tokio::main] +async fn main() -> Result<(), Box> { + use rabbitmq_stream_client::Environment; + let environment = Environment::builder().build().await?; + let stream = "hello-rust-stream"; + let create_response = environment + .stream_creator() + .max_length(ByteCapacity::GB(5)) + .create(stream) + .await; + + if let Err(e) = create_response { + if let StreamCreateError::Create { stream, status } = e { + match status { + // we can ignore this error because the stream already exists + ResponseCode::StreamAlreadyExists => {} + err => { + println!("Error creating stream: {:?} {:?}", stream, err); + } + } + } + } + + let producer = environment.producer().build(stream).await?; + + producer + .send_with_confirm(Message::builder().body("Hello, World!").build()) + .await?; + println!("Sent message to stream: {}", stream); + producer.close().await?; + Ok(()) +} diff --git a/rust-stream/src/bin/send_offset_tracking.rs b/rust-stream/src/bin/send_offset_tracking.rs new file mode 100644 index 00000000..d40be89e --- /dev/null +++ b/rust-stream/src/bin/send_offset_tracking.rs @@ -0,0 +1,66 @@ +use rabbitmq_stream_client::error::StreamCreateError; +use rabbitmq_stream_client::types::{ByteCapacity, Message, ResponseCode}; +use std::sync::atomic::{AtomicU32, Ordering}; +use std::sync::Arc; +use tokio::sync::Notify; + +#[tokio::main] +async fn main() -> Result<(), Box> { + use rabbitmq_stream_client::Environment; + let environment = Environment::builder().build().await?; + let stream = "stream-offset-tracking-rust"; + let message_count = 100; + let confirmed_messages = Arc::new(AtomicU32::new(0)); + let notify_on_send = Arc::new(Notify::new()); + + let create_response = environment + .stream_creator() + .max_length(ByteCapacity::GB(2)) + .create(stream) + .await; + + if let Err(e) = create_response { + if let StreamCreateError::Create { stream, status } = e { + match status { + // we can ignore this error because the stream already exists + ResponseCode::StreamAlreadyExists => {} + err => { + println!("Error creating stream: {:?} {:?}", stream, err); + std::process::exit(1); + } + } + } + } + + println!("Publishing {:?} messages", message_count); + + let producer = environment.producer().build(stream).await?; + + for i in 0..message_count { + let msg; + if i < message_count - 1 { + msg = Message::builder().body(format!("hello{}", i)).build(); + } else { + msg = Message::builder().body(format!("marker{}", i)).build(); + }; + + let counter = confirmed_messages.clone(); + let notifier = notify_on_send.clone(); + producer + .send(msg, move |_| { + let inner_counter = counter.clone(); + let inner_notifier = notifier.clone(); + async move { + if inner_counter.fetch_add(1, Ordering::Relaxed) == message_count - 1 { + inner_notifier.notify_one(); + } + } + }) + .await?; + } + + notify_on_send.notified().await; + println!("Messages confirmed: True"); + producer.close().await?; + Ok(()) +} diff --git a/scala/.mvn/wrapper/MavenWrapperDownloader.java b/scala/.mvn/wrapper/MavenWrapperDownloader.java new file mode 100755 index 00000000..2e394d5b --- /dev/null +++ b/scala/.mvn/wrapper/MavenWrapperDownloader.java @@ -0,0 +1,110 @@ +/* +Licensed to the Apache Software Foundation (ASF) under one +or more contributor license agreements. See the NOTICE file +distributed with this work for additional information +regarding copyright ownership. The ASF licenses this file +to you under the Apache License, Version 2.0 (the +"License"); you may not use this file except in compliance +with the License. You may obtain a copy of the License at + + https://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, +software distributed under the License is distributed on an +"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +KIND, either express or implied. See the License for the +specific language governing permissions and limitations +under the License. +*/ + +import java.net.*; +import java.io.*; +import java.nio.channels.*; +import java.util.Properties; + +public class MavenWrapperDownloader { + + /** + * Default URL to download the maven-wrapper.jar from, if no 'downloadUrl' is provided. + */ + private static final String DEFAULT_DOWNLOAD_URL = + "https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.4.2/maven-wrapper-0.4.2.jar"; + + /** + * Path to the maven-wrapper.properties file, which might contain a downloadUrl property to + * use instead of the default one. + */ + private static final String MAVEN_WRAPPER_PROPERTIES_PATH = + ".mvn/wrapper/maven-wrapper.properties"; + + /** + * Path where the maven-wrapper.jar will be saved to. + */ + private static final String MAVEN_WRAPPER_JAR_PATH = + ".mvn/wrapper/maven-wrapper.jar"; + + /** + * Name of the property which should be used to override the default download url for the wrapper. + */ + private static final String PROPERTY_NAME_WRAPPER_URL = "wrapperUrl"; + + public static void main(String args[]) { + System.out.println("- Downloader started"); + File baseDirectory = new File(args[0]); + System.out.println("- Using base directory: " + baseDirectory.getAbsolutePath()); + + // If the maven-wrapper.properties exists, read it and check if it contains a custom + // wrapperUrl parameter. + File mavenWrapperPropertyFile = new File(baseDirectory, MAVEN_WRAPPER_PROPERTIES_PATH); + String url = DEFAULT_DOWNLOAD_URL; + if(mavenWrapperPropertyFile.exists()) { + FileInputStream mavenWrapperPropertyFileInputStream = null; + try { + mavenWrapperPropertyFileInputStream = new FileInputStream(mavenWrapperPropertyFile); + Properties mavenWrapperProperties = new Properties(); + mavenWrapperProperties.load(mavenWrapperPropertyFileInputStream); + url = mavenWrapperProperties.getProperty(PROPERTY_NAME_WRAPPER_URL, url); + } catch (IOException e) { + System.out.println("- ERROR loading '" + MAVEN_WRAPPER_PROPERTIES_PATH + "'"); + } finally { + try { + if(mavenWrapperPropertyFileInputStream != null) { + mavenWrapperPropertyFileInputStream.close(); + } + } catch (IOException e) { + // Ignore ... + } + } + } + System.out.println("- Downloading from: : " + url); + + File outputFile = new File(baseDirectory.getAbsolutePath(), MAVEN_WRAPPER_JAR_PATH); + if(!outputFile.getParentFile().exists()) { + if(!outputFile.getParentFile().mkdirs()) { + System.out.println( + "- ERROR creating output direcrory '" + outputFile.getParentFile().getAbsolutePath() + "'"); + } + } + System.out.println("- Downloading to: " + outputFile.getAbsolutePath()); + try { + downloadFileFromURL(url, outputFile); + System.out.println("Done"); + System.exit(0); + } catch (Throwable e) { + System.out.println("- Error downloading"); + e.printStackTrace(); + System.exit(1); + } + } + + private static void downloadFileFromURL(String urlString, File destination) throws Exception { + URL website = new URL(urlString); + ReadableByteChannel rbc; + rbc = Channels.newChannel(website.openStream()); + FileOutputStream fos = new FileOutputStream(destination); + fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE); + fos.close(); + rbc.close(); + } + +} diff --git a/scala/.mvn/wrapper/maven-wrapper.jar b/scala/.mvn/wrapper/maven-wrapper.jar new file mode 100755 index 00000000..01e67997 Binary files /dev/null and b/scala/.mvn/wrapper/maven-wrapper.jar differ diff --git a/scala/.mvn/wrapper/maven-wrapper.properties b/scala/.mvn/wrapper/maven-wrapper.properties new file mode 100755 index 00000000..00d32aab --- /dev/null +++ b/scala/.mvn/wrapper/maven-wrapper.properties @@ -0,0 +1 @@ +distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.5.4/apache-maven-3.5.4-bin.zip \ No newline at end of file diff --git a/scala/README.md b/scala/README.md index 38b87f2f..453e759c 100644 --- a/scala/README.md +++ b/scala/README.md @@ -8,7 +8,7 @@ JDK 6 and doesn't have a lambda-friendly API. ## Compiling the Code - mvn compile + ./mvnw compile ## Running Examples @@ -17,41 +17,60 @@ JDK 6 and doesn't have a lambda-friendly API. Execute the following command to receive a hello world: - mvn exec:java -Dexec.mainClass="Recv" + ./mvnw exec:java -Dexec.mainClass="Recv" Execute the following in a separate shell to send a hello world: - mvn exec:java -Dexec.mainClass="Send" + ./mvnw exec:java -Dexec.mainClass="Send" ### Work Queues -Send a message which will be finshed immediately: +Send a message which will be finished immediately: - mvn exec:java -Dexec.mainClass="NewTask" + ./mvnw exec:java -Dexec.mainClass="NewTask" Send a message which need some second to execute each . is one second. - mvn exec:java -Dexec.mainClass="NewTask" -Dexec.args="rabbit1 ...." + ./mvnw exec:java -Dexec.mainClass="NewTask" -Dexec.args="rabbit1 ...." To start a worker (run in a separate shell): - mvn exec:java -Dexec.mainClass="Worker" + ./mvnw exec:java -Dexec.mainClass="Worker" Add more workers to the same queue, message will be distributed in the round robin manner. ### Publish and Subscriber - mvn exec:java -Dexec.mainClass="EmitLog" -Dexec.args="rabbit1 msg1" + ./mvnw exec:java -Dexec.mainClass="ReceiveLogs" - mvn exec:java -Dexec.mainClass="ReceiveLogs" + ./mvnw exec:java -Dexec.mainClass="EmitLog" -Dexec.args="rabbit1 msg1" + + +### Routing + + ./mvnw exec:java -Dexec.mainClass="ReceiveLogsDirect" -Dexec.args="info warning error" + + ./mvnw exec:java -Dexec.mainClass="EmitLogDirect" -Dexec.args="error Run. Run. Or it will explode." + +### Topics + + ./mvnw exec:java -Dexec.mainClass="ReceiveLogsTopic" -Dexec.args="#" + + ./mvnw exec:java -Dexec.mainClass="ReceiveLogsTopic" -Dexec.args="kern.*" + + ./mvnw exec:java -Dexec.mainClass="ReceiveLogsTopic" -Dexec.args="*.critical" + + ./mvnw exec:java -Dexec.mainClass="ReceiveLogsTopic" -Dexec.args="kern.* *.critical" + + ./mvnw exec:java -Dexec.mainClass="EmitLogTopic" -Dexec.args="kern.critical A critical kernel error" ### RPC In one shell: - mvn exec:java -Dexec.mainClass="RPCServer" + ./mvnw exec:java -Dexec.mainClass="RPCServer" In another shell: - mvn exec:java -Dexec.mainClass="RPCClient" + ./mvnw exec:java -Dexec.mainClass="RPCClient" diff --git a/scala/mvnw b/scala/mvnw new file mode 100755 index 00000000..8b9da3b8 --- /dev/null +++ b/scala/mvnw @@ -0,0 +1,286 @@ +#!/bin/sh +# ---------------------------------------------------------------------------- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# ---------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------- +# Maven2 Start Up Batch script +# +# Required ENV vars: +# ------------------ +# JAVA_HOME - location of a JDK home dir +# +# Optional ENV vars +# ----------------- +# M2_HOME - location of maven2's installed home dir +# MAVEN_OPTS - parameters passed to the Java VM when running Maven +# e.g. to debug Maven itself, use +# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +# MAVEN_SKIP_RC - flag to disable loading of mavenrc files +# ---------------------------------------------------------------------------- + +if [ -z "$MAVEN_SKIP_RC" ] ; then + + if [ -f /etc/mavenrc ] ; then + . /etc/mavenrc + fi + + if [ -f "$HOME/.mavenrc" ] ; then + . "$HOME/.mavenrc" + fi + +fi + +# OS specific support. $var _must_ be set to either true or false. +cygwin=false; +darwin=false; +mingw=false +case "`uname`" in + CYGWIN*) cygwin=true ;; + MINGW*) mingw=true;; + Darwin*) darwin=true + # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home + # See https://developer.apple.com/library/mac/qa/qa1170/_index.html + if [ -z "$JAVA_HOME" ]; then + if [ -x "/usr/libexec/java_home" ]; then + export JAVA_HOME="`/usr/libexec/java_home`" + else + export JAVA_HOME="/Library/Java/Home" + fi + fi + ;; +esac + +if [ -z "$JAVA_HOME" ] ; then + if [ -r /etc/gentoo-release ] ; then + JAVA_HOME=`java-config --jre-home` + fi +fi + +if [ -z "$M2_HOME" ] ; then + ## resolve links - $0 may be a link to maven's home + PRG="$0" + + # need this for relative symlinks + while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG="`dirname "$PRG"`/$link" + fi + done + + saveddir=`pwd` + + M2_HOME=`dirname "$PRG"`/.. + + # make it fully qualified + M2_HOME=`cd "$M2_HOME" && pwd` + + cd "$saveddir" + # echo Using m2 at $M2_HOME +fi + +# For Cygwin, ensure paths are in UNIX format before anything is touched +if $cygwin ; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --unix "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --unix "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --unix "$CLASSPATH"` +fi + +# For Mingw, ensure paths are in UNIX format before anything is touched +if $mingw ; then + [ -n "$M2_HOME" ] && + M2_HOME="`(cd "$M2_HOME"; pwd)`" + [ -n "$JAVA_HOME" ] && + JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" + # TODO classpath? +fi + +if [ -z "$JAVA_HOME" ]; then + javaExecutable="`which javac`" + if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then + # readlink(1) is not available as standard on Solaris 10. + readLink=`which readlink` + if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then + if $darwin ; then + javaHome="`dirname \"$javaExecutable\"`" + javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" + else + javaExecutable="`readlink -f \"$javaExecutable\"`" + fi + javaHome="`dirname \"$javaExecutable\"`" + javaHome=`expr "$javaHome" : '\(.*\)/bin'` + JAVA_HOME="$javaHome" + export JAVA_HOME + fi + fi +fi + +if [ -z "$JAVACMD" ] ; then + if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + else + JAVACMD="`which java`" + fi +fi + +if [ ! -x "$JAVACMD" ] ; then + echo "Error: JAVA_HOME is not defined correctly." >&2 + echo " We cannot execute $JAVACMD" >&2 + exit 1 +fi + +if [ -z "$JAVA_HOME" ] ; then + echo "Warning: JAVA_HOME environment variable is not set." +fi + +CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher + +# traverses directory structure from process work directory to filesystem root +# first directory with .mvn subdirectory is considered project base directory +find_maven_basedir() { + + if [ -z "$1" ] + then + echo "Path not specified to find_maven_basedir" + return 1 + fi + + basedir="$1" + wdir="$1" + while [ "$wdir" != '/' ] ; do + if [ -d "$wdir"/.mvn ] ; then + basedir=$wdir + break + fi + # workaround for JBEAP-8937 (on Solaris 10/Sparc) + if [ -d "${wdir}" ]; then + wdir=`cd "$wdir/.."; pwd` + fi + # end of workaround + done + echo "${basedir}" +} + +# concatenates all lines of a file +concat_lines() { + if [ -f "$1" ]; then + echo "$(tr -s '\n' ' ' < "$1")" + fi +} + +BASE_DIR=`find_maven_basedir "$(pwd)"` +if [ -z "$BASE_DIR" ]; then + exit 1; +fi + +########################################################################################## +# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +# This allows using the maven wrapper in projects that prohibit checking in binary data. +########################################################################################## +if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found .mvn/wrapper/maven-wrapper.jar" + fi +else + if [ "$MVNW_VERBOSE" = true ]; then + echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..." + fi + jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.4.2/maven-wrapper-0.4.2.jar" + while IFS="=" read key value; do + case "$key" in (wrapperUrl) jarUrl="$value"; break ;; + esac + done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties" + if [ "$MVNW_VERBOSE" = true ]; then + echo "Downloading from: $jarUrl" + fi + wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" + + if command -v wget > /dev/null; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found wget ... using wget" + fi + wget "$jarUrl" -O "$wrapperJarPath" + elif command -v curl > /dev/null; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found curl ... using curl" + fi + curl -o "$wrapperJarPath" "$jarUrl" + else + if [ "$MVNW_VERBOSE" = true ]; then + echo "Falling back to using Java to download" + fi + javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java" + if [ -e "$javaClass" ]; then + if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then + if [ "$MVNW_VERBOSE" = true ]; then + echo " - Compiling MavenWrapperDownloader.java ..." + fi + # Compiling the Java class + ("$JAVA_HOME/bin/javac" "$javaClass") + fi + if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then + # Running the downloader + if [ "$MVNW_VERBOSE" = true ]; then + echo " - Running MavenWrapperDownloader.java ..." + fi + ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR") + fi + fi + fi +fi +########################################################################################## +# End of extension +########################################################################################## + +export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} +if [ "$MVNW_VERBOSE" = true ]; then + echo $MAVEN_PROJECTBASEDIR +fi +MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" + +# For Cygwin, switch paths to Windows format before running java +if $cygwin; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --path --windows "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --windows "$CLASSPATH"` + [ -n "$MAVEN_PROJECTBASEDIR" ] && + MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"` +fi + +WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +exec "$JAVACMD" \ + $MAVEN_OPTS \ + -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ + "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ + ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" diff --git a/scala/mvnw.cmd b/scala/mvnw.cmd new file mode 100755 index 00000000..a5284c79 --- /dev/null +++ b/scala/mvnw.cmd @@ -0,0 +1,161 @@ +@REM ---------------------------------------------------------------------------- +@REM Licensed to the Apache Software Foundation (ASF) under one +@REM or more contributor license agreements. See the NOTICE file +@REM distributed with this work for additional information +@REM regarding copyright ownership. The ASF licenses this file +@REM to you under the Apache License, Version 2.0 (the +@REM "License"); you may not use this file except in compliance +@REM with the License. You may obtain a copy of the License at +@REM +@REM https://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, +@REM software distributed under the License is distributed on an +@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +@REM KIND, either express or implied. See the License for the +@REM specific language governing permissions and limitations +@REM under the License. +@REM ---------------------------------------------------------------------------- + +@REM ---------------------------------------------------------------------------- +@REM Maven2 Start Up Batch script +@REM +@REM Required ENV vars: +@REM JAVA_HOME - location of a JDK home dir +@REM +@REM Optional ENV vars +@REM M2_HOME - location of maven2's installed home dir +@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands +@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a key stroke before ending +@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven +@REM e.g. to debug Maven itself, use +@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files +@REM ---------------------------------------------------------------------------- + +@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' +@echo off +@REM set title of command window +title %0 +@REM enable echoing my setting MAVEN_BATCH_ECHO to 'on' +@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% + +@REM set %HOME% to equivalent of $HOME +if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") + +@REM Execute a user defined script before this one +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre +@REM check for pre script, once with legacy .bat ending and once with .cmd ending +if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat" +if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd" +:skipRcPre + +@setlocal + +set ERROR_CODE=0 + +@REM To isolate internal variables from possible post scripts, we use another setlocal +@setlocal + +@REM ==== START VALIDATION ==== +if not "%JAVA_HOME%" == "" goto OkJHome + +echo. +echo Error: JAVA_HOME not found in your environment. >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +:OkJHome +if exist "%JAVA_HOME%\bin\java.exe" goto init + +echo. +echo Error: JAVA_HOME is set to an invalid directory. >&2 +echo JAVA_HOME = "%JAVA_HOME%" >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +@REM ==== END VALIDATION ==== + +:init + +@REM Find the project base dir, i.e. the directory that contains the folder ".mvn". +@REM Fallback to current working directory if not found. + +set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% +IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir + +set EXEC_DIR=%CD% +set WDIR=%EXEC_DIR% +:findBaseDir +IF EXIST "%WDIR%"\.mvn goto baseDirFound +cd .. +IF "%WDIR%"=="%CD%" goto baseDirNotFound +set WDIR=%CD% +goto findBaseDir + +:baseDirFound +set MAVEN_PROJECTBASEDIR=%WDIR% +cd "%EXEC_DIR%" +goto endDetectBaseDir + +:baseDirNotFound +set MAVEN_PROJECTBASEDIR=%EXEC_DIR% +cd "%EXEC_DIR%" + +:endDetectBaseDir + +IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig + +@setlocal EnableExtensions EnableDelayedExpansion +for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a +@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% + +:endReadAdditionalConfig + +SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" +set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" +set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.4.2/maven-wrapper-0.4.2.jar" +FOR /F "tokens=1,2 delims==" %%A IN (%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties) DO ( + IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B +) + +@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +@REM This allows using the maven wrapper in projects that prohibit checking in binary data. +if exist %WRAPPER_JAR% ( + echo Found %WRAPPER_JAR% +) else ( + echo Couldn't find %WRAPPER_JAR%, downloading it ... + echo Downloading from: %DOWNLOAD_URL% + powershell -Command "(New-Object Net.WebClient).DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')" + echo Finished downloading %WRAPPER_JAR% +) +@REM End of extension + +%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* +if ERRORLEVEL 1 goto error +goto end + +:error +set ERROR_CODE=1 + +:end +@endlocal & set ERROR_CODE=%ERROR_CODE% + +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost +@REM check for post script, once with legacy .bat ending and once with .cmd ending +if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat" +if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd" +:skipRcPost + +@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' +if "%MAVEN_BATCH_PAUSE%" == "on" pause + +if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE% + +exit /B %ERROR_CODE% diff --git a/scala/pom.xml b/scala/pom.xml index 297c1b69..5ef6561e 100644 --- a/scala/pom.xml +++ b/scala/pom.xml @@ -1,91 +1,68 @@ - - 4.0.0 - explore - rabbitmq - 0.1-SNAPSHOT - - 2.11.8 - + + 4.0.0 + com.rabbitmq + rabbitmq-tutorial-scala + 0.1-SNAPSHOT + + UTF-8 + 2.12.7 + (5.5.0,) + 1.7.25 + 3.4.4 + 3.8.0 + - - - scala-tools.org - Scala-Tools Maven2 Repository - http://scala-tools.org/repo-releases - - - - - scala-tools.org - Scala-Tools Maven2 Repository - http://scala-tools.org/repo-releases - - + + + org.scala-lang + scala-library + ${scala.version} + + + com.rabbitmq + amqp-client + ${amqp-client.version} + + + org.slf4j + slf4j-simple + ${slf4j.version} + + - - - org.scala-lang - scala-library - ${scala.version} - - - com.rabbitmq - amqp-client - 4.0.0 - - + + src/main/scala + + + net.alchim31.maven + scala-maven-plugin + ${scala-maven-plugin.version} + + + + compile + + + + -dependencyfile + ${project.build.directory}/.scala_dependencies + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + ${maven-compiler-plugin.version} + + 1.8 + 1.8 + + + + - - src/main/scala - src/test/scala - - - org.scala-tools - maven-scala-plugin - - - - compile - testCompile - - - - - ${scala.version} - - -target:jvm-1.5 - - - - - org.apache.maven.plugins - maven-eclipse-plugin - - true - - ch.epfl.lamp.sdt.core.scalabuilder - - - ch.epfl.lamp.sdt.core.scalanature - - - org.eclipse.jdt.launching.JRE_CONTAINER - ch.epfl.lamp.sdt.launching.SCALA_CONTAINER - - - - - - - - - org.scala-tools - maven-scala-plugin - - ${scala.version} - - - - diff --git a/scala/src/main/scala/EmitLog.scala b/scala/src/main/scala/EmitLog.scala index 0628f71a..a0c38f8b 100644 --- a/scala/src/main/scala/EmitLog.scala +++ b/scala/src/main/scala/EmitLog.scala @@ -10,25 +10,11 @@ object EmitLog { val connection = factory.newConnection() val channel = connection.createChannel() channel.exchangeDeclare(EXCHANGE_NAME, "fanout") - val message = getMessage(argv) + val message = if (argv.length < 1) "Hello World!" else argv.mkString(" ") channel.basicPublish(EXCHANGE_NAME, "", null, message.getBytes("UTF-8")) println(" [x] Sent '" + message + "'") channel.close() connection.close() } - private def getMessage(strings: Array[String]): String = { - if (strings.length < 1) return "info: Hello World!" - joinStrings(strings, " ") - } - - private def joinStrings(strings: Array[String], delimiter: String): String = { - val length = strings.length - if (length == 0) return "" - val words = new StringBuilder(strings(0)) - for (i <- 1 until length) { - words.append(delimiter).append(strings(i)) - } - words.toString - } } diff --git a/scala/src/main/scala/EmitLogHeader.scala b/scala/src/main/scala/EmitLogHeader.scala index 7bc4da58..242b62a7 100644 --- a/scala/src/main/scala/EmitLogHeader.scala +++ b/scala/src/main/scala/EmitLogHeader.scala @@ -33,5 +33,7 @@ object EmitLogHeader { val theProps = builder.build() channel.basicPublish(EXCHANGE_NAME, routingKey, theProps, message.getBytes("UTF-8")) println(" [x] Sent message: '" + message + "'") + channel.close() + connection.close() } } diff --git a/scala/src/main/scala/EmitLogTopic.scala b/scala/src/main/scala/EmitLogTopic.scala index e4a770b8..7b35e379 100644 --- a/scala/src/main/scala/EmitLogTopic.scala +++ b/scala/src/main/scala/EmitLogTopic.scala @@ -1,33 +1,21 @@ -import com.rabbitmq.client.{Channel, Connection, ConnectionFactory} +import com.rabbitmq.client.ConnectionFactory object EmitLogTopic { private val EXCHANGE_NAME = "topic_logs" def main(argv: Array[String]) { - var connection: Connection = null - var channel: Channel = null - try { - val factory = new ConnectionFactory() - factory.setHost("localhost") - connection = factory.newConnection() - channel = connection.createChannel() - channel.exchangeDeclare(EXCHANGE_NAME, "topic") - val routingKey = getRouting(argv) - val message = getMessage(argv) - channel.basicPublish(EXCHANGE_NAME, routingKey, null, message.getBytes("UTF-8")) - println(" [x] Sent '" + routingKey + "':'" + message + "'") - } catch { - case e: Exception => e.printStackTrace() - } finally { - if (connection != null) { - try { - connection.close() - } catch { - case ignore: Exception => - } - } - } + val factory = new ConnectionFactory() + factory.setHost("localhost") + val connection = factory.newConnection() + val channel = connection.createChannel() + channel.exchangeDeclare(EXCHANGE_NAME, "topic") + val routingKey = getRouting(argv) + val message = getMessage(argv) + channel.basicPublish(EXCHANGE_NAME, routingKey, null, message.getBytes("UTF-8")) + println(" [x] Sent '" + routingKey + "':'" + message + "'") + channel.close() + connection.close() } private def getRouting(strings: Array[String]): String = { diff --git a/scala/src/main/scala/NewTask.scala b/scala/src/main/scala/NewTask.scala index bfed1fcf..b6d39db1 100644 --- a/scala/src/main/scala/NewTask.scala +++ b/scala/src/main/scala/NewTask.scala @@ -11,25 +11,10 @@ object NewTask { val connection = factory.newConnection() val channel = connection.createChannel() channel.queueDeclare(TASK_QUEUE_NAME, true, false, false, null) - val message = getMessage(argv) + val message = if (argv.length < 1) "Hello World!" else argv.mkString(" ") channel.basicPublish("", TASK_QUEUE_NAME, MessageProperties.PERSISTENT_TEXT_PLAIN, message.getBytes("UTF-8")) println(" [x] Sent '" + message + "'") channel.close() connection.close() } - - private def getMessage(strings: Array[String]): String = { - if (strings.length < 1) return "Hello World!" - joinStrings(strings, " ") - } - - private def joinStrings(strings: Array[String], delimiter: String): String = { - val length = strings.length - if (length == 0) return "" - val words = new StringBuilder(strings(0)) - for (i <- 1 until length) { - words.append(delimiter).append(strings(i)) - } - words.toString - } } diff --git a/scala/src/main/scala/RPCClient.scala b/scala/src/main/scala/RPCClient.scala index dbd16770..357ac319 100644 --- a/scala/src/main/scala/RPCClient.scala +++ b/scala/src/main/scala/RPCClient.scala @@ -1,7 +1,22 @@ import java.util.UUID +import java.util.concurrent.{ArrayBlockingQueue, BlockingQueue} import com.rabbitmq.client.AMQP.BasicProperties -import com.rabbitmq.client.{Channel, Connection, ConnectionFactory, QueueingConsumer} +import com.rabbitmq.client._ + +class ResponseCallback(val corrId: String) extends DeliverCallback { + val response: BlockingQueue[String] = new ArrayBlockingQueue[String](1) + + override def handle(consumerTag: String, message: Delivery): Unit = { + if (message.getProperties.getCorrelationId.equals(corrId)) { + response.offer(new String(message.getBody, "UTF-8")) + } + } + + def take(): String = { + response.take(); + } +} class RPCClient(host: String) { @@ -12,24 +27,18 @@ class RPCClient(host: String) { val channel: Channel = connection.createChannel() val requestQueueName: String = "rpc_queue" val replyQueueName: String = channel.queueDeclare().getQueue - val consumer: QueueingConsumer = new QueueingConsumer(channel) - - channel.basicConsume(replyQueueName, true, consumer) def call(message: String): String = { - var response: String = null val corrId = UUID.randomUUID().toString val props = new BasicProperties.Builder().correlationId(corrId) .replyTo(replyQueueName) .build() channel.basicPublish("", requestQueueName, props, message.getBytes("UTF-8")) - while (response == null) { - val delivery = consumer.nextDelivery() - if (delivery.getProperties.getCorrelationId == corrId) { - response = new String(delivery.getBody, "UTF-8") - } - } - response + + val responseCallback = new ResponseCallback(corrId) + channel.basicConsume(replyQueueName, true, responseCallback, _ => { }) + + responseCallback.take() } def close() { diff --git a/scala/src/main/scala/RPCServer.scala b/scala/src/main/scala/RPCServer.scala index 069bbe2d..487a9690 100644 --- a/scala/src/main/scala/RPCServer.scala +++ b/scala/src/main/scala/RPCServer.scala @@ -1,8 +1,45 @@ +import java.util.concurrent.CountDownLatch + import com.rabbitmq.client.AMQP.BasicProperties -import com.rabbitmq.client.{Channel, Connection, ConnectionFactory, QueueingConsumer} +import com.rabbitmq.client._ -object RPCServer { +class ServerCallback(val ch: Channel, val latch: CountDownLatch) extends DeliverCallback { + override def handle(consumerTag: String, delivery: Delivery): Unit = { + var response: String = null + val replyProps = new BasicProperties.Builder() + .correlationId(delivery.getProperties.getCorrelationId) + .build + + try { + val message = new String(delivery.getBody, "UTF-8") + val n = java.lang.Integer.parseInt(message) + println(" [.] fib(" + message + ")") + response = "" + Fibonacci.fib(n) + } catch { + case e: Exception => { + println(" [.] " + e.toString) + response = "" + } + } finally { + ch.basicPublish("", delivery.getProperties.getReplyTo, replyProps, response.getBytes("UTF-8")) + ch.basicAck(delivery.getEnvelope.getDeliveryTag, false) + latch.countDown() + } + + } + +} + +object Fibonacci { + def fib(n: Int): Int = { + if (n == 0) return 0 + if (n == 1) return 1 + fib(n - 1) + fib(n - 2) + } +} + +object RPCServer { private val RPC_QUEUE_NAME = "rpc_queue" def main(argv: Array[String]) { @@ -15,30 +52,12 @@ object RPCServer { channel = connection.createChannel() channel.queueDeclare(RPC_QUEUE_NAME, false, false, false, null) channel.basicQos(1) - val consumer = new QueueingConsumer(channel) - channel.basicConsume(RPC_QUEUE_NAME, false, consumer) + // stop after one consumed message since this is example code + val latch = new CountDownLatch(1) + val serverCallback = new ServerCallback(channel, latch) + channel.basicConsume(RPC_QUEUE_NAME, false, serverCallback, _ => { }) println(" [x] Awaiting RPC requests") - while (true) { - var response: String = null - val delivery = consumer.nextDelivery() - val props = delivery.getProperties - val replyProps = new BasicProperties.Builder().correlationId(props.getCorrelationId) - .build() - try { - val message = new String(delivery.getBody, "UTF-8") - val n = java.lang.Integer.parseInt(message) - println(" [.] fib(" + message + ")") - response = "" + fib(n) - } catch { - case e: Exception => { - println(" [.] " + e.toString) - response = "" - } - } finally { - channel.basicPublish("", props.getReplyTo, replyProps, response.getBytes("UTF-8")) - channel.basicAck(delivery.getEnvelope.getDeliveryTag, false) - } - } + latch.await() } catch { case e: Exception => e.printStackTrace() } finally { @@ -51,10 +70,4 @@ object RPCServer { } } } - - private def fib(n: Int): Int = { - if (n == 0) return 0 - if (n == 1) return 1 - fib(n - 1) + fib(n - 2) - } } diff --git a/scala/src/main/scala/ReceiveLogHeader.scala b/scala/src/main/scala/ReceiveLogHeader.scala index 54ff6606..7252cbf2 100644 --- a/scala/src/main/scala/ReceiveLogHeader.scala +++ b/scala/src/main/scala/ReceiveLogHeader.scala @@ -30,17 +30,11 @@ object ReceiveLogHeader { .getQueue channel.queueBind(queueName, EXCHANGE_NAME, routingKeyFromUser, headers) println(" [*] Waiting for messages. To exit press CTRL+C") - val consumer = new DefaultConsumer(channel) { - - override def handleDelivery(consumerTag: String, - envelope: Envelope, - properties: AMQP.BasicProperties, - body: Array[Byte]) { - val message = new String(body, "UTF-8") - println(" [x] Received '" + envelope.getRoutingKey + "':'" + message + - "'") - } + val deliverCallback: DeliverCallback = (_, delivery) => { + val message = new String(delivery.getBody, "UTF-8") + println(" [x] Received '" + + delivery.getEnvelope.getRoutingKey + "':'" + message + "'") } - channel.basicConsume(queueName, true, consumer) + channel.basicConsume(queueName, true, deliverCallback, _ => { }) } } diff --git a/scala/src/main/scala/ReceiveLogs.scala b/scala/src/main/scala/ReceiveLogs.scala index 508ad0bf..839b205d 100644 --- a/scala/src/main/scala/ReceiveLogs.scala +++ b/scala/src/main/scala/ReceiveLogs.scala @@ -14,16 +14,10 @@ object ReceiveLogs { val queueName = channel.queueDeclare().getQueue channel.queueBind(queueName, EXCHANGE_NAME, "") println(" [*] Waiting for messages. To exit press CTRL+C") - val consumer = new DefaultConsumer(channel) { - - override def handleDelivery(consumerTag: String, - envelope: Envelope, - properties: AMQP.BasicProperties, - body: Array[Byte]) { - val message = new String(body, "UTF-8") - println(" [x] Received '" + message + "'") - } + val deliverCallback: DeliverCallback = (_, delivery) => { + val message = new String(delivery.getBody, "UTF-8") + println(" [x] Received '" + message + "'") } - channel.basicConsume(queueName, true, consumer) + channel.basicConsume(queueName, true, deliverCallback, _ => {}) } } diff --git a/scala/src/main/scala/ReceiveLogsDirect.scala b/scala/src/main/scala/ReceiveLogsDirect.scala index 5157bf35..10acbc13 100644 --- a/scala/src/main/scala/ReceiveLogsDirect.scala +++ b/scala/src/main/scala/ReceiveLogsDirect.scala @@ -19,17 +19,11 @@ object ReceiveLogsDirect { channel.queueBind(queueName, EXCHANGE_NAME, severity) } println(" [*] Waiting for messages. To exit press CTRL+C") - val consumer = new DefaultConsumer(channel) { - - override def handleDelivery(consumerTag: String, - envelope: Envelope, - properties: AMQP.BasicProperties, - body: Array[Byte]) { - val message = new String(body, "UTF-8") - println(" [x] Received '" + envelope.getRoutingKey + "':'" + message + - "'") - } + val deliverCallback: DeliverCallback = (_, delivery) => { + val message = new String(delivery.getBody, "UTF-8") + println(" [x] Received '" + + delivery.getEnvelope.getRoutingKey + "':'" + message + "'") } - channel.basicConsume(queueName, true, consumer) + channel.basicConsume(queueName, true, deliverCallback, _ => { }) } } diff --git a/scala/src/main/scala/ReceiveLogsTopic.scala b/scala/src/main/scala/ReceiveLogsTopic.scala index 23105b24..54a79b82 100644 --- a/scala/src/main/scala/ReceiveLogsTopic.scala +++ b/scala/src/main/scala/ReceiveLogsTopic.scala @@ -19,17 +19,11 @@ object ReceiveLogsTopic { channel.queueBind(queueName, EXCHANGE_NAME, bindingKey) } println(" [*] Waiting for messages. To exit press CTRL+C") - val consumer = new DefaultConsumer(channel) { - - override def handleDelivery(consumerTag: String, - envelope: Envelope, - properties: AMQP.BasicProperties, - body: Array[Byte]) { - val message = new String(body, "UTF-8") - println(" [x] Received '" + envelope.getRoutingKey + "':'" + message + - "'") - } + val deliverCallback: DeliverCallback = (_, delivery) => { + val message = new String(delivery.getBody, "UTF-8") + println(" [x] Received '" + + delivery.getEnvelope.getRoutingKey + "':'" + message + "'") } - channel.basicConsume(queueName, true, consumer) + channel.basicConsume(queueName, true, deliverCallback, _ => {}) } } diff --git a/scala/src/main/scala/Recv.scala b/scala/src/main/scala/Recv.scala index 7d6fa6d2..8ee3649d 100644 --- a/scala/src/main/scala/Recv.scala +++ b/scala/src/main/scala/Recv.scala @@ -12,16 +12,10 @@ object Recv { val channel = connection.createChannel() channel.queueDeclare(QUEUE_NAME, false, false, false, null) println(" [*] Waiting for messages. To exit press CTRL+C") - val consumer = new DefaultConsumer(channel) { - - override def handleDelivery(consumerTag: String, - envelope: Envelope, - properties: AMQP.BasicProperties, - body: Array[Byte]) { - var message = new String(body, "UTF-8") - println(" [x] Received '" + message + "'") - } + val deliverCallback: DeliverCallback = (_, delivery) => { + val message = new String(delivery.getBody, "UTF-8") + println(" [x] Received '" + message + "'") } - channel.basicConsume(QUEUE_NAME, true, consumer) + channel.basicConsume(QUEUE_NAME, true, deliverCallback, _ => { }) } } diff --git a/scala/src/main/scala/Worker.scala b/scala/src/main/scala/Worker.scala index 2f54b7d1..004b3293 100644 --- a/scala/src/main/scala/Worker.scala +++ b/scala/src/main/scala/Worker.scala @@ -12,23 +12,17 @@ object Worker { channel.queueDeclare(TASK_QUEUE_NAME, true, false, false, null) println(" [*] Waiting for messages. To exit press CTRL+C") channel.basicQos(1) - val consumer = new DefaultConsumer(channel) { - - override def handleDelivery(consumerTag: String, - envelope: Envelope, - properties: AMQP.BasicProperties, - body: Array[Byte]) { - val message = new String(body, "UTF-8") - println(" [x] Received '" + message + "'") - try { - doWork(message) - } finally { - println(" Done") - channel.basicAck(envelope.getDeliveryTag, false) - } + val deliverCallback: DeliverCallback = (_, delivery) => { + val message = new String(delivery.getBody, "UTF-8") + println(" [x] Received '" + message + "'") + try { + doWork(message) + } finally { + println(" Done") + channel.basicAck(delivery.getEnvelope.getDeliveryTag, false) } } - channel.basicConsume(TASK_QUEUE_NAME, false, consumer) + channel.basicConsume(TASK_QUEUE_NAME, false, deliverCallback, _ => {}) } private def doWork(task: String) { diff --git a/soapui/.mvn/wrapper/MavenWrapperDownloader.java b/soapui/.mvn/wrapper/MavenWrapperDownloader.java new file mode 100755 index 00000000..2e394d5b --- /dev/null +++ b/soapui/.mvn/wrapper/MavenWrapperDownloader.java @@ -0,0 +1,110 @@ +/* +Licensed to the Apache Software Foundation (ASF) under one +or more contributor license agreements. See the NOTICE file +distributed with this work for additional information +regarding copyright ownership. The ASF licenses this file +to you under the Apache License, Version 2.0 (the +"License"); you may not use this file except in compliance +with the License. You may obtain a copy of the License at + + https://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, +software distributed under the License is distributed on an +"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +KIND, either express or implied. See the License for the +specific language governing permissions and limitations +under the License. +*/ + +import java.net.*; +import java.io.*; +import java.nio.channels.*; +import java.util.Properties; + +public class MavenWrapperDownloader { + + /** + * Default URL to download the maven-wrapper.jar from, if no 'downloadUrl' is provided. + */ + private static final String DEFAULT_DOWNLOAD_URL = + "https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.4.2/maven-wrapper-0.4.2.jar"; + + /** + * Path to the maven-wrapper.properties file, which might contain a downloadUrl property to + * use instead of the default one. + */ + private static final String MAVEN_WRAPPER_PROPERTIES_PATH = + ".mvn/wrapper/maven-wrapper.properties"; + + /** + * Path where the maven-wrapper.jar will be saved to. + */ + private static final String MAVEN_WRAPPER_JAR_PATH = + ".mvn/wrapper/maven-wrapper.jar"; + + /** + * Name of the property which should be used to override the default download url for the wrapper. + */ + private static final String PROPERTY_NAME_WRAPPER_URL = "wrapperUrl"; + + public static void main(String args[]) { + System.out.println("- Downloader started"); + File baseDirectory = new File(args[0]); + System.out.println("- Using base directory: " + baseDirectory.getAbsolutePath()); + + // If the maven-wrapper.properties exists, read it and check if it contains a custom + // wrapperUrl parameter. + File mavenWrapperPropertyFile = new File(baseDirectory, MAVEN_WRAPPER_PROPERTIES_PATH); + String url = DEFAULT_DOWNLOAD_URL; + if(mavenWrapperPropertyFile.exists()) { + FileInputStream mavenWrapperPropertyFileInputStream = null; + try { + mavenWrapperPropertyFileInputStream = new FileInputStream(mavenWrapperPropertyFile); + Properties mavenWrapperProperties = new Properties(); + mavenWrapperProperties.load(mavenWrapperPropertyFileInputStream); + url = mavenWrapperProperties.getProperty(PROPERTY_NAME_WRAPPER_URL, url); + } catch (IOException e) { + System.out.println("- ERROR loading '" + MAVEN_WRAPPER_PROPERTIES_PATH + "'"); + } finally { + try { + if(mavenWrapperPropertyFileInputStream != null) { + mavenWrapperPropertyFileInputStream.close(); + } + } catch (IOException e) { + // Ignore ... + } + } + } + System.out.println("- Downloading from: : " + url); + + File outputFile = new File(baseDirectory.getAbsolutePath(), MAVEN_WRAPPER_JAR_PATH); + if(!outputFile.getParentFile().exists()) { + if(!outputFile.getParentFile().mkdirs()) { + System.out.println( + "- ERROR creating output direcrory '" + outputFile.getParentFile().getAbsolutePath() + "'"); + } + } + System.out.println("- Downloading to: " + outputFile.getAbsolutePath()); + try { + downloadFileFromURL(url, outputFile); + System.out.println("Done"); + System.exit(0); + } catch (Throwable e) { + System.out.println("- Error downloading"); + e.printStackTrace(); + System.exit(1); + } + } + + private static void downloadFileFromURL(String urlString, File destination) throws Exception { + URL website = new URL(urlString); + ReadableByteChannel rbc; + rbc = Channels.newChannel(website.openStream()); + FileOutputStream fos = new FileOutputStream(destination); + fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE); + fos.close(); + rbc.close(); + } + +} diff --git a/soapui/.mvn/wrapper/maven-wrapper.jar b/soapui/.mvn/wrapper/maven-wrapper.jar new file mode 100755 index 00000000..01e67997 Binary files /dev/null and b/soapui/.mvn/wrapper/maven-wrapper.jar differ diff --git a/soapui/.mvn/wrapper/maven-wrapper.properties b/soapui/.mvn/wrapper/maven-wrapper.properties new file mode 100755 index 00000000..00d32aab --- /dev/null +++ b/soapui/.mvn/wrapper/maven-wrapper.properties @@ -0,0 +1 @@ +distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.5.4/apache-maven-3.5.4-bin.zip \ No newline at end of file diff --git a/soapui/README.md b/soapui/README.md new file mode 100644 index 00000000..b9ca4177 --- /dev/null +++ b/soapui/README.md @@ -0,0 +1,54 @@ +# RabbitMQ tutorials ported to SoapUI + +The code is based on the [Java tutorials](https://www.rabbitmq.com/tutorials), +with slight modifications to make it work in SoapUI. + +## Prerequisites + + * [SoapUI](https://www.soapui.org/downloads/soapui.html) or [ReadyAPI](https://smartbear.com/product/ready-api/overview/) + with the [Java Client](https://www.rabbitmq.com/download.html#clients) JAR file in the `bin/ext` directory. + * local RabbitMQ broker instance running with all the defaults + +## Running + +You can import and run the `RabbitMQ-tutorials-soapui-project.xml` in the GUI. +The project does not have any asserts; all log messages can be viewed in the "script log" tab. + +You can also use the [CLI runner](https://www.soapui.org/test-automation/running-from-command-line/functional-tests.html): +`$SOAPUI_HOME/bin/testrunner.sh RabbitMQ-tutorials-soapui-project.xml`. + +Or you can use the provided Maven pom: `./mvnw verify`. + +## Tutorials + +The code to *send* messages to RabbitMQ is pretty much unchanged from +the Java tutorials. The biggest change is: there is no reading of the +text of the message from the command line - the text of the message +is included in each individual step. + +There are two changes to the code to *consume* messages: + +1. The program pauses for a bit to let the message get processed. +Without this, SoapUI ends the script step immediately and +kills the asynchronous process running in the background waiting +for the message. The exact pause will depend on the specific case; +it can be as simple as `sleep 10000`, or little more elaborate, e.g.: + + ```groovy + int stop = 0 + while(message == null && stop++ < 20) { + log.info " [*] Waiting for messages." + sleep 500 + } + ``` + +2. At the end of each read script step, the channel and connections +are closed explicitly: + + ```groovy + channel.close() + connection.close() + ``` + +Without this, some background threads are still running and would make +the test hang. diff --git a/soapui/RabbitMQ-tutorials-soapui-project.xml b/soapui/RabbitMQ-tutorials-soapui-project.xml new file mode 100644 index 00000000..c011ab9b --- /dev/null +++ b/soapui/RabbitMQ-tutorials-soapui-project.xml @@ -0,0 +1,1006 @@ + + + + + The simplest thing that does something + + SEQUENTIAL + + + + + + + + + + + + + + + + + + + queue_name + hello + + + + + Distributing tasks among workers (the competing consumers pattern) + + PARALLELL + + + + + + + + + + + + + + + + + + + + + + + + + + 1000 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + queue_name + task_queue + + + + + Sending messages to many consumers at once + + PARALLELL + + + + + + + + + + + + + + + + + + + + + + + + + + 1000 + + + + + + + + + + + + + exchange_name + logs + + + + + Receiving messages selectively + + PARALLELL + + + + + + + + + + + + + + + + + + + + + + + + + + 1000 + + + + + + + + + + + + + + + + + + + + + + + + + exchange_name + direct_logs + + + + + Receiving messages based on a pattern (topics) + + PARALLELL + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1000 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + exchange_name + topic_logs + + + + + + + + + diff --git a/soapui/mvnw b/soapui/mvnw new file mode 100755 index 00000000..8b9da3b8 --- /dev/null +++ b/soapui/mvnw @@ -0,0 +1,286 @@ +#!/bin/sh +# ---------------------------------------------------------------------------- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# ---------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------- +# Maven2 Start Up Batch script +# +# Required ENV vars: +# ------------------ +# JAVA_HOME - location of a JDK home dir +# +# Optional ENV vars +# ----------------- +# M2_HOME - location of maven2's installed home dir +# MAVEN_OPTS - parameters passed to the Java VM when running Maven +# e.g. to debug Maven itself, use +# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +# MAVEN_SKIP_RC - flag to disable loading of mavenrc files +# ---------------------------------------------------------------------------- + +if [ -z "$MAVEN_SKIP_RC" ] ; then + + if [ -f /etc/mavenrc ] ; then + . /etc/mavenrc + fi + + if [ -f "$HOME/.mavenrc" ] ; then + . "$HOME/.mavenrc" + fi + +fi + +# OS specific support. $var _must_ be set to either true or false. +cygwin=false; +darwin=false; +mingw=false +case "`uname`" in + CYGWIN*) cygwin=true ;; + MINGW*) mingw=true;; + Darwin*) darwin=true + # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home + # See https://developer.apple.com/library/mac/qa/qa1170/_index.html + if [ -z "$JAVA_HOME" ]; then + if [ -x "/usr/libexec/java_home" ]; then + export JAVA_HOME="`/usr/libexec/java_home`" + else + export JAVA_HOME="/Library/Java/Home" + fi + fi + ;; +esac + +if [ -z "$JAVA_HOME" ] ; then + if [ -r /etc/gentoo-release ] ; then + JAVA_HOME=`java-config --jre-home` + fi +fi + +if [ -z "$M2_HOME" ] ; then + ## resolve links - $0 may be a link to maven's home + PRG="$0" + + # need this for relative symlinks + while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG="`dirname "$PRG"`/$link" + fi + done + + saveddir=`pwd` + + M2_HOME=`dirname "$PRG"`/.. + + # make it fully qualified + M2_HOME=`cd "$M2_HOME" && pwd` + + cd "$saveddir" + # echo Using m2 at $M2_HOME +fi + +# For Cygwin, ensure paths are in UNIX format before anything is touched +if $cygwin ; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --unix "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --unix "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --unix "$CLASSPATH"` +fi + +# For Mingw, ensure paths are in UNIX format before anything is touched +if $mingw ; then + [ -n "$M2_HOME" ] && + M2_HOME="`(cd "$M2_HOME"; pwd)`" + [ -n "$JAVA_HOME" ] && + JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" + # TODO classpath? +fi + +if [ -z "$JAVA_HOME" ]; then + javaExecutable="`which javac`" + if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then + # readlink(1) is not available as standard on Solaris 10. + readLink=`which readlink` + if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then + if $darwin ; then + javaHome="`dirname \"$javaExecutable\"`" + javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" + else + javaExecutable="`readlink -f \"$javaExecutable\"`" + fi + javaHome="`dirname \"$javaExecutable\"`" + javaHome=`expr "$javaHome" : '\(.*\)/bin'` + JAVA_HOME="$javaHome" + export JAVA_HOME + fi + fi +fi + +if [ -z "$JAVACMD" ] ; then + if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + else + JAVACMD="`which java`" + fi +fi + +if [ ! -x "$JAVACMD" ] ; then + echo "Error: JAVA_HOME is not defined correctly." >&2 + echo " We cannot execute $JAVACMD" >&2 + exit 1 +fi + +if [ -z "$JAVA_HOME" ] ; then + echo "Warning: JAVA_HOME environment variable is not set." +fi + +CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher + +# traverses directory structure from process work directory to filesystem root +# first directory with .mvn subdirectory is considered project base directory +find_maven_basedir() { + + if [ -z "$1" ] + then + echo "Path not specified to find_maven_basedir" + return 1 + fi + + basedir="$1" + wdir="$1" + while [ "$wdir" != '/' ] ; do + if [ -d "$wdir"/.mvn ] ; then + basedir=$wdir + break + fi + # workaround for JBEAP-8937 (on Solaris 10/Sparc) + if [ -d "${wdir}" ]; then + wdir=`cd "$wdir/.."; pwd` + fi + # end of workaround + done + echo "${basedir}" +} + +# concatenates all lines of a file +concat_lines() { + if [ -f "$1" ]; then + echo "$(tr -s '\n' ' ' < "$1")" + fi +} + +BASE_DIR=`find_maven_basedir "$(pwd)"` +if [ -z "$BASE_DIR" ]; then + exit 1; +fi + +########################################################################################## +# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +# This allows using the maven wrapper in projects that prohibit checking in binary data. +########################################################################################## +if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found .mvn/wrapper/maven-wrapper.jar" + fi +else + if [ "$MVNW_VERBOSE" = true ]; then + echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..." + fi + jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.4.2/maven-wrapper-0.4.2.jar" + while IFS="=" read key value; do + case "$key" in (wrapperUrl) jarUrl="$value"; break ;; + esac + done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties" + if [ "$MVNW_VERBOSE" = true ]; then + echo "Downloading from: $jarUrl" + fi + wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" + + if command -v wget > /dev/null; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found wget ... using wget" + fi + wget "$jarUrl" -O "$wrapperJarPath" + elif command -v curl > /dev/null; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found curl ... using curl" + fi + curl -o "$wrapperJarPath" "$jarUrl" + else + if [ "$MVNW_VERBOSE" = true ]; then + echo "Falling back to using Java to download" + fi + javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java" + if [ -e "$javaClass" ]; then + if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then + if [ "$MVNW_VERBOSE" = true ]; then + echo " - Compiling MavenWrapperDownloader.java ..." + fi + # Compiling the Java class + ("$JAVA_HOME/bin/javac" "$javaClass") + fi + if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then + # Running the downloader + if [ "$MVNW_VERBOSE" = true ]; then + echo " - Running MavenWrapperDownloader.java ..." + fi + ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR") + fi + fi + fi +fi +########################################################################################## +# End of extension +########################################################################################## + +export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} +if [ "$MVNW_VERBOSE" = true ]; then + echo $MAVEN_PROJECTBASEDIR +fi +MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" + +# For Cygwin, switch paths to Windows format before running java +if $cygwin; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --path --windows "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --windows "$CLASSPATH"` + [ -n "$MAVEN_PROJECTBASEDIR" ] && + MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"` +fi + +WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +exec "$JAVACMD" \ + $MAVEN_OPTS \ + -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ + "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ + ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" diff --git a/soapui/mvnw.cmd b/soapui/mvnw.cmd new file mode 100755 index 00000000..a5284c79 --- /dev/null +++ b/soapui/mvnw.cmd @@ -0,0 +1,161 @@ +@REM ---------------------------------------------------------------------------- +@REM Licensed to the Apache Software Foundation (ASF) under one +@REM or more contributor license agreements. See the NOTICE file +@REM distributed with this work for additional information +@REM regarding copyright ownership. The ASF licenses this file +@REM to you under the Apache License, Version 2.0 (the +@REM "License"); you may not use this file except in compliance +@REM with the License. You may obtain a copy of the License at +@REM +@REM https://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, +@REM software distributed under the License is distributed on an +@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +@REM KIND, either express or implied. See the License for the +@REM specific language governing permissions and limitations +@REM under the License. +@REM ---------------------------------------------------------------------------- + +@REM ---------------------------------------------------------------------------- +@REM Maven2 Start Up Batch script +@REM +@REM Required ENV vars: +@REM JAVA_HOME - location of a JDK home dir +@REM +@REM Optional ENV vars +@REM M2_HOME - location of maven2's installed home dir +@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands +@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a key stroke before ending +@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven +@REM e.g. to debug Maven itself, use +@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files +@REM ---------------------------------------------------------------------------- + +@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' +@echo off +@REM set title of command window +title %0 +@REM enable echoing my setting MAVEN_BATCH_ECHO to 'on' +@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% + +@REM set %HOME% to equivalent of $HOME +if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") + +@REM Execute a user defined script before this one +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre +@REM check for pre script, once with legacy .bat ending and once with .cmd ending +if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat" +if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd" +:skipRcPre + +@setlocal + +set ERROR_CODE=0 + +@REM To isolate internal variables from possible post scripts, we use another setlocal +@setlocal + +@REM ==== START VALIDATION ==== +if not "%JAVA_HOME%" == "" goto OkJHome + +echo. +echo Error: JAVA_HOME not found in your environment. >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +:OkJHome +if exist "%JAVA_HOME%\bin\java.exe" goto init + +echo. +echo Error: JAVA_HOME is set to an invalid directory. >&2 +echo JAVA_HOME = "%JAVA_HOME%" >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +@REM ==== END VALIDATION ==== + +:init + +@REM Find the project base dir, i.e. the directory that contains the folder ".mvn". +@REM Fallback to current working directory if not found. + +set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% +IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir + +set EXEC_DIR=%CD% +set WDIR=%EXEC_DIR% +:findBaseDir +IF EXIST "%WDIR%"\.mvn goto baseDirFound +cd .. +IF "%WDIR%"=="%CD%" goto baseDirNotFound +set WDIR=%CD% +goto findBaseDir + +:baseDirFound +set MAVEN_PROJECTBASEDIR=%WDIR% +cd "%EXEC_DIR%" +goto endDetectBaseDir + +:baseDirNotFound +set MAVEN_PROJECTBASEDIR=%EXEC_DIR% +cd "%EXEC_DIR%" + +:endDetectBaseDir + +IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig + +@setlocal EnableExtensions EnableDelayedExpansion +for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a +@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% + +:endReadAdditionalConfig + +SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" +set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" +set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.4.2/maven-wrapper-0.4.2.jar" +FOR /F "tokens=1,2 delims==" %%A IN (%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties) DO ( + IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B +) + +@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +@REM This allows using the maven wrapper in projects that prohibit checking in binary data. +if exist %WRAPPER_JAR% ( + echo Found %WRAPPER_JAR% +) else ( + echo Couldn't find %WRAPPER_JAR%, downloading it ... + echo Downloading from: %DOWNLOAD_URL% + powershell -Command "(New-Object Net.WebClient).DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')" + echo Finished downloading %WRAPPER_JAR% +) +@REM End of extension + +%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* +if ERRORLEVEL 1 goto error +goto end + +:error +set ERROR_CODE=1 + +:end +@endlocal & set ERROR_CODE=%ERROR_CODE% + +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost +@REM check for post script, once with legacy .bat ending and once with .cmd ending +if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat" +if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd" +:skipRcPost + +@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' +if "%MAVEN_BATCH_PAUSE%" == "on" pause + +if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE% + +exit /B %ERROR_CODE% diff --git a/soapui/pom.xml b/soapui/pom.xml new file mode 100644 index 00000000..9849ce8f --- /dev/null +++ b/soapui/pom.xml @@ -0,0 +1,48 @@ + + 4.0.0 + + com.rabbitmq + soapui-tutorials + 1-SNAPSHOT + + RabbitMQ tutorials - SoapUI port + https://github.com/rabbitmq/rabbitmq-tutorials + + + + smartbear-repository + https://smartbearsoftware.com/repository/maven2/ + + + + + + + + com.smartbear.soapui + soapui-maven-plugin + 5.4.0 + + ${project.basedir}/RabbitMQ-tutorials-soapui-project.xml + + + + integration-test + + test + + + + + + com.rabbitmq + amqp-client + 5.18.0 + + + + + + + + diff --git a/spring-amqp-stream/.mvn/wrapper/maven-wrapper.jar b/spring-amqp-stream/.mvn/wrapper/maven-wrapper.jar new file mode 100644 index 00000000..cb28b0e3 Binary files /dev/null and b/spring-amqp-stream/.mvn/wrapper/maven-wrapper.jar differ diff --git a/spring-amqp-stream/.mvn/wrapper/maven-wrapper.properties b/spring-amqp-stream/.mvn/wrapper/maven-wrapper.properties new file mode 100755 index 00000000..346d645f --- /dev/null +++ b/spring-amqp-stream/.mvn/wrapper/maven-wrapper.properties @@ -0,0 +1,18 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.6/apache-maven-3.9.6-bin.zip +wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar diff --git a/spring-amqp-stream/README.md b/spring-amqp-stream/README.md new file mode 100644 index 00000000..3acbcd75 --- /dev/null +++ b/spring-amqp-stream/README.md @@ -0,0 +1,71 @@ +# RabbitMQ Stream Tutorial Using Spring AMQP + +It is a CLI app that uses Spring Profiles to control its behavior. Each tutorial is a trio of classes: +sender, receiver, and configuration. + +## Prerequisites + +To successfully use the examples you will need a running RabbitMQ server with the + [stream plugin enabled](https://www.rabbitmq.com/docs/stream#enabling-plugin). + +See [First Application With RabbitMQ Streams](https://www.rabbitmq.com/blog/2021/07/19/rabbitmq-streams-first-application), +[Stream plugin documentation](https://www.rabbitmq.com/docs/stream) and +[how to preconfigure plugins](https://www.rabbitmq.com/docs/plugins#enabled-plugins-file). + +## Usage + +These tutorials use Maven. To build them run + +``` +./mvnw clean package +``` +The app uses Spring Profiles to control what tutorial it's running, and if it's a +Sender or Receiver. Choose which tutorial to run by using these profiles: + +- {tut1|hello-world},{sender|receiver} + +You can find usage instructions by running the following command: + +```bash +java -jar target/rabbitmq-stream-tutorials.jar +``` + +This will display the following message: + +```bash +This app uses Spring Profiles to control its behavior. + +Options are: +java -jar target/rabbitmq-stream-tutorials.jar --spring.profiles.active=hello-world,receiver +java -jar target/rabbitmq-stream-tutorials.jar --spring.profiles.active=hello-world,sender +``` + +## Configuration + +When running receivers/servers it's useful to set the duration the app runs to a longer time. Do this by setting +the `tutorial.client.duration` property. + +``` +java -jar target/rabbitmq-stream-tutorials.jar --spring.profiles.active=tut1,receiver,remote --tutorial.client.duration=60000 +``` + +By default, Spring AMQP uses localhost to connect to RabbitMQ. In the +sample, the `remote` profile causes Spring to load the properties in +`application-remote.yml` that are used for testing with a non-local +server. Set your own properties in the one in the project, or provide +your own on the command line when you run it. + +To use to a remote RabbitMQ installation set the following properties: + +``` +spring: + rabbitmq: + host: + username: + password: +``` + +To use this at runtime create a file called `application-remote.yml` (or properties) and set the properties in there. Then set the +remote profile as in the example above. See the [Spring Boot](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/) +and [Spring AMQP documentation](https://docs.spring.io/spring-amqp/reference/html/) for more information on setting application +properties and AMQP properties specifically. diff --git a/spring-amqp-stream/mvnw b/spring-amqp-stream/mvnw new file mode 100755 index 00000000..8d937f4c --- /dev/null +++ b/spring-amqp-stream/mvnw @@ -0,0 +1,308 @@ +#!/bin/sh +# ---------------------------------------------------------------------------- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# ---------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------- +# Apache Maven Wrapper startup batch script, version 3.2.0 +# +# Required ENV vars: +# ------------------ +# JAVA_HOME - location of a JDK home dir +# +# Optional ENV vars +# ----------------- +# MAVEN_OPTS - parameters passed to the Java VM when running Maven +# e.g. to debug Maven itself, use +# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +# MAVEN_SKIP_RC - flag to disable loading of mavenrc files +# ---------------------------------------------------------------------------- + +if [ -z "$MAVEN_SKIP_RC" ] ; then + + if [ -f /usr/local/etc/mavenrc ] ; then + . /usr/local/etc/mavenrc + fi + + if [ -f /etc/mavenrc ] ; then + . /etc/mavenrc + fi + + if [ -f "$HOME/.mavenrc" ] ; then + . "$HOME/.mavenrc" + fi + +fi + +# OS specific support. $var _must_ be set to either true or false. +cygwin=false; +darwin=false; +mingw=false +case "$(uname)" in + CYGWIN*) cygwin=true ;; + MINGW*) mingw=true;; + Darwin*) darwin=true + # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home + # See https://developer.apple.com/library/mac/qa/qa1170/_index.html + if [ -z "$JAVA_HOME" ]; then + if [ -x "/usr/libexec/java_home" ]; then + JAVA_HOME="$(/usr/libexec/java_home)"; export JAVA_HOME + else + JAVA_HOME="/Library/Java/Home"; export JAVA_HOME + fi + fi + ;; +esac + +if [ -z "$JAVA_HOME" ] ; then + if [ -r /etc/gentoo-release ] ; then + JAVA_HOME=$(java-config --jre-home) + fi +fi + +# For Cygwin, ensure paths are in UNIX format before anything is touched +if $cygwin ; then + [ -n "$JAVA_HOME" ] && + JAVA_HOME=$(cygpath --unix "$JAVA_HOME") + [ -n "$CLASSPATH" ] && + CLASSPATH=$(cygpath --path --unix "$CLASSPATH") +fi + +# For Mingw, ensure paths are in UNIX format before anything is touched +if $mingw ; then + [ -n "$JAVA_HOME" ] && [ -d "$JAVA_HOME" ] && + JAVA_HOME="$(cd "$JAVA_HOME" || (echo "cannot cd into $JAVA_HOME."; exit 1); pwd)" +fi + +if [ -z "$JAVA_HOME" ]; then + javaExecutable="$(which javac)" + if [ -n "$javaExecutable" ] && ! [ "$(expr "\"$javaExecutable\"" : '\([^ ]*\)')" = "no" ]; then + # readlink(1) is not available as standard on Solaris 10. + readLink=$(which readlink) + if [ ! "$(expr "$readLink" : '\([^ ]*\)')" = "no" ]; then + if $darwin ; then + javaHome="$(dirname "\"$javaExecutable\"")" + javaExecutable="$(cd "\"$javaHome\"" && pwd -P)/javac" + else + javaExecutable="$(readlink -f "\"$javaExecutable\"")" + fi + javaHome="$(dirname "\"$javaExecutable\"")" + javaHome=$(expr "$javaHome" : '\(.*\)/bin') + JAVA_HOME="$javaHome" + export JAVA_HOME + fi + fi +fi + +if [ -z "$JAVACMD" ] ; then + if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + else + JAVACMD="$(\unset -f command 2>/dev/null; \command -v java)" + fi +fi + +if [ ! -x "$JAVACMD" ] ; then + echo "Error: JAVA_HOME is not defined correctly." >&2 + echo " We cannot execute $JAVACMD" >&2 + exit 1 +fi + +if [ -z "$JAVA_HOME" ] ; then + echo "Warning: JAVA_HOME environment variable is not set." +fi + +# traverses directory structure from process work directory to filesystem root +# first directory with .mvn subdirectory is considered project base directory +find_maven_basedir() { + if [ -z "$1" ] + then + echo "Path not specified to find_maven_basedir" + return 1 + fi + + basedir="$1" + wdir="$1" + while [ "$wdir" != '/' ] ; do + if [ -d "$wdir"/.mvn ] ; then + basedir=$wdir + break + fi + # workaround for JBEAP-8937 (on Solaris 10/Sparc) + if [ -d "${wdir}" ]; then + wdir=$(cd "$wdir/.." || exit 1; pwd) + fi + # end of workaround + done + printf '%s' "$(cd "$basedir" || exit 1; pwd)" +} + +# concatenates all lines of a file +concat_lines() { + if [ -f "$1" ]; then + # Remove \r in case we run on Windows within Git Bash + # and check out the repository with auto CRLF management + # enabled. Otherwise, we may read lines that are delimited with + # \r\n and produce $'-Xarg\r' rather than -Xarg due to word + # splitting rules. + tr -s '\r\n' ' ' < "$1" + fi +} + +log() { + if [ "$MVNW_VERBOSE" = true ]; then + printf '%s\n' "$1" + fi +} + +BASE_DIR=$(find_maven_basedir "$(dirname "$0")") +if [ -z "$BASE_DIR" ]; then + exit 1; +fi + +MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}; export MAVEN_PROJECTBASEDIR +log "$MAVEN_PROJECTBASEDIR" + +########################################################################################## +# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +# This allows using the maven wrapper in projects that prohibit checking in binary data. +########################################################################################## +wrapperJarPath="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" +if [ -r "$wrapperJarPath" ]; then + log "Found $wrapperJarPath" +else + log "Couldn't find $wrapperJarPath, downloading it ..." + + if [ -n "$MVNW_REPOURL" ]; then + wrapperUrl="$MVNW_REPOURL/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" + else + wrapperUrl="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" + fi + while IFS="=" read -r key value; do + # Remove '\r' from value to allow usage on windows as IFS does not consider '\r' as a separator ( considers space, tab, new line ('\n'), and custom '=' ) + safeValue=$(echo "$value" | tr -d '\r') + case "$key" in (wrapperUrl) wrapperUrl="$safeValue"; break ;; + esac + done < "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties" + log "Downloading from: $wrapperUrl" + + if $cygwin; then + wrapperJarPath=$(cygpath --path --windows "$wrapperJarPath") + fi + + if command -v wget > /dev/null; then + log "Found wget ... using wget" + [ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--quiet" + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + wget $QUIET "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath" + else + wget $QUIET --http-user="$MVNW_USERNAME" --http-password="$MVNW_PASSWORD" "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath" + fi + elif command -v curl > /dev/null; then + log "Found curl ... using curl" + [ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--silent" + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + curl $QUIET -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath" + else + curl $QUIET --user "$MVNW_USERNAME:$MVNW_PASSWORD" -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath" + fi + else + log "Falling back to using Java to download" + javaSource="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.java" + javaClass="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.class" + # For Cygwin, switch paths to Windows format before running javac + if $cygwin; then + javaSource=$(cygpath --path --windows "$javaSource") + javaClass=$(cygpath --path --windows "$javaClass") + fi + if [ -e "$javaSource" ]; then + if [ ! -e "$javaClass" ]; then + log " - Compiling MavenWrapperDownloader.java ..." + ("$JAVA_HOME/bin/javac" "$javaSource") + fi + if [ -e "$javaClass" ]; then + log " - Running MavenWrapperDownloader.java ..." + ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$wrapperUrl" "$wrapperJarPath") || rm -f "$wrapperJarPath" + fi + fi + fi +fi +########################################################################################## +# End of extension +########################################################################################## + +# If specified, validate the SHA-256 sum of the Maven wrapper jar file +wrapperSha256Sum="" +while IFS="=" read -r key value; do + case "$key" in (wrapperSha256Sum) wrapperSha256Sum=$value; break ;; + esac +done < "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties" +if [ -n "$wrapperSha256Sum" ]; then + wrapperSha256Result=false + if command -v sha256sum > /dev/null; then + if echo "$wrapperSha256Sum $wrapperJarPath" | sha256sum -c > /dev/null 2>&1; then + wrapperSha256Result=true + fi + elif command -v shasum > /dev/null; then + if echo "$wrapperSha256Sum $wrapperJarPath" | shasum -a 256 -c > /dev/null 2>&1; then + wrapperSha256Result=true + fi + else + echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available." + echo "Please install either command, or disable validation by removing 'wrapperSha256Sum' from your maven-wrapper.properties." + exit 1 + fi + if [ $wrapperSha256Result = false ]; then + echo "Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised." >&2 + echo "Investigate or delete $wrapperJarPath to attempt a clean download." >&2 + echo "If you updated your Maven version, you need to update the specified wrapperSha256Sum property." >&2 + exit 1 + fi +fi + +MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" + +# For Cygwin, switch paths to Windows format before running java +if $cygwin; then + [ -n "$JAVA_HOME" ] && + JAVA_HOME=$(cygpath --path --windows "$JAVA_HOME") + [ -n "$CLASSPATH" ] && + CLASSPATH=$(cygpath --path --windows "$CLASSPATH") + [ -n "$MAVEN_PROJECTBASEDIR" ] && + MAVEN_PROJECTBASEDIR=$(cygpath --path --windows "$MAVEN_PROJECTBASEDIR") +fi + +# Provide a "standardized" way to retrieve the CLI args that will +# work with both Windows and non-Windows executions. +MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $*" +export MAVEN_CMD_LINE_ARGS + +WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +# shellcheck disable=SC2086 # safe args +exec "$JAVACMD" \ + $MAVEN_OPTS \ + $MAVEN_DEBUG_OPTS \ + -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ + "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ + ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" diff --git a/spring-amqp-stream/mvnw.cmd b/spring-amqp-stream/mvnw.cmd new file mode 100644 index 00000000..f80fbad3 --- /dev/null +++ b/spring-amqp-stream/mvnw.cmd @@ -0,0 +1,205 @@ +@REM ---------------------------------------------------------------------------- +@REM Licensed to the Apache Software Foundation (ASF) under one +@REM or more contributor license agreements. See the NOTICE file +@REM distributed with this work for additional information +@REM regarding copyright ownership. The ASF licenses this file +@REM to you under the Apache License, Version 2.0 (the +@REM "License"); you may not use this file except in compliance +@REM with the License. You may obtain a copy of the License at +@REM +@REM http://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, +@REM software distributed under the License is distributed on an +@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +@REM KIND, either express or implied. See the License for the +@REM specific language governing permissions and limitations +@REM under the License. +@REM ---------------------------------------------------------------------------- + +@REM ---------------------------------------------------------------------------- +@REM Apache Maven Wrapper startup batch script, version 3.2.0 +@REM +@REM Required ENV vars: +@REM JAVA_HOME - location of a JDK home dir +@REM +@REM Optional ENV vars +@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands +@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending +@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven +@REM e.g. to debug Maven itself, use +@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files +@REM ---------------------------------------------------------------------------- + +@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' +@echo off +@REM set title of command window +title %0 +@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on' +@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% + +@REM set %HOME% to equivalent of $HOME +if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") + +@REM Execute a user defined script before this one +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre +@REM check for pre script, once with legacy .bat ending and once with .cmd ending +if exist "%USERPROFILE%\mavenrc_pre.bat" call "%USERPROFILE%\mavenrc_pre.bat" %* +if exist "%USERPROFILE%\mavenrc_pre.cmd" call "%USERPROFILE%\mavenrc_pre.cmd" %* +:skipRcPre + +@setlocal + +set ERROR_CODE=0 + +@REM To isolate internal variables from possible post scripts, we use another setlocal +@setlocal + +@REM ==== START VALIDATION ==== +if not "%JAVA_HOME%" == "" goto OkJHome + +echo. +echo Error: JAVA_HOME not found in your environment. >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +:OkJHome +if exist "%JAVA_HOME%\bin\java.exe" goto init + +echo. +echo Error: JAVA_HOME is set to an invalid directory. >&2 +echo JAVA_HOME = "%JAVA_HOME%" >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +@REM ==== END VALIDATION ==== + +:init + +@REM Find the project base dir, i.e. the directory that contains the folder ".mvn". +@REM Fallback to current working directory if not found. + +set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% +IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir + +set EXEC_DIR=%CD% +set WDIR=%EXEC_DIR% +:findBaseDir +IF EXIST "%WDIR%"\.mvn goto baseDirFound +cd .. +IF "%WDIR%"=="%CD%" goto baseDirNotFound +set WDIR=%CD% +goto findBaseDir + +:baseDirFound +set MAVEN_PROJECTBASEDIR=%WDIR% +cd "%EXEC_DIR%" +goto endDetectBaseDir + +:baseDirNotFound +set MAVEN_PROJECTBASEDIR=%EXEC_DIR% +cd "%EXEC_DIR%" + +:endDetectBaseDir + +IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig + +@setlocal EnableExtensions EnableDelayedExpansion +for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a +@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% + +:endReadAdditionalConfig + +SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" +set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" +set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +set WRAPPER_URL="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" + +FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( + IF "%%A"=="wrapperUrl" SET WRAPPER_URL=%%B +) + +@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +@REM This allows using the maven wrapper in projects that prohibit checking in binary data. +if exist %WRAPPER_JAR% ( + if "%MVNW_VERBOSE%" == "true" ( + echo Found %WRAPPER_JAR% + ) +) else ( + if not "%MVNW_REPOURL%" == "" ( + SET WRAPPER_URL="%MVNW_REPOURL%/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" + ) + if "%MVNW_VERBOSE%" == "true" ( + echo Couldn't find %WRAPPER_JAR%, downloading it ... + echo Downloading from: %WRAPPER_URL% + ) + + powershell -Command "&{"^ + "$webclient = new-object System.Net.WebClient;"^ + "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ + "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ + "}"^ + "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%WRAPPER_URL%', '%WRAPPER_JAR%')"^ + "}" + if "%MVNW_VERBOSE%" == "true" ( + echo Finished downloading %WRAPPER_JAR% + ) +) +@REM End of extension + +@REM If specified, validate the SHA-256 sum of the Maven wrapper jar file +SET WRAPPER_SHA_256_SUM="" +FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( + IF "%%A"=="wrapperSha256Sum" SET WRAPPER_SHA_256_SUM=%%B +) +IF NOT %WRAPPER_SHA_256_SUM%=="" ( + powershell -Command "&{"^ + "$hash = (Get-FileHash \"%WRAPPER_JAR%\" -Algorithm SHA256).Hash.ToLower();"^ + "If('%WRAPPER_SHA_256_SUM%' -ne $hash){"^ + " Write-Output 'Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised.';"^ + " Write-Output 'Investigate or delete %WRAPPER_JAR% to attempt a clean download.';"^ + " Write-Output 'If you updated your Maven version, you need to update the specified wrapperSha256Sum property.';"^ + " exit 1;"^ + "}"^ + "}" + if ERRORLEVEL 1 goto error +) + +@REM Provide a "standardized" way to retrieve the CLI args that will +@REM work with both Windows and non-Windows executions. +set MAVEN_CMD_LINE_ARGS=%* + +%MAVEN_JAVA_EXE% ^ + %JVM_CONFIG_MAVEN_PROPS% ^ + %MAVEN_OPTS% ^ + %MAVEN_DEBUG_OPTS% ^ + -classpath %WRAPPER_JAR% ^ + "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" ^ + %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* +if ERRORLEVEL 1 goto error +goto end + +:error +set ERROR_CODE=1 + +:end +@endlocal & set ERROR_CODE=%ERROR_CODE% + +if not "%MAVEN_SKIP_RC%"=="" goto skipRcPost +@REM check for post script, once with legacy .bat ending and once with .cmd ending +if exist "%USERPROFILE%\mavenrc_post.bat" call "%USERPROFILE%\mavenrc_post.bat" +if exist "%USERPROFILE%\mavenrc_post.cmd" call "%USERPROFILE%\mavenrc_post.cmd" +:skipRcPost + +@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' +if "%MAVEN_BATCH_PAUSE%"=="on" pause + +if "%MAVEN_TERMINATE_CMD%"=="on" exit %ERROR_CODE% + +cmd /C exit /B %ERROR_CODE% diff --git a/spring-amqp-stream/pom.xml b/spring-amqp-stream/pom.xml new file mode 100644 index 00000000..a2ce43aa --- /dev/null +++ b/spring-amqp-stream/pom.xml @@ -0,0 +1,60 @@ + + + 4.0.0 + + org.springframework.amqp + rabbitmq-tutorials + 2.0.0.RELEASE + jar + + rabbitmq-tutorials + Spring AMQP Implementations for RabbitMQ Tutorials + + + org.springframework.boot + spring-boot-starter-parent + 4.0.6 + + + + + UTF-8 + UTF-8 + + + + + org.springframework.boot + spring-boot-starter-amqp + + + org.springframework.amqp + spring-rabbit-stream + 4.0.3 + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + org.springframework.amqp.tutorials.RabbitAmqpTutorialsApplication + rabbitmq-stream-tutorials + ZIP + + + + + maven-compiler-plugin + 3.15.0 + + 17 + + + + + + diff --git a/spring-amqp-stream/src/main/java/org/springframework/amqp/tutorials/RabbitAmqpTutorialsApplication.java b/spring-amqp-stream/src/main/java/org/springframework/amqp/tutorials/RabbitAmqpTutorialsApplication.java new file mode 100644 index 00000000..c6c949f4 --- /dev/null +++ b/spring-amqp-stream/src/main/java/org/springframework/amqp/tutorials/RabbitAmqpTutorialsApplication.java @@ -0,0 +1,34 @@ +package org.springframework.amqp.tutorials; + +import org.springframework.boot.CommandLineRunner; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Profile; +import org.springframework.scheduling.annotation.EnableScheduling; + +@SpringBootApplication +@EnableScheduling +public class RabbitAmqpTutorialsApplication { + + @Profile("usage_message") + @Bean + public CommandLineRunner usage() { + return args -> { + System.out.println("This app uses Spring Profiles to control its behavior.\n"); + System.out.println("Options are: "); + System.out.println("java -jar target/rabbitmq-stream-tutorials.jar --spring.profiles.active=hello-world,receiver"); + System.out.println("java -jar target/rabbitmq-stream-tutorials.jar --spring.profiles.active=hello-world,sender"); + }; + } + + @Profile("!usage_message") + @Bean + public CommandLineRunner tutorial() { + return new RabbitAmqpTutorialsRunner(); + } + + public static void main(String[] args) { + SpringApplication.run(RabbitAmqpTutorialsApplication.class, args); + } +} diff --git a/spring-amqp-stream/src/main/java/org/springframework/amqp/tutorials/RabbitAmqpTutorialsRunner.java b/spring-amqp-stream/src/main/java/org/springframework/amqp/tutorials/RabbitAmqpTutorialsRunner.java new file mode 100644 index 00000000..ea9d624d --- /dev/null +++ b/spring-amqp-stream/src/main/java/org/springframework/amqp/tutorials/RabbitAmqpTutorialsRunner.java @@ -0,0 +1,22 @@ +package org.springframework.amqp.tutorials; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.CommandLineRunner; +import org.springframework.context.ConfigurableApplicationContext; + +public class RabbitAmqpTutorialsRunner implements CommandLineRunner { + + @Value("${tutorial.client.duration:0}") + private int duration; + + @Autowired + private ConfigurableApplicationContext ctx; + + @Override + public void run(String... arg0) throws Exception { + System.out.println("Ready ... running for " + duration + "ms"); + Thread.sleep(duration); + ctx.close(); + } +} diff --git a/spring-amqp-stream/src/main/java/org/springframework/amqp/tutorials/tut1/Tut1Config.java b/spring-amqp-stream/src/main/java/org/springframework/amqp/tutorials/tut1/Tut1Config.java new file mode 100644 index 00000000..4ed59f29 --- /dev/null +++ b/spring-amqp-stream/src/main/java/org/springframework/amqp/tutorials/tut1/Tut1Config.java @@ -0,0 +1,56 @@ +package org.springframework.amqp.tutorials.tut1; + +import org.springframework.amqp.rabbit.listener.RabbitListenerContainerFactory; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Profile; +import org.springframework.rabbit.stream.config.StreamRabbitListenerContainerFactory; +import org.springframework.rabbit.stream.listener.StreamListenerContainer; +import org.springframework.rabbit.stream.producer.RabbitStreamTemplate; + +import com.rabbitmq.stream.ByteCapacity; +import com.rabbitmq.stream.Environment; +import com.rabbitmq.stream.OffsetSpecification; + +@Profile({"tut1","hello-world"}) +@Configuration +public class Tut1Config { + + private final String stream = "hello-spring-stream"; + + @Bean + public Environment rabbitStreamEnvironment() { + Environment environment = Environment.builder().build(); + environment.streamCreator().stream(stream).maxLengthBytes(ByteCapacity.GB(5)).create(); + return environment; + } + + @Bean + RabbitStreamTemplate streamTemplate(Environment env) { + return new RabbitStreamTemplate(env, stream); + } + + @Bean + RabbitListenerContainerFactory nativeFactory(Environment env) { + StreamRabbitListenerContainerFactory factory = new StreamRabbitListenerContainerFactory(env); + factory.setNativeListener(true); + factory.setConsumerCustomizer((id, builder) -> { + builder.stream(stream) + .offset(OffsetSpecification.first()) + .build(); + }); + return factory; + } + + @Profile("receiver") + @Bean + public Tut1Receiver receiver() { + return new Tut1Receiver(); + } + + @Profile("sender") + @Bean + public Tut1Sender sender() { + return new Tut1Sender(); + } +} diff --git a/spring-amqp-stream/src/main/java/org/springframework/amqp/tutorials/tut1/Tut1Receiver.java b/spring-amqp-stream/src/main/java/org/springframework/amqp/tutorials/tut1/Tut1Receiver.java new file mode 100644 index 00000000..8e56f311 --- /dev/null +++ b/spring-amqp-stream/src/main/java/org/springframework/amqp/tutorials/tut1/Tut1Receiver.java @@ -0,0 +1,13 @@ +package org.springframework.amqp.tutorials.tut1; + +import org.springframework.amqp.rabbit.annotation.RabbitListener; + +import com.rabbitmq.stream.Message; + +public class Tut1Receiver { + + @RabbitListener(queues="hello-spring-stream", containerFactory="nativeFactory") + public void listen(Message message) { + System.out.println("Received message: " + new String(message.getBodyAsBinary())); + } +} diff --git a/spring-amqp-stream/src/main/java/org/springframework/amqp/tutorials/tut1/Tut1Sender.java b/spring-amqp-stream/src/main/java/org/springframework/amqp/tutorials/tut1/Tut1Sender.java new file mode 100644 index 00000000..3b01c0c1 --- /dev/null +++ b/spring-amqp-stream/src/main/java/org/springframework/amqp/tutorials/tut1/Tut1Sender.java @@ -0,0 +1,18 @@ +package org.springframework.amqp.tutorials.tut1; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.rabbit.stream.producer.RabbitStreamTemplate; +import org.springframework.scheduling.annotation.Scheduled; + +public class Tut1Sender { + + @Autowired + private RabbitStreamTemplate template; + + @Scheduled(fixedDelay = 1000, initialDelay = 500) + public void send() { + String message = "Hello World!"; + this.template.convertAndSend(message); + System.out.println(" [x] Sent '" + message + "'"); + } +} diff --git a/spring-amqp-stream/src/main/resources/application-remote.yml b/spring-amqp-stream/src/main/resources/application-remote.yml new file mode 100644 index 00000000..6914dca2 --- /dev/null +++ b/spring-amqp-stream/src/main/resources/application-remote.yml @@ -0,0 +1,5 @@ +spring: + rabbitmq: + host: rabbitserver + username: tutorial + password: tutorial diff --git a/spring-amqp-stream/src/main/resources/application.yml b/spring-amqp-stream/src/main/resources/application.yml new file mode 100644 index 00000000..452d6169 --- /dev/null +++ b/spring-amqp-stream/src/main/resources/application.yml @@ -0,0 +1,11 @@ +spring: + profiles: + active: usage_message + +logging: + level: + org: ERROR + +tutorial: + client: + duration: 10000 diff --git a/spring-amqp-stream/src/main/resources/banner.txt b/spring-amqp-stream/src/main/resources/banner.txt new file mode 100644 index 00000000..576a98ea --- /dev/null +++ b/spring-amqp-stream/src/main/resources/banner.txt @@ -0,0 +1,4 @@ + __ __ ___ +|__)_ |_ |_ .|_|\/|/ \ | |_ _ _. _ | _ +| \(_||_)|_)||_| |\_\/ | |_||_(_)| |(_||_) + \ No newline at end of file diff --git a/spring-amqp/.mvn/wrapper/maven-wrapper.jar b/spring-amqp/.mvn/wrapper/maven-wrapper.jar new file mode 100644 index 00000000..cb28b0e3 Binary files /dev/null and b/spring-amqp/.mvn/wrapper/maven-wrapper.jar differ diff --git a/spring-amqp/.mvn/wrapper/maven-wrapper.properties b/spring-amqp/.mvn/wrapper/maven-wrapper.properties new file mode 100755 index 00000000..346d645f --- /dev/null +++ b/spring-amqp/.mvn/wrapper/maven-wrapper.properties @@ -0,0 +1,18 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.6/apache-maven-3.9.6-bin.zip +wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar diff --git a/spring-amqp/README.md b/spring-amqp/README.md index baca1fb0..0e48400b 100644 --- a/spring-amqp/README.md +++ b/spring-amqp/README.md @@ -8,7 +8,7 @@ sender, receiver, and configuration. [1]: https://www.rabbitmq.com/getstarted.html ## Prerequisites -These tutorials assume RabbitMQ is [installed](http://rabbitmq.com/download.html) and running +These tutorials assume RabbitMQ is [installed](https://rabbitmq.com/download.html) and running on `localhost` using the standard port (`5672`). In case you use a different host, port or credentials, connections settings would require adjusting. @@ -17,7 +17,7 @@ a different host, port or credentials, connections settings would require adjust These tutorials use Maven. To build them run ``` -mvn package +./mvnw clean package ``` The app uses Spring Profiles to control what tutorial it's running, and if it's a Sender or Receiver. Choose which tutorial to run by using these profiles: @@ -34,7 +34,7 @@ After building with maven, run the app however you like to run boot apps. For example: ``` -java -jar target/rabbit-tutorials-*.jar --spring.profiles.active=work-queues,sender +java -jar target/rabbitmq-tutorials.jar --spring.profiles.active=work-queues,sender ``` will run the publisher part of tutorial 2 (Work Queues). @@ -43,21 +43,47 @@ For tutorials 1-5, run the consumer (receiver) followed by the publisher (sender ``` # shell 1 -java -jar target/rabbit-tutorials-*.jar --spring.profiles.active=work-queues,receiver +java -jar target/rabbitmq-tutorials.jar --spring.profiles.active=work-queues,receiver # shell 2 -java -jar target/rabbit-tutorials-*.jar --spring.profiles.active=work-queues,sender +java -jar target/rabbitmq-tutorials.jar --spring.profiles.active=work-queues,sender ``` -For tutorial 6, run the Server followed by the Client. +For tutorial 6, run the server followed by the client. -##C onfiguration +You can find more usage instructions by running the following command: + +``` +java -jar target/rabbitmq-tutorials.jar +``` + +This will display the following message: + +``` +This app uses Spring Profiles to control its behavior. + +Options are: +java -jar target/rabbitmq-tutorials.jar --spring.profiles.active=hello-world,receiver +java -jar target/rabbitmq-tutorials.jar --spring.profiles.active=hello-world,sender +java -jar target/rabbitmq-tutorials.jar --spring.profiles.active=work-queues,receiver +java -jar target/rabbitmq-tutorials.jar --spring.profiles.active=work-queues,sender +java -jar target/rabbitmq-tutorials.jar --spring.profiles.active=pub-sub,receiver +java -jar target/rabbitmq-tutorials.jar --spring.profiles.active=pub-sub,sender +java -jar target/rabbitmq-tutorials.jar --spring.profiles.active=routing,receiver +java -jar target/rabbitmq-tutorials.jar --spring.profiles.active=routing,sender +java -jar target/rabbitmq-tutorials.jar --spring.profiles.active=topics,receiver +java -jar target/rabbitmq-tutorials.jar --spring.profiles.active=topics,sender +java -jar target/rabbitmq-tutorials.jar --spring.profiles.active=rpc,client +java -jar target/rabbitmq-tutorials.jar --spring.profiles.active=rpc,server +``` + +## Configuration When running receivers/servers it's useful to set the duration the app runs to a longer time. Do this by setting the `tutorial.client.duration` property. ``` -java -jar rabbitmq-tutorials.jar --spring.profiles.active=tut2,receiver,remote --tutorial.client.duration=60000 +java -jar target/rabbitmq-tutorials.jar --spring.profiles.active=tut2,receiver,remote --tutorial.client.duration=60000 ``` By default, Spring AMQP uses localhost to connect to RabbitMQ. In the @@ -78,5 +104,5 @@ spring: To use this at runtime create a file called `application-remote.yml` (or properties) and set the properties in there. Then set the remote profile as in the example above. See the [Spring Boot](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/) -and [Spring AMQP documentation](http://docs.spring.io/spring-amqp/reference/html/) for more information on setting application +and [Spring AMQP documentation](https://docs.spring.io/spring-amqp/reference/html/) for more information on setting application properties and AMQP properties specifically. diff --git a/spring-amqp/mvnw b/spring-amqp/mvnw index a1ba1bf5..8d937f4c 100755 --- a/spring-amqp/mvnw +++ b/spring-amqp/mvnw @@ -19,7 +19,7 @@ # ---------------------------------------------------------------------------- # ---------------------------------------------------------------------------- -# Maven2 Start Up Batch script +# Apache Maven Wrapper startup batch script, version 3.2.0 # # Required ENV vars: # ------------------ @@ -27,7 +27,6 @@ # # Optional ENV vars # ----------------- -# M2_HOME - location of maven2's installed home dir # MAVEN_OPTS - parameters passed to the Java VM when running Maven # e.g. to debug Maven itself, use # set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 @@ -36,6 +35,10 @@ if [ -z "$MAVEN_SKIP_RC" ] ; then + if [ -f /usr/local/etc/mavenrc ] ; then + . /usr/local/etc/mavenrc + fi + if [ -f /etc/mavenrc ] ; then . /etc/mavenrc fi @@ -50,109 +53,56 @@ fi cygwin=false; darwin=false; mingw=false -case "`uname`" in +case "$(uname)" in CYGWIN*) cygwin=true ;; MINGW*) mingw=true;; Darwin*) darwin=true - # - # Look for the Apple JDKs first to preserve the existing behaviour, and then look - # for the new JDKs provided by Oracle. - # - if [ -z "$JAVA_HOME" ] && [ -L /System/Library/Frameworks/JavaVM.framework/Versions/CurrentJDK ] ; then - # - # Apple JDKs - # - export JAVA_HOME=/System/Library/Frameworks/JavaVM.framework/Versions/CurrentJDK/Home - fi - - if [ -z "$JAVA_HOME" ] && [ -L /System/Library/Java/JavaVirtualMachines/CurrentJDK ] ; then - # - # Apple JDKs - # - export JAVA_HOME=/System/Library/Java/JavaVirtualMachines/CurrentJDK/Contents/Home - fi - - if [ -z "$JAVA_HOME" ] && [ -L "/Library/Java/JavaVirtualMachines/CurrentJDK" ] ; then - # - # Oracle JDKs - # - export JAVA_HOME=/Library/Java/JavaVirtualMachines/CurrentJDK/Contents/Home - fi - - if [ -z "$JAVA_HOME" ] && [ -x "/usr/libexec/java_home" ]; then - # - # Apple JDKs - # - export JAVA_HOME=`/usr/libexec/java_home` - fi - ;; + # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home + # See https://developer.apple.com/library/mac/qa/qa1170/_index.html + if [ -z "$JAVA_HOME" ]; then + if [ -x "/usr/libexec/java_home" ]; then + JAVA_HOME="$(/usr/libexec/java_home)"; export JAVA_HOME + else + JAVA_HOME="/Library/Java/Home"; export JAVA_HOME + fi + fi + ;; esac if [ -z "$JAVA_HOME" ] ; then if [ -r /etc/gentoo-release ] ; then - JAVA_HOME=`java-config --jre-home` + JAVA_HOME=$(java-config --jre-home) fi fi -if [ -z "$M2_HOME" ] ; then - ## resolve links - $0 may be a link to maven's home - PRG="$0" - - # need this for relative symlinks - while [ -h "$PRG" ] ; do - ls=`ls -ld "$PRG"` - link=`expr "$ls" : '.*-> \(.*\)$'` - if expr "$link" : '/.*' > /dev/null; then - PRG="$link" - else - PRG="`dirname "$PRG"`/$link" - fi - done - - saveddir=`pwd` - - M2_HOME=`dirname "$PRG"`/.. - - # make it fully qualified - M2_HOME=`cd "$M2_HOME" && pwd` - - cd "$saveddir" - # echo Using m2 at $M2_HOME -fi - # For Cygwin, ensure paths are in UNIX format before anything is touched if $cygwin ; then - [ -n "$M2_HOME" ] && - M2_HOME=`cygpath --unix "$M2_HOME"` [ -n "$JAVA_HOME" ] && - JAVA_HOME=`cygpath --unix "$JAVA_HOME"` + JAVA_HOME=$(cygpath --unix "$JAVA_HOME") [ -n "$CLASSPATH" ] && - CLASSPATH=`cygpath --path --unix "$CLASSPATH"` + CLASSPATH=$(cygpath --path --unix "$CLASSPATH") fi -# For Migwn, ensure paths are in UNIX format before anything is touched +# For Mingw, ensure paths are in UNIX format before anything is touched if $mingw ; then - [ -n "$M2_HOME" ] && - M2_HOME="`(cd "$M2_HOME"; pwd)`" - [ -n "$JAVA_HOME" ] && - JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" - # TODO classpath? + [ -n "$JAVA_HOME" ] && [ -d "$JAVA_HOME" ] && + JAVA_HOME="$(cd "$JAVA_HOME" || (echo "cannot cd into $JAVA_HOME."; exit 1); pwd)" fi if [ -z "$JAVA_HOME" ]; then - javaExecutable="`which javac`" - if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then + javaExecutable="$(which javac)" + if [ -n "$javaExecutable" ] && ! [ "$(expr "\"$javaExecutable\"" : '\([^ ]*\)')" = "no" ]; then # readlink(1) is not available as standard on Solaris 10. - readLink=`which readlink` - if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then + readLink=$(which readlink) + if [ ! "$(expr "$readLink" : '\([^ ]*\)')" = "no" ]; then if $darwin ; then - javaHome="`dirname \"$javaExecutable\"`" - javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" + javaHome="$(dirname "\"$javaExecutable\"")" + javaExecutable="$(cd "\"$javaHome\"" && pwd -P)/javac" else - javaExecutable="`readlink -f \"$javaExecutable\"`" + javaExecutable="$(readlink -f "\"$javaExecutable\"")" fi - javaHome="`dirname \"$javaExecutable\"`" - javaHome=`expr "$javaHome" : '\(.*\)/bin'` + javaHome="$(dirname "\"$javaExecutable\"")" + javaHome=$(expr "$javaHome" : '\(.*\)/bin') JAVA_HOME="$javaHome" export JAVA_HOME fi @@ -168,7 +118,7 @@ if [ -z "$JAVACMD" ] ; then JAVACMD="$JAVA_HOME/bin/java" fi else - JAVACMD="`which java`" + JAVACMD="$(\unset -f command 2>/dev/null; \command -v java)" fi fi @@ -182,52 +132,177 @@ if [ -z "$JAVA_HOME" ] ; then echo "Warning: JAVA_HOME environment variable is not set." fi -CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher - -# For Cygwin, switch paths to Windows format before running java -if $cygwin; then - [ -n "$M2_HOME" ] && - M2_HOME=`cygpath --path --windows "$M2_HOME"` - [ -n "$JAVA_HOME" ] && - JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` - [ -n "$CLASSPATH" ] && - CLASSPATH=`cygpath --path --windows "$CLASSPATH"` -fi - # traverses directory structure from process work directory to filesystem root # first directory with .mvn subdirectory is considered project base directory find_maven_basedir() { - local basedir=$(pwd) - local wdir=$(pwd) + if [ -z "$1" ] + then + echo "Path not specified to find_maven_basedir" + return 1 + fi + + basedir="$1" + wdir="$1" while [ "$wdir" != '/' ] ; do if [ -d "$wdir"/.mvn ] ; then basedir=$wdir break fi - wdir=$(cd "$wdir/.."; pwd) + # workaround for JBEAP-8937 (on Solaris 10/Sparc) + if [ -d "${wdir}" ]; then + wdir=$(cd "$wdir/.." || exit 1; pwd) + fi + # end of workaround done - echo "${basedir}" + printf '%s' "$(cd "$basedir" || exit 1; pwd)" } # concatenates all lines of a file concat_lines() { if [ -f "$1" ]; then - echo "$(tr -s '\n' ' ' < "$1")" + # Remove \r in case we run on Windows within Git Bash + # and check out the repository with auto CRLF management + # enabled. Otherwise, we may read lines that are delimited with + # \r\n and produce $'-Xarg\r' rather than -Xarg due to word + # splitting rules. + tr -s '\r\n' ' ' < "$1" fi } -export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-$(find_maven_basedir)} +log() { + if [ "$MVNW_VERBOSE" = true ]; then + printf '%s\n' "$1" + fi +} + +BASE_DIR=$(find_maven_basedir "$(dirname "$0")") +if [ -z "$BASE_DIR" ]; then + exit 1; +fi + +MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}; export MAVEN_PROJECTBASEDIR +log "$MAVEN_PROJECTBASEDIR" + +########################################################################################## +# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +# This allows using the maven wrapper in projects that prohibit checking in binary data. +########################################################################################## +wrapperJarPath="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" +if [ -r "$wrapperJarPath" ]; then + log "Found $wrapperJarPath" +else + log "Couldn't find $wrapperJarPath, downloading it ..." + + if [ -n "$MVNW_REPOURL" ]; then + wrapperUrl="$MVNW_REPOURL/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" + else + wrapperUrl="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" + fi + while IFS="=" read -r key value; do + # Remove '\r' from value to allow usage on windows as IFS does not consider '\r' as a separator ( considers space, tab, new line ('\n'), and custom '=' ) + safeValue=$(echo "$value" | tr -d '\r') + case "$key" in (wrapperUrl) wrapperUrl="$safeValue"; break ;; + esac + done < "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties" + log "Downloading from: $wrapperUrl" + + if $cygwin; then + wrapperJarPath=$(cygpath --path --windows "$wrapperJarPath") + fi + + if command -v wget > /dev/null; then + log "Found wget ... using wget" + [ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--quiet" + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + wget $QUIET "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath" + else + wget $QUIET --http-user="$MVNW_USERNAME" --http-password="$MVNW_PASSWORD" "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath" + fi + elif command -v curl > /dev/null; then + log "Found curl ... using curl" + [ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--silent" + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + curl $QUIET -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath" + else + curl $QUIET --user "$MVNW_USERNAME:$MVNW_PASSWORD" -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath" + fi + else + log "Falling back to using Java to download" + javaSource="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.java" + javaClass="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.class" + # For Cygwin, switch paths to Windows format before running javac + if $cygwin; then + javaSource=$(cygpath --path --windows "$javaSource") + javaClass=$(cygpath --path --windows "$javaClass") + fi + if [ -e "$javaSource" ]; then + if [ ! -e "$javaClass" ]; then + log " - Compiling MavenWrapperDownloader.java ..." + ("$JAVA_HOME/bin/javac" "$javaSource") + fi + if [ -e "$javaClass" ]; then + log " - Running MavenWrapperDownloader.java ..." + ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$wrapperUrl" "$wrapperJarPath") || rm -f "$wrapperJarPath" + fi + fi + fi +fi +########################################################################################## +# End of extension +########################################################################################## + +# If specified, validate the SHA-256 sum of the Maven wrapper jar file +wrapperSha256Sum="" +while IFS="=" read -r key value; do + case "$key" in (wrapperSha256Sum) wrapperSha256Sum=$value; break ;; + esac +done < "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties" +if [ -n "$wrapperSha256Sum" ]; then + wrapperSha256Result=false + if command -v sha256sum > /dev/null; then + if echo "$wrapperSha256Sum $wrapperJarPath" | sha256sum -c > /dev/null 2>&1; then + wrapperSha256Result=true + fi + elif command -v shasum > /dev/null; then + if echo "$wrapperSha256Sum $wrapperJarPath" | shasum -a 256 -c > /dev/null 2>&1; then + wrapperSha256Result=true + fi + else + echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available." + echo "Please install either command, or disable validation by removing 'wrapperSha256Sum' from your maven-wrapper.properties." + exit 1 + fi + if [ $wrapperSha256Result = false ]; then + echo "Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised." >&2 + echo "Investigate or delete $wrapperJarPath to attempt a clean download." >&2 + echo "If you updated your Maven version, you need to update the specified wrapperSha256Sum property." >&2 + exit 1 + fi +fi + MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" +# For Cygwin, switch paths to Windows format before running java +if $cygwin; then + [ -n "$JAVA_HOME" ] && + JAVA_HOME=$(cygpath --path --windows "$JAVA_HOME") + [ -n "$CLASSPATH" ] && + CLASSPATH=$(cygpath --path --windows "$CLASSPATH") + [ -n "$MAVEN_PROJECTBASEDIR" ] && + MAVEN_PROJECTBASEDIR=$(cygpath --path --windows "$MAVEN_PROJECTBASEDIR") +fi + # Provide a "standardized" way to retrieve the CLI args that will # work with both Windows and non-Windows executions. -MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@" +MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $*" export MAVEN_CMD_LINE_ARGS WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain +# shellcheck disable=SC2086 # safe args exec "$JAVACMD" \ $MAVEN_OPTS \ + $MAVEN_DEBUG_OPTS \ -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ - "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ - ${WRAPPER_LAUNCHER} "$@" + "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ + ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" diff --git a/spring-amqp/mvnw.cmd b/spring-amqp/mvnw.cmd index 2b934e89..f80fbad3 100644 --- a/spring-amqp/mvnw.cmd +++ b/spring-amqp/mvnw.cmd @@ -1,145 +1,205 @@ -@REM ---------------------------------------------------------------------------- -@REM Licensed to the Apache Software Foundation (ASF) under one -@REM or more contributor license agreements. See the NOTICE file -@REM distributed with this work for additional information -@REM regarding copyright ownership. The ASF licenses this file -@REM to you under the Apache License, Version 2.0 (the -@REM "License"); you may not use this file except in compliance -@REM with the License. You may obtain a copy of the License at -@REM -@REM http://www.apache.org/licenses/LICENSE-2.0 -@REM -@REM Unless required by applicable law or agreed to in writing, -@REM software distributed under the License is distributed on an -@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -@REM KIND, either express or implied. See the License for the -@REM specific language governing permissions and limitations -@REM under the License. -@REM ---------------------------------------------------------------------------- - -@REM ---------------------------------------------------------------------------- -@REM Maven2 Start Up Batch script -@REM -@REM Required ENV vars: -@REM JAVA_HOME - location of a JDK home dir -@REM -@REM Optional ENV vars -@REM M2_HOME - location of maven2's installed home dir -@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands -@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a key stroke before ending -@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven -@REM e.g. to debug Maven itself, use -@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 -@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files -@REM ---------------------------------------------------------------------------- - -@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' -@echo off -@REM enable echoing my setting MAVEN_BATCH_ECHO to 'on' -@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% - -@REM set %HOME% to equivalent of $HOME -if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") - -@REM Execute a user defined script before this one -if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre -@REM check for pre script, once with legacy .bat ending and once with .cmd ending -if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat" -if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd" -:skipRcPre - -@setlocal - -set ERROR_CODE=0 - -@REM To isolate internal variables from possible post scripts, we use another setlocal -@setlocal - -@REM ==== START VALIDATION ==== -if not "%JAVA_HOME%" == "" goto OkJHome - -echo. -echo Error: JAVA_HOME not found in your environment. >&2 -echo Please set the JAVA_HOME variable in your environment to match the >&2 -echo location of your Java installation. >&2 -echo. -goto error - -:OkJHome -if exist "%JAVA_HOME%\bin\java.exe" goto init - -echo. -echo Error: JAVA_HOME is set to an invalid directory. >&2 -echo JAVA_HOME = "%JAVA_HOME%" >&2 -echo Please set the JAVA_HOME variable in your environment to match the >&2 -echo location of your Java installation. >&2 -echo. -goto error - -@REM ==== END VALIDATION ==== - -:init - -set MAVEN_CMD_LINE_ARGS=%* - -@REM Find the project base dir, i.e. the directory that contains the folder ".mvn". -@REM Fallback to current working directory if not found. - -set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% -IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir - -set EXEC_DIR=%CD% -set WDIR=%EXEC_DIR% -:findBaseDir -IF EXIST "%WDIR%"\.mvn goto baseDirFound -cd .. -IF "%WDIR%"=="%CD%" goto baseDirNotFound -set WDIR=%CD% -goto findBaseDir - -:baseDirFound -set MAVEN_PROJECTBASEDIR=%WDIR% -cd "%EXEC_DIR%" -goto endDetectBaseDir - -:baseDirNotFound -set MAVEN_PROJECTBASEDIR=%EXEC_DIR% -cd "%EXEC_DIR%" - -:endDetectBaseDir - -IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig - -@setlocal EnableExtensions EnableDelayedExpansion -for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a -@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% - -:endReadAdditionalConfig - -SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" - -set WRAPPER_JAR="".\.mvn\wrapper\maven-wrapper.jar"" -set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain - -%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CMD_LINE_ARGS% -if ERRORLEVEL 1 goto error -goto end - -:error -set ERROR_CODE=1 - -:end -@endlocal & set ERROR_CODE=%ERROR_CODE% - -if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost -@REM check for post script, once with legacy .bat ending and once with .cmd ending -if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat" -if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd" -:skipRcPost - -@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' -if "%MAVEN_BATCH_PAUSE%" == "on" pause - -if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE% - -exit /B %ERROR_CODE% \ No newline at end of file +@REM ---------------------------------------------------------------------------- +@REM Licensed to the Apache Software Foundation (ASF) under one +@REM or more contributor license agreements. See the NOTICE file +@REM distributed with this work for additional information +@REM regarding copyright ownership. The ASF licenses this file +@REM to you under the Apache License, Version 2.0 (the +@REM "License"); you may not use this file except in compliance +@REM with the License. You may obtain a copy of the License at +@REM +@REM http://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, +@REM software distributed under the License is distributed on an +@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +@REM KIND, either express or implied. See the License for the +@REM specific language governing permissions and limitations +@REM under the License. +@REM ---------------------------------------------------------------------------- + +@REM ---------------------------------------------------------------------------- +@REM Apache Maven Wrapper startup batch script, version 3.2.0 +@REM +@REM Required ENV vars: +@REM JAVA_HOME - location of a JDK home dir +@REM +@REM Optional ENV vars +@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands +@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending +@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven +@REM e.g. to debug Maven itself, use +@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files +@REM ---------------------------------------------------------------------------- + +@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' +@echo off +@REM set title of command window +title %0 +@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on' +@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% + +@REM set %HOME% to equivalent of $HOME +if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") + +@REM Execute a user defined script before this one +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre +@REM check for pre script, once with legacy .bat ending and once with .cmd ending +if exist "%USERPROFILE%\mavenrc_pre.bat" call "%USERPROFILE%\mavenrc_pre.bat" %* +if exist "%USERPROFILE%\mavenrc_pre.cmd" call "%USERPROFILE%\mavenrc_pre.cmd" %* +:skipRcPre + +@setlocal + +set ERROR_CODE=0 + +@REM To isolate internal variables from possible post scripts, we use another setlocal +@setlocal + +@REM ==== START VALIDATION ==== +if not "%JAVA_HOME%" == "" goto OkJHome + +echo. +echo Error: JAVA_HOME not found in your environment. >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +:OkJHome +if exist "%JAVA_HOME%\bin\java.exe" goto init + +echo. +echo Error: JAVA_HOME is set to an invalid directory. >&2 +echo JAVA_HOME = "%JAVA_HOME%" >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +@REM ==== END VALIDATION ==== + +:init + +@REM Find the project base dir, i.e. the directory that contains the folder ".mvn". +@REM Fallback to current working directory if not found. + +set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% +IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir + +set EXEC_DIR=%CD% +set WDIR=%EXEC_DIR% +:findBaseDir +IF EXIST "%WDIR%"\.mvn goto baseDirFound +cd .. +IF "%WDIR%"=="%CD%" goto baseDirNotFound +set WDIR=%CD% +goto findBaseDir + +:baseDirFound +set MAVEN_PROJECTBASEDIR=%WDIR% +cd "%EXEC_DIR%" +goto endDetectBaseDir + +:baseDirNotFound +set MAVEN_PROJECTBASEDIR=%EXEC_DIR% +cd "%EXEC_DIR%" + +:endDetectBaseDir + +IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig + +@setlocal EnableExtensions EnableDelayedExpansion +for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a +@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% + +:endReadAdditionalConfig + +SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" +set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" +set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +set WRAPPER_URL="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" + +FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( + IF "%%A"=="wrapperUrl" SET WRAPPER_URL=%%B +) + +@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +@REM This allows using the maven wrapper in projects that prohibit checking in binary data. +if exist %WRAPPER_JAR% ( + if "%MVNW_VERBOSE%" == "true" ( + echo Found %WRAPPER_JAR% + ) +) else ( + if not "%MVNW_REPOURL%" == "" ( + SET WRAPPER_URL="%MVNW_REPOURL%/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" + ) + if "%MVNW_VERBOSE%" == "true" ( + echo Couldn't find %WRAPPER_JAR%, downloading it ... + echo Downloading from: %WRAPPER_URL% + ) + + powershell -Command "&{"^ + "$webclient = new-object System.Net.WebClient;"^ + "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ + "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ + "}"^ + "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%WRAPPER_URL%', '%WRAPPER_JAR%')"^ + "}" + if "%MVNW_VERBOSE%" == "true" ( + echo Finished downloading %WRAPPER_JAR% + ) +) +@REM End of extension + +@REM If specified, validate the SHA-256 sum of the Maven wrapper jar file +SET WRAPPER_SHA_256_SUM="" +FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( + IF "%%A"=="wrapperSha256Sum" SET WRAPPER_SHA_256_SUM=%%B +) +IF NOT %WRAPPER_SHA_256_SUM%=="" ( + powershell -Command "&{"^ + "$hash = (Get-FileHash \"%WRAPPER_JAR%\" -Algorithm SHA256).Hash.ToLower();"^ + "If('%WRAPPER_SHA_256_SUM%' -ne $hash){"^ + " Write-Output 'Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised.';"^ + " Write-Output 'Investigate or delete %WRAPPER_JAR% to attempt a clean download.';"^ + " Write-Output 'If you updated your Maven version, you need to update the specified wrapperSha256Sum property.';"^ + " exit 1;"^ + "}"^ + "}" + if ERRORLEVEL 1 goto error +) + +@REM Provide a "standardized" way to retrieve the CLI args that will +@REM work with both Windows and non-Windows executions. +set MAVEN_CMD_LINE_ARGS=%* + +%MAVEN_JAVA_EXE% ^ + %JVM_CONFIG_MAVEN_PROPS% ^ + %MAVEN_OPTS% ^ + %MAVEN_DEBUG_OPTS% ^ + -classpath %WRAPPER_JAR% ^ + "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" ^ + %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* +if ERRORLEVEL 1 goto error +goto end + +:error +set ERROR_CODE=1 + +:end +@endlocal & set ERROR_CODE=%ERROR_CODE% + +if not "%MAVEN_SKIP_RC%"=="" goto skipRcPost +@REM check for post script, once with legacy .bat ending and once with .cmd ending +if exist "%USERPROFILE%\mavenrc_post.bat" call "%USERPROFILE%\mavenrc_post.bat" +if exist "%USERPROFILE%\mavenrc_post.cmd" call "%USERPROFILE%\mavenrc_post.cmd" +:skipRcPost + +@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' +if "%MAVEN_BATCH_PAUSE%"=="on" pause + +if "%MAVEN_TERMINATE_CMD%"=="on" exit %ERROR_CODE% + +cmd /C exit /B %ERROR_CODE% diff --git a/spring-amqp/pom.xml b/spring-amqp/pom.xml index 84d0c7f9..35a27e03 100644 --- a/spring-amqp/pom.xml +++ b/spring-amqp/pom.xml @@ -1,78 +1,55 @@ - 4.0.0 - - org.springframework.amqp - rabbit-tutorials - 1.7.1.RELEASE - jar - - rabbitmq-tutorials - Spring AMQP Implementations for RabbitMQ Tutorials - - - org.springframework.boot - spring-boot-starter-parent - 1.5.2.RELEASE - - - - - UTF-8 - 1.8 - - - - - org.springframework.boot - spring-boot-starter-amqp - - - - org.springframework.boot - spring-boot-starter-test - test - - - - - - - org.springframework.boot - spring-boot-maven-plugin - - org.springframework.amqp.tutorials.RabbitAmqpTutorialsApplication - ZIP - - - - - - - - spring-all - Spring Snapshots - https://repo.spring.io/repo - - - - - spring-snapshots - Spring Snapshots - https://repo.spring.io/snapshot - - true - - - - spring-milestones - Spring Milestones - https://repo.spring.io/milestone - - false - - - + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> + 4.0.0 + + org.springframework.amqp + rabbitmq-tutorials + 2.0.0.RELEASE + jar + + rabbitmq-tutorials + Spring AMQP Implementations for RabbitMQ Tutorials + + + org.springframework.boot + spring-boot-starter-parent + 4.0.6 + + + + + UTF-8 + UTF-8 + + + + + org.springframework.boot + spring-boot-starter-amqp + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + org.springframework.amqp.tutorials.RabbitAmqpTutorialsApplication + rabbitmq-tutorials + ZIP + + + + + maven-compiler-plugin + 3.15.0 + + 17 + + + + diff --git a/spring-amqp/scripts.txt b/spring-amqp/scripts.txt deleted file mode 100644 index 4da91af9..00000000 --- a/spring-amqp/scripts.txt +++ /dev/null @@ -1,18 +0,0 @@ -#{tut1|hello-world},{sender|receiver} -java -jar target/rabbit-tutorials-1.6.6.RELEASE.jar --spring.profiles.active=hello-world,receiver -java -jar target/rabbit-tutorials-1.6.6.RELEASE.jar --spring.profiles.active=hello-world,sender -#{tut2|work-queues},{sender|receiver} -java -jar target/rabbit-tutorials-1.6.6.RELEASE.jar --spring.profiles.active=work-queues,receiver -java -jar target/rabbit-tutorials-1.6.6.RELEASE.jar --spring.profiles.active=work-queues,sender -#{tut3|pub-sub|publish-subscribe},{sender|receiver} -java -jar target/rabbit-tutorials-1.6.6.RELEASE.jar --spring.profiles.active=pub-sub,receiver -java -jar target/rabbit-tutorials-1.6.6.RELEASE.jar --spring.profiles.active=pub-sub,sender -#{tut4|routing},{sender|receiver} -java -jar target/rabbit-tutorials-1.6.6.RELEASE.jar --spring.profiles.active=routing,receiver -java -jar target/rabbit-tutorials-1.6.6.RELEASE.jar --spring.profiles.active=routing,sender -#{tut5|topics},{sender|receiver} -java -jar target/rabbit-tutorials-1.6.6.RELEASE.jar --spring.profiles.active=topics,receiver -java -jar target/rabbit-tutorials-1.6.6.RELEASE.jar --spring.profiles.active=topics,sender -#{tut6|rpc},{client|server} -java -jar target/rabbit-tutorials-1.6.6.RELEASE.jar --spring.profiles.active=rpc,server -java -jar target/rabbit-tutorials-1.6.6.RELEASE.jar --spring.profiles.active=rpc,client diff --git a/spring-amqp/src/main/java/org/springframework/amqp/tutorials/RabbitAmqpTutorialsApplication.java b/spring-amqp/src/main/java/org/springframework/amqp/tutorials/RabbitAmqpTutorialsApplication.java index 861229ca..b7240775 100644 --- a/spring-amqp/src/main/java/org/springframework/amqp/tutorials/RabbitAmqpTutorialsApplication.java +++ b/spring-amqp/src/main/java/org/springframework/amqp/tutorials/RabbitAmqpTutorialsApplication.java @@ -1,11 +1,11 @@ /* - * Copyright 2015 the original author or authors. + * Copyright 2015-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, @@ -25,7 +25,7 @@ /** * @author Gary Russell * @author Scott Deeg - * + * @author Arnaud Cogoluègnes */ @SpringBootApplication @EnableScheduling @@ -34,27 +34,21 @@ public class RabbitAmqpTutorialsApplication { @Profile("usage_message") @Bean public CommandLineRunner usage() { - return new CommandLineRunner() { - - @Override - public void run(String... arg0) throws Exception { - System.out.println("This app uses Spring Profiles to control its behavior.\n"); - System.out.println("Options are: "); - System.out.println("java -jar rabbit-tutorials.jar --spring.profiles.active=hello-world,receiver"); - System.out.println("java -jar rabbit-tutorials.jar --spring.profiles.active=hello-world,sender"); - System.out.println("java -jar rabbit-tutorials.jar --spring.profiles.active=work-queues,receiver"); - System.out.println("java -jar rabbit-tutorials.jar --spring.profiles.active=work-queues,sender"); - System.out.println("java -jar rabbit-tutorials.jar --spring.profiles.active=pub-sub,receiver"); - System.out.println("java -jar rabbit-tutorials.jar --spring.profiles.active=pub-sub,sender"); - System.out.println("java -jar rabbit-tutorials.jar --spring.profiles.active=routing,receiver"); - System.out.println("java -jar rabbit-tutorials.jar --spring.profiles.active=routing,sender"); - System.out.println("java -jar rabbit-tutorials.jar --spring.profiles.active=topics,receiver"); - System.out.println("java -jar rabbit-tutorials.jar --spring.profiles.active=topics,sender"); - System.out.println("java -jar rabbit-tutorials.jar --spring.profiles.active=rpc,client"); - System.out.println("java -jar rabbit-tutorials.jar --spring.profiles.active=rpc,server"); - - } - + return args -> { + System.out.println("This app uses Spring Profiles to control its behavior.\n"); + System.out.println("Options are: "); + System.out.println("java -jar target/rabbitmq-tutorials.jar --spring.profiles.active=hello-world,receiver"); + System.out.println("java -jar target/rabbitmq-tutorials.jar --spring.profiles.active=hello-world,sender"); + System.out.println("java -jar target/rabbitmq-tutorials.jar --spring.profiles.active=work-queues,receiver"); + System.out.println("java -jar target/rabbitmq-tutorials.jar --spring.profiles.active=work-queues,sender"); + System.out.println("java -jar target/rabbitmq-tutorials.jar --spring.profiles.active=pub-sub,receiver"); + System.out.println("java -jar target/rabbitmq-tutorials.jar --spring.profiles.active=pub-sub,sender"); + System.out.println("java -jar target/rabbitmq-tutorials.jar --spring.profiles.active=routing,receiver"); + System.out.println("java -jar target/rabbitmq-tutorials.jar --spring.profiles.active=routing,sender"); + System.out.println("java -jar target/rabbitmq-tutorials.jar --spring.profiles.active=topics,receiver"); + System.out.println("java -jar target/rabbitmq-tutorials.jar --spring.profiles.active=topics,sender"); + System.out.println("java -jar target/rabbitmq-tutorials.jar --spring.profiles.active=rpc,client"); + System.out.println("java -jar target/rabbitmq-tutorials.jar --spring.profiles.active=rpc,server"); }; } @@ -64,7 +58,7 @@ public CommandLineRunner tutorial() { return new RabbitAmqpTutorialsRunner(); } - public static void main(String[] args) throws Exception { + public static void main(String[] args) { SpringApplication.run(RabbitAmqpTutorialsApplication.class, args); } diff --git a/spring-amqp/src/main/java/org/springframework/amqp/tutorials/RabbitAmqpTutorialsRunner.java b/spring-amqp/src/main/java/org/springframework/amqp/tutorials/RabbitAmqpTutorialsRunner.java index be9b04be..1b536c54 100644 --- a/spring-amqp/src/main/java/org/springframework/amqp/tutorials/RabbitAmqpTutorialsRunner.java +++ b/spring-amqp/src/main/java/org/springframework/amqp/tutorials/RabbitAmqpTutorialsRunner.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, diff --git a/spring-amqp/src/main/java/org/springframework/amqp/tutorials/tut1/Tut1Config.java b/spring-amqp/src/main/java/org/springframework/amqp/tutorials/tut1/Tut1Config.java index 09b87f89..6b879092 100644 --- a/spring-amqp/src/main/java/org/springframework/amqp/tutorials/tut1/Tut1Config.java +++ b/spring-amqp/src/main/java/org/springframework/amqp/tutorials/tut1/Tut1Config.java @@ -1,11 +1,11 @@ /* - * Copyright 2015 the original author or authors. + * Copyright 2015-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, @@ -16,6 +16,7 @@ package org.springframework.amqp.tutorials.tut1; import org.springframework.amqp.core.Queue; +import org.springframework.amqp.core.QueueBuilder; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Profile; @@ -31,7 +32,7 @@ public class Tut1Config { @Bean public Queue hello() { - return new Queue("hello"); + return QueueBuilder.durable("hello").quorum().build(); } @Profile("receiver") diff --git a/spring-amqp/src/main/java/org/springframework/amqp/tutorials/tut1/Tut1Receiver.java b/spring-amqp/src/main/java/org/springframework/amqp/tutorials/tut1/Tut1Receiver.java index fc5721c7..27b278eb 100644 --- a/spring-amqp/src/main/java/org/springframework/amqp/tutorials/tut1/Tut1Receiver.java +++ b/spring-amqp/src/main/java/org/springframework/amqp/tutorials/tut1/Tut1Receiver.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, diff --git a/spring-amqp/src/main/java/org/springframework/amqp/tutorials/tut1/Tut1Sender.java b/spring-amqp/src/main/java/org/springframework/amqp/tutorials/tut1/Tut1Sender.java index d1010ae5..c8b9af8b 100644 --- a/spring-amqp/src/main/java/org/springframework/amqp/tutorials/tut1/Tut1Sender.java +++ b/spring-amqp/src/main/java/org/springframework/amqp/tutorials/tut1/Tut1Sender.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, diff --git a/spring-amqp/src/main/java/org/springframework/amqp/tutorials/tut2/Tut2Config.java b/spring-amqp/src/main/java/org/springframework/amqp/tutorials/tut2/Tut2Config.java index 68e47b36..6e75ed46 100644 --- a/spring-amqp/src/main/java/org/springframework/amqp/tutorials/tut2/Tut2Config.java +++ b/spring-amqp/src/main/java/org/springframework/amqp/tutorials/tut2/Tut2Config.java @@ -1,11 +1,11 @@ /* - * Copyright 2015 the original author or authors. + * Copyright 2015-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, @@ -16,6 +16,7 @@ package org.springframework.amqp.tutorials.tut2; import org.springframework.amqp.core.Queue; +import org.springframework.amqp.core.QueueBuilder; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Profile; @@ -30,7 +31,7 @@ public class Tut2Config { @Bean public Queue hello() { - return new Queue("tut.hello"); + return QueueBuilder.durable("task_queue").quorum().build(); } @Profile("receiver") diff --git a/spring-amqp/src/main/java/org/springframework/amqp/tutorials/tut2/Tut2Receiver.java b/spring-amqp/src/main/java/org/springframework/amqp/tutorials/tut2/Tut2Receiver.java index 058309dd..7587128d 100644 --- a/spring-amqp/src/main/java/org/springframework/amqp/tutorials/tut2/Tut2Receiver.java +++ b/spring-amqp/src/main/java/org/springframework/amqp/tutorials/tut2/Tut2Receiver.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, @@ -23,7 +23,7 @@ * @author Gary Russell * @author Scott Deeg */ -@RabbitListener(queues = "tut.hello") +@RabbitListener(queues = "task_queue") public class Tut2Receiver { private final int instance; @@ -45,7 +45,7 @@ public void receive(String in) throws InterruptedException { private void doWork(String in) throws InterruptedException { for (char ch : in.toCharArray()) { if (ch == '.') { - Thread.sleep(1000); + Thread.sleep(500); } } } diff --git a/spring-amqp/src/main/java/org/springframework/amqp/tutorials/tut2/Tut2Sender.java b/spring-amqp/src/main/java/org/springframework/amqp/tutorials/tut2/Tut2Sender.java index 100d113d..55a4ee21 100644 --- a/spring-amqp/src/main/java/org/springframework/amqp/tutorials/tut2/Tut2Sender.java +++ b/spring-amqp/src/main/java/org/springframework/amqp/tutorials/tut2/Tut2Sender.java @@ -1,11 +1,11 @@ /* - * Copyright 2015 the original author or authors. + * Copyright 2015-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, @@ -20,9 +20,12 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.scheduling.annotation.Scheduled; +import java.util.concurrent.atomic.AtomicInteger; + /** * @author Gary Russell * @author Scott Deeg + * @author Arnaud Cogoluègnes */ public class Tut2Sender { @@ -32,20 +35,20 @@ public class Tut2Sender { @Autowired private Queue queue; - int dots = 0; + AtomicInteger dots = new AtomicInteger(0); - int count = 0; + AtomicInteger count = new AtomicInteger(0); @Scheduled(fixedDelay = 1000, initialDelay = 500) public void send() { StringBuilder builder = new StringBuilder("Hello"); - if (dots++ == 3) { - dots = 1; + if (dots.getAndIncrement() == 4) { + dots.set(1); } - for (int i = 0; i < dots; i++) { + for (int i = 0; i < dots.get(); i++) { builder.append('.'); } - builder.append(Integer.toString(++count)); + builder.append(count.incrementAndGet()); String message = builder.toString(); template.convertAndSend(queue.getName(), message); System.out.println(" [x] Sent '" + message + "'"); diff --git a/spring-amqp/src/main/java/org/springframework/amqp/tutorials/tut3/Tut3Config.java b/spring-amqp/src/main/java/org/springframework/amqp/tutorials/tut3/Tut3Config.java index 26fca0ff..58f3ab7e 100644 --- a/spring-amqp/src/main/java/org/springframework/amqp/tutorials/tut3/Tut3Config.java +++ b/spring-amqp/src/main/java/org/springframework/amqp/tutorials/tut3/Tut3Config.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, diff --git a/spring-amqp/src/main/java/org/springframework/amqp/tutorials/tut3/Tut3Receiver.java b/spring-amqp/src/main/java/org/springframework/amqp/tutorials/tut3/Tut3Receiver.java index f2bbe8cb..170a4584 100644 --- a/spring-amqp/src/main/java/org/springframework/amqp/tutorials/tut3/Tut3Receiver.java +++ b/spring-amqp/src/main/java/org/springframework/amqp/tutorials/tut3/Tut3Receiver.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, diff --git a/spring-amqp/src/main/java/org/springframework/amqp/tutorials/tut3/Tut3Sender.java b/spring-amqp/src/main/java/org/springframework/amqp/tutorials/tut3/Tut3Sender.java index 126bbfdd..a1f0ad93 100644 --- a/spring-amqp/src/main/java/org/springframework/amqp/tutorials/tut3/Tut3Sender.java +++ b/spring-amqp/src/main/java/org/springframework/amqp/tutorials/tut3/Tut3Sender.java @@ -1,11 +1,11 @@ /* - * Copyright 2015 the original author or authors. + * Copyright 2015-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, @@ -20,9 +20,12 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.scheduling.annotation.Scheduled; +import java.util.concurrent.atomic.AtomicInteger; + /** * @author Gary Russell * @author Scott Deeg + * @author Arnaud Cogoluègnes */ public class Tut3Sender { @@ -32,20 +35,20 @@ public class Tut3Sender { @Autowired private FanoutExchange fanout; - int dots = 0; + AtomicInteger dots = new AtomicInteger(0); - int count = 0; + AtomicInteger count = new AtomicInteger(0); @Scheduled(fixedDelay = 1000, initialDelay = 500) public void send() { StringBuilder builder = new StringBuilder("Hello"); - if (dots++ == 3) { - dots = 1; + if (dots.getAndIncrement() == 3) { + dots.set(1); } - for (int i = 0; i < dots; i++) { + for (int i = 0; i < dots.get(); i++) { builder.append('.'); } - builder.append(Integer.toString(++count)); + builder.append(count.incrementAndGet()); String message = builder.toString(); template.convertAndSend(fanout.getName(), "", message); System.out.println(" [x] Sent '" + message + "'"); diff --git a/spring-amqp/src/main/java/org/springframework/amqp/tutorials/tut4/Tut4Config.java b/spring-amqp/src/main/java/org/springframework/amqp/tutorials/tut4/Tut4Config.java index 4f5b311e..c746240f 100644 --- a/spring-amqp/src/main/java/org/springframework/amqp/tutorials/tut4/Tut4Config.java +++ b/spring-amqp/src/main/java/org/springframework/amqp/tutorials/tut4/Tut4Config.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, diff --git a/spring-amqp/src/main/java/org/springframework/amqp/tutorials/tut4/Tut4Receiver.java b/spring-amqp/src/main/java/org/springframework/amqp/tutorials/tut4/Tut4Receiver.java index fa384c14..9239a719 100644 --- a/spring-amqp/src/main/java/org/springframework/amqp/tutorials/tut4/Tut4Receiver.java +++ b/spring-amqp/src/main/java/org/springframework/amqp/tutorials/tut4/Tut4Receiver.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, diff --git a/spring-amqp/src/main/java/org/springframework/amqp/tutorials/tut4/Tut4Sender.java b/spring-amqp/src/main/java/org/springframework/amqp/tutorials/tut4/Tut4Sender.java index 9d60374a..9b84bcee 100644 --- a/spring-amqp/src/main/java/org/springframework/amqp/tutorials/tut4/Tut4Sender.java +++ b/spring-amqp/src/main/java/org/springframework/amqp/tutorials/tut4/Tut4Sender.java @@ -1,11 +1,11 @@ /* - * Copyright 2015 the original author or authors. + * Copyright 2015-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, @@ -20,9 +20,12 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.scheduling.annotation.Scheduled; +import java.util.concurrent.atomic.AtomicInteger; + /** * @author Gary Russell * @author Scott Deeg + * @author Arnaud Cogoluègnes */ public class Tut4Sender { @@ -32,21 +35,24 @@ public class Tut4Sender { @Autowired private DirectExchange direct; - private int index; + AtomicInteger index = new AtomicInteger(0); - private int count; + AtomicInteger count = new AtomicInteger(0); private final String[] keys = {"orange", "black", "green"}; @Scheduled(fixedDelay = 1000, initialDelay = 500) public void send() { - StringBuilder builder = new StringBuilder("Hello to "); - if (++this.index == 3) { - this.index = 0; + StringBuilder builder = new StringBuilder("Hello to"); + if (this.index.incrementAndGet() == 3) { + this.index.set(0); + } + for (int i = 0; i < this.index.get() + 1; i++) { + builder.append('.'); } - String key = keys[this.index]; + String key = keys[this.index.get()]; builder.append(key).append(' '); - builder.append(Integer.toString(++this.count)); + builder.append(this.count.incrementAndGet()); String message = builder.toString(); template.convertAndSend(direct.getName(), key, message); System.out.println(" [x] Sent '" + message + "'"); diff --git a/spring-amqp/src/main/java/org/springframework/amqp/tutorials/tut5/Tut5Config.java b/spring-amqp/src/main/java/org/springframework/amqp/tutorials/tut5/Tut5Config.java index 10253b8a..1846af3c 100644 --- a/spring-amqp/src/main/java/org/springframework/amqp/tutorials/tut5/Tut5Config.java +++ b/spring-amqp/src/main/java/org/springframework/amqp/tutorials/tut5/Tut5Config.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, diff --git a/spring-amqp/src/main/java/org/springframework/amqp/tutorials/tut5/Tut5Receiver.java b/spring-amqp/src/main/java/org/springframework/amqp/tutorials/tut5/Tut5Receiver.java index 26460f83..0c0231ef 100644 --- a/spring-amqp/src/main/java/org/springframework/amqp/tutorials/tut5/Tut5Receiver.java +++ b/spring-amqp/src/main/java/org/springframework/amqp/tutorials/tut5/Tut5Receiver.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, diff --git a/spring-amqp/src/main/java/org/springframework/amqp/tutorials/tut5/Tut5Sender.java b/spring-amqp/src/main/java/org/springframework/amqp/tutorials/tut5/Tut5Sender.java index 48a20aa7..de1027c4 100644 --- a/spring-amqp/src/main/java/org/springframework/amqp/tutorials/tut5/Tut5Sender.java +++ b/spring-amqp/src/main/java/org/springframework/amqp/tutorials/tut5/Tut5Sender.java @@ -1,11 +1,11 @@ /* - * Copyright 2015 the original author or authors. + * Copyright 2015-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, @@ -20,9 +20,12 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.scheduling.annotation.Scheduled; +import java.util.concurrent.atomic.AtomicInteger; + /** * @author Gary Russell * @author Scott Deeg + * @author Arnaud Cogoluègnes */ public class Tut5Sender { @@ -32,10 +35,9 @@ public class Tut5Sender { @Autowired private TopicExchange topic; + AtomicInteger index = new AtomicInteger(0); - private int index; - - private int count; + AtomicInteger count = new AtomicInteger(0); private final String[] keys = {"quick.orange.rabbit", "lazy.orange.elephant", "quick.orange.fox", "lazy.brown.fox", "lazy.pink.rabbit", "quick.brown.fox"}; @@ -43,12 +45,12 @@ public class Tut5Sender { @Scheduled(fixedDelay = 1000, initialDelay = 500) public void send() { StringBuilder builder = new StringBuilder("Hello to "); - if (++this.index == keys.length) { - this.index = 0; + if (this.index.incrementAndGet() == keys.length) { + this.index.set(0); } - String key = keys[this.index]; + String key = keys[this.index.get()]; builder.append(key).append(' '); - builder.append(Integer.toString(++this.count)); + builder.append(this.count.incrementAndGet()); String message = builder.toString(); template.convertAndSend(topic.getName(), key, message); System.out.println(" [x] Sent '" + message + "'"); diff --git a/spring-amqp/src/main/java/org/springframework/amqp/tutorials/tut6/Tut6Client.java b/spring-amqp/src/main/java/org/springframework/amqp/tutorials/tut6/Tut6Client.java index 3ebaded7..7b7a894a 100644 --- a/spring-amqp/src/main/java/org/springframework/amqp/tutorials/tut6/Tut6Client.java +++ b/spring-amqp/src/main/java/org/springframework/amqp/tutorials/tut6/Tut6Client.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, diff --git a/spring-amqp/src/main/java/org/springframework/amqp/tutorials/tut6/Tut6Config.java b/spring-amqp/src/main/java/org/springframework/amqp/tutorials/tut6/Tut6Config.java index ded22fee..43ca8da1 100644 --- a/spring-amqp/src/main/java/org/springframework/amqp/tutorials/tut6/Tut6Config.java +++ b/spring-amqp/src/main/java/org/springframework/amqp/tutorials/tut6/Tut6Config.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, @@ -19,6 +19,7 @@ import org.springframework.amqp.core.BindingBuilder; import org.springframework.amqp.core.DirectExchange; import org.springframework.amqp.core.Queue; +import org.springframework.amqp.core.QueueBuilder; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Profile; @@ -37,7 +38,7 @@ private static class ClientConfig { @Bean public DirectExchange exchange() { - return new DirectExchange("tut.rpc"); + return new DirectExchange("rpc"); } @Bean @@ -52,12 +53,12 @@ private static class ServerConfig { @Bean public Queue queue() { - return new Queue("tut.rpc.requests"); + return QueueBuilder.durable("rpc_queue").quorum().build(); } @Bean public DirectExchange exchange() { - return new DirectExchange("tut.rpc"); + return new DirectExchange("rpc"); } @Bean diff --git a/spring-amqp/src/main/java/org/springframework/amqp/tutorials/tut6/Tut6Server.java b/spring-amqp/src/main/java/org/springframework/amqp/tutorials/tut6/Tut6Server.java index 1c83acfc..4f161c79 100644 --- a/spring-amqp/src/main/java/org/springframework/amqp/tutorials/tut6/Tut6Server.java +++ b/spring-amqp/src/main/java/org/springframework/amqp/tutorials/tut6/Tut6Server.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, @@ -23,8 +23,8 @@ */ public class Tut6Server { - @RabbitListener(queues = "tut.rpc.requests") - // @SendTo("tut.rpc.replies") used when the client doesn't set replyTo. + @RabbitListener(queues = "rpc_queue") + // @SendTo("rpc_replies") used when the client doesn't set replyTo. public int fibonacci(int n) { System.out.println(" [x] Received request for " + n); int result = fib(n); diff --git a/spring-amqp/src/main/resources/application.yml b/spring-amqp/src/main/resources/application.yml index fce427c5..452d6169 100644 --- a/spring-amqp/src/main/resources/application.yml +++ b/spring-amqp/src/main/resources/application.yml @@ -8,4 +8,4 @@ logging: tutorial: client: - duration: 600000 + duration: 10000 diff --git a/swift/.gitignore b/swift/.gitignore index cf28dc93..13eba632 100644 --- a/swift/.gitignore +++ b/swift/.gitignore @@ -1,4 +1,13 @@ -tutorial?/Carthage/Build -tutorial?/Carthage/Checkouts -*xcuserdata* +# Swift Package Manager +.build/ +.swiftpm/ +Package.resolved + +# Xcode +*.xcodeproj +*.xcworkspace +xcuserdata/ +DerivedData/ + +# macOS .DS_Store diff --git a/swift/AGENTS.md b/swift/AGENTS.md new file mode 100644 index 00000000..64d8eaaa --- /dev/null +++ b/swift/AGENTS.md @@ -0,0 +1,18 @@ +# Agent Notes: Swift Tutorials + +## Stdout Buffering (non-TTY environments) + +Swift's `print` is block-buffered when stdout is not connected to a TTY. Consumer +binaries will connect and receive messages correctly but produce no visible output. + +To work around this, set `NSUnbufferedIO=YES` before running +any compiled tutorial binary, like so: + +```sh +NSUnbufferedIO=YES .build/debug/Receive & +NSUnbufferedIO=YES .build/debug/Worker & +NSUnbufferedIO=YES .build/debug/RPCServer & +``` + +This applies to all binaries, including producers, though producers are less affected +since they exit promptly after sending. diff --git a/swift/Package.swift b/swift/Package.swift new file mode 100644 index 00000000..5ccbc005 --- /dev/null +++ b/swift/Package.swift @@ -0,0 +1,67 @@ +// swift-tools-version: 6.0 +// +// RabbitMQ Swift Tutorials +// https://www.rabbitmq.com/tutorials +// +// Copyright (c) 2016-2026 Broadcom. All Rights Reserved. +// The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// Licensed under the Apache License, version 2.0 (the "License"); +// you may not use these files except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import PackageDescription + +let package = Package( + name: "RabbitMQTutorials", + platforms: [.macOS(.v14), .iOS(.v17), .tvOS(.v17), .watchOS(.v10), .visionOS(.v1)], + dependencies: [ + .package(url: "https://github.com/michaelklishin/bunny-swift.git", from: "0.13.0") + ], + targets: [ + // Tutorial 1: Hello World + .executableTarget( + name: "Send", dependencies: [.product(name: "BunnySwift", package: "bunny-swift")]), + .executableTarget( + name: "Receive", dependencies: [.product(name: "BunnySwift", package: "bunny-swift")]), + + // Tutorial 2: Work Queues + .executableTarget( + name: "NewTask", dependencies: [.product(name: "BunnySwift", package: "bunny-swift")]), + .executableTarget( + name: "Worker", dependencies: [.product(name: "BunnySwift", package: "bunny-swift")]), + + // Tutorial 3: Publish/Subscribe + .executableTarget( + name: "EmitLog", dependencies: [.product(name: "BunnySwift", package: "bunny-swift")]), + .executableTarget( + name: "ReceiveLogs", dependencies: [.product(name: "BunnySwift", package: "bunny-swift")]), + + // Tutorial 4: Routing + .executableTarget( + name: "EmitLogDirect", dependencies: [.product(name: "BunnySwift", package: "bunny-swift")]), + .executableTarget( + name: "ReceiveLogsDirect", + dependencies: [.product(name: "BunnySwift", package: "bunny-swift")]), + + // Tutorial 5: Topics + .executableTarget( + name: "EmitLogTopic", dependencies: [.product(name: "BunnySwift", package: "bunny-swift")]), + .executableTarget( + name: "ReceiveLogsTopic", dependencies: [.product(name: "BunnySwift", package: "bunny-swift")]), + + // Tutorial 6: RPC + .executableTarget( + name: "RPCClient", dependencies: [.product(name: "BunnySwift", package: "bunny-swift")]), + .executableTarget( + name: "RPCServer", dependencies: [.product(name: "BunnySwift", package: "bunny-swift")]), + ] +) diff --git a/swift/README.md b/swift/README.md index b33c71b3..3783be7d 100644 --- a/swift/README.md +++ b/swift/README.md @@ -1,52 +1,109 @@ # Swift code for RabbitMQ tutorials -Swift code examples for the [RabbitMQ tutorials](http://www.rabbitmq.com/getstarted.html). +Swift code examples for the [RabbitMQ tutorials](https://www.rabbitmq.com/tutorials). + +These tutorials use [BunnySwift](https://github.com/michaelklishin/bunny-swift), a modern Swift 6 RabbitMQ client with async/await support. ## Requirements -To run this code you need -[Carthage](https://github.com/Carthage/Carthage) to pull down dependencies, -which include the -[Objective-C client](https://github.com/rabbitmq/rabbitmq-objc-client) itself. +- Swift 6.0 or later +- macOS 14+ (or iOS 17+, tvOS 17+, watchOS 10+, visionOS 1+) +- A running RabbitMQ server on localhost + +## Building -If you have Homebrew installed, simply: +Build all tutorials: ```sh -brew install carthage +swift build ``` -You also need a running RabbitMQ server on localhost. +## Running the Tutorials + +### Tutorial 1: Hello World -## Installation +In one terminal, start the receiver: -Each tutorial has its own Xcode project. Before the projects can be run, you -need to download and build their dependencies. +```sh +swift run Receive +``` -For example, to install tutorial 1: +In another terminal, send a message: ```sh -cd tutorial1 -carthage bootstrap --platform iOS +swift run Send ``` -You should then be able to open [the project](tutorial1/tutorial1.xcodeproj) in Xcode and hit Run. Output is -NSLogged. +### Tutorial 2: Work Queues -See [ViewController.swift](tutorial1/tutorial1/ViewController.swift) for the -implementation (each tutorial has its own `ViewController.swift`). +Start one or more workers: -## Running the tutorials on master +```sh +swift run Worker +``` -If you're QAing a change, or just want to run these tutorials on the master version of the client, follow these steps. +Send tasks with varying workloads (dots indicate seconds of work): -### Edit `Cartfile` +```sh +swift run NewTask "A simple task." +swift run NewTask "A longer task..." +swift run NewTask "A very long task....." +``` -Change the version number to the word "master" +### Tutorial 3: Publish/Subscribe + +Start one or more log receivers: + +```sh +swift run ReceiveLogs +``` + +Emit log messages: + +```sh +swift run EmitLog "Hello subscribers!" +``` -### Clear Carthage cache and update +### Tutorial 4: Routing -`rm -rf ~/Library/Caches/org.carthage.CarthageKit && carthage update --platform iOS` +Subscribe to specific severity levels: + +```sh +swift run ReceiveLogsDirect error warning +``` + +Emit logs with severity: + +```sh +swift run EmitLogDirect info "Just an info message" +swift run EmitLogDirect warning "This is a warning" +swift run EmitLogDirect error "This is an error!" +``` + +### Tutorial 5: Topics + +Subscribe using topic patterns (`*` matches one word, `#` matches zero or more): + +```sh +swift run ReceiveLogsTopic "kern.*" +swift run ReceiveLogsTopic "*.critical" +swift run ReceiveLogsTopic "#" +``` + +Emit logs with topic routing keys: + +```sh +swift run EmitLogTopic "kern.info" "Kernel info" +swift run EmitLogTopic "kern.critical" "Kernel critical error" +swift run EmitLogTopic "auth.critical" "Authentication failure" +``` -### Rebuild the project in Xcode +## Tutorial Source Files -If there have been breaking changes, you might now need to make changes to the tutorial. +| Tutorial | Producer | Consumer | +|----------|----------|----------| +| 1. Hello World | [Send](Sources/Send/main.swift) | [Receive](Sources/Receive/main.swift) | +| 2. Work Queues | [NewTask](Sources/NewTask/main.swift) | [Worker](Sources/Worker/main.swift) | +| 3. Pub/Sub | [EmitLog](Sources/EmitLog/main.swift) | [ReceiveLogs](Sources/ReceiveLogs/main.swift) | +| 4. Routing | [EmitLogDirect](Sources/EmitLogDirect/main.swift) | [ReceiveLogsDirect](Sources/ReceiveLogsDirect/main.swift) | +| 5. Topics | [EmitLogTopic](Sources/EmitLogTopic/main.swift) | [ReceiveLogsTopic](Sources/ReceiveLogsTopic/main.swift) | diff --git a/swift/Sources/EmitLog/main.swift b/swift/Sources/EmitLog/main.swift new file mode 100644 index 00000000..65e9b7e2 --- /dev/null +++ b/swift/Sources/EmitLog/main.swift @@ -0,0 +1,30 @@ +// +// RabbitMQ Swift Tutorials +// Tutorial 3: Publish/Subscribe - EmitLog +// +// https://www.rabbitmq.com/tutorials/tutorial-three-swift +// + +import BunnySwift +import Foundation + +@main +struct EmitLog { + static func main() async throws { + let connection = try await Connection.open() + let channel = try await connection.openChannel() + let exchange = try await channel.fanout("logs") + + let args = CommandLine.arguments.dropFirst() + let message = args.isEmpty ? "Hello World!" : args.joined(separator: " ") + + try await channel.basicPublish( + body: Data(message.utf8), + exchange: exchange.name, + routingKey: "" + ) + print(" [x] Sent '\(message)'") + + try await connection.close() + } +} diff --git a/swift/Sources/EmitLogDirect/main.swift b/swift/Sources/EmitLogDirect/main.swift new file mode 100644 index 00000000..f59a02f8 --- /dev/null +++ b/swift/Sources/EmitLogDirect/main.swift @@ -0,0 +1,31 @@ +// +// RabbitMQ Swift Tutorials +// Tutorial 4: Routing - EmitLogDirect +// +// https://www.rabbitmq.com/tutorials/tutorial-four-swift +// + +import BunnySwift +import Foundation + +@main +struct EmitLogDirect { + static func main() async throws { + let connection = try await Connection.open() + let channel = try await connection.openChannel() + let exchange = try await channel.direct("direct_logs") + + var args = Array(CommandLine.arguments.dropFirst()) + let severity = args.isEmpty ? "info" : args.removeFirst() + let message = args.isEmpty ? "Hello World!" : args.joined(separator: " ") + + try await channel.basicPublish( + body: Data(message.utf8), + exchange: exchange.name, + routingKey: severity + ) + print(" [x] Sent '\(severity):\(message)'") + + try await connection.close() + } +} diff --git a/swift/Sources/EmitLogTopic/main.swift b/swift/Sources/EmitLogTopic/main.swift new file mode 100644 index 00000000..cdf66cb7 --- /dev/null +++ b/swift/Sources/EmitLogTopic/main.swift @@ -0,0 +1,31 @@ +// +// RabbitMQ Swift Tutorials +// Tutorial 5: Topics - EmitLogTopic +// +// https://www.rabbitmq.com/tutorials/tutorial-five-swift +// + +import BunnySwift +import Foundation + +@main +struct EmitLogTopic { + static func main() async throws { + let connection = try await Connection.open() + let channel = try await connection.openChannel() + let exchange = try await channel.topic("topic_logs") + + var args = Array(CommandLine.arguments.dropFirst()) + let routingKey = args.isEmpty ? "anonymous.info" : args.removeFirst() + let message = args.isEmpty ? "Hello World!" : args.joined(separator: " ") + + try await channel.basicPublish( + body: Data(message.utf8), + exchange: exchange.name, + routingKey: routingKey + ) + print(" [x] Sent '\(routingKey):\(message)'") + + try await connection.close() + } +} diff --git a/swift/Sources/NewTask/main.swift b/swift/Sources/NewTask/main.swift new file mode 100644 index 00000000..30293692 --- /dev/null +++ b/swift/Sources/NewTask/main.swift @@ -0,0 +1,30 @@ +// +// RabbitMQ Swift Tutorials +// Tutorial 2: Work Queues - NewTask +// +// https://www.rabbitmq.com/tutorials/tutorial-two-swift +// + +import BunnySwift +import Foundation + +@main +struct NewTask { + static func main() async throws { + let connection = try await Connection.open() + let channel = try await connection.openChannel() + let queue = try await channel.queue("task_queue", type: .quorum, durable: true) + + let args = CommandLine.arguments.dropFirst() + let message = args.isEmpty ? "Hello World!" : args.joined(separator: " ") + + try await channel.basicPublish( + body: Data(message.utf8), + routingKey: queue.name, + properties: .persistent + ) + print(" [x] Sent '\(message)'") + + try await connection.close() + } +} diff --git a/swift/Sources/RPCClient/main.swift b/swift/Sources/RPCClient/main.swift new file mode 100644 index 00000000..3df978e4 --- /dev/null +++ b/swift/Sources/RPCClient/main.swift @@ -0,0 +1,47 @@ +// +// RabbitMQ Swift Tutorials +// Tutorial 6: RPC - RPCClient +// +// https://www.rabbitmq.com/tutorials/tutorial-six-swift +// + +import BunnySwift +import Foundation + +@main +struct RPCClient { + static func main() async throws { + let n = Int(CommandLine.arguments.dropFirst().first ?? "30") ?? 30 + + let connection = try await Connection.open() + let channel = try await connection.openChannel() + + let replyQueue = try await channel.queue("", exclusive: true) + let correlationId = UUID().uuidString + + print(" [x] Requesting fib(\(n))") + + try await channel.basicPublish( + body: Data("\(n)".utf8), + routingKey: "rpc_queue", + properties: BasicProperties( + correlationId: correlationId, + replyTo: replyQueue.name + ) + ) + + let consumer = try await channel.basicConsume( + queue: replyQueue.name, + acknowledgementMode: .automatic + ) + for try await message in consumer { + if message.properties.correlationId == correlationId { + let result = message.bodyString ?? "?" + print(" [.] Got \(result)") + break + } + } + + try await connection.close() + } +} diff --git a/swift/Sources/RPCServer/main.swift b/swift/Sources/RPCServer/main.swift new file mode 100644 index 00000000..af521a91 --- /dev/null +++ b/swift/Sources/RPCServer/main.swift @@ -0,0 +1,44 @@ +// +// RabbitMQ Swift Tutorials +// Tutorial 6: RPC - RPCServer +// +// https://www.rabbitmq.com/tutorials/tutorial-six-swift +// + +import BunnySwift +import Foundation + +func fibonacci(_ n: Int) -> Int { + if n <= 1 { return n } + return fibonacci(n - 1) + fibonacci(n - 2) +} + +@main +struct RPCServer { + static func main() async throws { + let connection = try await Connection.open() + let channel = try await connection.openChannel() + + let queue = try await channel.queue("rpc_queue", type: .quorum, durable: true) + try await channel.basicQos(prefetchCount: 1) + + print(" [x] Awaiting RPC requests") + + let consumer = try await channel.basicConsume(queue: queue.name) + for try await message in consumer { + let n = Int(message.bodyString ?? "0") ?? 0 + print(" [.] fib(\(n))") + + let result = fibonacci(n) + + if let replyTo = message.properties.replyTo { + try await channel.basicPublish( + body: Data("\(result)".utf8), + routingKey: replyTo, + properties: BasicProperties(correlationId: message.properties.correlationId) + ) + } + try await message.ack() + } + } +} diff --git a/swift/Sources/Receive/main.swift b/swift/Sources/Receive/main.swift new file mode 100644 index 00000000..715c05e6 --- /dev/null +++ b/swift/Sources/Receive/main.swift @@ -0,0 +1,27 @@ +// +// RabbitMQ Swift Tutorials +// Tutorial 1: Hello World - Receive +// +// https://www.rabbitmq.com/tutorials/tutorial-one-swift +// + +import BunnySwift + +@main +struct Receive { + static func main() async throws { + let connection = try await Connection.open() + let channel = try await connection.openChannel() + let queue = try await channel.queue("hello", type: .quorum, durable: true) + + print(" [*] Waiting for messages. To exit press CTRL+C") + + let consumer = try await channel.basicConsume( + queue: queue.name, + acknowledgementMode: .automatic + ) + for try await message in consumer { + print(" [x] Received '\(message.bodyString ?? "")'") + } + } +} diff --git a/swift/Sources/ReceiveLogs/main.swift b/swift/Sources/ReceiveLogs/main.swift new file mode 100644 index 00000000..e76175f8 --- /dev/null +++ b/swift/Sources/ReceiveLogs/main.swift @@ -0,0 +1,30 @@ +// +// RabbitMQ Swift Tutorials +// Tutorial 3: Publish/Subscribe - ReceiveLogs +// +// https://www.rabbitmq.com/tutorials/tutorial-three-swift +// + +import BunnySwift + +@main +struct ReceiveLogs { + static func main() async throws { + let connection = try await Connection.open() + let channel = try await connection.openChannel() + let exchange = try await channel.fanout("logs") + let queue = try await channel.queue("", exclusive: true) + + try await queue.bind(to: exchange) + + print(" [*] Waiting for logs. To exit press CTRL+C") + + let consumer = try await channel.basicConsume( + queue: queue.name, + acknowledgementMode: .automatic + ) + for try await message in consumer { + print(" [x] \(message.bodyString ?? "")") + } + } +} diff --git a/swift/Sources/ReceiveLogsDirect/main.swift b/swift/Sources/ReceiveLogsDirect/main.swift new file mode 100644 index 00000000..9acadb08 --- /dev/null +++ b/swift/Sources/ReceiveLogsDirect/main.swift @@ -0,0 +1,38 @@ +// +// RabbitMQ Swift Tutorials +// Tutorial 4: Routing - ReceiveLogsDirect +// +// https://www.rabbitmq.com/tutorials/tutorial-four-swift +// + +import BunnySwift + +@main +struct ReceiveLogsDirect { + static func main() async throws { + let severities = Array(CommandLine.arguments.dropFirst()) + guard !severities.isEmpty else { + print("Usage: ReceiveLogsDirect [info] [warning] [error]") + return + } + + let connection = try await Connection.open() + let channel = try await connection.openChannel() + let exchange = try await channel.direct("direct_logs") + let queue = try await channel.queue("", exclusive: true) + + for severity in severities { + try await queue.bind(to: exchange, routingKey: severity) + } + + print(" [*] Waiting for logs. To exit press CTRL+C") + + let consumer = try await channel.basicConsume( + queue: queue.name, + acknowledgementMode: .automatic + ) + for try await message in consumer { + print(" [x] \(message.deliveryInfo.routingKey):\(message.bodyString ?? "")") + } + } +} diff --git a/swift/Sources/ReceiveLogsTopic/main.swift b/swift/Sources/ReceiveLogsTopic/main.swift new file mode 100644 index 00000000..7f20c511 --- /dev/null +++ b/swift/Sources/ReceiveLogsTopic/main.swift @@ -0,0 +1,38 @@ +// +// RabbitMQ Swift Tutorials +// Tutorial 5: Topics - ReceiveLogsTopic +// +// https://www.rabbitmq.com/tutorials/tutorial-five-swift +// + +import BunnySwift + +@main +struct ReceiveLogsTopic { + static func main() async throws { + let bindingKeys = Array(CommandLine.arguments.dropFirst()) + guard !bindingKeys.isEmpty else { + print("Usage: ReceiveLogsTopic [binding_key]...") + return + } + + let connection = try await Connection.open() + let channel = try await connection.openChannel() + let exchange = try await channel.topic("topic_logs") + let queue = try await channel.queue("", exclusive: true) + + for key in bindingKeys { + try await queue.bind(to: exchange, routingKey: key) + } + + print(" [*] Waiting for logs. To exit press CTRL+C") + + let consumer = try await channel.basicConsume( + queue: queue.name, + acknowledgementMode: .automatic + ) + for try await message in consumer { + print(" [x] \(message.deliveryInfo.routingKey):\(message.bodyString ?? "")") + } + } +} diff --git a/swift/Sources/Send/main.swift b/swift/Sources/Send/main.swift new file mode 100644 index 00000000..8ee3d7d7 --- /dev/null +++ b/swift/Sources/Send/main.swift @@ -0,0 +1,26 @@ +// +// RabbitMQ Swift Tutorials +// Tutorial 1: Hello World - Send +// +// https://www.rabbitmq.com/tutorials/tutorial-one-swift +// + +import BunnySwift +import Foundation + +@main +struct Send { + static func main() async throws { + let connection = try await Connection.open() + let channel = try await connection.openChannel() + let queue = try await channel.queue("hello", type: .quorum, durable: true) + + try await channel.basicPublish( + body: Data("Hello World!".utf8), + routingKey: queue.name + ) + print(" [x] Sent 'Hello World!'") + + try await connection.close() + } +} diff --git a/swift/Sources/Worker/main.swift b/swift/Sources/Worker/main.swift new file mode 100644 index 00000000..ecd730ae --- /dev/null +++ b/swift/Sources/Worker/main.swift @@ -0,0 +1,33 @@ +// +// RabbitMQ Swift Tutorials +// Tutorial 2: Work Queues - Worker +// +// https://www.rabbitmq.com/tutorials/tutorial-two-swift +// + +import BunnySwift + +@main +struct Worker { + static func main() async throws { + let connection = try await Connection.open() + let channel = try await connection.openChannel() + let queue = try await channel.queue("task_queue", type: .quorum, durable: true) + + try await channel.basicQos(prefetchCount: 1) + print(" [*] Waiting for messages. To exit press CTRL+C") + + let consumer = try await channel.basicConsume(queue: queue.name) + for try await message in consumer { + let body = message.bodyString ?? "" + print(" [x] Received '\(body)'") + + // Simulate work: count the dots in the message + let dots = body.filter { $0 == "." }.count + try await Task.sleep(for: .seconds(dots)) + + print(" [x] Done") + try await message.ack() + } + } +} diff --git a/swift/bump_dependencies.sh b/swift/bump_dependencies.sh deleted file mode 100755 index 03579651..00000000 --- a/swift/bump_dependencies.sh +++ /dev/null @@ -1,12 +0,0 @@ -#!/bin/bash - -set -ex - -rm -rf ~/Library/Caches/org.carthage.CarthageKit - -for tutdir in `ls | grep "tutorial[1-9]"` -do - cd $tutdir - carthage update --platform iOS - cd .. -done diff --git a/swift/tutorial1/Cartfile b/swift/tutorial1/Cartfile deleted file mode 100644 index 38483e08..00000000 --- a/swift/tutorial1/Cartfile +++ /dev/null @@ -1 +0,0 @@ -github "rabbitmq/rabbitmq-objc-client" "v0.10.0" diff --git a/swift/tutorial1/Cartfile.resolved b/swift/tutorial1/Cartfile.resolved deleted file mode 100644 index 750d6791..00000000 --- a/swift/tutorial1/Cartfile.resolved +++ /dev/null @@ -1,3 +0,0 @@ -github "robbiehanson/CocoaAsyncSocket" "7.5.1" -github "jeffh/JKVValue" "v1.3.2" -github "rabbitmq/rabbitmq-objc-client" "v0.10.0" diff --git a/swift/tutorial1/tutorial1.xcodeproj/project.pbxproj b/swift/tutorial1/tutorial1.xcodeproj/project.pbxproj deleted file mode 100644 index 2b3b491f..00000000 --- a/swift/tutorial1/tutorial1.xcodeproj/project.pbxproj +++ /dev/null @@ -1,332 +0,0 @@ -// !$*UTF8*$! -{ - archiveVersion = 1; - classes = { - }; - objectVersion = 46; - objects = { - -/* Begin PBXBuildFile section */ - 1CBCD9B51DF9B18100A2B5F9 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1CBCD9B41DF9B18100A2B5F9 /* ViewController.swift */; }; - 1CBCD9B71DF9B21500A2B5F9 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1CBCD9B61DF9B21500A2B5F9 /* AppDelegate.swift */; }; - AE40C7391CB2719500CC7B97 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = AE40C7371CB2719500CC7B97 /* Main.storyboard */; }; - AE40C73B1CB2719500CC7B97 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = AE40C73A1CB2719500CC7B97 /* Assets.xcassets */; }; - AE40C73E1CB2719500CC7B97 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = AE40C73C1CB2719500CC7B97 /* LaunchScreen.storyboard */; }; - AE40C7931CB504BF00CC7B97 /* RMQClient.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AE40C7921CB504BF00CC7B97 /* RMQClient.framework */; }; - AE40C7941CB504C700CC7B97 /* RMQClient.framework in CopyFiles */ = {isa = PBXBuildFile; fileRef = AE40C7921CB504BF00CC7B97 /* RMQClient.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; -/* End PBXBuildFile section */ - -/* Begin PBXCopyFilesBuildPhase section */ - AE40C7471CB272C000CC7B97 /* CopyFiles */ = { - isa = PBXCopyFilesBuildPhase; - buildActionMask = 2147483647; - dstPath = ""; - dstSubfolderSpec = 10; - files = ( - AE40C7941CB504C700CC7B97 /* RMQClient.framework in CopyFiles */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXCopyFilesBuildPhase section */ - -/* Begin PBXFileReference section */ - 1CBCD9B41DF9B18100A2B5F9 /* ViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; - 1CBCD9B61DF9B21500A2B5F9 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; - AE40C72B1CB2719500CC7B97 /* tutorial1.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = tutorial1.app; sourceTree = BUILT_PRODUCTS_DIR; }; - AE40C7381CB2719500CC7B97 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; - AE40C73A1CB2719500CC7B97 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; - AE40C73D1CB2719500CC7B97 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; - AE40C73F1CB2719500CC7B97 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - AE40C7921CB504BF00CC7B97 /* RMQClient.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = RMQClient.framework; path = Carthage/Build/iOS/RMQClient.framework; sourceTree = ""; }; -/* End PBXFileReference section */ - -/* Begin PBXFrameworksBuildPhase section */ - AE40C7281CB2719500CC7B97 /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - AE40C7931CB504BF00CC7B97 /* RMQClient.framework in Frameworks */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXFrameworksBuildPhase section */ - -/* Begin PBXGroup section */ - AE40C7221CB2719500CC7B97 = { - isa = PBXGroup; - children = ( - AE40C7921CB504BF00CC7B97 /* RMQClient.framework */, - AE40C72D1CB2719500CC7B97 /* tutorial1 */, - AE40C72C1CB2719500CC7B97 /* Products */, - ); - sourceTree = ""; - }; - AE40C72C1CB2719500CC7B97 /* Products */ = { - isa = PBXGroup; - children = ( - AE40C72B1CB2719500CC7B97 /* tutorial1.app */, - ); - name = Products; - sourceTree = ""; - }; - AE40C72D1CB2719500CC7B97 /* tutorial1 */ = { - isa = PBXGroup; - children = ( - AE40C7371CB2719500CC7B97 /* Main.storyboard */, - AE40C73A1CB2719500CC7B97 /* Assets.xcassets */, - AE40C73C1CB2719500CC7B97 /* LaunchScreen.storyboard */, - AE40C73F1CB2719500CC7B97 /* Info.plist */, - 1CBCD9B41DF9B18100A2B5F9 /* ViewController.swift */, - 1CBCD9B61DF9B21500A2B5F9 /* AppDelegate.swift */, - ); - path = tutorial1; - sourceTree = ""; - }; -/* End PBXGroup section */ - -/* Begin PBXNativeTarget section */ - AE40C72A1CB2719500CC7B97 /* tutorial1 */ = { - isa = PBXNativeTarget; - buildConfigurationList = AE40C7421CB2719500CC7B97 /* Build configuration list for PBXNativeTarget "tutorial1" */; - buildPhases = ( - AE40C7271CB2719500CC7B97 /* Sources */, - AE40C7281CB2719500CC7B97 /* Frameworks */, - AE40C7291CB2719500CC7B97 /* Resources */, - AE40C7471CB272C000CC7B97 /* CopyFiles */, - ); - buildRules = ( - ); - dependencies = ( - ); - name = tutorial1; - productName = tutorial1; - productReference = AE40C72B1CB2719500CC7B97 /* tutorial1.app */; - productType = "com.apple.product-type.application"; - }; -/* End PBXNativeTarget section */ - -/* Begin PBXProject section */ - AE40C7231CB2719500CC7B97 /* Project object */ = { - isa = PBXProject; - attributes = { - LastUpgradeCheck = 0820; - ORGANIZATIONNAME = RabbitMQ; - TargetAttributes = { - AE40C72A1CB2719500CC7B97 = { - CreatedOnToolsVersion = 7.3; - LastSwiftMigration = 0800; - }; - }; - }; - buildConfigurationList = AE40C7261CB2719500CC7B97 /* Build configuration list for PBXProject "tutorial1" */; - compatibilityVersion = "Xcode 3.2"; - developmentRegion = English; - hasScannedForEncodings = 0; - knownRegions = ( - en, - Base, - ); - mainGroup = AE40C7221CB2719500CC7B97; - productRefGroup = AE40C72C1CB2719500CC7B97 /* Products */; - projectDirPath = ""; - projectRoot = ""; - targets = ( - AE40C72A1CB2719500CC7B97 /* tutorial1 */, - ); - }; -/* End PBXProject section */ - -/* Begin PBXResourcesBuildPhase section */ - AE40C7291CB2719500CC7B97 /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - AE40C73E1CB2719500CC7B97 /* LaunchScreen.storyboard in Resources */, - AE40C73B1CB2719500CC7B97 /* Assets.xcassets in Resources */, - AE40C7391CB2719500CC7B97 /* Main.storyboard in Resources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXResourcesBuildPhase section */ - -/* Begin PBXSourcesBuildPhase section */ - AE40C7271CB2719500CC7B97 /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 1CBCD9B71DF9B21500A2B5F9 /* AppDelegate.swift in Sources */, - 1CBCD9B51DF9B18100A2B5F9 /* ViewController.swift in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXSourcesBuildPhase section */ - -/* Begin PBXVariantGroup section */ - AE40C7371CB2719500CC7B97 /* Main.storyboard */ = { - isa = PBXVariantGroup; - children = ( - AE40C7381CB2719500CC7B97 /* Base */, - ); - name = Main.storyboard; - sourceTree = ""; - }; - AE40C73C1CB2719500CC7B97 /* LaunchScreen.storyboard */ = { - isa = PBXVariantGroup; - children = ( - AE40C73D1CB2719500CC7B97 /* Base */, - ); - name = LaunchScreen.storyboard; - sourceTree = ""; - }; -/* End PBXVariantGroup section */ - -/* Begin XCBuildConfiguration section */ - AE40C7401CB2719500CC7B97 /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_ANALYZER_NONNULL = YES; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - COPY_PHASE_STRIP = NO; - DEBUG_INFORMATION_FORMAT = dwarf; - ENABLE_STRICT_OBJC_MSGSEND = YES; - ENABLE_TESTABILITY = YES; - GCC_C_LANGUAGE_STANDARD = gnu99; - GCC_DYNAMIC_NO_PIC = NO; - GCC_NO_COMMON_BLOCKS = YES; - GCC_OPTIMIZATION_LEVEL = 0; - GCC_PREPROCESSOR_DEFINITIONS = ( - "DEBUG=1", - "$(inherited)", - ); - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 10.0; - MTL_ENABLE_DEBUG_INFO = YES; - ONLY_ACTIVE_ARCH = YES; - SDKROOT = iphoneos; - TARGETED_DEVICE_FAMILY = "1,2"; - }; - name = Debug; - }; - AE40C7411CB2719500CC7B97 /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_ANALYZER_NONNULL = YES; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - COPY_PHASE_STRIP = NO; - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - ENABLE_NS_ASSERTIONS = NO; - ENABLE_STRICT_OBJC_MSGSEND = YES; - GCC_C_LANGUAGE_STANDARD = gnu99; - GCC_NO_COMMON_BLOCKS = YES; - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 10.0; - MTL_ENABLE_DEBUG_INFO = NO; - SDKROOT = iphoneos; - SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; - TARGETED_DEVICE_FAMILY = "1,2"; - VALIDATE_PRODUCT = YES; - }; - name = Release; - }; - AE40C7431CB2719500CC7B97 /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - CLANG_ENABLE_MODULES = YES; - FRAMEWORK_SEARCH_PATHS = ( - "$(inherited)", - "$(PROJECT_DIR)/Carthage/Build/iOS", - ); - INFOPLIST_FILE = tutorial1/Info.plist; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; - PRODUCT_BUNDLE_IDENTIFIER = com.rabbitmq.tutorial1; - PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_VERSION = 3.0; - }; - name = Debug; - }; - AE40C7441CB2719500CC7B97 /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - CLANG_ENABLE_MODULES = YES; - FRAMEWORK_SEARCH_PATHS = ( - "$(inherited)", - "$(PROJECT_DIR)/Carthage/Build/iOS", - ); - INFOPLIST_FILE = tutorial1/Info.plist; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; - PRODUCT_BUNDLE_IDENTIFIER = com.rabbitmq.tutorial1; - PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_VERSION = 3.0; - }; - name = Release; - }; -/* End XCBuildConfiguration section */ - -/* Begin XCConfigurationList section */ - AE40C7261CB2719500CC7B97 /* Build configuration list for PBXProject "tutorial1" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - AE40C7401CB2719500CC7B97 /* Debug */, - AE40C7411CB2719500CC7B97 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - AE40C7421CB2719500CC7B97 /* Build configuration list for PBXNativeTarget "tutorial1" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - AE40C7431CB2719500CC7B97 /* Debug */, - AE40C7441CB2719500CC7B97 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; -/* End XCConfigurationList section */ - }; - rootObject = AE40C7231CB2719500CC7B97 /* Project object */; -} diff --git a/swift/tutorial1/tutorial1.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/swift/tutorial1/tutorial1.xcodeproj/project.xcworkspace/contents.xcworkspacedata deleted file mode 100644 index fc07c512..00000000 --- a/swift/tutorial1/tutorial1.xcodeproj/project.xcworkspace/contents.xcworkspacedata +++ /dev/null @@ -1,7 +0,0 @@ - - - - - diff --git a/swift/tutorial1/tutorial1/AppDelegate.swift b/swift/tutorial1/tutorial1/AppDelegate.swift deleted file mode 100644 index 4c8ef56b..00000000 --- a/swift/tutorial1/tutorial1/AppDelegate.swift +++ /dev/null @@ -1,41 +0,0 @@ -// -// AppDelegate.swift -// tutorial1 -// -// Created by Pivotal on 25/04/2016. -// Copyright © 2016 RabbitMQ. All rights reserved. -// -import UIKit - -@UIApplicationMain -class AppDelegate: UIResponder, UIApplicationDelegate { - var window: UIWindow? - - - func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { - // Override point for customization after application launch. - return true - } - - func applicationWillResignActive(_ application: UIApplication) { - // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. - // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. - } - - func applicationDidEnterBackground(_ application: UIApplication) { - // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. - // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. - } - - func applicationWillEnterForeground(_ application: UIApplication) { - // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background. - } - - func applicationDidBecomeActive(_ application: UIApplication) { - // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. - } - - func applicationWillTerminate(_ application: UIApplication) { - // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. - } -} diff --git a/swift/tutorial1/tutorial1/Assets.xcassets/AppIcon.appiconset/Contents.json b/swift/tutorial1/tutorial1/Assets.xcassets/AppIcon.appiconset/Contents.json deleted file mode 100644 index eeea76c2..00000000 --- a/swift/tutorial1/tutorial1/Assets.xcassets/AppIcon.appiconset/Contents.json +++ /dev/null @@ -1,73 +0,0 @@ -{ - "images" : [ - { - "idiom" : "iphone", - "size" : "29x29", - "scale" : "2x" - }, - { - "idiom" : "iphone", - "size" : "29x29", - "scale" : "3x" - }, - { - "idiom" : "iphone", - "size" : "40x40", - "scale" : "2x" - }, - { - "idiom" : "iphone", - "size" : "40x40", - "scale" : "3x" - }, - { - "idiom" : "iphone", - "size" : "60x60", - "scale" : "2x" - }, - { - "idiom" : "iphone", - "size" : "60x60", - "scale" : "3x" - }, - { - "idiom" : "ipad", - "size" : "29x29", - "scale" : "1x" - }, - { - "idiom" : "ipad", - "size" : "29x29", - "scale" : "2x" - }, - { - "idiom" : "ipad", - "size" : "40x40", - "scale" : "1x" - }, - { - "idiom" : "ipad", - "size" : "40x40", - "scale" : "2x" - }, - { - "idiom" : "ipad", - "size" : "76x76", - "scale" : "1x" - }, - { - "idiom" : "ipad", - "size" : "76x76", - "scale" : "2x" - }, - { - "idiom" : "ipad", - "size" : "83.5x83.5", - "scale" : "2x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/swift/tutorial1/tutorial1/Base.lproj/LaunchScreen.storyboard b/swift/tutorial1/tutorial1/Base.lproj/LaunchScreen.storyboard deleted file mode 100644 index 323bd431..00000000 --- a/swift/tutorial1/tutorial1/Base.lproj/LaunchScreen.storyboard +++ /dev/null @@ -1,27 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/swift/tutorial1/tutorial1/Base.lproj/Main.storyboard b/swift/tutorial1/tutorial1/Base.lproj/Main.storyboard deleted file mode 100644 index e45ea1b2..00000000 --- a/swift/tutorial1/tutorial1/Base.lproj/Main.storyboard +++ /dev/null @@ -1,30 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/swift/tutorial1/tutorial1/Info.plist b/swift/tutorial1/tutorial1/Info.plist deleted file mode 100644 index 40c6215d..00000000 --- a/swift/tutorial1/tutorial1/Info.plist +++ /dev/null @@ -1,47 +0,0 @@ - - - - - CFBundleDevelopmentRegion - en - CFBundleExecutable - $(EXECUTABLE_NAME) - CFBundleIdentifier - $(PRODUCT_BUNDLE_IDENTIFIER) - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - $(PRODUCT_NAME) - CFBundlePackageType - APPL - CFBundleShortVersionString - 1.0 - CFBundleSignature - ???? - CFBundleVersion - 1 - LSRequiresIPhoneOS - - UILaunchStoryboardName - LaunchScreen - UIMainStoryboardFile - Main - UIRequiredDeviceCapabilities - - armv7 - - UISupportedInterfaceOrientations - - UIInterfaceOrientationPortrait - UIInterfaceOrientationLandscapeLeft - UIInterfaceOrientationLandscapeRight - - UISupportedInterfaceOrientations~ipad - - UIInterfaceOrientationPortrait - UIInterfaceOrientationPortraitUpsideDown - UIInterfaceOrientationLandscapeLeft - UIInterfaceOrientationLandscapeRight - - - diff --git a/swift/tutorial1/tutorial1/ViewController.swift b/swift/tutorial1/tutorial1/ViewController.swift deleted file mode 100644 index f6d33b3e..00000000 --- a/swift/tutorial1/tutorial1/ViewController.swift +++ /dev/null @@ -1,41 +0,0 @@ -// -// ViewController.swift -// tutorial1 -// -// Copyright © 2016 RabbitMQ. All rights reserved. -// -import UIKit -import RMQClient - -class ViewController: UIViewController { - - - override func viewDidLoad() { - super.viewDidLoad() - self.send() - self.receive() - } - - func send() { - print("Attempting to connect to local RabbitMQ broker") - let conn = RMQConnection(delegate: RMQConnectionDelegateLogger()) - conn.start() - let ch = conn.createChannel() - let q = ch.queue("hello") - ch.defaultExchange().publish("Hello World!".data(using: .utf8), routingKey: q.name) - print("Sent 'Hello World!'") - conn.close() - } - - func receive() { - print("Attempting to connect to local RabbitMQ broker") - let conn = RMQConnection(delegate: RMQConnectionDelegateLogger()) - conn.start() - let ch = conn.createChannel() - let q = ch.queue("hello") - print("Waiting for messages.") - q.subscribe({(_ message: RMQMessage) -> Void in - print("Received \(String(data: message.body, encoding: String.Encoding.utf8)!)") - }) - } -} diff --git a/swift/tutorial2/Cartfile b/swift/tutorial2/Cartfile deleted file mode 100644 index 38483e08..00000000 --- a/swift/tutorial2/Cartfile +++ /dev/null @@ -1 +0,0 @@ -github "rabbitmq/rabbitmq-objc-client" "v0.10.0" diff --git a/swift/tutorial2/Cartfile.resolved b/swift/tutorial2/Cartfile.resolved deleted file mode 100644 index 750d6791..00000000 --- a/swift/tutorial2/Cartfile.resolved +++ /dev/null @@ -1,3 +0,0 @@ -github "robbiehanson/CocoaAsyncSocket" "7.5.1" -github "jeffh/JKVValue" "v1.3.2" -github "rabbitmq/rabbitmq-objc-client" "v0.10.0" diff --git a/swift/tutorial2/tutorial2.xcodeproj/project.pbxproj b/swift/tutorial2/tutorial2.xcodeproj/project.pbxproj deleted file mode 100644 index 9d071a31..00000000 --- a/swift/tutorial2/tutorial2.xcodeproj/project.pbxproj +++ /dev/null @@ -1,335 +0,0 @@ -// !$*UTF8*$! -{ - archiveVersion = 1; - classes = { - }; - objectVersion = 46; - objects = { - -/* Begin PBXBuildFile section */ - 1CBCD9B91DF9C3D600A2B5F9 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1CBCD9B81DF9C3D600A2B5F9 /* AppDelegate.swift */; }; - 1CBCD9BB1DF9C41900A2B5F9 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1CBCD9BA1DF9C41900A2B5F9 /* ViewController.swift */; }; - AE40C76F1CB2B50B00CC7B97 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = AE40C76D1CB2B50B00CC7B97 /* Main.storyboard */; }; - AE40C7711CB2B50B00CC7B97 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = AE40C7701CB2B50B00CC7B97 /* Assets.xcassets */; }; - AE40C7741CB2B50B00CC7B97 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = AE40C7721CB2B50B00CC7B97 /* LaunchScreen.storyboard */; }; - AE40C77C1CB2B5CD00CC7B97 /* RMQClient.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AE40C77B1CB2B5CD00CC7B97 /* RMQClient.framework */; }; - AE40C77E1CB2B5E200CC7B97 /* RMQClient.framework in CopyFiles */ = {isa = PBXBuildFile; fileRef = AE40C77B1CB2B5CD00CC7B97 /* RMQClient.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; -/* End PBXBuildFile section */ - -/* Begin PBXCopyFilesBuildPhase section */ - AE40C77D1CB2B5D800CC7B97 /* CopyFiles */ = { - isa = PBXCopyFilesBuildPhase; - buildActionMask = 2147483647; - dstPath = ""; - dstSubfolderSpec = 10; - files = ( - AE40C77E1CB2B5E200CC7B97 /* RMQClient.framework in CopyFiles */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXCopyFilesBuildPhase section */ - -/* Begin PBXFileReference section */ - 1CBCD9B81DF9C3D600A2B5F9 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; - 1CBCD9BA1DF9C41900A2B5F9 /* ViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; - AE40C7611CB2B50B00CC7B97 /* tutorial2.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = tutorial2.app; sourceTree = BUILT_PRODUCTS_DIR; }; - AE40C76E1CB2B50B00CC7B97 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; - AE40C7701CB2B50B00CC7B97 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; - AE40C7731CB2B50B00CC7B97 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; - AE40C7751CB2B50B00CC7B97 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - AE40C77B1CB2B5CD00CC7B97 /* RMQClient.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = RMQClient.framework; path = Carthage/Build/iOS/RMQClient.framework; sourceTree = ""; }; -/* End PBXFileReference section */ - -/* Begin PBXFrameworksBuildPhase section */ - AE40C75E1CB2B50B00CC7B97 /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - AE40C77C1CB2B5CD00CC7B97 /* RMQClient.framework in Frameworks */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXFrameworksBuildPhase section */ - -/* Begin PBXGroup section */ - AE40C7581CB2B50B00CC7B97 = { - isa = PBXGroup; - children = ( - AE40C77B1CB2B5CD00CC7B97 /* RMQClient.framework */, - AE40C7631CB2B50B00CC7B97 /* tutorial2 */, - AE40C7621CB2B50B00CC7B97 /* Products */, - ); - sourceTree = ""; - }; - AE40C7621CB2B50B00CC7B97 /* Products */ = { - isa = PBXGroup; - children = ( - AE40C7611CB2B50B00CC7B97 /* tutorial2.app */, - ); - name = Products; - sourceTree = ""; - }; - AE40C7631CB2B50B00CC7B97 /* tutorial2 */ = { - isa = PBXGroup; - children = ( - AE40C76D1CB2B50B00CC7B97 /* Main.storyboard */, - AE40C7701CB2B50B00CC7B97 /* Assets.xcassets */, - AE40C7721CB2B50B00CC7B97 /* LaunchScreen.storyboard */, - AE40C7751CB2B50B00CC7B97 /* Info.plist */, - 1CBCD9B81DF9C3D600A2B5F9 /* AppDelegate.swift */, - 1CBCD9BA1DF9C41900A2B5F9 /* ViewController.swift */, - ); - path = tutorial2; - sourceTree = ""; - }; -/* End PBXGroup section */ - -/* Begin PBXNativeTarget section */ - AE40C7601CB2B50B00CC7B97 /* tutorial2 */ = { - isa = PBXNativeTarget; - buildConfigurationList = AE40C7781CB2B50B00CC7B97 /* Build configuration list for PBXNativeTarget "tutorial2" */; - buildPhases = ( - AE40C75D1CB2B50B00CC7B97 /* Sources */, - AE40C75E1CB2B50B00CC7B97 /* Frameworks */, - AE40C75F1CB2B50B00CC7B97 /* Resources */, - AE40C77D1CB2B5D800CC7B97 /* CopyFiles */, - ); - buildRules = ( - ); - dependencies = ( - ); - name = tutorial2; - productName = tutorial2; - productReference = AE40C7611CB2B50B00CC7B97 /* tutorial2.app */; - productType = "com.apple.product-type.application"; - }; -/* End PBXNativeTarget section */ - -/* Begin PBXProject section */ - AE40C7591CB2B50B00CC7B97 /* Project object */ = { - isa = PBXProject; - attributes = { - LastUpgradeCheck = 0820; - ORGANIZATIONNAME = RabbitMQ; - TargetAttributes = { - AE40C7601CB2B50B00CC7B97 = { - CreatedOnToolsVersion = 7.3; - DevelopmentTeam = FLJTZE77QC; - LastSwiftMigration = 0800; - }; - }; - }; - buildConfigurationList = AE40C75C1CB2B50B00CC7B97 /* Build configuration list for PBXProject "tutorial2" */; - compatibilityVersion = "Xcode 3.2"; - developmentRegion = English; - hasScannedForEncodings = 0; - knownRegions = ( - en, - Base, - ); - mainGroup = AE40C7581CB2B50B00CC7B97; - productRefGroup = AE40C7621CB2B50B00CC7B97 /* Products */; - projectDirPath = ""; - projectRoot = ""; - targets = ( - AE40C7601CB2B50B00CC7B97 /* tutorial2 */, - ); - }; -/* End PBXProject section */ - -/* Begin PBXResourcesBuildPhase section */ - AE40C75F1CB2B50B00CC7B97 /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - AE40C7741CB2B50B00CC7B97 /* LaunchScreen.storyboard in Resources */, - AE40C7711CB2B50B00CC7B97 /* Assets.xcassets in Resources */, - AE40C76F1CB2B50B00CC7B97 /* Main.storyboard in Resources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXResourcesBuildPhase section */ - -/* Begin PBXSourcesBuildPhase section */ - AE40C75D1CB2B50B00CC7B97 /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 1CBCD9B91DF9C3D600A2B5F9 /* AppDelegate.swift in Sources */, - 1CBCD9BB1DF9C41900A2B5F9 /* ViewController.swift in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXSourcesBuildPhase section */ - -/* Begin PBXVariantGroup section */ - AE40C76D1CB2B50B00CC7B97 /* Main.storyboard */ = { - isa = PBXVariantGroup; - children = ( - AE40C76E1CB2B50B00CC7B97 /* Base */, - ); - name = Main.storyboard; - sourceTree = ""; - }; - AE40C7721CB2B50B00CC7B97 /* LaunchScreen.storyboard */ = { - isa = PBXVariantGroup; - children = ( - AE40C7731CB2B50B00CC7B97 /* Base */, - ); - name = LaunchScreen.storyboard; - sourceTree = ""; - }; -/* End PBXVariantGroup section */ - -/* Begin XCBuildConfiguration section */ - AE40C7761CB2B50B00CC7B97 /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_ANALYZER_NONNULL = YES; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - COPY_PHASE_STRIP = NO; - DEBUG_INFORMATION_FORMAT = dwarf; - ENABLE_STRICT_OBJC_MSGSEND = YES; - ENABLE_TESTABILITY = YES; - GCC_C_LANGUAGE_STANDARD = gnu99; - GCC_DYNAMIC_NO_PIC = NO; - GCC_NO_COMMON_BLOCKS = YES; - GCC_OPTIMIZATION_LEVEL = 0; - GCC_PREPROCESSOR_DEFINITIONS = ( - "DEBUG=1", - "$(inherited)", - ); - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 10.0; - MTL_ENABLE_DEBUG_INFO = YES; - ONLY_ACTIVE_ARCH = YES; - SDKROOT = iphoneos; - TARGETED_DEVICE_FAMILY = "1,2"; - }; - name = Debug; - }; - AE40C7771CB2B50B00CC7B97 /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_ANALYZER_NONNULL = YES; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - COPY_PHASE_STRIP = NO; - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - ENABLE_NS_ASSERTIONS = NO; - ENABLE_STRICT_OBJC_MSGSEND = YES; - GCC_C_LANGUAGE_STANDARD = gnu99; - GCC_NO_COMMON_BLOCKS = YES; - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 10.0; - MTL_ENABLE_DEBUG_INFO = NO; - SDKROOT = iphoneos; - SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; - TARGETED_DEVICE_FAMILY = "1,2"; - VALIDATE_PRODUCT = YES; - }; - name = Release; - }; - AE40C7791CB2B50B00CC7B97 /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - CLANG_ENABLE_MODULES = YES; - DEVELOPMENT_TEAM = FLJTZE77QC; - FRAMEWORK_SEARCH_PATHS = ( - "$(inherited)", - "$(PROJECT_DIR)/Carthage/Build/iOS", - ); - INFOPLIST_FILE = tutorial2/Info.plist; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; - PRODUCT_BUNDLE_IDENTIFIER = com.rabbitmq.tutorial2; - PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_VERSION = 3.0; - }; - name = Debug; - }; - AE40C77A1CB2B50B00CC7B97 /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - CLANG_ENABLE_MODULES = YES; - DEVELOPMENT_TEAM = FLJTZE77QC; - FRAMEWORK_SEARCH_PATHS = ( - "$(inherited)", - "$(PROJECT_DIR)/Carthage/Build/iOS", - ); - INFOPLIST_FILE = tutorial2/Info.plist; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; - PRODUCT_BUNDLE_IDENTIFIER = com.rabbitmq.tutorial2; - PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_VERSION = 3.0; - }; - name = Release; - }; -/* End XCBuildConfiguration section */ - -/* Begin XCConfigurationList section */ - AE40C75C1CB2B50B00CC7B97 /* Build configuration list for PBXProject "tutorial2" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - AE40C7761CB2B50B00CC7B97 /* Debug */, - AE40C7771CB2B50B00CC7B97 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - AE40C7781CB2B50B00CC7B97 /* Build configuration list for PBXNativeTarget "tutorial2" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - AE40C7791CB2B50B00CC7B97 /* Debug */, - AE40C77A1CB2B50B00CC7B97 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; -/* End XCConfigurationList section */ - }; - rootObject = AE40C7591CB2B50B00CC7B97 /* Project object */; -} diff --git a/swift/tutorial2/tutorial2.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/swift/tutorial2/tutorial2.xcodeproj/project.xcworkspace/contents.xcworkspacedata deleted file mode 100644 index 7424db3d..00000000 --- a/swift/tutorial2/tutorial2.xcodeproj/project.xcworkspace/contents.xcworkspacedata +++ /dev/null @@ -1,7 +0,0 @@ - - - - - diff --git a/swift/tutorial2/tutorial2/AppDelegate.swift b/swift/tutorial2/tutorial2/AppDelegate.swift deleted file mode 100644 index 3ba85564..00000000 --- a/swift/tutorial2/tutorial2/AppDelegate.swift +++ /dev/null @@ -1,41 +0,0 @@ -// -// AppDelegate.swift -// tutorial2 -// -// Created by Pivotal on 25/04/2016. -// Copyright © 2016 RabbitMQ. All rights reserved. -// -import UIKit - -@UIApplicationMain -class AppDelegate: UIResponder, UIApplicationDelegate { - var window: UIWindow? - - - func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { - // Override point for customization after application launch. - return true - } - - func applicationWillResignActive(_ application: UIApplication) { - // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. - // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. - } - - func applicationDidEnterBackground(_ application: UIApplication) { - // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. - // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. - } - - func applicationWillEnterForeground(_ application: UIApplication) { - // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background. - } - - func applicationDidBecomeActive(_ application: UIApplication) { - // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. - } - - func applicationWillTerminate(_ application: UIApplication) { - // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. - } -} diff --git a/swift/tutorial2/tutorial2/Assets.xcassets/AppIcon.appiconset/Contents.json b/swift/tutorial2/tutorial2/Assets.xcassets/AppIcon.appiconset/Contents.json deleted file mode 100644 index eeea76c2..00000000 --- a/swift/tutorial2/tutorial2/Assets.xcassets/AppIcon.appiconset/Contents.json +++ /dev/null @@ -1,73 +0,0 @@ -{ - "images" : [ - { - "idiom" : "iphone", - "size" : "29x29", - "scale" : "2x" - }, - { - "idiom" : "iphone", - "size" : "29x29", - "scale" : "3x" - }, - { - "idiom" : "iphone", - "size" : "40x40", - "scale" : "2x" - }, - { - "idiom" : "iphone", - "size" : "40x40", - "scale" : "3x" - }, - { - "idiom" : "iphone", - "size" : "60x60", - "scale" : "2x" - }, - { - "idiom" : "iphone", - "size" : "60x60", - "scale" : "3x" - }, - { - "idiom" : "ipad", - "size" : "29x29", - "scale" : "1x" - }, - { - "idiom" : "ipad", - "size" : "29x29", - "scale" : "2x" - }, - { - "idiom" : "ipad", - "size" : "40x40", - "scale" : "1x" - }, - { - "idiom" : "ipad", - "size" : "40x40", - "scale" : "2x" - }, - { - "idiom" : "ipad", - "size" : "76x76", - "scale" : "1x" - }, - { - "idiom" : "ipad", - "size" : "76x76", - "scale" : "2x" - }, - { - "idiom" : "ipad", - "size" : "83.5x83.5", - "scale" : "2x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/swift/tutorial2/tutorial2/Base.lproj/LaunchScreen.storyboard b/swift/tutorial2/tutorial2/Base.lproj/LaunchScreen.storyboard deleted file mode 100644 index 323bd431..00000000 --- a/swift/tutorial2/tutorial2/Base.lproj/LaunchScreen.storyboard +++ /dev/null @@ -1,27 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/swift/tutorial2/tutorial2/Base.lproj/Main.storyboard b/swift/tutorial2/tutorial2/Base.lproj/Main.storyboard deleted file mode 100644 index 4f54ae13..00000000 --- a/swift/tutorial2/tutorial2/Base.lproj/Main.storyboard +++ /dev/null @@ -1,30 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/swift/tutorial2/tutorial2/Info.plist b/swift/tutorial2/tutorial2/Info.plist deleted file mode 100644 index 40c6215d..00000000 --- a/swift/tutorial2/tutorial2/Info.plist +++ /dev/null @@ -1,47 +0,0 @@ - - - - - CFBundleDevelopmentRegion - en - CFBundleExecutable - $(EXECUTABLE_NAME) - CFBundleIdentifier - $(PRODUCT_BUNDLE_IDENTIFIER) - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - $(PRODUCT_NAME) - CFBundlePackageType - APPL - CFBundleShortVersionString - 1.0 - CFBundleSignature - ???? - CFBundleVersion - 1 - LSRequiresIPhoneOS - - UILaunchStoryboardName - LaunchScreen - UIMainStoryboardFile - Main - UIRequiredDeviceCapabilities - - armv7 - - UISupportedInterfaceOrientations - - UIInterfaceOrientationPortrait - UIInterfaceOrientationLandscapeLeft - UIInterfaceOrientationLandscapeRight - - UISupportedInterfaceOrientations~ipad - - UIInterfaceOrientationPortrait - UIInterfaceOrientationPortraitUpsideDown - UIInterfaceOrientationLandscapeLeft - UIInterfaceOrientationLandscapeRight - - - diff --git a/swift/tutorial2/tutorial2/ViewController.swift b/swift/tutorial2/tutorial2/ViewController.swift deleted file mode 100644 index e456f766..00000000 --- a/swift/tutorial2/tutorial2/ViewController.swift +++ /dev/null @@ -1,54 +0,0 @@ -// -// ViewController.swift -// tutorial2 -// -// Copyright © 2016 RabbitMQ. All rights reserved. -// -import UIKit -import RMQClient - -class ViewController: UIViewController { - - - override func viewDidLoad() { - super.viewDidLoad() - self.workerNamed("Jack") - self.workerNamed("Jill") - sleep(1) - self.newTask("Hello World...") - self.newTask("Just one this time.") - self.newTask("Five.....") - self.newTask("None") - self.newTask("Two..dots") - } - - func newTask(_ msg: String) { - let conn = RMQConnection(delegate: RMQConnectionDelegateLogger()) - conn.start() - let ch = conn.createChannel() - let q = ch.queue("task_queue", options: .durable) - let msgData = msg.data(using: .utf8) - ch.defaultExchange().publish(msgData, routingKey: q.name, persistent: true) - print("Sent \(msg)") - conn.close() - } - - func workerNamed(_ name: String) { - let conn = RMQConnection(delegate: RMQConnectionDelegateLogger()) - conn.start() - let ch = conn.createChannel() - let q = ch.queue("task_queue", options: .durable) - ch.basicQos(1, global: false) - print("\(name): Waiting for messages") - let manualAck = RMQBasicConsumeOptions() - q.subscribe(manualAck, handler: {(_ message: RMQMessage) -> Void in - let messageText = String(data: message.body, encoding: .utf8) - print("\(name): Received \(messageText!)") - // imitate some work - let sleepTime = UInt32(messageText!.components(separatedBy: ".").count) - 1 - print("\(name): Sleeping for \(sleepTime) seconds") - sleep(sleepTime) - ch.ack(message.deliveryTag) - }) - } -} diff --git a/swift/tutorial3/Cartfile b/swift/tutorial3/Cartfile deleted file mode 100644 index 38483e08..00000000 --- a/swift/tutorial3/Cartfile +++ /dev/null @@ -1 +0,0 @@ -github "rabbitmq/rabbitmq-objc-client" "v0.10.0" diff --git a/swift/tutorial3/Cartfile.resolved b/swift/tutorial3/Cartfile.resolved deleted file mode 100644 index 750d6791..00000000 --- a/swift/tutorial3/Cartfile.resolved +++ /dev/null @@ -1,3 +0,0 @@ -github "robbiehanson/CocoaAsyncSocket" "7.5.1" -github "jeffh/JKVValue" "v1.3.2" -github "rabbitmq/rabbitmq-objc-client" "v0.10.0" diff --git a/swift/tutorial3/tutorial3.xcodeproj/project.pbxproj b/swift/tutorial3/tutorial3.xcodeproj/project.pbxproj deleted file mode 100644 index 65a39112..00000000 --- a/swift/tutorial3/tutorial3.xcodeproj/project.pbxproj +++ /dev/null @@ -1,332 +0,0 @@ -// !$*UTF8*$! -{ - archiveVersion = 1; - classes = { - }; - objectVersion = 46; - objects = { - -/* Begin PBXBuildFile section */ - 1CBCD9BD1DF9C85200A2B5F9 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1CBCD9BC1DF9C85200A2B5F9 /* AppDelegate.swift */; }; - 1CBCD9BF1DF9C90400A2B5F9 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1CBCD9BE1DF9C90400A2B5F9 /* ViewController.swift */; }; - AE09C81D1CCE315E00FA6915 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = AE09C81B1CCE315E00FA6915 /* Main.storyboard */; }; - AE09C81F1CCE315E00FA6915 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = AE09C81E1CCE315E00FA6915 /* Assets.xcassets */; }; - AE09C8221CCE315E00FA6915 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = AE09C8201CCE315E00FA6915 /* LaunchScreen.storyboard */; }; - AE09C82A1CCE33D400FA6915 /* RMQClient.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AE09C8291CCE33D400FA6915 /* RMQClient.framework */; }; - AE09C82C1CCE33EA00FA6915 /* RMQClient.framework in CopyFiles */ = {isa = PBXBuildFile; fileRef = AE09C8291CCE33D400FA6915 /* RMQClient.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; -/* End PBXBuildFile section */ - -/* Begin PBXCopyFilesBuildPhase section */ - AE09C82B1CCE33DE00FA6915 /* CopyFiles */ = { - isa = PBXCopyFilesBuildPhase; - buildActionMask = 2147483647; - dstPath = ""; - dstSubfolderSpec = 10; - files = ( - AE09C82C1CCE33EA00FA6915 /* RMQClient.framework in CopyFiles */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXCopyFilesBuildPhase section */ - -/* Begin PBXFileReference section */ - 1CBCD9BC1DF9C85200A2B5F9 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; - 1CBCD9BE1DF9C90400A2B5F9 /* ViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; - AE09C80F1CCE315E00FA6915 /* tutorial3.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = tutorial3.app; sourceTree = BUILT_PRODUCTS_DIR; }; - AE09C81C1CCE315E00FA6915 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; - AE09C81E1CCE315E00FA6915 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; - AE09C8211CCE315E00FA6915 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; - AE09C8231CCE315E00FA6915 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - AE09C8291CCE33D400FA6915 /* RMQClient.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = RMQClient.framework; path = Carthage/Build/iOS/RMQClient.framework; sourceTree = ""; }; -/* End PBXFileReference section */ - -/* Begin PBXFrameworksBuildPhase section */ - AE09C80C1CCE315E00FA6915 /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - AE09C82A1CCE33D400FA6915 /* RMQClient.framework in Frameworks */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXFrameworksBuildPhase section */ - -/* Begin PBXGroup section */ - AE09C8061CCE315E00FA6915 = { - isa = PBXGroup; - children = ( - AE09C8291CCE33D400FA6915 /* RMQClient.framework */, - AE09C8111CCE315E00FA6915 /* tutorial3 */, - AE09C8101CCE315E00FA6915 /* Products */, - ); - sourceTree = ""; - }; - AE09C8101CCE315E00FA6915 /* Products */ = { - isa = PBXGroup; - children = ( - AE09C80F1CCE315E00FA6915 /* tutorial3.app */, - ); - name = Products; - sourceTree = ""; - }; - AE09C8111CCE315E00FA6915 /* tutorial3 */ = { - isa = PBXGroup; - children = ( - 1CBCD9BC1DF9C85200A2B5F9 /* AppDelegate.swift */, - 1CBCD9BE1DF9C90400A2B5F9 /* ViewController.swift */, - AE09C81B1CCE315E00FA6915 /* Main.storyboard */, - AE09C81E1CCE315E00FA6915 /* Assets.xcassets */, - AE09C8201CCE315E00FA6915 /* LaunchScreen.storyboard */, - AE09C8231CCE315E00FA6915 /* Info.plist */, - ); - path = tutorial3; - sourceTree = ""; - }; -/* End PBXGroup section */ - -/* Begin PBXNativeTarget section */ - AE09C80E1CCE315E00FA6915 /* tutorial3 */ = { - isa = PBXNativeTarget; - buildConfigurationList = AE09C8261CCE315E00FA6915 /* Build configuration list for PBXNativeTarget "tutorial3" */; - buildPhases = ( - AE09C80B1CCE315E00FA6915 /* Sources */, - AE09C80C1CCE315E00FA6915 /* Frameworks */, - AE09C80D1CCE315E00FA6915 /* Resources */, - AE09C82B1CCE33DE00FA6915 /* CopyFiles */, - ); - buildRules = ( - ); - dependencies = ( - ); - name = tutorial3; - productName = tutorial3; - productReference = AE09C80F1CCE315E00FA6915 /* tutorial3.app */; - productType = "com.apple.product-type.application"; - }; -/* End PBXNativeTarget section */ - -/* Begin PBXProject section */ - AE09C8071CCE315E00FA6915 /* Project object */ = { - isa = PBXProject; - attributes = { - LastUpgradeCheck = 0820; - ORGANIZATIONNAME = RabbitMQ; - TargetAttributes = { - AE09C80E1CCE315E00FA6915 = { - CreatedOnToolsVersion = 7.3; - LastSwiftMigration = 0800; - }; - }; - }; - buildConfigurationList = AE09C80A1CCE315E00FA6915 /* Build configuration list for PBXProject "tutorial3" */; - compatibilityVersion = "Xcode 3.2"; - developmentRegion = English; - hasScannedForEncodings = 0; - knownRegions = ( - en, - Base, - ); - mainGroup = AE09C8061CCE315E00FA6915; - productRefGroup = AE09C8101CCE315E00FA6915 /* Products */; - projectDirPath = ""; - projectRoot = ""; - targets = ( - AE09C80E1CCE315E00FA6915 /* tutorial3 */, - ); - }; -/* End PBXProject section */ - -/* Begin PBXResourcesBuildPhase section */ - AE09C80D1CCE315E00FA6915 /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - AE09C8221CCE315E00FA6915 /* LaunchScreen.storyboard in Resources */, - AE09C81F1CCE315E00FA6915 /* Assets.xcassets in Resources */, - AE09C81D1CCE315E00FA6915 /* Main.storyboard in Resources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXResourcesBuildPhase section */ - -/* Begin PBXSourcesBuildPhase section */ - AE09C80B1CCE315E00FA6915 /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 1CBCD9BF1DF9C90400A2B5F9 /* ViewController.swift in Sources */, - 1CBCD9BD1DF9C85200A2B5F9 /* AppDelegate.swift in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXSourcesBuildPhase section */ - -/* Begin PBXVariantGroup section */ - AE09C81B1CCE315E00FA6915 /* Main.storyboard */ = { - isa = PBXVariantGroup; - children = ( - AE09C81C1CCE315E00FA6915 /* Base */, - ); - name = Main.storyboard; - sourceTree = ""; - }; - AE09C8201CCE315E00FA6915 /* LaunchScreen.storyboard */ = { - isa = PBXVariantGroup; - children = ( - AE09C8211CCE315E00FA6915 /* Base */, - ); - name = LaunchScreen.storyboard; - sourceTree = ""; - }; -/* End PBXVariantGroup section */ - -/* Begin XCBuildConfiguration section */ - AE09C8241CCE315E00FA6915 /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_ANALYZER_NONNULL = YES; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - COPY_PHASE_STRIP = NO; - DEBUG_INFORMATION_FORMAT = dwarf; - ENABLE_STRICT_OBJC_MSGSEND = YES; - ENABLE_TESTABILITY = YES; - GCC_C_LANGUAGE_STANDARD = gnu99; - GCC_DYNAMIC_NO_PIC = NO; - GCC_NO_COMMON_BLOCKS = YES; - GCC_OPTIMIZATION_LEVEL = 0; - GCC_PREPROCESSOR_DEFINITIONS = ( - "DEBUG=1", - "$(inherited)", - ); - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 10.0; - MTL_ENABLE_DEBUG_INFO = YES; - ONLY_ACTIVE_ARCH = YES; - SDKROOT = iphoneos; - TARGETED_DEVICE_FAMILY = "1,2"; - }; - name = Debug; - }; - AE09C8251CCE315E00FA6915 /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_ANALYZER_NONNULL = YES; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - COPY_PHASE_STRIP = NO; - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - ENABLE_NS_ASSERTIONS = NO; - ENABLE_STRICT_OBJC_MSGSEND = YES; - GCC_C_LANGUAGE_STANDARD = gnu99; - GCC_NO_COMMON_BLOCKS = YES; - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 10.0; - MTL_ENABLE_DEBUG_INFO = NO; - SDKROOT = iphoneos; - SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; - TARGETED_DEVICE_FAMILY = "1,2"; - VALIDATE_PRODUCT = YES; - }; - name = Release; - }; - AE09C8271CCE315E00FA6915 /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - CLANG_ENABLE_MODULES = YES; - FRAMEWORK_SEARCH_PATHS = ( - "$(inherited)", - "$(PROJECT_DIR)/Carthage/Build/iOS", - ); - INFOPLIST_FILE = tutorial3/Info.plist; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; - PRODUCT_BUNDLE_IDENTIFIER = com.rabbitmq.tutorial3; - PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_VERSION = 3.0; - }; - name = Debug; - }; - AE09C8281CCE315E00FA6915 /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - CLANG_ENABLE_MODULES = YES; - FRAMEWORK_SEARCH_PATHS = ( - "$(inherited)", - "$(PROJECT_DIR)/Carthage/Build/iOS", - ); - INFOPLIST_FILE = tutorial3/Info.plist; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; - PRODUCT_BUNDLE_IDENTIFIER = com.rabbitmq.tutorial3; - PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_VERSION = 3.0; - }; - name = Release; - }; -/* End XCBuildConfiguration section */ - -/* Begin XCConfigurationList section */ - AE09C80A1CCE315E00FA6915 /* Build configuration list for PBXProject "tutorial3" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - AE09C8241CCE315E00FA6915 /* Debug */, - AE09C8251CCE315E00FA6915 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - AE09C8261CCE315E00FA6915 /* Build configuration list for PBXNativeTarget "tutorial3" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - AE09C8271CCE315E00FA6915 /* Debug */, - AE09C8281CCE315E00FA6915 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; -/* End XCConfigurationList section */ - }; - rootObject = AE09C8071CCE315E00FA6915 /* Project object */; -} diff --git a/swift/tutorial3/tutorial3.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/swift/tutorial3/tutorial3.xcodeproj/project.xcworkspace/contents.xcworkspacedata deleted file mode 100644 index 22bcdc83..00000000 --- a/swift/tutorial3/tutorial3.xcodeproj/project.xcworkspace/contents.xcworkspacedata +++ /dev/null @@ -1,7 +0,0 @@ - - - - - diff --git a/swift/tutorial3/tutorial3/AppDelegate.swift b/swift/tutorial3/tutorial3/AppDelegate.swift deleted file mode 100644 index 2dfb5f6c..00000000 --- a/swift/tutorial3/tutorial3/AppDelegate.swift +++ /dev/null @@ -1,40 +0,0 @@ -// -// AppDelegate.swift -// tutorial3 -// -// Created by Pivotal on 25/04/2016. -// Copyright © 2016 RabbitMQ. All rights reserved. -// -import UIKit -@UIApplicationMain -class AppDelegate: UIResponder, UIApplicationDelegate { - var window: UIWindow? - - - func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { - // Override point for customization after application launch. - return true - } - - func applicationWillResignActive(_ application: UIApplication) { - // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. - // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. - } - - func applicationDidEnterBackground(_ application: UIApplication) { - // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. - // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. - } - - func applicationWillEnterForeground(_ application: UIApplication) { - // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background. - } - - func applicationDidBecomeActive(_ application: UIApplication) { - // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. - } - - func applicationWillTerminate(_ application: UIApplication) { - // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. - } -} diff --git a/swift/tutorial3/tutorial3/Assets.xcassets/AppIcon.appiconset/Contents.json b/swift/tutorial3/tutorial3/Assets.xcassets/AppIcon.appiconset/Contents.json deleted file mode 100644 index 36d2c80d..00000000 --- a/swift/tutorial3/tutorial3/Assets.xcassets/AppIcon.appiconset/Contents.json +++ /dev/null @@ -1,68 +0,0 @@ -{ - "images" : [ - { - "idiom" : "iphone", - "size" : "29x29", - "scale" : "2x" - }, - { - "idiom" : "iphone", - "size" : "29x29", - "scale" : "3x" - }, - { - "idiom" : "iphone", - "size" : "40x40", - "scale" : "2x" - }, - { - "idiom" : "iphone", - "size" : "40x40", - "scale" : "3x" - }, - { - "idiom" : "iphone", - "size" : "60x60", - "scale" : "2x" - }, - { - "idiom" : "iphone", - "size" : "60x60", - "scale" : "3x" - }, - { - "idiom" : "ipad", - "size" : "29x29", - "scale" : "1x" - }, - { - "idiom" : "ipad", - "size" : "29x29", - "scale" : "2x" - }, - { - "idiom" : "ipad", - "size" : "40x40", - "scale" : "1x" - }, - { - "idiom" : "ipad", - "size" : "40x40", - "scale" : "2x" - }, - { - "idiom" : "ipad", - "size" : "76x76", - "scale" : "1x" - }, - { - "idiom" : "ipad", - "size" : "76x76", - "scale" : "2x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/swift/tutorial3/tutorial3/Base.lproj/LaunchScreen.storyboard b/swift/tutorial3/tutorial3/Base.lproj/LaunchScreen.storyboard deleted file mode 100644 index 2e721e18..00000000 --- a/swift/tutorial3/tutorial3/Base.lproj/LaunchScreen.storyboard +++ /dev/null @@ -1,27 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/swift/tutorial3/tutorial3/Base.lproj/Main.storyboard b/swift/tutorial3/tutorial3/Base.lproj/Main.storyboard deleted file mode 100644 index 4f3f16d0..00000000 --- a/swift/tutorial3/tutorial3/Base.lproj/Main.storyboard +++ /dev/null @@ -1,30 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/swift/tutorial3/tutorial3/Info.plist b/swift/tutorial3/tutorial3/Info.plist deleted file mode 100644 index 40c6215d..00000000 --- a/swift/tutorial3/tutorial3/Info.plist +++ /dev/null @@ -1,47 +0,0 @@ - - - - - CFBundleDevelopmentRegion - en - CFBundleExecutable - $(EXECUTABLE_NAME) - CFBundleIdentifier - $(PRODUCT_BUNDLE_IDENTIFIER) - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - $(PRODUCT_NAME) - CFBundlePackageType - APPL - CFBundleShortVersionString - 1.0 - CFBundleSignature - ???? - CFBundleVersion - 1 - LSRequiresIPhoneOS - - UILaunchStoryboardName - LaunchScreen - UIMainStoryboardFile - Main - UIRequiredDeviceCapabilities - - armv7 - - UISupportedInterfaceOrientations - - UIInterfaceOrientationPortrait - UIInterfaceOrientationLandscapeLeft - UIInterfaceOrientationLandscapeRight - - UISupportedInterfaceOrientations~ipad - - UIInterfaceOrientationPortrait - UIInterfaceOrientationPortraitUpsideDown - UIInterfaceOrientationLandscapeLeft - UIInterfaceOrientationLandscapeRight - - - diff --git a/swift/tutorial3/tutorial3/ViewController.swift b/swift/tutorial3/tutorial3/ViewController.swift deleted file mode 100644 index af754f20..00000000 --- a/swift/tutorial3/tutorial3/ViewController.swift +++ /dev/null @@ -1,45 +0,0 @@ -// -// ViewController.swift -// tutorial3 -// -// Copyright © 2016 RabbitMQ. All rights reserved. -// -import UIKit - -import RMQClient - -class ViewController: UIViewController { - - - override func viewDidLoad() { - super.viewDidLoad() - self.receiveLogs() - self.receiveLogs() - sleep(1) - self.emitLog() - } - - func emitLog() { - let conn = RMQConnection(delegate: RMQConnectionDelegateLogger()) - conn.start() - let ch = conn.createChannel() - let x = ch.fanout("logs") - let msg = "Hello World!" - x.publish(msg.data(using: String.Encoding.utf8)) - print("Sent \(msg)") - conn.close() - } - - func receiveLogs() { - let conn = RMQConnection(delegate: RMQConnectionDelegateLogger()) - conn.start() - let ch = conn.createChannel() - let x = ch.fanout("logs") - let q = ch.queue("", options: .exclusive) - q.bind(x) - print("Waiting for logs.") - q.subscribe({(_ message: RMQMessage) -> Void in - print("Received \(String(data: message.body, encoding: String.Encoding.utf8)!)") - }) - } -} diff --git a/swift/tutorial4/Cartfile b/swift/tutorial4/Cartfile deleted file mode 100644 index 38483e08..00000000 --- a/swift/tutorial4/Cartfile +++ /dev/null @@ -1 +0,0 @@ -github "rabbitmq/rabbitmq-objc-client" "v0.10.0" diff --git a/swift/tutorial4/Cartfile.resolved b/swift/tutorial4/Cartfile.resolved deleted file mode 100644 index 750d6791..00000000 --- a/swift/tutorial4/Cartfile.resolved +++ /dev/null @@ -1,3 +0,0 @@ -github "robbiehanson/CocoaAsyncSocket" "7.5.1" -github "jeffh/JKVValue" "v1.3.2" -github "rabbitmq/rabbitmq-objc-client" "v0.10.0" diff --git a/swift/tutorial4/tutorial4.xcodeproj/project.pbxproj b/swift/tutorial4/tutorial4.xcodeproj/project.pbxproj deleted file mode 100644 index 70fafd25..00000000 --- a/swift/tutorial4/tutorial4.xcodeproj/project.pbxproj +++ /dev/null @@ -1,332 +0,0 @@ -// !$*UTF8*$! -{ - archiveVersion = 1; - classes = { - }; - objectVersion = 46; - objects = { - -/* Begin PBXBuildFile section */ - 1CBCD9C11DF9CF3300A2B5F9 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1CBCD9C01DF9CF3200A2B5F9 /* AppDelegate.swift */; }; - 1CBCD9C31DF9CF7D00A2B5F9 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1CBCD9C21DF9CF7D00A2B5F9 /* ViewController.swift */; }; - AEF0F7B41CCEC50A007DAF85 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = AEF0F7B21CCEC50A007DAF85 /* Main.storyboard */; }; - AEF0F7B61CCEC50A007DAF85 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = AEF0F7B51CCEC50A007DAF85 /* Assets.xcassets */; }; - AEF0F7B91CCEC50A007DAF85 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = AEF0F7B71CCEC50A007DAF85 /* LaunchScreen.storyboard */; }; - AEF0F7C11CCEC5BE007DAF85 /* RMQClient.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AEF0F7C01CCEC5BE007DAF85 /* RMQClient.framework */; }; - AEF0F7C31CCEC5CB007DAF85 /* RMQClient.framework in CopyFiles */ = {isa = PBXBuildFile; fileRef = AEF0F7C01CCEC5BE007DAF85 /* RMQClient.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; -/* End PBXBuildFile section */ - -/* Begin PBXCopyFilesBuildPhase section */ - AEF0F7C21CCEC5C2007DAF85 /* CopyFiles */ = { - isa = PBXCopyFilesBuildPhase; - buildActionMask = 2147483647; - dstPath = ""; - dstSubfolderSpec = 10; - files = ( - AEF0F7C31CCEC5CB007DAF85 /* RMQClient.framework in CopyFiles */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXCopyFilesBuildPhase section */ - -/* Begin PBXFileReference section */ - 1CBCD9C01DF9CF3200A2B5F9 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; - 1CBCD9C21DF9CF7D00A2B5F9 /* ViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; - AEF0F7A61CCEC50A007DAF85 /* tutorial4.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = tutorial4.app; sourceTree = BUILT_PRODUCTS_DIR; }; - AEF0F7B31CCEC50A007DAF85 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; - AEF0F7B51CCEC50A007DAF85 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; - AEF0F7B81CCEC50A007DAF85 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; - AEF0F7BA1CCEC50A007DAF85 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - AEF0F7C01CCEC5BE007DAF85 /* RMQClient.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = RMQClient.framework; path = Carthage/Build/iOS/RMQClient.framework; sourceTree = ""; }; -/* End PBXFileReference section */ - -/* Begin PBXFrameworksBuildPhase section */ - AEF0F7A31CCEC50A007DAF85 /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - AEF0F7C11CCEC5BE007DAF85 /* RMQClient.framework in Frameworks */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXFrameworksBuildPhase section */ - -/* Begin PBXGroup section */ - AEF0F79D1CCEC50A007DAF85 = { - isa = PBXGroup; - children = ( - AEF0F7C01CCEC5BE007DAF85 /* RMQClient.framework */, - AEF0F7A81CCEC50A007DAF85 /* tutorial4 */, - AEF0F7A71CCEC50A007DAF85 /* Products */, - ); - sourceTree = ""; - }; - AEF0F7A71CCEC50A007DAF85 /* Products */ = { - isa = PBXGroup; - children = ( - AEF0F7A61CCEC50A007DAF85 /* tutorial4.app */, - ); - name = Products; - sourceTree = ""; - }; - AEF0F7A81CCEC50A007DAF85 /* tutorial4 */ = { - isa = PBXGroup; - children = ( - 1CBCD9C01DF9CF3200A2B5F9 /* AppDelegate.swift */, - 1CBCD9C21DF9CF7D00A2B5F9 /* ViewController.swift */, - AEF0F7B21CCEC50A007DAF85 /* Main.storyboard */, - AEF0F7B51CCEC50A007DAF85 /* Assets.xcassets */, - AEF0F7B71CCEC50A007DAF85 /* LaunchScreen.storyboard */, - AEF0F7BA1CCEC50A007DAF85 /* Info.plist */, - ); - path = tutorial4; - sourceTree = ""; - }; -/* End PBXGroup section */ - -/* Begin PBXNativeTarget section */ - AEF0F7A51CCEC50A007DAF85 /* tutorial4 */ = { - isa = PBXNativeTarget; - buildConfigurationList = AEF0F7BD1CCEC50A007DAF85 /* Build configuration list for PBXNativeTarget "tutorial4" */; - buildPhases = ( - AEF0F7A21CCEC50A007DAF85 /* Sources */, - AEF0F7A31CCEC50A007DAF85 /* Frameworks */, - AEF0F7A41CCEC50A007DAF85 /* Resources */, - AEF0F7C21CCEC5C2007DAF85 /* CopyFiles */, - ); - buildRules = ( - ); - dependencies = ( - ); - name = tutorial4; - productName = tutorial4; - productReference = AEF0F7A61CCEC50A007DAF85 /* tutorial4.app */; - productType = "com.apple.product-type.application"; - }; -/* End PBXNativeTarget section */ - -/* Begin PBXProject section */ - AEF0F79E1CCEC50A007DAF85 /* Project object */ = { - isa = PBXProject; - attributes = { - LastUpgradeCheck = 0820; - ORGANIZATIONNAME = RabbitMQ; - TargetAttributes = { - AEF0F7A51CCEC50A007DAF85 = { - CreatedOnToolsVersion = 7.3; - LastSwiftMigration = 0800; - }; - }; - }; - buildConfigurationList = AEF0F7A11CCEC50A007DAF85 /* Build configuration list for PBXProject "tutorial4" */; - compatibilityVersion = "Xcode 3.2"; - developmentRegion = English; - hasScannedForEncodings = 0; - knownRegions = ( - en, - Base, - ); - mainGroup = AEF0F79D1CCEC50A007DAF85; - productRefGroup = AEF0F7A71CCEC50A007DAF85 /* Products */; - projectDirPath = ""; - projectRoot = ""; - targets = ( - AEF0F7A51CCEC50A007DAF85 /* tutorial4 */, - ); - }; -/* End PBXProject section */ - -/* Begin PBXResourcesBuildPhase section */ - AEF0F7A41CCEC50A007DAF85 /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - AEF0F7B91CCEC50A007DAF85 /* LaunchScreen.storyboard in Resources */, - AEF0F7B61CCEC50A007DAF85 /* Assets.xcassets in Resources */, - AEF0F7B41CCEC50A007DAF85 /* Main.storyboard in Resources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXResourcesBuildPhase section */ - -/* Begin PBXSourcesBuildPhase section */ - AEF0F7A21CCEC50A007DAF85 /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 1CBCD9C11DF9CF3300A2B5F9 /* AppDelegate.swift in Sources */, - 1CBCD9C31DF9CF7D00A2B5F9 /* ViewController.swift in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXSourcesBuildPhase section */ - -/* Begin PBXVariantGroup section */ - AEF0F7B21CCEC50A007DAF85 /* Main.storyboard */ = { - isa = PBXVariantGroup; - children = ( - AEF0F7B31CCEC50A007DAF85 /* Base */, - ); - name = Main.storyboard; - sourceTree = ""; - }; - AEF0F7B71CCEC50A007DAF85 /* LaunchScreen.storyboard */ = { - isa = PBXVariantGroup; - children = ( - AEF0F7B81CCEC50A007DAF85 /* Base */, - ); - name = LaunchScreen.storyboard; - sourceTree = ""; - }; -/* End PBXVariantGroup section */ - -/* Begin XCBuildConfiguration section */ - AEF0F7BB1CCEC50A007DAF85 /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_ANALYZER_NONNULL = YES; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - COPY_PHASE_STRIP = NO; - DEBUG_INFORMATION_FORMAT = dwarf; - ENABLE_STRICT_OBJC_MSGSEND = YES; - ENABLE_TESTABILITY = YES; - GCC_C_LANGUAGE_STANDARD = gnu99; - GCC_DYNAMIC_NO_PIC = NO; - GCC_NO_COMMON_BLOCKS = YES; - GCC_OPTIMIZATION_LEVEL = 0; - GCC_PREPROCESSOR_DEFINITIONS = ( - "DEBUG=1", - "$(inherited)", - ); - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 10.0; - MTL_ENABLE_DEBUG_INFO = YES; - ONLY_ACTIVE_ARCH = YES; - SDKROOT = iphoneos; - TARGETED_DEVICE_FAMILY = "1,2"; - }; - name = Debug; - }; - AEF0F7BC1CCEC50A007DAF85 /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_ANALYZER_NONNULL = YES; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - COPY_PHASE_STRIP = NO; - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - ENABLE_NS_ASSERTIONS = NO; - ENABLE_STRICT_OBJC_MSGSEND = YES; - GCC_C_LANGUAGE_STANDARD = gnu99; - GCC_NO_COMMON_BLOCKS = YES; - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 10.0; - MTL_ENABLE_DEBUG_INFO = NO; - SDKROOT = iphoneos; - SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; - TARGETED_DEVICE_FAMILY = "1,2"; - VALIDATE_PRODUCT = YES; - }; - name = Release; - }; - AEF0F7BE1CCEC50A007DAF85 /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - CLANG_ENABLE_MODULES = YES; - FRAMEWORK_SEARCH_PATHS = ( - "$(inherited)", - "$(PROJECT_DIR)/Carthage/Build/iOS", - ); - INFOPLIST_FILE = tutorial4/Info.plist; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; - PRODUCT_BUNDLE_IDENTIFIER = com.rabbitmq.tutorial4; - PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_VERSION = 3.0; - }; - name = Debug; - }; - AEF0F7BF1CCEC50A007DAF85 /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - CLANG_ENABLE_MODULES = YES; - FRAMEWORK_SEARCH_PATHS = ( - "$(inherited)", - "$(PROJECT_DIR)/Carthage/Build/iOS", - ); - INFOPLIST_FILE = tutorial4/Info.plist; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; - PRODUCT_BUNDLE_IDENTIFIER = com.rabbitmq.tutorial4; - PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_VERSION = 3.0; - }; - name = Release; - }; -/* End XCBuildConfiguration section */ - -/* Begin XCConfigurationList section */ - AEF0F7A11CCEC50A007DAF85 /* Build configuration list for PBXProject "tutorial4" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - AEF0F7BB1CCEC50A007DAF85 /* Debug */, - AEF0F7BC1CCEC50A007DAF85 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - AEF0F7BD1CCEC50A007DAF85 /* Build configuration list for PBXNativeTarget "tutorial4" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - AEF0F7BE1CCEC50A007DAF85 /* Debug */, - AEF0F7BF1CCEC50A007DAF85 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; -/* End XCConfigurationList section */ - }; - rootObject = AEF0F79E1CCEC50A007DAF85 /* Project object */; -} diff --git a/swift/tutorial4/tutorial4.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/swift/tutorial4/tutorial4.xcodeproj/project.xcworkspace/contents.xcworkspacedata deleted file mode 100644 index a37c2d60..00000000 --- a/swift/tutorial4/tutorial4.xcodeproj/project.xcworkspace/contents.xcworkspacedata +++ /dev/null @@ -1,7 +0,0 @@ - - - - - diff --git a/swift/tutorial4/tutorial4/AppDelegate.swift b/swift/tutorial4/tutorial4/AppDelegate.swift deleted file mode 100644 index a86eb72d..00000000 --- a/swift/tutorial4/tutorial4/AppDelegate.swift +++ /dev/null @@ -1,41 +0,0 @@ -// -// AppDelegate.swift -// tutorial4 -// -// Created by Pivotal on 25/04/2016. -// Copyright © 2016 RabbitMQ. All rights reserved. -// - -import UIKit -@UIApplicationMain -class AppDelegate: UIResponder, UIApplicationDelegate { - var window: UIWindow? - - - func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { - // Override point for customization after application launch. - return true - } - - func applicationWillResignActive(_ application: UIApplication) { - // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. - // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. - } - - func applicationDidEnterBackground(_ application: UIApplication) { - // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. - // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. - } - - func applicationWillEnterForeground(_ application: UIApplication) { - // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background. - } - - func applicationDidBecomeActive(_ application: UIApplication) { - // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. - } - - func applicationWillTerminate(_ application: UIApplication) { - // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. - } -} diff --git a/swift/tutorial4/tutorial4/Assets.xcassets/AppIcon.appiconset/Contents.json b/swift/tutorial4/tutorial4/Assets.xcassets/AppIcon.appiconset/Contents.json deleted file mode 100644 index 36d2c80d..00000000 --- a/swift/tutorial4/tutorial4/Assets.xcassets/AppIcon.appiconset/Contents.json +++ /dev/null @@ -1,68 +0,0 @@ -{ - "images" : [ - { - "idiom" : "iphone", - "size" : "29x29", - "scale" : "2x" - }, - { - "idiom" : "iphone", - "size" : "29x29", - "scale" : "3x" - }, - { - "idiom" : "iphone", - "size" : "40x40", - "scale" : "2x" - }, - { - "idiom" : "iphone", - "size" : "40x40", - "scale" : "3x" - }, - { - "idiom" : "iphone", - "size" : "60x60", - "scale" : "2x" - }, - { - "idiom" : "iphone", - "size" : "60x60", - "scale" : "3x" - }, - { - "idiom" : "ipad", - "size" : "29x29", - "scale" : "1x" - }, - { - "idiom" : "ipad", - "size" : "29x29", - "scale" : "2x" - }, - { - "idiom" : "ipad", - "size" : "40x40", - "scale" : "1x" - }, - { - "idiom" : "ipad", - "size" : "40x40", - "scale" : "2x" - }, - { - "idiom" : "ipad", - "size" : "76x76", - "scale" : "1x" - }, - { - "idiom" : "ipad", - "size" : "76x76", - "scale" : "2x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/swift/tutorial4/tutorial4/Base.lproj/LaunchScreen.storyboard b/swift/tutorial4/tutorial4/Base.lproj/LaunchScreen.storyboard deleted file mode 100644 index 2e721e18..00000000 --- a/swift/tutorial4/tutorial4/Base.lproj/LaunchScreen.storyboard +++ /dev/null @@ -1,27 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/swift/tutorial4/tutorial4/Base.lproj/Main.storyboard b/swift/tutorial4/tutorial4/Base.lproj/Main.storyboard deleted file mode 100644 index fe3d3532..00000000 --- a/swift/tutorial4/tutorial4/Base.lproj/Main.storyboard +++ /dev/null @@ -1,30 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/swift/tutorial4/tutorial4/Info.plist b/swift/tutorial4/tutorial4/Info.plist deleted file mode 100644 index 40c6215d..00000000 --- a/swift/tutorial4/tutorial4/Info.plist +++ /dev/null @@ -1,47 +0,0 @@ - - - - - CFBundleDevelopmentRegion - en - CFBundleExecutable - $(EXECUTABLE_NAME) - CFBundleIdentifier - $(PRODUCT_BUNDLE_IDENTIFIER) - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - $(PRODUCT_NAME) - CFBundlePackageType - APPL - CFBundleShortVersionString - 1.0 - CFBundleSignature - ???? - CFBundleVersion - 1 - LSRequiresIPhoneOS - - UILaunchStoryboardName - LaunchScreen - UIMainStoryboardFile - Main - UIRequiredDeviceCapabilities - - armv7 - - UISupportedInterfaceOrientations - - UIInterfaceOrientationPortrait - UIInterfaceOrientationLandscapeLeft - UIInterfaceOrientationLandscapeRight - - UISupportedInterfaceOrientations~ipad - - UIInterfaceOrientationPortrait - UIInterfaceOrientationPortraitUpsideDown - UIInterfaceOrientationLandscapeLeft - UIInterfaceOrientationLandscapeRight - - - diff --git a/swift/tutorial4/tutorial4/ViewController.swift b/swift/tutorial4/tutorial4/ViewController.swift deleted file mode 100644 index 378892dc..00000000 --- a/swift/tutorial4/tutorial4/ViewController.swift +++ /dev/null @@ -1,47 +0,0 @@ -// -// ViewController.swift -// tutorial4 -// -// Copyright © 2016 RabbitMQ. All rights reserved. -// -import UIKit -import RMQClient - -class ViewController: UIViewController { - - - override func viewDidLoad() { - super.viewDidLoad() - self.receiveLogsDirect() - sleep(2) - self.emitLogDirect("Hello World!", severity: "info") - self.emitLogDirect("Missile button pressed", severity: "warning") - self.emitLogDirect("Launch mechanism jammed", severity: "error") - } - - func receiveLogsDirect() { - let conn = RMQConnection(delegate: RMQConnectionDelegateLogger()) - conn.start() - let ch = conn.createChannel() - let x = ch.direct("direct_logs") - let q = ch.queue("", options: .exclusive) - let severities = ["error", "warning", "info"] - for severity: String in severities { - q.bind(x, routingKey: severity) - } - print("Waiting for logs.") - q.subscribe({(_ message: RMQMessage) -> Void in - print("\(message.routingKey!):\(String(data: message.body, encoding: .utf8)!)") - }) - } - - func emitLogDirect(_ msg: String, severity: String) { - let conn = RMQConnection(delegate: RMQConnectionDelegateLogger()) - conn.start() - let ch = conn.createChannel() - let x = ch.direct("direct_logs") - x.publish(msg.data(using: .utf8), routingKey: severity) - print("Sent '\(msg)'") - conn.close() - } -} diff --git a/swift/tutorial5/Cartfile b/swift/tutorial5/Cartfile deleted file mode 100644 index 38483e08..00000000 --- a/swift/tutorial5/Cartfile +++ /dev/null @@ -1 +0,0 @@ -github "rabbitmq/rabbitmq-objc-client" "v0.10.0" diff --git a/swift/tutorial5/Cartfile.resolved b/swift/tutorial5/Cartfile.resolved deleted file mode 100644 index 750d6791..00000000 --- a/swift/tutorial5/Cartfile.resolved +++ /dev/null @@ -1,3 +0,0 @@ -github "robbiehanson/CocoaAsyncSocket" "7.5.1" -github "jeffh/JKVValue" "v1.3.2" -github "rabbitmq/rabbitmq-objc-client" "v0.10.0" diff --git a/swift/tutorial5/tutorial5.xcodeproj/project.pbxproj b/swift/tutorial5/tutorial5.xcodeproj/project.pbxproj deleted file mode 100644 index 144b6788..00000000 --- a/swift/tutorial5/tutorial5.xcodeproj/project.pbxproj +++ /dev/null @@ -1,332 +0,0 @@ -// !$*UTF8*$! -{ - archiveVersion = 1; - classes = { - }; - objectVersion = 46; - objects = { - -/* Begin PBXBuildFile section */ - 1CBCD9C51DF9D45300A2B5F9 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1CBCD9C41DF9D45300A2B5F9 /* AppDelegate.swift */; }; - 1CBCD9C71DF9D47F00A2B5F9 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1CBCD9C61DF9D47F00A2B5F9 /* ViewController.swift */; }; - AE929C671CCF6FAF001A6524 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = AE929C651CCF6FAF001A6524 /* Main.storyboard */; }; - AE929C691CCF6FAF001A6524 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = AE929C681CCF6FAF001A6524 /* Assets.xcassets */; }; - AE929C6C1CCF6FAF001A6524 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = AE929C6A1CCF6FAF001A6524 /* LaunchScreen.storyboard */; }; - AE929C741CCF70A5001A6524 /* RMQClient.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AE929C731CCF70A5001A6524 /* RMQClient.framework */; }; - AE929C761CCF70C0001A6524 /* RMQClient.framework in CopyFiles */ = {isa = PBXBuildFile; fileRef = AE929C731CCF70A5001A6524 /* RMQClient.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; -/* End PBXBuildFile section */ - -/* Begin PBXCopyFilesBuildPhase section */ - AE929C751CCF70B8001A6524 /* CopyFiles */ = { - isa = PBXCopyFilesBuildPhase; - buildActionMask = 2147483647; - dstPath = ""; - dstSubfolderSpec = 10; - files = ( - AE929C761CCF70C0001A6524 /* RMQClient.framework in CopyFiles */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXCopyFilesBuildPhase section */ - -/* Begin PBXFileReference section */ - 1CBCD9C41DF9D45300A2B5F9 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; - 1CBCD9C61DF9D47F00A2B5F9 /* ViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; - AE929C591CCF6FAF001A6524 /* tutorial5.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = tutorial5.app; sourceTree = BUILT_PRODUCTS_DIR; }; - AE929C661CCF6FAF001A6524 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; - AE929C681CCF6FAF001A6524 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; - AE929C6B1CCF6FAF001A6524 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; - AE929C6D1CCF6FAF001A6524 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - AE929C731CCF70A5001A6524 /* RMQClient.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = RMQClient.framework; path = Carthage/Build/iOS/RMQClient.framework; sourceTree = ""; }; -/* End PBXFileReference section */ - -/* Begin PBXFrameworksBuildPhase section */ - AE929C561CCF6FAF001A6524 /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - AE929C741CCF70A5001A6524 /* RMQClient.framework in Frameworks */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXFrameworksBuildPhase section */ - -/* Begin PBXGroup section */ - AE929C501CCF6FAF001A6524 = { - isa = PBXGroup; - children = ( - AE929C731CCF70A5001A6524 /* RMQClient.framework */, - AE929C5B1CCF6FAF001A6524 /* tutorial5 */, - AE929C5A1CCF6FAF001A6524 /* Products */, - ); - sourceTree = ""; - }; - AE929C5A1CCF6FAF001A6524 /* Products */ = { - isa = PBXGroup; - children = ( - AE929C591CCF6FAF001A6524 /* tutorial5.app */, - ); - name = Products; - sourceTree = ""; - }; - AE929C5B1CCF6FAF001A6524 /* tutorial5 */ = { - isa = PBXGroup; - children = ( - AE929C651CCF6FAF001A6524 /* Main.storyboard */, - AE929C681CCF6FAF001A6524 /* Assets.xcassets */, - AE929C6A1CCF6FAF001A6524 /* LaunchScreen.storyboard */, - AE929C6D1CCF6FAF001A6524 /* Info.plist */, - 1CBCD9C41DF9D45300A2B5F9 /* AppDelegate.swift */, - 1CBCD9C61DF9D47F00A2B5F9 /* ViewController.swift */, - ); - path = tutorial5; - sourceTree = ""; - }; -/* End PBXGroup section */ - -/* Begin PBXNativeTarget section */ - AE929C581CCF6FAF001A6524 /* tutorial5 */ = { - isa = PBXNativeTarget; - buildConfigurationList = AE929C701CCF6FAF001A6524 /* Build configuration list for PBXNativeTarget "tutorial5" */; - buildPhases = ( - AE929C551CCF6FAF001A6524 /* Sources */, - AE929C561CCF6FAF001A6524 /* Frameworks */, - AE929C571CCF6FAF001A6524 /* Resources */, - AE929C751CCF70B8001A6524 /* CopyFiles */, - ); - buildRules = ( - ); - dependencies = ( - ); - name = tutorial5; - productName = tutorial5; - productReference = AE929C591CCF6FAF001A6524 /* tutorial5.app */; - productType = "com.apple.product-type.application"; - }; -/* End PBXNativeTarget section */ - -/* Begin PBXProject section */ - AE929C511CCF6FAF001A6524 /* Project object */ = { - isa = PBXProject; - attributes = { - LastUpgradeCheck = 0820; - ORGANIZATIONNAME = RabbitMQ; - TargetAttributes = { - AE929C581CCF6FAF001A6524 = { - CreatedOnToolsVersion = 7.3; - LastSwiftMigration = 0800; - }; - }; - }; - buildConfigurationList = AE929C541CCF6FAF001A6524 /* Build configuration list for PBXProject "tutorial5" */; - compatibilityVersion = "Xcode 3.2"; - developmentRegion = English; - hasScannedForEncodings = 0; - knownRegions = ( - en, - Base, - ); - mainGroup = AE929C501CCF6FAF001A6524; - productRefGroup = AE929C5A1CCF6FAF001A6524 /* Products */; - projectDirPath = ""; - projectRoot = ""; - targets = ( - AE929C581CCF6FAF001A6524 /* tutorial5 */, - ); - }; -/* End PBXProject section */ - -/* Begin PBXResourcesBuildPhase section */ - AE929C571CCF6FAF001A6524 /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - AE929C6C1CCF6FAF001A6524 /* LaunchScreen.storyboard in Resources */, - AE929C691CCF6FAF001A6524 /* Assets.xcassets in Resources */, - AE929C671CCF6FAF001A6524 /* Main.storyboard in Resources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXResourcesBuildPhase section */ - -/* Begin PBXSourcesBuildPhase section */ - AE929C551CCF6FAF001A6524 /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 1CBCD9C71DF9D47F00A2B5F9 /* ViewController.swift in Sources */, - 1CBCD9C51DF9D45300A2B5F9 /* AppDelegate.swift in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXSourcesBuildPhase section */ - -/* Begin PBXVariantGroup section */ - AE929C651CCF6FAF001A6524 /* Main.storyboard */ = { - isa = PBXVariantGroup; - children = ( - AE929C661CCF6FAF001A6524 /* Base */, - ); - name = Main.storyboard; - sourceTree = ""; - }; - AE929C6A1CCF6FAF001A6524 /* LaunchScreen.storyboard */ = { - isa = PBXVariantGroup; - children = ( - AE929C6B1CCF6FAF001A6524 /* Base */, - ); - name = LaunchScreen.storyboard; - sourceTree = ""; - }; -/* End PBXVariantGroup section */ - -/* Begin XCBuildConfiguration section */ - AE929C6E1CCF6FAF001A6524 /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_ANALYZER_NONNULL = YES; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - COPY_PHASE_STRIP = NO; - DEBUG_INFORMATION_FORMAT = dwarf; - ENABLE_STRICT_OBJC_MSGSEND = YES; - ENABLE_TESTABILITY = YES; - GCC_C_LANGUAGE_STANDARD = gnu99; - GCC_DYNAMIC_NO_PIC = NO; - GCC_NO_COMMON_BLOCKS = YES; - GCC_OPTIMIZATION_LEVEL = 0; - GCC_PREPROCESSOR_DEFINITIONS = ( - "DEBUG=1", - "$(inherited)", - ); - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 10.0; - MTL_ENABLE_DEBUG_INFO = YES; - ONLY_ACTIVE_ARCH = YES; - SDKROOT = iphoneos; - TARGETED_DEVICE_FAMILY = "1,2"; - }; - name = Debug; - }; - AE929C6F1CCF6FAF001A6524 /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_ANALYZER_NONNULL = YES; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - COPY_PHASE_STRIP = NO; - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - ENABLE_NS_ASSERTIONS = NO; - ENABLE_STRICT_OBJC_MSGSEND = YES; - GCC_C_LANGUAGE_STANDARD = gnu99; - GCC_NO_COMMON_BLOCKS = YES; - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 10.0; - MTL_ENABLE_DEBUG_INFO = NO; - SDKROOT = iphoneos; - SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; - TARGETED_DEVICE_FAMILY = "1,2"; - VALIDATE_PRODUCT = YES; - }; - name = Release; - }; - AE929C711CCF6FAF001A6524 /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - CLANG_ENABLE_MODULES = YES; - FRAMEWORK_SEARCH_PATHS = ( - "$(inherited)", - "$(PROJECT_DIR)/Carthage/Build/iOS", - ); - INFOPLIST_FILE = tutorial5/Info.plist; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; - PRODUCT_BUNDLE_IDENTIFIER = com.rabbitmq.tutorial5; - PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_VERSION = 3.0; - }; - name = Debug; - }; - AE929C721CCF6FAF001A6524 /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - CLANG_ENABLE_MODULES = YES; - FRAMEWORK_SEARCH_PATHS = ( - "$(inherited)", - "$(PROJECT_DIR)/Carthage/Build/iOS", - ); - INFOPLIST_FILE = tutorial5/Info.plist; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; - PRODUCT_BUNDLE_IDENTIFIER = com.rabbitmq.tutorial5; - PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_VERSION = 3.0; - }; - name = Release; - }; -/* End XCBuildConfiguration section */ - -/* Begin XCConfigurationList section */ - AE929C541CCF6FAF001A6524 /* Build configuration list for PBXProject "tutorial5" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - AE929C6E1CCF6FAF001A6524 /* Debug */, - AE929C6F1CCF6FAF001A6524 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - AE929C701CCF6FAF001A6524 /* Build configuration list for PBXNativeTarget "tutorial5" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - AE929C711CCF6FAF001A6524 /* Debug */, - AE929C721CCF6FAF001A6524 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; -/* End XCConfigurationList section */ - }; - rootObject = AE929C511CCF6FAF001A6524 /* Project object */; -} diff --git a/swift/tutorial5/tutorial5.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/swift/tutorial5/tutorial5.xcodeproj/project.xcworkspace/contents.xcworkspacedata deleted file mode 100644 index ac57ad93..00000000 --- a/swift/tutorial5/tutorial5.xcodeproj/project.xcworkspace/contents.xcworkspacedata +++ /dev/null @@ -1,7 +0,0 @@ - - - - - diff --git a/swift/tutorial5/tutorial5/AppDelegate.swift b/swift/tutorial5/tutorial5/AppDelegate.swift deleted file mode 100644 index c8398731..00000000 --- a/swift/tutorial5/tutorial5/AppDelegate.swift +++ /dev/null @@ -1,40 +0,0 @@ -// -// AppDelegate.swift -// tutorial5 -// -// Created by Pivotal on 25/04/2016. -// Copyright © 2016 RabbitMQ. All rights reserved. -// -import UIKit -@UIApplicationMain -class AppDelegate: UIResponder, UIApplicationDelegate { - var window: UIWindow? - - - func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { - // Override point for customization after application launch. - return true - } - - func applicationWillResignActive(_ application: UIApplication) { - // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. - // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. - } - - func applicationDidEnterBackground(_ application: UIApplication) { - // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. - // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. - } - - func applicationWillEnterForeground(_ application: UIApplication) { - // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background. - } - - func applicationDidBecomeActive(_ application: UIApplication) { - // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. - } - - func applicationWillTerminate(_ application: UIApplication) { - // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. - } -} diff --git a/swift/tutorial5/tutorial5/Assets.xcassets/AppIcon.appiconset/Contents.json b/swift/tutorial5/tutorial5/Assets.xcassets/AppIcon.appiconset/Contents.json deleted file mode 100644 index 36d2c80d..00000000 --- a/swift/tutorial5/tutorial5/Assets.xcassets/AppIcon.appiconset/Contents.json +++ /dev/null @@ -1,68 +0,0 @@ -{ - "images" : [ - { - "idiom" : "iphone", - "size" : "29x29", - "scale" : "2x" - }, - { - "idiom" : "iphone", - "size" : "29x29", - "scale" : "3x" - }, - { - "idiom" : "iphone", - "size" : "40x40", - "scale" : "2x" - }, - { - "idiom" : "iphone", - "size" : "40x40", - "scale" : "3x" - }, - { - "idiom" : "iphone", - "size" : "60x60", - "scale" : "2x" - }, - { - "idiom" : "iphone", - "size" : "60x60", - "scale" : "3x" - }, - { - "idiom" : "ipad", - "size" : "29x29", - "scale" : "1x" - }, - { - "idiom" : "ipad", - "size" : "29x29", - "scale" : "2x" - }, - { - "idiom" : "ipad", - "size" : "40x40", - "scale" : "1x" - }, - { - "idiom" : "ipad", - "size" : "40x40", - "scale" : "2x" - }, - { - "idiom" : "ipad", - "size" : "76x76", - "scale" : "1x" - }, - { - "idiom" : "ipad", - "size" : "76x76", - "scale" : "2x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/swift/tutorial5/tutorial5/Base.lproj/LaunchScreen.storyboard b/swift/tutorial5/tutorial5/Base.lproj/LaunchScreen.storyboard deleted file mode 100644 index 2e721e18..00000000 --- a/swift/tutorial5/tutorial5/Base.lproj/LaunchScreen.storyboard +++ /dev/null @@ -1,27 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/swift/tutorial5/tutorial5/Base.lproj/Main.storyboard b/swift/tutorial5/tutorial5/Base.lproj/Main.storyboard deleted file mode 100644 index dd721b26..00000000 --- a/swift/tutorial5/tutorial5/Base.lproj/Main.storyboard +++ /dev/null @@ -1,30 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/swift/tutorial5/tutorial5/Info.plist b/swift/tutorial5/tutorial5/Info.plist deleted file mode 100644 index 40c6215d..00000000 --- a/swift/tutorial5/tutorial5/Info.plist +++ /dev/null @@ -1,47 +0,0 @@ - - - - - CFBundleDevelopmentRegion - en - CFBundleExecutable - $(EXECUTABLE_NAME) - CFBundleIdentifier - $(PRODUCT_BUNDLE_IDENTIFIER) - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - $(PRODUCT_NAME) - CFBundlePackageType - APPL - CFBundleShortVersionString - 1.0 - CFBundleSignature - ???? - CFBundleVersion - 1 - LSRequiresIPhoneOS - - UILaunchStoryboardName - LaunchScreen - UIMainStoryboardFile - Main - UIRequiredDeviceCapabilities - - armv7 - - UISupportedInterfaceOrientations - - UIInterfaceOrientationPortrait - UIInterfaceOrientationLandscapeLeft - UIInterfaceOrientationLandscapeRight - - UISupportedInterfaceOrientations~ipad - - UIInterfaceOrientationPortrait - UIInterfaceOrientationPortraitUpsideDown - UIInterfaceOrientationLandscapeLeft - UIInterfaceOrientationLandscapeRight - - - diff --git a/swift/tutorial5/tutorial5/ViewController.swift b/swift/tutorial5/tutorial5/ViewController.swift deleted file mode 100644 index 0ce2c015..00000000 --- a/swift/tutorial5/tutorial5/ViewController.swift +++ /dev/null @@ -1,47 +0,0 @@ -// -// ViewController.swift -// tutorial5 -// -// Copyright © 2016 RabbitMQ. All rights reserved. -// -import UIKit -import RMQClient - -class ViewController: UIViewController { - - - override func viewDidLoad() { - super.viewDidLoad() - self.receiveLogsTopic(["kern.*", "*.critical"]) - sleep(2) - self.emitLogTopic("Hello World!", routingKey: "kern.info") - self.emitLogTopic("A critical kernel error", routingKey: "kern.critical") - self.emitLogTopic("Critical module error", routingKey: "somemod.critical") - self.emitLogTopic("Just some module info. You won't get this.", routingKey: "somemod.info") - } - - func receiveLogsTopic(_ routingKeys: [String]) { - let conn = RMQConnection(delegate: RMQConnectionDelegateLogger()) - conn.start() - let ch = conn.createChannel() - let x = ch.topic("topic_logs") - let q = ch.queue("", options: .exclusive) - for routingKey: String in routingKeys { - q.bind(x, routingKey: routingKey) - } - print("Waiting for logs.") - q.subscribe({(_ message: RMQMessage) -> Void in - print("\(message.routingKey!):\(String(data: message.body, encoding: .utf8)!)") - }) - } - - func emitLogTopic(_ msg: String, routingKey: String) { - let conn = RMQConnection(delegate: RMQConnectionDelegateLogger()) - conn.start() - let ch = conn.createChannel() - let x = ch.topic("topic_logs") - x.publish(msg.data(using: .utf8), routingKey: routingKey) - print("Sent '\(msg)'") - conn.close() - } -}