Git Product home page Git Product logo

rabbitmq_http_api_client's Introduction

RabbitMQ HTTP API Client for Ruby

This gem is a RabbitMQ HTTP API client for Ruby. It supports

  • Getting cluster overview information
  • Getting cluster nodes status (# file descriptors used, RAM consumption and so on)
  • Getting information about exchanges, queues, bindings
  • Closing client connections
  • Getting information about vhosts, users, permissions
  • Getting information about enabled plugins, protocols, their ports, etc
  • Managing vhosts, users, permissions

and will support more HTTP API features in the future

  • Publishing messages via HTTP
  • Operations on components/extensions
  • Operations on federation policies

Supported Ruby Versions

  • CRuby 2.7 through 3.x
  • JRuby 9K

Supported RabbitMQ Versions

This library targets RabbitMQ release series covered by community support.

All versions require RabbitMQ Management UI plugin to be installed and enabled.

Installation

Add this line to your application's Gemfile to use the latest version of this library:

# Depends on Faraday 2.x
gem 'rabbitmq_http_api_client', '>= 3.0.0'

If you absolutely must use Faraday 1.x, use the 2.x series:

# Depends on Faraday 1.x.
# Consider using 3.0.0 and later versions.
gem 'rabbitmq_http_api_client', '>= 2.2.0'

And then execute:

bundle install

Or install it manually with:

gem install rabbitmq_http_api_client

Usage

To require the client:

require "rabbitmq/http/client"

Specifying Endpoint and Credentials

Use RabbitMQ::HTTP::Client#connect to specify RabbitMQ HTTP API endpoint (e.g. http://127.0.0.1:15672) and credentials:

require "rabbitmq/http/client"

endpoint = "http://127.0.0.1:15672"
client = RabbitMQ::HTTP::Client.new(endpoint, username: "guest", password: "guest")

Alternatively, credentials can be specified in the endpoint URI:

require "rabbitmq/http/client"

client = RabbitMQ::HTTP::Client.new("http://guest:[email protected]:15672")

Client API Design Overview

All client methods return arrays or hash-like structures that can be used like JSON, via Hash#[] or regular method access:

r = client.overview

puts r[:rabbitmq_version]
puts r.erlang_version

Accessing Management API with HTTPS

All additional options other than :username and :password are passed to Faraday::Connection. So, it is possible to use HTTPS like so:

c = RabbitMQ::HTTP::Client.new("https://127.0.0.1:15672/", username: "guest", password: "guest", ssl: {
  client_cer: ...,
  client_key: ...,
  ca_file:    ...,
  ca_path:    ...,
  cert_store: ...
})

Or, if you have good reasons to do so, disable peer verification:

c = RabbitMQ::HTTP::Client.new("https://127.0.0.1:15672/", username: "guest", password: "guest", ssl: {
  verify: false
})

Node and Cluster Status

# Get cluster information overview
h     = client.overview

# List cluster nodes with detailed status info for each one of them
nodes = client.list_nodes
n     = nodes.first
puts n.mem_used
puts n.run_queue

# Get detailed status of a node
n     = client.node_info("rabbit@localhost")
puts n.disk_free
puts n.proc_used
puts n.fd_total

# Get Management Plugin extension list
xs    = client.list_extensions

# List all the entities (vhosts, queues, exchanges, bindings, users, etc)
defs  = client.list_definitions

Operations on Connections

# List all connections to a node
conns = client.list_connections
conn  = conns.first
puts conn.name
puts conn.client_properties.product

# Get a connection information by name
conns = client.list_connections
conn  = client.connection_info(conns.first.name)
puts conn.name
puts conn.client_properties.product

# Forcefully close a connection
conns = client.list_connections
client.close_connection(conns.first.name)

Operations on Channels

# List all channels
channs = client.list_channels
ch     = channs.first
puts ch.number
puts ch.prefetch_count
puts ch.name


# Get a channel information by name
conns = client.list_channels
conn  = client.channel_info(conns.first.name)
puts conn.name

Operations on Exchanges

# List all exchanges in the cluster
xs = client.list_exchanges
x  = xs.first

puts x.type
puts x.name
puts x.vhost
puts x.durable
puts x.auto_delete

# List all exchanges in a vhost
xs = client.list_exchanges("myapp.production")
x  = xs.first

puts x.type
puts x.name
puts x.vhost

# Get information about an exchange in a vhost
x  = client.exchange_info("/", "log.events")

puts x.type
puts x.name
puts x.vhost

# List all exchanges in a vhost for which an exchange is the source
client.list_bindings_by_source("/", "log.events")

# List all exchanges in a vhost for which an exchange is the destination
client.list_bindings_by_destination("/", "command.handlers.email")

Operations on Queues

# List all queues in a node
qs = client.list_queues
q  = qs.first

puts q.name
puts q.auto_delete
puts q.durable
puts q.backing_queue_status
puts q.active_consumers


# Get information about a queue
client.queue_info("/", "collector1.megacorp.local")

# Declare a queue
client.declare_queue("/", "collector1.megacorp.local", :durable => false, :auto_delete => true)

# Delete a queue
client.delete_queue("/", "collector1.megacorp.local")

# List bindings for a queue
bs = client.list_queue_bindings("/", "collector1.megacorp.local")

# Purge a queue
client.purge_queue("/", "collector1.megacorp.local")

# Fetch messages from a queue
ms = client.get_messages("/", "collector1.megacorp.local", :count => 10, :requeue => false, :encoding => "auto")
m  = ms.first

puts m.properties.content_type
puts m.payload
puts m.payload_encoding

Operations on Bindings

# List all bindings
bs = client.list_bindings
b  = bs.first

puts b.destination
puts b.destination_type
puts b.source
puts b.routing_key
puts b.vhost

# List all bindings in a vhost
bs = client.list_bindings("/")

# List all bindings between an exchange and a queue
bs = client.list_bindings_between_queue_and_exchange("/", "collector1.megacorp.local", "log.events")

Operations on Vhosts

# List all vhosts
vs = client.list_vhosts
v  = vs.first

puts v.name
puts v.tracing

# Get information about a vhost
v  = client.vhost_info("/")

puts v.name
puts v.tracing

# Create a vhost
client.create_vhost("myapp.staging")

# Delete a vhost
client.delete_vhost("myapp.staging")

Managing Users

# List all users
us = client.list_users
u  = us.first

puts u.name
puts u.password_hash
puts u.tags

# Get information about a user
u  = client.user_info("guest")

puts u.name
puts u.password_hash
puts u.tags

# Update information about a user
client.update_user("myapp", :tags => "services,policymaker,management", :password => "t0ps3krEt")

# Delete a user
client.delete_user("myapp")

Managing Permissions

# List all permissions
ps = client.list_permissions

puts p.user
puts p.read
puts p.write
puts p.configure
puts p.vhost

# List all permissions in a vhost
ps = client.list_permissions("/")

puts p.user
puts p.read
puts p.write
puts p.configure
puts p.vhost

# List permissions of a user
ps = client.user_permissions("guest")

# List permissions of a user in a vhost
ps = client.list_permissions_of("/", "guest")

# Update permissions of a user in a vhost
ps = client.update_permissions_of("/", "guest", :write => ".*", :read => ".*", :configure => ".*")

# Clear permissions of a user in a vhost
ps = client.clear_permissions_of("/", "guest")

Running Tests

Before running the test suites, run a script that will set up the local node:

export RUBY_RABBITMQ_HTTP_API_CLIENT_RABBITMQCTL="/path/to/sbin/rabbitmqctl"
export RUBY_RABBITMQ_HTTP_API_CLIENT_RABBITMQ_PLUGINS="/path/to/sbin/rabbitmq-plugins"

./bin/ci/before_build.sh

To run all specs:

bundle install
bundle exec rspec -cfd spec

The test suite assumes that RabbitMQ is running locally with stock settings and rabbitmq-management plugin enabled.

Contributing

  1. Fork it
  2. Create your feature branch (git checkout -b my-new-feature)
  3. Commit your changes (git commit -am 'Add some feature')
  4. Push to the branch (git push origin my-new-feature)
  5. Create a new pull request

License & Copyright

Double-licensed under the MIT and the Mozilla Public License 2.0 (same as RabbitMQ).

(c) Michael S. Klishin, 2012-2024.

rabbitmq_http_api_client's People

Contributors

bagedevimo avatar bitdeli-chef avatar carlhoerberg avatar damonmorgan avatar dcorbacho avatar deadtrickster avatar goromlagche avatar hatch-carl avatar jakedavis avatar jamur2 avatar jeremyhamm avatar jonhoman avatar lhoguin avatar mattbostock avatar michaelklishin avatar nielsjansendk avatar noahhaon avatar olleolleolle avatar pjk25 avatar polmiro avatar rquant avatar rud avatar sharshenov avatar shashankmehra avatar wconrad avatar

Stargazers

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

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

rabbitmq_http_api_client's Issues

Empty object returned when "content-length" header is null, even if content is present

Hi. I came across an issue in which when requesting exchange info against our CloudAMQP host. The response body contains the correct data, but the "content-length" header is null. This causes an empty Hashie Mash object to get returned due to the following code:

def decode_resource(response)
        case response.headers["content-length"]
        when nil then Hashie::Mash.new
        when 0   then Hashie::Mash.new
        when "0" then Hashie::Mash.new
        else
          if response.body.empty?
            Hashie::Mash.new
          else
            Hashie::Mash.new(response.body)
          end
        end
      end

Here is a screenshot of my debugging context to demonstrate the issue:
Screen Shot 2021-01-07 at 5 01 28 AM

Unable to update_parameters_of - the server responded with status 400

I'm trying to run this:

attributes = { 'uri' => "amqp://test:[email protected]/monitor", 'reconnect-delay' => 60 }
client.update_parameters_of('federation-upstream', 'monitor', 'Timeline', attributes)

But I'm getting:

Faraday::ClientError: the server responded with status 400
from /var/lib/gems/2.2.0/gems/faraday-0.9.1/lib/faraday/response/raise_error.rb:13:in `on_complete'

Any ideas?

Unable to list parameters by name - undefined method `each_pair'

When I try to list_parameters_of by name, I get this error:

> client.list_parameters_of('federation-upstream', 'monitor', '33ac8c46007794dee1a6')
NoMethodError: undefined method `each_pair' for #<Array:0x0000000153b1e0>
from /var/lib/gems/2.2.0/gems/hashie-3.4.1/lib/hashie/mash.rb:179:in `deep_update'

If I don't list by name, I see the parameters as expected:

> client.list_parameters_of('federation-upstream', 'monitor')
=> [{"value"=>{"uri"=>"amqp://33ac8c46007794dee1a6:[email protected]/monitor", "reconnect-delay"=>30}, "vhost"=>"monitor", "component"=>"federation-upstream", "name"=>"33ac8c46007794dee1a6"}]

{bad_header,<<"PUT /api">>}

archlinux, erlang-nox 20.3.7-1, rabbitmq 3.7.5-1, ruby 2.5.1p57
gems versions after running bundle update

$ grep rabbitmq_http_api_client Gemfile.lock
    rabbitmq_http_api_client (1.9.1)

$ grep faraday Gemfile.lock
    faraday (0.13.1)
    faraday_middleware (0.12.2)
      faraday (>= 0.7.4, < 1.0)
      faraday (~> 0.13.0)
      faraday_middleware (~> 0.12.0)

ruby code

require 'rabbitmq/http/client'
client = RabbitMQ::HTTP::Client.new('http://127.0.0.1:5672', username: 'guest', password: 'guest')
client.create_vhost( 'chat' )
Faraday::ConnectionFailed: Connection reset by peer
from /usr/lib/ruby/2.5.0/socket.rb:452:in `__read_nonblock'

And what in the /var/log/rabbitmq/node.log

2018-06-25 23:50:06.279 [info] <0.13072.0> accepting AMQP connection <0.13072.0> (127.0.0.1:38678 -> 127.0.0.1:5672)
2018-06-25 23:50:06.279 [warning] <0.13072.0> closing AMQP connection <0.13072.0> (127.0.0.1:38678 -> 127.0.0.1:5672):
{bad_header,<<"PUT /api">>}

failed tests

I'm looking into making some changes so I ran the tests first and got some failures. Do other folks see these same failures? I used ruby 2.2.1.

$ bundle exec rspec
<trimmed>
Finished in 1 minute 48.04 seconds (files took 0.38489 seconds to load)
1070 examples, 25 failures, 4 pending

Failed examples:

rspec ./spec/integration/client_spec.rb:77 # RabbitMQ::HTTP::Client GET /api/nodes lists cluster nodes with detailed status information for each one of them
rspec ./spec/integration/client_spec.rb:101 # RabbitMQ::HTTP::Client GET /api/node/:name returns status information for a single cluster node
rspec ./spec/integration/client_spec.rb:130 # RabbitMQ::HTTP::Client GET /api/extensions returns a list of enabled management plugin extensions
rspec ./spec/integration/client_spec.rb:210 # RabbitMQ::HTTP::Client GET /api/channels returns a list of all active channels
rspec ./spec/integration/client_spec.rb:224 # RabbitMQ::HTTP::Client GET /api/channels/:name returns information about the channel
rspec ./spec/integration/client_spec.rb:260 # RabbitMQ::HTTP::Client GET /api/exchanges/:vhost/:name returns information about the exchange
rspec ./spec/integration/client_spec.rb:283 # RabbitMQ::HTTP::Client PUT /api/exchanges/:vhost/:name declares an exchange
rspec ./spec/integration/client_spec.rb:302 # RabbitMQ::HTTP::Client DELETE /api/exchanges/:vhost/:name deletes an exchange
rspec ./spec/integration/client_spec.rb:318 # RabbitMQ::HTTP::Client GET /api/exchanges/:vhost/:name/bindings/source returns a list of all bindings in which the given exchange is the source
rspec ./spec/integration/client_spec.rb:343 # RabbitMQ::HTTP::Client GET /api/exchanges/:vhost/:name/bindings/destination returns a list of all bindings in which the given exchange is the source
rspec ./spec/integration/client_spec.rb:378 # RabbitMQ::HTTP::Client GET /api/queues returns a list of all queues
rspec ./spec/integration/client_spec.rb:391 # RabbitMQ::HTTP::Client GET /api/queues/:vhost returns a list of all queues
rspec ./spec/integration/client_spec.rb:405 # RabbitMQ::HTTP::Client GET /api/queues/:vhost/:name when queue exists returns information about a queue
rspec ./spec/integration/client_spec.rb:435 # RabbitMQ::HTTP::Client PUT /api/queues/:vhost/:name declares a queue
rspec ./spec/integration/client_spec.rb:450 # RabbitMQ::HTTP::Client DELETE /api/queues/:vhost/:name deletes a queue
rspec ./spec/integration/client_spec.rb:461 # RabbitMQ::HTTP::Client GET /api/queues/:vhost/:name/bindings returns a list of bindings for a queue
rspec ./spec/integration/client_spec.rb:478 # RabbitMQ::HTTP::Client DELETE /api/queues/:vhost/:name/contents purges a queue
rspec ./spec/integration/client_spec.rb:502 # RabbitMQ::HTTP::Client POST /api/queues/:vhost/:name/get fetches a message from a queue, a la basic.get
rspec ./spec/integration/client_spec.rb:555 # RabbitMQ::HTTP::Client GET /api/bindings/:vhost/e/:exchange/q/:queue returns a list of all bindings between an exchange and a queue
rspec ./spec/integration/client_spec.rb:579 # RabbitMQ::HTTP::Client POST /api/bindings/:vhost/e/:exchange/q/:queue creates a binding between an exchange and a queue
rspec ./spec/integration/client_spec.rb:599 # RabbitMQ::HTTP::Client GET /api/bindings/:vhost/e/:exchange/q/:queue/props returns an individual binding between an exchange and a queue
rspec ./spec/integration/client_spec.rb:620 # RabbitMQ::HTTP::Client DELETE /api/bindings/:vhost/e/:exchange/q/:queue/props deletes an individual binding between an exchange and a queue
rspec ./spec/integration/client_spec.rb:641 # RabbitMQ::HTTP::Client GET /api/vhosts returns a list of vhosts
rspec ./spec/integration/client_spec.rb:652 # RabbitMQ::HTTP::Client GET /api/vhosts/:name when vhost exists returns infomation about a vhost
rspec ./spec/integration/client_spec.rb:866 # RabbitMQ::HTTP::Client GET /api/aliveness-test/:vhost performs aliveness check

support optional parameters, e.g. columns

Per RMQ Management HTTP API:

Many URIs return lists. [...] You can also restrict what information is returned per item with the columns parameter. This is a comma-separated list of subfields separated by dots. See the example below.

It would be great if I could, for example, say something likeclient.list_queues(:columns=>["name","messages"]) This particular use case can reduce both client and server load when incurred when doing monitoring checks using the client for clusters with 100s or 1000s of queues.

Not able to detect 401 unauthorised

When I run client.overview, I can see in stderr: "Faraday::ClientError: the server responded with status 401"

I cannot find a programatic way of accessing this however. It would be great if this gem raised an exception for authentication errors or provide some other way to detect this.

decode_resource fails when response.body is nil

With the introduction of v1.15.0, some errors started appearing on CI builds of a project using this library for tests. The errors are related to the decode_resource method of the http/client.rb, which seem to indicate that response.body is nil.

NoMethodError:
  undefined method `empty?' for nil:NilClass
# /bundle/ruby/2.4.0/gems/rabbitmq_http_api_client-1.15.0/lib/rabbitmq/http/client.rb:429:in `decode_resource'
# /bundle/ruby/2.4.0/gems/rabbitmq_http_api_client-1.15.0/lib/rabbitmq/http/client.rb:134:in `delete_exchange'
# ./spec/support/helpers/rabbitmq_management.rb:11:in `block in delete_exchanges'
# ./spec/support/helpers/rabbitmq_management.rb:11:in `each'
# ./spec/support/helpers/rabbitmq_management.rb:11:in `delete_exchanges'
# ./spec/integration/consumer/event_consumer_spec.rb:27:in `block (2 levels) in <top (required)>'

The CI job used faraday 1.3.0, faraday-net_http 1.0.1 and faraday_middleware 1.0.0.
Downgrading this library to v1.14.0 fixed the problem.

Hosts containing path

Currently BigWig RabbitMQ provides URIs that contain a path on it. It doesn't work with rabbitmq_http_api_client and Faraday. Spefically, this issue talks about how a connection needs to be initialized to have path_prefix.

I'm going to be working on a PR for this. I'm thinking of:

  • taking the path from the URI provided
  • checking if it contains the "/api" RabbitMQ management url's, appending it if necessary.
  • the path_prefix along to the Faraday connection initialization.

Let me know if you have any feedback on this.

Faraday upgrade

This is still requiring quite an old version of Faraday, which is forcing me to use older versions of other gems in my project.

Unable to create users without tags - status 400

client.create_user('test', password: 'test')
Faraday::ClientError: the server responded with status 400
from /usr/local/lib/ruby/gems/2.2.0/gems/faraday-0.9.1/lib/faraday/response/raise_error.rb:13:in `on_complete'

Maybe the method should do some argument verifying and provide an empty list of tags if missing?

unable to set faraday adapters

When trying to set faraday adapters explicitly, there is an issue. Apparently you can't pass adapter as an option while initializing a new Faraday connection.

I have tried to fix it in #31

Invalid URI encoding with `CGI.escape()`

The client uses CGI.escape() to escape path segments in the created URLs. Unfortunately, this module produces invalid path segments: it transforms spaces to +. This is fine for query string, but not path segments.

Newer versions of Cowboy, which have a stricter URL parsing, leave the + characters alone. So when a user of this HTTP client wants to create eg. a vhost with a space in it, he ends up with a vhost with a +.

Release a new gem version

When will the new version of the gem be released? There are some important commits made, like the faraday update and delete_queue, that would be nice to see in a new release.

How do you add permissions for new user on a new vhost?

Hey,

Thanks for the great gem. I was playing around and did not found how do you add permissions to a new created user on a new created vhost ? Here is what I tried so far:

c = RabbitMQ::HTTP::Client.new("http://guest:[email protected]:15672")
c.create_vhost("test")
c.create_user('foo', tags: 'foo', password: 'please')
c.user_permissions('foo')
=> []
c.update_permissions_of('test', 'foo', write: '.*', read: '.*')
=> Faraday::Error::ClientError: the server responded with status 400

Thanks for your help.

Recommend Projects

  • React photo React

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

  • Vue.js photo Vue.js

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

  • Typescript photo Typescript

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

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

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

  • web

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

  • server

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

  • Machine learning

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

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

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

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.