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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 98f7be7547ebdcd263f54d286682869bd629dfdc
4
- data.tar.gz: 6d331deff1f746ce7c69f3914f9527f115701d08
3
+ metadata.gz: 2219fec79fa845ec467d80462b2b25b521649afe
4
+ data.tar.gz: 0a505a71f3c16bba88a160925c9f4d6d511b51cf
5
5
  SHA512:
6
- metadata.gz: 4ecab7afd0cc50ecfcd9475d8f2b04da77aac64b063e022b29498556241e0bddd64329291fb2c64299cc670d22c9368203c4cb66c5ab5113d4aeb2bac035bc2f
7
- data.tar.gz: 33a712ea45a402f0f5fd501fc3b4eb14af603c56c1496424512252e2c387f9801a0fc8a6b2d91e45dc25e45f6c70a50939bf60222ad16211b1e4f44c59695854
6
+ metadata.gz: 2e7f795882099bbc4753f0295085962ddb6c05af8cfd4ae67dc4cdcabb854e5a16816acaf147a7ba43860ea37249e61f06270de638cd47bb2c2cc64bc4930709
7
+ data.tar.gz: 89b9886afc1ff2b7fda147ed7fda063489a60052204b5a2ab9a5e334571aefc51520f432ece8aa6e1d2266814f9c944d2cf1403add28bf7623a32442227ed526
data/lib/ione/rpc/peer.rb CHANGED
@@ -37,6 +37,8 @@ module Ione
37
37
  handle_message(@current_message, channel)
38
38
  @current_message = nil
39
39
  end
40
+ rescue => e
41
+ @connection.close(e)
40
42
  end
41
43
 
42
44
  def handle_message(message, channel)
@@ -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.handle_request(message, self)
94
- f.on_value do |response|
95
- @connection.write(@codec.encode(response, channel))
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
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Ione
4
4
  module Rpc
5
- VERSION = '1.0.0.pre3'.freeze
5
+ VERSION = '1.0.0.pre4'.freeze
6
6
  end
7
7
  end
@@ -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
- it 'handles that the server fails to process the request'
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.pre3
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-21 00:00:00.000000000 Z
11
+ date: 2014-06-26 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: ione