fluffle 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: e96a7339ec279990aaa392943d1311b880fb8b50
4
+ data.tar.gz: ce5531d38a298fc3dc30bbb6bf5e9f60be34e35a
5
+ SHA512:
6
+ metadata.gz: 2ccae5e39f1dcfa3f32fddaf180ee0124e3d76313beb0983d0fa1f668fa6e3ebfd118620b8bc18d783804bce022dc119ae2e08c1afb4b7dc996c05c9970373b3
7
+ data.tar.gz: 69eeb23cf91f14575f93a1c2f82a53fdd1e719fb39b132a3baf5e0baa74132987257fa1456c53cbb5623a0b0a715eb074f027e784ed68fc3b05e9cef7d88d63d
data/.gitignore ADDED
@@ -0,0 +1,2 @@
1
+ .DS_Store
2
+ pkg
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --color
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,52 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ fluffle (0.0.1)
5
+ bunny (~> 2.5.0)
6
+ concurrent-ruby (~> 1.0.2)
7
+ oj (~> 2.17.1)
8
+ uuidtools (~> 2.1.5)
9
+
10
+ GEM
11
+ remote: https://rubygems.org/
12
+ specs:
13
+ amq-protocol (2.0.1)
14
+ bunny (2.5.0)
15
+ amq-protocol (>= 2.0.1)
16
+ coderay (1.1.1)
17
+ concurrent-ruby (1.0.2)
18
+ diff-lcs (1.2.5)
19
+ method_source (0.8.2)
20
+ oj (2.17.1)
21
+ pry (0.10.4)
22
+ coderay (~> 1.1.0)
23
+ method_source (~> 0.8.1)
24
+ slop (~> 3.4)
25
+ rake (11.2.2)
26
+ rspec (3.4.0)
27
+ rspec-core (~> 3.4.0)
28
+ rspec-expectations (~> 3.4.0)
29
+ rspec-mocks (~> 3.4.0)
30
+ rspec-core (3.4.4)
31
+ rspec-support (~> 3.4.0)
32
+ rspec-expectations (3.4.0)
33
+ diff-lcs (>= 1.2.0, < 2.0)
34
+ rspec-support (~> 3.4.0)
35
+ rspec-mocks (3.4.1)
36
+ diff-lcs (>= 1.2.0, < 2.0)
37
+ rspec-support (~> 3.4.0)
38
+ rspec-support (3.4.1)
39
+ slop (3.6.0)
40
+ uuidtools (2.1.5)
41
+
42
+ PLATFORMS
43
+ ruby
44
+
45
+ DEPENDENCIES
46
+ fluffle!
47
+ pry (~> 0.10.1)
48
+ rake (~> 11.2.2)
49
+ rspec (~> 3.4.0)
50
+
51
+ BUNDLED WITH
52
+ 1.12.5
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2016 Everlane Inc.
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,33 @@
1
+ # fluffle
2
+
3
+ An implementation of [JSON-RPC][] over RabbitMQ through the [Bunny][] library. Provides both a client and server.
4
+
5
+ ![](fluffle.jpg)
6
+
7
+ > A group of baby bunnies is called a [fluffle][].
8
+
9
+ [Bunny]: https://github.com/ruby-amqp/bunny
10
+ [fluffle]: http://imgur.com/6eABy1v
11
+ [JSON-RPC]: http://www.jsonrpc.org/specification
12
+
13
+ ## Features
14
+
15
+ - Client: Thread-safe blocking client (via [concurrent-ruby][])
16
+ - Server: One-thread-per-queue implementation (multi-threaded coming soon)
17
+ - Server: Easy-to-use built-in handlers and straightforward API for building custom handlers
18
+
19
+ [concurrent-ruby]: https://github.com/ruby-concurrency/concurrent-ruby
20
+
21
+ ## Examples
22
+
23
+ See the [`examples`](examples/) directory.
24
+
25
+ The server provides a few options for handling RPC requests:
26
+
27
+ - Dispatcher pattern: `dispatcher.handle('upcase') { |str| str.upcase }`
28
+ - Delegator pattern: delegate will receive the `#upcase` message with a single argument (the string)
29
+ - Custom: any handler needs to implement the API described in `Fluffle::Handlers::Base`
30
+
31
+ ## License
32
+
33
+ Released under the MIT license, see [LICENSE](LICENSE) for details.
data/Rakefile ADDED
@@ -0,0 +1,4 @@
1
+ require 'bundler/gem_tasks'
2
+
3
+ # No-op to make Travis CI happy
4
+ task 'default'
@@ -0,0 +1,14 @@
1
+ lib = File.join File.dirname(__FILE__), '..', 'lib'
2
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
+
4
+ require 'fluffle'
5
+
6
+ server = Fluffle::Client.new url: 'amqp://localhost'
7
+
8
+ timings = 10.times.map do
9
+ t0 = Time.now
10
+ server.call('foo').inspect
11
+ Time.now - t0
12
+ end
13
+
14
+ puts timings
@@ -0,0 +1,12 @@
1
+ lib = File.join File.dirname(__FILE__), '..', 'lib'
2
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
+
4
+ require 'fluffle'
5
+
6
+ server = Fluffle::Server.new url: 'amqp://localhost'
7
+
8
+ server.drain 'default' do |dispatcher|
9
+ dispatcher.handle('foo') { 'bar' }
10
+ end
11
+
12
+ server.start
data/fluffle.gemspec ADDED
@@ -0,0 +1,28 @@
1
+ lib = File.join File.dirname(__FILE__), 'lib'
2
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
+
4
+ require 'fluffle/version'
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = 'fluffle'
8
+ s.version = Fluffle::VERSION
9
+ s.authors = ['Dirk Gadsden']
10
+ s.email = ['dirk@esherido.com']
11
+ s.summary = 'Client and server implementations for JSON-RPC over RabbitMQ'
12
+ s.homepage = 'https://github.com/Everlane/fluffle'
13
+ s.license = 'MIT'
14
+
15
+ s.required_ruby_version = '>= 1.9.3'
16
+
17
+ s.add_dependency 'bunny', '~> 2.5.0'
18
+ s.add_dependency 'concurrent-ruby', '~> 1.0.2'
19
+ s.add_dependency 'oj', '~> 2.17.1'
20
+ s.add_dependency 'uuidtools', '~> 2.1.5'
21
+
22
+ s.add_development_dependency 'pry', '~> 0.10.1'
23
+ s.add_development_dependency 'rake', '~> 11.2.2'
24
+ s.add_development_dependency 'rspec', '~> 3.4.0'
25
+
26
+ s.files = `git ls-files`.split("\n")
27
+ s.test_files = `git ls-files -- test/*`.split("\n")
28
+ end
data/fluffle.jpg ADDED
Binary file
@@ -0,0 +1,76 @@
1
+ require 'concurrent'
2
+ require 'oj'
3
+ require 'securerandom'
4
+ require 'uuidtools'
5
+
6
+ require 'fluffle/connectable'
7
+
8
+ module Fluffle
9
+ class Client
10
+ include Connectable
11
+
12
+ def initialize(url:)
13
+ self.connect url
14
+
15
+ @uuid = UUIDTools::UUID.timestamp_create.to_s
16
+ @channel = @connection.create_channel
17
+ @exchange = @channel.default_exchange
18
+ @reply_queue = @channel.queue Fluffle.response_queue_name(@uuid), exclusive: true
19
+
20
+ # Used for generating unique message IDs
21
+ @prng = Random.new
22
+
23
+ @pending_responses = Concurrent::Map.new
24
+
25
+ self.subscribe
26
+ end
27
+
28
+ def subscribe
29
+ @reply_queue.subscribe do |delivery_info, properties, payload|
30
+ self.handle_resposne delivery_info: delivery_info,
31
+ properties: properties,
32
+ payload: payload
33
+ end
34
+ end
35
+
36
+ def handle_resposne(delivery_info:, properties:, payload:)
37
+ payload = Oj.load payload
38
+
39
+ ivar = @pending_responses.delete payload['id']
40
+ ivar.set payload
41
+ end
42
+
43
+ def call(method, params = [], queue: 'default')
44
+ id = random_bytes_as_hex 8
45
+
46
+ payload = {
47
+ 'jsonrpc' => '2.0',
48
+ 'id' => id,
49
+ 'method' => method,
50
+ 'params' => params
51
+ }
52
+
53
+ @exchange.publish Oj.dump(payload), routing_key: Fluffle.request_queue_name(queue),
54
+ correlation_id: id,
55
+ reply_to: @reply_queue.name
56
+
57
+ ivar = Concurrent::IVar.new
58
+ @pending_responses[id] = ivar
59
+
60
+ response = ivar.value
61
+
62
+ if response['result']
63
+ response['result']
64
+ else
65
+ raise # TODO: Raise known error subclass to be caught by client code
66
+ end
67
+ end
68
+
69
+ protected
70
+
71
+ def random_bytes_as_hex(bytes)
72
+ # Adapted from `SecureRandom.hex`
73
+ @prng.bytes(bytes).unpack('H*')[0]
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,20 @@
1
+ module Fluffle
2
+ module Connectable
3
+ def self.included(klass)
4
+ klass.class_eval do
5
+ attr_reader :connection
6
+ end
7
+ end
8
+
9
+ def connect(*args)
10
+ self.stop if self.connected?
11
+
12
+ @connection = Bunny.new *args
13
+ @connection.start
14
+ end
15
+
16
+ def connected?
17
+ @connection&.connected?
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,50 @@
1
+ module Fluffle
2
+ module Errors
3
+ class BaseError < StandardError
4
+ def to_response
5
+ {
6
+ 'code' => self.code,
7
+ 'message' => self.message,
8
+ 'data' => self.data
9
+ }
10
+ end
11
+ end
12
+
13
+ # Raise this within your own code to get an error that will be faithfully
14
+ # translated into the code, message, and data member fields of the
15
+ # spec's `Error` response object
16
+ class CustomError < BaseError
17
+ attr_accessor :data
18
+
19
+ def initialize(code: 0, message:, data: nil)
20
+ @code = code
21
+ @data = data
22
+
23
+ super message
24
+ end
25
+ end
26
+
27
+ # Superclass of all errors that may be raised within the server
28
+ class ServerError < BaseError
29
+ # Longer-form description that may be present in the `data` field of
30
+ # the `Error` response object
31
+ attr_reader :description
32
+
33
+ def data
34
+ { 'description' => @description }
35
+ end
36
+ end
37
+
38
+ class InvalidRequestError < ServerError
39
+ def initialize(description)
40
+ @description = description
41
+
42
+ super 'Invalid Request'
43
+ end
44
+
45
+ def code
46
+ -32600
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,9 @@
1
+ module Fluffle
2
+ module Handlers
3
+ class Base
4
+ def call(method:, params:, id:, meta:)
5
+ raise RuntimeError, '#call is not implemented on abstract Base handler class'
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,13 @@
1
+ module Fluffle
2
+ module Handlers
3
+ class Delegator < Base
4
+ def initialize(delegated_object)
5
+ @delegated_object = delegated_object
6
+ end
7
+
8
+ def call(method:, params:, **_)
9
+ @delegated_object.send method.to_sym, *params
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,27 @@
1
+ module Fluffle
2
+ module Handlers
3
+ class Dispatcher < Base
4
+ def initialize
5
+ @routes = []
6
+
7
+ yield self if block_given?
8
+ end
9
+
10
+ # pattern - Right now just a String that 1-to-1 matches the `method`
11
+ # block - Block to call with the `params`
12
+ def handle(pattern, &block)
13
+ @routes << [pattern, block]
14
+ end
15
+
16
+ def call(method:, params:, **_)
17
+ @routes.each do |(pattern, block)|
18
+ next if pattern != method
19
+
20
+ return block.call(*params)
21
+ end
22
+
23
+ raise NoMethodError, "Undefined method '#{method}'"
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,128 @@
1
+ module Fluffle
2
+ class Server
3
+ class << self
4
+ attr_accessor :default_server
5
+ end
6
+
7
+ attr_reader :connection, :handlers
8
+
9
+ def initialize(url: nil)
10
+ self.connect(url) if url
11
+
12
+ @handlers = {}
13
+ @queues = {}
14
+
15
+ self.class.default_server ||= self
16
+ end
17
+
18
+ def connect(*args)
19
+ self.stop if self.connected?
20
+
21
+ @connection = Bunny.new *args
22
+ @connection.start
23
+ end
24
+
25
+ def connected?
26
+ @connection&.connected?
27
+ end
28
+
29
+ def drain(queue: 'default', handler: nil, &block)
30
+ if handler && block
31
+ raise ArgumentError, 'Cannot provide both handler: and block'
32
+ end
33
+
34
+ handler = Fluffle::Handlers::Dispatcher.new(&block) if block
35
+
36
+ @handlers[queue_name.to_s] = handler
37
+ end
38
+
39
+ def start
40
+ @channel = @connection.create_channel
41
+ @exchange = @channel.default_exchange
42
+
43
+ @handlers.each do |name, handler|
44
+ qualified_name = Fluffle.response_queue_name name
45
+ queue = @channel.queue qualified_name
46
+
47
+ queue.subscribe do |delivery_info, properties, payload|
48
+ self.handle_request queue_name: name,
49
+ handler: handler,
50
+ delivery_info: delivery_info,
51
+ properties: properties,
52
+ payload: payload
53
+ end
54
+ end
55
+
56
+ @channel.work_pool.join
57
+ end
58
+
59
+ def handle_request(queue_name:, handler:, delivery_info:, properties:, payload:)
60
+ id = nil
61
+ reply_to = properties[:reply_to]
62
+
63
+ begin
64
+ id, method, params = self.decode payload
65
+
66
+ validate_request method: method
67
+
68
+ result = handler.call id: id,
69
+ method: method,
70
+ params: params,
71
+ meta: {
72
+ reply_to: reply_to
73
+ }
74
+ rescue => err
75
+ error = self.build_error_response err
76
+ end
77
+
78
+ response = { 'jsonrpc' => '2.0', 'id' => id }
79
+
80
+ if error
81
+ response['error'] = error
82
+ else
83
+ response['result'] = result
84
+ end
85
+
86
+ @exchange.publish Oj.dump(response), routing_key: reply_to,
87
+ correlation_id: id
88
+ end
89
+
90
+ protected
91
+
92
+ def decode(payload)
93
+ payload = Oj.load payload
94
+
95
+ id = payload['id']
96
+ method = payload['method']
97
+ params = payload['params']
98
+
99
+ [id, method, params]
100
+ end
101
+
102
+ # Raises if elements of the request do not comply with the spec
103
+ def validate_request(request)
104
+ raise Errors::InvalidRequestError.new("Missing `method' Request object member") unless request[:method]
105
+ end
106
+
107
+ # Convert a Ruby error into a hash complying with the JSON-RPC spec
108
+ # for `Error` response objects
109
+ def build_error_response(err)
110
+ if err.is_a? Errors::BaseError
111
+ err.to_response
112
+
113
+ elsif err.is_a? NoMethodError
114
+ { 'code' => -32601, 'message' => 'Method not found' }
115
+
116
+ else
117
+ response = {
118
+ 'code' => 0,
119
+ 'message' => err.message
120
+ }
121
+
122
+ response['data'] = err.data if err.respond_to? :data
123
+
124
+ response
125
+ end
126
+ end
127
+ end
128
+ end
@@ -0,0 +1,3 @@
1
+ module Fluffle
2
+ VERSION = '0.0.1'
3
+ end
data/lib/fluffle.rb ADDED
@@ -0,0 +1,20 @@
1
+ require 'bunny'
2
+
3
+ require 'fluffle/version'
4
+ require 'fluffle/client'
5
+ require 'fluffle/errors'
6
+ require 'fluffle/handlers/base'
7
+ require 'fluffle/handlers/delegator'
8
+ require 'fluffle/handlers/dispatcher'
9
+ require 'fluffle/server'
10
+
11
+ module Fluffle
12
+ # Expand a short name into a fully-qualified one
13
+ def self.request_queue_name(name)
14
+ "fluffle.requests.#{name}"
15
+ end
16
+
17
+ def self.response_queue_name(name)
18
+ "fluffle.responses.#{name}"
19
+ end
20
+ end
@@ -0,0 +1,116 @@
1
+ require 'spec_helper'
2
+
3
+ describe Fluffle::Server do
4
+ describe '#handle_request' do
5
+ before do
6
+ @exchange_spy = exchange_spy = spy 'Exchange'
7
+
8
+ subject.instance_eval do
9
+ @exchange = exchange_spy
10
+ end
11
+ end
12
+
13
+ def id
14
+ @id = '123' unless instance_variable_defined? :@id
15
+ @id
16
+ end
17
+
18
+ def method
19
+ @method = 'do_something' unless instance_variable_defined? :@method
20
+ @method
21
+ end
22
+
23
+ def params
24
+ @params = [] unless instance_variable_defined? :@params
25
+ @params
26
+ end
27
+
28
+ def reply_to
29
+ @reply_to = 'fakeclient' unless instance_variable_defined? :@reply_to
30
+ @reply_to
31
+ end
32
+
33
+ def make_request(payload: {}, handler:)
34
+ payload['jsonrpc'] = '2.0' unless payload.key? 'jsonrpc'
35
+ payload['id'] = id unless payload.key? 'id'
36
+ payload['method'] = method unless payload.key? 'method'
37
+ payload['params'] = params unless payload.key? 'params'
38
+
39
+ subject.handle_request queue_name: 'fakequeue',
40
+ handler: handler,
41
+ delivery_info: double('DeliveryInfo'),
42
+ properties: { reply_to: reply_to },
43
+ payload: Oj.dump(payload)
44
+ end
45
+
46
+ def expect_response(payload:, routing_key: nil, correlation_id: nil)
47
+ routing_key ||= reply_to
48
+ correlation_id ||= id
49
+
50
+ payload['jsonrpc'] ||= '2.0'
51
+ payload['id'] ||= id
52
+
53
+ expect(@exchange_spy).to have_received(:publish) do |payload_json, opts|
54
+ expect(Oj.load(payload_json)).to eq payload
55
+
56
+ expect(opts).to eq routing_key: routing_key,
57
+ correlation_id: correlation_id
58
+ end
59
+ end
60
+
61
+ it 'responds with the result for a normal request' do
62
+ @params = ['bar']
63
+
64
+ result = 'baz'
65
+
66
+ handler = double 'Handler'
67
+ expect(handler).to receive(:call) do |args|
68
+ expect(args).to eq({
69
+ id: id,
70
+ method: method,
71
+ params: params,
72
+ meta: { reply_to: reply_to }
73
+ })
74
+
75
+ result
76
+ end
77
+
78
+ make_request handler: handler
79
+
80
+ expect_response payload: { 'result' => result }
81
+ end
82
+
83
+ it 'responds with the appropriate code and message when method not found' do
84
+ @method = 'notfound'
85
+
86
+ handler = double 'Handler'
87
+ expect(handler).to receive(:call).and_raise(NoMethodError.new("undefined method `#{method}'"))
88
+
89
+ make_request handler: handler
90
+
91
+ expect_response payload: {
92
+ 'error' => {
93
+ 'code' => -32601,
94
+ 'message' => 'Method not found'
95
+ }
96
+ }
97
+ end
98
+
99
+ it "responds with the appropriate code and message when `method' isn't supplied" do
100
+ @method = nil
101
+
102
+ handler = double 'Handler'
103
+ expect(handler).not_to receive(:call)
104
+
105
+ make_request handler: handler
106
+
107
+ expect_response payload: {
108
+ 'error' => {
109
+ 'code' => -32600,
110
+ 'message' => 'Invalid Request',
111
+ 'data' => { 'description' => "Missing `method' Request object member" }
112
+ }
113
+ }
114
+ end
115
+ end
116
+ end
@@ -0,0 +1,3 @@
1
+ require 'bundler/setup'
2
+
3
+ require 'fluffle'
metadata ADDED
@@ -0,0 +1,164 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: fluffle
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Dirk Gadsden
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2016-08-09 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: 2.5.0
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: 2.5.0
27
+ - !ruby/object:Gem::Dependency
28
+ name: concurrent-ruby
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: 1.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: 1.0.2
41
+ - !ruby/object:Gem::Dependency
42
+ name: oj
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: 2.17.1
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: 2.17.1
55
+ - !ruby/object:Gem::Dependency
56
+ name: uuidtools
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: 2.1.5
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: 2.1.5
69
+ - !ruby/object:Gem::Dependency
70
+ name: pry
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: 0.10.1
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: 0.10.1
83
+ - !ruby/object:Gem::Dependency
84
+ name: rake
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: 11.2.2
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: 11.2.2
97
+ - !ruby/object:Gem::Dependency
98
+ name: rspec
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: 3.4.0
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: 3.4.0
111
+ description:
112
+ email:
113
+ - dirk@esherido.com
114
+ executables: []
115
+ extensions: []
116
+ extra_rdoc_files: []
117
+ files:
118
+ - ".gitignore"
119
+ - ".rspec"
120
+ - Gemfile
121
+ - Gemfile.lock
122
+ - LICENSE
123
+ - README.md
124
+ - Rakefile
125
+ - examples/client.rb
126
+ - examples/server.rb
127
+ - fluffle.gemspec
128
+ - fluffle.jpg
129
+ - lib/fluffle.rb
130
+ - lib/fluffle/client.rb
131
+ - lib/fluffle/connectable.rb
132
+ - lib/fluffle/errors.rb
133
+ - lib/fluffle/handlers/base.rb
134
+ - lib/fluffle/handlers/delegator.rb
135
+ - lib/fluffle/handlers/dispatcher.rb
136
+ - lib/fluffle/server.rb
137
+ - lib/fluffle/version.rb
138
+ - spec/server_spec.rb
139
+ - spec/spec_helper.rb
140
+ homepage: https://github.com/Everlane/fluffle
141
+ licenses:
142
+ - MIT
143
+ metadata: {}
144
+ post_install_message:
145
+ rdoc_options: []
146
+ require_paths:
147
+ - lib
148
+ required_ruby_version: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - ">="
151
+ - !ruby/object:Gem::Version
152
+ version: 1.9.3
153
+ required_rubygems_version: !ruby/object:Gem::Requirement
154
+ requirements:
155
+ - - ">="
156
+ - !ruby/object:Gem::Version
157
+ version: '0'
158
+ requirements: []
159
+ rubyforge_project:
160
+ rubygems_version: 2.5.1
161
+ signing_key:
162
+ specification_version: 4
163
+ summary: Client and server implementations for JSON-RPC over RabbitMQ
164
+ test_files: []