arpie 0.0.6 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/COPYING DELETED
@@ -1,15 +0,0 @@
1
- Copyright (C) 2008 Bernhard Stoeckner <elven@swordcoast.net> and contributors
2
-
3
- This program is free software; you can redistribute it and/or modify
4
- it under the terms of the GNU General Public License as published by
5
- the Free Software Foundation; either version 2 of the License, or
6
- (at your option) any later version.
7
-
8
- This program is distributed in the hope that it will be useful,
9
- but WITHOUT ANY WARRANTY; without even the implied warranty of
10
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
- GNU General Public License for more details.
12
-
13
- You should have received a copy of the GNU General Public License along
14
- with this program; if not, write to the Free Software Foundation, Inc.,
15
- 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
data/README DELETED
@@ -1,167 +0,0 @@
1
- = What's this?
2
-
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
-
7
- Arpie also provides a robust replay-protected RPC framework.
8
-
9
- The useful core of arpie is a protocol stack that can be used to read/split/assemble/write
10
- any data stream, but is tailored for packeted streaming data.
11
-
12
- The Arpie server uses one ruby-thread per client, the client runs entirely in the
13
- calling thread; though an example implementation for evented callbacks is provided.
14
-
15
- == Source Code
16
-
17
- Source code is in git[http://git.swordcoast.net/?p=lib/ruby/arpie.git;a=summary].
18
-
19
- You can contact me via email at elven@swordcoast.net.
20
-
21
- arpie is available on the rubygems gem server - just do <tt>gem1.8 install arpie</tt>
22
- to get the newest version.
23
-
24
-
25
- == Simple, contrived example: A string reverse server
26
-
27
- require 'rubygems'
28
- require 'arpie'
29
- require 'socket'
30
-
31
- server = TCPServer.new(51210)
32
-
33
- e = Arpie::Server.new(Arpie::MarshalProtocol.new, Arpie::SizedProtocol.new)
34
-
35
- e.handle do |server, ep, msg|
36
- ep.write_message msg.reverse
37
- end
38
-
39
- e.accept do
40
- server.accept
41
- end
42
-
43
- c = Arpie::Client.new(Arpie::MarshalProtocol.new, Arpie::SizedProtocol.new)
44
- c.connect do
45
- TCPSocket.new("127.0.0.1", 51210)
46
- end
47
-
48
- c.write_message "hi"
49
- puts c.read_message
50
- # => "ih"
51
-
52
- == Advanced, but still simple example: Using Proxy to access remote objects
53
-
54
- require 'rubygems'
55
- require 'arpie'
56
- require 'socket'
57
-
58
- class MyHandler
59
- def reverse str
60
- str.reverse
61
- end
62
- end
63
-
64
- server = TCPServer.new(51210)
65
-
66
- e = Arpie::ProxyServer.new(Arpie::MarshalProtocol.new, Arpie::SizedProtocol.new)
67
-
68
- e.handle MyHandler.new
69
-
70
- e.accept do
71
- server.accept
72
- end
73
-
74
- p = Arpie::ProxyClient.new(Arpie::MarshalProtocol.new, Arpie::SizedProtocol.new)
75
- p.connect do |transport|
76
- TCPSocket.new("127.0.0.1", 51210)
77
- end
78
-
79
- puts p.reverse "hi"
80
- # => "ih"
81
-
82
- == Writing custom Protocols
83
-
84
- You can use arpies Protocol layer to write your custom protocol parser/emitters.
85
- Consider the following, again very contrived, example. You have a linebased wire format,
86
- which sends regular object updates in multiple lines, each holding a property to be updated.
87
- What objects get updated is not relevant to this example.
88
-
89
- For this example, we'll be using the SeparatorProtocol already contained in protocols.rb as
90
- a base.
91
-
92
- class AssembleExample < Arpie::Protocol
93
-
94
- def from binary
95
- # The wire format is simply a collection of lines
96
- # where the first one is a number containing the
97
- # # of lines to expect.
98
- assemble! binary do |binaries, meta|
99
- binaries.size >= 1 or incomplete!
100
- binaries.size - 1 >= binaries[0].to_i or incomplete!
101
-
102
- # Here, you can wrap all collected updates in
103
- # whatever format you want it to be. We're just
104
- # "joining" them to be a single array.
105
- binaries.shift
106
- binaries
107
- end
108
- end
109
-
110
- def to object
111
- yield object.size
112
- object.each {|oo|
113
- yield oo
114
- }
115
- end
116
- end
117
-
118
- p = Arpie::ProtocolChain.new(
119
- AssembleExample.new,
120
- Arpie::SeparatorProtocol.new
121
- )
122
- r, w = IO.pipe
123
-
124
- p.write_message(w, %w{we want to be assembled})
125
-
126
- p p.read_message(r)
127
- # => ["we", "want", "to", "be", "assembled"]
128
-
129
- == Replay protection
130
-
131
- It can happen that a Client loses connection to a Server.
132
- In that case, the Transport tries transparently reconnecting by simply
133
- invoking the block again that was given to Client#connect.
134
- See the Client accessors for modifying this behaviour.
135
-
136
- It is assumed that each call, that is being placed, is atomic - eg, no
137
- connection losses in between message send and receive; lost messages
138
- will be retransmitted. Some Protocol classes provide support for replay
139
- protection through in-band UUIDs; though it is not a requirement to implement it.
140
- If a UUID is provided in the data stream, the Protocol will not call
141
- the handler again for retransmissions, but instead reply with the old,
142
- already evaluated value.
143
-
144
- Not all protocols support UUIDs; those who do not offer no replay protection,
145
- and special care has to be taken elsewhere.
146
-
147
- All object-encoding protocols support UUIDs, including YAML and Marshal.
148
- XMLRPC does not.
149
-
150
- == Benchmarks
151
-
152
- There is a benchmark script included in the git repository (and in the gem
153
- under tools/). A sample output follows; your milage may vary.
154
-
155
- user system total real
156
-
157
- native DRb
158
- 1 0.000000 0.000000 0.000000 ( 0.000172)
159
- 1000 0.110000 0.010000 0.120000 ( 0.119767)
160
-
161
- Arpie: proxied MarshalProtocol with replay protection through uuidtools
162
- 1 0.000000 0.000000 0.010000 ( 0.075373)
163
- 1000 0.530000 0.090000 0.600000 ( 0.608665)
164
-
165
- Arpie: proxied MarshalProtocol without replay protection
166
- 1 0.000000 0.000000 0.000000 ( 0.000173)
167
- 1000 0.170000 0.020000 0.190000 ( 0.194649)
@@ -1,195 +0,0 @@
1
- module Arpie
2
-
3
- # A Client is a connection manager, and acts as the
4
- # glue between a user-defined medium (for example, a TCP
5
- # socket), and a protocol, with automatic reconnecting
6
- # and fault handling.
7
- #
8
- # See README for examples.
9
- class Client
10
- # The protocol chain used.
11
- attr_reader :protocol
12
-
13
- # How often should this Client retry a connection.
14
- # 0 for never, greater than 0 for that many attempts,
15
- # nil for infinite (default).
16
- # Values other than nil will raise network exceptions
17
- # to the caller.
18
- attr_accessor :connect_retry
19
-
20
- # How long should the caller sleep after each reconnect
21
- # attempt. (default: 1.0). The default value is probably
22
- # okay. Do not set this to 0; that will produce
23
- # unnecessary load in case of network failure.
24
- attr_accessor :connect_sleep
25
-
26
- def initialize *protocols
27
- @protocol = Arpie::ProtocolChain.new(*protocols)
28
- @read_io = nil
29
- @write_io = nil
30
- @connector = lambda { raise ArgumentError, "No connector specified, cannot connect to Endpoint." }
31
- @connect_retry = nil
32
- @connect_sleep = 1.0
33
- @on_error = lambda {|client, exception|
34
- $stderr.puts "Error in Transport IO: #{exception.message.to_s}"
35
- $stderr.puts exception.backtrace.join("\n")
36
- $stderr.puts "Set Transport#on_error &block to override this."
37
- }
38
- end
39
-
40
- # Provide a connector block, which will be called
41
- # each time a connection is needed.
42
- # Expectes an IO object.
43
- # Alternatively, you can return a two-item array.
44
- # To test something without involving any networking,
45
- # simply run IO.pipe in this block.
46
- # Set +connect_immediately+ to true to connect
47
- # immediately, instead on the first message.
48
- def connect connect_immediately = false, &connector
49
- @connector = connector
50
- _connect if connect_immediately
51
- self
52
- end
53
-
54
- # Set an error handler. It will be called with two
55
- # parameters, the client, and the exception that occured.
56
- # Optional, and just for notification.
57
- def on_error &handler #:yields: client, exception
58
- @on_error = handler
59
- self
60
- end
61
-
62
- # Send a message. Returns immediately.
63
- def write_message message
64
- io_retry do
65
- @protocol.write_message(@write_io, message)
66
- end
67
- end
68
- alias_method :<<, :write_message
69
-
70
- # Receive a message. Blocks until received.
71
- def read_message
72
- io_retry do
73
- return @protocol.read_message(@read_io)
74
- end
75
- end
76
-
77
- # Execute the given block until all connection attempts
78
- # have been exceeded.
79
- # Yields self.
80
- # You do not usually want to use this.
81
- def io_retry &block
82
- try = 0
83
-
84
- begin
85
- _connect
86
- yield self
87
- rescue IOError => e
88
- try += 1
89
- @on_error.call(self, e) if @on_error
90
- p e
91
-
92
- if @connect_retry == 0 || (@connect_retry && try > @connect_retry)
93
- raise EOFError, "Cannot read from io: lost connection after #{try} attempts (#{e.message.to_s})"
94
- end
95
-
96
- sleep @connect_sleep
97
- begin; @read_io.close if @read_io; rescue; end
98
- @read_io = nil
99
- begin; @write_io.close if @write_io; rescue; end
100
- @write_io = nil
101
- retry
102
- end
103
- end
104
-
105
- private
106
-
107
- def _connect
108
- @read_io and return
109
- @read_io, @write_io = @connector.call(self)
110
- @protocol.reset
111
- @write_io ||= @read_io
112
- end
113
- end
114
-
115
- # A simple pseudo event-based client, using a thread
116
- # with a callback.
117
- class EventedClient < Client
118
- private :read_message
119
-
120
- # Set a callback for incoming messages.
121
- def handle &handler #:yields: client, message
122
- @handler = handler
123
- end
124
-
125
- private
126
-
127
- def _read_thread
128
- loop do
129
- io_retry do
130
- message = read_message
131
- @handler and @handler.call(self, message)
132
- end
133
- end
134
- end
135
-
136
- def _connect
137
- super
138
- @read_thread ||= Thread.new { _read_thread }
139
- end
140
- end
141
-
142
-
143
- # A Client extension which provides a RPC-like
144
- # interface. Used by ProxyClient.
145
- class RPCClient < Client
146
- private :read_message, :write_message
147
-
148
- def initialize *protocols
149
- super(*protocols)
150
-
151
- @on_pre_call = lambda {|client, message| }
152
- @on_post_call = lambda {|client, message, reply| }
153
- end
154
-
155
- # Callback that gets invoked before placing a call to the
156
- # Server. You can stop the call from happening by raising
157
- # an exception (which will be passed on to the caller).
158
- def pre_call &handler #:yields: client, message
159
- @on_pre_call = handler
160
- self
161
- end
162
-
163
- # Callback that gets invoked after receiving an answer.
164
- # You can raise an exception here; and it will be passed
165
- # to the caller, instead of returning the value.
166
- def post_call &handler #:yields: client, message, reply
167
- @on_post_call = handler
168
- self
169
- end
170
-
171
-
172
- # Send a message and receive a reply in a synchronous
173
- # fashion. Will block until transmitted, or until
174
- # all reconnect attempts failed.
175
- def request message
176
- reply = nil
177
-
178
- @on_pre_call.call(self, message) if @on_pre_call
179
-
180
- io_retry do
181
- write_message(message)
182
- reply = read_message
183
- end
184
-
185
- @on_post_call.call(self, message, reply) if @on_post_call
186
-
187
- case reply
188
- when Exception
189
- raise reply
190
- else
191
- reply
192
- end
193
- end
194
- end
195
- end
@@ -1,143 +0,0 @@
1
- require 'uuidtools'
2
-
3
- module Arpie
4
-
5
- # A Endpoint which supports arbitary objects as handlers,
6
- # instead of a proc.
7
- #
8
- # Note that this will only export public instance method
9
- # of the class as they are defined.
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, ..).
17
- attr_accessor :interface
18
-
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
36
- @handler = handler
37
- @interface = handler.class.public_instance_methods(false).map {|x|
38
- x.to_sym
39
- }
40
- self
41
- end
42
-
43
- private
44
-
45
- def _handle endpoint, message
46
- if !@handler.respond_to?(message.meth.to_sym) ||
47
- (@interface && !@interface.index(message.meth.to_sym))
48
- endpoint.write_message NoMethodError.new("No such method: #{message.meth.inspect}")
49
- return
50
- end
51
-
52
- begin
53
- # Prune old serials. This can probably be optimized, but works well enough for now.
54
- if @uuid_tracking && message.uuid
55
- uuid, serial = * message.uuid
56
-
57
- raise ArgumentError,
58
- "Invalid UUID given, expect [uuid/64bit, serial/numeric]." unless
59
- uuid.is_a?(Integer) && serial.is_a?(Integer)
60
-
61
- # Limit to sane values.
62
- uuid &= 0xffffffffffffffff
63
- serial &= 0xffffffffffffffff
64
-
65
- timestamps = @uuids.values.map {|v| v[0] }.sort
66
- latest_timestamp = timestamps[-@max_uuids]
67
- @uuids.reject! {|uuid, (time, value)|
68
- time < latest_timestamp
69
- } if latest_timestamp
70
-
71
- endpoint.write_message((@uuids[message.uuid] ||=
72
- [Time.now, @handler.send(message.meth, *message.argv)])[1])
73
-
74
- else
75
- endpoint.write_message @handler.send(message.meth, *message.argv)
76
- end
77
- rescue IOError
78
- raise
79
-
80
- rescue Exception => e
81
- endpoint.write_message RuntimeError.new("Internal Error")
82
- raise
83
- end
84
- end
85
- end
86
-
87
- # A Proxy is a wrapper around a Client, which transparently tunnels
88
- # method calls to the remote ProxyServer.
89
- # Note that the methods of Client cannot be proxied.
90
- class ProxyClient < RPCClient
91
- attr_accessor :namespace
92
-
93
- # Set to false to disable replay protection.
94
- # Default is true.
95
- attr_accessor :replay_protection
96
-
97
- # The current serial for this transport.
98
- attr_accessor :serial
99
-
100
- # The generated uuid for this Client.
101
- # nil if no call has been made yet.
102
- attr_accessor :uuid
103
-
104
- def initialize *protocols
105
- super
106
- @protocol, @namespace = protocol, ""
107
- @serial = 0
108
- @uuid_generator = lambda {|client, method, argv|
109
- UUIDTools::UUID.random_create.to_i
110
- }
111
- @replay_protection = true
112
- end
113
-
114
- # Set up a new UUID generator for this proxy client.
115
- # Make sure that this yields really random numbers.
116
- # The default uses the uuidtools gem and is usually okay.
117
- #
118
- # This gets called exactly once for each created ProxyClient.
119
- def uuid_generator &handler #:yields: client, method, argv
120
- @uuid_generator = handler
121
- self
122
- end
123
-
124
- def method_missing meth, *argv # :nodoc:
125
- serial = nil
126
- if @replay_protection
127
- serial = [
128
- @uuid ||= @uuid_generator.call(self, meth, argv),
129
- @serial += 1
130
- ]
131
- end
132
-
133
- call = Arpie::RPCall.new(@namespace, meth, argv, serial)
134
- ret = self.request(call)
135
- case ret
136
- when Exception
137
- raise ret
138
- else
139
- ret
140
- end
141
- end
142
- end
143
- end