arpie 0.0.2 → 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
data/README CHANGED
@@ -1,11 +1,13 @@
1
1
  = What's this?
2
2
 
3
- Arpie is a end-to-end framework for sending synchronous message-and-answer
4
- pairs, and serves as the basis for a RPC framework, handling a variety of protocols,
5
- including a generic Marshal proof of concept; writing your own Protocol is quite simple.
3
+ Arpie is a end-to-end framework for sending protocol-encoded messages over arbitary
4
+ IO channels, including UNIX/TCP Sockets, Pipes, and pidgeon carriers (depending on
5
+ your implementation details).
6
6
 
7
- * It uses ruby threads on the server side, one per connection.
8
- * The client is single-threaded.
7
+ Arpie also provides a robust replay-protected RPC framework.
8
+
9
+ The Arpie server uses one ruby-thread per client, the client runs entirely in the
10
+ calling thread; though an example implementation for evented callbacks is provided.
9
11
 
10
12
  == Source Code
11
13
 
@@ -36,7 +38,7 @@ to get the newest version.
36
38
  end
37
39
 
38
40
  c = Arpie::Client.new(Arpie::MarshalProtocol.new)
39
- c.connect do |transport|
41
+ c.connect do
40
42
  TCPSocket.new("127.0.0.1", 51210)
41
43
  end
42
44
 
@@ -85,14 +87,17 @@ See the Client accessors for modifying this behaviour.
85
87
  It is assumed that each call, that is being placed, is atomic - eg, no
86
88
  connection losses in between message send and receive; lost messages
87
89
  will be retransmitted. Some Protocol classes provide support for replay
88
- protection through in-band serials; though it is not a requirement to implement it.
89
- If a serial is provided in the data stream, the Protocol will not call
90
+ protection through in-band UUIDs; though it is not a requirement to implement it.
91
+ If a UUID is provided in the data stream, the Protocol will not call
90
92
  the handler again for retransmissions, but instead reply with the old,
91
93
  already evaluated value.
92
94
 
93
- Not all protocols support serials; those who do not offer no replay protection,
95
+ Not all protocols support UUIDs; those who do not offer no replay protection,
94
96
  and special care has to be taken elsewhere.
95
97
 
98
+ All object-encoding protocols support UUIDs, including YAML and Marshal.
99
+ XMLRPC does not.
100
+
96
101
  == Benchmarks
97
102
 
98
103
  There is a benchmark script included in the git repository (and in the gem
@@ -101,11 +106,13 @@ under tools/). A sample output follows; your milage may vary.
101
106
  user system total real
102
107
 
103
108
  native DRb
104
- 1 0.000000 0.000000 0.000000 ( 0.000167)
105
- 1000 0.120000 0.010000 0.130000 ( 0.121834)
109
+ 1 0.000000 0.000000 0.000000 ( 0.000172)
110
+ 1000 0.110000 0.010000 0.120000 ( 0.119767)
106
111
 
107
- ruby xmlrpc/server - too slow to benchmark
112
+ Arpie: proxied MarshalProtocol with replay protection through uuidtools
113
+ 1 0.000000 0.000000 0.010000 ( 0.075373)
114
+ 1000 0.530000 0.090000 0.600000 ( 0.608665)
108
115
 
109
- Arpie: proxied MarshalProtocol
110
- 1 0.000000 0.000000 0.000000 ( 0.000617)
111
- 1000 0.100000 0.020000 0.120000 ( 0.114573)
116
+ Arpie: proxied MarshalProtocol without replay protection
117
+ 1 0.000000 0.000000 0.000000 ( 0.000173)
118
+ 1000 0.170000 0.020000 0.190000 ( 0.194649)
data/Rakefile CHANGED
@@ -9,7 +9,7 @@ include FileUtils
9
9
  # Configuration
10
10
  ##############################################################################
11
11
  NAME = "arpie"
12
- VERS = "0.0.2"
12
+ VERS = "0.0.3"
13
13
  CLEAN.include ["**/.*.sw?", "pkg", ".config", "rdoc", "coverage"]
14
14
  RDOC_OPTS = ["--quiet", "--line-numbers", "--inline-source", '--title', \
15
15
  "#{NAME}: A high-performing layered networking protocol framework. Simple to use, simple to extend.", \
@@ -44,6 +44,7 @@ spec = Gem::Specification.new do |s|
44
44
  s.files = %w(COPYING README Rakefile) + Dir.glob("{bin,doc,spec,lib,tools,scripts,data}/**/*")
45
45
  s.require_path = "lib"
46
46
  s.bindir = "bin"
47
+ s.add_dependency('uuidtools', '>= 1.0.7')
47
48
  end
48
49
 
49
50
  Rake::GemPackageTask.new(spec) do |p|
data/lib/arpie/client.rb CHANGED
@@ -106,6 +106,7 @@ module Arpie
106
106
  def _connect
107
107
  @read_io and return
108
108
  @read_io, @write_io = @connector.call(self)
109
+ @protocol.reset
109
110
  @write_io ||= @read_io
110
111
  end
111
112
  end
@@ -182,7 +182,7 @@ module Arpie
182
182
  class RPCProtocol < Protocol
183
183
 
184
184
  # A RPC call.
185
- class Call < Struct.new(:ns, :meth, :argv); end
185
+ class Call < Struct.new(:ns, :meth, :argv, :uuid); end
186
186
  end
187
187
 
188
188
  # A XMLRPC Protocol based on rubys xmlrpc stdlib.
data/lib/arpie/proxy.rb CHANGED
@@ -1,3 +1,5 @@
1
+ require 'uuidtools'
2
+
1
3
  module Arpie
2
4
 
3
5
  # A Endpoint which supports arbitary objects as handlers,
@@ -6,30 +8,61 @@ module Arpie
6
8
  # Note that this will only export public instance method
7
9
  # of the class as they are defined.
8
10
  class ProxyServer < Server
11
+
12
+ # An array containing symbols of method names that the
13
+ # handler should be allowed to call. Defaults to
14
+ # all public instance methods the class defines (wysiwyg).
15
+ # Set this to nil to allow calling of ALL methods, but be
16
+ # warned of the security implications (instance_eval, ..).
9
17
  attr_accessor :interface
10
18
 
11
- # Set a class handler. All instance methods will be
12
- # callable over RPC (with a Proxy object).
13
- # Consider yourself warned of the security implications:
14
- # proxy.instance_eval ..
15
- # Optional interface parameter is an array of method
16
- # names (as symbols). If given, only those will be
17
- # accessible for Transports.
18
- def handle handler, interface = nil
19
+ # Set this to false to disable replay protection.
20
+ attr_accessor :uuid_tracking
21
+
22
+ # The maximum number of method call results to remember.
23
+ # Defaults to 100, which should be enough for everyone. ;)
24
+ attr_accessor :max_uuids
25
+
26
+ def initialize *va
27
+ super
28
+ @uuids = {}
29
+ @max_uuids = 100
30
+ @uuid_tracking = true
31
+ end
32
+
33
+ # Set a class handler. All public instance methods will be
34
+ # callable over RPC (with a Proxy object) (see attribute interface).
35
+ def handle handler
19
36
  @handler = handler
20
- @interface = interface
37
+ @interface = handler.class.public_instance_methods(false).map {|x|
38
+ x.to_sym
39
+ }
21
40
  self
22
41
  end
23
42
 
24
43
  private
25
44
 
26
45
  def _handle endpoint, message
27
- if !@handler.respond_to?(message.meth) || (@interface && !@interface.index(message.meth))
46
+ if !@handler.respond_to?(message.meth.to_sym) ||
47
+ (@interface && !@interface.index(message.meth.to_sym))
28
48
  raise NoMethodError, "No such method: #{message.meth.inspect}"
29
49
  end
30
50
 
31
- ret = @handler.send(message.meth, *message.argv)
32
- endpoint.write_message(ret)
51
+ # Prune old serials. This can probably be optimized, but works well enough for now.
52
+ if @uuid_tracking && message.uuid
53
+ timestamps = @uuids.values.map {|v| v[0] }.sort
54
+ latest_timestamp = timestamps[-@max_uuids]
55
+ @uuids.reject! {|uuid, (time, value)|
56
+ time < latest_timestamp
57
+ } if latest_timestamp
58
+
59
+ endpoint.write_message((@uuids[message.uuid] ||=
60
+ [Time.now, @handler.send(message.meth, *message.argv)])[1])
61
+
62
+ else
63
+ endpoint.write_message @handler.send(message.meth, *message.argv)
64
+ end
65
+
33
66
  end
34
67
  end
35
68
 
@@ -39,11 +72,30 @@ module Arpie
39
72
  class ProxyClient < RPCClient
40
73
 
41
74
  def initialize protocol, namespace = ""
75
+ super(protocol)
42
76
  @protocol, @namespace = protocol, namespace
77
+ @uuid_generator = lambda {|client, method, argv|
78
+ UUID.timestamp_create.to_i.to_s
79
+ }
80
+ end
81
+
82
+ # Set up a new UUID generator for this proxy client.
83
+ # Make sure that this yields really random numbers.
84
+ # The default uses the uuidtools gem and is usually okay.
85
+ # You can simply set this to nil (by calling it without
86
+ # a block).
87
+ #
88
+ # Note that disabling this disables replay protection.
89
+ def uuid_generator &handler #:yields: client, method, argv
90
+ @uuid_generator = handler
91
+ self
43
92
  end
44
93
 
45
94
  def method_missing meth, *argv # :nodoc:
46
- call = RPCProtocol::Call.new(@namespace, meth, argv)
95
+ uuid = @uuid_generator ?
96
+ @uuid_generator.call(self, meth, argv) : nil
97
+
98
+ call = RPCProtocol::Call.new(@namespace, meth, argv, uuid)
47
99
  ret = self.request(call)
48
100
  case ret
49
101
  when Exception
data/lib/arpie/server.rb CHANGED
@@ -7,18 +7,19 @@ module Arpie
7
7
  class Endpoint
8
8
  attr_reader :io
9
9
 
10
- attr_reader :server
10
+ attr_reader :protocol
11
11
 
12
- def initialize server, io
13
- @io, @server = io, server
12
+ def initialize protocol, io
13
+ @protocol, @io = protocol, io
14
+ @protocol.reset
14
15
  end
15
16
 
16
17
  def read_message
17
- @server.protocol.read_message(@io)
18
+ @protocol.read_message(@io)
18
19
  end
19
20
 
20
21
  def write_message message
21
- @server.protocol.write_message(@io, message)
22
+ @protocol.write_message(@io, message)
22
23
  end
23
24
  alias_method :<<, :write_message
24
25
 
@@ -119,7 +120,7 @@ module Arpie
119
120
  def _acceptor_thread
120
121
  loop do
121
122
  client = @acceptor.call(self)
122
- c = @protocol.endpoint_klass.new(self, client)
123
+ c = @protocol.endpoint_klass.new(@protocol.clone, client)
123
124
  Thread.new { _read_thread(c) }
124
125
  end
125
126
  end
data/tools/benchmark.rb CHANGED
@@ -39,27 +39,13 @@ Benchmark.bm {|b|
39
39
  b.report("1000") { 1000.times { drbobject.reverse "benchmark" } }
40
40
 
41
41
  puts ""
42
- puts "ruby xmlrpc/server - too slow to benchmark"
43
- #server = XMLRPC::Server.new(51211, "127.0.0.1", 4, nil, false)
44
- #server.add_handler(XMLRPC::iPIMethods("wrap"), Wrap.new)
45
- #server_thread = Thread.new { server.serve }
46
- #client = XMLRPC::Client.new( "127.0.0.1", "/", 51211)
47
- #b.report(" 1") { 1.times { client.call("wrap.reverse", "benchmark") } }
48
- #b.report("1000") { 1000.times { client.call("wrap.reverse", "benchmark") } }
49
- #server.shutdown
50
- #server_thread.join
51
-
52
- puts ""
53
- puts "Arpie: proxied MarshalProtocol"
42
+ puts "Arpie: proxied MarshalProtocol with replay protection through uuidtools"
54
43
  b.report(" 1") { 1.times { $proxy.reverse "benchmark" } }
55
44
  b.report("1000") { 1000.times { $proxy.reverse "benchmark" } }
56
45
 
57
-
58
- def evented_call
59
- $transport.request(ProxyCall.new("reverse",["benchmark"])) do end
60
- end
61
46
  puts ""
62
- # puts "Arpie: evented messaging"
63
- # b.report(" 1") { 1.times { evented_call } }
64
- # b.report("1000") { 1000.times { evented_call } }
47
+ puts "Arpie: proxied MarshalProtocol without replay protection"
48
+ $proxy.uuid_generator
49
+ b.report(" 1") { 1.times { $proxy.reverse "benchmark" } }
50
+ b.report("1000") { 1000.times { $proxy.reverse "benchmark" } }
65
51
  }
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.2
4
+ version: 0.0.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Bernhard Stoeckner
@@ -9,10 +9,19 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2009-02-10 00:00:00 +01:00
12
+ date: 2009-02-11 00:00:00 +01:00
13
13
  default_executable:
14
- dependencies: []
15
-
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: uuidtools
17
+ type: :runtime
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: 1.0.7
24
+ version:
16
25
  description: A high-performing layered networking protocol framework. Simple to use, simple to extend.
17
26
  email: elven@swordcoast.net
18
27
  executables: []