arpie 0.0.4 → 0.0.5

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.
@@ -0,0 +1,39 @@
1
+ module Arpie
2
+
3
+ # Raised by arpie when a Protocol thinks the stream got corrupted
4
+ # (by calling bogon!).
5
+ # This usually results in a dropped connection.
6
+ class StreamError < IOError ; end
7
+ # Raised by arpie when a Protocol needs more data to parse a packet.
8
+ # Usually only of relevance to the programmer when using Protocol#from directly.
9
+ class EIncomplete < RuntimeError ; end
10
+
11
+ # :stopdoc:
12
+ # Used internally by arpie.
13
+ class ETryAgain < RuntimeError ; end
14
+ class YieldResult < RuntimeError
15
+ attr_reader :result
16
+ def initialize result
17
+ @result = result
18
+ end
19
+ end
20
+ # :startdoc:
21
+
22
+ # Call this if you think the stream has been corrupted, or
23
+ # non-protocol data arrived.
24
+ # +message+ is the text to display.
25
+ # +data+ is the optional misbehaving data for printing.
26
+ # This breaks out of the caller.
27
+ def bogon! data = nil, message = nil
28
+ raise StreamError, "#{self.is_a?(Class) ? self.to_s : self.class.to_s}:" +
29
+ " BOGON#{data.nil? ? "" : " " + data.inspect}" +
30
+ "#{message.nil? ? "" : " -- #{message}" }"
31
+ end
32
+
33
+ # Tell the caller that the given chunk of data
34
+ # is not enough to construct a whole message.
35
+ # This breaks out of the caller.
36
+ def incomplete!
37
+ raise EIncomplete, "#{self.is_a?(Class) ? self : self.class} needs more data"
38
+ end
39
+ end
@@ -1,28 +1,9 @@
1
1
  require 'shellwords'
2
2
  require 'yaml'
3
+ require 'zlib'
3
4
 
4
5
  module Arpie
5
6
  MTU = 1024
6
- # Raised by arpie when a Protocol thinks the stream got corrupted
7
- # (by calling stream_error!).
8
- # This usually results in a dropped connection.
9
- class StreamError < IOError ; end
10
- # Raised by arpie when a Protocol needs more data to parse a packet.
11
- # Usually only of relevance to the programmer when using Protocol#from directly.
12
- class EIncomplete < RuntimeError ; end
13
-
14
- # :stopdoc:
15
- # Used internally by arpie.
16
- class ESwallow < RuntimeError ; end
17
- class ETryAgain < RuntimeError ; end
18
- class YieldResult < RuntimeError
19
- attr_reader :result
20
- def initialize result
21
- @result = result
22
- end
23
- end
24
- # :startdoc:
25
-
26
7
  # A RPC call. You need to wrap all calls sent over RPC protocols in this.
27
8
  class RPCall < Struct.new(:ns, :meth, :argv, :uuid); end
28
9
 
@@ -30,6 +11,7 @@ module Arpie
30
11
  # list, into which io data can be fed and parsed packets received; and
31
12
  # vice versa.
32
13
  class ProtocolChain
14
+ include Arpie
33
15
 
34
16
  # Array of Protocols.
35
17
  attr_reader :chain
@@ -67,9 +49,18 @@ module Arpie
67
49
  # Convert the given +message+ to wire format by
68
50
  # passing it through all protocols in the chain.
69
51
  def to message
70
- ret = @chain.inject(message) {|msg, p|
71
- p.to(msg)
52
+ messages = [message]
53
+ @chain.each {|p|
54
+ for_next = []
55
+ messages.each {|msg|
56
+ p.to(msg) do |a|
57
+ for_next << a
58
+ end
59
+ }
60
+
61
+ messages = for_next
72
62
  }
63
+ messages
73
64
  end
74
65
 
75
66
  # Convert the given +binary+ to message format
@@ -106,56 +97,54 @@ module Arpie
106
97
  cut_to_index = nil
107
98
  messages_for_next = []
108
99
 
109
- messages.each do |message|
100
+ messages.each_with_index do |message, m_index|
110
101
  cut_to_index = p.from(message) do |object|
111
102
  messages_for_next << object
112
103
  end rescue case $!
113
-
114
104
  when YieldResult
115
105
  messages_for_next.concat($!.result)
116
106
  next
117
107
 
118
- when ESwallow
119
- messages.delete(message)
120
- messages_for_next = messages
121
- p_index -= 1
122
- break
123
-
124
108
  when EIncomplete
125
- # All protocols above the io one need to wait for each
126
- # one above to yield more messages.
127
- if p_index > 0
128
- # Unwind to the parent protocol and let it read in some
129
- # more messages ..
130
- messages_for_next = messages
131
- messages_for_next.shift
132
- p_index -= 1
133
- break
134
-
135
- # The first protocol manages +io+.
109
+ if messages.size - 1 - m_index > 0
110
+ next
136
111
  else
137
- select([io])
138
- @buffer << io.readpartial(MTU) rescue raise $!.class,
139
- "#{$!.to_s}; unparseable bytes remaining in buffer: #{@buffer.size}"
140
- retry
112
+ raise
141
113
  end
142
114
 
143
- when ETryAgain
115
+ else
116
+ raise
117
+ end
118
+ end rescue case $!
119
+ when EIncomplete
120
+ if p_index == 0
121
+ select([io])
122
+ @buffer << io.readpartial(MTU) rescue raise $!.class,
123
+ "#{$!.to_s}; unparseable bytes remaining in buffer: #{@buffer.size}"
144
124
  retry
145
125
 
146
126
  else
147
- raise
148
- end # rescue case
149
- end # messages.each
127
+ p_index = 0
128
+ messages_for_next = []
129
+ messages = [@buffer]
130
+ next # of loop protocol chain
131
+ end
150
132
 
151
- raise "BUG: #{p.class.to_s}#from did not yield a message." if
152
- messages_for_next.size == 0
133
+ else
134
+ raise
135
+ end
136
+
137
+ if messages_for_next.size == 0
138
+ # Get back to IO level and retry reading more crud
139
+ messages_for_next = [@buffer]
140
+ p_index = -1
141
+ end
153
142
 
154
143
  messages = messages_for_next
155
144
 
156
145
  if p_index == 0
157
146
  if cut_to_index.nil? || cut_to_index < 0
158
- raise "Protocol '#{p.class.to_s}'implementation faulty: " +
147
+ raise "Protocol '#{p.class.to_s}' implementation faulty: " +
159
148
  "from did return an invalid cut index: #{cut_to_index.inspect}."
160
149
  else
161
150
  @buffer[0, cut_to_index] = ""
@@ -163,16 +152,18 @@ module Arpie
163
152
  end
164
153
 
165
154
  p_index += 1
166
- end # chain loop
155
+ end # loop chain
167
156
 
168
157
  message = messages.shift
169
158
  @messages = messages
170
159
  message
171
160
  end
172
161
 
162
+
173
163
  # Write +message+ to +io+.
174
- def write_message io, message
175
- io.write(to message)
164
+ def write_message io, *messages
165
+ binary = messages.map {|m| to(m)}
166
+ io.write(binary)
176
167
  end
177
168
 
178
169
  def reset
@@ -183,17 +174,25 @@ module Arpie
183
174
  # A Protocol converts messages (which are arbitary objects)
184
175
  # to a suitable on-the-wire format, and back.
185
176
  class Protocol
177
+ include Arpie
186
178
 
187
179
  # Set this to true in child classes which implement
188
180
  # message separation within a stream.
189
181
  CAN_SEPARATE_MESSAGES = false
190
182
 
183
+ # :stopdoc:
184
+ # The stowbuffer hash used by assemble! No need to touch this, usually.
185
+ attr_reader :stowbuffer
186
+ # The meta-information hash used by assemble! No need to touch this, usually.
187
+ attr_reader :metabuffer
188
+ # :startdoc:
189
+
191
190
  # Convert obj to on-the-wire format.
192
191
  def to obj
193
- obj
192
+ yield obj
194
193
  end
195
194
 
196
- # Extract message(s) from +binary+.i
195
+ # Extract message(s) from +binary+.
197
196
  #
198
197
  # Yields each message found, with all protocol-specifics stripped.
199
198
  #
@@ -219,74 +218,55 @@ module Arpie
219
218
  raise ETryAgain
220
219
  end
221
220
 
222
- # Tell the protocol chain that the given chunk of data
223
- # is not enough to construct a whole message.
224
- # This breaks out of Protocol#from.
225
- def incomplete!
226
- raise EIncomplete
227
- end
228
-
229
- # Swallow the complete message currently passed to Protocol#from.
230
- # Not advised for the outermost protocol, which works on io buffer streams
231
- # and may swallow more than intended.
232
- def gulp!
233
- raise ESwallow
234
- end
235
- alias_method :swallow!, :gulp!
236
-
237
221
  # Stow away a message in this protocols buffer for later reassembly.
238
222
  # Optional argument: a token if you are planning to reassemble multiple
239
223
  # interleaved/fragmented message streams.
240
224
  #
225
+ # If you pass a block, that block will be called; if no block is given,
226
+ # Protocol#assemble will be invoked.
227
+ #
228
+ # Any leftover binaries passed into assemble! that get not returned in
229
+ # the assembler will be discarded; they are assumed to be syntax or framework.
230
+ #
241
231
  # +binary+ is the binary packet you want to add to the assembly
242
- # +token+ is a object which can be used to re-identify multiple concurrent assemblies
232
+ # +token+ is a object which can be used to identify multiple concurrent assemblies (think Hash.keys)
243
233
  # +meta+ is a hash containing meta-information for this assembly
244
234
  # each call to assemble! will merge these hashes, and pass them
245
235
  # on to Protocol#assemble
246
- def assemble! binary, token = :default, meta = {}
236
+ def assemble! binary, token = :default, meta = {} # :yields: binaries, metadata
247
237
  @stowbuffer ||= {}
248
238
  @stowbuffer[token] ||= []
249
239
  @stowbuffer[token] << binary
250
240
 
251
241
  @metabuffer ||= {}
252
242
  @metabuffer[token] ||= {}
253
- @metabuffer[token].merge(meta)
254
-
255
- assembled = []
256
-
257
- assemble @stowbuffer[token], token, meta do |a|
258
- assembled << a
259
- end # rescue case $!
260
- #when EIncomplet
261
- # puts "Cant reassemble, asking for moarh"
262
- # raise EIncomplete
263
- #else
264
- # raise
265
- #end
266
-
267
- assembled.size > 0 or raise "assemble! did not return any results."
268
- raise YieldResult, assembled
243
+ @metabuffer[token].merge!(meta)
244
+
245
+ assembled = nil
246
+
247
+ if block_given?
248
+ assembled = yield(@stowbuffer[token], @metabuffer[token])
249
+
250
+ else
251
+ # This raises EIncomplete when not enough messages are there,
252
+ # and passes it straight on to #read_message
253
+ assembled = assemble(@stowbuffer[token], @metabuffer[token])
254
+ end
255
+
256
+ @stowbuffer.delete(token)
257
+ @metabuffer.delete(token)
258
+ raise YieldResult, [assembled]
269
259
  end
270
260
 
271
261
  # Called when we're trying to reassemble a stream of packets.
272
262
  #
273
263
  # Call incomplete! when not enough data is here to reassemble this stream,
274
264
  # and yield all results of the reassembled stream.
275
- def assemble binaries, token
265
+ def assemble binaries, meta
276
266
  raise NotImplementedError, "Tried to assemble! something, but no assembler defined."
277
267
  end
278
-
279
- # Call this if you think the stream has been corrupted, or
280
- # non-protocol data arrived.
281
- # +message+ is the text to display.
282
- # +data+ is the optional misbehaving data for printing.
283
- # This breaks out of Protocol#from.
284
- def bogon! data = nil, message = nil
285
- raise StreamError, "#{self.class.to_s}#{message.nil? ? " thinks the data is bogus" : ": " + message }#{data.nil? ? "" : ": " + data.inspect}."
286
- end
287
268
  end
288
269
 
289
-
290
270
  # A sample binary protocol, which simply prefixes each message with the
291
271
  # size of the data to be expected.
292
272
  class SizedProtocol < Protocol
@@ -300,7 +280,7 @@ module Arpie
300
280
  end
301
281
 
302
282
  def to object
303
- [object.size, object].pack('Qa*')
283
+ yield [object.size, object].pack('Qa*')
304
284
  end
305
285
  end
306
286
 
@@ -310,7 +290,7 @@ module Arpie
310
290
  # Messages are arbitary objects.
311
291
  class MarshalProtocol < Protocol
312
292
  def to object
313
- Marshal.dump(object)
293
+ yield Marshal.dump(object)
314
294
  end
315
295
 
316
296
  def from binary
@@ -336,7 +316,7 @@ module Arpie
336
316
  end
337
317
 
338
318
  def to object
339
- object.to_s + @separator
319
+ yield object.to_s + @separator
340
320
  end
341
321
  end
342
322
 
@@ -347,7 +327,7 @@ module Arpie
347
327
  def to object
348
328
  raise ArgumentError, "#{self.class.to_s} can only encode arrays." unless
349
329
  object.is_a?(Array)
350
- Shellwords.join(object.map {|x| x.to_s })
330
+ yield Shellwords.join(object.map {|x| x.to_s })
351
331
  end
352
332
 
353
333
  def from binary
@@ -361,7 +341,7 @@ module Arpie
361
341
  CAN_SEPARATE_MESSAGES = true
362
342
 
363
343
  def to object
364
- YAML.dump(object) + "...\n"
344
+ yield YAML.dump(object) + "...\n"
365
345
  end
366
346
 
367
347
  def from binary
@@ -370,4 +350,21 @@ module Arpie
370
350
  4 + index
371
351
  end
372
352
  end
353
+
354
+ # A transparent zlib stream de/compression protocol.
355
+ class ZlibProtocol < Protocol
356
+ def initialize
357
+ @inflater = Zlib::Inflate.new
358
+ @deflater = Zlib::Deflate.new
359
+ end
360
+
361
+ def to object
362
+ yield @deflater.deflate(object) + @deflater.flush
363
+ end
364
+
365
+ def from binary
366
+ yield @inflater.inflate(binary)
367
+ binary.size
368
+ end
369
+ end
373
370
  end
data/lib/arpie/xmlrpc.rb CHANGED
@@ -29,7 +29,7 @@ module Arpie
29
29
  raise ArgumentError, "Can only encode Arpie::RPCall" unless
30
30
  object.is_a?(Arpie::RPCall)
31
31
 
32
- @writer.methodCall((object.ns.nil? ? '' : object.ns + '.') + object.meth, *object.argv)
32
+ yield @writer.methodCall((object.ns.nil? ? '' : object.ns + '.') + object.meth, *object.argv)
33
33
  end
34
34
 
35
35
  def from binary
@@ -43,7 +43,7 @@ module Arpie
43
43
 
44
44
  def to object
45
45
  setup
46
- case object
46
+ yield case object
47
47
  when Exception
48
48
  # TODO: wrap XMLFault
49
49
  raise NotImplementedError, "Cannot encode exceptions"
@@ -93,7 +93,7 @@ module Arpie
93
93
  public_class_method :new
94
94
 
95
95
  def to object
96
- "GET / HTTP/1.[01]\r\nContent-Length: #{object.size}\r\n\r\n" + object
96
+ yield "GET / HTTP/1.[01]\r\nContent-Length: #{object.size}\r\n\r\n" + object
97
97
  end
98
98
 
99
99
  end
@@ -102,7 +102,7 @@ module Arpie
102
102
  public_class_method :new
103
103
 
104
104
  def to object
105
- "HTTP/1.0 200 OK\r\nContent-Length: #{object.size}\r\n\r\n" + object
105
+ yield "HTTP/1.0 200 OK\r\nContent-Length: #{object.size}\r\n\r\n" + object
106
106
  end
107
107
  end
108
108
  end
data/lib/arpie.rb CHANGED
@@ -1,4 +1,6 @@
1
+ require 'arpie/error'
1
2
  require 'arpie/protocol'
3
+ require 'arpie/binary'
2
4
  require 'arpie/xmlrpc'
3
5
  require 'arpie/server'
4
6
  require 'arpie/client'
@@ -3,25 +3,35 @@ require File.join(File.dirname(__FILE__), 'spec_helper')
3
3
  class Splitter < Arpie::Protocol
4
4
  def from binary
5
5
  yield binary.size
6
+
6
7
  binary.each_char do |c|
7
8
  yield c
8
9
  end
9
10
  end
10
11
  end
11
12
 
12
- class Merger < Arpie::Protocol
13
+ class BufferedSplitter < Arpie::Protocol
13
14
  def from binary
14
- unless @expect
15
- @expect = binary or incomplete!
16
- gulp!
15
+ unless defined? @buf
16
+ yield binary.size * 2
17
+ @buf = true
17
18
  end
18
19
 
19
- assemble! binary, :d, :size => @expect
20
+ binary.each_char do |c|
21
+ yield c
22
+ end
20
23
  end
24
+ end
21
25
 
22
- def assemble binaries, token, meta
23
- binaries.size >= meta[:size] or incomplete!
24
- yield binaries.join('')
26
+
27
+ class Merger < Arpie::Protocol
28
+ def from binary
29
+ assemble! binary do |binaries, meta|
30
+ binaries.size >= 1 or incomplete!
31
+ binaries.size - 1 >= binaries[0].to_i or incomplete!
32
+ binaries.shift
33
+ binaries.join('')
34
+ end
25
35
  end
26
36
  end
27
37
 
@@ -45,3 +55,13 @@ describe "Merger::Splitter::Sized" do subject { [Merger, Splitter, Arpie::SizedP
45
55
  chain_read.should == t
46
56
  end
47
57
  end
58
+
59
+ describe "Merger::BufferedSplitter::Sized" do subject { [Merger, BufferedSplitter, Arpie::SizedProtocol] }
60
+ it_should_behave_like "ProtocolChainSetup"
61
+
62
+ it "should re-read io for more data if assembly fails" do
63
+ @chain.write_message(@w, "split")
64
+ Thread.new { sleep 0.1; @chain.write_message(@w, "split") }
65
+ chain_read.should == "splitsplit"
66
+ end
67
+ end
@@ -112,6 +112,10 @@ describe "Sized::Marshal::Sized" do subject { [Arpie::SizedProtocol, Arpie::Mars
112
112
  it_should_behave_like "ProtocolChain"
113
113
  end
114
114
 
115
+ describe "Zlib::Marshal::Sized" do subject { [Arpie::ZlibProtocol, Arpie::MarshalProtocol, Arpie::SizedProtocol] }
116
+ it_should_behave_like "ProtocolChain"
117
+ end
118
+
115
119
  # Shellwords is a bit of a special case, because it only encodes arrays.
116
120
  describe "Shellwords::Separator" do subject { [Arpie::ShellwordsProtocol, Arpie::SeparatorProtocol] }
117
121
  it_should_behave_like "ProtocolChain"
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: arpie
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.4
4
+ version: 0.0.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Bernhard Stoeckner
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2009-02-16 00:00:00 +01:00
12
+ date: 2009-02-24 00:00:00 +01:00
13
13
  default_executable:
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
@@ -31,25 +31,29 @@ extensions: []
31
31
  extra_rdoc_files:
32
32
  - README
33
33
  - COPYING
34
+ - BINARY_SPEC
34
35
  files:
35
36
  - COPYING
36
37
  - README
37
38
  - Rakefile
38
- - spec/rcov.opts
39
- - spec/protocol_merge_and_split_spec.rb
40
- - spec/spec_helper.rb
41
39
  - spec/protocol_spec.rb
42
40
  - spec/spec.opts
41
+ - spec/spec_helper.rb
42
+ - spec/protocol_merge_and_split_spec.rb
43
+ - spec/rcov.opts
43
44
  - spec/client_server_spec.rb
45
+ - lib/arpie.rb
44
46
  - lib/arpie
45
- - lib/arpie/proxy.rb
46
- - lib/arpie/xmlrpc.rb
47
47
  - lib/arpie/client.rb
48
+ - lib/arpie/error.rb
48
49
  - lib/arpie/protocol.rb
50
+ - lib/arpie/binary.rb
51
+ - lib/arpie/proxy.rb
52
+ - lib/arpie/xmlrpc.rb
49
53
  - lib/arpie/server.rb
50
- - lib/arpie.rb
51
54
  - tools/benchmark.rb
52
55
  - tools/protocol_benchmark.rb
56
+ - BINARY_SPEC
53
57
  has_rdoc: true
54
58
  homepage: http://arpie.elv.es
55
59
  post_install_message: