ione-rpc 1.0.0.pre2 → 1.0.0.pre3
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/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
|