ione-rpc 1.0.0.pre3 → 1.0.0.pre4
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 +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
|