vx-consumer 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +17 -0
- data/.rspec +3 -0
- data/.travis.yml +14 -0
- data/Gemfile +8 -0
- data/LICENSE.txt +22 -0
- data/README.md +29 -0
- data/Rakefile +1 -0
- data/lib/vx/consumer.rb +124 -0
- data/lib/vx/consumer/ack.rb +40 -0
- data/lib/vx/consumer/configuration.rb +64 -0
- data/lib/vx/consumer/error.rb +8 -0
- data/lib/vx/consumer/instrument.rb +21 -0
- data/lib/vx/consumer/params.rb +68 -0
- data/lib/vx/consumer/publish.rb +45 -0
- data/lib/vx/consumer/serializer.rb +88 -0
- data/lib/vx/consumer/session.rb +119 -0
- data/lib/vx/consumer/subscribe.rb +77 -0
- data/lib/vx/consumer/subscriber.rb +26 -0
- data/lib/vx/consumer/testing.rb +47 -0
- data/lib/vx/consumer/version.rb +5 -0
- data/spec/lib/consumer_spec.rb +116 -0
- data/spec/lib/serializer_spec.rb +43 -0
- data/spec/lib/session_spec.rb +29 -0
- data/spec/spec_helper.rb +19 -0
- data/spec/support/beefcake_test_message.rb +8 -0
- data/spec/support/test_consumers.rb +46 -0
- data/vx-consumer.gemspec +27 -0
- metadata +148 -0
    
        checksums.yaml
    ADDED
    
    | @@ -0,0 +1,7 @@ | |
| 1 | 
            +
            ---
         | 
| 2 | 
            +
            SHA1:
         | 
| 3 | 
            +
              metadata.gz: ac03dcb9650a50486b30d3894d974a034e51db69
         | 
| 4 | 
            +
              data.tar.gz: da8ffda3715e6d70cdc955508e954bad26ba3c0d
         | 
| 5 | 
            +
            SHA512:
         | 
| 6 | 
            +
              metadata.gz: ae6f10ae48f6e94efe9dc47663ad9acaef54e78457068b03b1fd1411ae8d043bd7026b27a9e0651decb9c8f47f98e853cf6bfcc09df3009abdfd9f9535b15bec
         | 
| 7 | 
            +
              data.tar.gz: aa8694409352d12979039a539771ee5a8c8d50a2509f8b196f91bd336626122df29c54965cdb8883e44d02f36621324106ee6c9003d8b2c88c9bc10f6686d93b
         | 
    
        data/.gitignore
    ADDED
    
    
    
        data/.rspec
    ADDED
    
    
    
        data/.travis.yml
    ADDED
    
    
    
        data/Gemfile
    ADDED
    
    
    
        data/LICENSE.txt
    ADDED
    
    | @@ -0,0 +1,22 @@ | |
| 1 | 
            +
            Copyright (c) 2014 Dmitry Galinsky
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            MIT License
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            Permission is hereby granted, free of charge, to any person obtaining
         | 
| 6 | 
            +
            a copy of this software and associated documentation files (the
         | 
| 7 | 
            +
            "Software"), to deal in the Software without restriction, including
         | 
| 8 | 
            +
            without limitation the rights to use, copy, modify, merge, publish,
         | 
| 9 | 
            +
            distribute, sublicense, and/or sell copies of the Software, and to
         | 
| 10 | 
            +
            permit persons to whom the Software is furnished to do so, subject to
         | 
| 11 | 
            +
            the following conditions:
         | 
| 12 | 
            +
             | 
| 13 | 
            +
            The above copyright notice and this permission notice shall be
         | 
| 14 | 
            +
            included in all copies or substantial portions of the Software.
         | 
| 15 | 
            +
             | 
| 16 | 
            +
            THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
         | 
| 17 | 
            +
            EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
         | 
| 18 | 
            +
            MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
         | 
| 19 | 
            +
            NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
         | 
| 20 | 
            +
            LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
         | 
| 21 | 
            +
            OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
         | 
| 22 | 
            +
            WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
         | 
    
        data/README.md
    ADDED
    
    | @@ -0,0 +1,29 @@ | |
| 1 | 
            +
            # Vx::Consumer
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            TODO: Write a gem description
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            ## Installation
         | 
| 6 | 
            +
             | 
| 7 | 
            +
            Add this line to your application's Gemfile:
         | 
| 8 | 
            +
             | 
| 9 | 
            +
                gem 'vx-consumer'
         | 
| 10 | 
            +
             | 
| 11 | 
            +
            And then execute:
         | 
| 12 | 
            +
             | 
| 13 | 
            +
                $ bundle
         | 
| 14 | 
            +
             | 
| 15 | 
            +
            Or install it yourself as:
         | 
| 16 | 
            +
             | 
| 17 | 
            +
                $ gem install vx-consumer
         | 
| 18 | 
            +
             | 
| 19 | 
            +
            ## Usage
         | 
| 20 | 
            +
             | 
| 21 | 
            +
            TODO: Write usage instructions here
         | 
| 22 | 
            +
             | 
| 23 | 
            +
            ## Contributing
         | 
| 24 | 
            +
             | 
| 25 | 
            +
            1. Fork it ( http://github.com/<my-github-username>/vx-consumer/fork )
         | 
| 26 | 
            +
            2. Create your feature branch (`git checkout -b my-new-feature`)
         | 
| 27 | 
            +
            3. Commit your changes (`git commit -am 'Add some feature'`)
         | 
| 28 | 
            +
            4. Push to the branch (`git push origin my-new-feature`)
         | 
| 29 | 
            +
            5. Create new Pull Request
         | 
    
        data/Rakefile
    ADDED
    
    | @@ -0,0 +1 @@ | |
| 1 | 
            +
            require "bundler/gem_tasks"
         | 
    
        data/lib/vx/consumer.rb
    ADDED
    
    | @@ -0,0 +1,124 @@ | |
| 1 | 
            +
            %w{
         | 
| 2 | 
            +
              version
         | 
| 3 | 
            +
              error
         | 
| 4 | 
            +
              configuration
         | 
| 5 | 
            +
              instrument
         | 
| 6 | 
            +
              session
         | 
| 7 | 
            +
              params
         | 
| 8 | 
            +
              serializer
         | 
| 9 | 
            +
              subscriber
         | 
| 10 | 
            +
              publish
         | 
| 11 | 
            +
              subscribe
         | 
| 12 | 
            +
              ack
         | 
| 13 | 
            +
            }.each do |f|
         | 
| 14 | 
            +
              require File.expand_path("../consumer/#{f}", __FILE__)
         | 
| 15 | 
            +
            end
         | 
| 16 | 
            +
             | 
| 17 | 
            +
            module Vx
         | 
| 18 | 
            +
              module Consumer
         | 
| 19 | 
            +
             | 
| 20 | 
            +
                attr_accessor :properties
         | 
| 21 | 
            +
                attr_accessor :delivery_info
         | 
| 22 | 
            +
                attr_accessor :channel
         | 
| 23 | 
            +
             | 
| 24 | 
            +
                def self.included(base)
         | 
| 25 | 
            +
                  base.extend ClassMethods
         | 
| 26 | 
            +
                  base.extend Instrument
         | 
| 27 | 
            +
                  base.extend Publish
         | 
| 28 | 
            +
                  base.extend Subscribe
         | 
| 29 | 
            +
                  base.send :include, Ack
         | 
| 30 | 
            +
                  base.send :include, Instrument
         | 
| 31 | 
            +
                end
         | 
| 32 | 
            +
             | 
| 33 | 
            +
                module ClassMethods
         | 
| 34 | 
            +
                  def params
         | 
| 35 | 
            +
                    @params ||= Params.new(self.name)
         | 
| 36 | 
            +
                  end
         | 
| 37 | 
            +
             | 
| 38 | 
            +
                  def exchange(*args)
         | 
| 39 | 
            +
                    params.exchange_options = args.last.is_a?(Hash) ? args.pop : nil
         | 
| 40 | 
            +
                    params.exchange_name    = args.first
         | 
| 41 | 
            +
                  end
         | 
| 42 | 
            +
             | 
| 43 | 
            +
                  def fanout
         | 
| 44 | 
            +
                    params.exchange_type = :fanout
         | 
| 45 | 
            +
                  end
         | 
| 46 | 
            +
             | 
| 47 | 
            +
                  def topic
         | 
| 48 | 
            +
                    params.exchange_type = :topic
         | 
| 49 | 
            +
                  end
         | 
| 50 | 
            +
             | 
| 51 | 
            +
                  def queue(*args)
         | 
| 52 | 
            +
                    params.queue_options = args.last.is_a?(Hash) ? args.pop : nil
         | 
| 53 | 
            +
                    params.queue_name    = args.first
         | 
| 54 | 
            +
                  end
         | 
| 55 | 
            +
             | 
| 56 | 
            +
                  def routing_key(name)
         | 
| 57 | 
            +
                    params.routing_key = name
         | 
| 58 | 
            +
                  end
         | 
| 59 | 
            +
             | 
| 60 | 
            +
                  def headers(value)
         | 
| 61 | 
            +
                    params.headers = value
         | 
| 62 | 
            +
                  end
         | 
| 63 | 
            +
             | 
| 64 | 
            +
                  def content_type(value)
         | 
| 65 | 
            +
                    params.content_type = value
         | 
| 66 | 
            +
                  end
         | 
| 67 | 
            +
             | 
| 68 | 
            +
                  def ack
         | 
| 69 | 
            +
                    params.ack = true
         | 
| 70 | 
            +
                  end
         | 
| 71 | 
            +
             | 
| 72 | 
            +
                  def model(value)
         | 
| 73 | 
            +
                    params.model = value
         | 
| 74 | 
            +
                  end
         | 
| 75 | 
            +
             | 
| 76 | 
            +
                  def session
         | 
| 77 | 
            +
                    Consumer.session
         | 
| 78 | 
            +
                  end
         | 
| 79 | 
            +
             | 
| 80 | 
            +
                  def configuration
         | 
| 81 | 
            +
                    Consumer.configuration
         | 
| 82 | 
            +
                  end
         | 
| 83 | 
            +
             | 
| 84 | 
            +
                  def with_middlewares(name, env, &block)
         | 
| 85 | 
            +
                    Consumer.configuration.builders[name].to_app(block).call(env)
         | 
| 86 | 
            +
                  end
         | 
| 87 | 
            +
                end
         | 
| 88 | 
            +
             | 
| 89 | 
            +
                extend self
         | 
| 90 | 
            +
             | 
| 91 | 
            +
                @@session       = Session.new
         | 
| 92 | 
            +
                @@configuration = Configuration.new
         | 
| 93 | 
            +
             | 
| 94 | 
            +
                def shutdown
         | 
| 95 | 
            +
                  session.shutdown
         | 
| 96 | 
            +
                end
         | 
| 97 | 
            +
             | 
| 98 | 
            +
                def shutdown?
         | 
| 99 | 
            +
                  session.shutdown?
         | 
| 100 | 
            +
                end
         | 
| 101 | 
            +
             | 
| 102 | 
            +
                def configure
         | 
| 103 | 
            +
                  yield configuration
         | 
| 104 | 
            +
                end
         | 
| 105 | 
            +
             | 
| 106 | 
            +
                def configuration
         | 
| 107 | 
            +
                  @@configuration
         | 
| 108 | 
            +
                end
         | 
| 109 | 
            +
             | 
| 110 | 
            +
                def session
         | 
| 111 | 
            +
                  @@session
         | 
| 112 | 
            +
                end
         | 
| 113 | 
            +
             | 
| 114 | 
            +
                def exception_handler(e, env)
         | 
| 115 | 
            +
                  $stderr.puts "#{e.class}: #{e.message}, env: #{env.inspect}"
         | 
| 116 | 
            +
                  $stderr.puts e.backtrace.map{|b| "\t#{b}" }.join("\n")
         | 
| 117 | 
            +
                  unless env.is_a?(Hash)
         | 
| 118 | 
            +
                    env = {env: env}
         | 
| 119 | 
            +
                  end
         | 
| 120 | 
            +
                  configuration.on_error.call(e, env)
         | 
| 121 | 
            +
                end
         | 
| 122 | 
            +
             | 
| 123 | 
            +
              end
         | 
| 124 | 
            +
            end
         | 
| @@ -0,0 +1,40 @@ | |
| 1 | 
            +
            module Vx
         | 
| 2 | 
            +
              module Consumer
         | 
| 3 | 
            +
                module Ack
         | 
| 4 | 
            +
             | 
| 5 | 
            +
                  def ack(multiple = false)
         | 
| 6 | 
            +
                    instrumentation = {
         | 
| 7 | 
            +
                      consumer:      self.class.params.consumer_name,
         | 
| 8 | 
            +
                      properties:    properties,
         | 
| 9 | 
            +
                      multiple:      multiple,
         | 
| 10 | 
            +
                    }
         | 
| 11 | 
            +
                    if channel.open?
         | 
| 12 | 
            +
                      channel.ack delivery_info.delivery_tag, multiple
         | 
| 13 | 
            +
                      instrument("ack", instrumentation)
         | 
| 14 | 
            +
                      true
         | 
| 15 | 
            +
                    else
         | 
| 16 | 
            +
                      instrument("ack_failed", instrumentation)
         | 
| 17 | 
            +
                      false
         | 
| 18 | 
            +
                    end
         | 
| 19 | 
            +
                  end
         | 
| 20 | 
            +
             | 
| 21 | 
            +
                  def nack(multiple = false, requeue = false)
         | 
| 22 | 
            +
                    instrumentation = {
         | 
| 23 | 
            +
                      consumer:      self.class.params.consumer_name,
         | 
| 24 | 
            +
                      properties:    properties,
         | 
| 25 | 
            +
                      multiple:      multiple,
         | 
| 26 | 
            +
                      requeue:       requeue,
         | 
| 27 | 
            +
                    }
         | 
| 28 | 
            +
                    if channel.open?
         | 
| 29 | 
            +
                      channel.ack delivery_info.delivery_tag, multiple, requeue
         | 
| 30 | 
            +
                      instrument("nack", instrumentation)
         | 
| 31 | 
            +
                      true
         | 
| 32 | 
            +
                    else
         | 
| 33 | 
            +
                      instrument("nack_failed", instrumentation)
         | 
| 34 | 
            +
                      false
         | 
| 35 | 
            +
                    end
         | 
| 36 | 
            +
                  end
         | 
| 37 | 
            +
             | 
| 38 | 
            +
                end
         | 
| 39 | 
            +
              end
         | 
| 40 | 
            +
            end
         | 
| @@ -0,0 +1,64 @@ | |
| 1 | 
            +
            require 'vx/common/rack/builder'
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module Vx
         | 
| 4 | 
            +
              module Consumer
         | 
| 5 | 
            +
                class Configuration
         | 
| 6 | 
            +
             | 
| 7 | 
            +
                  attr_accessor :default_exchange_options, :default_queue_options,
         | 
| 8 | 
            +
                    :default_publish_options, :default_exchange_type, :pool_timeout,
         | 
| 9 | 
            +
                    :heartbeat, :spawn_attempts, :content_type, :instrumenter, :debug,
         | 
| 10 | 
            +
                    :on_error, :builders, :prefetch
         | 
| 11 | 
            +
             | 
| 12 | 
            +
                  def initialize
         | 
| 13 | 
            +
                    reset!
         | 
| 14 | 
            +
                  end
         | 
| 15 | 
            +
             | 
| 16 | 
            +
                  def debug?
         | 
| 17 | 
            +
                    ENV['VX_CONSUMER_DEBUG']
         | 
| 18 | 
            +
                  end
         | 
| 19 | 
            +
             | 
| 20 | 
            +
                  def use(target, middleware, *args)
         | 
| 21 | 
            +
                    @builders[target].use middleware, *args
         | 
| 22 | 
            +
                  end
         | 
| 23 | 
            +
             | 
| 24 | 
            +
                  def on_error(&block)
         | 
| 25 | 
            +
                    @on_error = block if block
         | 
| 26 | 
            +
                    @on_error
         | 
| 27 | 
            +
                  end
         | 
| 28 | 
            +
             | 
| 29 | 
            +
                  def reset!
         | 
| 30 | 
            +
                    @default_exchange_type = :topic
         | 
| 31 | 
            +
                    @pool_timeout          = 0.5
         | 
| 32 | 
            +
                    @heartbeat             = :server
         | 
| 33 | 
            +
             | 
| 34 | 
            +
                    @spawn_attempts        = 1
         | 
| 35 | 
            +
             | 
| 36 | 
            +
                    @content_type          = 'application/json'
         | 
| 37 | 
            +
                    @prefetch              = 1
         | 
| 38 | 
            +
             | 
| 39 | 
            +
                    @instrumenter          = nil
         | 
| 40 | 
            +
                    @on_error              = ->(e, env){ nil }
         | 
| 41 | 
            +
             | 
| 42 | 
            +
                    @builders = {
         | 
| 43 | 
            +
                      pub: Vx::Common::Rack::Builder.new,
         | 
| 44 | 
            +
                      sub: Vx::Common::Rack::Builder.new
         | 
| 45 | 
            +
                    }
         | 
| 46 | 
            +
             | 
| 47 | 
            +
                    @default_exchange_options = {
         | 
| 48 | 
            +
                      durable:     true,
         | 
| 49 | 
            +
                      auto_delete: false
         | 
| 50 | 
            +
                    }
         | 
| 51 | 
            +
             | 
| 52 | 
            +
                    @default_queue_options = {
         | 
| 53 | 
            +
                      durable:      true,
         | 
| 54 | 
            +
                      auto_delete:  false,
         | 
| 55 | 
            +
                      exclusive:    false
         | 
| 56 | 
            +
                    }
         | 
| 57 | 
            +
             | 
| 58 | 
            +
                    @default_publish_options = {
         | 
| 59 | 
            +
                    }
         | 
| 60 | 
            +
                  end
         | 
| 61 | 
            +
             | 
| 62 | 
            +
                end
         | 
| 63 | 
            +
              end
         | 
| 64 | 
            +
            end
         | 
| @@ -0,0 +1,21 @@ | |
| 1 | 
            +
            module Vx
         | 
| 2 | 
            +
              module Consumer
         | 
| 3 | 
            +
                module Instrument
         | 
| 4 | 
            +
             | 
| 5 | 
            +
                  def instrument(name, payload, &block)
         | 
| 6 | 
            +
                    name = "#{name}.consumer.vx"
         | 
| 7 | 
            +
             | 
| 8 | 
            +
                    if Consumer.configuration.debug?
         | 
| 9 | 
            +
                      $stdout.puts " --> #{name}: #{payload}"
         | 
| 10 | 
            +
                    end
         | 
| 11 | 
            +
             | 
| 12 | 
            +
                    if Consumer.configuration.instrumenter
         | 
| 13 | 
            +
                      Consumer.configuration.instrumenter.instrument(name, payload, &block)
         | 
| 14 | 
            +
                    else
         | 
| 15 | 
            +
                      yield if block_given?
         | 
| 16 | 
            +
                    end
         | 
| 17 | 
            +
                  end
         | 
| 18 | 
            +
             | 
| 19 | 
            +
                end
         | 
| 20 | 
            +
              end
         | 
| 21 | 
            +
            end
         | 
| @@ -0,0 +1,68 @@ | |
| 1 | 
            +
            module Vx
         | 
| 2 | 
            +
              module Consumer
         | 
| 3 | 
            +
                Params = Struct.new(:consumer_class) do
         | 
| 4 | 
            +
             | 
| 5 | 
            +
                  attr_accessor :exchange_name, :exchange_options
         | 
| 6 | 
            +
                  attr_accessor :queue_name, :queue_options
         | 
| 7 | 
            +
                  attr_accessor :routing_key, :headers
         | 
| 8 | 
            +
                  attr_accessor :content_type
         | 
| 9 | 
            +
                  attr_accessor :ack
         | 
| 10 | 
            +
                  attr_accessor :exchange_type
         | 
| 11 | 
            +
                  attr_accessor :model
         | 
| 12 | 
            +
             | 
| 13 | 
            +
                  def exchange_name
         | 
| 14 | 
            +
                    @exchange_name || default_exchange_name
         | 
| 15 | 
            +
                  end
         | 
| 16 | 
            +
             | 
| 17 | 
            +
                  def queue_name
         | 
| 18 | 
            +
                    @queue_name || ""
         | 
| 19 | 
            +
                  end
         | 
| 20 | 
            +
             | 
| 21 | 
            +
                  def ack
         | 
| 22 | 
            +
                    !!@ack
         | 
| 23 | 
            +
                  end
         | 
| 24 | 
            +
             | 
| 25 | 
            +
                  def content_type
         | 
| 26 | 
            +
                    @content_type || config.content_type
         | 
| 27 | 
            +
                  end
         | 
| 28 | 
            +
             | 
| 29 | 
            +
                  def exchange_type
         | 
| 30 | 
            +
                    @exchange_type || config.default_exchange_type
         | 
| 31 | 
            +
                  end
         | 
| 32 | 
            +
             | 
| 33 | 
            +
                  def exchange_options
         | 
| 34 | 
            +
                    (@exchange_options || config.default_exchange_options).merge(type: exchange_type)
         | 
| 35 | 
            +
                  end
         | 
| 36 | 
            +
             | 
| 37 | 
            +
                  def queue_options
         | 
| 38 | 
            +
                    @queue_options || config.default_queue_options
         | 
| 39 | 
            +
                  end
         | 
| 40 | 
            +
             | 
| 41 | 
            +
                  def publish_options
         | 
| 42 | 
            +
                    config.default_publish_options
         | 
| 43 | 
            +
                  end
         | 
| 44 | 
            +
             | 
| 45 | 
            +
                  def bind_options
         | 
| 46 | 
            +
                    opts = { }
         | 
| 47 | 
            +
                    opts.merge!(routing_key: routing_key) if routing_key
         | 
| 48 | 
            +
                    opts.merge!(headers: headers) if headers
         | 
| 49 | 
            +
                    opts
         | 
| 50 | 
            +
                  end
         | 
| 51 | 
            +
             | 
| 52 | 
            +
                  def consumer_name
         | 
| 53 | 
            +
                    consumer_class.to_s
         | 
| 54 | 
            +
                  end
         | 
| 55 | 
            +
             | 
| 56 | 
            +
                  private
         | 
| 57 | 
            +
             | 
| 58 | 
            +
                    def config
         | 
| 59 | 
            +
                      Consumer.configuration
         | 
| 60 | 
            +
                    end
         | 
| 61 | 
            +
             | 
| 62 | 
            +
                    def default_exchange_name
         | 
| 63 | 
            +
                      "amq.#{exchange_type}"
         | 
| 64 | 
            +
                    end
         | 
| 65 | 
            +
             | 
| 66 | 
            +
                end
         | 
| 67 | 
            +
              end
         | 
| 68 | 
            +
            end
         | 
| @@ -0,0 +1,45 @@ | |
| 1 | 
            +
            require 'securerandom'
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module Vx
         | 
| 4 | 
            +
              module Consumer
         | 
| 5 | 
            +
                module Publish
         | 
| 6 | 
            +
             | 
| 7 | 
            +
                  def publish(payload, options = {})
         | 
| 8 | 
            +
                    session.open
         | 
| 9 | 
            +
             | 
| 10 | 
            +
                    options ||= {}
         | 
| 11 | 
            +
                    options[:routing_key]  = params.routing_key if params.routing_key && !options.key?(:routing_key)
         | 
| 12 | 
            +
                    options[:headers]      = params.headers     if params.headers && !options.key?(:headers)
         | 
| 13 | 
            +
             | 
| 14 | 
            +
                    options[:content_type] ||= params.content_type || configuration.content_type
         | 
| 15 | 
            +
                    options[:message_id]   ||= SecureRandom.uuid
         | 
| 16 | 
            +
             | 
| 17 | 
            +
                    name = params.exchange_name
         | 
| 18 | 
            +
             | 
| 19 | 
            +
                    instrumentation = {
         | 
| 20 | 
            +
                      payload:     payload,
         | 
| 21 | 
            +
                      exchange:    name,
         | 
| 22 | 
            +
                      consumer:    params.consumer_name,
         | 
| 23 | 
            +
                      properties:  options,
         | 
| 24 | 
            +
                    }
         | 
| 25 | 
            +
             | 
| 26 | 
            +
                    with_middlewares :pub, instrumentation do
         | 
| 27 | 
            +
                      instrument("process_publishing", instrumentation) do
         | 
| 28 | 
            +
                        session.with_channel do |ch|
         | 
| 29 | 
            +
                          encoded = encode_payload(payload, options[:content_type])
         | 
| 30 | 
            +
                          x = session.declare_exchange ch, name, params.exchange_options
         | 
| 31 | 
            +
                          x.publish encoded, options
         | 
| 32 | 
            +
                        end
         | 
| 33 | 
            +
                      end
         | 
| 34 | 
            +
                    end
         | 
| 35 | 
            +
                  end
         | 
| 36 | 
            +
             | 
| 37 | 
            +
                  private
         | 
| 38 | 
            +
             | 
| 39 | 
            +
                    def encode_payload(payload, content_type)
         | 
| 40 | 
            +
                      Serializer.pack(content_type, payload)
         | 
| 41 | 
            +
                    end
         | 
| 42 | 
            +
             | 
| 43 | 
            +
                end
         | 
| 44 | 
            +
              end
         | 
| 45 | 
            +
            end
         | 
| @@ -0,0 +1,88 @@ | |
| 1 | 
            +
            module Vx
         | 
| 2 | 
            +
              module Consumer
         | 
| 3 | 
            +
                class Serializer
         | 
| 4 | 
            +
                  @@types = {}
         | 
| 5 | 
            +
             | 
| 6 | 
            +
                  Type = Struct.new(:content_type) do
         | 
| 7 | 
            +
                    def pack(&block)
         | 
| 8 | 
            +
                      @pack = block if block_given?
         | 
| 9 | 
            +
                      @pack
         | 
| 10 | 
            +
                    end
         | 
| 11 | 
            +
             | 
| 12 | 
            +
                    def unpack(&block)
         | 
| 13 | 
            +
                      @unpack = block if block_given?
         | 
| 14 | 
            +
                      @unpack
         | 
| 15 | 
            +
                    end
         | 
| 16 | 
            +
                  end
         | 
| 17 | 
            +
             | 
| 18 | 
            +
                  class << self
         | 
| 19 | 
            +
                    def types
         | 
| 20 | 
            +
                      @@types
         | 
| 21 | 
            +
                    end
         | 
| 22 | 
            +
             | 
| 23 | 
            +
                    def define(content_type, &block)
         | 
| 24 | 
            +
                      fmt = Type.new content_type
         | 
| 25 | 
            +
                      fmt.instance_eval(&block)
         | 
| 26 | 
            +
                      types.merge! content_type => fmt
         | 
| 27 | 
            +
                    end
         | 
| 28 | 
            +
             | 
| 29 | 
            +
                    def lookup(content_type)
         | 
| 30 | 
            +
                      types[content_type]
         | 
| 31 | 
            +
                    end
         | 
| 32 | 
            +
             | 
| 33 | 
            +
                    def pack(content_type, body)
         | 
| 34 | 
            +
                      if fmt = lookup(content_type)
         | 
| 35 | 
            +
                        fmt.pack.call(body)
         | 
| 36 | 
            +
                      end
         | 
| 37 | 
            +
                    end
         | 
| 38 | 
            +
             | 
| 39 | 
            +
                    def unpack(content_type, body, model)
         | 
| 40 | 
            +
                      if fmt = lookup(content_type)
         | 
| 41 | 
            +
                        fmt.unpack.call(body, model)
         | 
| 42 | 
            +
                      end
         | 
| 43 | 
            +
                    end
         | 
| 44 | 
            +
                  end
         | 
| 45 | 
            +
             | 
| 46 | 
            +
                  define 'text/plain' do
         | 
| 47 | 
            +
                    pack do |body|
         | 
| 48 | 
            +
                      body.to_s
         | 
| 49 | 
            +
                    end
         | 
| 50 | 
            +
             | 
| 51 | 
            +
                    unpack do |body, _|
         | 
| 52 | 
            +
                      body
         | 
| 53 | 
            +
                    end
         | 
| 54 | 
            +
                  end
         | 
| 55 | 
            +
             | 
| 56 | 
            +
                  define 'application/json' do
         | 
| 57 | 
            +
                    pack do |body|
         | 
| 58 | 
            +
                      if body.is_a?(String)
         | 
| 59 | 
            +
                        body
         | 
| 60 | 
            +
                      else
         | 
| 61 | 
            +
                        ::JSON.dump body
         | 
| 62 | 
            +
                      end
         | 
| 63 | 
            +
                    end
         | 
| 64 | 
            +
             | 
| 65 | 
            +
                    unpack do |payload, model|
         | 
| 66 | 
            +
                      if model && model.respond_to?(:from_json)
         | 
| 67 | 
            +
                        model.from_json payload
         | 
| 68 | 
            +
                      else
         | 
| 69 | 
            +
                        ::JSON.parse(payload)
         | 
| 70 | 
            +
                      end
         | 
| 71 | 
            +
                    end
         | 
| 72 | 
            +
                  end
         | 
| 73 | 
            +
             | 
| 74 | 
            +
                  define 'application/x-protobuf' do
         | 
| 75 | 
            +
             | 
| 76 | 
            +
                    pack do |object|
         | 
| 77 | 
            +
                      object.encode.to_s
         | 
| 78 | 
            +
                    end
         | 
| 79 | 
            +
             | 
| 80 | 
            +
                    unpack do |payload, model|
         | 
| 81 | 
            +
                      raise ModelIsNotDefined unless model
         | 
| 82 | 
            +
                      model.decode payload
         | 
| 83 | 
            +
                    end
         | 
| 84 | 
            +
                  end
         | 
| 85 | 
            +
             | 
| 86 | 
            +
                end
         | 
| 87 | 
            +
              end
         | 
| 88 | 
            +
            end
         | 
| @@ -0,0 +1,119 @@ | |
| 1 | 
            +
            require 'bunny'
         | 
| 2 | 
            +
            require 'thread'
         | 
| 3 | 
            +
             | 
| 4 | 
            +
            module Vx
         | 
| 5 | 
            +
              module Consumer
         | 
| 6 | 
            +
                class Session
         | 
| 7 | 
            +
             | 
| 8 | 
            +
                  include Instrument
         | 
| 9 | 
            +
             | 
| 10 | 
            +
                  @@session_lock = Mutex.new
         | 
| 11 | 
            +
             | 
| 12 | 
            +
                  attr_reader :conn
         | 
| 13 | 
            +
             | 
| 14 | 
            +
                  def shutdown
         | 
| 15 | 
            +
                    @shutdown = true
         | 
| 16 | 
            +
                  end
         | 
| 17 | 
            +
             | 
| 18 | 
            +
                  def shutdown?
         | 
| 19 | 
            +
                    !!@shutdown
         | 
| 20 | 
            +
                  end
         | 
| 21 | 
            +
             | 
| 22 | 
            +
                  def resume
         | 
| 23 | 
            +
                    @shutdown = false
         | 
| 24 | 
            +
                  end
         | 
| 25 | 
            +
             | 
| 26 | 
            +
                  def close
         | 
| 27 | 
            +
                    if open?
         | 
| 28 | 
            +
                      @@session_lock.synchronize do
         | 
| 29 | 
            +
                        instrument("closing_collection", info: conn_info)
         | 
| 30 | 
            +
             | 
| 31 | 
            +
                        instrument("close_collection", info: conn_info) do
         | 
| 32 | 
            +
                          begin
         | 
| 33 | 
            +
                            conn.close
         | 
| 34 | 
            +
                            while conn.status != :closed
         | 
| 35 | 
            +
                              sleep 0.01
         | 
| 36 | 
            +
                            end
         | 
| 37 | 
            +
                          rescue Bunny::ChannelError, Bunny::ClientTimeout => e
         | 
| 38 | 
            +
                            $stderr.puts "got #{e.class} #{e.message} in Vx::Consumer::Session#close"
         | 
| 39 | 
            +
                          end
         | 
| 40 | 
            +
                        end
         | 
| 41 | 
            +
                        @conn = nil
         | 
| 42 | 
            +
                      end
         | 
| 43 | 
            +
                    end
         | 
| 44 | 
            +
                  end
         | 
| 45 | 
            +
             | 
| 46 | 
            +
                  def open(options = {})
         | 
| 47 | 
            +
                    return self if open?
         | 
| 48 | 
            +
             | 
| 49 | 
            +
                    @@session_lock.synchronize do
         | 
| 50 | 
            +
                      unless open?
         | 
| 51 | 
            +
                        resume
         | 
| 52 | 
            +
             | 
| 53 | 
            +
                        @conn ||= Bunny.new(
         | 
| 54 | 
            +
                          nil,       # from ENV['RABBITMQ_URL']
         | 
| 55 | 
            +
                          heartbeat: Consumer.configuration.heartbeat,
         | 
| 56 | 
            +
                          automatically_recover: false
         | 
| 57 | 
            +
                        )
         | 
| 58 | 
            +
             | 
| 59 | 
            +
                        instrumentation = { info: conn_info }.merge(options)
         | 
| 60 | 
            +
             | 
| 61 | 
            +
                        instrument("start_connecting", instrumentation)
         | 
| 62 | 
            +
             | 
| 63 | 
            +
                        instrument("connect", instrumentation) do
         | 
| 64 | 
            +
                          conn.start
         | 
| 65 | 
            +
                          while conn.connecting?
         | 
| 66 | 
            +
                            sleep 0.01
         | 
| 67 | 
            +
                          end
         | 
| 68 | 
            +
                        end
         | 
| 69 | 
            +
                      end
         | 
| 70 | 
            +
                    end
         | 
| 71 | 
            +
             | 
| 72 | 
            +
                    self
         | 
| 73 | 
            +
                  end
         | 
| 74 | 
            +
             | 
| 75 | 
            +
                  def open?
         | 
| 76 | 
            +
                    conn && conn.open? && conn.status == :open
         | 
| 77 | 
            +
                  end
         | 
| 78 | 
            +
             | 
| 79 | 
            +
                  def conn_info
         | 
| 80 | 
            +
                    if conn
         | 
| 81 | 
            +
                      "amqp://#{conn.user}@#{conn.host}:#{conn.port}/#{conn.vhost}"
         | 
| 82 | 
            +
                    else
         | 
| 83 | 
            +
                      "not connected"
         | 
| 84 | 
            +
                    end
         | 
| 85 | 
            +
                  end
         | 
| 86 | 
            +
             | 
| 87 | 
            +
                  def with_channel
         | 
| 88 | 
            +
                    assert_connection_is_open
         | 
| 89 | 
            +
             | 
| 90 | 
            +
                    conn.with_channel { |ch| yield ch }
         | 
| 91 | 
            +
                  end
         | 
| 92 | 
            +
             | 
| 93 | 
            +
                  def declare_exchange(ch, name, options = nil)
         | 
| 94 | 
            +
                    assert_connection_is_open
         | 
| 95 | 
            +
             | 
| 96 | 
            +
                    options  ||= {}
         | 
| 97 | 
            +
                    ch.exchange name, options
         | 
| 98 | 
            +
                  end
         | 
| 99 | 
            +
             | 
| 100 | 
            +
                  def declare_queue(ch, name, options = nil)
         | 
| 101 | 
            +
                    assert_connection_is_open
         | 
| 102 | 
            +
             | 
| 103 | 
            +
                    options ||= {}
         | 
| 104 | 
            +
                    ch.queue name, options
         | 
| 105 | 
            +
                  end
         | 
| 106 | 
            +
             | 
| 107 | 
            +
                  private
         | 
| 108 | 
            +
             | 
| 109 | 
            +
                    def assert_connection_is_open
         | 
| 110 | 
            +
                      open? || raise(ConnectionDoesNotExistError)
         | 
| 111 | 
            +
                    end
         | 
| 112 | 
            +
             | 
| 113 | 
            +
                    def config
         | 
| 114 | 
            +
                      Consumer.configuration
         | 
| 115 | 
            +
                    end
         | 
| 116 | 
            +
             | 
| 117 | 
            +
                end
         | 
| 118 | 
            +
              end
         | 
| 119 | 
            +
            end
         | 
| @@ -0,0 +1,77 @@ | |
| 1 | 
            +
            module Vx
         | 
| 2 | 
            +
              module Consumer
         | 
| 3 | 
            +
                module Subscribe
         | 
| 4 | 
            +
             | 
| 5 | 
            +
                  def subscribe
         | 
| 6 | 
            +
                    ch, q = bind
         | 
| 7 | 
            +
                    bunny_consumer = q.subscribe(block: false, ack: params.ack) do |delivery_info, properties, payload|
         | 
| 8 | 
            +
                      payload = decode_payload properties, payload
         | 
| 9 | 
            +
             | 
| 10 | 
            +
                      instrumentation = {
         | 
| 11 | 
            +
                        consumer:   params.consumer_name,
         | 
| 12 | 
            +
                        payload:    payload,
         | 
| 13 | 
            +
                        properties: properties,
         | 
| 14 | 
            +
                      }
         | 
| 15 | 
            +
             | 
| 16 | 
            +
                      with_middlewares :sub, instrumentation do
         | 
| 17 | 
            +
                        instrument("start_processing", instrumentation)
         | 
| 18 | 
            +
                        instrument("process", instrumentation) do
         | 
| 19 | 
            +
                          run_instance delivery_info, properties, payload, ch
         | 
| 20 | 
            +
                        end
         | 
| 21 | 
            +
                      end
         | 
| 22 | 
            +
                    end
         | 
| 23 | 
            +
             | 
| 24 | 
            +
                    Subscriber.new(bunny_consumer)
         | 
| 25 | 
            +
                  end
         | 
| 26 | 
            +
             | 
| 27 | 
            +
                  def run_instance(delivery_info, properties, payload, channel)
         | 
| 28 | 
            +
                    new.tap do |inst|
         | 
| 29 | 
            +
                      inst.properties    = properties
         | 
| 30 | 
            +
                      inst.delivery_info = delivery_info
         | 
| 31 | 
            +
                      inst.channel       = channel
         | 
| 32 | 
            +
                    end.perform payload
         | 
| 33 | 
            +
                  end
         | 
| 34 | 
            +
             | 
| 35 | 
            +
                  private
         | 
| 36 | 
            +
             | 
| 37 | 
            +
                    def decode_payload(properties, payload)
         | 
| 38 | 
            +
                      Serializer.unpack(properties[:content_type], payload, params.model)
         | 
| 39 | 
            +
                    end
         | 
| 40 | 
            +
             | 
| 41 | 
            +
                    def bind
         | 
| 42 | 
            +
             | 
| 43 | 
            +
                      instrumentation = {
         | 
| 44 | 
            +
                        consumer: params.consumer_name
         | 
| 45 | 
            +
                      }
         | 
| 46 | 
            +
             | 
| 47 | 
            +
                      session.open
         | 
| 48 | 
            +
             | 
| 49 | 
            +
                      ch = session.conn.create_channel
         | 
| 50 | 
            +
                      assign_error_handlers_to_channel(ch)
         | 
| 51 | 
            +
                      ch.prefetch configuration.prefetch
         | 
| 52 | 
            +
             | 
| 53 | 
            +
                      x = session.declare_exchange ch, params.exchange_name, params.exchange_options
         | 
| 54 | 
            +
                      q = session.declare_queue ch, params.queue_name, params.queue_options
         | 
| 55 | 
            +
             | 
| 56 | 
            +
                      instrumentation.merge!(
         | 
| 57 | 
            +
                        exchange:         x.name,
         | 
| 58 | 
            +
                        queue:            q.name,
         | 
| 59 | 
            +
                        queue_options:    params.queue_options,
         | 
| 60 | 
            +
                        exchange_options: params.exchange_options,
         | 
| 61 | 
            +
                        bind:             params.bind_options
         | 
| 62 | 
            +
                      )
         | 
| 63 | 
            +
                      instrument("bind_queue", instrumentation) do
         | 
| 64 | 
            +
                        q.bind(x, params.bind_options)
         | 
| 65 | 
            +
                      end
         | 
| 66 | 
            +
             | 
| 67 | 
            +
                      [ch, q]
         | 
| 68 | 
            +
                    end
         | 
| 69 | 
            +
             | 
| 70 | 
            +
                    def assign_error_handlers_to_channel(ch)
         | 
| 71 | 
            +
                      ch.on_uncaught_exception {|e, c| Consumer.exception_handler(e, consumer: c) }
         | 
| 72 | 
            +
                      ch.on_error {|e, c| Consumer.exception_handler(e, consumer: c) }
         | 
| 73 | 
            +
                    end
         | 
| 74 | 
            +
             | 
| 75 | 
            +
                end
         | 
| 76 | 
            +
              end
         | 
| 77 | 
            +
            end
         | 
| @@ -0,0 +1,26 @@ | |
| 1 | 
            +
            module Vx
         | 
| 2 | 
            +
              module Consumer
         | 
| 3 | 
            +
                Subscriber = Struct.new(:consumer) do
         | 
| 4 | 
            +
             | 
| 5 | 
            +
                  def cancel
         | 
| 6 | 
            +
                    consumer.cancel
         | 
| 7 | 
            +
                    consumer.channel.close unless consumer.channel.closed?
         | 
| 8 | 
            +
                  end
         | 
| 9 | 
            +
             | 
| 10 | 
            +
                  def join
         | 
| 11 | 
            +
                    consumer.channel.work_pool.join
         | 
| 12 | 
            +
                  end
         | 
| 13 | 
            +
             | 
| 14 | 
            +
                  def wait
         | 
| 15 | 
            +
                    loop do
         | 
| 16 | 
            +
                      if Consumer.shutdown?
         | 
| 17 | 
            +
                        cancel
         | 
| 18 | 
            +
                        break
         | 
| 19 | 
            +
                      end
         | 
| 20 | 
            +
                      sleep Consumer.configuration.pool_timeout
         | 
| 21 | 
            +
                    end
         | 
| 22 | 
            +
                  end
         | 
| 23 | 
            +
             | 
| 24 | 
            +
                end
         | 
| 25 | 
            +
              end
         | 
| 26 | 
            +
            end
         | 
| @@ -0,0 +1,47 @@ | |
| 1 | 
            +
            require File.expand_path("../../consumer", __FILE__)
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module Vx
         | 
| 4 | 
            +
              module Consumer
         | 
| 5 | 
            +
             | 
| 6 | 
            +
                module Testing
         | 
| 7 | 
            +
             | 
| 8 | 
            +
                  extend self
         | 
| 9 | 
            +
             | 
| 10 | 
            +
                  @@messages = Hash.new { |h,k| h[k] = [] }
         | 
| 11 | 
            +
                  @@messages_and_options = Hash.new { |h,k| h[k] = [] }
         | 
| 12 | 
            +
             | 
| 13 | 
            +
                  def messages
         | 
| 14 | 
            +
                    @@messages
         | 
| 15 | 
            +
                  end
         | 
| 16 | 
            +
             | 
| 17 | 
            +
                  def messages_and_options
         | 
| 18 | 
            +
                    @@messages_and_options
         | 
| 19 | 
            +
                  end
         | 
| 20 | 
            +
             | 
| 21 | 
            +
                  def clear
         | 
| 22 | 
            +
                    messages.clear
         | 
| 23 | 
            +
                    messages_and_options.clear
         | 
| 24 | 
            +
                  end
         | 
| 25 | 
            +
                end
         | 
| 26 | 
            +
             | 
| 27 | 
            +
                module Consumer::Publish
         | 
| 28 | 
            +
             | 
| 29 | 
            +
                  def publish(message, options = nil)
         | 
| 30 | 
            +
                    options ||= {}
         | 
| 31 | 
            +
                    Testing.messages[params.exchange_name] << message
         | 
| 32 | 
            +
                    Testing.messages_and_options[params.exchange_name] << [message, options]
         | 
| 33 | 
            +
                    self
         | 
| 34 | 
            +
                  end
         | 
| 35 | 
            +
             | 
| 36 | 
            +
                  def messages
         | 
| 37 | 
            +
                    Testing.messages[params.exchange_name]
         | 
| 38 | 
            +
                  end
         | 
| 39 | 
            +
             | 
| 40 | 
            +
                  def messages_and_options
         | 
| 41 | 
            +
                    Testing.messages_and_options[params.exchange_name]
         | 
| 42 | 
            +
                  end
         | 
| 43 | 
            +
             | 
| 44 | 
            +
                end
         | 
| 45 | 
            +
             | 
| 46 | 
            +
              end
         | 
| 47 | 
            +
            end
         | 
| @@ -0,0 +1,116 @@ | |
| 1 | 
            +
            require 'json'
         | 
| 2 | 
            +
            require 'timeout'
         | 
| 3 | 
            +
            require 'spec_helper'
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            describe Vx::Consumer do
         | 
| 6 | 
            +
             | 
| 7 | 
            +
              context "test consumer declaration" do
         | 
| 8 | 
            +
                context "alice" do
         | 
| 9 | 
            +
                  subject { Alice.params }
         | 
| 10 | 
            +
                  its(:exchange_name)    { should eq 'amq.fanout' }
         | 
| 11 | 
            +
                  its(:exchange_options) { should eq(durable: true, auto_delete: false, type: :fanout) }
         | 
| 12 | 
            +
                  its(:queue_name)       { should eq '' }
         | 
| 13 | 
            +
                  its(:queue_options)    { should eq(exclusive: false, durable: true, auto_delete: false) }
         | 
| 14 | 
            +
                  its(:ack)              { should be_false }
         | 
| 15 | 
            +
                  its(:routing_key)      { should eq 'mykey' }
         | 
| 16 | 
            +
                  its(:headers)          { should be_nil }
         | 
| 17 | 
            +
                  its(:content_type)     { should eq 'text/plain' }
         | 
| 18 | 
            +
                end
         | 
| 19 | 
            +
             | 
| 20 | 
            +
                context "bob" do
         | 
| 21 | 
            +
                  subject { Bob.params }
         | 
| 22 | 
            +
                  its(:exchange_name)    { should eq 'bob_exch' }
         | 
| 23 | 
            +
                  its(:exchange_options) { should eq(durable: false, auto_delete: true, type: :topic) }
         | 
| 24 | 
            +
                  its(:queue_name)       { should eq 'bob_queue' }
         | 
| 25 | 
            +
                  its(:queue_options)    { should eq(exclusive: true, durable: false) }
         | 
| 26 | 
            +
                  its(:ack)              { should be_true }
         | 
| 27 | 
            +
                  its(:routing_key)      { should be_nil }
         | 
| 28 | 
            +
                  its(:content_type)     { should eq 'application/json' }
         | 
| 29 | 
            +
                end
         | 
| 30 | 
            +
              end
         | 
| 31 | 
            +
             | 
| 32 | 
            +
              it "simple pub/sub" do
         | 
| 33 | 
            +
                consumer = Bob.subscribe
         | 
| 34 | 
            +
                sleep 1
         | 
| 35 | 
            +
                3.times {|n| Bob.publish("a" => n) }
         | 
| 36 | 
            +
             | 
| 37 | 
            +
                Timeout.timeout(3) do
         | 
| 38 | 
            +
                  loop do
         | 
| 39 | 
            +
                    break if Bob._collected.size == 3
         | 
| 40 | 
            +
                    sleep 0.1
         | 
| 41 | 
            +
                  end
         | 
| 42 | 
            +
                end
         | 
| 43 | 
            +
                consumer.cancel
         | 
| 44 | 
            +
             | 
| 45 | 
            +
                expect(Bob._collected).to eq([{"a"=>0}, {"a"=>1}, {"a"=>2}])
         | 
| 46 | 
            +
              end
         | 
| 47 | 
            +
             | 
| 48 | 
            +
              it "pub/sub in multithreaded environment" do
         | 
| 49 | 
            +
                handle_errors do
         | 
| 50 | 
            +
                  cns = []
         | 
| 51 | 
            +
                  30.times do
         | 
| 52 | 
            +
                    cns << Bob.subscribe
         | 
| 53 | 
            +
                  end
         | 
| 54 | 
            +
             | 
| 55 | 
            +
                  90.times do |n|
         | 
| 56 | 
            +
                    Thread.new do
         | 
| 57 | 
            +
                      Bob.publish("a" => n)
         | 
| 58 | 
            +
                    end
         | 
| 59 | 
            +
                  end
         | 
| 60 | 
            +
             | 
| 61 | 
            +
                  Timeout.timeout(10) do
         | 
| 62 | 
            +
                    loop do
         | 
| 63 | 
            +
                      break if Bob._collected.size == 90
         | 
| 64 | 
            +
                      sleep 0.1
         | 
| 65 | 
            +
                    end
         | 
| 66 | 
            +
                  end
         | 
| 67 | 
            +
                  cns.map(&:cancel)
         | 
| 68 | 
            +
             | 
| 69 | 
            +
                  expect(Bob._collected.map{|c| c["a"] }.sort).to eq((0...90).to_a)
         | 
| 70 | 
            +
                end
         | 
| 71 | 
            +
              end
         | 
| 72 | 
            +
             | 
| 73 | 
            +
              it "should catch errors" do
         | 
| 74 | 
            +
                error = nil
         | 
| 75 | 
            +
                Vx::Consumer.configure do |c|
         | 
| 76 | 
            +
                  c.on_error do |e, env|
         | 
| 77 | 
            +
                    error = [e, env]
         | 
| 78 | 
            +
                  end
         | 
| 79 | 
            +
                end
         | 
| 80 | 
            +
                consumer = Bob.subscribe
         | 
| 81 | 
            +
                sleep 0.1
         | 
| 82 | 
            +
                Bob.publish "not json"
         | 
| 83 | 
            +
             | 
| 84 | 
            +
                sleep 0.1
         | 
| 85 | 
            +
                consumer.cancel
         | 
| 86 | 
            +
             | 
| 87 | 
            +
                expect(error[0]).to be_an_instance_of(JSON::ParserError)
         | 
| 88 | 
            +
                expect(error[1][:consumer]).to_not be_nil
         | 
| 89 | 
            +
              end
         | 
| 90 | 
            +
             | 
| 91 | 
            +
              it "should wait shutdown" do
         | 
| 92 | 
            +
                consumer = Bob.subscribe
         | 
| 93 | 
            +
                Bob.publish a: 1
         | 
| 94 | 
            +
             | 
| 95 | 
            +
                th = Thread.new {
         | 
| 96 | 
            +
                  consumer.wait
         | 
| 97 | 
            +
                }
         | 
| 98 | 
            +
                sleep 1
         | 
| 99 | 
            +
                Vx::Consumer.shutdown
         | 
| 100 | 
            +
             | 
| 101 | 
            +
                Timeout.timeout(1) do
         | 
| 102 | 
            +
                  th.join
         | 
| 103 | 
            +
                end
         | 
| 104 | 
            +
             | 
| 105 | 
            +
                expect(Bob._collected).to eq(["a" => 1])
         | 
| 106 | 
            +
              end
         | 
| 107 | 
            +
             | 
| 108 | 
            +
              def handle_errors
         | 
| 109 | 
            +
                begin
         | 
| 110 | 
            +
                  yield
         | 
| 111 | 
            +
                rescue Exception => e
         | 
| 112 | 
            +
                  Vx::Consumer.exception_handler(e, {})
         | 
| 113 | 
            +
                  raise e
         | 
| 114 | 
            +
                end
         | 
| 115 | 
            +
              end
         | 
| 116 | 
            +
            end
         | 
| @@ -0,0 +1,43 @@ | |
| 1 | 
            +
            require 'json'
         | 
| 2 | 
            +
            require 'beefcake'
         | 
| 3 | 
            +
            require 'spec_helper'
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            describe Vx::Consumer::Serializer do
         | 
| 6 | 
            +
              let(:s) { described_class }
         | 
| 7 | 
            +
             | 
| 8 | 
            +
              context "text/plain" do
         | 
| 9 | 
            +
                let(:payload) { 'payload' }
         | 
| 10 | 
            +
             | 
| 11 | 
            +
                it "should pack payload" do
         | 
| 12 | 
            +
                  expect(s.pack('text/plain', payload)).to eq 'payload'
         | 
| 13 | 
            +
                end
         | 
| 14 | 
            +
             | 
| 15 | 
            +
                it "should unpack payload" do
         | 
| 16 | 
            +
                  expect(s.unpack('text/plain', payload, nil)).to eq 'payload'
         | 
| 17 | 
            +
                end
         | 
| 18 | 
            +
              end
         | 
| 19 | 
            +
             | 
| 20 | 
            +
              context "application/json" do
         | 
| 21 | 
            +
                let(:payload) { {a: 1} }
         | 
| 22 | 
            +
             | 
| 23 | 
            +
                it "should pack payload" do
         | 
| 24 | 
            +
                  expect(s.pack('application/json', payload)).to eq "{\"a\":1}"
         | 
| 25 | 
            +
                end
         | 
| 26 | 
            +
             | 
| 27 | 
            +
                it "should unpack payload" do
         | 
| 28 | 
            +
                  expect(s.unpack('application/json', payload.to_json, nil)).to eq("a"=>1)
         | 
| 29 | 
            +
                end
         | 
| 30 | 
            +
              end
         | 
| 31 | 
            +
             | 
| 32 | 
            +
              context "application/x-protobuf" do
         | 
| 33 | 
            +
                let(:payload) { BeefcakeTestMessage.new(x: 1, y: 2) }
         | 
| 34 | 
            +
             | 
| 35 | 
            +
                it "should pack payload" do
         | 
| 36 | 
            +
                  expect(s.pack('application/x-protobuf', payload)).to eq payload.encode.to_s
         | 
| 37 | 
            +
                end
         | 
| 38 | 
            +
             | 
| 39 | 
            +
                it "should unpack payload" do
         | 
| 40 | 
            +
                  expect(s.unpack('application/x-protobuf', payload.encode.to_s, BeefcakeTestMessage)).to eq payload
         | 
| 41 | 
            +
                end
         | 
| 42 | 
            +
              end
         | 
| 43 | 
            +
            end
         | 
| @@ -0,0 +1,29 @@ | |
| 1 | 
            +
            require 'spec_helper'
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            describe Vx::Consumer::Session do
         | 
| 4 | 
            +
              let(:sess) { described_class.new }
         | 
| 5 | 
            +
              subject { sess }
         | 
| 6 | 
            +
             | 
| 7 | 
            +
              after do
         | 
| 8 | 
            +
                sess.close
         | 
| 9 | 
            +
              end
         | 
| 10 | 
            +
             | 
| 11 | 
            +
              it { should be }
         | 
| 12 | 
            +
             | 
| 13 | 
            +
              it "should successfuly open connection" do
         | 
| 14 | 
            +
                expect {
         | 
| 15 | 
            +
                  sess.open
         | 
| 16 | 
            +
                }.to change(sess, :open?).to(true)
         | 
| 17 | 
            +
              end
         | 
| 18 | 
            +
             | 
| 19 | 
            +
              it "should successfuly open connection in multithread environment" do
         | 
| 20 | 
            +
                (0..10).map do |n|
         | 
| 21 | 
            +
                  Thread.new do
         | 
| 22 | 
            +
                    sess.open
         | 
| 23 | 
            +
                  end
         | 
| 24 | 
            +
                end.map(&:value)
         | 
| 25 | 
            +
             | 
| 26 | 
            +
                expect(sess).to be_open
         | 
| 27 | 
            +
              end
         | 
| 28 | 
            +
             | 
| 29 | 
            +
            end
         | 
    
        data/spec/spec_helper.rb
    ADDED
    
    | @@ -0,0 +1,19 @@ | |
| 1 | 
            +
            require File.expand_path '../../lib/vx/consumer', __FILE__
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require 'rspec/autorun'
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            Dir[File.expand_path("../..", __FILE__) + "/spec/support/**.rb"].each {|f| require f}
         | 
| 6 | 
            +
             | 
| 7 | 
            +
            ENV['VX_CONSUMER_DEBUG'] = '1'
         | 
| 8 | 
            +
             | 
| 9 | 
            +
            RSpec.configure do |config|
         | 
| 10 | 
            +
             | 
| 11 | 
            +
              config.before(:each) do
         | 
| 12 | 
            +
                Vx::Consumer.configuration.reset!
         | 
| 13 | 
            +
              end
         | 
| 14 | 
            +
             | 
| 15 | 
            +
              config.after(:each) do
         | 
| 16 | 
            +
                Vx::Consumer.session.close
         | 
| 17 | 
            +
                Bob._reset
         | 
| 18 | 
            +
              end
         | 
| 19 | 
            +
            end
         | 
| @@ -0,0 +1,46 @@ | |
| 1 | 
            +
            require 'thread'
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            class Alice
         | 
| 4 | 
            +
              include Vx::Consumer
         | 
| 5 | 
            +
             | 
| 6 | 
            +
              content_type 'text/plain'
         | 
| 7 | 
            +
              routing_key 'mykey'
         | 
| 8 | 
            +
             | 
| 9 | 
            +
              fanout
         | 
| 10 | 
            +
             | 
| 11 | 
            +
            end
         | 
| 12 | 
            +
             | 
| 13 | 
            +
            class Bob
         | 
| 14 | 
            +
              include Vx::Consumer
         | 
| 15 | 
            +
             | 
| 16 | 
            +
              exchange 'bob_exch',  durable: false, auto_delete: true
         | 
| 17 | 
            +
              queue    'bob_queue', exclusive: true, durable: false
         | 
| 18 | 
            +
              ack
         | 
| 19 | 
            +
             | 
| 20 | 
            +
              @@m         = Mutex.new
         | 
| 21 | 
            +
              @@collected = []
         | 
| 22 | 
            +
             | 
| 23 | 
            +
              class << self
         | 
| 24 | 
            +
                def _collected
         | 
| 25 | 
            +
                  @@collected
         | 
| 26 | 
            +
                end
         | 
| 27 | 
            +
             | 
| 28 | 
            +
                def _reset
         | 
| 29 | 
            +
                  @@m.synchronize do
         | 
| 30 | 
            +
                    @@collected = []
         | 
| 31 | 
            +
                  end
         | 
| 32 | 
            +
                end
         | 
| 33 | 
            +
             | 
| 34 | 
            +
                def _save(payload)
         | 
| 35 | 
            +
                  @@m.synchronize do
         | 
| 36 | 
            +
                    @@collected << payload
         | 
| 37 | 
            +
                  end
         | 
| 38 | 
            +
                end
         | 
| 39 | 
            +
              end
         | 
| 40 | 
            +
             | 
| 41 | 
            +
              def perform(payload)
         | 
| 42 | 
            +
                self.class._save payload
         | 
| 43 | 
            +
                sleep 0.1
         | 
| 44 | 
            +
                ack
         | 
| 45 | 
            +
              end
         | 
| 46 | 
            +
            end
         | 
    
        data/vx-consumer.gemspec
    ADDED
    
    | @@ -0,0 +1,27 @@ | |
| 1 | 
            +
            # coding: utf-8
         | 
| 2 | 
            +
            lib = File.expand_path('../lib', __FILE__)
         | 
| 3 | 
            +
            $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
         | 
| 4 | 
            +
            require 'vx/consumer/version'
         | 
| 5 | 
            +
             | 
| 6 | 
            +
            Gem::Specification.new do |spec|
         | 
| 7 | 
            +
              spec.name          = "vx-consumer"
         | 
| 8 | 
            +
              spec.version       = Vx::Consumer::VERSION
         | 
| 9 | 
            +
              spec.authors       = ["Dmitry Galinsky"]
         | 
| 10 | 
            +
              spec.email         = ["dima.exe@gmail.com"]
         | 
| 11 | 
            +
              spec.summary       = %q{ summary }
         | 
| 12 | 
            +
              spec.description   = %q{ description }
         | 
| 13 | 
            +
              spec.homepage      = ""
         | 
| 14 | 
            +
              spec.license       = "MIT"
         | 
| 15 | 
            +
             | 
| 16 | 
            +
              spec.files         = `git ls-files -z`.split("\x0")
         | 
| 17 | 
            +
              spec.executables   = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
         | 
| 18 | 
            +
              spec.test_files    = spec.files.grep(%r{^(test|spec|features)/})
         | 
| 19 | 
            +
              spec.require_paths = ["lib"]
         | 
| 20 | 
            +
             | 
| 21 | 
            +
              spec.add_runtime_dependency 'bunny', '= 1.1.1'
         | 
| 22 | 
            +
              spec.add_runtime_dependency 'vx-common-rack-builder', '>= 0.0.2'
         | 
| 23 | 
            +
             | 
| 24 | 
            +
              spec.add_development_dependency "bundler", "~> 1.5"
         | 
| 25 | 
            +
              spec.add_development_dependency "rake"
         | 
| 26 | 
            +
              spec.add_development_dependency "rspec"
         | 
| 27 | 
            +
            end
         | 
    
        metadata
    ADDED
    
    | @@ -0,0 +1,148 @@ | |
| 1 | 
            +
            --- !ruby/object:Gem::Specification
         | 
| 2 | 
            +
            name: vx-consumer
         | 
| 3 | 
            +
            version: !ruby/object:Gem::Version
         | 
| 4 | 
            +
              version: 0.0.1
         | 
| 5 | 
            +
            platform: ruby
         | 
| 6 | 
            +
            authors:
         | 
| 7 | 
            +
            - Dmitry Galinsky
         | 
| 8 | 
            +
            autorequire: 
         | 
| 9 | 
            +
            bindir: bin
         | 
| 10 | 
            +
            cert_chain: []
         | 
| 11 | 
            +
            date: 2014-02-04 00:00:00.000000000 Z
         | 
| 12 | 
            +
            dependencies:
         | 
| 13 | 
            +
            - !ruby/object:Gem::Dependency
         | 
| 14 | 
            +
              name: bunny
         | 
| 15 | 
            +
              requirement: !ruby/object:Gem::Requirement
         | 
| 16 | 
            +
                requirements:
         | 
| 17 | 
            +
                - - '='
         | 
| 18 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 19 | 
            +
                    version: 1.1.1
         | 
| 20 | 
            +
              type: :runtime
         | 
| 21 | 
            +
              prerelease: false
         | 
| 22 | 
            +
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 23 | 
            +
                requirements:
         | 
| 24 | 
            +
                - - '='
         | 
| 25 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 26 | 
            +
                    version: 1.1.1
         | 
| 27 | 
            +
            - !ruby/object:Gem::Dependency
         | 
| 28 | 
            +
              name: vx-common-rack-builder
         | 
| 29 | 
            +
              requirement: !ruby/object:Gem::Requirement
         | 
| 30 | 
            +
                requirements:
         | 
| 31 | 
            +
                - - '>='
         | 
| 32 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 33 | 
            +
                    version: 0.0.2
         | 
| 34 | 
            +
              type: :runtime
         | 
| 35 | 
            +
              prerelease: false
         | 
| 36 | 
            +
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 37 | 
            +
                requirements:
         | 
| 38 | 
            +
                - - '>='
         | 
| 39 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 40 | 
            +
                    version: 0.0.2
         | 
| 41 | 
            +
            - !ruby/object:Gem::Dependency
         | 
| 42 | 
            +
              name: bundler
         | 
| 43 | 
            +
              requirement: !ruby/object:Gem::Requirement
         | 
| 44 | 
            +
                requirements:
         | 
| 45 | 
            +
                - - ~>
         | 
| 46 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 47 | 
            +
                    version: '1.5'
         | 
| 48 | 
            +
              type: :development
         | 
| 49 | 
            +
              prerelease: false
         | 
| 50 | 
            +
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 51 | 
            +
                requirements:
         | 
| 52 | 
            +
                - - ~>
         | 
| 53 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 54 | 
            +
                    version: '1.5'
         | 
| 55 | 
            +
            - !ruby/object:Gem::Dependency
         | 
| 56 | 
            +
              name: rake
         | 
| 57 | 
            +
              requirement: !ruby/object:Gem::Requirement
         | 
| 58 | 
            +
                requirements:
         | 
| 59 | 
            +
                - - '>='
         | 
| 60 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 61 | 
            +
                    version: '0'
         | 
| 62 | 
            +
              type: :development
         | 
| 63 | 
            +
              prerelease: false
         | 
| 64 | 
            +
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 65 | 
            +
                requirements:
         | 
| 66 | 
            +
                - - '>='
         | 
| 67 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 68 | 
            +
                    version: '0'
         | 
| 69 | 
            +
            - !ruby/object:Gem::Dependency
         | 
| 70 | 
            +
              name: rspec
         | 
| 71 | 
            +
              requirement: !ruby/object:Gem::Requirement
         | 
| 72 | 
            +
                requirements:
         | 
| 73 | 
            +
                - - '>='
         | 
| 74 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 75 | 
            +
                    version: '0'
         | 
| 76 | 
            +
              type: :development
         | 
| 77 | 
            +
              prerelease: false
         | 
| 78 | 
            +
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 79 | 
            +
                requirements:
         | 
| 80 | 
            +
                - - '>='
         | 
| 81 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 82 | 
            +
                    version: '0'
         | 
| 83 | 
            +
            description: ' description '
         | 
| 84 | 
            +
            email:
         | 
| 85 | 
            +
            - dima.exe@gmail.com
         | 
| 86 | 
            +
            executables: []
         | 
| 87 | 
            +
            extensions: []
         | 
| 88 | 
            +
            extra_rdoc_files: []
         | 
| 89 | 
            +
            files:
         | 
| 90 | 
            +
            - .gitignore
         | 
| 91 | 
            +
            - .rspec
         | 
| 92 | 
            +
            - .travis.yml
         | 
| 93 | 
            +
            - Gemfile
         | 
| 94 | 
            +
            - LICENSE.txt
         | 
| 95 | 
            +
            - README.md
         | 
| 96 | 
            +
            - Rakefile
         | 
| 97 | 
            +
            - lib/vx/consumer.rb
         | 
| 98 | 
            +
            - lib/vx/consumer/ack.rb
         | 
| 99 | 
            +
            - lib/vx/consumer/configuration.rb
         | 
| 100 | 
            +
            - lib/vx/consumer/error.rb
         | 
| 101 | 
            +
            - lib/vx/consumer/instrument.rb
         | 
| 102 | 
            +
            - lib/vx/consumer/params.rb
         | 
| 103 | 
            +
            - lib/vx/consumer/publish.rb
         | 
| 104 | 
            +
            - lib/vx/consumer/serializer.rb
         | 
| 105 | 
            +
            - lib/vx/consumer/session.rb
         | 
| 106 | 
            +
            - lib/vx/consumer/subscribe.rb
         | 
| 107 | 
            +
            - lib/vx/consumer/subscriber.rb
         | 
| 108 | 
            +
            - lib/vx/consumer/testing.rb
         | 
| 109 | 
            +
            - lib/vx/consumer/version.rb
         | 
| 110 | 
            +
            - spec/lib/consumer_spec.rb
         | 
| 111 | 
            +
            - spec/lib/serializer_spec.rb
         | 
| 112 | 
            +
            - spec/lib/session_spec.rb
         | 
| 113 | 
            +
            - spec/spec_helper.rb
         | 
| 114 | 
            +
            - spec/support/beefcake_test_message.rb
         | 
| 115 | 
            +
            - spec/support/test_consumers.rb
         | 
| 116 | 
            +
            - vx-consumer.gemspec
         | 
| 117 | 
            +
            homepage: ''
         | 
| 118 | 
            +
            licenses:
         | 
| 119 | 
            +
            - MIT
         | 
| 120 | 
            +
            metadata: {}
         | 
| 121 | 
            +
            post_install_message: 
         | 
| 122 | 
            +
            rdoc_options: []
         | 
| 123 | 
            +
            require_paths:
         | 
| 124 | 
            +
            - lib
         | 
| 125 | 
            +
            required_ruby_version: !ruby/object:Gem::Requirement
         | 
| 126 | 
            +
              requirements:
         | 
| 127 | 
            +
              - - '>='
         | 
| 128 | 
            +
                - !ruby/object:Gem::Version
         | 
| 129 | 
            +
                  version: '0'
         | 
| 130 | 
            +
            required_rubygems_version: !ruby/object:Gem::Requirement
         | 
| 131 | 
            +
              requirements:
         | 
| 132 | 
            +
              - - '>='
         | 
| 133 | 
            +
                - !ruby/object:Gem::Version
         | 
| 134 | 
            +
                  version: '0'
         | 
| 135 | 
            +
            requirements: []
         | 
| 136 | 
            +
            rubyforge_project: 
         | 
| 137 | 
            +
            rubygems_version: 2.0.14
         | 
| 138 | 
            +
            signing_key: 
         | 
| 139 | 
            +
            specification_version: 4
         | 
| 140 | 
            +
            summary: summary
         | 
| 141 | 
            +
            test_files:
         | 
| 142 | 
            +
            - spec/lib/consumer_spec.rb
         | 
| 143 | 
            +
            - spec/lib/serializer_spec.rb
         | 
| 144 | 
            +
            - spec/lib/session_spec.rb
         | 
| 145 | 
            +
            - spec/spec_helper.rb
         | 
| 146 | 
            +
            - spec/support/beefcake_test_message.rb
         | 
| 147 | 
            +
            - spec/support/test_consumers.rb
         | 
| 148 | 
            +
            has_rdoc: 
         |