ione-rpc 1.0.0.pre3 → 1.0.0.pre4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/ione/rpc/peer.rb +2 -0
- data/lib/ione/rpc/server.rb +61 -8
- data/lib/ione/rpc/version.rb +1 -1
- data/spec/ione/rpc/peer_common.rb +13 -0
- data/spec/ione/rpc/server_spec.rb +143 -7
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2219fec79fa845ec467d80462b2b25b521649afe
|
4
|
+
data.tar.gz: 0a505a71f3c16bba88a160925c9f4d6d511b51cf
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 2e7f795882099bbc4753f0295085962ddb6c05af8cfd4ae67dc4cdcabb854e5a16816acaf147a7ba43860ea37249e61f06270de638cd47bb2c2cc64bc4930709
|
7
|
+
data.tar.gz: 89b9886afc1ff2b7fda147ed7fda063489a60052204b5a2ab9a5e334571aefc51520f432ece8aa6e1d2266814f9c944d2cf1403add28bf7623a32442227ed526
|
data/lib/ione/rpc/peer.rb
CHANGED
data/lib/ione/rpc/server.rb
CHANGED
@@ -6,7 +6,8 @@ module Ione
|
|
6
6
|
#
|
7
7
|
# To implement a server you need to create a subclass of this class and
|
8
8
|
# implement {#handle_request}. You can also optionally implement
|
9
|
-
# {#handle_connection} to do initialization when a new client connects
|
9
|
+
# {#handle_connection} to do initialization when a new client connects, and
|
10
|
+
# {#handle_error} to handle errors that occur during the request handling.
|
10
11
|
class Server
|
11
12
|
attr_reader :port
|
12
13
|
|
@@ -41,8 +42,6 @@ module Ione
|
|
41
42
|
@io_reactor.stop.map(self)
|
42
43
|
end
|
43
44
|
|
44
|
-
protected
|
45
|
-
|
46
45
|
# Override this method to do work when a new client connects.
|
47
46
|
#
|
48
47
|
# This method may be called concurrently.
|
@@ -51,6 +50,27 @@ module Ione
|
|
51
50
|
def handle_connection(connection)
|
52
51
|
end
|
53
52
|
|
53
|
+
# Override this method to handle errors raised or returned by
|
54
|
+
# {#handle_request} or any other part of the request handling (for example
|
55
|
+
# the response encoding).
|
56
|
+
#
|
57
|
+
# When this method raises an error or returns a failed future there will be
|
58
|
+
# no response for the request. Unless you use a custom client this means
|
59
|
+
# that you lock up one of the connection's channels forever, and depending
|
60
|
+
# on the client implementation it may lock up resources. Make sure that
|
61
|
+
# you always return a future that will resolve to something that will be
|
62
|
+
# encodeable.
|
63
|
+
#
|
64
|
+
# Should this method fail a message will be logged at the `error` level
|
65
|
+
# with the error message and class. The full backtrace will also be logged
|
66
|
+
# at the `debug` level.
|
67
|
+
#
|
68
|
+
# @return [Ione::Future<Object>] a future that will resolve to an alternate
|
69
|
+
# response for this request.
|
70
|
+
def handle_error(error, request, connection)
|
71
|
+
Ione::Future.failed(error)
|
72
|
+
end
|
73
|
+
|
54
74
|
# Override this method to handle requests.
|
55
75
|
#
|
56
76
|
# You must respond to all requests, otherwise the client will eventually
|
@@ -58,6 +78,9 @@ module Ione
|
|
58
78
|
#
|
59
79
|
# This method may be called concurrently.
|
60
80
|
#
|
81
|
+
# When this method raises an error, or returns a failed future, {#handle_error}
|
82
|
+
# will be called with the error.
|
83
|
+
#
|
61
84
|
# @param [Object] message a (decoded) message from a client
|
62
85
|
# @param [#host, #port, #on_closed] connection the client connection that
|
63
86
|
# received the message
|
@@ -66,6 +89,20 @@ module Ione
|
|
66
89
|
Future.resolved
|
67
90
|
end
|
68
91
|
|
92
|
+
# @private
|
93
|
+
def guarded_handle_error(error, request, connection)
|
94
|
+
handle_error(error, request, connection)
|
95
|
+
rescue => e
|
96
|
+
Ione::Future.failed(e)
|
97
|
+
end
|
98
|
+
|
99
|
+
# @private
|
100
|
+
def guarded_handle_request(message, connection)
|
101
|
+
handle_request(message, connection)
|
102
|
+
rescue => e
|
103
|
+
Ione::Future.failed(e)
|
104
|
+
end
|
105
|
+
|
69
106
|
private
|
70
107
|
|
71
108
|
def setup_server
|
@@ -73,7 +110,7 @@ module Ione
|
|
73
110
|
@logger.info('Server listening for connections on %s:%d' % [@bind_address, @port]) if @logger
|
74
111
|
acceptor.on_accept do |connection|
|
75
112
|
@logger.info('Connection from %s:%d accepted' % [connection.host, connection.port]) if @logger
|
76
|
-
peer = ServerPeer.new(connection, @codec, self)
|
113
|
+
peer = ServerPeer.new(connection, @codec, self, @logger)
|
77
114
|
peer.on_closed do
|
78
115
|
@logger.info('Connection from %s:%d closed' % [connection.host, connection.port]) if @logger
|
79
116
|
end
|
@@ -84,15 +121,31 @@ module Ione
|
|
84
121
|
|
85
122
|
# @private
|
86
123
|
class ServerPeer < Peer
|
87
|
-
def initialize(connection, codec, server)
|
124
|
+
def initialize(connection, codec, server, logger)
|
88
125
|
super(connection, codec)
|
89
126
|
@server = server
|
127
|
+
@logger = logger
|
90
128
|
end
|
91
129
|
|
92
130
|
def handle_message(message, channel)
|
93
|
-
f = @server.
|
94
|
-
f
|
95
|
-
|
131
|
+
f = @server.guarded_handle_request(message, self)
|
132
|
+
send_response(f, message, channel, true)
|
133
|
+
end
|
134
|
+
|
135
|
+
def send_response(f, message, channel, try_again)
|
136
|
+
f = f.map do |response|
|
137
|
+
encoded_response = @codec.encode(response, channel)
|
138
|
+
@connection.write(encoded_response)
|
139
|
+
nil
|
140
|
+
end
|
141
|
+
f.fallback do |error|
|
142
|
+
if try_again
|
143
|
+
ff = @server.guarded_handle_error(error, message, self)
|
144
|
+
send_response(ff, message, channel, false)
|
145
|
+
else
|
146
|
+
@logger.error('Unhandled error: %s (%s)' % [error.message, error.class.name])
|
147
|
+
error.backtrace && @logger.debug(error.backtrace.join("#{$/}\t"))
|
148
|
+
end
|
96
149
|
end
|
97
150
|
end
|
98
151
|
end
|
data/lib/ione/rpc/version.rb
CHANGED
@@ -66,6 +66,15 @@ shared_examples 'peers' do
|
|
66
66
|
connection.data_listener.call('FOOBARBAZ')
|
67
67
|
peer.messages.should have(3).items
|
68
68
|
end
|
69
|
+
|
70
|
+
it 'closes the connection when there is an error decoding a frame' do
|
71
|
+
error = nil
|
72
|
+
peer.on_closed { |e| error = e }
|
73
|
+
codec.stub(:decode).with(Ione::ByteBuffer.new('FOOBARBAZ'), anything).and_raise(Ione::Rpc::CodecError.new('Bork'))
|
74
|
+
connection.data_listener.call('FOOBARBAZ')
|
75
|
+
error.should be_a(Ione::Rpc::CodecError)
|
76
|
+
error.message.should == 'Bork'
|
77
|
+
end
|
69
78
|
end
|
70
79
|
|
71
80
|
context 'when sending data' do
|
@@ -123,6 +132,10 @@ module RpcSpec
|
|
123
132
|
@written_bytes = ''
|
124
133
|
end
|
125
134
|
|
135
|
+
def close(cause=nil)
|
136
|
+
@closed_listener.call(cause) if @closed_listener
|
137
|
+
end
|
138
|
+
|
126
139
|
def write(bytes)
|
127
140
|
@written_bytes << bytes
|
128
141
|
end
|
@@ -15,7 +15,7 @@ module Ione
|
|
15
15
|
end
|
16
16
|
|
17
17
|
let :logger do
|
18
|
-
double(:logger)
|
18
|
+
double(:logger, debug: nil, info: nil, warn: nil, error: nil)
|
19
19
|
end
|
20
20
|
|
21
21
|
let :acceptor do
|
@@ -43,10 +43,6 @@ module Ione
|
|
43
43
|
codec.stub(:encode) { |msg, _| msg }
|
44
44
|
end
|
45
45
|
|
46
|
-
before do
|
47
|
-
logger.stub(:info)
|
48
|
-
end
|
49
|
-
|
50
46
|
describe '#port' do
|
51
47
|
it 'returns the port the server is listening on' do
|
52
48
|
server.port.should == 4321
|
@@ -182,7 +178,133 @@ module Ione
|
|
182
178
|
raw_connection.should have_received(:write).with('42BAZFOO')
|
183
179
|
end
|
184
180
|
|
185
|
-
|
181
|
+
context 'and there is an error' do
|
182
|
+
before do
|
183
|
+
codec.stub(:encode).with('BAZFOO', 42).and_return('42BAZFOO')
|
184
|
+
end
|
185
|
+
|
186
|
+
it 'calls #handle_error when #handle_request raises an error' do
|
187
|
+
server.override_handle_request { raise 'Borkzor' }
|
188
|
+
peer = server.connections.first
|
189
|
+
peer.handle_message('FOOBAZ', 42)
|
190
|
+
server.errors.first.message.should == 'Borkzor'
|
191
|
+
end
|
192
|
+
|
193
|
+
it 'calls #handle_error when #handle_request returns a failed future' do
|
194
|
+
server.override_handle_request { Ione::Future.failed(StandardError.new('Borkzor')) }
|
195
|
+
peer = server.connections.first
|
196
|
+
peer.handle_message('FOOBAZ', 42)
|
197
|
+
server.errors.should == [StandardError.new('Borkzor')]
|
198
|
+
end
|
199
|
+
|
200
|
+
it 'calls #handle_error with the error, the request and the connection' do
|
201
|
+
error, request, connection = nil, nil, nil
|
202
|
+
server.override_handle_request { Ione::Future.failed(StandardError.new('Borkzor')) }
|
203
|
+
server.override_handle_error { |e, r, c| error = e; request = r; connection = c }
|
204
|
+
peer = server.connections.first
|
205
|
+
peer.handle_message('FOOBAZ', 42)
|
206
|
+
error.should be_a(StandardError)
|
207
|
+
error.message.should == 'Borkzor'
|
208
|
+
request.should == 'FOOBAZ'
|
209
|
+
connection.should == peer
|
210
|
+
end
|
211
|
+
|
212
|
+
it 'responds with the message returned by #handle_error' do
|
213
|
+
server.override_handle_request { Ione::Future.failed(StandardError.new('Borkzor')) }
|
214
|
+
server.override_handle_error { |e| Ione::Future.resolved("OH NOES: #{e.message}") }
|
215
|
+
peer = server.connections.first
|
216
|
+
peer.handle_message('FOOBAZ', 42)
|
217
|
+
raw_connection.should have_received(:write).with('OH NOES: Borkzor')
|
218
|
+
end
|
219
|
+
|
220
|
+
it 'does not respond at all by default' do
|
221
|
+
server.override_handle_request { Ione::Future.failed(StandardError.new('Borkzor')) }
|
222
|
+
peer = server.connections.first
|
223
|
+
peer.handle_message('FOOBAZ', 42)
|
224
|
+
raw_connection.should_not have_received(:write)
|
225
|
+
end
|
226
|
+
|
227
|
+
it 'does not respond when #handle_error raises an error' do
|
228
|
+
server.override_handle_request { Ione::Future.failed(StandardError.new('Borkzor')) }
|
229
|
+
server.override_handle_error { |e| raise "OH NOES: #{e.message}" }
|
230
|
+
peer = server.connections.first
|
231
|
+
peer.handle_message('FOOBAZ', 42)
|
232
|
+
raw_connection.should_not have_received(:write)
|
233
|
+
end
|
234
|
+
|
235
|
+
it 'does not respond when #handle_error returns a failed future' do
|
236
|
+
server.override_handle_request { Ione::Future.failed(StandardError.new('Borkzor')) }
|
237
|
+
server.override_handle_error { |e| Ione::Future.failed(StandardError.new("OH NOES: #{e.message}")) }
|
238
|
+
peer = server.connections.first
|
239
|
+
peer.handle_message('FOOBAZ', 42)
|
240
|
+
raw_connection.should_not have_received(:write)
|
241
|
+
end
|
242
|
+
|
243
|
+
it 'logs unhandled errors' do
|
244
|
+
server.override_handle_request { raise StandardError, 'Borkzor' }
|
245
|
+
peer = server.connections.first
|
246
|
+
peer.handle_message('FOOBAZ', 42)
|
247
|
+
logger.should have_received(:error).with(/^Unhandled error: Borkzor \(StandardError\)/i)
|
248
|
+
logger.should have_received(:debug).with(/\d+:in/)
|
249
|
+
end
|
250
|
+
|
251
|
+
it 'logs when #handle_error raises an error' do
|
252
|
+
server.override_handle_request { Ione::Future.failed(StandardError.new('Borkzor')) }
|
253
|
+
server.override_handle_error { |e| raise StandardError, "OH NOES: #{e.message}" }
|
254
|
+
peer = server.connections.first
|
255
|
+
peer.handle_message('FOOBAZ', 42)
|
256
|
+
logger.should have_received(:error).with(/^Unhandled error: OH NOES: Borkzor \(StandardError\)/i)
|
257
|
+
end
|
258
|
+
|
259
|
+
it 'logs when #handle_error returns a failed future' do
|
260
|
+
server.override_handle_request { Ione::Future.failed(StandardError.new('Borkzor')) }
|
261
|
+
server.override_handle_error { |e| Ione::Future.failed(StandardError.new("OH NOES: #{e.message}")) }
|
262
|
+
peer = server.connections.first
|
263
|
+
peer.handle_message('FOOBAZ', 42)
|
264
|
+
logger.should have_received(:error).with(/^Unhandled error: OH NOES: Borkzor \(StandardError\)/i)
|
265
|
+
end
|
266
|
+
|
267
|
+
context 'when the codec fails to encode the response' do
|
268
|
+
it 'calls #handle_error' do
|
269
|
+
server.override_handle_request { Ione::Future.resolved('OK') }
|
270
|
+
codec.stub(:encode).with('OK', 42).and_raise(CodecError.new('Borkzor'))
|
271
|
+
peer = server.connections.first
|
272
|
+
peer.handle_message('FOOBAZ', 42)
|
273
|
+
server.errors.first.should be_a(CodecError)
|
274
|
+
server.errors.first.message.should == 'Borkzor'
|
275
|
+
end
|
276
|
+
|
277
|
+
it 'responds with what #handle_error responds with' do
|
278
|
+
server.override_handle_request { Ione::Future.resolved('OK') }
|
279
|
+
server.override_handle_error { |e| Ione::Future.resolved("ERROR: #{e.message}") }
|
280
|
+
codec.stub(:encode).with('OK', 42).and_raise(CodecError.new('Borkzor'))
|
281
|
+
peer = server.connections.first
|
282
|
+
peer.handle_message('FOOBAZ', 42)
|
283
|
+
raw_connection.should have_received(:write).with('ERROR: Borkzor')
|
284
|
+
end
|
285
|
+
|
286
|
+
it 'does not respond when the codec cannot encode the response returned by #handle_error' do
|
287
|
+
server.override_handle_request { Ione::Future.resolved('OK') }
|
288
|
+
server.override_handle_error { |e| Ione::Future.resolved("ERROR: #{e.message}") }
|
289
|
+
codec.stub(:encode).with('OK', 42).and_raise(CodecError.new('Borkzor'))
|
290
|
+
codec.stub(:encode).with('ERROR: Borkzor', 42).and_raise(CodecError.new('Buzzfuzz'))
|
291
|
+
peer = server.connections.first
|
292
|
+
peer.handle_message('FOOBAZ', 42)
|
293
|
+
raw_connection.should_not have_received(:write)
|
294
|
+
server.errors.should have(1).item
|
295
|
+
end
|
296
|
+
|
297
|
+
it 'logs when the codec cannot encode the response returned by #handle_error' do
|
298
|
+
server.override_handle_request { Ione::Future.resolved('OK') }
|
299
|
+
server.override_handle_error { |e| Ione::Future.resolved("ERROR: #{e.message}") }
|
300
|
+
codec.stub(:encode).with('OK', 42).and_raise(CodecError.new('Borkzor'))
|
301
|
+
codec.stub(:encode).with('ERROR: Borkzor', 42).and_raise(CodecError.new('Buzzfuzz'))
|
302
|
+
peer = server.connections.first
|
303
|
+
peer.handle_message('FOOBAZ', 42)
|
304
|
+
logger.should have_received(:error).with(/^Unhandled error: Buzzfuzz \(Ione::Rpc::CodecError\)/i)
|
305
|
+
end
|
306
|
+
end
|
307
|
+
end
|
186
308
|
end
|
187
309
|
end
|
188
310
|
end
|
@@ -190,22 +312,36 @@ end
|
|
190
312
|
|
191
313
|
module ServerSpec
|
192
314
|
class TestServer < Ione::Rpc::Server
|
193
|
-
attr_reader :connections, :received_messages
|
315
|
+
attr_reader :connections, :received_messages, :errors
|
194
316
|
|
195
317
|
def initialize(*)
|
196
318
|
super
|
197
319
|
@connections = []
|
198
320
|
@received_messages = []
|
321
|
+
@errors = []
|
199
322
|
end
|
200
323
|
|
201
324
|
def handle_connection(peer)
|
202
325
|
@connections << peer
|
203
326
|
end
|
204
327
|
|
328
|
+
def override_handle_error(&handler)
|
329
|
+
@error_handler = handler
|
330
|
+
end
|
331
|
+
|
205
332
|
def override_handle_request(&handler)
|
206
333
|
@request_handler = handler
|
207
334
|
end
|
208
335
|
|
336
|
+
def handle_error(error, request, connection)
|
337
|
+
@errors << error
|
338
|
+
if @error_handler
|
339
|
+
@error_handler.call(error, request, connection)
|
340
|
+
else
|
341
|
+
super
|
342
|
+
end
|
343
|
+
end
|
344
|
+
|
209
345
|
def handle_request(request, peer)
|
210
346
|
@received_messages << [request, peer]
|
211
347
|
if @request_handler
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ione-rpc
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0.0.
|
4
|
+
version: 1.0.0.pre4
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Theo Hultberg
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-06-
|
11
|
+
date: 2014-06-26 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: ione
|