rjr 0.6.1 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
data/Rakefile CHANGED
@@ -1,7 +1,7 @@
1
1
  # rjr project Rakefile
2
2
  #
3
- # Copyright (C) 2010 Mohammed Morsi <movitto@yahoo.com>
4
- # Licensed under the AGPLv3+ http://www.gnu.org/licenses/agpl.txt
3
+ # Copyright (C) 2010-2012 Mohammed Morsi <mo@morsi.org>
4
+ # Licensed under the Apache License, Version 2.0
5
5
 
6
6
  require 'rdoc/task'
7
7
  require "rspec/core/rake_task"
data/bin/rjr-server CHANGED
@@ -5,8 +5,8 @@
5
5
  # Flags:
6
6
  # -h --help
7
7
  #
8
- # Copyright (C) 2011 Mohammed Morsi <mo@morsi.org>
9
- # Licensed under the AGPLv3+ http://www.gnu.org/licenses/agpl.txt
8
+ # Copyright (C) 2011-2012 Mohammed Morsi <mo@morsi.org>
9
+ # Licensed under the Apache License, Version 2.0
10
10
 
11
11
  require 'rubygems'
12
12
  require 'optparse'
data/lib/rjr/amqp_node.rb CHANGED
@@ -1,7 +1,7 @@
1
1
  # RJR AMQP Endpoint
2
2
  #
3
3
  # Copyright (C) 2012 Mohammed Morsi <mo@morsi.org>
4
- # Licensed under the AGPLv3+ http://www.gnu.org/licenses/agpl.txt
4
+ # Licensed under the Apache License, Version 2.0
5
5
 
6
6
  # establish client connection w/ specified args and invoke block w/
7
7
  # newly created client, returning it after block terminates
@@ -57,7 +57,6 @@ class AMQPNode < RJR::Node
57
57
  :rjr_callback =>
58
58
  AMQPNodeCallback.new(:node => self,
59
59
  :exchange => @exchange,
60
- :amqp_lock => @amqp_lock,
61
60
  :destination => reply_to,
62
61
  :headers => headers))
63
62
  response = ResponseMessage.new(:id => msg.msg_id, :result => result, :headers => headers)
@@ -74,8 +73,9 @@ class AMQPNode < RJR::Node
74
73
  end
75
74
 
76
75
  @response_lock.synchronize{
77
- @result = [res]
78
- @result << err if !err.nil?
76
+ result = [msg.msg_id, res]
77
+ result << err if !err.nil?
78
+ @responses << result
79
79
  @response_cv.signal
80
80
  }
81
81
  end
@@ -88,7 +88,10 @@ class AMQPNode < RJR::Node
88
88
  @broker = args[:broker]
89
89
  @connection_event_handlers = {:closed => [], :error => []}
90
90
  @response_lock = Mutex.new
91
- @response_cv = ConditionVariable.new
91
+ @response_cv = ConditionVariable.new
92
+ @response_check_cv = ConditionVariable.new
93
+ @responses = []
94
+ @amqp_lock = Mutex.new
92
95
  end
93
96
 
94
97
  # Initialize the amqp subsystem
@@ -106,11 +109,11 @@ class AMQPNode < RJR::Node
106
109
  @exchange = @channel.default_exchange
107
110
 
108
111
  @listening = false
109
- @disconnected = false
112
+ #@disconnected = false
110
113
 
111
114
  @exchange.on_return do |basic_return, metadata, payload|
112
115
  puts "#{payload} was returned! reply_code = #{basic_return.reply_code}, reply_text = #{basic_return.reply_text}"
113
- @disconnected = true # FIXME member will be set on wrong class
116
+ #@disconnected = true # FIXME member will be set on wrong class
114
117
  connection_event(:error)
115
118
  connection_event(:closed)
116
119
  end
@@ -118,25 +121,41 @@ class AMQPNode < RJR::Node
118
121
 
119
122
  # publish a message using the amqp exchange
120
123
  def publish(*args)
121
- raise RJR::Errors::ConnectionError.new("client unreachable") if @disconnected
122
- @exchange.publish *args
124
+ @amqp_lock.synchronize {
125
+ #raise RJR::Errors::ConnectionError.new("client unreachable") if @disconnected
126
+ @exchange.publish *args
127
+ }
128
+ nil
123
129
  end
124
130
 
125
131
  # subscribe to messages using the amqp queue
126
132
  def subscribe(*args, &bl)
127
133
  return if @listening
128
- @listening = true
129
- @queue.subscribe do |metadata, msg|
130
- bl.call metadata, msg
131
- end
134
+ @amqp_lock.synchronize {
135
+ @listening = true
136
+ @queue.subscribe do |metadata, msg|
137
+ bl.call metadata, msg
138
+ end
139
+ }
140
+ nil
132
141
  end
133
142
 
134
143
  def wait_for_result(message)
135
144
  res = nil
136
- @response_lock.synchronize{
137
- @response_cv.wait @response_lock
138
- res = @result
139
- }
145
+ while res.nil?
146
+ @response_lock.synchronize{
147
+ @response_cv.wait @response_lock
148
+ # FIXME throw err if more than 1 match found
149
+ res = @responses.select { |response| message.msg_id == response.first }.first
150
+ unless res.nil?
151
+ @responses.delete(res)
152
+ else
153
+ @response_cv.signal
154
+ @response_check_cv.wait @response_lock
155
+ end
156
+ @response_check_cv.signal
157
+ }
158
+ end
140
159
  return res
141
160
  end
142
161
 
@@ -185,14 +204,15 @@ class AMQPNode < RJR::Node
185
204
  publish message.to_s, :routing_key => routing_key, :reply_to => @queue_name
186
205
  end
187
206
 
207
+ # TODO optional timeout for response ?
188
208
  result = wait_for_result(message)
189
- self.stop
190
- self.join unless self.em_running?
209
+ #self.stop
210
+ #self.join unless self.em_running?
191
211
 
192
- if result.size > 1
193
- raise result[1]
212
+ if result.size > 2
213
+ raise result[2]
194
214
  end
195
- return result.first
215
+ return result[1]
196
216
  end
197
217
 
198
218
  end
data/lib/rjr/common.rb CHANGED
@@ -1,7 +1,7 @@
1
1
  # RJR Utility Methods
2
2
  #
3
3
  # Copyright (C) 2011 Mohammed Morsi <mo@morsi.org>
4
- # Licensed under the AGPLv3+ http://www.gnu.org/licenses/agpl.txt
4
+ # Licensed under the Apache License, Version 2.0
5
5
 
6
6
  require 'logger'
7
7
 
@@ -1,7 +1,7 @@
1
1
  # RJR Request / Response Dispatcher
2
2
  #
3
3
  # Copyright (C) 2012 Mohammed Morsi <mo@morsi.org>
4
- # Licensed under the AGPLv3+ http://www.gnu.org/licenses/agpl.txt
4
+ # Licensed under the Apache License, Version 2.0
5
5
 
6
6
  # establish client connection w/ specified args and invoke block w/
7
7
  # newly created client, returning it after block terminates
data/lib/rjr/errors.rb CHANGED
@@ -1,7 +1,7 @@
1
1
  # RJR Errors
2
2
  #
3
3
  # Copyright (C) 2012 Mohammed Morsi <mo@morsi.org>
4
- # Licensed under the AGPLv3+ http://www.gnu.org/licenses/agpl.txt
4
+ # Licensed under the Apache License, Version 2.0
5
5
 
6
6
  # establish client connection w/ specified args and invoke block w/
7
7
  # newly created client, returning it after block terminates
@@ -1,7 +1,7 @@
1
1
  # RJR Local Endpoint
2
2
  #
3
3
  # Copyright (C) 2012 Mohammed Morsi <mo@morsi.org>
4
- # Licensed under the AGPLv3+ http://www.gnu.org/licenses/agpl.txt
4
+ # Licensed under the Apache License, Version 2.0
5
5
 
6
6
  # establish client connection w/ specified args and invoke block w/
7
7
  # newly created client, returning it after block terminates
@@ -19,6 +19,7 @@ class LocalNodeCallback
19
19
  end
20
20
 
21
21
  def invoke(callback_method, *data)
22
+ # TODO any exceptions from handler will propagate here, surround w/ begin/rescue block
22
23
  @node.invoke_request(callback_method, *data)
23
24
  # TODO support local_node 'disconnections'
24
25
  end
data/lib/rjr/message.rb CHANGED
@@ -1,7 +1,7 @@
1
1
  # RJR Message
2
2
  #
3
3
  # Copyright (C) 2012 Mohammed Morsi <mo@morsi.org>
4
- # Licensed under the AGPLv3+ http://www.gnu.org/licenses/agpl.txt
4
+ # Licensed under the Apache License, Version 2.0
5
5
 
6
6
  # establish client connection w/ specified args and invoke block w/
7
7
  # newly created client, returning it after block terminates
@@ -1,7 +1,7 @@
1
1
  # RJR MultiNode Endpoint
2
2
  #
3
3
  # Copyright (C) 2012 Mohammed Morsi <mo@morsi.org>
4
- # Licensed under the AGPLv3+ http://www.gnu.org/licenses/agpl.txt
4
+ # Licensed under the Apache License, Version 2.0
5
5
 
6
6
  # establish client connection w/ specified args and invoke block w/
7
7
  # newly created client, returning it after block terminates
data/lib/rjr/node.rb CHANGED
@@ -1,7 +1,7 @@
1
1
  # RJR Node
2
2
  #
3
3
  # Copyright (C) 2012 Mohammed Morsi <mo@morsi.org>
4
- # Licensed under the AGPLv3+ http://www.gnu.org/licenses/agpl.txt
4
+ # Licensed under the Apache License, Version 2.0
5
5
 
6
6
  # establish client connection w/ specified args and invoke block w/
7
7
  # newly created client, returning it after block terminates
@@ -20,11 +20,19 @@ class Node
20
20
  # attitional parameters to set on messages
21
21
  attr_accessor :message_headers
22
22
 
23
+ attr_reader :thread_pool
24
+
23
25
  def initialize(args = {})
24
26
  @node_id = args[:node_id]
25
27
 
26
28
  @message_headers = {}
27
29
  @message_headers.merge!(args[:headers]) if args.has_key?(:headers)
30
+
31
+ ObjectSpace.define_finalizer(self, self.class.finalize(self))
32
+ end
33
+
34
+ def self.finalize(node)
35
+ proc { node.halt ; node.join }
28
36
  end
29
37
 
30
38
  # run job in event machine
@@ -46,7 +54,7 @@ class Node
46
54
  begin
47
55
  EventMachine.run
48
56
  rescue Exception => e
49
- puts "Critical exception #{e}"
57
+ puts "Critical exception #{e}\n#{e.backtrace.join("\n")}"
50
58
  ensure
51
59
  end
52
60
  }
@@ -60,10 +68,10 @@ class Node
60
68
  end
61
69
 
62
70
  def join
63
- if @@em_thread
64
- @@em_thread.join
65
- @@em_thread = nil
66
- end
71
+ @@em_thread.join if @@em_thread
72
+ @@em_thread = nil
73
+ @thread_pool.join if @thread_pool
74
+ @thread_pool = nil
67
75
  end
68
76
 
69
77
  def stop
@@ -77,6 +85,7 @@ class Node
77
85
  def halt
78
86
  @@em_jobs = 0
79
87
  EventMachine.stop
88
+ @thread_pool.stop unless @thread_pool.nil?
80
89
  end
81
90
 
82
91
  end
data/lib/rjr/tcp_node.rb CHANGED
@@ -1 +1,174 @@
1
- # TODO
1
+ # RJR TCP Endpoint
2
+ #
3
+ # Copyright (C) 2012 Mohammed Morsi <mo@morsi.org>
4
+ # Licensed under the Apache License, Version 2.0
5
+
6
+ require 'uri'
7
+ require 'eventmachine'
8
+
9
+ require 'rjr/node'
10
+ require 'rjr/message'
11
+
12
+ module RJR
13
+
14
+ # TCP client node callback interface,
15
+ # send data back to client via established tcp socket.
16
+ class TCPNodeCallback
17
+ def initialize(args = {})
18
+ @endpoint = args[:endpoint]
19
+ @message_headers = args[:headers]
20
+ end
21
+
22
+ def invoke(callback_method, *data)
23
+ msg = RequestMessage.new :method => callback_method, :args => data, :headers => @message_headers
24
+ # TODO surround w/ begin/rescue block incase of socket errors
25
+ @endpoint.send_data msg.to_s
26
+ end
27
+ end
28
+
29
+ # helper class intialized by event machine corresponding to
30
+ # a client or server socket connection
31
+ class TCPNodeEndpoint < EventMachine::Connection
32
+ def initialize(args = {})
33
+ @rjr_node = args[:rjr_node]
34
+
35
+ # these params should be set for clients
36
+ @send_message = args[:init_message]
37
+ end
38
+
39
+ def post_init
40
+ unless @send_message.nil?
41
+ send_data @send_message.to_s
42
+ @send_message = nil
43
+ end
44
+ end
45
+
46
+ def receive_data(data)
47
+ if RequestMessage.is_request_message?(data)
48
+ @rjr_node.thread_pool << ThreadPoolJob.new { handle_request(data) }
49
+
50
+ elsif ResponseMessage.is_response_message?(data)
51
+ handle_response(data)
52
+
53
+ end
54
+ end
55
+
56
+
57
+ def handle_request(data)
58
+ client_port, client_ip = Socket.unpack_sockaddr_in(get_peername)
59
+ msg = RequestMessage.new(:message => data, :headers => @rjr_node.message_headers)
60
+ headers = @rjr_node.message_headers.merge(msg.headers)
61
+ result = Dispatcher.dispatch_request(msg.jr_method,
62
+ :method_args => msg.jr_args,
63
+ :headers => headers,
64
+ :client_ip => client_ip,
65
+ :client_port => client_port,
66
+ :rjr_node => @rjr_node,
67
+ :rjr_node_id => @rjr_node.node_id,
68
+ :rjr_node_type => TCPNode::RJR_NODE_TYPE,
69
+ :rjr_callback =>
70
+ TCPNodeCallback.new(:endpoint => self,
71
+ :headers => headers))
72
+ response = ResponseMessage.new(:id => msg.msg_id, :result => result, :headers => headers)
73
+ send_data(response.to_s)
74
+ end
75
+
76
+ def handle_response(data)
77
+ msg = ResponseMessage.new(:message => data, :headers => @rjr_node.message_headers)
78
+ res = err = nil
79
+ begin
80
+ res = Dispatcher.handle_response(msg.result)
81
+ rescue Exception => e
82
+ err = e
83
+ end
84
+
85
+ @rjr_node.response_lock.synchronize {
86
+ @rjr_node.responses << [msg.msg_id, res]
87
+ @rjr_node.responses.last << err unless err.nil?
88
+ @rjr_node.response_cv.signal
89
+ }
90
+ end
91
+ end
92
+
93
+ # TCP node definition, listen for and invoke json-rpc requests via tcp sockets
94
+ class TCPNode < RJR::Node
95
+ RJR_NODE_TYPE = :tcp
96
+
97
+ attr_accessor :response_lock
98
+ attr_accessor :response_cv
99
+ attr_accessor :responses
100
+
101
+ public
102
+ # initialize the node w/ the specified params
103
+ def initialize(args = {})
104
+ super(args)
105
+ @host = args[:host]
106
+ @port = args[:port]
107
+
108
+ @response_lock = Mutex.new
109
+ @response_cv = ConditionVariable.new
110
+ @response_cv = ConditionVariable.new
111
+ @response_check_cv = ConditionVariable.new
112
+ @responses = []
113
+
114
+ @connection_event_handlers = {:closed => [], :error => []}
115
+ end
116
+
117
+ # register connection event handler
118
+ def on(event, &handler)
119
+ if @connection_event_handlers.keys.include?(event)
120
+ @connection_event_handlers[event] << handler
121
+ end
122
+ end
123
+
124
+ # Initialize the tcp subsystem
125
+ def init_node
126
+ end
127
+
128
+ # Instruct Node to start listening for and dispatching rpc requests
129
+ def listen
130
+ em_run {
131
+ init_node
132
+ EventMachine::start_server @host, @port, TCPNodeEndpoint, { :rjr_node => self }
133
+ }
134
+ end
135
+
136
+ # Instructs node to send rpc request, and wait for / return response
137
+ def invoke_request(uri, rpc_method, *args)
138
+ uri = URI.parse(uri)
139
+ host,port = uri.host, uri.port
140
+
141
+ message = RequestMessage.new :method => rpc_method,
142
+ :args => args,
143
+ :headers => @message_headers
144
+ em_run{
145
+ init_node
146
+ EventMachine::connect host, port, TCPNodeEndpoint, { :rjr_node => self,
147
+ :init_message => message }
148
+ }
149
+
150
+ # wait for matching response
151
+ res = nil
152
+ while res.nil?
153
+ @response_lock.synchronize {
154
+ @response_cv.wait response_lock
155
+ res = @responses.select { |response| message.msg_id == response.first }.first
156
+ unless res.nil?
157
+ @responses.delete(res)
158
+ else
159
+ @response_cv.signal
160
+ @response_check_cv.wait @response_lock
161
+ end
162
+ @response_check_cv.signal
163
+ }
164
+ end
165
+
166
+ # raise error or return result
167
+ if res.size > 2
168
+ raise res[2]
169
+ end
170
+ return res[1]
171
+ end
172
+ end
173
+
174
+ end # module RJR
@@ -1,27 +1,7 @@
1
1
  # Thread Pool
2
2
  #
3
- # Copyright (C) 2010 Mohammed Morsi <movitto@yahoo.com>
4
- #
5
- # Permission is hereby granted, free of charge, to any person
6
- # obtaining a copy of this software and associated documentation
7
- # files (the "Software"), to deal in the Software without
8
- # restriction, including without limitation the rights to use,
9
- # copy, modify, merge, publish, distribute, sublicense, and/or sell
10
- # copies of the Software, and to permit persons to whom the
11
- # Software is furnished to do so, subject to the following
12
- # conditions:
13
- #
14
- # The above copyright notice and this permission notice shall be
15
- # included in all copies or substantial portions of the Software.
16
- #
17
- # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
18
- # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
19
- # OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
20
- # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
21
- # HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
22
- # WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
23
- # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
24
- # OTHER DEALINGS IN THE SOFTWARE.
3
+ # Copyright (C) 2010-2012 Mohammed Morsi <mo@morsi.org>
4
+ # Licensed under the Apache License, Version 2.0
25
5
 
26
6
  # Work item to be executed in thread pool
27
7
  class ThreadPoolJob
@@ -72,7 +52,7 @@ class ThreadPool
72
52
  def running?
73
53
  res = nil
74
54
  @thread_lock.synchronize{
75
- res = (@thread.status != false)
55
+ res = (!@thread.nil? && (@thread.status != false))
76
56
  }
77
57
  res
78
58
  end
@@ -93,6 +73,13 @@ class ThreadPool
93
73
  @thread.kill
94
74
  @thread.join
95
75
  end
76
+ @thread = nil
77
+ }
78
+ end
79
+
80
+ def join
81
+ @thread_lock.synchronize {
82
+ @thread.join unless @thread.nil?
96
83
  }
97
84
  end
98
85
  end
@@ -130,7 +117,7 @@ class ThreadPool
130
117
  end
131
118
 
132
119
  def running?
133
- !terminate && (@timeout.nil? || @timeout_thread.status) &&
120
+ !terminate && (@timeout.nil? || (!@timeout_thread.nil? && @timeout_thread.status)) &&
134
121
  @job_runners.all? { |r| r.running? }
135
122
  end
136
123
 
@@ -157,9 +144,17 @@ class ThreadPool
157
144
  # Terminate the thread pool
158
145
  def stop
159
146
  terminate = true
160
- @timeout_thread.join unless @timout_thread.nil?
147
+ unless @timout_thread.nil?
148
+ @timeout_thread.join
149
+ @timeout_thread.terminate
150
+ end
151
+ @timeout_thread = nil
161
152
  @work_queue.clear
162
153
  @job_runners_lock.synchronize { @job_runners.each { |jr| jr.stop } }
163
154
  end
155
+
156
+ def join
157
+ @job_runners_lock.synchronize { @job_runners.each { |jr| jr.join } }
158
+ end
164
159
  end
165
160
 
data/lib/rjr/web_node.rb CHANGED
@@ -1,7 +1,7 @@
1
1
  # RJR WWW Endpoint
2
2
  #
3
3
  # Copyright (C) 2012 Mohammed Morsi <mo@morsi.org>
4
- # Licensed under the AGPLv3+ http://www.gnu.org/licenses/agpl.txt
4
+ # Licensed under the Apache License, Version 2.0
5
5
 
6
6
  # establish client connection w/ specified args and invoke block w/
7
7
  # newly created client, returning it after block terminates
data/lib/rjr/ws_node.rb CHANGED
@@ -1,7 +1,7 @@
1
1
  # RJR WebSockets Endpoint
2
2
  #
3
3
  # Copyright (C) 2012 Mohammed Morsi <mo@morsi.org>
4
- # Licensed under the AGPLv3+ http://www.gnu.org/licenses/agpl.txt
4
+ # Licensed under the Apache License, Version 2.0
5
5
 
6
6
  # establish client connection w/ specified args and invoke block w/
7
7
  # newly created client, returning it after block terminates
@@ -26,6 +26,7 @@ class WSNodeCallback
26
26
  #msg = CallbackMessage.new(:data => data)
27
27
  msg = RequestMessage.new :method => callback_method, :args => data, :headers => @message_headers
28
28
  raise RJR::Errors::ConnectionError.new("websocket closed") if @socket.state == :closed
29
+ # TODO surround w/ begin/rescue block incase of other socket errors?
29
30
  @socket.send(msg.to_s)
30
31
  end
31
32
  end
data/lib/rjr.rb CHANGED
@@ -1,7 +1,7 @@
1
- # json-rpc over qpid
1
+ # rjr - Ruby Json Rpc
2
2
  #
3
- # Copyright (C) 2010 Mohammed Morsi <movitto@yahoo.com>
4
- # Licensed under the AGPLv3+ http://www.gnu.org/licenses/agpl.txt
3
+ # Copyright (C) 2010-2012 Mohammed Morsi <mo@morsi.org>
4
+ # Licensed under the Apache License, Version 2.0
5
5
 
6
6
  require 'rjr/common'
7
7
  require 'rjr/errors'
@@ -14,4 +14,5 @@ require 'rjr/local_node'
14
14
  require 'rjr/amqp_node'
15
15
  require 'rjr/ws_node'
16
16
  require 'rjr/web_node'
17
+ require 'rjr/tcp_node'
17
18
  require 'rjr/multi_node'
@@ -0,0 +1,32 @@
1
+ require 'rjr/tcp_node'
2
+ require 'rjr/dispatcher'
3
+
4
+ describe RJR::TCPNode do
5
+ it "should invoke and satisfy tcp requests" do
6
+ server = RJR::TCPNode.new :node_id => 'tcp', :host => 'localhost', :port => 9987
7
+ client = RJR::TCPNode.new
8
+
9
+ foobar_invoked = false
10
+ RJR::Dispatcher.init_handlers
11
+ RJR::Dispatcher.add_handler('foobar') { |param|
12
+ @client_ip.should == "127.0.0.1"
13
+ #@client_port.should == 9987
14
+ @rjr_node.should == server
15
+ @rjr_node_id.should == 'tcp'
16
+ @rjr_node_type.should == :tcp
17
+ param.should == 'myparam'
18
+ foobar_invoked = true
19
+ 'retval'
20
+ }
21
+
22
+ server.listen
23
+ sleep 1
24
+ res = client.invoke_request 'jsonrpc://localhost:9987', 'foobar', 'myparam'
25
+ res.should == 'retval'
26
+ server.halt
27
+ server.join
28
+ foobar_invoked.should == true
29
+ end
30
+
31
+ # TODO ensure closed / error event handlers are invoked
32
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rjr
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.1
4
+ version: 0.7.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-04-25 00:00:00.000000000 Z
12
+ date: 2012-08-26 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rspec
@@ -53,6 +53,7 @@ files:
53
53
  - specs/dispatcher_spec.rb
54
54
  - specs/node_spec.rb
55
55
  - specs/amqp_node_spec.rb
56
+ - specs/tcp_node_spec.rb
56
57
  - specs/multi_node_spec.rb
57
58
  - specs/local_node_spec.rb
58
59
  - specs/web_node_spec.rb