arpie 0.0.3 → 0.0.4

Sign up to get free protection for your applications and to get access to all the features.
@@ -45,24 +45,34 @@ module Arpie
45
45
  def _handle endpoint, message
46
46
  if !@handler.respond_to?(message.meth.to_sym) ||
47
47
  (@interface && !@interface.index(message.meth.to_sym))
48
- raise NoMethodError, "No such method: #{message.meth.inspect}"
48
+ endpoint.write_message NoMethodError.new("No such method: #{message.meth.inspect}")
49
+ return
49
50
  end
50
51
 
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
52
+ begin
53
+ # Prune old serials. This can probably be optimized, but works well enough for now.
54
+ if @uuid_tracking && message.uuid && message.uuid.is_a?(Numeric)
55
+ message.uuid &= 0xffffffffffffffff
58
56
 
59
- endpoint.write_message((@uuids[message.uuid] ||=
60
- [Time.now, @handler.send(message.meth, *message.argv)])[1])
57
+ timestamps = @uuids.values.map {|v| v[0] }.sort
58
+ latest_timestamp = timestamps[-@max_uuids]
59
+ @uuids.reject! {|uuid, (time, value)|
60
+ time < latest_timestamp
61
+ } if latest_timestamp
61
62
 
62
- else
63
- endpoint.write_message @handler.send(message.meth, *message.argv)
64
- end
63
+ endpoint.write_message((@uuids[message.uuid] ||=
64
+ [Time.now, @handler.send(message.meth, *message.argv)])[1])
65
65
 
66
+ else
67
+ endpoint.write_message @handler.send(message.meth, *message.argv)
68
+ end
69
+ rescue IOError
70
+ raise
71
+
72
+ rescue Exception => e
73
+ endpoint.write_message RuntimeError.new("Internal Error")
74
+ raise
75
+ end
66
76
  end
67
77
  end
68
78
 
@@ -70,12 +80,13 @@ module Arpie
70
80
  # method calls to the remote ProxyServer.
71
81
  # Note that the methods of Client cannot be proxied.
72
82
  class ProxyClient < RPCClient
83
+ attr_accessor :namespace
73
84
 
74
- def initialize protocol, namespace = ""
75
- super(protocol)
76
- @protocol, @namespace = protocol, namespace
85
+ def initialize *protocols
86
+ super
87
+ @protocol, @namespace = protocol, ""
77
88
  @uuid_generator = lambda {|client, method, argv|
78
- UUID.timestamp_create.to_i.to_s
89
+ UUID.random_create.to_i
79
90
  }
80
91
  end
81
92
 
@@ -95,7 +106,7 @@ module Arpie
95
106
  uuid = @uuid_generator ?
96
107
  @uuid_generator.call(self, meth, argv) : nil
97
108
 
98
- call = RPCProtocol::Call.new(@namespace, meth, argv, uuid)
109
+ call = Arpie::RPCall.new(@namespace, meth, argv, uuid)
99
110
  ret = self.request(call)
100
111
  case ret
101
112
  when Exception
@@ -36,11 +36,11 @@ module Arpie
36
36
 
37
37
  attr_reader :endpoints
38
38
 
39
- # Create a new Server with the given +Protocol+.
39
+ # Create a new Server with the given protocols.
40
40
  # You will need to define a handler, and an acceptor
41
41
  # before it becomes operational.
42
- def initialize protocol
43
- @protocol = protocol
42
+ def initialize *protocols
43
+ @protocol = Arpie::ProtocolChain.new(*protocols)
44
44
  @endpoints = []
45
45
 
46
46
  @on_connect = lambda {|server, endpoint| }
@@ -120,7 +120,7 @@ module Arpie
120
120
  def _acceptor_thread
121
121
  loop do
122
122
  client = @acceptor.call(self)
123
- c = @protocol.endpoint_klass.new(@protocol.clone, client)
123
+ c = @protocol.endpoint_class.new(@protocol.clone, client)
124
124
  Thread.new { _read_thread(c) }
125
125
  end
126
126
  end
@@ -0,0 +1,108 @@
1
+ require 'xmlrpc/create'
2
+ require 'xmlrpc/parser'
3
+ require 'xmlrpc/config'
4
+
5
+ module Arpie
6
+
7
+ class XMLRPCProtocol < Protocol
8
+ private_class_method :new
9
+
10
+ attr_accessor :writer
11
+ attr_accessor :parser
12
+
13
+ def setup
14
+ @writer ||= XMLRPC::Create.new
15
+ @parser ||= XMLRPC::XMLParser::REXMLStreamParser.new
16
+ end
17
+ private :setup
18
+
19
+ end
20
+
21
+ # A XMLRPC Protocol based on rubys xmlrpc stdlib.
22
+ # This does not encode HTTP headers; usage together with
23
+ # a real webserver is advised.
24
+ class XMLRPCClientProtocol < XMLRPCProtocol
25
+ public_class_method :new
26
+
27
+ def to object
28
+ setup
29
+ raise ArgumentError, "Can only encode Arpie::RPCall" unless
30
+ object.is_a?(Arpie::RPCall)
31
+
32
+ @writer.methodCall((object.ns.nil? ? '' : object.ns + '.') + object.meth, *object.argv)
33
+ end
34
+
35
+ def from binary
36
+ setup
37
+ yield @parser.parseMethodResponse(binary)[1]
38
+ end
39
+ end
40
+
41
+ class XMLRPCServerProtocol < XMLRPCProtocol
42
+ public_class_method :new
43
+
44
+ def to object
45
+ setup
46
+ case object
47
+ when Exception
48
+ # TODO: wrap XMLFault
49
+ raise NotImplementedError, "Cannot encode exceptions"
50
+
51
+ else
52
+ @writer.methodResponse(true, object)
53
+ end
54
+ end
55
+
56
+ def from binary
57
+ setup
58
+ vv = @parser.parseMethodCall(binary)
59
+ ns, meth = vv[0].split('.')
60
+ meth.nil? and begin meth, ns = ns, nil end
61
+ yield RPCall.new(ns, meth, vv[1])
62
+ end
63
+ end
64
+
65
+ # This simulates a very basic HTTP XMLRPC client/server.
66
+ # It is not recommended to use this with production code.
67
+ class HTTPTestProtocol < Protocol
68
+ CAN_SEPARATE_MESSAGES = true
69
+
70
+ private_class_method :new
71
+
72
+ def from binary
73
+ # Simply strip all HTTP headers.
74
+ binary && binary.size > 0 or incomplete!
75
+ header, body_and_rest = binary.split(/\r\n\r\n/, 2)
76
+ header && body_and_rest or incomplete!
77
+
78
+ header =~ /^\s*content-length:\s+(\d+)$\s*/xi or stream_error! "No content-length was provided."
79
+ content_length = $1.to_i
80
+
81
+ content_length <= 0
82
+ body_and_rest.size >= content_length or incomplete!
83
+
84
+ body = body_and_rest[0, content_length]
85
+
86
+ yield body
87
+
88
+ header.size + 4 + content_length
89
+ end
90
+ end
91
+
92
+ class HTTPClientTestProtocol < HTTPTestProtocol
93
+ public_class_method :new
94
+
95
+ def to object
96
+ "GET / HTTP/1.[01]\r\nContent-Length: #{object.size}\r\n\r\n" + object
97
+ end
98
+
99
+ end
100
+
101
+ class HTTPServerTestProtocol < HTTPTestProtocol
102
+ public_class_method :new
103
+
104
+ def to object
105
+ "HTTP/1.0 200 OK\r\nContent-Length: #{object.size}\r\n\r\n" + object
106
+ end
107
+ end
108
+ end
@@ -0,0 +1,107 @@
1
+ require File.join(File.dirname(__FILE__), 'spec_helper')
2
+
3
+ require 'socket'
4
+ require 'timeout'
5
+
6
+ describe "ClientServer", :shared => true do
7
+ before(:all) do
8
+ @tcp_server = TCPServer.new('127.0.0.1', 56000)
9
+ end
10
+
11
+ before do
12
+ @handler_errors = 0
13
+ $evaluate_calls = 0
14
+ @client = Arpie::Client.new(Arpie::MarshalProtocol.new, Arpie::SizedProtocol.new)
15
+ @server = Arpie::Server.new(Arpie::MarshalProtocol.new, Arpie::SizedProtocol.new)
16
+ @client.connect do TCPSocket.new('127.0.0.1', 56000) end
17
+ @server.accept do @tcp_server.accept end
18
+ @server.on_handler_error do |s, e, p, x|
19
+ @handler_errors += 1
20
+ end
21
+
22
+ @server.handle do |s, e, m|
23
+ e.write_message m
24
+ end
25
+ end
26
+ end
27
+
28
+ describe "ProxyClientServer", :shared => true do
29
+ before(:all) do
30
+ @tcp_server = TCPServer.new('127.0.0.1', 56001)
31
+ end
32
+
33
+ before do
34
+ @handler_errors = 0
35
+ $evaluate_calls = 0
36
+ @client = Arpie::ProxyClient.new(Arpie::MarshalProtocol.new, Arpie::SizedProtocol.new)
37
+ @server = Arpie::ProxyServer.new(Arpie::MarshalProtocol.new, Arpie::SizedProtocol.new)
38
+ @client.connect do TCPSocket.new('127.0.0.1', 56001) end
39
+ @server.accept do @tcp_server.accept end
40
+ @server.on_handler_error do |s, e, p, x|
41
+ @handler_errors += 1
42
+ end
43
+
44
+ @server.handle((Class.new do
45
+ def evaluate_calls
46
+ $evaluate_calls += 1
47
+ end
48
+ def raise_something
49
+ raise "test"
50
+ end
51
+ end).new)
52
+ end
53
+ end
54
+
55
+ describe Arpie::Client do
56
+ it_should_behave_like "ClientServer"
57
+ end
58
+
59
+ describe Arpie::EventedClient do
60
+ it_should_behave_like "ClientServer"
61
+
62
+ before do
63
+ @client = Arpie::EventedClient.new(Arpie::MarshalProtocol.new, Arpie::SizedProtocol.new)
64
+ @handler_calls = 0
65
+ @queue = Queue.new
66
+ @client.handle do
67
+ @queue.push true
68
+ @handler_calls += 1
69
+ end
70
+ @client.connect do TCPSocket.new('127.0.0.1', 56000) end
71
+ end
72
+
73
+ it "should not allow reading messages" do
74
+ @client.should_not respond_to :read_message
75
+ end
76
+
77
+ it "should call the handler for incoming messages" do
78
+ @client.write_message "test"
79
+ lambda {Timeout.timeout(1.0) { @queue.pop } }.should_not raise_error TimeoutError
80
+ end
81
+ end
82
+
83
+ describe Arpie::Server do
84
+ it_should_behave_like "ClientServer"
85
+ end
86
+
87
+ describe "ProxyServer" do
88
+ it_should_behave_like "ProxyClientServer"
89
+ it "should raise handler errors to the client" do
90
+ lambda { @client.raise_something }.should(raise_error RuntimeError, "Internal Error")
91
+ @handler_errors.should == 1
92
+ end
93
+
94
+ it "should not re-evaluate for already-seen uuids" do
95
+ @client.evaluate_calls
96
+ @client.evaluate_calls
97
+ @client.uuid_generator do 100 end
98
+ @client.evaluate_calls
99
+ @client.evaluate_calls
100
+ $evaluate_calls.should == 3
101
+ end
102
+
103
+ it "should not call handler errors for missing methods" do
104
+ lambda { @client.missing }.should raise_error NoMethodError
105
+ $evaluate_calls.should == 0
106
+ end
107
+ end
@@ -0,0 +1,47 @@
1
+ require File.join(File.dirname(__FILE__), 'spec_helper')
2
+
3
+ class Splitter < Arpie::Protocol
4
+ def from binary
5
+ yield binary.size
6
+ binary.each_char do |c|
7
+ yield c
8
+ end
9
+ end
10
+ end
11
+
12
+ class Merger < Arpie::Protocol
13
+ def from binary
14
+ unless @expect
15
+ @expect = binary or incomplete!
16
+ gulp!
17
+ end
18
+
19
+ assemble! binary, :d, :size => @expect
20
+ end
21
+
22
+ def assemble binaries, token, meta
23
+ binaries.size >= meta[:size] or incomplete!
24
+ yield binaries.join('')
25
+ end
26
+ end
27
+
28
+ describe "Merger::Splitter::Sized" do subject { [Splitter, Arpie::SizedProtocol] }
29
+ it_should_behave_like "ProtocolChainSetup"
30
+
31
+ it "should split messages correctly" do
32
+ chain_write(t = 'test')
33
+ chain_read.should == t.size
34
+ for i in 1..t.size do
35
+ chain_read.should == t[i-1, 1]
36
+ end
37
+ end
38
+ end
39
+
40
+ describe "Merger::Splitter::Sized" do subject { [Merger, Splitter, Arpie::SizedProtocol] }
41
+ it_should_behave_like "ProtocolChainSetup"
42
+
43
+ it "should assemble split messages correctly" do
44
+ chain_write(t = 'test')
45
+ chain_read.should == t
46
+ end
47
+ end
@@ -0,0 +1,152 @@
1
+ require File.join(File.dirname(__FILE__), 'spec_helper')
2
+
3
+ describe "ProtocolChain", :shared => true do
4
+ it_should_behave_like "ProtocolChainSetup"
5
+
6
+ it "should convert without io correctly" do
7
+ v = @chain.to @testdata_a
8
+ @chain.from(v).should == [@testdata_a]
9
+ end
10
+
11
+ it "should read written binary data correctly" do
12
+ chain_write(@testdata_a)
13
+ chain_read.should == @testdata_a
14
+ end
15
+
16
+ it "should read multiple written messages correctly" do
17
+ write = []
18
+ for i in 0...4 do
19
+ write << (i % 2 == 0 ? @testdata_a : @testdata_b)
20
+ end
21
+ chain_write(*write)
22
+
23
+ for i in 0...4 do
24
+ chain_read.should == (i % 2 == 0 ? @testdata_a : @testdata_b)
25
+ end
26
+ end
27
+
28
+ it "should not clobber the buffer before the first read" do
29
+ chain_write(@testdata_a)
30
+ end
31
+
32
+ it "should parse a io-written buffer correctly" do
33
+ write = []
34
+ for i in 0...4 do
35
+ write << (i % 2 == 0 ? @testdata_a : @testdata_b)
36
+ end
37
+ chain_write(*write)
38
+
39
+ @chain.from(@r.readpartial(4096)).should == [@testdata_a, @testdata_b, @testdata_a, @testdata_b]
40
+ end
41
+
42
+ it "should read messages greater than MTU correctly" do
43
+ chain_write(message = (@testdata_a * (Arpie::MTU + 10)))
44
+ chain_read.should == message
45
+ end
46
+
47
+ it "should not fail on interleaved io streams" do
48
+ r2, w2 = IO.pipe
49
+ chain_write(@testdata_a)
50
+ @chain.write_message(w2, @testdata_b)
51
+ w2.close
52
+ chain_read.should == @testdata_a
53
+ @chain.read_message(r2).should == @testdata_b
54
+ end
55
+ end
56
+
57
+ describe "ObjectProtocolChain", :shared => true do
58
+ it_should_behave_like "ProtocolChain"
59
+ # Now, lets try some variations.
60
+
61
+ it "should read written objects correctly" do
62
+ chain_write(
63
+ a = [1, 2.4, false, true, nil, "string"],
64
+ b = {1 => "hi", 2 => "test", 3 => "bloh"}
65
+ )
66
+
67
+ chain_read.should == a
68
+ chain_read.should == b
69
+ end
70
+ end
71
+
72
+ describe "RPCProtocolChain", :shared => true do
73
+ it_should_behave_like "RPCProtocolChainSetup"
74
+
75
+ it "should send namespace-less RPC calls correctly encoded" do
76
+ call = Arpie::RPCall.new(nil, 'meth', [1, 2, 3])
77
+ @client.write_message(@w, call)
78
+ @w.close
79
+ @server.read_message(@r).should == call
80
+ end
81
+
82
+ it "should send namespaced RPC calls correctly encoded" do
83
+ call = Arpie::RPCall.new('ns', 'meth', [1, 2, 3])
84
+ @client.write_message(@w, call)
85
+ @w.close
86
+ @server.read_message(@r).should == call
87
+ end
88
+
89
+ it "should encode result values correctly" do
90
+ for r in inp = [1, 2.4, false, true, "string", {"1"=>"hash"}, [1,2,3]]
91
+ @server.write_message(@w, r)
92
+ end
93
+ @w.close
94
+
95
+ for r in inp
96
+ @client.read_message(@r).should == r
97
+ end
98
+ end
99
+ end
100
+
101
+ # Now, lets try some variations.
102
+
103
+ describe "Sized" do subject { [Arpie::SizedProtocol] }
104
+ it_should_behave_like "ProtocolChain"
105
+ end
106
+
107
+ describe "Sized::Sized" do subject { [Arpie::SizedProtocol, Arpie::SizedProtocol] }
108
+ it_should_behave_like "ProtocolChain"
109
+ end
110
+
111
+ describe "Sized::Marshal::Sized" do subject { [Arpie::SizedProtocol, Arpie::MarshalProtocol, Arpie::SizedProtocol] }
112
+ it_should_behave_like "ProtocolChain"
113
+ end
114
+
115
+ # Shellwords is a bit of a special case, because it only encodes arrays.
116
+ describe "Shellwords::Separator" do subject { [Arpie::ShellwordsProtocol, Arpie::SeparatorProtocol] }
117
+ it_should_behave_like "ProtocolChain"
118
+ before do
119
+ @testdata_a, @testdata_b = ['I am test', '1'], ['I am test', '2']
120
+ end
121
+ end
122
+
123
+ describe "HTTPTest" do subject { [Arpie::HTTPClientTestProtocol] }
124
+ it_should_behave_like "ProtocolChain"
125
+ end
126
+
127
+ describe "HTTPTest" do subject { [Arpie::HTTPServerTestProtocol] }
128
+ it_should_behave_like "ProtocolChain"
129
+ end
130
+
131
+ describe "YAML" do subject { [Arpie::YAMLProtocol] }
132
+ it_should_behave_like "ObjectProtocolChain"
133
+ end
134
+
135
+ describe "XMLRPC::Sized" do subject {
136
+ [
137
+ [Arpie::XMLRPCClientProtocol, Arpie::SizedProtocol],
138
+ [Arpie::XMLRPCServerProtocol, Arpie::SizedProtocol]
139
+ ]
140
+ }
141
+ it_should_behave_like "RPCProtocolChain"
142
+ end
143
+
144
+ describe "XMLRPC::HTTPTest" do subject {
145
+ [
146
+ [Arpie::XMLRPCClientProtocol, Arpie::HTTPClientTestProtocol],
147
+ [Arpie::XMLRPCServerProtocol, Arpie::HTTPServerTestProtocol]
148
+ ]
149
+ }
150
+ it_should_behave_like "RPCProtocolChain"
151
+ end
152
+