├── .github └── workflows │ └── ci.yml ├── .gitignore ├── .rspec ├── .yardopts ├── ChangeLog.md ├── Gemfile ├── LICENSE ├── README.md ├── Rakefile ├── bin └── ci │ ├── before_build.sh │ ├── before_build_docker.sh │ └── install_on_debian.sh ├── docs └── guides │ ├── bindings.md │ ├── community.md │ ├── connecting.md │ ├── durability.md │ ├── error_handling.md │ ├── exchanges.md │ ├── extensions.md │ ├── getting_started.md │ ├── guides.md │ ├── queues.md │ └── troubleshooting.md ├── examples ├── README.md ├── blocked_connection_notifications.rb ├── connection │ ├── automatic_consumer_recovery.rb │ ├── publisher_recovery_with_begin_rescue_retry.rb │ └── recovering_merry_go_around.rb ├── guides │ ├── exchanges │ │ ├── direct_exchange_routing.rb │ │ ├── fanout_exchange_routing.rb │ │ └── mandatory_messages.rb │ ├── extensions │ │ ├── alternate_exchange.rb │ │ ├── basic_nack.rb │ │ ├── consumer_cancellation_notification.rb │ │ ├── dead_letter_exchange.rb │ │ ├── exchange_to_exchange_bindings.rb │ │ ├── per_message_ttl.rb │ │ ├── per_queue_message_ttl.rb │ │ ├── publisher_confirms.rb │ │ ├── queue_lease.rb │ │ └── sender_selected_distribution.rb │ ├── getting_started │ │ ├── blabbr.rb │ │ ├── hello_world.rb │ │ └── weathr.rb │ └── queues │ │ └── redeliveries.rb └── non_blocking_subscription.rb ├── lib ├── ext │ ├── rabbitmq-client.jar │ ├── slf4j-api.jar │ └── slf4j-simple.jar ├── march_hare.rb └── march_hare │ ├── channel.rb │ ├── consumers.rb │ ├── consumers │ ├── base.rb │ └── blocking.rb │ ├── exception_handler.rb │ ├── exceptions.rb │ ├── exchange.rb │ ├── juc.rb │ ├── metadata.rb │ ├── queue.rb │ ├── session.rb │ ├── shutdown_listener.rb │ ├── thread_pools.rb │ ├── version.rb │ └── versioned_delivery_tag.rb ├── march_hare.gemspec ├── repl └── spec ├── higher_level_api └── integration │ ├── alternate_exchanges_spec.rb │ ├── basic_ack_spec.rb │ ├── basic_cancel_spec.rb │ ├── basic_consume_spec.rb │ ├── basic_reject_spec.rb │ ├── channel_close_spec.rb │ ├── connection_recovery_spec.rb │ ├── connection_spec.rb │ ├── consumer_cancellation_notification_spec.rb │ ├── exchange_bind_spec.rb │ ├── exchange_declaration_failure_spec.rb │ ├── exchange_declare_spec.rb │ ├── exchange_publish_spec.rb │ ├── exchange_unbind_spec.rb │ ├── merry_go_round_spec.rb │ ├── message_metadata_access_spec.rb │ ├── publisher_confirmations_spec.rb │ ├── queue_bind_spec.rb │ ├── queue_declare_spec.rb │ ├── queue_delete_spec.rb │ ├── queue_purge_spec.rb │ ├── queue_unbind_spec.rb │ ├── sender_selected_distribution_spec.rb │ └── tls_spec.rb ├── spec_helper.rb └── tls ├── .gitignore └── README.md /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: [main] 6 | paths: 7 | - ".github/workflows/*" 8 | - "lib/**" 9 | - "spec/**" 10 | - "Gemfile" 11 | pull_request: 12 | branches: [main] 13 | 14 | jobs: 15 | test: 16 | runs-on: ubuntu-latest 17 | 18 | services: 19 | rabbitmq: 20 | image: rabbitmq:4-management 21 | ports: 22 | - 5672:5672 23 | - 15672:15672 24 | options: --name rabbitmq 25 | 26 | strategy: 27 | fail-fast: false 28 | matrix: 29 | ruby-version: 30 | - jruby-9.3 31 | - jruby-9.4 32 | - jruby-head 33 | 34 | steps: 35 | - uses: actions/checkout@v2 36 | - name: Set up Ruby 37 | uses: ruby/setup-ruby@v1 38 | with: 39 | ruby-version: ${{ matrix.ruby-version }} 40 | bundler-cache: true # runs 'bundle install' and caches installed gems automatically 41 | - name: Set up RabbitMQ 42 | run: | 43 | until sudo lsof -i:5672; do echo "Waiting for RabbitMQ to start..."; sleep 1; done 44 | ./bin/ci/before_build_docker.sh 45 | - name: Run tests 46 | run: bundle exec rspec spec 47 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.gem 2 | .bundle 3 | Gemfile.lock 4 | pkg/* 5 | .DS_Store 6 | .rvmrc 7 | doc/* 8 | .yardoc/* 9 | debug/* 10 | deploy.docs.sh 11 | .ruby-version 12 | .idea 13 | spec/examples.txt 14 | -------------------------------------------------------------------------------- /.rspec: -------------------------------------------------------------------------------- 1 | --backtrace 2 | --color 3 | --require spec_helper 4 | -------------------------------------------------------------------------------- /.yardopts: -------------------------------------------------------------------------------- 1 | --no-private 2 | --protected 3 | --markup="markdown" lib/**/*.rb 4 | --main README.md 5 | --hide-tag todo 6 | - 7 | LICENSE 8 | ChangeLog.md 9 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | 3 | gem 'rake', ">= 12.0.0" 4 | 5 | group :test do 6 | gem "rspec", "~> 3.11" 7 | gem "rabbitmq_http_api_client", ">= 2.2.0" 8 | end 9 | 10 | group :development do 11 | gem "yard" 12 | # JRuby-friendly Markdown renderer 13 | gem "kramdown", :platform => :jruby 14 | 15 | gem "faraday" 16 | end 17 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2011-2013 Theo Hultberg 2 | Copyright (c) 2013-2021 Michael S. Klishin and contributors 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining 5 | a copy of this software and associated documentation files (the 6 | "Software"), to deal in the Software without restriction, including 7 | without limitation the rights to use, copy, modify, merge, publish, 8 | distribute, sublicense, and/or sell copies of the Software, and to 9 | permit persons to whom the Software is furnished to do so, subject to 10 | the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be 13 | included in all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 17 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 19 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 20 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 21 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # March Hare, a JRuby RabbitMQ Client 2 | 3 | March Hare is an idiomatic, fast and well-maintained (J)Ruby DSL on top of the [RabbitMQ Java client](https://www.rabbitmq.com/client-libraries/java-api-guide). 4 | 5 | It strives to combine strong parts of the Java client with over many years of experience with other client libraries, 6 | both for Ruby and other languages. 7 | 8 | ## Why March Hare 9 | 10 | * Concurrency support on the JVM is excellent, with many tools & approaches available. Lets make use of it. 11 | * RabbitMQ Java client is rock solid and supports every RabbitMQ feature. Very nice. 12 | * It is screaming fast thanks to all the heavy duty being done in the pretty efficient & lightweight Java code. 13 | * It uses synchronous APIs where it makes sense and asynchronous APIs where it makes sense. Some other [Ruby RabbitMQ clients](https://github.com/ruby-amqp) 14 | only use one or the other. 15 | * [amqp gem](https://github.com/ruby-amqp/amqp) has certain amount of baggage it cannot drop because of backwards compatibility concerns. March Hare is a 16 | clean room design, much more open to radical new ideas. 17 | 18 | 19 | ## What March Hare is not 20 | 21 | March Hare is not 22 | 23 | * A replacement for the RabbitMQ Java client 24 | * A replacement for Bunny, the most popular Ruby RabbitMQ client 25 | * A long running "work queue" service 26 | 27 | 28 | ## Project Maturity 29 | 30 | March Hare has been around since 2011 and can be considered a mature library. 31 | 32 | It is based on the [RabbitMQ Java client](https://www.rabbitmq.com/java-client.html), which is officially 33 | supported by the [RabbitMQ team at VMware](https://github.com/rabbitmq/). 34 | 35 | 36 | ## Installation, Dependency 37 | 38 | ### With Rubygems 39 | 40 | ``` shell 41 | gem install march_hare 42 | ``` 43 | 44 | ### With Bundler 45 | 46 | ``` ruby 47 | gem "march_hare", "~> 4.7" 48 | ``` 49 | 50 | ## Documentation 51 | 52 | ### Guides 53 | 54 | [MarchHare documentation guides](https://github.com/ruby-amqp/march_hare/tree/master/docs/guides) are now 55 | a part of this repository and can be read directly on GitHub. 56 | 57 | ### Examples 58 | 59 | Several [code examples](./examples) are available. Our [test suite](./spec/higher_level_api/integration) also has many code examples 60 | that demonstrate various parts of the API. 61 | 62 | 63 | ## Supported Ruby Versions 64 | 65 | March Hare supports JRuby 9.0 or later. 66 | 67 | 68 | ## Supported JDK Versions 69 | 70 | March Hare requires JDK 8 or later. 71 | 72 | 73 | ## Change Log 74 | 75 | See [ChangeLog.md](ChangeLog.md). 76 | 77 | 78 | ## Continuous Integration 79 | 80 | [![Continuous Integration status](https://secure.travis-ci.org/ruby-amqp/march_hare.svg)](http://travis-ci.org/ruby-amqp/march_hare) 81 | 82 | CI is hosted by [travis-ci.org](http://travis-ci.org) 83 | 84 | 85 | ## Testing 86 | 87 | You'll need a running RabbitMQ instance with all defaults and 88 | management plugin enabled on your local machine to run the specs. 89 | 90 | To boot one via docker you can use: 91 | 92 | ```bash 93 | docker run -p 5672:5672 -p 15672:15672 rabbitmq:3-management 94 | ``` 95 | 96 | And then you can run the specs using `rspec`: 97 | 98 | ```bash 99 | bundle exec rspec 100 | ``` 101 | 102 | 103 | ## License 104 | 105 | MIT, see LICENSE in the repository root 106 | 107 | 108 | ## Copyright 109 | 110 | (c) 2011-2013 Theo Hultberg 111 | (c) 2013-2025 Michael S. Klishin and contributors 112 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require 'bundler' 4 | 5 | 6 | Bundler::GemHelper.install_tasks 7 | -------------------------------------------------------------------------------- /bin/ci/before_build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | CTL=${MARCH_HARE_RABBITMQCTL:="sudo rabbitmqctl"} 4 | PLUGINS=${MARCH_HARE_RABBITMQ_PLUGINS:="sudo rabbitmq-plugins"} 5 | 6 | $PLUGINS enable rabbitmq_management 7 | 8 | sleep 3 9 | 10 | # guest:guest has full access to / 11 | 12 | $CTL add_vhost / 13 | $CTL add_user guest guest 14 | $CTL set_permissions -p / guest ".*" ".*" ".*" 15 | 16 | # Reduce retention policy for faster publishing of stats 17 | $CTL eval 'supervisor2:terminate_child(rabbit_mgmt_sup_sup, rabbit_mgmt_sup), application:set_env(rabbitmq_management, sample_retention_policies, [{global, [{605, 1}]}, {basic, [{605, 1}]}, {detailed, [{10, 1}]}]), rabbit_mgmt_sup_sup:start_child().' 18 | $CTL eval 'supervisor2:terminate_child(rabbit_mgmt_agent_sup_sup, rabbit_mgmt_agent_sup), application:set_env(rabbitmq_management_agent, sample_retention_policies, [{global, [{605, 1}]}, {basic, [{605, 1}]}, {detailed, [{10, 1}]}]), rabbit_mgmt_agent_sup_sup:start_child().' 19 | 20 | sleep 3 21 | -------------------------------------------------------------------------------- /bin/ci/before_build_docker.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | CTL=${MARCH_HARE_RABBITMQCTL:="docker exec rabbitmq rabbitmqctl"} 4 | PLUGINS=${MARCH_HARE_RABBITMQ_PLUGINS:="docker exec rabbitmq rabbitmq-plugins"} 5 | 6 | $PLUGINS enable rabbitmq_management 7 | 8 | sleep 3 9 | 10 | # guest:guest has full access to / 11 | 12 | $CTL add_vhost / 13 | # $CTL add_user guest guest # already exists 14 | $CTL set_permissions -p / guest ".*" ".*" ".*" 15 | 16 | # Reduce retention policy for faster publishing of stats 17 | $CTL eval 'supervisor2:terminate_child(rabbit_mgmt_sup_sup, rabbit_mgmt_sup), application:set_env(rabbitmq_management, sample_retention_policies, [{global, [{605, 1}]}, {basic, [{605, 1}]}, {detailed, [{10, 1}]}]), rabbit_mgmt_sup_sup:start_child().' 18 | $CTL eval 'supervisor2:terminate_child(rabbit_mgmt_agent_sup_sup, rabbit_mgmt_agent_sup), application:set_env(rabbitmq_management_agent, sample_retention_policies, [{global, [{605, 1}]}, {basic, [{605, 1}]}, {detailed, [{10, 1}]}]), rabbit_mgmt_agent_sup_sup:start_child().' 19 | 20 | sleep 3 21 | -------------------------------------------------------------------------------- /bin/ci/install_on_debian.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | sudo apt-get install curl gnupg debian-keyring debian-archive-keyring apt-transport-https -y 4 | 5 | ## Team RabbitMQ's main signing key 6 | sudo apt-key adv --keyserver "hkps://keys.openpgp.org" --recv-keys "0x0A9AF2115F4687BD29803A206B73A36E6026DFCA" 7 | ## Launchpad PPA that provides modern Erlang releases 8 | sudo apt-key adv --keyserver "keyserver.ubuntu.com" --recv-keys "F77F1EDA57EBB1CC" 9 | ## PackageCloud RabbitMQ repository 10 | sudo apt-key adv --keyserver "keyserver.ubuntu.com" --recv-keys "F6609E60DC62814E" 11 | 12 | ## Add apt repositories maintained by Team RabbitMQ 13 | sudo tee /etc/apt/sources.list.d/rabbitmq.list <Creative Commons Attribution 3.0 Unported License 11 | (including images and stylesheets). The source is available [on Github](https://github.com/ruby-amqp/rubymarchhare.info). 12 | 13 | 14 | ## What version of March Hare does this guide cover?? 15 | 16 | This guide covers March Hare 3.0. 17 | 18 | 19 | ## Bindings in AMQP 0.9.1 20 | 21 | Learn more about how bindings fit into the AMQP Model in the [AMQP 0.9.1 Model Concepts](http://www.rabbitmq.com/tutorials/amqp-concepts.html) guide. 22 | 23 | 24 | ## What Are AMQP 0.9.1 Bindings 25 | 26 | Bindings are rules that exchanges use (among other things) to route messages to queues. To instruct an exchange E to route messages to a queue Q, Q has to _be bound_ to E. 27 | Bindings may have an optional _routing key_ attribute used by some exchange types. The purpose of the routing key is to selectively match only specific (matching) messages 28 | published to an exchange to the bound queue. In other words, the routing key acts like a filter. 29 | 30 | To draw an analogy: 31 | 32 | * Queue is like your destination in New York city 33 | * Exchange is like JFK airport 34 | * Bindings are routes from JFK to your destination. There may be no way, or more than one way, to reach it 35 | 36 | Some exchange types use routing keys while some others do not (routing messages unconditionally or based on message metadata). If an AMQP message cannot be routed to any 37 | queue (for example, because there are no bindings for the exchange it was published to), it is either dropped or returned to the publisher, depending on the message 38 | attributes that the publisher has set. 39 | 40 | If an application wants to connect a queue to an exchange, it needs to _bind_ them. The opposite operation is called _unbinding_. 41 | 42 | ## Binding Queues to Exchanges 43 | 44 | In order to receive messages, a queue needs to be bound to at least one exchange. Most of the time binding is explcit (done by applications). 45 | To bind a queue to an exchange, use `MarchHare::Queue#bind` where the argument passed can be either an `MarchHare::Exchange` instance or a string. 46 | 47 | ``` ruby 48 | q.bind(x) 49 | ``` 50 | 51 | The same example using a string without a callback: 52 | 53 | ``` ruby 54 | q.bind("amq.fanout") 55 | ``` 56 | 57 | 58 | ## Unbinding queues from exchanges 59 | 60 | To unbind a queue from an exchange use `MarchHare::Queue#unbind`: 61 | 62 | ``` ruby 63 | q.unbind(x) 64 | ``` 65 | Trying to unbind a queue from an exchange that the queue was never bound to will result in a channel-level exception. 66 | 67 | 68 | ## Exchange-to-Exchange Bindings 69 | 70 | Exchange-to-Exchange bindings is a RabbitMQ extension to AMQP 0.9.1. It is covered in the [RabbitMQ extensions guide](/articles/extensions.html). 71 | 72 | 73 | ## Bindings, Routing and Returned Messages 74 | 75 | ### How RabbitMQ Routes Messages 76 | 77 | After an AMQP message reaches RabbitMQ and before it reaches a consumer, several things happen: 78 | 79 | * RabbitMQ needs to find one or more queues that the message needs to be routed to, depending on type of exchange 80 | * RabbitMQ puts a copy of the message into each of those queues or decides to return the message to the publisher 81 | * RabbitMQ pushes message to consumers on those queues or waits for applications to fetch them on demand 82 | 83 | A more in-depth description is this: 84 | 85 | * RabbitMQ needs to consult bindings list for the exchange the message was published to in order to find one or more queues that the message needs to be routed to (step 1) 86 | * If there are no suitable queues found during step 1 and the message was published as mandatory, it is returned to the publisher (step 1b) 87 | * If there are suitable queues, a _copy_ of the message is placed into each one (step 2) 88 | * If the message was published as mandatory, but there are no active consumers for it, it is returned to the publisher (step 2b) 89 | * If there are active consumers on those queues and the basic.qos setting permits, message is pushed to those consumers (step 3) 90 | 91 | The important thing to take away from this is that messages may or may not be routed and it is important for applications to handle unroutable messages. 92 | 93 | ### Handling of Unroutable Messages 94 | 95 | Unroutable messages are either dropped or returned to producers. RabbitMQ extensions can provide additional ways of handling unroutable messages: for example, 96 | RabbitMQ's [Alternate Exchanges extension](http://www.rabbitmq.com/ae.html) makes it possible to route unroutable messages to another exchange. 97 | Bunny support for it is documented in the [RabbitMQ Extensions guide](/articles/extensions.html). 98 | 99 | Bunny provides a way to handle returned messages with the `MarchHare::Exchange#on_return` method: 100 | 101 | ``` ruby 102 | ch.on_return do |reply_code, reply_text, exchange, routing_key, basic_properties, payload| 103 | puts "Got a returned message: #{payload}, reply: #{reply_code} #{reply_text}" 104 | end 105 | ``` 106 | 107 | [Exchanges and Publishing](/articles/exchanges.html) documentation guide provides more information on the subject, including full code examples. 108 | 109 | 110 | ## What to Read Next 111 | 112 | The documentation is organized as [a number of guides](/articles/guides.html), covering various topics. 113 | 114 | We recommend that you read the following guides first, if possible, in this order: 115 | 116 | * [RabbitMQ Extensions to AMQP 0.9.1](/articles/extensions.html) 117 | * [Durability and Related Matters](/articles/durability.html) 118 | * [Error Handling and Recovery](/articles/error_handling.html) 119 | * [Troubleshooting](/articles/troubleshooting.html) 120 | * [Using TLS (SSL) Connections](/articles/tls.html) 121 | 122 | 123 | ## Tell Us What You Think! 124 | 125 | Please take a moment to tell us what you think about this guide [on Twitter](http://twitter.com/rubyamqp) or the [March Hare mailing list](https://groups.google.com/forum/#!forum/ruby-amqp) 126 | 127 | Let us know what was unclear or what has not been covered. Maybe you do not like the guide style or grammar or discover spelling mistakes. Reader feedback is key to making the documentation better. 128 | -------------------------------------------------------------------------------- /docs/guides/community.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "March Hare: Community and Getting Help" 3 | layout: article 4 | --- 5 | 6 | ## Mailing List 7 | 8 | [March Hare has a mailing list](groups.google.com/group/ruby-amqp). We encourage you 9 | to also join the [rabbitmq-users](https://groups.google.com/forum/#!forum/rabbitmq-users) mailing list. Feel free to ask any questions that you may have. 10 | 11 | 12 | ## IRC 13 | 14 | For more immediate help, please join `#rabbitmq` on `irc.freenode.net`. 15 | 16 | 17 | ## News & Announcements on Twitter 18 | 19 | To subscribe for announcements of releases, important changes and so on, please follow [@rubyamqp](https://twitter.com/#!/rubyamqp) on Twitter. 20 | 21 | 22 | ## Reporting Issues 23 | 24 | If you find a bug, poor default, missing feature or find any part of the API inconvenient, please [file an issue](http://github.com/ruby-amqp/bunny/issues) on GitHub. 25 | When filing an issue, please specify which Bunny and RabbitMQ versions you are using, provide recent RabbitMQ log file contents if possible, 26 | and try to explain what behavior you expected and why. Bonus points for contributing failing test cases. 27 | 28 | 29 | ## Contributing 30 | 31 | First, clone the repository and run 32 | 33 | bundle install --binstubs 34 | 35 | and then run tests with 36 | 37 | ./bin/rspec -cfs spec 38 | 39 | After that create a branch and make your changes on it. Once you are done with your changes and all tests pass, submit a pull request 40 | on GitHub. 41 | -------------------------------------------------------------------------------- /docs/guides/connecting.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Connecting to RabbitMQ from Ruby with March Hare" 3 | layout: article 4 | --- 5 | 6 | ## About this guide 7 | 8 | This guide covers connection to RabbitMQ with MarchHare, connection error handling, authentication failure handling and related issues. 9 | 10 | This work is licensed under a Creative Commons Attribution 3.0 Unported License 11 | (including images and stylesheets). The source is available [on Github](https://github.com/ruby-amqp/rubymarchhare.info). 12 | 13 | 14 | ## What version of March Hare does this guide cover?? 15 | 16 | This guide covers March Hare 3.0. 17 | 18 | 19 | 20 | ## Two ways to specify connection parameters 21 | 22 | With Bunny, connection parameters (host, port, username, vhost and so on) can be passed in two forms: 23 | 24 | * As a map of attributes 25 | * As a connection URI string (à la JDBC) 26 | 27 | 28 | ### Using a Map of Parameters 29 | 30 | Map options that Bunny will recognize are 31 | 32 | * `:host` 33 | * `:port` 34 | * `:user` or `:username` 35 | * `:pass` or `:password` 36 | * `:vhost` or `virtual_host` 37 | * `:heartbeat` or `:heartbeat_interval`, in seconds, default is 0 (no heartbeats). `:server` means "use the value from RabbitMQ config" 38 | 39 | To connect to RabbitMQ with a map of parameters, pass them to `MarchHare.connect`. The connection 40 | will be established immediately: 41 | 42 | ``` ruby 43 | conn = MarchHare.connect(:host => "localhost", :vhost => "myapp.production", :user => "bunny", :password => "t0ps3kret") 44 | ``` 45 | 46 | `MarchHare.connect` returns a connection instance that is used to open channels. More about channels later in this guide. 47 | 48 | #### Default parameters 49 | 50 | Default connection parameters are 51 | 52 | ``` ruby 53 | { 54 | :hostname => "127.0.0.1", 55 | :port => 5672, 56 | :ssl => false, 57 | :vhost => "/", 58 | :username => "guest", 59 | :password => "guest", 60 | :heartbeat => 0, # will use RabbitMQ setting 61 | } 62 | ``` 63 | 64 | 65 | ### Using Connection URIs (Strings) 66 | 67 | It is also possible to specify connection parameters as a URI string: 68 | 69 | ``` ruby 70 | MarchHare.connect(:uri => "amqp://guest:guest@vm188.dev.megacorp.com/profitd.qa") 71 | ``` 72 | 73 | Unfortunately, there is no URI standard for AMQP URIs, so while several schemes used in the wild share the same basic idea, they differ in some details. 74 | The implementation used by Bunny aims to encourage URIs that work as widely as possible. 75 | 76 | Here are some examples of valid AMQP URIs: 77 | 78 | * amqp://dev.rabbitmq.com 79 | * amqp://dev.rabbitmq.com:5672 80 | * amqp://guest:guest@dev.rabbitmq.com:5672 81 | * amqp://hedgehog:t0ps3kr3t@hub.megacorp.internal/production 82 | * amqps://hub.megacorp.internal/%2Fvault 83 | 84 | The URI scheme should be "amqp", or "amqps" if SSL is required. 85 | 86 | The host, port, username and password are represented in the authority component of the URI in the same way as in HTTP URIs. 87 | 88 | The vhost is obtained from the first segment of the path, with the leading slash removed. The path should contain only a single segment (i.e, the only slash in it should be the leading one). If the vhost is to include slashes or other reserved URI characters, these should be percent-escaped. 89 | 90 | ### Connection Failures 91 | 92 | If a connection does not succeed, Bunny will raise one of the following exceptions: 93 | 94 | * `MarchHare::PossibleAuthenticationFailureException` indicates an authentication issue or that connection to RabbitMQ was closed before successfully finishing connection negotiation 95 | * `java.net.UnknownHostException` indicates that the host is misconfigured or does not resolve (e.g. due to a DNS issue) 96 | * `java.net.ConnectException` indicates that RabbitMQ is not running on the target host or it binds to a different port 97 | 98 | 99 | ### Heartbeats and Connection Failure Detection 100 | 101 | Due to how TCP works, it sometimes can take a while (minutes) to detect an unresponsive 102 | peer. To make connection failure detection quicker, the protocol has a feature called 103 | "heartbeats". Client and server exchange heartbeat frames periodically (about 1/2 104 | the configured timeout value). When either peer detects 2 missed heartbeats, it 105 | should consider the connection to be dead. 106 | 107 | `:heartbeat` is the option passed to `MarchHare.connect` to configure the desired 108 | timeout interval. We recommend setting this value in the 10-30 seconds range. 109 | 110 | Enabling heartbeats will also ensure firewalls won't consider connections with low 111 | activity to be stale. 112 | 113 | 114 | 115 | ## PaaS Environments 116 | 117 | ### The RABBITMQ_URL Environment Variable 118 | 119 | Several PaaS environments set `RABBITMQ_URL` environment variable to indicate the connection URI 120 | to use. In this case, connect using the connection URI. Accessing environment variables 121 | with March Hare is not any different from any other Ruby code: 122 | 123 | ``` ruby 124 | MarchHare.connect(:uri => ENV["RABBITMQ_URL"]) 125 | ``` 126 | 127 | 128 | ## Opening a Channel 129 | 130 | Some applications need multiple connections to RabbitMQ. However, it is undesirable to keep many TCP connections open at the same time because 131 | doing so consumes system resources and makes it more difficult to configure firewalls. AMQP 0-9-1 connections are multiplexed with channels that can 132 | be thought of as "lightweight connections that share a single TCP connection". 133 | 134 | To open a channel, use the `MarchHare::Session#create_channel` method: 135 | 136 | ``` ruby 137 | conn = MarchHare.connect 138 | ch = conn.create_channel 139 | ``` 140 | 141 | Channels are typically long lived: you open one or more of them and use them for a period of time, as opposed to opening 142 | a new channel for each published message, for example. 143 | 144 | 145 | ## Closing Channels 146 | 147 | To close a channel, use the `MarchHare::Channel#close` method. A closed channel 148 | can no longer be used. 149 | 150 | ``` ruby 151 | conn = Bunny.new 152 | conn.start 153 | 154 | ch = conn.create_channel 155 | ch.close 156 | ``` 157 | 158 | 159 | ## Connecting in Web applications (Ruby on Rails, Sinatra, etc) 160 | 161 | When connecting in Web apps, the rule of thumb is: do it in an initializer, not controller 162 | actions or request handlers. 163 | 164 | ### Ruby on Rails 165 | 166 | Currently Bunny does not have integration points for Rails (e.g. a rail tie). 167 | 168 | 169 | ## Disconnecting 170 | 171 | To close a connection, use the `MarchHare::Session#close` function. This will automatically 172 | close all channels of that connection first: 173 | 174 | ``` ruby 175 | conn = Bunny.new 176 | conn.start 177 | 178 | conn.close 179 | ``` 180 | 181 | 182 | ## Troubleshooting 183 | 184 | If you have read this guide and still have issues with connecting, check our [Troubleshooting guide](/articles/troubleshooting.html) 185 | and feel free to ask [on the mailing list](https://groups.google.com/forum/#!forum/ruby-amqp). 186 | 187 | 188 | ## Wrapping Up 189 | 190 | There are two ways to specify connection parameters with Bunny: with a map of parameters or via URI string. 191 | Connection issues are indicated by various exceptions. If the `RABBITMQ_URL` env variable is set, Bunny 192 | will use its value as RabbitMQ connection URI. 193 | 194 | 195 | ## What to Read Next 196 | 197 | The documentation is organized as [a number of guides](/articles/guides.html), covering various topics. 198 | 199 | We recommend that you read the following guides first, if possible, in this order: 200 | 201 | * [Queues and Consumers](/articles/queues.html) 202 | * [Exchanges and Publishing](/articles/exchanges.html) 203 | * [Bindings](/articles/bindings.html) 204 | * [RabbitMQ Extensions to AMQP 0.9.1](/articles/rabbitmq_extensions.html) 205 | * [Durability and Related Matters](/articles/durability.html) 206 | * [Error Handling and Recovery](/articles/error_handling.html) 207 | * [Troubleshooting](/articles/troubleshooting.html) 208 | 209 | 210 | 211 | ## Tell Us What You Think! 212 | 213 | Please take a moment to tell us what you think about this guide [on Twitter](http://twitter.com/rubyamqp) 214 | or the [March Hare mailing list](https://groups.google.com/forum/#!forum/ruby-amqp) 215 | 216 | Let us know what was unclear or what has not been covered. Maybe you 217 | do not like the guide style or grammar or discover spelling 218 | mistakes. Reader feedback is key to making the documentation better. 219 | -------------------------------------------------------------------------------- /docs/guides/durability.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Durability and related matters" 3 | layout: article 4 | --- 5 | 6 | ## About This Guide 7 | 8 | This guide covers queue, exchange and message durability, as well as other topics related to durability, for example, durability in clustered environments. 9 | 10 | This work is licensed under a Creative Commons Attribution 3.0 Unported License 11 | (including images and stylesheets). The source is available [on Github](https://github.com/ruby-amqp/rubymarchhare.info). 12 | 13 | 14 | ## What version of March Hare does this guide cover?? 15 | 16 | This guide covers March Hare 3.0. 17 | 18 | ## Entity durability and message persistence 19 | 20 | ### Exchange durability 21 | 22 | AMQP separates the concept of entity durability (queues, exchanges) from message persistence. Exchanges can be durable or transient. Durable exchanges survive broker restart, transient exchanges do not (they have to be redeclared when the broker comes back online), however, not all scenarios and use cases mandate exchanges to be durable. 23 | 24 | To create a durable exchange, declare it with the `:durable => true` argument: 25 | 26 | ``` ruby 27 | ch = conn.create_channel 28 | exch = ch.direct("my_direct_exchange", :durable => true) 29 | ``` 30 | 31 | ### Queue durability 32 | 33 | Queues can be durable or transient. Durable queues survive broker restart, transient queues do not (they have to be redeclared when the broker comes back online), however, not all scenarios and use cases mandate queues to be durable. 34 | 35 | To create a durable queue, declare it with the `:durable => true` argument: 36 | 37 | ``` ruby 38 | ch = conn.create_channel 39 | q = ch.queue("my_queue", :durable => true) 40 | ``` 41 | 42 | Durability of a queue does not make _messages_ that are routed to that queue durable. If a broker is taken down and then brought back up, durable queues will be re-declared during broker startup, however, only _persistent_ messages will be recovered. 43 | 44 | ### Binding durability 45 | 46 | Bindings of durable queues to durable exchanges are automatically durable and are restored after a broker restart. The AMQP 0.9.1 specification states that the binding of durable queues to transient exchanges must be allowed. In this case, since the exchange would not survive a broker restart, neither would any bindings to such and exchange. 47 | 48 | ### Message persistence 49 | 50 | Messages may be published as persistent and this, in conjunction with queue durability, is what makes an AMQP broker persist them to disk. If the server is restarted, the system ensures that received persistent messages in durable queues are not lost. Simply publishing a message to a durable exchange or the fact that a queue to which a message is routed is durable does not make that message persistent. Message persistence depends on the persistence mode of the message itself. 51 | 52 | **Note** that publishing persistent messages affects performance (just like with data stores, durability comes at a certain cost to performance). 53 | 54 | Pass the `:persistent => true` argument to the `MarchHare::Exchange#publish` method to publish your message as persistent: 55 | 56 | ``` ruby 57 | exch.publish("My message", :persistent => true) 58 | ``` 59 | 60 | ### Clustering and High Availability 61 | 62 | To achieve the degree of durability that critical applications need, 63 | it is necessary but not enough to use durable queues, exchanges and 64 | persistent messages. You need to use a cluster of brokers because 65 | otherwise, a single hardware problem may bring a broker down 66 | completely. 67 | 68 | RabbitMQ offers a number of high availability features for both scenarios with more 69 | (LAN) and less (WAN) reliable network connections. 70 | 71 | See the [RabbitMQ clustering](http://www.rabbitmq.com/clustering.html) 72 | and [high availability](http://www.rabbitmq.com/ha.html) guides for 73 | in-depth discussion of this topic. 74 | 75 | 76 | ### Highly Available (Mirrored) Queues 77 | 78 | Whilst the use of clustering provides for greater durability of 79 | critical systems, in order to achieve the highest level of resilience 80 | for queues and messages, high availability configuration should be 81 | used. This is because although exchanges and bindings survive the loss 82 | of individual nodes by using clustering, messages do 83 | not. Without mirroring, queue contents reside on exactly one node, thus the 84 | loss of a node will cause message loss. 85 | 86 | See the [RabbitMQ high availability 87 | guide](http://www.rabbitmq.com/ha.html) for more information about 88 | mirrored queues. 89 | 90 | 91 | ## What to Read Next 92 | 93 | The documentation is organized as [a number of guides](/articles/guides.html), covering various topics. 94 | 95 | We recommend that you read the following guides first, if possible, in this order: 96 | 97 | * [Queues and Consumers](/articles/queues.html) 98 | * [Exchanges and Publishing](/articles/exchanges.html) 99 | * [Bindings](/articles/bindings.html) 100 | * [RabbitMQ Extensions to AMQP 0.9.1](/articles/rabbitmq_extensions.html) 101 | 102 | ## Tell Us What You Think! 103 | 104 | Please take a moment to tell us what you think about this guide [on Twitter](http://twitter.com/rubyamqp) or the [March Hare mailing list](https://groups.google.com/forum/#!forum/ruby-amqp) 105 | 106 | Let us know what was unclear or what has not been covered. Maybe you do not like the guide style or grammar or discover spelling mistakes. Reader feedback is key to making the documentation better. 107 | -------------------------------------------------------------------------------- /docs/guides/error_handling.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Error Handling and Recovery with March Hare" 3 | layout: article 4 | --- 5 | 6 | ## About this guide 7 | 8 | Development of a robust application, be it message publisher or 9 | message consumer, involves dealing with multiple kinds of failures: 10 | protocol exceptions, network failures, broker failures and so 11 | on. Correct error handling and recovery is not easy. This guide 12 | explains how the amqp gem helps you in dealing with issues like 13 | 14 | * Initial connection failures 15 | * Network connection failures 16 | * AMQP 0.9.1 connection-level exceptions 17 | * AMQP 0.9.1 channel-level exceptions 18 | * Broker failure 19 | * TLS (SSL) related issues 20 | 21 | This work is licensed under a Creative Commons 23 | Attribution 3.0 Unported License (including images and 24 | stylesheets). The source is available [on 25 | Github](https://github.com/ruby-amqp/rubymarchhare.info). 26 | 27 | 28 | ## What version of March Hare does this guide cover?? 29 | 30 | This guide covers March Hare 3.0. 31 | 32 | 33 | ## Initial RabbitMQ Connection Failures 34 | 35 | When applications connect to the broker, they need to handle 36 | connection failures. Networks are not 100% reliable, even with modern 37 | system configuration tools like Chef or Puppet misconfigurations 38 | happen and the broker might also be down. Error detection should 39 | happen as early as possible. To handle TCP 40 | connection failure, catch the `MarchHare::ConnectionRefused` exception: 41 | 42 | ``` ruby 43 | begin 44 | conn = MarchHare.connect("amqp://guest:guest@aksjhdkajshdkj.example82737.com") 45 | rescue MarchHare::ConnectionRefused => e 46 | puts "Connection to aksjhdkajshdkj.example82737.com failed" 47 | end 48 | ``` 49 | 50 | `MarchHare.connect` will raise `MarchHare::ConnectionRefused` if a 51 | connection fails. Code that catches it can write to a log about the 52 | issue or use retry to execute the begin block one more time. Because 53 | initial connection failures are due to misconfiguration or network 54 | outage, reconnection to the same endpoint (hostname, port, vhost 55 | combination) may result in the same issue over and over. 56 | 57 | 58 | ## Authentication Failures 59 | 60 | Another reason why a connection may fail is authentication 61 | failure. Handling authentication failure is very similar to handling 62 | initial TCP connection failure: 63 | 64 | ``` ruby 65 | begin 66 | conn = MarchHare.connect("amqp://guest8we78w7e8:guest2378278@127.0.0.1") 67 | rescue MarchHare::PossibleAuthenticationFailureError => e 68 | puts "Could not authenticate as #{conn.username}" 69 | end 70 | ``` 71 | 72 | In case you are wondering why the exception name has "possible" in it: 73 | [AMQP 0.9.1 spec](http://www.rabbitmq.com/resources/specs/amqp0-9-1.pdf) requires broker 74 | implementations to simply close TCP connection without sending any 75 | more data when an exception (such as authentication failure) occurs 76 | before AMQP connection is open. In practice, however, when broker 77 | closes TCP connection between successful TCP connection and before 78 | AMQP connection is open, it means that authentication has failed. 79 | 80 | RabbitMQ 3.2 introduces [authentication failure notifications](http://www.rabbitmq.com/auth-notification.html) 81 | which March Hare supports. When connecting to RabbitMQ 3.2 or later, Bunny will 82 | raise `MarchHare::AuthenticationFailureError` when it receives a proper 83 | authentication failure notification. 84 | 85 | 86 | ## Network Connection Failures 87 | 88 | Detecting network connections is nearly useless if an application 89 | cannot recover from them. Recovery is the hard part in "error handling 90 | and recovery". Fortunately, the recovery process for many applications 91 | follows one simple scheme that March Hare can perform automatically for 92 | you. 93 | 94 | Note that automatic connection recovery is a new feature and it is not 95 | nearly as battle tested as the rest of the library. 96 | 97 | When March Hare detects TCP connection failure, it will try to reconnect 98 | every 5 seconds. Currently there is no limit on the number of reconnection 99 | attempts. 100 | 101 | To disable automatic connection recovery, pass `:automatic_recovery => false` 102 | to `MarchHare.connect`. 103 | 104 | 105 | ### Heartbeats and Connection Failure Detection 106 | 107 | Due to how TCP works, it sometimes can take a while (minutes) to detect an unresponsive 108 | peer. To make connection failure detection quicker, the protocol has a feature called 109 | "heartbeats". Client and server exchange heartbeat frames periodically (about 1/2 110 | the configured timeout value). When either peer detects 2 missed heartbeats, it 111 | should consider the connection to be dead. 112 | 113 | `:heartbeat` is the option passed to `MarchHare.connect` to configure the desired 114 | timeout interval. We recommend setting this value in the 10-30 seconds range. 115 | 116 | Enabling heartbeats will also ensure firewalls won't consider connections with low 117 | activity to be stale. 118 | 119 | 120 | ### Automatic Recovery 121 | 122 | Many applications use the same recovery strategy that consists of the following steps: 123 | 124 | * Re-open channels 125 | * For each channel, re-declare exchanges (except for predefined ones) 126 | * For each channel, re-declare queues 127 | * For each queue, recover all bindings 128 | * For each queue, recover all consumers 129 | 130 | March Hare provides a feature known as "automatic recovery" that performs these steps 131 | after connection recovery, while taking care of some of the more tricky details 132 | such as recovery of server-named queues with consumers. 133 | 134 | Currently the automatic recovery mode is not configurable. 135 | 136 | 137 | ## Channel-level Exceptions 138 | 139 | Channel-level exceptions are more common than connection-level ones and often indicate 140 | issues applications can recover from (such as consuming from or trying to delete 141 | a queue that does not exist). 142 | 143 | With March Hare, channel-level exceptions are raised as Ruby exceptions, for example, 144 | `MarchHare::NotFound`, that provide access to the underlying `channel.close` method 145 | information: 146 | 147 | ``` ruby 148 | begin 149 | ch.queue_delete("queue_that_should_not_exist#{rand}") 150 | rescue MarchHare::NotFound => e 151 | puts "Channel-level exception! Code: #{e.channel_close.reply_code}, message: #{e.channel_close.reply_text}" 152 | end 153 | ``` 154 | 155 | ``` ruby 156 | begin 157 | ch2 = conn.create_channel 158 | q = "MarchHare.examples.recovery.q#{rand}" 159 | 160 | ch2.queue_declare(q, :durable => false) 161 | ch2.queue_declare(q, :durable => true) 162 | rescue MarchHare::PreconditionFailed => e 163 | puts "Channel-level exception! Code: #{e.channel_close.reply_code}, message: #{e.channel_close.reply_text}" 164 | ensure 165 | conn.create_channel.queue_delete(q) 166 | end 167 | ``` 168 | 169 | 170 | ### Common channel-level exceptions and what they mean 171 | 172 | A few channel-level exceptions are common and deserve more attention. 173 | 174 | #### 406 Precondition Failed 175 | 176 |
177 |
Description
178 |
The client requested a method that was not allowed because some precondition failed.
179 |
What might cause it
180 |
181 |
    182 |
  • AMQP entity (a queue or exchange) was re-declared with attributes different from original declaration. Maybe two applications or pieces of code declare the same entity with different attributes. Note that different RabbitMQ client libraries historically use slightly different defaults for entities and this may cause attribute mismatches.
  • 183 |
  • `MarchHare::Channel#tx_commit` or `MarchHare::Channel#tx_rollback` might be run on a channel that wasn't previously made transactional with `MarchHare::Channel#tx_select`
  • 184 |
185 |
186 |
Example RabbitMQ error message
187 |
188 |
    189 |
  • PRECONDITION_FAILED - parameters for queue 'MarchHare.examples.channel_exception' in vhost '/' not equivalent
  • 190 |
  • PRECONDITION_FAILED - channel is not transactional
  • 191 |
192 |
193 |
194 | 195 | #### 405 Resource Locked 196 | 197 |
198 |
Description
199 |
The client attempted to work with a server entity to which it has no access because another client is working with it.
200 |
What might cause it
201 |
202 |
    203 |
  • Multiple applications (or different pieces of code/threads/processes/routines within a single application) might try to declare queues with the same name as exclusive.
  • 204 |
  • Multiple consumer across multiple or single app might be registered as exclusive for the same queue.
  • 205 |
206 |
207 |
Example RabbitMQ error message
208 |
RESOURCE_LOCKED - cannot obtain exclusive access to locked queue 'amqpgem.examples.queue' in vhost '/'
209 |
210 | 211 | #### 404 Not Found 212 | 213 |
214 |
Description
215 |
The client attempted to use (publish to, delete, etc) an entity (exchange, queue) that does not exist.
216 |
What might cause it
217 |
Application miscalculates queue or exchange name or tries to use an entity that was deleted earlier
218 |
Example RabbitMQ error message
219 |
NOT_FOUND - no queue 'queue_that_should_not_exist0.6798199937619038' in vhost '/'
220 |
221 | 222 | #### 403 Access Refused 223 | 224 |
225 |
Description
226 |
The client attempted to work with a server entity to which it has no access due to security settings.
227 |
What might cause it
228 |
Application tries to access a queue or exchange it has no permissions for (or right kind of permissions, for example, write permissions)
229 |
Example RabbitMQ error message
230 |
ACCESS_REFUSED - access to queue 'march_hare.examples.channel_exception' in vhost 'march_hare_testbed' refused for user 'march_hare_reader'
231 |
232 | 233 | 234 | 235 | ## What to Read Next 236 | 237 | The documentation is organized as [a number of 238 | guides](/articles/guides.html), covering various topics. 239 | 240 | We recommend that you read the following guides first, if possible, in this order: 241 | 242 | * [Troubleshooting](/articles/troubleshooting.html) 243 | * [Using TLS (SSL) Connections](/articles/tls.html) 244 | 245 | 246 | ## Tell Us What You Think! 247 | 248 | Please take a moment to tell us what you think about this guide [on Twitter](http://twitter.com/rubyamqp) or the [March Hare mailing list](https://groups.google.com/forum/#!forum/ruby-amqp) 249 | 250 | Let us know what was unclear or what has not been covered. Maybe you 251 | do not like the guide style or grammar or discover spelling 252 | mistakes. Reader feedback is key to making the documentation better. 253 | -------------------------------------------------------------------------------- /docs/guides/getting_started.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Getting Started with RabbitMQ on JRuby using March Hare" 3 | layout: article 4 | --- 5 | 6 | ## About this guide 7 | 8 | This guide is a quick tutorial that helps you to get started with 9 | RabbitMQ and [March Hare](http://github.com/ruby-amqp/march_hare). It 10 | should take about 20 minutes to read and study the provided code 11 | examples. This guide covers: 12 | 13 | * Installing RabbitMQ, a mature popular messaging broker server. 14 | * Installing March Hare via [Rubygems](http://rubygems.org) and [Bundler](http://gembundler.com). 15 | * Running a "Hello, world" messaging example that is a simple demonstration of 1:1 communication. 16 | * Creating a "Twitter-like" publish/subscribe example with one publisher and four subscribers that demonstrates 1:n communication. 17 | * Creating a topic routing example with two publishers and eight subscribers showcasing n:m communication when subscribers only receive messages that they are interested in. 18 | 19 | This work is licensed under a Creative Commons 21 | Attribution 3.0 Unported License (including images and 22 | stylesheets). The source is available [on 23 | GitHub](https://github.com/ruby-amqp/rubymarchhare.info). 24 | 25 | 26 | ## Which versions of March Hare does this guide cover? 27 | 28 | This guide covers March Hare 3.0, including preview releases. 29 | 30 | ## Installing RabbitMQ 31 | 32 | The [RabbitMQ site](http://rabbitmq.com) has a good [installation 33 | guide](http://www.rabbitmq.com/install.html) that addresses many 34 | operating systems. On Mac OS X, the fastest way to install RabbitMQ 35 | is with [Homebrew](http://mxcl.github.com/homebrew/): 36 | 37 | brew install rabbitmq 38 | 39 | then run it: 40 | 41 | rabbitmq-server 42 | 43 | On Debian and Ubuntu, you can either [download the RabbitMQ .deb 44 | package](http://www.rabbitmq.com/server.html) and install it with 45 | [dpkg](http://www.debian.org/doc/FAQ/ch-pkgtools.en.html) or make use 46 | of the [apt repository](http://www.rabbitmq.com/debian.html#apt_) that 47 | the RabbitMQ team provides. 48 | 49 | For RPM-based distributions like RedHat or CentOS, the RabbitMQ team 50 | provides an [RPM package](http://www.rabbitmq.com/install.html#rpm). 51 | 52 |
Note: The RabbitMQ 53 | packages that ship with Ubuntu versions earlier than 11.10 are 54 | outdated and will not work with March Hare (you will 55 | need at least RabbitMQ v2.0 for use with this guide).
56 | 57 | ## Installing March Hare 58 | 59 | ### Make sure that you have JRuby 9.x installed 60 | 61 | This guide assumes that you have [JRuby](http://jruby.org) 9.0.0.0 installed. 62 | 63 | ### You can use Rubygems to install March Hare 64 | 65 | gem install march_hare 66 | 67 | ### Adding March Hare as a dependency with Bundler 68 | 69 | ``` ruby 70 | source "https://rubygems.org" 71 | 72 | gem "march_hare", "~> 3.0" 73 | ``` 74 | 75 | ### Verifying your installation 76 | 77 | Verify your installation with a quick irb session: 78 | 79 | ``` 80 | irb -rubygems 81 | :001 > require "march_hare" 82 | => true 83 | :002 > MarchHare::VERSION 84 | => "3.0.0" 85 | ``` 86 | 87 | ## "Hello, world" example 88 | 89 | Let us begin with the classic "Hello, world" example. First, here is the code: 90 | 91 | ``` ruby 92 | require "rubygems" 93 | require "march_hare" 94 | 95 | conn = MarchHare.connect 96 | 97 | ch = conn.create_channel 98 | q = ch.queue("march_hare.examples.hello_world", :auto_delete => true) 99 | 100 | c = q.subscribe do |metadata, payload| 101 | puts "Received #{payload}" 102 | end 103 | 104 | q.publish("Hello!", :routing_key => q.name) 105 | 106 | sleep 1.0 107 | 108 | c.cancel 109 | conn.close 110 | ``` 111 | 112 | This example demonstrates a very common communication scenario: 113 | *application A* wants to publish a message that will end up in a queue 114 | that *application B* listens on. In this case, the queue name is 115 | "bunny.examples.hello_world". Let us go through the code step by step: 116 | 117 | ``` ruby 118 | require "rubygems" 119 | require "march_hare" 120 | ``` 121 | 122 | is the simplest way to load March Hare if you have installed it with 123 | RubyGems, but remember that you can omit the rubygems line if your 124 | environment does not need it. The following piece of code 125 | 126 | ``` ruby 127 | conn = MarchHare.connect 128 | ``` 129 | 130 | connects to RabbitMQ running on localhost, with the default port 131 | (5672), username (guest), password (guest) and virtual host ('/'). 132 | 133 | The next line 134 | 135 | ``` ruby 136 | ch = conn.create_channel 137 | ``` 138 | 139 | opens a new _channel_. AMQP 0.9.1 is a multi-channeled protocol that 140 | uses channels to multiplex a TCP connection. 141 | 142 | Channels are opened on a 143 | connection. `MarchHare::Session#create_channel` will return only when 144 | March Hare receives a confirmation that the channel is open from 145 | RabbitMQ. 146 | 147 | This line 148 | 149 | ``` ruby 150 | q = ch.queue("march_hare.examples.hello_world", :auto_delete => true) 151 | ``` 152 | 153 | declares a **queue** on the channel that we have just opened. Consumer 154 | applications get messages from queues. We declared this queue with the 155 | "auto-delete" parameter. Basically, this means that the queue will be 156 | deleted when there are no more processes consuming messages from it. 157 | 158 | The next line 159 | 160 | ``` ruby 161 | x = ch.default_exchange 162 | ``` 163 | 164 | instantiates an **exchange**. Exchanges receive messages that are sent 165 | by producers. Exchanges route messages to queues according to rules 166 | called **bindings**. In this particular example, there are no 167 | explicitly defined bindings. The exchange that we use is known as the 168 | **default exchange** and it has implied bindings to all queues. Before 169 | we get into that, let us see how we define a handler for incoming 170 | messages 171 | 172 | ``` ruby 173 | c = q.subscribe do |delivery_info, metadata, payload| 174 | puts "Received #{payload}" 175 | end 176 | ``` 177 | 178 | `MarchHare::Queue#subscribe` takes a block that will be called every 179 | time a message arrives. This will happen in a thread pool, so 180 | `MarchHare::Queue#subscribe` does not block the thread that invokes 181 | it. It returns a **consumer**, a message delivery subscription that 182 | can be cancelled. 183 | 184 | Finally, we publish our message 185 | 186 | ``` ruby 187 | x.publish("Hello!", :routing_key => q.name) 188 | ``` 189 | 190 | Routing key is one of the **message properties**. The default exchange 191 | will route the message to a queue that has the same name as the 192 | message's routing key. This is how our message ends up in the 193 | "bunny.examples.hello_world" queue. 194 | 195 | This diagram demonstrates the "Hello, world" example data flow: 196 | 197 | ![Hello, World AMQP example data flow](http://github.com/ruby-amqp/amqp/raw/master/docs/diagrams/001_hello_world_example_routing.png) 198 | 199 | For the sake of simplicity, both the message producer (publisher) and 200 | the consumer are running in the same Ruby process. Now let us move on 201 | to a little bit more sophisticated example. 202 | 203 | ## Blabblr: one-to-many publish/subscribe (pubsub) example 204 | 205 | The previous example demonstrated how a connection to a broker is made 206 | and how to do 1:1 communication using the default exchange. Now let us 207 | take a look at another common scenario: broadcast, or multiple 208 | consumers and one producer. 209 | 210 | A very well-known broadcast example is Twitter: every time a person 211 | tweets, followers receive a notification. Blabbr, our imaginary 212 | information network, models this scenario: every network member has a 213 | separate queue and publishes blabs to a separate exchange. Three 214 | Blabbr members, Joe, Aaron and Bob, follow the official NBA account on 215 | Blabbr to get updates about what is happening in the world of 216 | basketball. Here is the code: 217 | 218 | ``` ruby 219 | require "rubygems" 220 | require "march_hare" 221 | 222 | conn = MarchHare.connect 223 | 224 | ch = conn.create_channel 225 | x = ch.fanout("march_hare.nba.scores") 226 | 227 | ch.queue("joe", :auto_delete => true).bind(x).subscribe do |meta, payload| 228 | puts "#{payload} => joe" 229 | end 230 | 231 | ch.queue("aaron", :auto_delete => true).bind(x).subscribe do |meta, payload| 232 | puts "#{payload} => aaron" 233 | end 234 | 235 | ch.queue("bob", :auto_delete => true).bind(x).subscribe do |meta, payload| 236 | puts "#{payload} => bob" 237 | end 238 | 239 | x.publish("BOS 101, NYK 89") 240 | x.publish("ORL 85, ALT 88") 241 | sleep 1.0 242 | 243 | conn.close 244 | ``` 245 | 246 | Unlike the "Hello, world" example above, here we use a connection URI instead of 247 | the default arguments. 248 | 249 | In this example, opening a channel is no different to opening a channel in the previous example, however, the exchange is declared differently: 250 | 251 | ``` ruby 252 | x = ch.fanout("nba.scores") 253 | ``` 254 | 255 | The exchange that we declare above using `MarchHare::Channel#fanout` 256 | is a **fanout exchange**. A fanout exchange delivers messages to all 257 | of the queues that are bound to it: exactly what we want in the case 258 | of Blabbr! 259 | 260 | This piece of code 261 | 262 | ``` ruby 263 | ch.queue("joe", :auto_delete => true).bind(x).subscribe do |meta, payload| 264 | puts "#{payload} => joe" 265 | end 266 | ``` 267 | 268 | is similar to the subscription code that we used for message delivery 269 | previously, but what does that `MarchHare::Queue#bind` method do? It 270 | sets up a binding between the queue and the exchange that you pass to 271 | it. We need to do this to make sure that our fanout exchange routes 272 | messages to the queues of any subscribed followers. 273 | 274 | ``` ruby 275 | x.publish("BOS 101, NYK 89") 276 | x.publish("ORL 85, ALT 88") 277 | ``` 278 | 279 | publishes two messages using `MarchHare::Exchange#publish`. Blabbr 280 | members use a fanout exchange for publishing, so there is no need to 281 | specify a message routing key because every queue that is bound to the 282 | exchange will get its own copy of all messages, regardless of the 283 | queue name and routing key used. 284 | 285 | A diagram for Blabbr looks like this: 286 | 287 | ![Blabbr Data Flow](https://github.com/ruby-amqp/amqp/raw/master/docs/diagrams/002_blabbr_example_routing.png) 288 | 289 | Blabbr is pretty unlikely to secure hundreds of millions of dollars in 290 | funding, but it does a pretty good job of demonstrating how one can 291 | use RabbitMQ fanout exchanges to do broadcasting. 292 | 293 | 294 | ## Weathr: many-to-many topic routing example 295 | 296 | So far, we have seen point-to-point communication and 297 | broadcasting. Those two communication styles are possible with many 298 | protocols, for instance, HTTP handles these scenarios just fine. You 299 | may ask "what differentiates RabbitMQ?" Well, next we are going to 300 | introduce you to **topic exchanges** and routing with patterns, one of 301 | the features that makes RabbitMQ very powerful. 302 | 303 | Our third example involves weather condition updates. What makes it 304 | different from the previous two examples is that not all of the 305 | consumers are interested in all of the messages. People who live in 306 | Portland usually do not care about the weather in Hong Kong (unless 307 | they are visiting soon). They are much more interested in weather 308 | conditions around Portland, possibly all of Oregon and sometimes a few 309 | neighbouring states. 310 | 311 | Our example features multiple consumer applications monitoring updates 312 | for different regions. Some are interested in updates for a specific 313 | city, others for a specific state and so on, all the way up to 314 | continents. Updates may overlap so that an update for San Diego, CA 315 | appears as an update for California, but also should show up on the 316 | North America updates list. 317 | 318 | Here is the code: 319 | 320 | ``` ruby 321 | require "rubygems" 322 | require "march_hare" 323 | 324 | connection = MarchHare.connect 325 | 326 | ch = connection.create_channel 327 | # topic exchange name can be any string 328 | x = ch.topic("weathr", :auto_delete => true) 329 | 330 | # Subscribers. 331 | ch.queue("", :exclusive => true).bind(x, :routing_key => "americas.north.#").subscribe do |metadata, payload| 332 | puts "An update for North America: #{payload}, routing key is #{metadata.routing_key}" 333 | end 334 | ch.queue("americas.south").bind(x, :routing_key => "americas.south.#").subscribe do |metadata, payload| 335 | puts "An update for South America: #{payload}, routing key is #{metadata.routing_key}" 336 | end 337 | ch.queue("us.california").bind(x, :routing_key => "americas.north.us.ca.*").subscribe do |metadata, payload| 338 | puts "An update for US/California: #{payload}, routing key is #{metadata.routing_key}" 339 | end 340 | ch.queue("us.tx.austin").bind(x, :routing_key => "#.tx.austin").subscribe do |metadata, payload| 341 | puts "An update for Austin, TX: #{payload}, routing key is #{metadata.routing_key}" 342 | end 343 | ch.queue("it.rome").bind(x, :routing_key => "europe.italy.rome").subscribe do |metadata, payload| 344 | puts "An update for Rome, Italy: #{payload}, routing key is #{metadata.routing_key}" 345 | end 346 | ch.queue("asia.hk").bind(x, :routing_key => "asia.southeast.hk.#").subscribe do |metadata, payload| 347 | puts "An update for Hong Kong: #{payload}, routing key is #{metadata.routing_key}" 348 | end 349 | 350 | x.publish("San Diego update", :routing_key => "americas.north.us.ca.sandiego") 351 | x.publish("Berkeley update", :routing_key => "americas.north.us.ca.berkeley") 352 | x.publish("San Francisco update", :routing_key => "americas.north.us.ca.sanfrancisco") 353 | x.publish("New York update", :routing_key => "americas.north.us.ny.newyork") 354 | x.publish("São Paolo update", :routing_key => "americas.south.brazil.saopaolo") 355 | x.publish("Hong Kong update", :routing_key => "asia.southeast.hk.hongkong") 356 | x.publish("Kyoto update", :routing_key => "asia.southeast.japan.kyoto") 357 | x.publish("Shanghai update", :routing_key => "asia.southeast.prc.shanghai") 358 | x.publish("Rome update", :routing_key => "europe.italy.roma") 359 | x.publish("Paris update", :routing_key => "europe.france.paris") 360 | 361 | sleep 1.0 362 | 363 | connection.close 364 | ``` 365 | 366 | The first line that is different from the Blabbr example is 367 | 368 | ``` ruby 369 | x = ch.topic("weathr", :auto_delete => true) 370 | ``` 371 | 372 | We use a topic exchange here. Topic exchanges are used for 373 | [multicast](http://en.wikipedia.org/wiki/Multicast) messaging where 374 | consumers indicate which topics they are interested in (think of it as 375 | subscribing to a feed for an individual tag in your favourite blog as 376 | opposed to the full feed). Routing with a topic exchange is done by 377 | specifying a **routing pattern** on binding, for example: 378 | 379 | ``` ruby ch.queue("americas.south").bind(exchange, :routing_key => 380 | "americas.south.#").subscribe do |metadata, payload| puts "An update 381 | for South America: #{payload}, routing key is #{metadata.routing_key}" 382 | end ``` Here we bind a queue with the name of "americas.south" to the 383 | topic exchange declared earlier using the `MarchHare::Queue#bind` 384 | method. This means that only messages with a routing key matching 385 | "americas.south.#" will be routed to that queue. A routing pattern 386 | consists of several words separated by dots, in a similar way to URI 387 | path segments joined by slashes. Here are a few examples: 388 | 389 | * asia.southeast.thailand.bangkok 390 | * sports.basketball 391 | * usa.nasdaq.aapl 392 | * tasks.search.indexing.accounts 393 | 394 | Now let us take a look at a few routing keys that match the "americas.south.#" pattern: 395 | 396 | * americas.south 397 | * americas.south.*brazil* 398 | * americas.south.*brazil.saopaolo* 399 | * americas.south.*chile.santiago* 400 | 401 | In other words, the "#" part of the pattern matches 0 or more words. 402 | 403 | For a pattern like "americas.south.*", some matching routing keys would be: 404 | 405 | * americas.south.*brazil* 406 | * americas.south.*chile* 407 | * americas.south.*peru* 408 | 409 | but not 410 | 411 | * americas.south 412 | * americas.south.chile.santiago 413 | 414 | so "*" only matches a single word. The AMQP 0.9.1 specification says 415 | that topic segments (words) may contain the letters A-Z and a-z and 416 | digits 0-9. 417 | 418 | A (very simplistic) diagram to demonstrate topic exchange in action: 419 | 420 | ![Weathr Data Flow](https://github.com/ruby-amqp/amqp/raw/master/docs/diagrams/003_weathr_example_routing.png) 421 | 422 | 423 | As in the previous examples, the block that we pass to 424 | `MarchHare::Queue#subscribe` takes multiple arguments: **delivery 425 | information**, **message metadata** (properties) and **message body** 426 | (often called the **payload**). Long story short, the metadata 427 | parameter lets you access metadata associated with the message. Some 428 | examples of message metadata attributes are: 429 | 430 | * message content type 431 | * message content encoding 432 | * message priority 433 | * message expiration time 434 | * message identifier 435 | * reply to (specifies which message this is a reply to) 436 | * application id (identifier of the application that produced the message) 437 | 438 | and so on. 439 | 440 | As the following binding demonstrates, `"#"` and `"*"` can also appear 441 | at the beginning of routing patterns: 442 | 443 | ``` ruby 444 | ch.queue("us.tx.austin").bind(x, :routing_key => "#.tx.austin").subscribe do |metadata, payload| 445 | 446 | puts "An update for Austin, TX: #{payload}, routing key is #{metadata.routing_key}" 447 | end 448 | ``` 449 | 450 | For this example the publishing of messages is no different from that 451 | of previous examples. If we were to run the program, a message 452 | published with a routing key of `"americas.north.us.ca.berkeley"` 453 | would be routed to 2 queues: `"us.california"` and the **server-named 454 | queue** that we declared by passing a blank string as the name: 455 | 456 | ``` ruby 457 | ch.queue("", :exclusive => true).bind(exchange, :routing_key => "americas.north.#").subscribe do |metadata, payload| 458 | puts "An update for North America: #{payload}, routing key is #{metadata.routing_key}" 459 | end 460 | ``` 461 | 462 | The name of the server-named queue is generated by the broker and sent 463 | back to the client with a queue declaration confirmation. 464 | 465 | 466 | ## Wrapping up 467 | 468 | This is the end of the tutorial. Congratulations! You have learned 469 | quite a bit about both AMQP 0.9.1 and March Hare. This is only the tip 470 | of the iceberg. RabbitMQ has many more features to offer: 471 | 472 | * Reliable delivery of messages 473 | * Message confirmations (a way to tell broker that a message was or was not processed successfully) 474 | * Message redelivery when consumer applications fail or crash 475 | * Load balancing of messages between multiple consumers 476 | * Message metadata attributes 477 | * High Availability features 478 | 479 | and so on. Other guides explain these features in depth, as well as 480 | use cases for them. To stay up to date with March Hare development, 481 | [follow @rubyamqp on Twitter](http://twitter.com/rubyamqp) and [join 482 | our mailing list](http://groups.google.com/group/ruby-amqp). 483 | 484 | ## What to read next 485 | 486 | Documentation is organized as a number of documentation guides, covering all 488 | kinds of topics including use cases for various exchange types, 489 | fault-tolerant message processing with acknowledgements and error 490 | handling. 491 | 492 | We recommend that you read the following guides next, if possible, in this order: 493 | 494 | * [AMQP 0.9.1 Model Explained](http://www.rabbitmq.com/tutorials/amqp-concepts.html). A simple 2 page long introduction to the AMQP Model concepts and features. Understanding the AMQP 0.9.1 Model 495 | will make a lot of other documentation, both for March Hare and RabbitMQ itself, easier to follow. With this guide, you don't have to waste hours of time reading the whole specification. 496 | * [Connecting to the broker](/articles/connecting.html). This guide explains how to connect to an RabbitMQ and how to integrate March Hare into standalone and Web applications. 497 | * [Queues and Consumers](/articles/queues.html). This guide focuses on features that consumer applications use heavily. 498 | * [Exchanges and Publishers](/articles/exchanges.html). This guide focuses on features that producer applications use heavily. 499 | * [Error Handling and Recovery](/articles/error_handling.html). This guide explains how to handle protocol errors, network failures and other things that may go wrong in real world projects. 500 | 501 | 502 | ## Tell Us What You Think! 503 | 504 | Please take a moment to tell us what you think about this guide [on 505 | Twitter](http://twitter.com/rubyamqp) or the [March Hare mailing 506 | list](https://groups.google.com/forum/#!forum/ruby-amqp) 507 | 508 | Let us know what was unclear or what has not been covered. Maybe you 509 | do not like the guide style or grammar or discover spelling 510 | mistakes. Reader feedback is key to making the documentation better. 511 | -------------------------------------------------------------------------------- /docs/guides/guides.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "March Hare: all documentation guides" 3 | layout: article 4 | --- 5 | 6 | ## Guide list 7 | 8 | [March Hare 9 | documentation](https://github.com/ruby-amqp/rubymarchhare.info) is 10 | organized as a number of guides, covering all kinds of topics. 11 | 12 | We recommend that you read these guides, if possible, in this order: 13 | 14 | 15 | ### [Getting started](/articles/getting_started.html) 16 | 17 | An overview of March Hare with a quick tutorial that helps you to get 18 | started with it. It should take about 20 minutes to read and study the 19 | provided code examples. 20 | 21 | ### [AMQP 0.9.1 Model Concepts](http://www.rabbitmq.com/tutorials/amqp-concepts.html) 22 | 23 | This guide covers: 24 | 25 | * AMQP 0.9.1 model overview 26 | * What are channels 27 | * What are vhosts 28 | * What are queues 29 | * What are exchanges 30 | * What are bindings 31 | * What are AMQP 0.9.1 classes and methods 32 | 33 | ### [Connecting To RabbitMQ](/articles/connecting.html) 34 | 35 | This guide covers: 36 | 37 | * How to connect to RabbitMQ with March Hare 38 | * How to use connection URI to connect to RabbitMQ (also: in PaaS environments such as Heroku and CloudFoundry) 39 | * How to open a channel 40 | * How to close a channel 41 | * How to disconnect 42 | 43 | 44 | ### [Queues and Consumers](/articles/queues.html) 45 | 46 | This guide covers: 47 | 48 | * How to declare AMQP queues with March Hare 49 | * Queue properties 50 | * How to declare server-named queues 51 | * How to declare temporary exclusive queues 52 | * How to consume messages ("push API") 53 | * How to fetch messages ("pull API") 54 | * Message and delivery properties 55 | * Message acknowledgements 56 | * How to purge queues 57 | * How to delete queues 58 | * Other topics related to queues 59 | 60 | 61 | ### [Exchanges and Publishing](/articles/exchanges.html) 62 | 63 | This guide covers: 64 | 65 | * Exchange types 66 | * How to declare AMQP exchanges with March Hare 67 | * How to publish messages 68 | * Exchange propreties 69 | * Fanout exchanges 70 | * Direct exchanges 71 | * Topic exchanges 72 | * Default exchange 73 | * Message and delivery properties 74 | * Message routing 75 | * Bindings 76 | * How to delete exchanges 77 | * Other topics related to exchanges and publishing 78 | 79 | 80 | ### [Bindings](/articles/bindings.html) 81 | 82 | This guide covers: 83 | 84 | * How to bind exchanges to queues 85 | * How to unbind exchanges from queues 86 | * Other topics related to bindings 87 | 88 | 89 | ### [Durability and Related Matters](/articles/durability.html) 90 | 91 | This guide covers: 92 | 93 | * Topics related to durability of exchanges and queues 94 | * Durability of messages 95 | 96 | 97 | ### [RabbitMQ Extensions to AMQP 0.9.1](/articles/extensions.html) 98 | 99 | This guide covers [RabbitMQ extensions](http://www.rabbitmq.com/extensions.html) and how they are used in March Hare: 100 | 101 | * How to use Publishing Confirms with March Hare 102 | * How to use exchange-to-exchange bindings 103 | * How to the alternate exchange extension 104 | * How to set per-queue message TTL 105 | * How to set per-message TTL 106 | * What are consumer cancellation notifications and how to use them 107 | * Message *dead lettering* and the dead letter exchange 108 | * How to use sender-selected routing (`CC` and `BCC` headers) 109 | 110 | 111 | ### [Error Handling and Recovery](/articles/error_handling.html) 112 | 113 | This guide covers: 114 | 115 | * AMQP 0.9.1 protocol exceptions 116 | * How to deal with network failures 117 | * Other things that may go wrong 118 | 119 | 120 | ### [Using TLS (SSL) Connections](/articles/tls.html) (TBD) 121 | 122 | This guide covers: 123 | 124 | * How to use TLS (SSL) connections to RabbitMQ with March Hare 125 | 126 | 127 | 128 | ### [Troubleshooting](/articles/troubleshooting.html) 129 | 130 | This guide covers: 131 | 132 | * What to check when your apps that use March Hare and RabbitMQ misbehave 133 | 134 | 135 | 136 | ## Tell Us What You Think! 137 | 138 | Please take a moment to tell us what you think about this guide on 139 | Twitter or the [ruby-amqp mailing 140 | list](https://groups.google.com/forum/?fromgroups#!forum/ruby-amqp). 141 | 142 | Let us know what was unclear or what has not been covered. Maybe you 143 | do not like the guide style or grammar or discover spelling mistakes. 144 | Reader feedback is key to making the documentation better. 145 | -------------------------------------------------------------------------------- /docs/guides/troubleshooting.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Troubleshooting and debugging RabbitMQ applications" 3 | layout: article 4 | --- 5 | 6 | ## About this guide 7 | 8 | This guide describes tools and strategies that help in troubleshooting 9 | and debugging applications that use RabbitMQ in general and March Hare in 10 | particular. 11 | 12 | This work is licensed under a Creative Commons 14 | Attribution 3.0 Unported License (including images and 15 | stylesheets). The source is available [on 16 | Github](https://github.com/ruby-amqp/rubymarchhare.info). 17 | 18 | 19 | ## What version of March Hare does this guide cover? 20 | 21 | This guide covers March Hare 2.8 and later versions. 22 | 23 | 24 | ## First steps 25 | 26 | Whenever something doesn't work, check the following things before 27 | asking on the mailing list: 28 | 29 | * RabbitMQ log. 30 | * List of users in a particular vhost you are trying to connect. 31 | * Network connectivity, firewall settings, DNS host resolution. 32 | 33 | 34 | ## Inspecting RabbitMQ log file 35 | 36 | In this section we will cover typical problems that can be tracked 37 | down by reading RabbitMQ log. 38 | 39 | RabbitMQ logs abrupt TCP connection failures, timeouts, protocol 40 | version mismatches and so on. If you are running RabbitMQ, log 41 | file location depends on the operating systems and installation method. 42 | See [RabbitMQ installation guide](http://www.rabbitmq.com/install.html) for 43 | more information. 44 | 45 | ### OS X with Homebrew 46 | 47 | On Mac OS X, RabbitMQ installed via Homebrew logs to 48 | `$HOMEBREW_HOME/var/log/rabbitmq/rabbit@$HOSTNAME.log`. For example, if 49 | you have Homebrew installed at `/usr/local` and your hostname is `giove`, 50 | the log will be at `/usr/local/var/log/rabbitmq/rabbit@giove.log`. 51 | 52 | 53 | 54 | ### Authentication Failures 55 | 56 | Here is what authentication failure looks like in a RabbitMQ log: 57 | 58 | ``` 59 | =ERROR REPORT==== 12-Jul-2013::16:49:03 === 60 | closing AMQP connection <0.31567.1> (127.0.0.1:50458 -> 127.0.0.1:5672): 61 | {handshake_error,starting,0, 62 | {amqp_error,access_refused, 63 | "PLAIN login refused: user 'pipeline_agent' - invalid credentials", 64 | 'connection.start_ok'}} 65 | ``` 66 | 67 | This means that the connection attempt with the username 68 | `pipeline_agent` failed because the credentials were invalid. If you 69 | are seeing this message, make sure username, password *and vhost* are 70 | correct. 71 | 72 | The following entry: 73 | 74 | ``` 75 | =ERROR REPORT==== 17-May-2011::17:26:28 === 76 | exception on TCP connection <0.4201.62> from 10.8.0.30:57990 77 | {bad_header,<<65,77,81,80,0,0,9,1>>} 78 | ``` 79 | 80 | means that an old RabbitMQ version (pre-`2.0`) is used. Those versions 81 | are not supported by March Hare 0.9+. It is recommended to use the 82 | [latest stable release](http://www.rabbitmq.com/download.html). 83 | 84 | March Hare will raise `MarchHare::PossibleAuthenticationFailureError` in such 85 | cases. 86 | 87 | 88 | ## Handling Channel-level Exceptions 89 | 90 | A broad range of problems result in AMQP channel exceptions: an 91 | indication by the broker that there was an issue that the application 92 | needs to be aware of. Channel-level exceptions are typically not fatal 93 | and can be recovered from. Some examples are: 94 | 95 | * Exchange is re-declared with attributes different from the original declaration. For example, a non-durable exchange is being re-declared as durable. 96 | * Queue is re-declared with attributes different from the original declaration. For example, an auto-deletable queue is being re-declared as non-auto-deletable. 97 | * Queue is bound to an exchange that does not exist. 98 | 99 | and so on. These will result in a reasonably descriptive exception that subclasses `MarchHare::ChannelLevelException`. 100 | Handling and logging them will likely reveal an issue when it arises. 101 | 102 | 103 | 104 | ## Testing Network Connection with RabbitMQ using Telnet 105 | 106 | One simple way to check network connection between a particular network node and a RabbitMQ node is to use `telnet`: 107 | 108 | ``` 109 | telnet [host or ip] 5672 110 | ``` 111 | 112 | then enter any random string of text and hit Enter. RabbitMQ should immediately close down the connection. Here is an example session: 113 | 114 | ``` 115 | telnet localhost 5672 116 | Connected to localhost. 117 | Escape character is '^]'. 118 | adjasd 119 | AMQP Connection closed by foreign host. 120 | ``` 121 | 122 | If Telnet exits after printing instead 123 | 124 | ``` 125 | telnet: connect to address [host or ip]: Connection refused 126 | telnet: Unable to connect to remote host 127 | ``` 128 | 129 | then the connection between the machine that you are running Telnet 130 | tests on and RabbitMQ fails. This can be due to many different 131 | reasons, but it is a good idea to check these two things first: 132 | 133 | * Firewall configuration for port 5672 or 5671 (if TLS/SSL is used) 134 | * DNS resolution (if hostname is used) 135 | 136 | 137 | ## RabbitMQ Startup Issues 138 | 139 | ### Missing erlang-os-mon on Debian and Ubuntu 140 | 141 | The following error on RabbitMQ startup on Debian or Ubuntu 142 | 143 | ``` 144 | ERROR: failed to load application os_mon: {"no such file or directory","os_mon.app"} 145 | ``` 146 | 147 | suggests that the *erlang-os-mon* package is not installed. 148 | 149 | 150 | ## asn1 Issue on Erlang R16B01 151 | 152 | ``` 153 | BOOT FAILED 154 | =========== 155 | 156 | Error description: 157 | {error,{cannot_start_application,public_key,{not_started,asn1}}} 158 | ``` 159 | 160 | is an issue in RabbitMQ 3.1 on Erlang R16B01+. It is [resolved](http://rabbitmq.1065348.n5.nabble.com/Erlang-R16B01-and-SSL-td27526.html) 161 | in RabbitMQ 3.1.2 and later versions. 162 | -------------------------------------------------------------------------------- /examples/README.md: -------------------------------------------------------------------------------- 1 | # Where to Find March Hare Code Examples 2 | 3 | This directory contains a few basic examples. See [March Hare 4 | documentation](http://rubymarchhare.info) and [RabbitMQ 5 | tutotirals](http://www.rabbitmq.com/getstarted.html) to find more 6 | examples as well as discussion of RabbitMQ concepts and what March 7 | Hare can do. 8 | -------------------------------------------------------------------------------- /examples/blocked_connection_notifications.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require 'march_hare' 3 | 4 | connection = MarchHare.connect 5 | 6 | connection.on_blocked do |reason| 7 | puts "I am blocked now. Reason: #{reason}" 8 | end 9 | connection.on_unblocked do 10 | puts "I am unblocked now." 11 | end 12 | 13 | ch = connection.create_channel 14 | x = ch.default_exchange 15 | 16 | 10.times do |i| 17 | x.publish("") 18 | end 19 | 20 | sleep 5.0 21 | 22 | 10.times do |i| 23 | x.publish("") 24 | end 25 | 26 | sleep 30.0 27 | 28 | connection.close 29 | -------------------------------------------------------------------------------- /examples/connection/automatic_consumer_recovery.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # encoding: utf-8 3 | 4 | require "bundler" 5 | Bundler.setup 6 | 7 | $:.unshift(File.expand_path("../../../lib", __FILE__)) 8 | 9 | require 'march_hare' 10 | 11 | conn = MarchHare.connect(:heartbeat_interval => 8) 12 | 13 | ch0 = conn.create_channel 14 | ch1 = conn.create_channel 15 | ch2 = conn.create_channel 16 | ch3 = conn.create_channel 17 | 18 | x = ch1.topic("hb.examples.recovery.topic", :durable => false) 19 | q1 = ch1.queue("hb.examples.recovery.client_named_queue1", :durable => false) 20 | q2 = ch2.queue("hb.examples.recovery.client_named_queue2", :durable => false) 21 | q3 = ch3.queue("hb.examples.recovery.client_named_queue3", :durable => false) 22 | 23 | q1.bind(x, :routing_key => "abc") 24 | q2.bind(x, :routing_key => "def") 25 | q3.bind(x, :routing_key => "xyz") 26 | 27 | x0 = ch0.fanout("hb.examples.recovery.fanout0") 28 | x1 = ch1.fanout("hb.examples.recovery.fanout1") 29 | x2 = ch2.fanout("hb.examples.recovery.fanout2") 30 | x3 = ch3.fanout("hb.examples.recovery.fanout3") 31 | 32 | q4 = ch1.queue("", :exclusive => true) 33 | q4.bind(x0) 34 | 35 | q5 = ch2.queue("", :exclusive => true) 36 | q5.bind(x1) 37 | 38 | q6 = ch3.queue("", :exclusive => true) 39 | q6.bind(x2) 40 | q6.bind(x3) 41 | 42 | 43 | q1.subscribe do |metadata, payload| 44 | puts "[Q1] Consumed #{payload} on channel #{q1.channel.id}" 45 | if ch0.open? 46 | puts "Publishing a reply on channel #{ch0.id} which is open" 47 | x0.publish(Time.now.to_i.to_s) 48 | end 49 | end 50 | 51 | q2.subscribe do |metadata, payload| 52 | puts "[Q2] Consumed #{payload} on channel #{q2.channel.id}" 53 | 54 | if ch1.open? 55 | puts "Publishing a reply on channel #{ch1.id} which is open" 56 | x1.publish(Time.now.to_i.to_s) 57 | end 58 | end 59 | 60 | q3.subscribe do |metadata, payload| 61 | puts "[Q3] Consumed #{payload} (consumer 1, channel #{q3.channel.id})" 62 | 63 | if ch2.open? 64 | puts "Publishing a reply on channel #{ch1.id} which is open" 65 | x2.publish(Time.now.to_i.to_s) 66 | end 67 | end 68 | 69 | q3.subscribe do |metadata, payload| 70 | puts "[Q3] Consumed #{payload} (consumer 2, channel #{q3.channel.id})" 71 | 72 | if ch3.open? 73 | puts "Publishing a reply on channel #{ch3.id} which is open" 74 | x3.publish(Time.now.to_i.to_s) 75 | end 76 | end 77 | 78 | q4.subscribe do |metadata, payload| 79 | puts "[Q4] Consumed #{payload} on channel #{q4.channel.id}" 80 | end 81 | 82 | q5.subscribe do |metadata, payload| 83 | puts "[Q5] Consumed #{payload} on channel #{q5.channel.id}" 84 | end 85 | 86 | q6.subscribe do |metadata, payload| 87 | puts "[Q6] Consumed #{payload} on channel #{q6.channel.id}" 88 | end 89 | 90 | loop do 91 | sleep 1 92 | data = rand.to_s 93 | rk = ["abc", "def", "xyz", Time.now.to_i.to_s].sample 94 | 95 | begin 96 | 3.times do 97 | x.publish(rand.to_s, :routing_key => rk) 98 | puts "Published #{data}, routing key: #{rk} on channel #{x.channel.id}" 99 | end 100 | # happens when a message is published before the connection 101 | # is recovered 102 | rescue Exception, java.lang.Throwable => e 103 | puts "Exception: #{e.message}" 104 | # e.backtrace.each do |line| 105 | # puts "\t#{line}" 106 | # end 107 | end 108 | end 109 | -------------------------------------------------------------------------------- /examples/connection/publisher_recovery_with_begin_rescue_retry.rb: -------------------------------------------------------------------------------- 1 | $: << 'lib' 2 | 3 | require 'march_hare' 4 | 5 | 6 | begin 7 | conn = MarchHare.connect(:host => 'localhost') 8 | ch = conn.create_channel 9 | x = ch.default_exchange 10 | 11 | loop do 12 | 10.times do 13 | print "." 14 | x.publish("") 15 | end 16 | 17 | sleep 3.0 18 | end 19 | rescue MarchHare::Exception => e 20 | puts "RabbitMQ connection error: #{e.message}. Will reconnect in 10 seconds..." 21 | 22 | sleep 10 23 | retry 24 | end 25 | -------------------------------------------------------------------------------- /examples/connection/recovering_merry_go_around.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # encoding: utf-8 3 | 4 | require "bundler" 5 | Bundler.setup 6 | 7 | $:.unshift(File.expand_path("../../../lib", __FILE__)) 8 | 9 | require 'march_hare' 10 | 11 | # This example passes messages between N queues: 12 | # 13 | # Q1 => Q2 => ... => Qn-1 => Qn 14 | # 15 | # and is used primarily to test (and demonstrate) connection 16 | # recovery with a lot of channels and mixed consumer/producer 17 | # workloads. 18 | 19 | c1 = MarchHare.connect(:heartbeat_interval => 8) 20 | c2 = MarchHare.connect(:heartbeat_interval => 8) 21 | 22 | s = 32 23 | n = 1000 24 | chs = [] 25 | qs = [] 26 | cs = [] 27 | 28 | s.times do |i| 29 | ch = c1.create_channel 30 | chs << ch 31 | 32 | next_q = qs.last 33 | 34 | q = ch.queue("", :exclusive => true) 35 | qs << q 36 | 37 | cs << q.subscribe do |_, payload| 38 | if next_q 39 | next_q.publish(payload) 40 | else 41 | puts "#{payload} has reached queue #{q.name}" 42 | end 43 | end 44 | end 45 | 46 | pch = c2.create_channel 47 | x = pch.default_exchange 48 | 49 | loop do 50 | sleep 1.0 51 | 10.times do 52 | x.publish("msg #{rand}", :routing_key => qs.last.name) if pch.open? 53 | end 54 | end 55 | -------------------------------------------------------------------------------- /examples/guides/exchanges/direct_exchange_routing.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # encoding: utf-8 3 | 4 | require "rubygems" 5 | require "march_hare" 6 | 7 | puts "=> Direct exchange routing" 8 | puts 9 | 10 | conn = MarchHare.connect 11 | 12 | ch = conn.create_channel 13 | x = ch.direct("examples.imaging") 14 | 15 | q1 = ch.queue("", :auto_delete => true).bind(x, :routing_key => "resize") 16 | q1.subscribe do |delivery_info, properties, payload| 17 | puts "[consumer] #{q1.name} received a 'resize' message" 18 | end 19 | q2 = ch.queue("", :auto_delete => true).bind(x, :routing_key => "watermark") 20 | q2.subscribe do |delivery_info, properties, payload| 21 | puts "[consumer] #{q2.name} received a 'watermark' message" 22 | end 23 | 24 | # just an example 25 | data = rand.to_s 26 | x.publish(data, :routing_key => "resize") 27 | x.publish(data, :routing_key => "watermark") 28 | 29 | sleep 0.5 30 | x.delete 31 | q1.delete 32 | q2.delete 33 | 34 | puts "Disconnecting..." 35 | conn.close 36 | -------------------------------------------------------------------------------- /examples/guides/exchanges/fanout_exchange_routing.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # encoding: utf-8 3 | 4 | require "rubygems" 5 | require "march_hare" 6 | 7 | puts "=> Fanout exchange routing" 8 | puts 9 | 10 | conn = MarchHare.connect 11 | 12 | ch = conn.create_channel 13 | x = ch.fanout("examples.pings") 14 | 15 | 10.times do |i| 16 | q = ch.queue("", :auto_delete => true).bind(x) 17 | q.subscribe do |delivery_info, properties, payload| 18 | puts "[consumer] #{q.name} received a message: #{payload}" 19 | end 20 | end 21 | 22 | x.publish("Ping") 23 | 24 | sleep 0.5 25 | x.delete 26 | puts "Disconnecting..." 27 | conn.close 28 | -------------------------------------------------------------------------------- /examples/guides/exchanges/mandatory_messages.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # encoding: utf-8 3 | 4 | require "rubygems" 5 | require "march_hare" 6 | 7 | puts "=> Publishing messages as mandatory" 8 | puts 9 | 10 | conn = MarchHare.connect 11 | 12 | ch = conn.create_channel 13 | x = ch.default_exchange 14 | 15 | ch.on_return do |reply_code, reply_text, exchange, routing_key, basic_properties, payload| 16 | puts "Got a returned message: #{payload}, reply: #{reply_code} #{reply_text}" 17 | end 18 | 19 | q = ch.queue("", :exclusive => true) 20 | q.subscribe do |metadata, payload| 21 | puts "Consumed a message: #{payload}" 22 | end 23 | 24 | x.publish("This will NOT be returned", :mandatory => true, :routing_key => q.name) 25 | x.publish("This will be returned", :mandatory => true, :routing_key => "akjhdfkjsh#{rand}") 26 | 27 | sleep 1.0 28 | puts "Disconnecting..." 29 | conn.close 30 | -------------------------------------------------------------------------------- /examples/guides/extensions/alternate_exchange.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # encoding: utf-8 3 | 4 | require "rubygems" 5 | require "march_hare" 6 | 7 | puts "=> Demonstrating alternate exchanges" 8 | puts 9 | 10 | conn = MarchHare.connect 11 | 12 | ch = conn.create_channel 13 | x1 = ch.fanout("march_hare.examples.ae.exchange1", :auto_delete => true, :durable => false) 14 | x2 = ch.fanout("march_hare.examples.ae.exchange2", :auto_delete => true, :durable => false, :arguments => { 15 | "alternate-exchange" => x1.name 16 | }) 17 | q = ch.queue("", :exclusive => true).bind(x1) 18 | 19 | x2.publish("") 20 | 21 | sleep 0.2 22 | puts "Queue #{q.name} now has #{q.message_count} message in it" 23 | 24 | sleep 0.7 25 | puts "Disconnecting..." 26 | conn.close 27 | -------------------------------------------------------------------------------- /examples/guides/extensions/basic_nack.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # encoding: utf-8 3 | 4 | require "rubygems" 5 | require "march_hare" 6 | 7 | puts "=> Demonstrating basic.nack" 8 | puts 9 | 10 | conn = MarchHare.connect 11 | 12 | ch = conn.create_channel 13 | q = ch.queue("", :exclusive => true) 14 | 15 | 20.times do 16 | q.publish("") 17 | end 18 | 19 | 20.times do 20 | metadata, _ = q.pop(:ack => true) 21 | 22 | if metadata.delivery_tag == 20 23 | # requeue them all at once with basic.nack 24 | ch.nack(metadata.delivery_tag, true, true) 25 | end 26 | end 27 | 28 | puts "Queue #{q.name} still has #{q.message_count} messages in it" 29 | 30 | sleep 0.7 31 | puts "Disconnecting..." 32 | conn.close 33 | -------------------------------------------------------------------------------- /examples/guides/extensions/consumer_cancellation_notification.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # encoding: utf-8 3 | 4 | require "rubygems" 5 | require "march_hare" 6 | 7 | puts "=> Demonstrating consumer cancellation notification" 8 | puts 9 | 10 | conn = MarchHare.connect 11 | 12 | ch = conn.create_channel 13 | q = ch.queue("", :exclusive => true) 14 | c = q.subscribe(:on_cancellation => Proc.new { |ch, consumer, consumer_tag| puts "Consumer w/ tag #{consumer_tag} was cancelled remotely" }) do |metadata, payload| 15 | # no-op 16 | end 17 | 18 | sleep 0.1 19 | puts "Consumer #{c.consumer_tag} is not yet cancelled" unless c.cancelled? 20 | q.delete 21 | 22 | sleep 0.1 23 | 24 | puts "Consumer #{c.consumer_tag} is now cancelled" if c.cancelled? 25 | 26 | puts "Disconnecting..." 27 | conn.close 28 | -------------------------------------------------------------------------------- /examples/guides/extensions/dead_letter_exchange.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # encoding: utf-8 3 | 4 | require "rubygems" 5 | require "march_hare" 6 | 7 | puts "=> Demonstrating dead letter exchange" 8 | puts 9 | 10 | conn = MarchHare.connect 11 | 12 | ch = conn.create_channel 13 | x = ch.fanout("amq.fanout") 14 | dlx = ch.fanout("march_hare.examples.dlx.exchange") 15 | q = ch.queue("", :exclusive => true, :arguments => {"x-dead-letter-exchange" => dlx.name}).bind(x) 16 | # dead letter queue 17 | dlq = ch.queue("", :exclusive => true).bind(dlx) 18 | 19 | x.publish("") 20 | sleep 0.2 21 | 22 | metadata, _ = q.pop(:ack => true) 23 | puts "#{dlq.message_count} messages dead lettered so far" 24 | puts "Rejecting a message" 25 | ch.nack(metadata.delivery_tag) 26 | sleep 0.2 27 | puts "#{dlq.message_count} messages dead lettered so far" 28 | 29 | dlx.delete 30 | puts "Disconnecting..." 31 | conn.close 32 | -------------------------------------------------------------------------------- /examples/guides/extensions/exchange_to_exchange_bindings.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # encoding: utf-8 3 | 4 | require "rubygems" 5 | require "march_hare" 6 | 7 | puts "=> Demonstrating exchange-to-exchange bindings" 8 | puts 9 | 10 | conn = MarchHare.connect 11 | 12 | ch = conn.create_channel 13 | x1 = ch.fanout("march_hare.examples.e2e.exchange1", :auto_delete => true, :durable => false) 14 | x2 = ch.fanout("march_hare.examples.e2e.exchange2", :auto_delete => true, :durable => false) 15 | # x1 will be the source 16 | x2.bind(x1) 17 | 18 | q = ch.queue("", :exclusive => true) 19 | q.bind(x2) 20 | 21 | x1.publish("") 22 | 23 | sleep 0.2 24 | puts "Queue #{q.name} now has #{q.message_count} message in it" 25 | 26 | sleep 0.7 27 | puts "Disconnecting..." 28 | conn.close 29 | -------------------------------------------------------------------------------- /examples/guides/extensions/per_message_ttl.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # encoding: utf-8 3 | 4 | require "rubygems" 5 | require "march_hare" 6 | 7 | puts "=> Demonstrating per-message TTL" 8 | puts 9 | 10 | conn = MarchHare.connect 11 | 12 | ch = conn.create_channel 13 | x = ch.fanout("amq.fanout") 14 | q = ch.queue("", :exclusive => true).bind(x) 15 | 16 | 10.times do |i| 17 | x.publish("Message #{i}", :properties => {:expiration => 1000}) 18 | end 19 | 20 | sleep 0.7 21 | _, content1 = q.pop 22 | puts "Fetched #{content1.inspect} after 0.7 second" 23 | 24 | sleep 0.8 25 | _, content2 = q.pop 26 | msg = if content2 27 | content2.inspect 28 | else 29 | "nothing" 30 | end 31 | puts "Fetched #{msg} after 1.5 second" 32 | 33 | sleep 0.7 34 | puts "Closing..." 35 | conn.close 36 | -------------------------------------------------------------------------------- /examples/guides/extensions/per_queue_message_ttl.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # encoding: utf-8 3 | 4 | require "rubygems" 5 | require "march_hare" 6 | 7 | puts "=> Demonstrating per-queue message TTL" 8 | puts 9 | 10 | conn = MarchHare.connect 11 | 12 | ch = conn.create_channel 13 | x = ch.fanout("amq.fanout") 14 | q = ch.queue("", :exclusive => true, :arguments => {"x-message-ttl" => 1000}).bind(x) 15 | 16 | 10.times do |i| 17 | x.publish("Message #{i}") 18 | end 19 | 20 | sleep 0.7 21 | _, content1 = q.pop 22 | puts "Fetched #{content1.inspect} after 0.7 second" 23 | 24 | sleep 0.8 25 | _, content2 = q.pop 26 | msg = if content2 27 | content2.inspect 28 | else 29 | "nothing" 30 | end 31 | puts "Fetched #{msg} after 1.5 second" 32 | 33 | sleep 0.7 34 | puts "Closing..." 35 | conn.close 36 | -------------------------------------------------------------------------------- /examples/guides/extensions/publisher_confirms.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # encoding: utf-8 3 | 4 | require "rubygems" 5 | require "march_hare" 6 | 7 | puts "=> Demonstrating publisher confirms" 8 | puts 9 | 10 | conn = MarchHare.connect 11 | 12 | ch = conn.create_channel 13 | x = ch.fanout("amq.fanout") 14 | q = ch.queue("", :exclusive => true).bind(x) 15 | 16 | ch.confirm_select 17 | 1000.times do 18 | x.publish("") 19 | end 20 | ch.wait_for_confirms # blocks calling thread until all acks are received 21 | 22 | sleep 0.2 23 | puts "Received acks for all published messages. #{q.name} now has #{q.message_count} messages." 24 | 25 | sleep 0.7 26 | puts "Disconnecting..." 27 | conn.close 28 | -------------------------------------------------------------------------------- /examples/guides/extensions/queue_lease.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # encoding: utf-8 3 | 4 | require "rubygems" 5 | require "march_hare" 6 | 7 | puts "=> Demonstrating queue TTL (queue leases)" 8 | puts 9 | 10 | conn = MarchHare.connect 11 | 12 | ch = conn.create_channel 13 | q = ch.queue("", :exclusive => true, :arguments => {"x-expires" => 300}) 14 | 15 | sleep 0.4 16 | begin 17 | # this will raise because the queue is already deleted 18 | q.message_count 19 | rescue MarchHare::NotFound => nfe 20 | puts "Got a 404 response: the queue has already been removed" 21 | end 22 | 23 | sleep 0.7 24 | puts "Closing..." 25 | conn.close 26 | -------------------------------------------------------------------------------- /examples/guides/extensions/sender_selected_distribution.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # encoding: utf-8 3 | 4 | require "rubygems" 5 | require "march_hare" 6 | 7 | puts "=> Demonstrating sender-selected distribution" 8 | puts 9 | 10 | conn = MarchHare.connect 11 | 12 | ch = conn.create_channel 13 | x = ch.direct("march_hare.examples.ssd.exchange") 14 | q1 = ch.queue("", :exclusive => true).bind(x, :routing_key => "one") 15 | q2 = ch.queue("", :exclusive => true).bind(x, :routing_key => "two") 16 | q3 = ch.queue("", :exclusive => true).bind(x, :routing_key => "three") 17 | q4 = ch.queue("", :exclusive => true).bind(x, :routing_key => "four") 18 | 19 | 10.times do |i| 20 | x.publish("Message #{i}", :routing_key => "one", :properties => { 21 | :headers => {"CC" => ["two", "three"]} 22 | }) 23 | end 24 | 25 | sleep 0.2 26 | puts "Queue #{q1.name} now has #{q1.message_count} messages in it" 27 | puts "Queue #{q2.name} now has #{q2.message_count} messages in it" 28 | puts "Queue #{q3.name} now has #{q3.message_count} messages in it" 29 | puts "Queue #{q4.name} now has #{q4.message_count} messages in it" 30 | 31 | sleep 0.7 32 | puts "Closing..." 33 | conn.close 34 | -------------------------------------------------------------------------------- /examples/guides/getting_started/blabbr.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # encoding: utf-8 3 | 4 | require "rubygems" 5 | require "march_hare" 6 | 7 | conn = MarchHare.connect 8 | 9 | ch = conn.create_channel 10 | x = ch.fanout("march_hare.nba.scores") 11 | 12 | ch.queue("joe", :auto_delete => true).bind(x).subscribe do |meta, payload| 13 | puts "#{payload} => joe" 14 | end 15 | 16 | ch.queue("aaron", :auto_delete => true).bind(x).subscribe do |meta, payload| 17 | puts "#{payload} => aaron" 18 | end 19 | 20 | ch.queue("bob", :auto_delete => true).bind(x).subscribe do |meta, payload| 21 | puts "#{payload} => bob" 22 | end 23 | 24 | x.publish("BOS 101, NYK 89") 25 | x.publish("ORL 85, ALT 88") 26 | sleep 1.0 27 | 28 | conn.close 29 | -------------------------------------------------------------------------------- /examples/guides/getting_started/hello_world.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # encoding: utf-8 3 | 4 | require "rubygems" 5 | require "march_hare" 6 | 7 | conn = MarchHare.connect 8 | 9 | ch = conn.create_channel 10 | q = ch.queue("march_hare.examples.hello_world", :auto_delete => true) 11 | 12 | c = q.subscribe do |metadata, payload| 13 | puts "Received #{payload}" 14 | end 15 | 16 | q.publish("Hello!", :routing_key => q.name) 17 | 18 | sleep 1.0 19 | 20 | c.cancel 21 | conn.close 22 | -------------------------------------------------------------------------------- /examples/guides/getting_started/weathr.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # encoding: utf-8 3 | 4 | require "rubygems" 5 | require "march_hare" 6 | 7 | connection = MarchHare.connect 8 | 9 | ch = connection.create_channel 10 | # topic exchange name can be any string 11 | x = ch.topic("weathr", :auto_delete => true) 12 | 13 | # Subscribers. 14 | ch.queue("", :exclusive => true).bind(x, :routing_key => "americas.north.#").subscribe do |metadata, payload| 15 | puts "An update for North America: #{payload}, routing key is #{metadata.routing_key}" 16 | end 17 | ch.queue("americas.south").bind(x, :routing_key => "americas.south.#").subscribe do |metadata, payload| 18 | puts "An update for South America: #{payload}, routing key is #{metadata.routing_key}" 19 | end 20 | ch.queue("us.california").bind(x, :routing_key => "americas.north.us.ca.*").subscribe do |metadata, payload| 21 | puts "An update for US/California: #{payload}, routing key is #{metadata.routing_key}" 22 | end 23 | ch.queue("us.tx.austin").bind(x, :routing_key => "#.tx.austin").subscribe do |metadata, payload| 24 | puts "An update for Austin, TX: #{payload}, routing key is #{metadata.routing_key}" 25 | end 26 | ch.queue("it.rome").bind(x, :routing_key => "europe.italy.rome").subscribe do |metadata, payload| 27 | puts "An update for Rome, Italy: #{payload}, routing key is #{metadata.routing_key}" 28 | end 29 | ch.queue("asia.hk").bind(x, :routing_key => "asia.southeast.hk.#").subscribe do |metadata, payload| 30 | puts "An update for Hong Kong: #{payload}, routing key is #{metadata.routing_key}" 31 | end 32 | 33 | x.publish("San Diego update", :routing_key => "americas.north.us.ca.sandiego") 34 | x.publish("Berkeley update", :routing_key => "americas.north.us.ca.berkeley") 35 | x.publish("San Francisco update", :routing_key => "americas.north.us.ca.sanfrancisco") 36 | x.publish("New York update", :routing_key => "americas.north.us.ny.newyork") 37 | x.publish("São Paolo update", :routing_key => "americas.south.brazil.saopaolo") 38 | x.publish("Hong Kong update", :routing_key => "asia.southeast.hk.hongkong") 39 | x.publish("Kyoto update", :routing_key => "asia.southeast.japan.kyoto") 40 | x.publish("Shanghai update", :routing_key => "asia.southeast.prc.shanghai") 41 | x.publish("Rome update", :routing_key => "europe.italy.roma") 42 | x.publish("Paris update", :routing_key => "europe.france.paris") 43 | 44 | sleep 1.0 45 | 46 | connection.close 47 | -------------------------------------------------------------------------------- /examples/guides/queues/redeliveries.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # encoding: utf-8 3 | 4 | require "rubygems" 5 | require "march_hare" 6 | 7 | puts "=> Subscribing for messages using explicit acknowledgements model" 8 | puts 9 | 10 | connection1 = MarchHare.connect 11 | connection2 = MarchHare.connect 12 | connection3 = MarchHare.connect 13 | 14 | ch1 = connection1.create_channel 15 | ch1.prefetch = 1 16 | 17 | ch2 = connection2.create_channel 18 | ch2.prefetch = 1 19 | 20 | ch3 = connection3.create_channel 21 | ch3.prefetch = 1 22 | 23 | x = ch3.fanout("amq.fanout") 24 | q1 = ch1.queue("march_hare.examples.acknowledgements.explicit", :auto_delete => false) 25 | q1.purge 26 | 27 | q1.bind(x).subscribe(:manual_ack => true) do |metadata, payload| 28 | # do some work 29 | sleep(0.2) 30 | 31 | # acknowledge some messages, they will be removed from the queue 32 | if rand > 0.5 33 | # FYI: there is a shortcut, MarchHare::Channel.ack 34 | ch1.acknowledge(metadata.delivery_tag, false) 35 | puts "[consumer1] Got message ##{metadata.headers['i']}, redelivered?: #{metadata.redelivered?}, ack-ed" 36 | else 37 | # some messages are not ack-ed and will remain in the queue for redelivery 38 | # when app #1 connection is closed (either properly or due to a crash) 39 | puts "[consumer1] Got message ##{metadata.headers['i']}, SKIPPPED" 40 | end 41 | end 42 | 43 | q2 = ch2.queue("march_hare.examples.acknowledgements.explicit", :auto_delete => false) 44 | q2.bind(x).subscribe(:manual_ack => true) do |metadata, payload| 45 | # do some work 46 | sleep(0.2) 47 | 48 | ch2.acknowledge(metadata.delivery_tag, false) 49 | puts "[consumer2] Got message ##{metadata.headers['i']}, redelivered?: #{metadata.redelivered?}, ack-ed" 50 | end 51 | 52 | t1 = Thread.new do 53 | i = 0 54 | loop do 55 | sleep 0.5 56 | 57 | x.publish("Message ##{i}", :properties => { 58 | :headers => { "i" => i } 59 | }) 60 | i += 1 61 | end 62 | end 63 | t1.abort_on_exception = true 64 | 65 | t2 = Thread.new do 66 | sleep 4.0 67 | 68 | puts "----- Connection 1 is now closed (we pretend that it has crashed) -----" 69 | connection1.close 70 | end 71 | t2.abort_on_exception = true 72 | 73 | 74 | sleep 7.0 75 | connection2.close 76 | connection3.close 77 | -------------------------------------------------------------------------------- /examples/non_blocking_subscription.rb: -------------------------------------------------------------------------------- 1 | $: << 'lib' 2 | 3 | require 'march_hare' 4 | 5 | connection = MarchHare.connect(:host => 'localhost') 6 | channel = connection.create_channel 7 | channel.prefetch = 10 8 | 9 | exchange = channel.exchange('test', :type => :direct) 10 | 11 | queue = channel.queue('hello.world') 12 | queue.bind(exchange, :routing_key => 'xyz') 13 | queue.purge 14 | 15 | consumer = queue.subscribe(:ack => true, :blocking => false) do |headers, msg| 16 | puts msg 17 | headers.ack 18 | end 19 | 20 | 100.times do |i| 21 | exchange.publish("hello world! #{i}", :routing_key => 'xyz') 22 | end 23 | 24 | # make sure all messages are processed before we cancel 25 | # to avoid confusing exceptions from the [already shutdown] executor. MK. 26 | sleep 1.0 27 | consumer.cancel 28 | 29 | puts "Disconnecting now..." 30 | 31 | at_exit do 32 | channel.close 33 | connection.close 34 | end 35 | -------------------------------------------------------------------------------- /lib/ext/rabbitmq-client.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ruby-amqp/march_hare/aaf3cc9561b66afce8d4eb7f9ce96de61e5cb5ed/lib/ext/rabbitmq-client.jar -------------------------------------------------------------------------------- /lib/ext/slf4j-api.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ruby-amqp/march_hare/aaf3cc9561b66afce8d4eb7f9ce96de61e5cb5ed/lib/ext/slf4j-api.jar -------------------------------------------------------------------------------- /lib/ext/slf4j-simple.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ruby-amqp/march_hare/aaf3cc9561b66afce8d4eb7f9ce96de61e5cb5ed/lib/ext/slf4j-simple.jar -------------------------------------------------------------------------------- /lib/march_hare.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require 'java' unless defined?(TruffleRuby) 4 | 5 | # Java client logging depends on SLF4J 6 | require 'ext/slf4j-api' 7 | require 'ext/slf4j-simple' 8 | 9 | require 'ext/rabbitmq-client' 10 | 11 | require 'march_hare/version' 12 | require 'march_hare/exceptions' 13 | require 'march_hare/session' 14 | 15 | # MarchHare is a JRuby client for RabbitMQ built on top of the official Java client. 16 | # 17 | # @see MarchHare.connect 18 | # @see MarchHare::Session 19 | # @see MarchHare::Channel 20 | module MarchHare 21 | # Delegates to {MarchHare::Session.connect} 22 | # @see MarchHare::Session.connect 23 | def self.connect(*args) 24 | Session.connect(*args) 25 | end 26 | end 27 | 28 | # Backwards compatibility 29 | # @private 30 | Hotbunnies = MarchHare 31 | # Backwards compatibility 32 | # @private 33 | HotBunnies = MarchHare 34 | 35 | require 'march_hare/channel' 36 | require 'march_hare/queue' 37 | require 'march_hare/exchange' 38 | -------------------------------------------------------------------------------- /lib/march_hare/consumers.rb: -------------------------------------------------------------------------------- 1 | require "march_hare/consumers/base" 2 | require "march_hare/consumers/blocking" 3 | -------------------------------------------------------------------------------- /lib/march_hare/consumers/base.rb: -------------------------------------------------------------------------------- 1 | require "march_hare/versioned_delivery_tag" 2 | 3 | module MarchHare 4 | import com.rabbitmq.client.DefaultConsumer 5 | 6 | class BaseConsumer < DefaultConsumer 7 | attr_accessor :consumer_tag 8 | attr_accessor :auto_ack 9 | 10 | def initialize(channel, queue, opts) 11 | super(channel) 12 | @channel = channel 13 | @queue = queue 14 | @opts = opts 15 | @auto_ack = true 16 | 17 | @cancelling = JavaConcurrent::AtomicBoolean.new 18 | @cancelled = JavaConcurrent::AtomicBoolean.new 19 | 20 | @terminated = JavaConcurrent::AtomicBoolean.new 21 | end 22 | 23 | def handleDelivery(consumer_tag, envelope, properties, bytes) 24 | body = String.from_java_bytes(bytes) 25 | headers = Headers.new(channel, consumer_tag, envelope, properties) 26 | 27 | deliver(headers, body) 28 | end 29 | 30 | def handleCancel(consumer_tag) 31 | @cancelled.set(true) 32 | @channel.unregister_consumer(consumer_tag) 33 | 34 | if f = @opts[:on_cancellation] 35 | case f.arity 36 | when 0 then 37 | f.call 38 | when 1 then 39 | f.call(self) 40 | when 2 then 41 | f.call(@channel, self) 42 | when 3 then 43 | f.call(@channel, self, consumer_tag) 44 | else 45 | f.call(@channel, self, consumer_tag) 46 | end 47 | end 48 | 49 | @terminated.set(true) 50 | end 51 | 52 | def handleCancelOk(consumer_tag) 53 | @cancelled.set(true) 54 | @channel.unregister_consumer(consumer_tag) 55 | 56 | @terminated.set(true) 57 | end 58 | 59 | def start 60 | # no-op 61 | end 62 | 63 | def gracefully_shut_down 64 | # no-op 65 | end 66 | 67 | def deliver(headers, message) 68 | raise NotImplementedError, 'To be implemented by a subclass' 69 | end 70 | 71 | def cancelled? 72 | @cancelling.get || @cancelled.get 73 | end 74 | 75 | def active? 76 | !terminated? 77 | end 78 | 79 | def terminated? 80 | @terminated.get 81 | end 82 | 83 | # @private 84 | def recover_from_network_failure 85 | @terminated.set(false) 86 | @cancelled.set(false) 87 | @consumer_tag = @channel.basic_consume(@queue.name, @auto_ack, @consumer_tag, self) 88 | 89 | @consumer_tag 90 | end 91 | end 92 | 93 | class CallbackConsumer < BaseConsumer 94 | def initialize(channel, queue, opts, callback) 95 | raise ArgumentError, "callback must not be nil!" if callback.nil? 96 | 97 | super(channel, queue, opts) 98 | @callback = callback 99 | @callback_arity = @callback.arity 100 | end 101 | 102 | def deliver(headers, message) 103 | if @callback_arity == 2 or @callback_arity < 0 104 | @callback.call(headers, message) 105 | else 106 | @callback.call(message) 107 | end 108 | end 109 | 110 | def cancel 111 | if @cancelling.get_and_set(true) 112 | false 113 | else 114 | @channel.basic_cancel(@consumer_tag) 115 | @cancelled.set(true) 116 | @terminated.set(true) 117 | true 118 | end 119 | end 120 | end 121 | end 122 | -------------------------------------------------------------------------------- /lib/march_hare/consumers/blocking.rb: -------------------------------------------------------------------------------- 1 | require "march_hare/consumers/base" 2 | 3 | module MarchHare 4 | class BlockingCallbackConsumer < CallbackConsumer 5 | POISON = :__poison__ 6 | 7 | def initialize(channel, queue, buffer_size, opts, callback) 8 | super(channel, queue, opts, callback) 9 | if buffer_size 10 | @internal_queue = JavaConcurrent::ArrayBlockingQueue.new(buffer_size) 11 | else 12 | @internal_queue = JavaConcurrent::LinkedBlockingQueue.new 13 | end 14 | end 15 | 16 | def cancel 17 | if super 18 | @internal_queue.offer(POISON) 19 | end 20 | end 21 | 22 | def start 23 | interrupted = false 24 | until (@cancelling.get || @cancelled.get) || JavaConcurrent::Thread.current_thread.interrupted? 25 | begin 26 | pair = @internal_queue.take 27 | if pair 28 | if pair == POISON 29 | @cancelling.set(true) 30 | else 31 | @callback.call(*pair) 32 | end 33 | end 34 | rescue JavaConcurrent::InterruptedException => e 35 | interrupted = true 36 | end 37 | end 38 | while (pair = @internal_queue.poll) 39 | if pair 40 | if pair == POISON 41 | @cancelling.set(true) 42 | else 43 | @callback.call(*pair) 44 | end 45 | end 46 | end 47 | @terminated.set(true) 48 | if interrupted 49 | JavaConcurrent::Thread.current_thread.interrupt 50 | end 51 | end 52 | 53 | def deliver(*pair) 54 | if (@cancelling.get || @cancelled.get) || JavaConcurrent::Thread.current_thread.interrupted? 55 | @internal_queue.offer(pair) 56 | else 57 | begin 58 | @internal_queue.put(pair) 59 | rescue JavaConcurrent::InterruptedException => e 60 | JavaConcurrent::Thread.current_thread.interrupt 61 | end 62 | end 63 | end 64 | 65 | def gracefully_shut_down 66 | @cancelling.set(true) 67 | @internal_queue.offer(POISON) 68 | 69 | @terminated.set(true) 70 | end 71 | 72 | end 73 | end 74 | -------------------------------------------------------------------------------- /lib/march_hare/exception_handler.rb: -------------------------------------------------------------------------------- 1 | module MarchHare 2 | class ExceptionHandler < com.rabbitmq.client.impl.ForgivingExceptionHandler 3 | def initialize(logger) 4 | super() 5 | @logger = logger 6 | end 7 | 8 | def log(msg, error) 9 | logger.error(msg) 10 | logger.error(error) 11 | end 12 | 13 | private 14 | 15 | attr_reader :logger 16 | end 17 | end -------------------------------------------------------------------------------- /lib/march_hare/exceptions.rb: -------------------------------------------------------------------------------- 1 | module MarchHare 2 | class Exception < StandardError 3 | end 4 | 5 | class ShutdownSignal < Exception 6 | attr_reader :cause 7 | 8 | def initialize(cause) 9 | @cause = cause 10 | end 11 | end 12 | 13 | class NetworkException < Exception 14 | end 15 | 16 | class ConnectionRefused < NetworkException 17 | end 18 | 19 | class ConnectionClosedException < Exception 20 | def initialize(message='') 21 | super("Connection was explicitly closed and cannot be reopened. Create a new Connection instead. #{message}") 22 | end 23 | end 24 | 25 | class ChannelLevelException < Exception 26 | attr_reader :channel_close 27 | 28 | def initialize(message, channel_close) 29 | super(message) 30 | 31 | @channel_close = channel_close 32 | end 33 | end 34 | 35 | class ConnectionLevelException < Exception 36 | attr_reader :connection_close 37 | 38 | def initialize(message, connection_close) 39 | super(message) 40 | 41 | @connection_close = connection_close 42 | end 43 | end 44 | 45 | # Raised when RabbitMQ closes network connection before 46 | # finalizing the connection, typically indicating authentication failure. 47 | # 48 | # RabbitMQ versions beyond 3.2 use a better defined authentication failure 49 | # notifications. 50 | class PossibleAuthenticationFailureError < Exception 51 | attr_reader :username, :vhost 52 | 53 | def initialize(username, vhost, password_length) 54 | @username = username 55 | @vhost = vhost 56 | 57 | super("Authentication with RabbitMQ failed or RabbitMQ version used does not support AMQP 0-9-1. Username: #{username}, vhost: #{vhost}, password length: #{password_length}. Please check your configuration.") 58 | end 59 | end 60 | 61 | # Raised when RabbitMQ 3.2+ reports authentication 62 | # failure before closing TCP connection. 63 | class AuthenticationFailureError < PossibleAuthenticationFailureError 64 | attr_reader :username, :vhost 65 | 66 | def initialize(username, vhost, password_length) 67 | super(username, vhost, password_length) 68 | end 69 | end 70 | 71 | class PreconditionFailed < ChannelLevelException 72 | end 73 | 74 | class NotFound < ChannelLevelException 75 | end 76 | 77 | class ResourceLocked < ChannelLevelException 78 | end 79 | 80 | class AccessRefused < ChannelLevelException 81 | end 82 | 83 | class ChannelError < ConnectionLevelException 84 | end 85 | 86 | class InvalidCommand < ConnectionLevelException 87 | end 88 | 89 | class FrameError < ConnectionLevelException 90 | end 91 | 92 | class UnexpectedFrame < ConnectionLevelException 93 | end 94 | 95 | class ChannelAlreadyClosed < Exception 96 | end 97 | 98 | class ConnectionForced < Exception 99 | end 100 | 101 | # Converts RabbitMQ Java client exceptions 102 | # 103 | # @private 104 | class Exceptions 105 | def self.convert(e, unwrap_io_exception = true) 106 | case e 107 | when java.net.SocketException then 108 | IOError.new(e.message) 109 | when java.io.IOException then 110 | c = e.cause 111 | 112 | if c && unwrap_io_exception 113 | convert(c, false) 114 | else 115 | IOError.new(e.message) 116 | end 117 | when com.rabbitmq.client.AlreadyClosedException then 118 | ChannelAlreadyClosed.new(e.reason) 119 | when com.rabbitmq.client.ShutdownSignalException then 120 | exception_for_protocol_method(e.reason) 121 | else 122 | e 123 | end 124 | end 125 | 126 | def self.convert_and_reraise(e) 127 | raise convert(e) 128 | end 129 | 130 | def self.exception_for_protocol_method(m) 131 | case m 132 | # com.rabbitmq.client.AMQP.Connection.Close does not resolve the inner 133 | # class correctly. Looks like a JRuby bug we work around by using Rubyesque 134 | # class name. MK. 135 | when Java::ComRabbitmqClient::AMQP::Connection::Close then 136 | exception_for_connection_close(m) 137 | when Java::ComRabbitmqClient::AMQP::Channel::Close then 138 | exception_for_channel_close(m) 139 | else 140 | NotImplementedError.new("Exception convertion for protocol method #{m.inspect} is not implemented!") 141 | end 142 | end # def self 143 | 144 | 145 | def self.exception_for_connection_close(m) 146 | klass = case m.reply_code 147 | when 320 then 148 | ConnectionForced 149 | when 501 then 150 | FrameError 151 | when 503 then 152 | InvalidCommand 153 | when 504 then 154 | ChannelError 155 | when 505 then 156 | UnexpectedFrame 157 | else 158 | raise "Unknown reply code: #{m.reply_code}, text: #{m.reply_text}" 159 | end 160 | 161 | klass.new("Connection-level error: #{m.reply_text}", m) 162 | end 163 | 164 | def self.exception_for_channel_close(m) 165 | klass = case m.reply_code 166 | when 403 then 167 | AccessRefused 168 | when 404 then 169 | NotFound 170 | when 405 then 171 | ResourceLocked 172 | when 406 then 173 | PreconditionFailed 174 | else 175 | ChannelLevelException 176 | end 177 | 178 | klass.new(m.reply_text, m) 179 | end 180 | end 181 | end # MarchHare 182 | -------------------------------------------------------------------------------- /lib/march_hare/exchange.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | module MarchHare 4 | import com.rabbitmq.client.AMQP 5 | 6 | # Represents AMQP 0.9.1 exchanges. 7 | # 8 | # @see http://rubymarchhare.info/articles/exchanges.html Exchanges and Publishing guide 9 | # @see http://rubymarchhare.info/articles/extensions.html RabbitMQ Extensions guide 10 | # @see Queue#bind 11 | class Exchange 12 | # @return [String] Exchange name 13 | attr_reader :name 14 | # @return [MarchHare::Channel] Channel this exchange object uses 15 | attr_reader :channel 16 | 17 | # Type of this exchange (one of: :direct, :fanout, :topic, :headers). 18 | # @return [Symbol] 19 | attr_reader :type 20 | 21 | # Instantiates a new exchange. 22 | # 23 | # @param [Channel] channel Channel to declare exchange on 24 | # @params [String] name Exchange name 25 | # @params [Hash] options ({}) Exchange and declaration attributes 26 | # 27 | # @options opts :type [Symbol, String] Exchange type 28 | # @options opts :durable [Boolean] (false) Will the exchange be durable? 29 | # @options opts :auto_delete [Boolean] (false) Will the exchange be auto-deleted? 30 | # @options opts :passive [Boolean] (false) Should passive declaration be used? 31 | # @options opts :internal [Boolean] (false) Will the exchange be internal? 32 | # 33 | # @see MarchHare::Channel#default_exchange 34 | # @see MarchHare::Channel#fanout 35 | # @see MarchHare::Channel#topic 36 | # @see MarchHare::Channel#direct 37 | # @see MarchHare::Channel#headers 38 | # @see MarchHare::Channel#exchange 39 | def initialize(channel, name, options = {}) 40 | raise ArgumentError, "exchange channel cannot be nil" if channel.nil? 41 | raise ArgumentError, "exchange name cannot be nil" if name.nil? 42 | raise ArgumentError, "exchange :type must be specified as an option" if options[:type].nil? 43 | 44 | @channel = channel 45 | @name = name 46 | @type = options[:type] 47 | @options = {:type => :fanout, :durable => false, :auto_delete => false, :internal => false, :passive => false}.merge(options) 48 | end 49 | 50 | # Publishes a message 51 | # 52 | # @param [String] payload Message payload. It will never be modified by MarchHare or RabbitMQ in any way. 53 | # @param [Hash] opts Message properties (metadata) and delivery settings 54 | # 55 | # @option opts [String] :routing_key Routing key 56 | # @option opts [Boolean] :persistent Should the message be persisted to disk? 57 | # @option opts [Boolean] :mandatory Should the message be returned if it cannot be routed to any queue? 58 | # @option opts [Hash] :properties Messages and delivery properties 59 | # 60 | # * :timestamp (Time) A timestamp associated with this message 61 | # * :expiration (Integer) Expiration time after which the message will be deleted 62 | # * :type (String) Message type, e.g. what type of event or command this message represents. Can be any string 63 | # * :reply_to (String) Queue name other apps should send the response to 64 | # * :content_type (String) Message content type (e.g. application/json) 65 | # * :content_encoding (String) Message content encoding (e.g. gzip) 66 | # * :correlation_id (String) Message correlated to this one, e.g. what request this message is a reply for 67 | # * :priority (Integer) Message priority, 0 to 9. Not used by RabbitMQ, only applications 68 | # * :message_id (String) Any message identifier 69 | # * :user_id (String) Optional user ID. Verified by RabbitMQ against the actual connection username 70 | # * :app_id (String) Optional application ID 71 | # 72 | # @return [MarchHare::Exchange] Self 73 | # @see http://rubymarchhare.info/articles/exchanges.html Exchanges and Publishing guide 74 | # @api public 75 | def publish(body, opts = {}) 76 | options = {:routing_key => '', :mandatory => false}.merge(opts) 77 | @channel.basic_publish(@name, 78 | options.delete(:routing_key), 79 | options.delete(:mandatory), 80 | options.fetch(:properties, options), 81 | body.to_java_bytes) 82 | end 83 | 84 | # Deletes the exchange unless it is predefined 85 | # 86 | # @param [Hash] options Options 87 | # 88 | # @option opts [Boolean] if_unused (false) Should this exchange be deleted only if it is no longer used 89 | # 90 | # @see http://rubymarchhare.info/articles/exchanges.html Exchanges and Publishing guide 91 | # @api public 92 | def delete(options={}) 93 | @channel.deregister_exchange(self) 94 | @channel.exchange_delete(@name, options.fetch(:if_unused, false)) unless predefined? 95 | end 96 | 97 | # Binds an exchange to another (source) exchange using exchange.bind AMQP 0.9.1 extension 98 | # that RabbitMQ provides. 99 | # 100 | # @param [String] exchange Source exchange name 101 | # @param [Hash] options Options 102 | # 103 | # @option opts [String] routing_key (nil) Routing key used for binding 104 | # @option opts [Hash] arguments ({}) Optional arguments 105 | # 106 | # @return [MarchHare::Exchange] Self 107 | # @see http://rubymarchhare.info/articles/exchanges.html Exchanges and Publishing guide 108 | # @see http://rubymarchhare.info/articles/bindings.html Bindings guide 109 | # @see http://rubymarchhare.info/articles/extensions.html RabbitMQ Extensions guide 110 | # @api public 111 | def bind(exchange, options={}) 112 | exchange_name = if exchange.respond_to?(:name) then exchange.name else exchange.to_s end 113 | @channel.exchange_bind(@name, exchange_name, options.fetch(:routing_key, '')) 114 | end 115 | 116 | # Unbinds an exchange from another (source) exchange using exchange.unbind AMQP 0.9.1 extension 117 | # that RabbitMQ provides. 118 | # 119 | # @param [String] source Source exchange name 120 | # @param [Hash] opts Options 121 | # 122 | # @option opts [String] routing_key (nil) Routing key used for binding 123 | # @option opts [Hash] arguments ({}) Optional arguments 124 | # 125 | # @return [Bunny::Exchange] Self 126 | # @see http://rubymarchhare.info/articles/exchanges.html Exchanges and Publishing guide 127 | # @see http://rubymarchhare.info/articles/bindings.html Bindings guide 128 | # @see http://rubymarchhare.info/articles/extensions.html RabbitMQ Extensions guide 129 | # @api public 130 | def unbind(exchange, opts = {}) 131 | exchange_name = if exchange.respond_to?(:name) then exchange.name else exchange.to_s end 132 | @channel.exchange_unbind(@name, exchange_name, opts.fetch(:routing_key, ''), opts[:arguments]) 133 | end 134 | 135 | # @return [Boolean] true if this exchange is a pre-defined one (amq.direct, amq.fanout, amq.match and so on) 136 | def predefined? 137 | @name.empty? || @name.start_with?("amq.") 138 | end 139 | 140 | # @return [Boolean] true if this exchange was declared as durable (will survive broker restart). 141 | # @api public 142 | def durable? 143 | !!@options[:durable] 144 | end # durable? 145 | 146 | # @return [Boolean] true if this exchange was declared as automatically deleted (deleted as soon as last consumer unbinds). 147 | # @api public 148 | def auto_delete? 149 | !!@options[:auto_delete] 150 | end # auto_delete? 151 | 152 | # @return [Boolean] true if this exchange is internal (used solely for exchange-to-exchange 153 | # bindings and cannot be published to by clients) 154 | def internal? 155 | !!@options[:internal] 156 | end 157 | 158 | # Waits until all outstanding publisher confirms on the channel 159 | # arrive. 160 | # 161 | # This is a convenience method that delegates to {Channel#wait_for_confirms} 162 | # 163 | # @api public 164 | def wait_for_confirms 165 | @channel.wait_for_confirms 166 | end 167 | 168 | 169 | # 170 | # Implementation 171 | # 172 | 173 | # @private 174 | def declare! 175 | unless predefined? 176 | if @options[:passive] 177 | then @channel.exchange_declare_passive(@name) 178 | else @channel.exchange_declare(@name, @options[:type].to_s, 179 | @options[:durable], 180 | @options[:auto_delete], 181 | @options[:internal], 182 | @options[:arguments]) 183 | end 184 | end 185 | end 186 | 187 | # @private 188 | def recover_from_network_failure 189 | # puts "Recovering exchange #{@name} from network failure" 190 | unless predefined? 191 | begin 192 | declare! 193 | 194 | @channel.register_exchange(self) 195 | rescue Exception => e 196 | @channel.logger.error("Caught Exception while redeclaring and registering exchange #{@name}!") 197 | @channel.logger.error(e) 198 | end 199 | end 200 | end 201 | end 202 | end 203 | -------------------------------------------------------------------------------- /lib/march_hare/juc.rb: -------------------------------------------------------------------------------- 1 | module MarchHare 2 | module JavaConcurrent 3 | java_import 'java.lang.Thread' 4 | java_import 'java.lang.Runnable' 5 | java_import 'java.lang.InterruptedException' 6 | include_package 'java.util.concurrent' 7 | include_package 'java.util.concurrent.atomic' 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /lib/march_hare/metadata.rb: -------------------------------------------------------------------------------- 1 | module MarchHare 2 | class Headers 3 | attr_reader :channel, :consumer_tag, :envelope, :properties 4 | 5 | def initialize(channel, consumer_tag, envelope, properties) 6 | @channel = channel 7 | @consumer_tag = consumer_tag 8 | @envelope = envelope 9 | @properties = properties 10 | 11 | # Prime the delivery tag when the instance is created. If #delivery_tag is first 12 | # called after a recovery, then it'll fail to mismatch and will allow an invalid 13 | # ack/nack, which will cause the channel to unexpectedly close 14 | @delivery_tag = delivery_tag 15 | end 16 | 17 | def ack(options={}) 18 | @channel.basic_ack(delivery_tag, options.fetch(:multiple, false)) 19 | end 20 | 21 | def reject(options={}) 22 | @channel.basic_reject(delivery_tag, options.fetch(:requeue, false)) 23 | end 24 | 25 | begin :envelope_delegation 26 | [ 27 | :routing_key, 28 | :redeliver, 29 | :exchange 30 | ].each do |envelope_property| 31 | define_method(envelope_property) { @envelope.__send__(envelope_property) } 32 | end 33 | 34 | alias_method :redelivered?, :redeliver 35 | end 36 | 37 | def delivery_tag 38 | @delivery_tag ||= VersionedDeliveryTag.new(@envelope.delivery_tag, @channel.recoveries_counter.get) 39 | end 40 | 41 | begin :message_properties_delegation 42 | [ 43 | :content_encoding, 44 | :content_type, 45 | :content_encoding, 46 | :delivery_mode, 47 | :priority, 48 | :correlation_id, 49 | :reply_to, 50 | :expiration, 51 | :message_id, 52 | :timestamp, 53 | :type, 54 | :user_id, 55 | :app_id, 56 | :cluster_id 57 | ].each do |properties_property| 58 | define_method(properties_property) { @properties.__send__(properties_property) } 59 | end # each 60 | end 61 | 62 | def headers 63 | deep_normalize_headers(@properties.headers) 64 | end 65 | 66 | def persistent? 67 | delivery_mode == 2 68 | end 69 | 70 | def redelivered? 71 | redeliver 72 | end 73 | 74 | def redelivery? 75 | redeliver 76 | end 77 | 78 | # Turns LongString instances into String 79 | private 80 | LONG_STRING_TYPE = com.rabbitmq.client.LongString 81 | def deep_normalize_headers(value) 82 | if value.is_a?(java.util.Map) 83 | new_map = {} 84 | value.each {|k,v| new_map[k] = deep_normalize_headers(v)} 85 | new_map 86 | elsif value.is_a?(java.util.List) 87 | value.map {|v| deep_normalize_headers(v)} 88 | else 89 | if value.kind_of?(LONG_STRING_TYPE) 90 | value.to_s 91 | else 92 | value 93 | end 94 | end 95 | end 96 | end # Headers 97 | 98 | 99 | class BasicPropertiesBuilder 100 | def self.build_properties_from(props = {}) 101 | builder = AMQP::BasicProperties::Builder.new 102 | 103 | builder.content_type(props[:content_type]). 104 | content_encoding(props[:content_encoding]). 105 | headers(self.deep_stringify_keys(props[:headers])). 106 | delivery_mode(props[:persistent] ? 2 : 1). 107 | priority(props[:priority]). 108 | correlation_id(props[:correlation_id]). 109 | reply_to(props[:reply_to]). 110 | expiration(if props[:expiration] then props[:expiration].to_s end). 111 | message_id(props[:message_id]). 112 | timestamp(props[:timestamp]). 113 | type(props[:type]). 114 | user_id(props[:user_id]). 115 | app_id(props[:app_id]). 116 | cluster_id(props[:cluster_id]). 117 | build 118 | end 119 | 120 | # Deep hash transformation fn is courtesy of Avdi Grimm 121 | # and Markus Kuhnt, with some modifications to support primitive 122 | # and array values. 123 | def self.transform_hash(value, options = {}, &block) 124 | return nil if value.nil? 125 | return value if !value.is_a?(Hash) && !value.is_a?(Array) 126 | return value.map { |v| transform_hash(v, options, &block) } if value.is_a?(Array) 127 | 128 | value.inject({}) do |result, (key, value)| 129 | value = if (options[:deep] && value.is_a?(Hash)) 130 | transform_hash(value, options, &block) 131 | else 132 | if value.is_a?(Array) 133 | value.map { |v| transform_hash(v, options, &block) } 134 | else 135 | value 136 | end 137 | end 138 | block.call(result, key, value) 139 | result 140 | end 141 | end 142 | 143 | def self.deep_stringify_keys(hash) 144 | transform_hash(hash, :deep => true) do |hash, key, value| 145 | hash[key.to_s] = value 146 | end 147 | end 148 | end 149 | end # MarchHare 150 | -------------------------------------------------------------------------------- /lib/march_hare/queue.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require "march_hare/juc" 4 | require "march_hare/metadata" 5 | require "march_hare/consumers" 6 | require "set" 7 | 8 | module MarchHare 9 | # Represents AMQP 0.9.1 queue. 10 | # 11 | # @see http://rubymarchhare.info/articles/queues.html Queues and Consumers guide 12 | # @see http://rubymarchhare.info/articles/extensions.html RabbitMQ Extensions guide 13 | class Queue 14 | # 15 | # API 16 | # 17 | 18 | module Types 19 | QUORUM = "quorum" 20 | CLASSIC = "classic" 21 | STREAM = "stream" 22 | 23 | KNOWN = [CLASSIC, QUORUM, STREAM] 24 | 25 | def self.known?(q_type) 26 | KNOWN.include?(q_type) 27 | end 28 | end 29 | 30 | module XArgs 31 | MAX_LENGTH = "x-max-length", 32 | QUEUE_TYPE = "x-queue-type" 33 | end 34 | 35 | # @return [MarchHare::Channel] Channel this queue uses 36 | attr_reader :channel 37 | # @return [String] Queue name 38 | attr_reader :name 39 | 40 | # @param [MarchHare::Channel] channel_or_connection Channel this queue will use. 41 | # @param [String] name Queue name. Pass an empty string to make RabbitMQ generate a unique one. 42 | # @param [Hash] opts Queue properties 43 | # 44 | # @option opts [Boolean] :durable (false) Should this queue be durable? 45 | # @option opts [Boolean] :auto_delete (false) Should this queue be automatically deleted when the last consumer disconnects? 46 | # @option opts [Boolean] :exclusive (false) Should this queue be exclusive (only can be used by this connection, removed when the connection is closed)? 47 | # @option opts [Boolean] :arguments ({}) Additional optional arguments (typically used by RabbitMQ extensions and plugins) 48 | # 49 | # @see MarchHare::Channel#queue 50 | # @see http://rubymarchhare.info/articles/queues.html Queues and Consumers guide 51 | # @see http://rubymarchhare.info/articles/extensions.html RabbitMQ Extensions guide 52 | def initialize(channel, name, options={}) 53 | raise ArgumentError, 'queue name must be a string' unless name.is_a? String 54 | 55 | @channel = channel 56 | @name = name 57 | @options = {:durable => false, :exclusive => false, :auto_delete => false, :passive => false, :arguments => Hash.new}.merge(options) 58 | 59 | args = @options[:arguments] || {} 60 | @type = @options.fetch(:type, args.fetch(XArgs::QUEUE_TYPE, Types::CLASSIC)).to_s 61 | @durable = if @type == Types::QUORUM or @type == Types::STREAM 62 | true 63 | else 64 | @options[:durable] 65 | end 66 | 67 | @exclusive = @options[:exclusive] 68 | @server_named = @name.empty? 69 | @auto_delete = @options[:auto_delete] 70 | 71 | @arguments = if @type and !@type.empty? then 72 | args = @options[:arguments] || {} 73 | {XArgs::QUEUE_TYPE => @type}.merge(args) 74 | else 75 | @options[:arguments] 76 | end 77 | verify_type!(@arguments) 78 | 79 | @bindings = Set.new 80 | end 81 | 82 | 83 | # @return [Boolean] true if this queue was declared as durable (will survive broker restart). 84 | # @see http://rubymarchhare.info/articles/queues.html Queues and Consumers guide 85 | def durable? 86 | @durable 87 | end # durable? 88 | 89 | # @return [Boolean] true if this queue was declared as exclusive (limited to just one consumer) 90 | # @see http://rubymarchhare.info/articles/queues.html Queues and Consumers guide 91 | def exclusive? 92 | @exclusive 93 | end # exclusive? 94 | 95 | # @return [Boolean] true if this queue was declared as automatically deleted (deleted as soon as last consumer unbinds). 96 | # @see http://rubymarchhare.info/articles/queues.html Queues and Consumers guide 97 | def auto_delete? 98 | @auto_delete 99 | end # auto_delete? 100 | 101 | # @return [Boolean] true if this queue was declared as server named. 102 | # @see http://rubymarchhare.info/articles/queues.html Queues and Consumers guide 103 | def server_named? 104 | @server_named 105 | end # server_named? 106 | 107 | # @return [Hash] Additional optional arguments (typically used by RabbitMQ extensions and plugins) 108 | def arguments 109 | @arguments 110 | end 111 | 112 | # Binds queue to an exchange 113 | # 114 | # @param [MarchHare::Exchange,String] exchange Exchange to bind to 115 | # @param [Hash] options Binding properties 116 | # 117 | # @option options [String] :routing_key Routing key 118 | # @option options [Hash] :arguments ({}) Additional optional binding arguments 119 | # 120 | # @see http://rubymarchhare.info/articles/queues.html Queues and Consumers guide 121 | # @see http://rubymarchhare.info/articles/bindings.html Bindings guide 122 | def bind(exchange, options={}) 123 | exchange_name = if exchange.respond_to?(:name) then 124 | exchange.name 125 | else 126 | exchange.to_s 127 | end 128 | @channel.queue_bind(@name, exchange_name, (options[:routing_key] || options[:key] || ""), options[:arguments]) 129 | 130 | # store bindings for automatic recovery. We need to be very careful to 131 | # not cause an infinite rebinding loop here when we recover. MK. 132 | binding = { :exchange => exchange_name, :routing_key => (options[:routing_key] || options[:key]), :arguments => options[:arguments] } 133 | @bindings << binding unless @bindings.include?(binding) 134 | 135 | self 136 | end 137 | 138 | # Unbinds queue from an exchange 139 | # 140 | # @param [MarchHare::Exchange,String] exchange Exchange to unbind from 141 | # @param [Hash] options Binding properties 142 | # 143 | # @option options [String] :routing_key Routing key 144 | # @option options [Hash] :arguments ({}) Additional optional binding arguments 145 | # 146 | # @see http://rubymarchhare.info/articles/queues.html Queues and Consumers guide 147 | # @see http://rubymarchhare.info/articles/bindings.html Bindings guide 148 | def unbind(exchange, options={}) 149 | exchange_name = if exchange.respond_to?(:name) then 150 | exchange.name 151 | else 152 | exchange.to_s 153 | end 154 | @channel.queue_unbind(@name, exchange_name, options.fetch(:routing_key, '')) 155 | 156 | binding = { :exchange => exchange_name, :routing_key => (options[:routing_key] || options[:key] || ""), :arguments => options[:arguments] } 157 | @bindings.delete(binding) unless @bindings.include?(binding) 158 | 159 | self 160 | end 161 | 162 | # Deletes the queue 163 | # 164 | # @option [Boolean] if_unused (false) Should this queue be deleted only if it has no consumers? 165 | # @option [Boolean] if_empty (false) Should this queue be deleted only if it has no messages? 166 | # 167 | # @see http://rubymarchhare.info/articles/queues.html Queues and Consumers guide 168 | def delete(if_unused = false, if_empty = false) 169 | @channel.queue_delete(@name, if_unused, if_empty) 170 | end 171 | 172 | # Purges a queue (removes all messages from it) 173 | # @see http://rubymarchhare.info/articles/queues.html Queues and Consumers guide 174 | # @api public 175 | def purge 176 | @channel.queue_purge(@name) 177 | end 178 | 179 | def get(options = {}) 180 | response = @channel.basic_get(@name, !options.fetch(:ack, false)) 181 | 182 | if response 183 | [Headers.new(@channel, nil, response.envelope, response.props), String.from_java_bytes(response.body)] 184 | else 185 | nil 186 | end 187 | end 188 | alias pop get 189 | 190 | def build_consumer(opts = {}, &block) 191 | if opts[:block] || opts[:blocking] 192 | BlockingCallbackConsumer.new(@channel, self, opts[:buffer_size], opts, block) 193 | else 194 | CallbackConsumer.new(@channel, self, opts, block) 195 | end 196 | end 197 | 198 | # Adds a consumer to the queue (subscribes for message deliveries). 199 | # 200 | # @param [Hash] opts Options 201 | # 202 | # @option opts [Boolean] :manual_ack (false) Will this consumer use manual acknowledgements? 203 | # @option opts [Boolean] :exclusive (false) Should this consumer be exclusive for this queue? 204 | # @option opts [#call] :on_cancellation Block to execute when this consumer is cancelled remotely (e.g. via the RabbitMQ Management plugin) 205 | # @option opts [String] :consumer_tag Unique consumer identifier. It is usually recommended to let MarchHare generate it for you. 206 | # @option opts [Hash] :arguments ({}) Additional (optional) arguments, typically used by RabbitMQ extensions 207 | # 208 | # @see http://rubymarchhare.info/articles/queues.html Queues and Consumers guide 209 | # @api public 210 | def subscribe(opts = {}, &block) 211 | subscribe_with(build_consumer(opts, &block), opts) 212 | end 213 | 214 | def subscribe_with(consumer, opts = {}) 215 | @consumer_tag = @channel.basic_consume(@name, !(opts[:ack] || opts[:manual_ack]), opts[:consumer_tag], consumer) 216 | consumer.consumer_tag = @consumer_tag 217 | 218 | @default_consumer = consumer 219 | @channel.register_consumer(@consumer_tag, consumer) 220 | 221 | consumer.start 222 | consumer 223 | end 224 | 225 | # @return [Array] A pair with information about the number of queue messages and consumers 226 | # @see #message_count 227 | # @see #consumer_count 228 | def status 229 | response = @channel.queue_declare_passive(@name) 230 | [response.message_count, response.consumer_count] 231 | end 232 | 233 | # @return [Integer] How many messages the queue has ready (e.g. not delivered but not unacknowledged) 234 | def message_count 235 | response = @channel.queue_declare_passive(@name) 236 | response.message_count 237 | end 238 | 239 | # @return [Integer] How many active consumers the queue has 240 | def consumer_count 241 | response = @channel.queue_declare_passive(@name) 242 | response.consumer_count 243 | end 244 | 245 | # Publishes a message to the queue via default exchange. Takes the same arguments 246 | # as {MarchHare::Exchange#publish} 247 | # 248 | # @see MarchHare::Exchange#publish 249 | # @see MarchHare::Channel#default_exchange 250 | def publish(payload, opts = {}) 251 | @channel.default_exchange.publish(payload, opts.merge(:routing_key => @name)) 252 | 253 | self 254 | end 255 | 256 | 257 | # 258 | # Implementation 259 | # 260 | 261 | def self.verify_type!(args0 = {}) 262 | # be extra defensive 263 | args = args0 || {} 264 | q_type = args["x-queue-type"] || args[:"x-queue-type"] 265 | throw ArgumentError.new( 266 | "unsupported queue type #{q_type.inspect}, supported ones: #{Types::KNOWN.join(', ')}") if (q_type and !Types.known?(q_type)) 267 | end 268 | 269 | def verify_type!(args) 270 | self.class.verify_type!(args) 271 | end 272 | 273 | # @return [Boolean] true if this queue is a pre-defined one (amq.direct, amq.fanout, amq.match and so on) 274 | def predefined? 275 | @name.start_with?("amq.") 276 | end 277 | 278 | # @private 279 | def declare! 280 | @channel.logger.debug("queue: declare! #{@name}, type: #{@type}") 281 | response = if @options[:passive] 282 | then @channel.queue_declare_passive(@name) 283 | else @channel.queue_declare(@name, @durable, @exclusive, @auto_delete, @arguments) 284 | end 285 | @name = response.queue 286 | end 287 | 288 | # @private 289 | def recover_from_network_failure 290 | if self.server_named? 291 | old_name = @name.dup 292 | @name = "" 293 | 294 | @channel.deregister_queue_named(old_name) 295 | end 296 | 297 | declare! if !predefined? 298 | 299 | @channel.register_queue(self) 300 | recover_bindings 301 | end 302 | 303 | # @private 304 | def recover_bindings 305 | @bindings.each do |b| 306 | @channel.logger.debug("Recovering binding #{b.inspect}") 307 | self.bind(b[:exchange], b) 308 | end 309 | end 310 | end # Queue 311 | end # MarchHare 312 | -------------------------------------------------------------------------------- /lib/march_hare/shutdown_listener.rb: -------------------------------------------------------------------------------- 1 | module MarchHare 2 | class ShutdownListener 3 | include com.rabbitmq.client.ShutdownListener 4 | 5 | def initialize(entity, &block) 6 | # connection or channel 7 | @entity = entity 8 | @block = block 9 | end 10 | 11 | def shutdown_completed(cause) 12 | @block.call(@entity, cause) 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /lib/march_hare/thread_pools.rb: -------------------------------------------------------------------------------- 1 | require "march_hare/juc" 2 | 3 | module MarchHare 4 | # A slighly more Ruby developer-friendly way of instantiating various 5 | # JDK executors (thread pools). 6 | class ThreadPools 7 | # Returns a new thread pool (JDK executor) of a fixed size. 8 | # 9 | # @param [Integer] n Number of threads to use 10 | # 11 | # @option options [Boolean] :use_daemon_threads (false) Should new threads be marked as daemon? 12 | # 13 | # @return A thread pool (JDK executor) 14 | def self.fixed_of_size(n, use_daemon_threads: false) 15 | raise ArgumentError.new("n must be a positive integer!") unless Integer === n 16 | raise ArgumentError.new("n must be a positive integer!") unless n > 0 17 | 18 | if use_daemon_threads 19 | JavaConcurrent::Executors.new_fixed_thread_pool(n, &daemon_thread_factory) 20 | else 21 | JavaConcurrent::Executors.new_fixed_thread_pool(n) 22 | end 23 | end 24 | 25 | # Returns a new thread pool (JDK executor) of a fixed size of 1. 26 | # 27 | # @option options [Boolean] :use_daemon_threads (false) Should new threads be marked as daemon? 28 | # 29 | # @return A thread pool (JDK executor) 30 | def self.single_threaded(use_daemon_threads: false) 31 | if use_daemon_threads 32 | JavaConcurrent::Executors.new_single_thread_executor(&daemon_thread_factory) 33 | else 34 | JavaConcurrent::Executors.new_single_thread_executor 35 | end 36 | end 37 | 38 | # Returns a new thread pool (JDK executor) that will create new 39 | # threads as needed. 40 | # 41 | # @option options [Boolean] :use_daemon_threads (false) Should new threads be marked as daemon? 42 | # 43 | # @return A thread pool (JDK executor) 44 | def self.dynamically_growing(use_daemon_threads: false) 45 | if use_daemon_threads 46 | JavaConcurrent::Executors.new_cached_thread_pool(&daemon_thread_factory) 47 | else 48 | JavaConcurrent::Executors.new_cached_thread_pool 49 | end 50 | end 51 | 52 | # Returns a new thread factory that creates daemon threads. 53 | # 54 | # @option options [ThreadFactory] :base_factory Upstream thread factory used to create threads 55 | # 56 | # @return A thread factory 57 | def self.daemon_thread_factory(base_factory: JavaConcurrent::Executors.default_thread_factory) 58 | proc { |runnable| 59 | base_factory.new_thread(runnable).tap { |t| t.daemon = true } 60 | } 61 | end 62 | end 63 | end 64 | -------------------------------------------------------------------------------- /lib/march_hare/version.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | module MarchHare 4 | VERSION = "4.9.0-dev" 5 | end 6 | -------------------------------------------------------------------------------- /lib/march_hare/versioned_delivery_tag.rb: -------------------------------------------------------------------------------- 1 | module MarchHare 2 | # Wraps a delivery tag (which is an integer) so that {Bunny::Channel} could 3 | # detect stale tags after connection recovery. 4 | # 5 | # @private 6 | class VersionedDeliveryTag 7 | attr_reader :tag 8 | attr_reader :version 9 | 10 | def initialize(tag, version) 11 | raise ArgumentError.new("tag cannot be nil") unless tag 12 | raise ArgumentError.new("version cannot be nil") unless version 13 | 14 | @tag = tag 15 | @version = version 16 | end 17 | 18 | def to_i 19 | @tag 20 | end 21 | 22 | def stale?(version) 23 | raise ArgumentError.new("version cannot be nil") unless version 24 | 25 | @version < version 26 | end 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /march_hare.gemspec: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | $: << File.expand_path('../lib', __FILE__) 4 | 5 | require 'march_hare/version' 6 | 7 | 8 | Gem::Specification.new do |s| 9 | s.name = 'march_hare' 10 | s.version = MarchHare::VERSION 11 | s.platform = 'java' 12 | s.authors = ['Theo Hultberg', 'Michael S. Klishin'] 13 | s.email = ['theo@burtcorp.com', 'michael@rabbitmq.com'] 14 | s.homepage = 'https://github.com/ruby-amqp/march_hare' 15 | s.summary = %q{RabbitMQ client for JRuby built around the official RabbitMQ Java client} 16 | s.description = %q{RabbitMQ client for JRuby built around the official RabbitMQ Java client} 17 | 18 | s.files = `git ls-files -- lib`.split("\n") 19 | 20 | s.require_paths = %w(lib) 21 | end 22 | -------------------------------------------------------------------------------- /repl: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | irb -Ilib -r'march_hare' 4 | -------------------------------------------------------------------------------- /spec/higher_level_api/integration/alternate_exchanges_spec.rb: -------------------------------------------------------------------------------- 1 | RSpec.describe "Any exchange" do 2 | let(:connection) { MarchHare.connect } 3 | 4 | after :each do 5 | connection.close 6 | end 7 | 8 | 9 | # 10 | # Examples 11 | # 12 | 13 | it "can have an alternate exchange (a RabbitMQ-specific extension to AMQP 0.9.1)" do 14 | ch = connection.create_channel 15 | ch.confirm_select 16 | q = ch.queue("", :exclusive => true) 17 | 18 | fe = ch.fanout("march_hare.extensions.alternate_xchanges.fanout1") 19 | de = ch.direct("march_hare.extensions.alternate_xchanges.direct1", :arguments => { 20 | "alternate-exchange" => fe.name 21 | }) 22 | 23 | q.bind(fe) 24 | de.publish("1010", :routing_key => "", :mandatory => true) 25 | ch.wait_for_confirms(1000) 26 | 27 | expect(q.message_count).to eq(1) 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /spec/higher_level_api/integration/basic_ack_spec.rb: -------------------------------------------------------------------------------- 1 | RSpec.describe MarchHare::Channel, "#ack" do 2 | let(:connection) { MarchHare.connect } 3 | 4 | after :each do 5 | connection.close 6 | end 7 | 8 | context "with a valid (known) delivery tag" do 9 | let(:ch) { connection.create_channel } 10 | 11 | it "acknowledges a message" do 12 | q = ch.queue("hotbunnies.basic.ack.manual-acks", :exclusive => true) 13 | x = ch.default_exchange 14 | 15 | x.publish("bunneth", :routing_key => q.name) 16 | sleep(0.25) 17 | expect(q.message_count).to eq(1) 18 | meta, _content = q.pop(:ack => true) 19 | 20 | ch.ack(meta.delivery_tag, true) 21 | expect(meta.delivery_tag.to_i).to eq(1) 22 | 23 | sleep(0.25) 24 | expect(q.message_count).to eq(0) 25 | 26 | expect(ch).to be_open 27 | ch.close 28 | end 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /spec/higher_level_api/integration/basic_cancel_spec.rb: -------------------------------------------------------------------------------- 1 | RSpec.describe 'A consumer' do 2 | let(:connection) { MarchHare.connect } 3 | 4 | after :each do 5 | connection.close 6 | end 7 | 8 | context "that does not block the caller" do 9 | it 'receives messages until cancelled' do 10 | x = connection.create_channel.default_exchange 11 | q = connection.create_channel.queue("", :exclusive => true) 12 | 13 | messages = [] 14 | consumer_exited = false 15 | consumer = nil 16 | 17 | consumer_thread = Thread.new do 18 | consumer = q.subscribe do |headers, message| 19 | messages << message 20 | sleep 0.1 21 | end 22 | consumer_exited = true 23 | end 24 | 25 | publisher_thread = Thread.new do 26 | 20.times do 27 | x.publish('hello world', :routing_key => q.name) 28 | end 29 | end 30 | 31 | sleep 0.2 32 | 33 | consumer.cancel 34 | 35 | consumer_thread.join 36 | publisher_thread.join 37 | 38 | expect(messages).not_to be_empty 39 | expect(consumer_exited).to eq(true) 40 | end 41 | end 42 | 43 | context "that DOES block the caller" do 44 | it 'receives messages until cancelled' do 45 | x = connection.create_channel.default_exchange 46 | q = connection.create_channel.queue("", :exclusive => true) 47 | 48 | messages = [] 49 | consumer_exited = false 50 | consumer = nil 51 | 52 | consumer_thread = Thread.new do 53 | consumer = q.build_consumer do |headers, message| 54 | messages << message 55 | sleep 0.1 56 | end 57 | q.subscribe_with(consumer, :block => true) 58 | consumer_exited = true 59 | end 60 | 61 | publisher_thread = Thread.new do 62 | 20.times do 63 | x.publish('hello world', :routing_key => q.name) 64 | end 65 | end 66 | 67 | sleep 0.5 68 | 69 | consumer.cancel 70 | 71 | consumer_thread.join 72 | publisher_thread.join 73 | 74 | expect(messages).not_to be_empty 75 | expect(consumer_exited).to eq(true) 76 | end 77 | end 78 | 79 | context "that DOES block the caller and never receives any messages" do 80 | it 'can be cancelled' do 81 | q = connection.create_channel.queue("", :exclusive => true) 82 | 83 | consumer_exited = false 84 | consumer = nil 85 | 86 | consumer_thread = Thread.new do 87 | co = q.build_consumer(:block => true) do |headers, message| 88 | messages << message 89 | sleep 0.1 90 | end 91 | 92 | consumer = co 93 | q.subscribe_with(co, :block => true) 94 | consumer_exited = true 95 | end 96 | 97 | sleep 1.0 98 | 99 | consumer.cancel 100 | 101 | consumer_thread.join 102 | expect(consumer_exited).to eq(true) 103 | end 104 | end 105 | 106 | context 'that is cancelled' do 107 | it 'will not raise errors when cancelled again' do 108 | queue = connection.create_channel.queue('') 109 | consumer = queue.build_consumer(:block => true) { |headers, message| } 110 | thread = Thread.new do 111 | queue.subscribe_with(consumer, :block => true) 112 | end 113 | sleep 1 114 | begin 115 | consumer.cancel 116 | consumer.cancel 117 | rescue NativeException => e 118 | raise e 119 | ensure 120 | thread.join 121 | end 122 | end 123 | end 124 | end 125 | -------------------------------------------------------------------------------- /spec/higher_level_api/integration/basic_consume_spec.rb: -------------------------------------------------------------------------------- 1 | RSpec.describe "A consumer" do 2 | let(:connection) { MarchHare.connect } 3 | 4 | after :each do 5 | connection.close 6 | end 7 | 8 | it "provides predicates" do 9 | ch = connection.create_channel 10 | q = ch.queue("", :exclusive => true) 11 | 12 | consumer = q.subscribe(:blocking => false) { |_, _| nil } 13 | 14 | # consumer tag will be sent by the broker, so this happens 15 | # asynchronously and we can either add callbacks/use latches or 16 | # just wait. MK. 17 | sleep(1.0) 18 | expect(consumer).to be_active 19 | 20 | consumer.cancel 21 | sleep(1.0) 22 | expect(consumer).not_to be_active 23 | expect(consumer).to be_cancelled 24 | end 25 | 26 | it "has a consumer_tag" do 27 | ch = connection.create_channel 28 | q = ch.queue("", :exclusive => true) 29 | 30 | consumer1 = q.subscribe(:blocking => false) { |_, _| nil } 31 | 32 | sleep(1.0) 33 | expect(consumer1.consumer_tag).to match(/^amq.ctag/) 34 | consumer1.cancel 35 | 36 | custom_consumer_tag = "unique_consumer_tag_#{rand(1_000)}" 37 | consumer2 = q.subscribe(:consumer_tag => custom_consumer_tag, :blocking => false) { |_, _| nil } 38 | 39 | expect(consumer2.consumer_tag).to eq(custom_consumer_tag) 40 | 41 | consumer2.cancel 42 | end 43 | end 44 | 45 | 46 | RSpec.describe "Multiple non-exclusive consumers per queue" do 47 | let(:connection) { MarchHare.connect } 48 | 49 | after :each do 50 | connection.close 51 | end 52 | 53 | context "on the same channel (so prefetch levels won't affect message distribution)" do 54 | it "have messages distributed to them in the round robin manner" do 55 | ch = connection.create_channel 56 | 57 | n = 100 58 | mailbox1 = [] 59 | mailbox2 = [] 60 | mailbox3 = [] 61 | 62 | all_received = java.util.concurrent.CountDownLatch.new(n) 63 | consumer_ch = connection.create_channel 64 | 65 | q = consumer_ch.queue("", :exclusive => true) 66 | 67 | consumer1 = q.subscribe(:blocking => false) do |metadata, payload| 68 | mailbox1 << payload 69 | all_received.count_down 70 | end 71 | consumer2 = q.subscribe(:blocking => false) do |metadata, payload| 72 | mailbox2 << payload 73 | all_received.count_down 74 | end 75 | consumer3 = q.subscribe(:blocking => false) do |metadata, payload| 76 | mailbox3 << payload 77 | all_received.count_down 78 | end 79 | 80 | 81 | sleep 1.0 # let consumers in other threads start. 82 | n.times do |i| 83 | ch.default_exchange.publish("Message #{i}", :routing_key => q.name) 84 | end 85 | 86 | all_received.await(10, java.util.concurrent.TimeUnit::SECONDS) 87 | 88 | expect(mailbox1.size).to be >= 33 89 | expect(mailbox2.size).to be >= 33 90 | expect(mailbox3.size).to be >= 33 91 | 92 | consumer1.cancel 93 | consumer2.cancel 94 | consumer3.cancel 95 | end 96 | end 97 | end 98 | 99 | RSpec.describe "A consumer" do 100 | let(:connection) { MarchHare.connect } 101 | 102 | after :each do 103 | connection.close 104 | end 105 | 106 | context "instantiated manually" do 107 | it "works just like MarchHare::Queue#subscribe" do 108 | ch = connection.create_channel 109 | 110 | n = 100 111 | mailbox1 = [] 112 | mailbox2 = [] 113 | mailbox3 = [] 114 | 115 | all_received = java.util.concurrent.CountDownLatch.new(n) 116 | consumer_ch = connection.create_channel 117 | 118 | q = consumer_ch.queue("", :exclusive => true) 119 | 120 | fn = lambda do |metadata, payload| 121 | mailbox1 << payload 122 | all_received.count_down 123 | end 124 | consumer_object = q.build_consumer(:blocking => false, &fn) 125 | 126 | consumer1 = q.subscribe_with(consumer_object, :blocking => false) 127 | consumer2 = q.subscribe(:blocking => false) do |metadata, payload| 128 | mailbox2 << payload 129 | all_received.count_down 130 | end 131 | consumer3 = q.subscribe(:blocking => false) do |metadata, payload| 132 | mailbox3 << payload 133 | all_received.count_down 134 | end 135 | 136 | 137 | sleep 1.0 # let consumers in other threads start. 138 | n.times do |i| 139 | ch.default_exchange.publish("Message #{i}", :routing_key => q.name) 140 | end 141 | 142 | all_received.await(10, java.util.concurrent.TimeUnit::SECONDS) 143 | 144 | expect(mailbox1.size).to be >= 33 145 | expect(mailbox2.size).to be >= 33 146 | expect(mailbox3.size).to be >= 33 147 | 148 | consumer1.cancel 149 | consumer2.cancel 150 | consumer3.cancel 151 | end 152 | end 153 | 154 | describe "header consumption" do 155 | let(:connection) { MarchHare.connect } 156 | let(:channel) { connection.create_channel } 157 | 158 | it "should convert long headers" do 159 | queue = channel.temporary_queue() 160 | sleep(1) 161 | 162 | expected_short = "short" 163 | expected_long = "l"*512 164 | expected_complex = "c"*1024 165 | queue.publish("hello", :headers => { 166 | :long => expected_long, 167 | :short => expected_short, 168 | :complex => {:foo => [{:bar => expected_complex}]} 169 | }) 170 | delivery = nil 171 | latch = java.util.concurrent.CountDownLatch.new(1) 172 | 173 | queue.subscribe do |metadata, message| 174 | delivery = {:metadata => metadata, :message => message} 175 | latch.count_down 176 | end 177 | 178 | latch.await(10, java.util.concurrent.TimeUnit::SECONDS) 179 | 180 | headers = delivery[:metadata].headers 181 | expect(headers["short"]).to eq(expected_short) 182 | expect(headers["long"]).to eq(expected_long) 183 | expect(headers["complex"]["foo"][0]["bar"]).to eq(expected_complex) 184 | end 185 | end 186 | end 187 | -------------------------------------------------------------------------------- /spec/higher_level_api/integration/basic_reject_spec.rb: -------------------------------------------------------------------------------- 1 | RSpec.describe MarchHare::Channel, "#reject" do 2 | let(:connection) { MarchHare.connect } 3 | 4 | after :each do 5 | connection.close 6 | end 7 | 8 | context "with a valid (known) delivery tag" do 9 | let(:ch) { connection.create_channel } 10 | 11 | context "with requeue = true" do 12 | it "requeues a message" do 13 | q = ch.queue("bunny.basic.reject.manual-acks", :exclusive => true) 14 | x = ch.default_exchange 15 | 16 | x.publish("bunneth", :routing_key => q.name) 17 | sleep(0.5) 18 | expect(q.message_count).to eq(1) 19 | meta, _ = q.pop(:ack => true) 20 | 21 | ch.reject(meta.delivery_tag, true) 22 | sleep(0.5) 23 | expect(q.message_count).to eq(1) 24 | 25 | ch.close 26 | end 27 | end 28 | 29 | context "with requeue = false" do 30 | it "rejects a message" do 31 | q = ch.queue("bunny.basic.reject.with-requeue-false", :exclusive => true) 32 | x = ch.default_exchange 33 | 34 | x.publish("bunneth", :routing_key => q.name) 35 | sleep(0.5) 36 | expect(q.message_count).to eq(1) 37 | delivery_info, _, _ = q.pop(:ack => true) 38 | 39 | ch.reject(delivery_info.delivery_tag, false) 40 | sleep(0.5) 41 | expect(q.message_count).to eq(0) 42 | 43 | ch.close 44 | end 45 | end 46 | end 47 | end 48 | -------------------------------------------------------------------------------- /spec/higher_level_api/integration/channel_close_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | RSpec.describe "A channel" do 4 | 5 | # 6 | # Environment 7 | # 8 | 9 | let(:connection) { MarchHare.connect } 10 | 11 | after :each do 12 | connection.close 13 | end 14 | 15 | # 16 | # Examples 17 | # 18 | 19 | context "when closed" do 20 | it "releases the id" do 21 | ch = connection.create_channel 22 | n = ch.number 23 | 24 | expect(ch).to be_open 25 | ch.close 26 | expect(ch).to be_closed 27 | 28 | # a new channel with the same id can be created 29 | ch2 = connection.create_channel(n) 30 | ch2.close 31 | end 32 | end 33 | 34 | context "when instructed to cancel consumers before closing" do 35 | it "releases the id" do 36 | ch = connection.create_channel.configure do |new_ch| 37 | new_ch.cancel_consumers_before_closing! 38 | end 39 | n = ch.number 40 | 41 | 10.times do |i| 42 | q = ch.temporary_queue() 43 | q.subscribe(manual_ack: true) do |delivery_info, properties, payload| 44 | # a no-op 45 | end 46 | end 47 | 48 | expect(ch).to be_open 49 | ch.close 50 | expect(ch).to be_closed 51 | 52 | # a new channel with the same id can be created 53 | ch2 = connection.create_channel(n) 54 | ch2.close 55 | end 56 | end 57 | end 58 | -------------------------------------------------------------------------------- /spec/higher_level_api/integration/connection_recovery_spec.rb: -------------------------------------------------------------------------------- 1 | require "rabbitmq/http/client" 2 | 3 | RSpec.describe "Connection recovery" do 4 | before :all do 5 | @connections = [] 6 | end 7 | 8 | let(:http_client) { RabbitMQ::HTTP::Client.new("http://127.0.0.1:15672") } 9 | let(:amqp_uri) { "amqp://localhost:5672/%2F" } 10 | 11 | def close_all_connections! 12 | # wait for stats to refresh, make sure to run bin/ci/before_build.sh as well! 13 | sleep 1.7 14 | http_client.list_connections.each do |conn_info| 15 | puts "Closing connection #{conn_info.name}..." 16 | http_client.close_connection(conn_info.name) 17 | end 18 | 19 | # wait up to a few seconds 20 | n = 50 21 | while http_client.list_connections.size > 0 && n > 0 22 | sleep 0.1 23 | n = (n - 1) 24 | end 25 | end 26 | 27 | def wait_for_recovery 28 | # wait for stats to refresh, make sure to run bin/ci/before_build.sh as well! 29 | sleep 0.7 30 | 31 | # wait up to a few seconds 32 | n = 50 33 | while http_client.list_connections.size == 0 && n > 0 34 | sleep 0.1 35 | n = (n - 1) 36 | end 37 | # wait for recovery to finish on the client side 38 | sleep 1.0 39 | end 40 | 41 | def with_open(c = MarchHare.connect(network_recovery_interval: 0.6), &block) 42 | @connections << c 43 | begin 44 | block.call(c) 45 | ensure 46 | c.close if c.open? 47 | end 48 | end 49 | 50 | def with_open_uri(c = MarchHare.connect(uri: amqp_uri, network_recovery_interval: 0.6), &block) 51 | @connections << c 52 | begin 53 | block.call(c) 54 | ensure 55 | c.close if c.open? 56 | end 57 | end 58 | 59 | def close_all_outstanding_connections 60 | xs = @connections.select { |c| c.open? } 61 | return if xs.size.zero? 62 | puts "Recovery example group: have #{xs.size} tracked connections to ensure closed..." 63 | xs.each do |c| 64 | c.close if c.open? 65 | end 66 | end 67 | 68 | after :each do 69 | close_all_outstanding_connections 70 | end 71 | 72 | after :all do 73 | close_all_outstanding_connections 74 | end 75 | 76 | def ensure_queue_recovery(ch, q) 77 | q.purge 78 | x = ch.default_exchange 79 | x.publish("msg", routing_key: q.name) 80 | sleep 0.2 81 | expect(q.message_count).to eq(1) 82 | q.purge 83 | end 84 | 85 | def ensure_queue_binding_recovery(x, q, routing_key = "") 86 | q.purge 87 | x.publish("msg", routing_key: routing_key) 88 | sleep 0.2 89 | expect(q.message_count).to eq(1) 90 | q.purge 91 | end 92 | 93 | def ensure_exchange_binding_recovery(ch, source, destination, routing_key = "") 94 | q = ch.queue("", exclusive: true) 95 | q.bind(destination, routing_key: routing_key) 96 | 97 | source.publish("msg", routing_key: routing_key) 98 | ch.wait_for_confirms 99 | expect(q.message_count).to eq(1) 100 | q.delete 101 | end 102 | 103 | # 104 | # Examples 105 | # 106 | 107 | it "reconnects after a grace period" do 108 | with_open do |c| 109 | close_all_connections! 110 | 111 | wait_for_recovery 112 | expect(c).to be_open 113 | 114 | c.close 115 | end 116 | end 117 | 118 | it "when connecting with a URI, it reconnects after a grace period" do 119 | with_open_uri do |c| 120 | close_all_connections! 121 | 122 | wait_for_recovery 123 | expect(c).to be_open 124 | 125 | c.close 126 | end 127 | end 128 | 129 | it "recovers channel" do 130 | with_open do |c| 131 | ch1 = c.create_channel 132 | ch2 = c.create_channel 133 | close_all_connections! 134 | 135 | wait_for_recovery 136 | expect(ch1).to be_open 137 | expect(ch2).to be_open 138 | 139 | c.close 140 | end 141 | end 142 | 143 | it "recovers basic.qos prefetch setting" do 144 | with_open do |c| 145 | ch = c.create_channel 146 | ch.prefetch = 11 147 | expect(ch.prefetch).to eq(11) 148 | close_all_connections! 149 | 150 | wait_for_recovery 151 | expect(ch).to be_open 152 | expect(ch.prefetch).to eq(11) 153 | 154 | c.close 155 | end 156 | end 157 | 158 | 159 | it "recovers publisher confirms setting" do 160 | with_open do |c| 161 | ch = c.create_channel 162 | ch.confirm_select 163 | expect(ch).to be_using_publisher_confirms 164 | close_all_connections! 165 | 166 | wait_for_recovery 167 | expect(ch).to be_open 168 | expect(ch).to be_using_publisher_confirms 169 | 170 | c.close 171 | end 172 | end 173 | 174 | it "recovers transactionality setting" do 175 | with_open do |c| 176 | ch = c.create_channel 177 | ch.tx_select 178 | expect(ch).to be_using_tx 179 | close_all_connections! 180 | 181 | wait_for_recovery 182 | expect(ch).to be_open 183 | expect(ch).to be_using_tx 184 | 185 | c.close 186 | end 187 | end 188 | 189 | it "recovers client-named queues" do 190 | with_open do |c| 191 | ch = c.create_channel 192 | q = ch.queue("bunny.tests.recovery.client-named#{rand}") 193 | close_all_connections! 194 | 195 | wait_for_recovery 196 | expect(ch).to be_open 197 | ensure_queue_recovery(ch, q) 198 | q.delete 199 | 200 | c.close 201 | end 202 | end 203 | 204 | 205 | it "recovers server-named queues" do 206 | with_open do |c| 207 | ch = c.create_channel 208 | q = ch.queue("", exclusive: true) 209 | close_all_connections! 210 | 211 | wait_for_recovery 212 | expect(ch).to be_open 213 | ensure_queue_recovery(ch, q) 214 | 215 | c.close 216 | end 217 | end 218 | 219 | it "recovers queue bindings" do 220 | with_open do |c| 221 | ch = c.create_channel 222 | x = ch.fanout("amq.fanout") 223 | q = ch.queue("", exclusive: true) 224 | q.bind(x) 225 | close_all_connections! 226 | 227 | wait_for_recovery 228 | expect(ch).to be_open 229 | ensure_queue_binding_recovery(x, q) 230 | 231 | c.close 232 | end 233 | end 234 | 235 | it "recovers exchange bindings" do 236 | with_open do |c| 237 | ch = c.create_channel 238 | ch.confirm_select 239 | x = ch.fanout("amq.fanout") 240 | x2 = ch.fanout("bunny.tests.recovery.fanout") 241 | x2.bind(x) 242 | close_all_connections! 243 | 244 | wait_for_recovery 245 | expect(ch).to be_open 246 | ensure_exchange_binding_recovery(ch, x, x2) 247 | 248 | c.close 249 | end 250 | end 251 | 252 | it "recovers consumers" do 253 | with_open do |c| 254 | delivered = false 255 | 256 | ch = c.create_channel 257 | q = ch.queue("", exclusive: true) 258 | q.subscribe do |_, _, _| 259 | delivered = true 260 | end 261 | close_all_connections! 262 | 263 | wait_for_recovery 264 | expect(ch).to be_open 265 | 266 | q.publish("") 267 | sleep 0.5 268 | expect(delivered).to eq(true) 269 | 270 | c.close 271 | end 272 | end 273 | 274 | it "recovers all consumers" do 275 | n = 64 276 | 277 | with_open do |c| 278 | ch = c.create_channel 279 | q = ch.queue("", exclusive: true) 280 | n.times do 281 | q.subscribe { |_, _, _| } 282 | end 283 | close_all_connections! 284 | 285 | wait_for_recovery 286 | expect(ch).to be_open 287 | 288 | expect(q.consumer_count).to eq(n) 289 | 290 | c.close 291 | end 292 | end 293 | 294 | it "recovers all queues" do 295 | n = 64 296 | 297 | qs = [] 298 | 299 | with_open do |c| 300 | ch = c.create_channel 301 | 302 | n.times do 303 | qs << ch.queue("", exclusive: true) 304 | end 305 | close_all_connections! 306 | 307 | wait_for_recovery 308 | expect(ch).to be_open 309 | 310 | qs.each do |q| 311 | ch.queue_declare_passive(q.name) 312 | end 313 | 314 | c.close 315 | end 316 | end 317 | 318 | it 'fires on_recovery_start hooks' do 319 | with_open do |c| 320 | recovery_start_hook_called = false 321 | c.on_recovery_start do |session| 322 | recovery_start_hook_called = true 323 | expect(session).to eq(c) 324 | end 325 | 326 | close_all_connections! 327 | wait_for_recovery 328 | 329 | expect(recovery_start_hook_called).to be true 330 | end 331 | end 332 | 333 | it 'fires on_recovery hooks' do 334 | with_open do |c| 335 | recovery_hook_called = false 336 | 337 | c.on_recovery do |session| 338 | recovery_hook_called = true 339 | expect(session).to eq(c) 340 | end 341 | 342 | close_all_connections! 343 | wait_for_recovery 344 | 345 | expect(recovery_hook_called).to be true 346 | end 347 | end 348 | end 349 | -------------------------------------------------------------------------------- /spec/higher_level_api/integration/connection_spec.rb: -------------------------------------------------------------------------------- 1 | java_import java.util.concurrent.CountDownLatch 2 | java_import java.util.concurrent.TimeUnit 3 | 4 | RSpec.describe "MarchHare.connect" do 5 | 6 | # 7 | # Examples 8 | # 9 | 10 | it "lets you specify requested heartbeat interval" do 11 | c = MarchHare.connect(requested_heartbeat: 10) 12 | c.close 13 | end 14 | 15 | it "lets you specify connection timeout interval" do 16 | c = MarchHare.connect(connection_timeout: 3) 17 | c.close 18 | end 19 | 20 | context "when connection fails due to unknown host" do 21 | it "raises an exception" do 22 | expect { 23 | MarchHare.connect(hostname: "a8s878787s8d78sd78.lol") 24 | }.to raise_error(MarchHare::ConnectionRefused) 25 | end 26 | end 27 | 28 | context "when connection fails due to RabbitMQ node not running" do 29 | it "raises an exception" do 30 | expect { 31 | MarchHare.connect(hostname: "localhost", port: 65534) 32 | }.to raise_error(MarchHare::ConnectionRefused) 33 | end 34 | end 35 | 36 | context "when connection fails due to invalid credentials" do 37 | it "raises an exception" do 38 | expect { 39 | MarchHare.connect(:username => "this$username%does*not&exist") 40 | }.to raise_error(MarchHare::PossibleAuthenticationFailureError) 41 | end 42 | end 43 | 44 | context "when connection manually closed" do 45 | it "raises an exception on #automatically_recover" do 46 | c = MarchHare.connect(uri: "amqp://127.0.0.1") 47 | c.close 48 | expect { 49 | c.automatically_recover 50 | }.to raise_error(MarchHare::ConnectionClosedException) 51 | end 52 | end 53 | 54 | it "handles amqp:// URIs w/o path part" do 55 | c = MarchHare.connect(uri: "amqp://127.0.0.1") 56 | 57 | expect(c.vhost).to eq("/") 58 | expect(c.host).to eq("127.0.0.1") 59 | expect(c.port).to eq(5672) 60 | expect(c.ssl?).to eq(false) 61 | 62 | c.close 63 | end 64 | 65 | it "lets you specify executor (thread pool) factory" do 66 | calls = 0 67 | factory = double(:executor_factory) 68 | allow(factory).to receive(:call) do 69 | calls += 1 70 | MarchHare::JavaConcurrent::Executors.new_cached_thread_pool 71 | end 72 | c = MarchHare.connect(executor_factory: factory, network_recovery_interval: 0) 73 | c.close 74 | c.reopen 75 | c.close 76 | expect(calls).to eq(2) 77 | end 78 | 79 | 80 | it "lets you specify fixed thread pool size" do 81 | c = MarchHare.connect(thread_pool_size: 20, network_recovery_interval: 0) 82 | expect(c).to be_connected 83 | c.close 84 | expect(c).not_to be_connected 85 | c.reopen 86 | sleep 0.5 87 | expect(c).to be_connected 88 | c.close 89 | end 90 | 91 | it "lets you specify host and port" do 92 | expect { 93 | MarchHare.connect(host: "127.0.0.1", port: 35672, network_recovery_interval: 0) 94 | }.to raise_error(MarchHare::ConnectionRefused) 95 | 96 | c = MarchHare.connect(host: "127.0.0.1", port: 5672, network_recovery_interval: 0) 97 | expect(c).to be_connected 98 | c.close 99 | end 100 | 101 | it "lets you specify multiple hosts" do 102 | c = MarchHare.connect(hosts: ["127.0.0.1"], network_recovery_interval: 0) 103 | expect(c).to be_connected 104 | c.close 105 | expect(c).not_to be_connected 106 | c.reopen 107 | sleep 0.5 108 | expect(c).to be_connected 109 | c.close 110 | end 111 | 112 | it "lets you specify multiple addresses" do 113 | c = MarchHare.connect(addresses: ["127.0.0.1:5672", "127.0.0.1"], network_recovery_interval: 0) 114 | expect(c).to be_connected 115 | c.close 116 | expect(c).not_to be_connected 117 | c.reopen 118 | sleep 0.5 119 | expect(c).to be_connected 120 | c.close 121 | end 122 | 123 | it "lets you specify thread factory (e.g. for GAE)" do 124 | class ThreadFactory 125 | include java.util.concurrent.ThreadFactory 126 | 127 | def newThread(runnable) 128 | java.lang.Thread.new(runnable) 129 | end 130 | end 131 | 132 | c = MarchHare.connect(thread_factory: ThreadFactory.new) 133 | expect(c).to be_connected 134 | _ch = c.create_channel 135 | c.close 136 | end 137 | 138 | it "lets you specify a maximum inbound message size" do 139 | c = MarchHare.connect(max_inbound_message_body_size: 64000) 140 | expect(c).to be_connected 141 | c.close 142 | end 143 | 144 | 145 | it "lets you specify exception handler" do 146 | class ExceptionHandler < com.rabbitmq.client.impl.DefaultExceptionHandler 147 | include com.rabbitmq.client.ExceptionHandler 148 | 149 | def handleConsumerException(ch, ex, consumer, tag, method_name) 150 | # different from the default in that it does not print 151 | # anything. MK. 152 | end 153 | end 154 | 155 | c = MarchHare.connect(exception_handler: ExceptionHandler.new) 156 | ch = c.create_channel 157 | q = ch.queue("", exclusive: true) 158 | q.subscribe do |*args| 159 | raise "oops" 160 | end 161 | 162 | x = ch.default_exchange 163 | x.publish("", routing_key: q.name) 164 | sleep 0.5 165 | 166 | c.close 167 | end 168 | end 169 | 170 | 171 | RSpec.describe "MarchHare::Session#start" do 172 | it "is a no-op added for better compatibility with Bunny and to guard non-idempotent AMQConnection#start" do 173 | c = MarchHare.connect 174 | 100.times do 175 | c.start 176 | end 177 | 178 | c.close 179 | end 180 | end 181 | -------------------------------------------------------------------------------- /spec/higher_level_api/integration/consumer_cancellation_notification_spec.rb: -------------------------------------------------------------------------------- 1 | RSpec.describe "Non-blocking consumer" do 2 | let(:connection) do 3 | MarchHare.connect 4 | end 5 | 6 | after :each do 7 | connection.close if connection.open? 8 | end 9 | 10 | let(:queue_name) { "basic.consume#{rand}" } 11 | 12 | it "supports consumer cancellation notifications" do 13 | cancelled = false 14 | 15 | ch = connection.create_channel 16 | ch2 = connection.create_channel 17 | q = ch2.queue(queue_name, :auto_delete => true) 18 | 19 | q.subscribe(:on_cancellation => Proc.new { |_ch, consumer| cancelled = true }) do |_, _| 20 | # no-op 21 | end 22 | 23 | sleep 0.5 24 | x = ch.default_exchange 25 | x.publish("abc", :routing_key => queue_name) 26 | 27 | sleep 0.5 28 | ch.queue(queue_name, :auto_delete => true).delete 29 | 30 | sleep 0.5 31 | expect(cancelled).to eq(true) 32 | 33 | ch.close 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /spec/higher_level_api/integration/exchange_bind_spec.rb: -------------------------------------------------------------------------------- 1 | RSpec.describe MarchHare::Exchange do 2 | let(:connection) { MarchHare.connect } 3 | 4 | after :each do 5 | connection.close 6 | end 7 | 8 | it "should bind two exchanges using exchange instances" do 9 | ch = connection.create_channel 10 | source = ch.fanout("march_hare.spec.exchanges.source", :auto_delete => true) 11 | destination = ch.fanout("march_hare.spec.exchanges.destination", :auto_delete => true) 12 | 13 | queue = ch.queue("", :exclusive => true) 14 | queue.bind(destination) 15 | 16 | destination.bind(source) 17 | source.publish("") 18 | expect(queue.get).not_to be_nil 19 | end 20 | 21 | it "should bind two exchanges using exchange name" do 22 | ch = connection.create_channel 23 | source = ch.fanout("march_hare.spec.exchanges.source", :auto_delete => true) 24 | destination = ch.fanout("march_hare.spec.exchanges.destination", :auto_delete => true) 25 | 26 | queue = ch.queue("", :exclusive => true) 27 | queue.bind(destination) 28 | 29 | destination.bind(source.name) 30 | source.publish("") 31 | expect(queue.get).not_to be_nil 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /spec/higher_level_api/integration/exchange_declaration_failure_spec.rb: -------------------------------------------------------------------------------- 1 | require "rabbitmq/http/client" 2 | 3 | RSpec.describe "Exchange declaration error handling" do 4 | def with_open(c = MarchHare.connect(automatically_recover: false), &block) 5 | begin 6 | block.call(c) 7 | ensure 8 | c.close if c.open? 9 | end 10 | end 11 | 12 | # 13 | # Examples 14 | # 15 | 16 | it "does not throw Java exceptions" do 17 | with_open do |c| 18 | ch = c.create_channel 19 | 20 | expect do 21 | ch.direct("direct.exchange.exception.test", durable: false) 22 | ch.direct("direct.exchange.exception.test", durable: true) 23 | ch.direct("direct.exchange.exception.test", durable: false) 24 | ch.direct("direct.exchange.exception.test", durable: true) 25 | end.to raise_exception(MarchHare::PreconditionFailed) 26 | 27 | ch2 = c.create_channel 28 | ch2.exchange_delete("direct.exchange.exception.test") 29 | end 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /spec/higher_level_api/integration/exchange_declare_spec.rb: -------------------------------------------------------------------------------- 1 | RSpec.describe "Direct exchange" do 2 | let(:connection) { MarchHare.connect } 3 | let(:channel) { connection.create_channel } 4 | 5 | after :each do 6 | channel.close 7 | connection.close 8 | end 9 | 10 | it "can be declared" do 11 | exchange = channel.exchange("march.hare.exchanges.direct1", :type => :direct) 12 | queue = channel.queue("", :exclusive => true) 13 | 14 | queue.bind(exchange, :routing_key => "abc") 15 | 16 | exchange.publish("", :routing_key => "xyz") 17 | exchange.publish("", :routing_key => "abc") 18 | 19 | sleep(0.3) 20 | 21 | mc, _cc = queue.status 22 | expect(mc).to eq(1) 23 | end 24 | end 25 | 26 | 27 | 28 | RSpec.describe "Fanout exchange" do 29 | let(:connection) { MarchHare.connect } 30 | let(:channel) { connection.create_channel } 31 | 32 | after :each do 33 | channel.close 34 | connection.close 35 | end 36 | 37 | it "can be declared" do 38 | exchange = channel.exchange("march.hare.exchanges.fanout1", :type => :fanout) 39 | expect(exchange).not_to be_internal 40 | queue = channel.queue("", :exclusive => true) 41 | 42 | queue.bind(exchange) 43 | 44 | exchange.publish("") 45 | exchange.publish("", :routing_key => "xyz") 46 | exchange.publish("", :routing_key => "abc") 47 | 48 | sleep(0.5) 49 | 50 | mc, _cc = queue.status 51 | expect(mc).to eq(3) 52 | end 53 | end 54 | 55 | 56 | 57 | RSpec.describe "Topic exchange" do 58 | let(:connection) { MarchHare.connect } 59 | let(:channel) { connection.create_channel } 60 | 61 | after :each do 62 | channel.close 63 | connection.close 64 | end 65 | 66 | it "can be declared" do 67 | exchange = channel.exchange("march.hare.exchanges.topic1", :type => :topic) 68 | queue = channel.queue("", :exclusive => true) 69 | 70 | queue.bind(exchange, :routing_key => "log.*") 71 | 72 | exchange.publish("") 73 | exchange.publish("", :routing_key => "accounts.signup") 74 | exchange.publish("", :routing_key => "log.info") 75 | exchange.publish("", :routing_key => "log.warn") 76 | 77 | sleep(0.5) 78 | 79 | mc, _cc = queue.status 80 | expect(mc).to eq(2) 81 | end 82 | end 83 | 84 | 85 | 86 | RSpec.describe "Headers exchange" do 87 | let(:connection) { MarchHare.connect } 88 | let(:channel) { connection.create_channel } 89 | 90 | after :each do 91 | channel.close 92 | connection.close 93 | end 94 | 95 | it "can be declared" do 96 | exchange = channel.exchange("march.hare.exchanges.headers1", :type => :headers) 97 | queue = channel.queue("", :exclusive => true) 98 | 99 | queue.bind(exchange, :arguments => { 'x-match' => 'all', 'arch' => "x86_64", 'os' => "linux" }) 100 | 101 | exchange.publish "For linux/IA64", :properties => { :headers => { 'arch' => "x86_64", 'os' => 'linux' } } 102 | exchange.publish "For linux/x86", :properties => { :headers => { 'arch' => "x86", 'os' => 'linux' } } 103 | exchange.publish "For any linux", :properties => { :headers => { 'os' => 'linux' } } 104 | exchange.publish "For OS X", :properties => { :headers => { 'os' => 'macosx' } } 105 | exchange.publish "For solaris/IA64", :properties => { :headers => { 'os' => 'solaris', 'arch' => 'x86_64' } } 106 | 107 | sleep(0.3) 108 | 109 | mc, _cc = queue.status 110 | expect(mc).to eq(1) 111 | end 112 | end 113 | 114 | 115 | 116 | RSpec.describe "Internal exchange" do 117 | let(:connection) { MarchHare.connect } 118 | let(:channel) { connection.create_channel } 119 | 120 | after :each do 121 | channel.close 122 | connection.close 123 | end 124 | 125 | it "can be declared" do 126 | exchange = channel.topic("march.hare.exchanges.topic.internal", :internal => true) 127 | expect(exchange).to be_internal 128 | 129 | exchange.delete 130 | end 131 | end 132 | -------------------------------------------------------------------------------- /spec/higher_level_api/integration/exchange_publish_spec.rb: -------------------------------------------------------------------------------- 1 | RSpec.describe MarchHare::Exchange do 2 | let(:connection) { MarchHare.connect } 3 | 4 | after do 5 | connection.close 6 | end 7 | 8 | it 'allows a message timestamp to be included when publishing a message' do 9 | ch = connection.create_channel 10 | ch.confirm_select 11 | queue = ch.queue('publish_spec', exclusive: true) 12 | timestamp = Time.new(2016) 13 | 14 | ch.default_exchange.publish( 15 | 'hello, world!', 16 | routing_key: queue.name, 17 | properties: {timestamp: timestamp}, 18 | ) 19 | 20 | expect(ch.wait_for_confirms).to be_truthy 21 | expect(queue.get.first.properties.timestamp).to eq timestamp.to_java 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /spec/higher_level_api/integration/exchange_unbind_spec.rb: -------------------------------------------------------------------------------- 1 | RSpec.describe MarchHare::Exchange do 2 | let(:connection) { MarchHare.connect } 3 | 4 | after :each do 5 | connection.close 6 | end 7 | 8 | it "unbinds two existing exchanges" do 9 | ch = connection.create_channel 10 | 11 | source = ch.fanout("bunny.exchanges.source#{rand}") 12 | destination = ch.fanout("bunny.exchanges.destination#{rand}") 13 | 14 | queue = ch.queue("", :exclusive => true) 15 | queue.bind(destination) 16 | 17 | destination.bind(source) 18 | source.publish("") 19 | sleep 0.5 20 | 21 | expect(queue.message_count).to eq(1) 22 | queue.pop(:ack => true) 23 | 24 | destination.unbind(source) 25 | source.publish("") 26 | sleep 0.5 27 | 28 | expect(queue.message_count).to eq(0) 29 | 30 | source.delete 31 | destination.delete 32 | ch.close 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /spec/higher_level_api/integration/merry_go_round_spec.rb: -------------------------------------------------------------------------------- 1 | RSpec.describe "A message that is proxied by multiple intermediate consumers" do 2 | let(:c1) do 3 | MarchHare.connect 4 | end 5 | 6 | let(:c2) do 7 | MarchHare.connect 8 | end 9 | 10 | let(:c3) do 11 | MarchHare.connect 12 | end 13 | 14 | let(:c4) do 15 | MarchHare.connect 16 | end 17 | 18 | let(:c5) do 19 | MarchHare.connect 20 | end 21 | 22 | after :each do 23 | [c1, c2, c3, c4, c5].each do |c| 24 | c.close if c.open? 25 | end 26 | end 27 | 28 | # the message flow is as follows: 29 | # 30 | # x => q4 => q3 => q2 => q1 => xs (results) 31 | it "reaches its final destination" do 32 | n = 10000 33 | xs = [] 34 | 35 | ch1 = c1.create_channel 36 | q1 = ch1.queue("", :exclusive => true) 37 | cn1 = q1.subscribe do |_, payload| 38 | xs << payload 39 | end 40 | 41 | ch2 = c2.create_channel 42 | q2 = ch2.queue("", :exclusive => true) 43 | cn2 = q2.subscribe do |_, payload| 44 | q1.publish(payload) 45 | end 46 | 47 | ch3 = c3.create_channel 48 | q3 = ch2.queue("", :exclusive => true) 49 | cn3 = q3.subscribe do |_, payload| 50 | q2.publish(payload) 51 | end 52 | 53 | ch4 = c4.create_channel 54 | q4 = ch2.queue("", :exclusive => true) 55 | cn4 = q4.subscribe do |_, payload| 56 | q3.publish(payload) 57 | end 58 | 59 | ch5 = c5.create_channel 60 | x = ch5.default_exchange 61 | 62 | n.times do |i| 63 | x.publish("msg #{i}", :routing_key => q4.name) 64 | end 65 | 66 | until xs.size == n 67 | sleep 0.5 68 | end 69 | 70 | expect(xs.size).to eq(n) 71 | expect(xs.last).to eq("msg #{n - 1}") 72 | 73 | [cn1, cn2, cn3, cn4].each do |cons| 74 | cons.cancel 75 | end 76 | 77 | [ch1, ch2, ch3, ch4, ch5].each do |ch| 78 | ch.close 79 | end 80 | end 81 | 82 | 83 | 84 | it "can stretch tens of stages" do 85 | s = 32 86 | n = 1000 87 | xs = [] 88 | chs = [] 89 | qs = [] 90 | cs = [] 91 | 92 | s.times do |i| 93 | ch = c1.create_channel 94 | chs << ch 95 | 96 | next_q = qs.last 97 | 98 | q = ch.queue("", :exclusive => true) 99 | qs << q 100 | 101 | cs << q.subscribe do |_, payload| 102 | if next_q 103 | next_q.publish(payload) 104 | else 105 | xs << payload 106 | end 107 | end 108 | end 109 | 110 | pch = c2.create_channel 111 | x = pch.default_exchange 112 | 113 | n.times do |i| 114 | x.publish("msg #{i}", :routing_key => qs.last.name) 115 | end 116 | 117 | until xs.size == n 118 | sleep 0.5 119 | end 120 | 121 | expect(xs.size).to eq(n) 122 | expect(xs.last).to eq("msg #{n - 1}") 123 | 124 | cs.each do |cons| 125 | cons.cancel 126 | end 127 | 128 | chs.each do |ch| 129 | ch.close 130 | end 131 | end 132 | end 133 | -------------------------------------------------------------------------------- /spec/higher_level_api/integration/message_metadata_access_spec.rb: -------------------------------------------------------------------------------- 1 | RSpec.describe "A consumer" do 2 | let(:connection) { MarchHare.connect } 3 | let(:channel) { connection.create_channel } 4 | 5 | after :each do 6 | channel.close 7 | connection.close 8 | end 9 | 10 | it "can access message metadata (both message properties and delivery information)" do 11 | latch = java.util.concurrent.CountDownLatch.new(1) 12 | queue = channel.queue("", :exclusive => true) 13 | exchange = channel.exchange("amq.fanout", :type => :fanout) 14 | 15 | queue.bind(exchange, :routing_key => "march.hare.key") 16 | 17 | @now = Time.now 18 | @payload = "Hello, world!" 19 | @meta = nil 20 | 21 | _ = queue.subscribe(:blocking => false) do |metadata, payload| 22 | begin 23 | # we will run assertions on the main thread because RSpec uses exceptions 24 | # for its purposes every once in a while. MK. 25 | @meta = metadata 26 | rescue Exception => e 27 | e.print_stack_trace 28 | ensure 29 | latch.count_down 30 | end 31 | end 32 | 33 | exchange.publish(@payload, 34 | :properties => { 35 | :app_id => "march.hare.tests", 36 | :persistent => true, 37 | :priority => 8, 38 | :type => "kinda.checkin", 39 | # headers table keys can be anything 40 | :headers => { 41 | :coordinates => { 42 | :latitude => 59.35, 43 | "longitude" => 18.066667 44 | }, 45 | "time" => @now, 46 | "participants" => 11, 47 | :venue => "Stockholm", 48 | "true_field" => true, 49 | "false_field" => false, 50 | "nil_field" => nil, 51 | "ary_field" => ["one", 2.0, 3, [{ :abc => 123 }]] 52 | }, 53 | :timestamp => @now, 54 | :reply_to => "a.sender", 55 | :correlation_id => "r-1", 56 | :message_id => "m-1", 57 | :content_type => "application/octet-stream", 58 | # just an example. MK. 59 | :content_encoding => "zip/zap" 60 | }, 61 | :routing_key => "march.hare.key") 62 | latch.await 63 | 64 | expect(@meta.routing_key).to eq("march.hare.key") 65 | expect(@meta.content_type).to eq("application/octet-stream") 66 | expect(@meta.content_encoding).to eq("zip/zap") 67 | expect(@meta.priority).to eq(8) 68 | 69 | time = Time.at(@meta.headers["time"].getTime/1000) 70 | expect(time.to_i).to eq(@now.to_i) 71 | 72 | expect(@meta.headers["coordinates"]["latitude"]).to eq(59.35) 73 | expect(@meta.headers["participants"]).to eq(11) 74 | expect(@meta.headers["true_field"]).to eq(true) 75 | expect(@meta.headers["false_field"]).to eq(false) 76 | expect(@meta.headers["nil_field"]).to be_nil 77 | 78 | expect(@meta.timestamp).to eq(Time.at(@now.to_i)) 79 | expect(@meta.type).to eq("kinda.checkin") 80 | expect(@meta.consumer_tag).not_to be_nil 81 | expect(@meta.consumer_tag).not_to be_empty 82 | expect(@meta.delivery_tag.to_i).to eq(1) 83 | expect(@meta.delivery_mode).to eq(2) 84 | expect(@meta).to be_persistent 85 | expect(@meta.reply_to).to eq("a.sender") 86 | expect(@meta.correlation_id).to eq("r-1") 87 | expect(@meta.message_id).to eq("m-1") 88 | expect(@meta).not_to be_redelivered 89 | 90 | expect(@meta.app_id).to eq("march.hare.tests") 91 | expect(@meta.exchange).to eq("amq.fanout") 92 | end 93 | 94 | it "can handle properties being set at the top level" do 95 | latch = java.util.concurrent.CountDownLatch.new(1) 96 | queue = channel.queue("", :exclusive => true) 97 | exchange = channel.exchange("amq.fanout", :type => :fanout) 98 | 99 | queue.bind(exchange, :routing_key => "march.hare.key") 100 | 101 | @now = Time.now 102 | @payload = "Hello, world!" 103 | @meta = nil 104 | 105 | _ = queue.subscribe(:blocking => false) do |metadata, payload| 106 | begin 107 | # we will run assertions on the main thread because RSpec uses exceptions 108 | # for its purposes every once in a while. MK. 109 | @meta = metadata 110 | rescue Exception => e 111 | e.print_stack_trace 112 | ensure 113 | latch.count_down 114 | end 115 | end 116 | 117 | exchange.publish(@payload, 118 | :app_id => "march.hare.tests", 119 | :persistent => true, 120 | :priority => 8, 121 | :type => "kinda.checkin", 122 | # headers table keys can be anything 123 | :headers => { 124 | "coordinates" => { 125 | "latitude" => 59.35, 126 | "longitude" => 18.066667 127 | }, 128 | "time" => @now, 129 | "participants" => 11, 130 | "venue" => "Stockholm", 131 | "true_field" => true, 132 | "false_field" => false, 133 | "nil_field" => nil, 134 | "ary_field" => ["one", 2.0, 3, [{ "abc" => 123 }]] 135 | }, 136 | :timestamp => @now, 137 | :reply_to => "a.sender", 138 | :correlation_id => "r-1", 139 | :message_id => "m-1", 140 | :content_type => "application/octet-stream", 141 | # just an example. MK. 142 | :content_encoding => "zip/zap", 143 | :routing_key => "march.hare.key") 144 | latch.await 145 | 146 | expect(@meta.routing_key).to eq("march.hare.key") 147 | expect(@meta.content_type).to eq("application/octet-stream") 148 | expect(@meta.content_encoding).to eq("zip/zap") 149 | expect(@meta.priority).to eq(8) 150 | 151 | time = Time.at(@meta.headers["time"].getTime/1000) 152 | expect(time.to_i).to eq(@now.to_i) 153 | 154 | expect(@meta.headers["coordinates"]["latitude"]).to eq(59.35) 155 | expect(@meta.headers["participants"]).to eq(11) 156 | expect(@meta.headers["true_field"]).to eq(true) 157 | expect(@meta.headers["false_field"]).to eq(false) 158 | expect(@meta.headers["nil_field"]).to be_nil 159 | 160 | expect(@meta.timestamp).to eq(Time.at(@now.to_i)) 161 | expect(@meta.type).to eq("kinda.checkin") 162 | expect(@meta.consumer_tag).not_to be_nil 163 | expect(@meta.consumer_tag).not_to be_empty 164 | expect(@meta.delivery_tag.to_i).to eq(1) 165 | expect(@meta.delivery_mode).to eq(2) 166 | expect(@meta).to be_persistent 167 | expect(@meta.reply_to).to eq("a.sender") 168 | expect(@meta.correlation_id).to eq("r-1") 169 | expect(@meta.message_id).to eq("m-1") 170 | expect(@meta).not_to be_redelivered 171 | 172 | expect(@meta.app_id).to eq("march.hare.tests") 173 | expect(@meta.exchange).to eq("amq.fanout") 174 | end 175 | end 176 | -------------------------------------------------------------------------------- /spec/higher_level_api/integration/publisher_confirmations_spec.rb: -------------------------------------------------------------------------------- 1 | RSpec.describe "Any channel" do 2 | 3 | # 4 | # Environment 5 | # 6 | 7 | let(:connection) { MarchHare.connect } 8 | 9 | after :each do 10 | connection.close 11 | end 12 | 13 | 14 | # 15 | # Examples 16 | # 17 | 18 | it "can use publisher confirmations" do 19 | ch = connection.create_channel 20 | q = ch.queue("", :exclusive => true) 21 | 22 | ch.confirm_select 23 | ch.default_exchange.publish("", :routing_key => q.name) 24 | 25 | ch.wait_for_confirms(400) 26 | 27 | expect(true).to eq(true) 28 | end 29 | 30 | it "can receive publisher confirmation acks" do 31 | got_ack = false 32 | ch = connection.create_channel 33 | q = ch.queue("", :exclusive => true) 34 | 35 | ch.confirm_select 36 | ch.on_confirm { |type, seq, multiple| got_ack = (type ==:ack) } 37 | 38 | ch.default_exchange.publish("", :routing_key => q.name) 39 | 40 | ch.wait_for_confirms(40) 41 | 42 | expect(got_ack).to eq(true) 43 | end 44 | end 45 | -------------------------------------------------------------------------------- /spec/higher_level_api/integration/queue_bind_spec.rb: -------------------------------------------------------------------------------- 1 | RSpec.describe "A queue" do 2 | 3 | # 4 | # Environment 5 | # 6 | 7 | let(:connection) { MarchHare.connect } 8 | 9 | after :each do 10 | connection.close 11 | end 12 | 13 | 14 | # 15 | # Examples 16 | # 17 | 18 | it "can be bound to amq.fanout" do 19 | ch = connection.create_channel 20 | x = ch.exchange("amq.fanout", :type => :fanout, :durable => true, :auto_delete => false) 21 | q = ch.queue("", :exclusive => true) 22 | x.publish("") 23 | expect(q.get).to eq(nil) 24 | 25 | q.bind(x) 26 | 27 | x.publish("", :routing_key => q.name) 28 | expect(q.get).not_to eq(nil) 29 | 30 | 31 | end 32 | 33 | 34 | it "can be bound to a newly declared exchange [an HB::Exchange instance]" do 35 | ch = connection.create_channel 36 | x = ch.exchange("march.hare.fanout", :type => :fanout, :durable => false, :auto_delete => true) 37 | q = ch.queue("", :exclusive => true) 38 | x.publish("") 39 | expect(q.get).to eq(nil) 40 | 41 | q.bind(x) 42 | 43 | x.publish("", :routing_key => q.name) 44 | expect(q.get).not_to eq(nil) 45 | 46 | q.unbind(x) 47 | end 48 | 49 | it "can be bound to a newly declared exchange [a string]" do 50 | ch = connection.create_channel 51 | x = ch.exchange("march.hare.fanout", :type => :fanout, :durable => false, :auto_delete => true) 52 | q = ch.queue("", :exclusive => true) 53 | x.publish("") 54 | expect(q.get).to eq(nil) 55 | 56 | q.bind("march.hare.fanout") 57 | 58 | x.publish("", :routing_key => q.name) 59 | expect(q.get).not_to eq(nil) 60 | 61 | q.unbind("march.hare.fanout") 62 | end 63 | 64 | 65 | it "is automatically bound to the default exchange" do 66 | ch = connection.create_channel 67 | x = ch.default_exchange 68 | q = ch.queue("", :exclusive => true) 69 | 70 | x.publish("", :routing_key => q.name) 71 | expect(q.get).not_to eq(nil) 72 | end 73 | 74 | context "when the exchange does not exist" do 75 | it "raises an exception" do 76 | ch = connection.create_channel 77 | q = ch.queue("", :exclusive => true) 78 | 79 | raised = nil 80 | begin 81 | q.bind("asyd8a9d98sa73t78hd9as^&&(&@#(*^") 82 | rescue MarchHare::NotFound => e 83 | raised = e 84 | end 85 | 86 | expect(raised.channel_close.reply_text).to be =~ /no exchange/ 87 | end 88 | end 89 | end 90 | -------------------------------------------------------------------------------- /spec/higher_level_api/integration/queue_declare_spec.rb: -------------------------------------------------------------------------------- 1 | RSpec.describe "Queue" do 2 | let(:connection) { MarchHare.connect } 3 | let(:channel) { connection.create_channel } 4 | 5 | after :each do 6 | channel.close 7 | connection.close 8 | end 9 | 10 | it "cannot be created using a symbol as name" do 11 | expect { 12 | channel.queue(:not_valid_name) 13 | }.to raise_error(ArgumentError) 14 | end 15 | 16 | context "with a server-generated name" do 17 | it "can be declared as auto-deleted" do 18 | q = channel.queue("", :auto_delete => true) 19 | expect(q).to be_auto_delete 20 | q.delete 21 | end 22 | 23 | it "can be declared as auto-deleted and non-durable" do 24 | q = channel.queue("", :auto_delete => true, :durable => false) 25 | expect(q).to be_auto_delete 26 | expect(q).not_to be_durable 27 | q.delete 28 | end 29 | 30 | it "can be declared as NON-auto-deleted" do 31 | q = channel.queue("", :auto_delete => false) 32 | expect(q).not_to be_auto_delete 33 | expect(q).not_to be_durable 34 | q.delete 35 | end 36 | 37 | it "can be declared as NON-durable" do 38 | q = channel.queue("", :durable => false) 39 | expect(q).not_to be_durable 40 | q.delete 41 | end 42 | 43 | it "can be declared with additional attributes like x-message-ttle" do 44 | q = channel.queue("", :durable => false, :arguments => { 'x-message-ttl' => 2000 }) 45 | x = channel.exchange("", :type => :direct) 46 | 47 | 100.times do |i| 48 | x.publish("Message #{i}", :routing_key => q.name) 49 | end 50 | 51 | expect(q.get).not_to be_nil 52 | sleep(2.1) 53 | 54 | expect(q.get).to be_nil 55 | q.delete 56 | end 57 | end 58 | 59 | context "declared as temporary" do 60 | it "is declared as exclusive" do 61 | q = channel.temporary_queue() 62 | expect(q).to be_exclusive 63 | expect(q).not_to be_durable 64 | q.delete 65 | end 66 | end 67 | 68 | context "declared as durable" do 69 | it "is declared as such" do 70 | q = channel.durable_queue("bunny.durable.123") 71 | expect(q).to be_durable 72 | expect(q).not_to be_exclusive 73 | q.delete 74 | end 75 | end 76 | 77 | context "declared as quorum" do 78 | it "is declared as durable and non-exclusive via MarchHare::Channel#quorum_queue" do 79 | q = channel.quorum_queue("march_hare.qq.#{rand}", arguments: { 80 | "x-quorum-initial-group-size" => 3 81 | }) 82 | expect(q).to be_durable 83 | expect(q).not_to be_exclusive 84 | q.delete 85 | end 86 | 87 | it "is declared as durable and non-exclusive via MarchHare::Channel#queue with :type" do 88 | q = channel.queue("march_hare.qq.#{rand}", type: "quorum") 89 | expect(q).to be_durable 90 | expect(q).not_to be_exclusive 91 | q.delete 92 | end 93 | 94 | it "is declared as durable and non-exclusive via MarchHare::Channel#queue with x-queue-type" do 95 | q = channel.queue("march_hare.qq.#{rand}", arguments: { 96 | "x-queue-type" => "quorum", 97 | "x-quorum-initial-group-size" => 3 98 | }) 99 | expect(q).to be_durable 100 | expect(q).not_to be_exclusive 101 | q.delete 102 | end 103 | 104 | it "is declared as a quorum queue" do 105 | q_name = "march_hare.qq.property_equivalence_check" 106 | q = channel.quorum_queue(q_name) 107 | 108 | # property equivalence check should not fail for these… 109 | _ = channel.queue(q_name, type: "quorum") 110 | _ = channel.queue(q_name, arguments: { 111 | "x-queue-type" => "quorum" 112 | }) 113 | 114 | # …but finally fails here 115 | expect do 116 | one_off_ch = connection.create_channel 117 | one_off_ch.queue(q_name, arguments: { 118 | "x-queue-type" => "classic" 119 | }) 120 | end.to raise_error(MarchHare::PreconditionFailed) 121 | 122 | expect(q).to be_durable 123 | expect(q).not_to be_exclusive 124 | q.delete 125 | end 126 | end 127 | 128 | context "declared as stream" do 129 | it "is declared as durable and non-exclusive" do 130 | q = channel.stream("bunny.sq.1", arguments: { 131 | "x-max-length-bytes" => 20_000_000_000, 132 | "x-stream-max-segment-size-bytes" => 100_000_000 }) 133 | expect(q).to be_durable 134 | expect(q).not_to be_exclusive 135 | q.delete 136 | end 137 | end 138 | end 139 | -------------------------------------------------------------------------------- /spec/higher_level_api/integration/queue_delete_spec.rb: -------------------------------------------------------------------------------- 1 | RSpec.describe "Queue" do 2 | 3 | # 4 | # Environment 5 | # 6 | 7 | let(:connection) { MarchHare.connect } 8 | let(:channel) { connection.create_channel } 9 | 10 | after :each do 11 | channel.close 12 | connection.close 13 | end 14 | 15 | 16 | context "that exists" do 17 | it "can be deleted" do 18 | q = channel.queue("") 19 | q.delete 20 | end 21 | end 22 | 23 | context "that DOES NOT exist" do 24 | it "raises NO exception (as of RabbitMQ 3.2)" do 25 | ch = connection.create_channel 26 | q = ch.queue("") 27 | 28 | q.delete(true, true) 29 | # No exception as of RabbitMQ 3.2. MK. 30 | q.delete(true, true) 31 | end 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /spec/higher_level_api/integration/queue_purge_spec.rb: -------------------------------------------------------------------------------- 1 | RSpec.describe "Any queue" do 2 | 3 | # 4 | # Environment 5 | # 6 | 7 | let(:connection) { MarchHare.connect } 8 | let(:channel) { connection.create_channel } 9 | 10 | after :each do 11 | channel.close 12 | connection.close 13 | end 14 | 15 | 16 | # 17 | # Examples 18 | # 19 | 20 | it "can be purged" do 21 | exchange = channel.exchange("amq.fanout", :type => :fanout, :durable => true, :auto_delete => false) 22 | queue = channel.queue("", :auto_delete => true) 23 | exchange.publish("") 24 | expect(queue.get).to be_nil 25 | queue.purge 26 | expect(queue.get).to be_nil 27 | 28 | queue.bind(exchange) 29 | 30 | exchange.publish("", :routing_key => queue.name) 31 | expect(queue.get).not_to be_nil 32 | queue.purge 33 | expect(queue.get).to be_nil 34 | queue.delete 35 | end 36 | end 37 | -------------------------------------------------------------------------------- /spec/higher_level_api/integration/queue_unbind_spec.rb: -------------------------------------------------------------------------------- 1 | RSpec.describe "Any queue" do 2 | 3 | # 4 | # Environment 5 | # 6 | 7 | let(:connection) { MarchHare.connect } 8 | let(:channel) { connection.create_channel } 9 | 10 | after :each do 11 | channel.close 12 | connection.close 13 | end 14 | 15 | 16 | # 17 | # Examples 18 | # 19 | 20 | it "can be unbound from amq.fanout" do 21 | exchange = channel.exchange("amq.fanout", :type => :fanout, :durable => true, :auto_delete => false) 22 | queue = channel.queue("", :exclusive => true) 23 | 24 | queue.bind(exchange) 25 | 26 | exchange.publish("", :routing_key => queue.name) 27 | expect(queue.get).not_to be_nil 28 | 29 | queue.unbind(exchange) 30 | 31 | exchange.publish("") 32 | expect(queue.get).to be_nil 33 | end 34 | 35 | 36 | it "can be unbound from a client-declared exchange" do 37 | exchange = channel.exchange("hot.bunnies.fanout#{Time.now.to_i}", :type => :fanout, :durable => false) 38 | queue = channel.queue("", :exclusive => true) 39 | 40 | queue.bind(exchange) 41 | 42 | exchange.publish("", :routing_key => queue.name) 43 | expect(queue.get).not_to be_nil 44 | 45 | queue.unbind(exchange) 46 | 47 | exchange.publish("") 48 | expect(queue.get).to be_nil 49 | end 50 | end 51 | -------------------------------------------------------------------------------- /spec/higher_level_api/integration/sender_selected_distribution_spec.rb: -------------------------------------------------------------------------------- 1 | RSpec.describe "Any AMQP 0.9.1 client using RabbitMQ" do 2 | 3 | # 4 | # Environment 5 | # 6 | 7 | let(:connection) { MarchHare.connect } 8 | let(:channel) { connection.create_channel } 9 | 10 | after :each do 11 | channel.close 12 | connection.close 13 | end 14 | 15 | 16 | # 17 | # Examples 18 | # 19 | 20 | it "can have use CC and BCC headers for sender selected routing" do 21 | queue1 = channel.queue("", :exclusive => true) 22 | queue2 = channel.queue("", :exclusive => true) 23 | queue3 = channel.queue("", :exclusive => true) 24 | queue4 = channel.queue("", :exclusive => true) 25 | 26 | channel.default_exchange.publish("1010", :properties => { 27 | :headers => { 28 | "CC" => [queue2.name], 29 | "BCC" => [queue3.name] 30 | } 31 | }, :routing_key => queue1.name) 32 | 33 | sleep 1 34 | 35 | mc1, _ = queue1.status 36 | mc2, _ = queue2.status 37 | mc3, _ = queue3.status 38 | mc4, _ = queue4.status 39 | 40 | expect(mc1).to eq(1) 41 | expect(mc2).to eq(1) 42 | expect(mc3).to eq(1) 43 | expect(mc4).to eq(0) 44 | end 45 | end 46 | -------------------------------------------------------------------------------- /spec/higher_level_api/integration/tls_spec.rb: -------------------------------------------------------------------------------- 1 | RSpec.describe "MarchHare.connect with TLS" do 2 | 3 | # 4 | # Examples 5 | # 6 | 7 | if !ENV["CI"] && ENV["TLS_TESTS"] 8 | it "supports TLS w/o custom protocol or trust manager" do 9 | c1 = MarchHare.connect(:tls => true, :port => 5671) 10 | c1.close 11 | end 12 | 13 | it "supports TLS with a client key" do 14 | c = MarchHare.connect( 15 | :tls => "TLSv1.1", 16 | :port => 5671, 17 | :tls_certificate_path => "./spec/tls/client_key.p12", 18 | :tls_certificate_password => ENV.fetch("PKCS12_PASSWORD", "bunnies")) 19 | _ch = c.create_channel 20 | c.close 21 | end 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | $LOAD_PATH.unshift File.expand_path("../../lib", __FILE__) 2 | 3 | require 'bundler' 4 | Bundler.setup(:default, :test) 5 | 6 | require "march_hare" 7 | 8 | puts "Running on Ruby #{RUBY_VERSION}." 9 | 10 | RSpec.configure do |config| 11 | config.raise_errors_for_deprecations! 12 | 13 | config.expect_with :rspec do |expectations| 14 | # This option will default to `true` in RSpec 4. It makes the `description` 15 | # and `failure_message` of custom matchers include text for helper methods 16 | # defined using `chain`, e.g.: 17 | # be_bigger_than(2).and_smaller_than(4).description 18 | # # => "be bigger than 2 and smaller than 4" 19 | # ...rather than: 20 | # # => "be bigger than 2" 21 | expectations.include_chain_clauses_in_custom_matcher_descriptions = true 22 | end 23 | 24 | config.mock_with :rspec do |mocks| 25 | # Prevents you from mocking or stubbing a method that does not exist on 26 | # a real object. This is generally recommended, and will default to 27 | # `true` in RSpec 4. 28 | mocks.verify_partial_doubles = true 29 | end 30 | 31 | # This option will default to `:apply_to_host_groups` in RSpec 4 (and will 32 | # have no way to turn it off -- the option exists only for backwards 33 | # compatibility in RSpec 3). It causes shared context metadata to be 34 | # inherited by the metadata hash of host groups and examples, rather than 35 | # triggering implicit auto-inclusion in groups with matching metadata. 36 | config.shared_context_metadata_behavior = :apply_to_host_groups 37 | 38 | # This allows you to limit a spec run to individual examples or groups 39 | # you care about by tagging them with `:focus` metadata. When nothing 40 | # is tagged with `:focus`, all examples get run. RSpec also provides 41 | # aliases for `it`, `describe`, and `context` that include `:focus` 42 | # metadata: `fit`, `fdescribe` and `fcontext`, respectively. 43 | config.filter_run_when_matching :focus 44 | 45 | # Allows RSpec to persist some state between runs in order to support 46 | # the `--only-failures` and `--next-failure` CLI options. We recommend 47 | # you configure your source control system to ignore this file. 48 | config.example_status_persistence_file_path = "spec/examples.txt" 49 | 50 | # Limits the available syntax to the non-monkey patched syntax that is 51 | # recommended. For more details, see: 52 | # - http://rspec.info/blog/2012/06/rspecs-new-expectation-syntax/ 53 | # - http://www.teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/ 54 | # - http://rspec.info/blog/2014/05/notable-changes-in-rspec-3/#zero-monkey-patching-mode 55 | config.disable_monkey_patching! 56 | 57 | # This setting enables warnings. It's recommended, but in some cases may 58 | # be too noisy due to issues in dependencies. 59 | config.warnings = true 60 | 61 | # Many RSpec users commonly either run the entire suite or an individual 62 | # file, and it's useful to allow more verbose output when running an 63 | # individual spec file. 64 | if config.files_to_run.one? 65 | # Use the documentation formatter for detailed output, 66 | # unless a formatter has already been configured 67 | # (e.g. via a command-line flag). 68 | config.default_formatter = 'doc' 69 | end 70 | 71 | # Print the 10 slowest examples and example groups at the 72 | # end of the spec run, to help surface which specs are running 73 | # particularly slow. 74 | config.profile_examples = 10 75 | 76 | # Run specs in random order to surface order dependencies. If you find an 77 | # order dependency and want to debug it, you can fix the order by providing 78 | # the seed, which is printed after each run. 79 | # --seed 1234 80 | config.order = :random 81 | 82 | # Seed global randomization in this process using the `--seed` CLI option. 83 | # Setting this allows you to use `--seed` to deterministically reproduce 84 | # test failures related to randomization by passing the same `--seed` value 85 | # as the one that triggered the failure. 86 | Kernel.srand config.seed 87 | end 88 | -------------------------------------------------------------------------------- /spec/tls/.gitignore: -------------------------------------------------------------------------------- 1 | *.pem 2 | *.p12 3 | -------------------------------------------------------------------------------- /spec/tls/README.md: -------------------------------------------------------------------------------- 1 | # TLS Certificates for March Hare Tests 2 | 3 | This directory is for CA and client certificate/key pairs 4 | used by the TLS connection tests.. 5 | 6 | The files it is expected to have: 7 | 8 | * `ca_certificate.pem` 9 | * `client_certificate.pem` 10 | * `client_key.pem` 11 | * `client_key.p12` 12 | 13 | By far the easiest way to generate these files is with [tls-gen](https://github.com/michaelklishin/tls-gen/). 14 | N.B. that RabbitMQ has to be configured to use the same CA certificate and a certificate/key 15 | pair signed by that CA certificate. 16 | --------------------------------------------------------------------------------