Connecting to RabbitMQ from Ruby with Bunny

About this guide

This guide covers connection to RabbitMQ with Bunny, connection error handling, authentication failure handling and related issues.

This work is licensed under a Creative Commons Attribution 3.0 Unported License (including images and stylesheets). The source is available on Github.

What version of Bunny does this guide cover?

This guide covers Bunny 2.10.x and later versions.

Two ways to specify connection parameters

With Bunny, connection parameters (host, port, username, vhost and so on) can be passed in two forms:

  • As a map of attributes
  • As a connection URI string (à la JDBC)

Using a Map of Parameters

Map options that Bunny will recognize are

  • :host (string, default: "127.0.0.1")
  • :port (integer, default: 5672)
  • :user or :username (string, default: "guest")
  • :pass or :password (string, default: "guest")
  • :vhost or virtual_host (string, default: '/')
  • :heartbeat or :heartbeat_interval (string or integer, default: :server): standard RabbitMQ server heartbeat. :server means "use the value from RabbitMQ config". 0 means no heartbeats (not recommended).
  • :logger (Logger): The logger. If missing, one is created using :log_file and :log_level.
  • :log_level (symbol or integer, default: Logger::WARN): Log level to use when creating a logger.
  • :log_file (string or IO, default: STDOUT): log file or IO object to use when creating a logger
  • :automatically_recover (boolean, default: true): when false, will disable automatic network failure recovery
  • :network_recovery_interval (number, default: 5.0): interval between reconnection attempts
  • :threaded (boolean, default: true): switches to single-threaded connections when set to false. Only recommended for apps that only publish messages.
  • :continuation_timeout (integer, default: 4000 ms): timeout for client operations that expect a response (e.g. Bunny::Queue#get), in milliseconds.
  • :frame_max (integer, default: 131072): maximum permissible size of a frame (in bytes) to negotiate with clients. Setting to 0 means "unlimited" but will trigger a bug in some QPid clients. Setting a larger value may improve throughput; setting a smaller value may improve latency.
  • :auth_mechanism (string or array, default: "PLAIN"): Mechanism to authenticate with the server. Currently supporting "PLAIN" and "EXTERNAL".

plus TLS connection parameters covered in Using TLS (SSL) Connections.

To connect to RabbitMQ with a map of parameters, pass them to Bunny.new. The connection will be established when Bunny::Session#start is called:

conn = Bunny.new(:host => "localhost", :vhost => "myapp.production", :user => "bunny", :password => "t0ps3kret")
conn.start

Bunny.new returns a connection instance that is used to open channels. More about channels later in this guide.

Default parameters

Default connection parameters are

{
  :host      => "127.0.0.1",
  :port      => 5672,
  :ssl       => false,
  :vhost     => "/",
  :user      => "guest",
  :pass      => "guest",
  :heartbeat => :server, # will use RabbitMQ setting
  :frame_max => 131072,
  :auth_mechanism => "PLAIN"
}

Single-Threaded Mode

Bunny 0.9+ uses a separate thread for network (I/O) activity. In some cases, developers may want to disable it and handle network failure issues manually. This is possible by passing the :threaded option as false.

Reconnection Interval

When Bunny detects a network issue, it will by default try to reconnect. This behavior can be tweaked using two options:

  • :network_recovery_interval controls for how long Bunny will wait between reconnection attempts (including the first one). The value is in seconds, 5.0 is the default.
  • :automatically_recover can be set to false to completely disable network recovery. In such case, Bunny will raise exceptions on the thread Bunny::Session was instantiated on.

Using Connection Strings

It is also possible to specify connection parameters as a URI string:

b = Bunny.new("amqp://guest:guest@vm188.dev.megacorp.com/profitd.qa")
b.start

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. The implementation used by Bunny aims to encourage URIs that work as widely as possible.

Here are some examples of valid AMQP URIs:

The URI scheme should be "amqp", or "amqps" if SSL is required.

The host, port, username and password are represented in the authority component of the URI in the same way as in HTTP URIs.

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.

Here are some examples that demonstrate how AMQ::Settings.parse_amqp_url parses out the vhost from connection URIs:

AMQ::Settings.parse_amqp_url("amqp://dev.rabbitmq.com")            # => vhost is nil, so default ("/") will be used
AMQ::Settings.parse_amqp_url("amqp://dev.rabbitmq.com/")           # => vhost is an empty string
AMQ::Settings.parse_amqp_url("amqp://dev.rabbitmq.com/%2Fvault")   # => vhost is "/vault"
AMQ::Settings.parse_amqp_url("amqp://dev.rabbitmq.com/production") # => vhost is "production"
AMQ::Settings.parse_amqp_url("amqp://dev.rabbitmq.com/a.b.c")      # => vhost is "a.b.c"
AMQ::Settings.parse_amqp_url("amqp://dev.rabbitmq.com/foo/bar")    # => ArgumentError

Bunny is able to parse RabbitMQ URI query parameters, where you may specify some common client connection attributes:

  • auth_mechanism
  • heartbeat
  • connection_timout
  • channel_max
  • verify
  • fail_if_no_peer_cert
  • cacertfile
  • certfile
  • keyfile

Here is an example:

b = Bunny.new("amqps://rabbitmq?heartbeat=10&connection_timeout=100&channel_max=1000&verify=true&fail_if_no_peer_cert=true&cacertfile=/examples/tls/cacert.pem&certfile=/examples/tls/client_cert.pem&keyfile=/examples/tls/client_key.pem")
b.start
b.user #=> "guest"
b.pass #=> "guest"
b.hostname #=> "rabbitmq"
b.port #=> 5671
b.vhost #=> "/"
b.heartbeat #=> 10
b.transport.connect_timeout #=> 100
b.channel_max #=> 1000
b.ssl #=> true
b.transport.verify_peer #=> false
b.transport.tls_ca_certificates #=> "/examples/tls/cacert.pem"
b.transport.tls_ca_certificates #=> "/examples/tls/client_cert.pem"
b.transport.tls_key_path #=> "/examples/tls/client_key.pem"

Pay attention that some attibutes are not allowed for specific scheme, particularly TLS options don't make sense for "amqp" scheme.

Connection Failures

If a connection does not succeed, Bunny will raise one of the following exceptions:

  • Bunny::PossibleAuthenticationFailureException indicates an authentication issue or that connection to RabbitMQ was closed before successfully finishing connection negotiation
  • Bunny::TCPConnectionFailed indicates that connection to the host has failed. Either the address is not reachable or DNS entry does not exist. Often may suggest a misconfiguration.
  • Bunny::NetworkFailure for other exceptions in the I/O thread.

When automatic connection recovery mode is disabled, Bunny will raise exceptions on the thread Bunny::Session was instantiated.

PaaS Environments

The RABBITMQ_URL Environment Variable

If no arguments are passed to Bunny.new but the RABBITMQ_URL environment variable is set, Bunny will use it as connection URI.

Opening a Channel

Some applications need multiple connections to RabbitMQ. However, it is undesirable to keep many TCP connections open at the same time because doing so consumes system resources and makes it more difficult to configure firewalls. AMQP 0-9-1 connections are multiplexed with channels that can be thought of as "lightweight connections that share a single TCP connection".

To open a channel, use the Bunny::Session#create_channel method:

conn = Bunny.new
conn.start

ch   = conn.create_channel

Channels are typically long lived: you open one or more of them and use them for a period of time, as opposed to opening a new channel for each published message, for example.

Closing Channels

To close a channel, use the Bunny::Channel#close method. A closed channel can no longer be used.

conn = Bunny.new
conn.start

ch   = conn.create_channel
ch.close

Connecting in Web applications (Ruby on Rails, Sinatra, etc)

When connecting in Web apps, the rule of thumb is: do it in an initializer, not controller actions or request handlers.

Using Bunny with Unicorn

Unicorn is a pre-forking server. That means it forks worker processes that serve HTTP requests. The "fork(2) system call has several gotchas associated with it:

To avoid both problems, connect to RabbitMQ after the master process forks workers. The master Unicorn process never serves HTTP requests and usually does not need to hold a RabbitMQ connection. Next, let us see how to connect to the broker after Unicorn forks a worker.

Unicorn lets you specify a configuration file to use. In that file you define a callback that Unicorn runs after it forks worker process(es):

preload_app true

after_fork do |server, worker|
  require "bunny"

  # the following is *required* for Rails + "preload_app true",
  defined?(ActiveRecord::Base) and
    ActiveRecord::Base.establish_connection

    $rabbitmq_connection = Bunny.new
    $rabbitmq_connection.start

    $rabbitmq_channel    = $rabbitmq_connection.create_channel
 end
end

In the example above we connect to RabbitMQ after Unicorn has forked off child processes.

Note that a configuration file can easily be used in development environments because, other than the fact that Unicorn runs in the foreground, it gives you exactly the same application boot behavior as in QA and production environments.

Using Bunny with Passenger

Phusion Passenger is also a pre-forking server, and just as with Unicorn, clients should connect to RabbitMQ after it forks worker processes. The Passenger documentation has a section that explains how to avoid problems related to the behavior of the fork(2) system call, namely:

Using Event Handler to Spawn One Connection Per Worker Process

Passenger provides a hook that you should use for spawning RabbitMQ connections:

if defined?(PhusionPassenger) # otherwise it breaks rake commands if you put this in an initializer
  PhusionPassenger.on_event(:starting_worker_process) do |forked|
    if forked
       # We’re in a smart spawning mode
       # Now is a good time to connect to RabbitMQ
       $rabbitmq_connection = Bunny.new
       $rabbitmq_connection.start

       $rabbitmq_channel    = $rabbitmq_connection.create_channel
    end
  end

  PhusionPassenger.on_event(:stopping_worker_process) do
    if $rabbitmq_connection
      $rabbitmq_connection.close
    end
  end
end

Basically, the recommended default smart spawn mode works exactly the same as in Unicorn.

Ruby on Rails

Currently Bunny does not have integration points for Rails (e.g. a rail tie).

Disconnecting

To close a connection, use the Bunny::Session#close function. This will automatically close all channels of that connection first:

conn = Bunny.new
conn.start

conn.close

Troubleshooting

If you have read this guide and still have issues with connecting, check our Troubleshooting guide and feel free to ask on the mailing list.

Wrapping Up

There are two ways to specify connection parameters with Bunny: with a map of parameters or via URI string. Connection issues are indicated by various exceptions. If the RABBITMQ_URL env variable is set, Bunny will use its value as RabbitMQ connection URI.

The documentation is organized as a number of guides, covering various topics.

We recommend that you read the following guides first, if possible, in this order:

Tell Us What You Think!

Please take a moment to tell us what you think about this guide on Twitter or the Bunny mailing list.

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.