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 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