ione-rpc 1.0.0.pre2 → 1.0.0.pre3
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/client.rb +38 -2
- data/lib/ione/rpc/client_peer.rb +55 -24
- data/lib/ione/rpc/codec.rb +114 -23
- data/lib/ione/rpc/version.rb +1 -1
- data/spec/ione/rpc/client_peer_spec.rb +52 -0
- data/spec/ione/rpc/client_spec.rb +21 -0
- data/spec/ione/rpc/codec_spec.rb +75 -0
- 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: 98f7be7547ebdcd263f54d286682869bd629dfdc
|
4
|
+
data.tar.gz: 6d331deff1f746ce7c69f3914f9527f115701d08
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4ecab7afd0cc50ecfcd9475d8f2b04da77aac64b063e022b29498556241e0bddd64329291fb2c64299cc670d22c9368203c4cb66c5ab5113d4aeb2bac035bc2f
|
7
|
+
data.tar.gz: 33a712ea45a402f0f5fd501fc3b4eb14af603c56c1496424512252e2c387f9801a0fc8a6b2d91e45dc25e45f6c70a50939bf60222ad16211b1e4f44c59695854
|
data/lib/ione/rpc/client.rb
CHANGED
@@ -48,7 +48,36 @@ module Ione
|
|
48
48
|
|
49
49
|
# A client is connected when it has at least one open connection.
|
50
50
|
def connected?
|
51
|
-
@lock.
|
51
|
+
@lock.lock
|
52
|
+
@connections.any?
|
53
|
+
ensure
|
54
|
+
@lock.unlock
|
55
|
+
end
|
56
|
+
|
57
|
+
# Returns an array of info and statistics about the currently open connections.
|
58
|
+
#
|
59
|
+
# Each open connection is represented by a hash which includes the keys
|
60
|
+
#
|
61
|
+
# * `:host` and `:port`
|
62
|
+
# * `:max_channels`: the maximum number of messages to send concurrently
|
63
|
+
# * `:active_channels`: the number of sent messages that have not yet
|
64
|
+
# received a response
|
65
|
+
# * `:queued_messages`: the number of messages that couldn't be sent
|
66
|
+
# immediately because all channels were occupied and thus had to be queued.
|
67
|
+
# * `:sent_messages`: the total number of messages sent since the connection
|
68
|
+
# was opened
|
69
|
+
# * `:received_responses`: the total number of responses received since
|
70
|
+
# the connection was opened
|
71
|
+
# * `:timeouts`: the number of sent messages that did not receive a response
|
72
|
+
# before their timeout expired
|
73
|
+
#
|
74
|
+
# @return [Array<Hash>] an array of hashes that each contain info and
|
75
|
+
# statistics from an open connection.
|
76
|
+
def connection_stats
|
77
|
+
@lock.lock
|
78
|
+
@connections.map(&:stats)
|
79
|
+
ensure
|
80
|
+
@lock.unlock
|
52
81
|
end
|
53
82
|
|
54
83
|
# Start the client and connect to all hosts. This also starts the IO
|
@@ -150,7 +179,14 @@ module Ione
|
|
150
179
|
# error response – that is protocol specific and up to the implementation
|
151
180
|
# to handle), or when there was no connection open.
|
152
181
|
def send_request(request, connection=nil, timeout=nil)
|
153
|
-
|
182
|
+
unless connection
|
183
|
+
@lock.lock
|
184
|
+
begin
|
185
|
+
connection = choose_connection(@connections, request)
|
186
|
+
ensure
|
187
|
+
@lock.unlock
|
188
|
+
end
|
189
|
+
end
|
154
190
|
if connection
|
155
191
|
f = connection.send_message(request, timeout)
|
156
192
|
f = f.fallback do |error|
|
data/lib/ione/rpc/client_peer.rb
CHANGED
@@ -14,22 +14,46 @@ module Ione
|
|
14
14
|
@channels = [nil] * max_channels
|
15
15
|
@queue = []
|
16
16
|
@encode_eagerly = @codec.recoding?
|
17
|
+
@sent_messages = 0
|
18
|
+
@received_responses = 0
|
19
|
+
@timeouts = 0
|
20
|
+
end
|
21
|
+
|
22
|
+
def stats
|
23
|
+
@lock.lock
|
24
|
+
{
|
25
|
+
:host => @host,
|
26
|
+
:port => @port,
|
27
|
+
:max_channels => @channels.size,
|
28
|
+
:active_channels => @channels.size - @channels.count(nil),
|
29
|
+
:queued_messages => @queue.size,
|
30
|
+
:sent_messages => @sent_messages,
|
31
|
+
:received_responses => @received_responses,
|
32
|
+
:timeouts => @timeouts,
|
33
|
+
}
|
34
|
+
ensure
|
35
|
+
@lock.unlock
|
17
36
|
end
|
18
37
|
|
19
38
|
def send_message(request, timeout=nil)
|
20
39
|
promise = Ione::Promise.new
|
21
|
-
channel =
|
22
|
-
|
40
|
+
channel = nil
|
41
|
+
@lock.lock
|
42
|
+
begin
|
43
|
+
channel = take_channel(promise)
|
44
|
+
@sent_messages += 1 if channel
|
45
|
+
ensure
|
46
|
+
@lock.unlock
|
23
47
|
end
|
24
48
|
if channel
|
25
49
|
@connection.write(@codec.encode(request, channel))
|
26
50
|
else
|
27
|
-
@
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
51
|
+
pair = [@encode_eagerly ? @codec.encode(request, -1) : request, promise]
|
52
|
+
@lock.lock
|
53
|
+
begin
|
54
|
+
@queue << pair
|
55
|
+
ensure
|
56
|
+
@lock.unlock
|
33
57
|
end
|
34
58
|
end
|
35
59
|
if timeout
|
@@ -37,6 +61,7 @@ module Ione
|
|
37
61
|
unless promise.future.completed?
|
38
62
|
error = Rpc::TimeoutError.new('No response received within %ss' % timeout.to_s)
|
39
63
|
promise.fail(error)
|
64
|
+
@timeouts += 1
|
40
65
|
end
|
41
66
|
end
|
42
67
|
end
|
@@ -46,10 +71,14 @@ module Ione
|
|
46
71
|
private
|
47
72
|
|
48
73
|
def handle_message(response, channel)
|
49
|
-
promise =
|
74
|
+
promise = nil
|
75
|
+
@lock.lock
|
76
|
+
begin
|
50
77
|
promise = @channels[channel]
|
51
78
|
@channels[channel] = nil
|
52
|
-
promise
|
79
|
+
@received_responses += 1 if promise
|
80
|
+
ensure
|
81
|
+
@lock.unlock
|
53
82
|
end
|
54
83
|
if promise && !promise.future.completed?
|
55
84
|
promise.fulfill(response)
|
@@ -58,24 +87,26 @@ module Ione
|
|
58
87
|
end
|
59
88
|
|
60
89
|
def flush_queue
|
61
|
-
@lock.
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
else
|
70
|
-
@connection.write(@codec.encode(request))
|
71
|
-
end
|
72
|
-
count += 1
|
90
|
+
@lock.lock
|
91
|
+
count = 0
|
92
|
+
max = @queue.size
|
93
|
+
while count < max
|
94
|
+
request, promise = @queue[count]
|
95
|
+
if (channel = take_channel(promise))
|
96
|
+
if @encode_eagerly
|
97
|
+
@connection.write(@codec.recode(request, channel))
|
73
98
|
else
|
74
|
-
|
99
|
+
@connection.write(@codec.encode(request))
|
75
100
|
end
|
101
|
+
count += 1
|
102
|
+
else
|
103
|
+
break
|
76
104
|
end
|
77
|
-
@queue = @queue.drop(count)
|
78
105
|
end
|
106
|
+
@sent_messages += count
|
107
|
+
@queue = @queue.drop(count)
|
108
|
+
ensure
|
109
|
+
@lock.unlock
|
79
110
|
end
|
80
111
|
|
81
112
|
def take_channel(promise)
|
data/lib/ione/rpc/codec.rb
CHANGED
@@ -2,10 +2,21 @@
|
|
2
2
|
|
3
3
|
module Ione
|
4
4
|
module Rpc
|
5
|
+
CodecError = Class.new(StandardError)
|
6
|
+
|
5
7
|
# Codecs are used to encode and decode the messages sent between the client
|
6
8
|
# and server. Codecs must be able to decode frames in a streaming fashion,
|
7
9
|
# i.e. frames that come in pieces.
|
8
10
|
#
|
11
|
+
# Codecs can be configured to compress frame bodies by giving them a
|
12
|
+
# compressor. A compressor is an object that responds to `#compress`,
|
13
|
+
# `#decompress` and `#compress?`. The first takes a string and returns
|
14
|
+
# a compressed string, the second does the reverse and the third is a way
|
15
|
+
# for the compressor to advice the codec whether or not it's meaningful to
|
16
|
+
# encode the frame at all. `#compress?` will get the same argument as
|
17
|
+
# `#compress` and should return true or false. It could for example return
|
18
|
+
# true when the frame size is over a threshold value.
|
19
|
+
#
|
9
20
|
# If you want to control how messages are framed you can implement your
|
10
21
|
# own codec from scratch by implementing {#encode} and {#decode}, but most
|
11
22
|
# of the time you should only need to implement {#encode_message} and
|
@@ -15,48 +26,90 @@ module Ione
|
|
15
26
|
#
|
16
27
|
# Codecs must be stateless.
|
17
28
|
#
|
29
|
+
# Since the IO layer has no knowledge about the framing it can't know
|
30
|
+
# how many bytes are needed before a frame can be fully decoded so instead
|
31
|
+
# the codec needs to be able to process partial frames. At the same time
|
32
|
+
# a codec can be used concurrently and must be stateless. To support partial
|
33
|
+
# decoding a codec can return a state instead of a decoded message until
|
34
|
+
# a complete frame can be decoded.
|
35
|
+
#
|
36
|
+
# The codec must return three values: the last value must be true if a
|
37
|
+
# if a message was decoded fully, and false if not enough bytes were
|
38
|
+
# available. When it is true the first piece is the message and the second
|
39
|
+
# the channel. When it is false the first piece is the partial decoded
|
40
|
+
# state (this can be any object at all) and the second is nil. The partial
|
41
|
+
# decoded state will be passed in as the second argument on the next call.
|
42
|
+
#
|
43
|
+
# In other words: the first time {#decode} is called the second argument
|
44
|
+
# will be nil, but on subsequent calls, until the third return value is
|
45
|
+
# true, the second argument will be whatever was returned by the previous
|
46
|
+
# call.
|
47
|
+
#
|
48
|
+
# The buffer might contain more bytes than needed to decode a frame, and
|
49
|
+
# the implementation must not consume these. The implementation must
|
50
|
+
# consume all of the bytes of the current frame, but none of the bytes of
|
51
|
+
# the next frame.
|
52
|
+
#
|
18
53
|
# Codecs must also make sure that these (conceptual) invariants hold:
|
19
54
|
# `decode(encode(message, channel)) == [message, channel]` and
|
20
55
|
# `encode(decode(frame)) == frame` (the return values are not entirely
|
21
56
|
# compatible, but the concept should be clear).
|
22
57
|
class Codec
|
58
|
+
# @param [Hash] options
|
59
|
+
# @option options [Object] :compressor a compressor to use to compress
|
60
|
+
# and decompress frames (see above for the required interface it needs
|
61
|
+
# to implement).
|
62
|
+
# @option options [Object] :lazy whether or not to decode the frames
|
63
|
+
# immediately or return an object that can be decoded later, see {#decode}.
|
64
|
+
def initialize(options={})
|
65
|
+
@compressor = options[:compressor]
|
66
|
+
@lazy = options[:lazy]
|
67
|
+
end
|
68
|
+
|
23
69
|
# Encodes a frame with a header that includes the frame size and channel,
|
24
70
|
# and the message as body.
|
25
71
|
#
|
72
|
+
# Will compress the frame body and set the compression flag when the codec
|
73
|
+
# has been configured with a compressor, and it says the frame should be
|
74
|
+
# compressed.
|
75
|
+
#
|
26
76
|
# @param [Object] message the message to encode
|
27
77
|
# @param [Integer] channel the channel to encode into the frame
|
28
78
|
# @return [String] an encoded frame with the message and channel
|
29
79
|
def encode(message, channel)
|
30
80
|
data = encode_message(message)
|
31
|
-
|
81
|
+
flags = 0
|
82
|
+
if @compressor && @compressor.compress?(data)
|
83
|
+
data = @compressor.compress(data)
|
84
|
+
flags |= COMPRESSION_FLAG
|
85
|
+
end
|
86
|
+
[2, flags, channel, data.bytesize, data.to_s].pack(FRAME_V2_FORMAT)
|
32
87
|
end
|
33
88
|
|
34
89
|
# Decodes a frame, piece by piece if necessary.
|
35
90
|
#
|
36
|
-
# Since the
|
37
|
-
#
|
38
|
-
#
|
39
|
-
# a
|
40
|
-
#
|
41
|
-
# a
|
42
|
-
#
|
43
|
-
#
|
44
|
-
# if a message was decoded fully, and false if not enough bytes were
|
45
|
-
# available. When it is true the first piece is the message and the second
|
46
|
-
# the channel. When it is false the first piece is the partial decoded
|
47
|
-
# state (this can be any object at all) and the second is nil. The partial
|
48
|
-
# decoded state will be passed in as the second argument on the next call.
|
91
|
+
# Since the codec can be called before a full frame has been received it
|
92
|
+
# returns a three-tuple where the last component is a boolean flag that says
|
93
|
+
# whether or not the frame is completely decoded. As long as the buffer
|
94
|
+
# does not contain a full frame the first component will be a state object
|
95
|
+
# which must be passed in as the second argument on the next call to
|
96
|
+
# {#decode}. When the buffer contains a full frame {#decode_message} will
|
97
|
+
# be called and the decoded message returned as the first component, the
|
98
|
+
# second will be the channel and the third will be true.
|
49
99
|
#
|
50
|
-
#
|
51
|
-
#
|
52
|
-
#
|
53
|
-
# call.
|
100
|
+
# When the compression flag is set the codec uses the configured compressor
|
101
|
+
# to decode the frame before passing the decompressed bytes to
|
102
|
+
# {#decode_message}.
|
54
103
|
#
|
55
|
-
#
|
56
|
-
# the
|
57
|
-
#
|
58
|
-
# the
|
104
|
+
# Since the IO thread can spend a significant proportion of its time doing
|
105
|
+
# frame decoding (the encoding is done in the calling thread) there is an
|
106
|
+
# option to create "lazy" codecs that return an intermediate object instead
|
107
|
+
# of a decoded message. When the codec is created with the `:lazy` option
|
108
|
+
# {#decode} returns an object with a `#decode` method, that in turn returns
|
109
|
+
# the decoded message.
|
59
110
|
#
|
111
|
+
# @raise [Ione::Rpc::CodecError] when a frame has the compression flag set
|
112
|
+
# and the codec is not configured with a compressor.
|
60
113
|
# @param [Ione::ByteBuffer] buffer the byte buffer that contains the frame
|
61
114
|
# data. The byte buffer is owned by the caller and should only be read from.
|
62
115
|
# @param [Object, nil] state the first value returned from the previous
|
@@ -73,12 +126,31 @@ module Ione
|
|
73
126
|
state.read_header
|
74
127
|
end
|
75
128
|
if state.body_ready?
|
76
|
-
|
129
|
+
body = state.read_body
|
130
|
+
channel = state.channel
|
131
|
+
if @lazy
|
132
|
+
message = LazilyDecodedFrame.new(self, state, body)
|
133
|
+
else
|
134
|
+
message = decode_body(state, body)
|
135
|
+
end
|
136
|
+
return message, state.channel, true
|
77
137
|
else
|
78
138
|
return state, nil, false
|
79
139
|
end
|
80
140
|
end
|
81
141
|
|
142
|
+
# @private
|
143
|
+
def decode_body(state, body)
|
144
|
+
if state.compressed?
|
145
|
+
if @compressor
|
146
|
+
body = @compressor.decompress(body)
|
147
|
+
else
|
148
|
+
raise CodecError, 'Compressed frame received but no compressor available'
|
149
|
+
end
|
150
|
+
end
|
151
|
+
decode_message(body)
|
152
|
+
end
|
153
|
+
|
82
154
|
# Whether or not this codec supports channel recoding, see {#recode}.
|
83
155
|
def recoding?
|
84
156
|
true
|
@@ -127,6 +199,7 @@ module Ione
|
|
127
199
|
def read_header
|
128
200
|
n = @buffer.read_short
|
129
201
|
@version = n >> 8
|
202
|
+
@compressed = n & COMPRESSION_FLAG == COMPRESSION_FLAG
|
130
203
|
if @version == 1
|
131
204
|
@channel = n & 0xff
|
132
205
|
else
|
@@ -146,6 +219,23 @@ module Ione
|
|
146
219
|
def body_ready?
|
147
220
|
@length && @buffer.size >= @length
|
148
221
|
end
|
222
|
+
|
223
|
+
def compressed?
|
224
|
+
@compressed
|
225
|
+
end
|
226
|
+
end
|
227
|
+
|
228
|
+
# @private
|
229
|
+
class LazilyDecodedFrame
|
230
|
+
def initialize(codec, state, body)
|
231
|
+
@codec = codec
|
232
|
+
@state = state
|
233
|
+
@body = body
|
234
|
+
end
|
235
|
+
|
236
|
+
def decode
|
237
|
+
@decoded ||= @codec.decode_body(@state, @body)
|
238
|
+
end
|
149
239
|
end
|
150
240
|
|
151
241
|
private
|
@@ -153,6 +243,7 @@ module Ione
|
|
153
243
|
FRAME_V1_FORMAT = 'ccNa*'.freeze
|
154
244
|
FRAME_V2_FORMAT = 'ccnNa*'.freeze
|
155
245
|
CHANNEL_FORMAT = 'n'.freeze
|
246
|
+
COMPRESSION_FLAG = 1
|
156
247
|
end
|
157
248
|
|
158
249
|
# A codec that works with encoders like JSON, MessagePack, YAML and others
|
data/lib/ione/rpc/version.rb
CHANGED
@@ -138,6 +138,58 @@ module Ione
|
|
138
138
|
end
|
139
139
|
end
|
140
140
|
end
|
141
|
+
|
142
|
+
describe '#stats' do
|
143
|
+
context 'returns a hash that' do
|
144
|
+
it 'contains the host' do
|
145
|
+
peer.stats.should include(host: connection.host)
|
146
|
+
end
|
147
|
+
|
148
|
+
it 'contains the port' do
|
149
|
+
peer.stats.should include(port: connection.port)
|
150
|
+
end
|
151
|
+
|
152
|
+
it 'contains the max number of channels' do
|
153
|
+
peer.stats.should include(max_channels: max_channels)
|
154
|
+
end
|
155
|
+
|
156
|
+
it 'contains the number of active channels' do
|
157
|
+
peer.stats.should include(active_channels: 0)
|
158
|
+
peer.send_message('hello')
|
159
|
+
peer.stats.should include(active_channels: 1)
|
160
|
+
end
|
161
|
+
|
162
|
+
it 'contains the number of queued messages' do
|
163
|
+
peer.stats.should include(queued_messages: 0)
|
164
|
+
17.times { peer.send_message('hello') }
|
165
|
+
peer.stats.should include(queued_messages: 1)
|
166
|
+
connection.data_listener.call('bar@000')
|
167
|
+
peer.stats.should include(queued_messages: 0)
|
168
|
+
end
|
169
|
+
|
170
|
+
it 'contains the number of sent messages' do
|
171
|
+
peer.stats.should include(sent_messages: 0)
|
172
|
+
17.times { peer.send_message('hello') }
|
173
|
+
connection.data_listener.call('bar@000')
|
174
|
+
peer.stats.should include(sent_messages: 17)
|
175
|
+
end
|
176
|
+
|
177
|
+
it 'contains the number of received responses' do
|
178
|
+
peer.stats.should include(received_responses: 0)
|
179
|
+
17.times { peer.send_message('hello') }
|
180
|
+
connection.data_listener.call('bar@000')
|
181
|
+
connection.data_listener.call('bar@000')
|
182
|
+
peer.stats.should include(received_responses: 2)
|
183
|
+
end
|
184
|
+
|
185
|
+
it 'contains the number of timed out responses' do
|
186
|
+
peer.stats.should include(timeouts: 0)
|
187
|
+
peer.send_message('hello', 1)
|
188
|
+
scheduler.timer_promises.first.fulfill
|
189
|
+
peer.stats.should include(timeouts: 1)
|
190
|
+
end
|
191
|
+
end
|
192
|
+
end
|
141
193
|
end
|
142
194
|
end
|
143
195
|
end
|
@@ -419,6 +419,19 @@ module Ione
|
|
419
419
|
end
|
420
420
|
end
|
421
421
|
|
422
|
+
describe '#connection_stats' do
|
423
|
+
it 'is empty when no connections are open' do
|
424
|
+
client.connection_stats.should be_empty
|
425
|
+
end
|
426
|
+
|
427
|
+
it 'returns an array of the host, port and statistics from each open connection' do
|
428
|
+
client.start.value
|
429
|
+
stats = client.connection_stats
|
430
|
+
stats.should have(3).items
|
431
|
+
stats.should include(host: 'node1.example.com', port: 5432, fake_stats: true)
|
432
|
+
end
|
433
|
+
end
|
434
|
+
|
422
435
|
context 'when disconnected' do
|
423
436
|
it 'logs that the connection closed' do
|
424
437
|
client.start.value
|
@@ -605,6 +618,14 @@ module ClientSpec
|
|
605
618
|
@requests = []
|
606
619
|
end
|
607
620
|
|
621
|
+
def stats
|
622
|
+
{
|
623
|
+
:host => @peer_connection.host,
|
624
|
+
:port => @peer_connection.port,
|
625
|
+
:fake_stats => true,
|
626
|
+
}
|
627
|
+
end
|
628
|
+
|
608
629
|
def closed?
|
609
630
|
!!@closed
|
610
631
|
end
|
data/spec/ione/rpc/codec_spec.rb
CHANGED
@@ -34,6 +34,37 @@ module Ione
|
|
34
34
|
it 'encodes the object as JSON' do
|
35
35
|
encoded_message[8..-1].should == '{"foo":"bar","baz":42}'
|
36
36
|
end
|
37
|
+
|
38
|
+
context 'with a compressor' do
|
39
|
+
let :codec do
|
40
|
+
CodecSpec::JsonCodec.new(compressor: compressor)
|
41
|
+
end
|
42
|
+
|
43
|
+
let :compressor do
|
44
|
+
double(:compressor)
|
45
|
+
end
|
46
|
+
|
47
|
+
before do
|
48
|
+
compressor.stub(:compress?).and_return(true)
|
49
|
+
compressor.stub(:compress).and_return('FAKECOMPRESSEDFREAME')
|
50
|
+
end
|
51
|
+
|
52
|
+
it 'sets the compression flag' do
|
53
|
+
encoded_message = codec.encode(object, 42)
|
54
|
+
encoded_message[1].unpack('c').should == [1]
|
55
|
+
end
|
56
|
+
|
57
|
+
it 'compresses the frame body' do
|
58
|
+
encoded_message = codec.encode(object, 42)
|
59
|
+
encoded_message[4, 4].unpack('N').should == [20]
|
60
|
+
encoded_message[8..-1].should == 'FAKECOMPRESSEDFREAME'
|
61
|
+
end
|
62
|
+
|
63
|
+
it 'does not compress the frame body when the compressor advices against it' do
|
64
|
+
compressor.stub(:compress?).and_return(false)
|
65
|
+
encoded_message[8..-1].should == '{"foo":"bar","baz":42}'
|
66
|
+
end
|
67
|
+
end
|
37
68
|
end
|
38
69
|
|
39
70
|
describe '#decode' do
|
@@ -88,6 +119,50 @@ module Ione
|
|
88
119
|
message.should == object
|
89
120
|
channel.should == 42
|
90
121
|
end
|
122
|
+
|
123
|
+
context 'when the frame is compressed' do
|
124
|
+
let :codec do
|
125
|
+
CodecSpec::JsonCodec.new(compressor: compressor)
|
126
|
+
end
|
127
|
+
|
128
|
+
let :compressor do
|
129
|
+
double(:compressor)
|
130
|
+
end
|
131
|
+
|
132
|
+
let :encoded_message do
|
133
|
+
%(\x02\x01\x00\x2a\x00\x00\x00\x13FAKECOMPRESSEDFRAME)
|
134
|
+
end
|
135
|
+
|
136
|
+
before do
|
137
|
+
compressor.stub(:decompress).with('FAKECOMPRESSEDFRAME').and_return('{"foo":"bar","baz":42}')
|
138
|
+
end
|
139
|
+
|
140
|
+
it 'decompresses the frame before decoding it' do
|
141
|
+
message, channel, complete = codec.decode(Ione::ByteBuffer.new(encoded_message), nil)
|
142
|
+
complete.should be_true
|
143
|
+
message.should == object
|
144
|
+
channel.should == 42
|
145
|
+
end
|
146
|
+
|
147
|
+
it 'raises an error when the frame is compressed and the codec has not been configured with a compressor' do
|
148
|
+
codec = CodecSpec::JsonCodec.new
|
149
|
+
buffer = Ione::ByteBuffer.new(encoded_message)
|
150
|
+
expect { codec.decode(buffer, nil) }.to raise_error(CodecError, 'Compressed frame received but no compressor available')
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
context 'when the codec is lazy' do
|
155
|
+
let :codec do
|
156
|
+
CodecSpec::JsonCodec.new(lazy: true)
|
157
|
+
end
|
158
|
+
|
159
|
+
it 'returns an object with a #decode method instead of the message' do
|
160
|
+
message, channel, complete = codec.decode(Ione::ByteBuffer.new(encoded_message), nil)
|
161
|
+
complete.should be_true
|
162
|
+
channel.should == 42
|
163
|
+
message.decode.should == object
|
164
|
+
end
|
165
|
+
end
|
91
166
|
end
|
92
167
|
|
93
168
|
describe '#recode' do
|
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.pre3
|
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-21 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: ione
|