arpie 0.0.4 → 0.0.5

Sign up to get free protection for your applications and to get access to all the features.
@@ -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: