arpie 0.0.6 → 0.1.0
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.
- data/.gitignore +4 -0
- data/.yardopts +1 -0
- data/{BINARY_SPEC → BINARY.rdoc} +73 -28
- data/Gemfile +8 -0
- data/LICENCE +15 -0
- data/PROTOCOLS.rdoc +46 -0
- data/README.rdoc +17 -0
- data/Rakefile +6 -114
- data/arpie.gemspec +26 -0
- data/examples/em.rb +18 -0
- data/lib/arpie.rb +2 -4
- data/lib/arpie/binary.rb +18 -4
- data/lib/arpie/binary/bytes_type.rb +7 -2
- data/lib/arpie/binary/fixed_type.rb +11 -4
- data/lib/arpie/binary/list_type.rb +2 -0
- data/lib/arpie/binary/pack_type.rb +20 -10
- data/lib/arpie/em.rb +48 -0
- data/lib/arpie/error.rb +2 -4
- data/lib/arpie/protocol.rb +7 -23
- data/lib/arpie/version.rb +3 -0
- data/spec/binary_spec.rb +113 -8
- data/spec/bytes_binary_type_spec.rb +16 -3
- data/spec/em_spec.rb +27 -0
- data/spec/examples_spec.rb +11 -0
- data/spec/fixed_binary_type_spec.rb +2 -2
- data/spec/list_binary_type_spec.rb +9 -0
- data/spec/protocol_merge_and_split_spec.rb +3 -3
- data/spec/protocol_spec.rb +20 -43
- data/spec/spec_helper.rb +13 -12
- metadata +74 -83
- data/COPYING +0 -15
- data/README +0 -167
- data/lib/arpie/client.rb +0 -195
- data/lib/arpie/proxy.rb +0 -143
- data/lib/arpie/server.rb +0 -157
- data/lib/arpie/xmlrpc.rb +0 -108
- data/spec/client_server_spec.rb +0 -107
- data/tools/benchmark.rb +0 -52
- data/tools/protocol_benchmark.rb +0 -42
data/lib/arpie/server.rb
DELETED
@@ -1,157 +0,0 @@
|
|
1
|
-
module Arpie
|
2
|
-
|
3
|
-
# Endpoint wraps client IO objects. One Endpoint
|
4
|
-
# per client. This is provided as a convenience
|
5
|
-
# mechanism for protocols to store
|
6
|
-
# protocol-and-client-specific data.
|
7
|
-
class Endpoint
|
8
|
-
attr_reader :io
|
9
|
-
|
10
|
-
attr_reader :protocol
|
11
|
-
|
12
|
-
def initialize protocol, io
|
13
|
-
@protocol, @io = protocol, io
|
14
|
-
@protocol.reset
|
15
|
-
end
|
16
|
-
|
17
|
-
def read_message
|
18
|
-
@protocol.read_message(@io)
|
19
|
-
end
|
20
|
-
|
21
|
-
def write_message message
|
22
|
-
@protocol.write_message(@io, message)
|
23
|
-
end
|
24
|
-
alias_method :<<, :write_message
|
25
|
-
|
26
|
-
end
|
27
|
-
|
28
|
-
# A Server is the server-side part of a RPC setup.
|
29
|
-
# It accepts connections (via the acceptor), and handles
|
30
|
-
# incoming RPC calls on them.
|
31
|
-
#
|
32
|
-
# There will be one Thread per connection, so order of
|
33
|
-
# execution with multiple threads is not guaranteed.
|
34
|
-
class Server
|
35
|
-
attr_reader :protocol
|
36
|
-
|
37
|
-
attr_reader :endpoints
|
38
|
-
|
39
|
-
# Create a new Server with the given protocols.
|
40
|
-
# You will need to define a handler, and an acceptor
|
41
|
-
# before it becomes operational.
|
42
|
-
def initialize *protocols
|
43
|
-
@protocol = Arpie::ProtocolChain.new(*protocols)
|
44
|
-
@endpoints = []
|
45
|
-
|
46
|
-
@on_connect = lambda {|server, endpoint| }
|
47
|
-
@on_disconnect = lambda {|server, endpoint, exception| }
|
48
|
-
@on_handler_error = lambda {|server, endpoint, message, exception|
|
49
|
-
$stderr.puts "Error in handler: #{exception.message.to_s}"
|
50
|
-
$stderr.puts exception.backtrace.join("\n")
|
51
|
-
$stderr.puts "Returning exception for this call."
|
52
|
-
Exception.new("internal error")
|
53
|
-
}
|
54
|
-
@handler = lambda {|server, endpoint, message| raise ArgumentError, "No handler defined." }
|
55
|
-
end
|
56
|
-
|
57
|
-
# Provide an acceptor; this will be run in a a loop
|
58
|
-
# to get IO objects.
|
59
|
-
#
|
60
|
-
# Example:
|
61
|
-
# listener = TCPServer.new(12345)
|
62
|
-
# my_server.accept do
|
63
|
-
# listener.accept
|
64
|
-
# end
|
65
|
-
def accept &acceptor #:yields: server
|
66
|
-
@acceptor = acceptor
|
67
|
-
Thread.new { _acceptor_thread }
|
68
|
-
self
|
69
|
-
end
|
70
|
-
|
71
|
-
# Set a message handler, which is a proc that will receive
|
72
|
-
# three parameters: the server, the endpoint, and the message.
|
73
|
-
#
|
74
|
-
# Example:
|
75
|
-
# my_server.handle do |server, endpoint, message|
|
76
|
-
# puts "Got a message: #{message.inspect}"
|
77
|
-
# endpoint.write_message "ok"
|
78
|
-
# end
|
79
|
-
def handle &handler #:yields: server, endpoint, message
|
80
|
-
raise ArgumentError, "No handler given; need a block or proc." unless handler
|
81
|
-
@handler = handler
|
82
|
-
self
|
83
|
-
end
|
84
|
-
|
85
|
-
# Set an error handler.
|
86
|
-
# The return value will be sent to the client.
|
87
|
-
#
|
88
|
-
# Default is to print the exception to stderr, and return
|
89
|
-
# a generic exception that does not leak information.
|
90
|
-
def on_handler_error &handler #:yields: server, endpoint, message, exception
|
91
|
-
raise ArgumentError, "No handler given; need a block or proc." unless handler
|
92
|
-
@on_handler_error = handler
|
93
|
-
self
|
94
|
-
end
|
95
|
-
|
96
|
-
# Callback that gets invoked when a new client connects.
|
97
|
-
# You can <tt>throw :kill_client</tt> here to stop this client
|
98
|
-
# from connecting. Clients stopped this way will invoke
|
99
|
-
# the on_disconnect handler normally.
|
100
|
-
def on_connect &handler #:yields: server, endpoint
|
101
|
-
raise ArgumentError, "No handler given; need a block or proc." unless handler
|
102
|
-
@on_connect = handler
|
103
|
-
self
|
104
|
-
end
|
105
|
-
|
106
|
-
# Callback that gets invoked when a client disconnects.
|
107
|
-
# The exception is the error that occured (usually a EOFError).
|
108
|
-
def on_disconnect &handler #:yields: server, endpoint, exception
|
109
|
-
raise ArgumentError, "No handler given; need a block or proc." unless handler
|
110
|
-
@on_disconnect = handler
|
111
|
-
self
|
112
|
-
end
|
113
|
-
|
114
|
-
private
|
115
|
-
|
116
|
-
def _handle endpoint, message
|
117
|
-
@handler.call(self, endpoint, message)
|
118
|
-
end
|
119
|
-
|
120
|
-
def _acceptor_thread
|
121
|
-
loop do
|
122
|
-
client = @acceptor.call(self)
|
123
|
-
c = @protocol.endpoint_class.new(@protocol.clone, client)
|
124
|
-
Thread.new { _read_thread(c) }
|
125
|
-
end
|
126
|
-
end
|
127
|
-
|
128
|
-
def _read_thread endpoint
|
129
|
-
@endpoints << endpoint
|
130
|
-
_exception = nil
|
131
|
-
|
132
|
-
catch(:kill_client) {
|
133
|
-
@on_connect.call(self, endpoint)
|
134
|
-
|
135
|
-
loop do
|
136
|
-
message, answer = nil, nil
|
137
|
-
|
138
|
-
begin
|
139
|
-
message = endpoint.read_message
|
140
|
-
rescue IOError => e
|
141
|
-
_exception = e
|
142
|
-
break
|
143
|
-
end
|
144
|
-
|
145
|
-
begin
|
146
|
-
answer = _handle(endpoint, message)
|
147
|
-
rescue Exception => e
|
148
|
-
answer = @on_handler_error.call(self, endpoint, message, e)
|
149
|
-
end
|
150
|
-
end
|
151
|
-
}
|
152
|
-
|
153
|
-
@on_disconnect.call(self, endpoint, _exception)
|
154
|
-
@endpoints.delete(endpoint)
|
155
|
-
end
|
156
|
-
end
|
157
|
-
end
|
data/lib/arpie/xmlrpc.rb
DELETED
@@ -1,108 +0,0 @@
|
|
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
|
-
yield @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
|
-
yield 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
|
-
yield "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
|
-
yield "HTTP/1.0 200 OK\r\nContent-Length: #{object.size}\r\n\r\n" + object
|
106
|
-
end
|
107
|
-
end
|
108
|
-
end
|
data/spec/client_server_spec.rb
DELETED
@@ -1,107 +0,0 @@
|
|
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.serial -= 1
|
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
|
data/tools/benchmark.rb
DELETED
@@ -1,52 +0,0 @@
|
|
1
|
-
require 'rubygems'
|
2
|
-
require 'socket'
|
3
|
-
$:.unshift(File.join(File.dirname(__FILE__), "../lib/"))
|
4
|
-
require 'arpie'
|
5
|
-
require 'benchmark'
|
6
|
-
require 'drb'
|
7
|
-
require 'xmlrpc/server'
|
8
|
-
require 'xmlrpc/client'
|
9
|
-
|
10
|
-
class Wrap
|
11
|
-
def reverse x
|
12
|
-
x.reverse
|
13
|
-
end
|
14
|
-
end
|
15
|
-
|
16
|
-
include Arpie
|
17
|
-
|
18
|
-
server = TCPServer.new(51210)
|
19
|
-
|
20
|
-
endpoint = ProxyServer.new MarshalProtocol.new, SizedProtocol.new
|
21
|
-
endpoint.handle Wrap.new
|
22
|
-
|
23
|
-
endpoint.accept do
|
24
|
-
server.accept
|
25
|
-
end
|
26
|
-
|
27
|
-
$proxy = ProxyClient.new MarshalProtocol.new, SizedProtocol.new
|
28
|
-
$proxy.connect(true) do
|
29
|
-
TCPSocket.new("127.0.0.1", 51210)
|
30
|
-
end
|
31
|
-
|
32
|
-
Benchmark.bm {|b|
|
33
|
-
|
34
|
-
puts ""
|
35
|
-
puts "native DRb"
|
36
|
-
drbserver = DRb.start_service nil, Wrap.new
|
37
|
-
drbobject = DRbObject.new nil, DRb.uri
|
38
|
-
|
39
|
-
b.report(" 1") { 1.times { drbobject.reverse "benchmark" } }
|
40
|
-
b.report("1000") { 1000.times { drbobject.reverse "benchmark" } }
|
41
|
-
|
42
|
-
puts ""
|
43
|
-
puts "Arpie: proxied MarshalProtocol with replay protection through uuidtools"
|
44
|
-
b.report(" 1") { 1.times { $proxy.reverse "benchmark" } }
|
45
|
-
b.report("1000") { 1000.times { $proxy.reverse "benchmark" } }
|
46
|
-
|
47
|
-
puts ""
|
48
|
-
puts "Arpie: proxied MarshalProtocol without replay protection"
|
49
|
-
$proxy.replay_protection = false
|
50
|
-
b.report(" 1") { 1.times { $proxy.reverse "benchmark" } }
|
51
|
-
b.report("1000") { 1000.times { $proxy.reverse "benchmark" } }
|
52
|
-
}
|
data/tools/protocol_benchmark.rb
DELETED
@@ -1,42 +0,0 @@
|
|
1
|
-
require 'rubygems'
|
2
|
-
$:.unshift(File.join(File.dirname(__FILE__), "../lib/"))
|
3
|
-
require 'socket'
|
4
|
-
require 'arpie'
|
5
|
-
require 'benchmark'
|
6
|
-
|
7
|
-
include Arpie
|
8
|
-
|
9
|
-
# Data test size.
|
10
|
-
DATA_SIZE = 512
|
11
|
-
|
12
|
-
rpc_call = RPCall.new('ns.', 'meth', [1, 2, 3, 4])
|
13
|
-
$test_data = "a" * DATA_SIZE
|
14
|
-
$test_data.freeze
|
15
|
-
|
16
|
-
# Protocols to test:
|
17
|
-
PROTOCOLS = [
|
18
|
-
[SizedProtocol.new],
|
19
|
-
[MarshalProtocol.new, SizedProtocol.new],
|
20
|
-
[YAMLProtocol.new]
|
21
|
-
]
|
22
|
-
|
23
|
-
ITERATIONS = 1000
|
24
|
-
|
25
|
-
$stderr.puts "Testing protocols with a data size of #{DATA_SIZE}, #{ITERATIONS} iterations"
|
26
|
-
|
27
|
-
|
28
|
-
Benchmark.bm {|b|
|
29
|
-
r, w = IO.pipe
|
30
|
-
PROTOCOLS.each {|p|
|
31
|
-
a ||= []
|
32
|
-
proto = ProtocolChain.new *p
|
33
|
-
r, w = IO.pipe
|
34
|
-
|
35
|
-
b.report("%-30s\n" % p.map{|x| x.class.to_s}.inspect) {
|
36
|
-
ITERATIONS.times do
|
37
|
-
proto.write_message(w, $test_data)
|
38
|
-
proto.read_message(r)
|
39
|
-
end
|
40
|
-
}
|
41
|
-
}
|
42
|
-
}
|