arpie 0.0.1

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/COPYING ADDED
@@ -0,0 +1,15 @@
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 ADDED
@@ -0,0 +1,89 @@
1
+ = What's this?
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.
6
+
7
+ * It uses ruby threads on the server side, one per connection.
8
+ * The client is single-threaded.
9
+
10
+ == Source Code
11
+
12
+ Source code is in git[http://git.swordcoast.net/?p=lib/ruby/arpie.git;a=summary].
13
+
14
+ You can contact me via email at elven@swordcoast.net.
15
+
16
+
17
+ == Simple, contrived example: A string reverse server
18
+
19
+ require 'rubygems'
20
+ require 'arpie'
21
+ require 'socket'
22
+
23
+ server = TCPServer.new(51210)
24
+
25
+ e = Arpie::Endpoint.new(Arpie::MarshalProtocol.new)
26
+
27
+ e.handle do |ep, msg|
28
+ msg.reverse
29
+ end
30
+
31
+ e.accept do
32
+ server.accept
33
+ end
34
+
35
+ c = Arpie::Transport.new(Arpie::MarshalProtocol.new)
36
+ c.connect do |transport|
37
+ TCPSocket.new("127.0.0.1", 51210)
38
+ end
39
+
40
+ puts c.request "hi"
41
+ # => "ih"
42
+
43
+ == Advanced, but still simple example: Using Proxy to access remote objects
44
+
45
+ require 'rubygems'
46
+ require 'arpie'
47
+ require 'socket'
48
+
49
+ class MyHandler
50
+ def reverse str
51
+ str.reverse
52
+ end
53
+ end
54
+
55
+ server = TCPServer.new(51210)
56
+
57
+ e = Arpie::ProxyEndpoint.new(Arpie::MarshalProtocol.new)
58
+
59
+ e.handle MyHandler.new
60
+
61
+ e.accept do
62
+ server.accept
63
+ end
64
+
65
+ c = Arpie::Transport.new(Arpie::MarshalProtocol.new)
66
+ c.connect do |transport|
67
+ TCPSocket.new("127.0.0.1", 51210)
68
+ end
69
+ p = Arpie::Proxy.new(c)
70
+
71
+ puts p.reverse "hi"
72
+ # => "ih"
73
+
74
+ == Benchmarks
75
+
76
+ There is a benchmark script included in the git repository (and in the gem
77
+ under tools/). A sample output follows; your milage may vary.
78
+
79
+ user system total real
80
+
81
+ native DRb
82
+ 1 0.000000 0.000000 0.000000 ( 0.000167)
83
+ 1000 0.120000 0.010000 0.130000 ( 0.121834)
84
+
85
+ ruby xmlrpc/server - too slow to benchmark
86
+
87
+ Arpie: proxied MarshalProtocol
88
+ 1 0.000000 0.000000 0.000000 ( 0.000617)
89
+ 1000 0.100000 0.020000 0.120000 ( 0.114573)
@@ -0,0 +1,111 @@
1
+ require "rake"
2
+ require "rake/clean"
3
+ require "rake/gempackagetask"
4
+ require "rake/rdoctask"
5
+ require "fileutils"
6
+ include FileUtils
7
+
8
+ ##############################################################################
9
+ # Configuration
10
+ ##############################################################################
11
+ NAME = "arpie"
12
+ VERS = "0.0.1"
13
+ CLEAN.include ["**/.*.sw?", "pkg", ".config", "rdoc", "coverage"]
14
+ RDOC_OPTS = ["--quiet", "--line-numbers", "--inline-source", '--title', \
15
+ "#{NAME}: A high-performing layered RPC framework. Simple to use, simple to extend.", \
16
+ '--main', 'README']
17
+
18
+ DOCS = ["README", "COPYING"]
19
+
20
+ Rake::RDocTask.new do |rdoc|
21
+ rdoc.rdoc_dir = "rdoc"
22
+ rdoc.options += RDOC_OPTS
23
+ rdoc.rdoc_files.add DOCS + ["doc/*.rdoc", "lib/**/*.rb"]
24
+ end
25
+
26
+ desc "Packages up #{NAME}"
27
+ task :package => [:clean]
28
+
29
+ spec = Gem::Specification.new do |s|
30
+ s.name = NAME
31
+ s.rubyforge_project = "#{NAME}"
32
+ s.version = VERS
33
+ s.platform = Gem::Platform::RUBY
34
+ s.has_rdoc = true
35
+ s.extra_rdoc_files = DOCS + Dir["doc/*.rdoc"]
36
+ s.rdoc_options += RDOC_OPTS + ["--exclude", "^(examples|extras)\/"]
37
+ s.summary = "a synchronous RPC library based on google protobuf"
38
+ s.description = s.summary
39
+ s.author = "Bernhard Stoeckner"
40
+ s.email = "elven@swordcoast.net"
41
+ s.homepage = "http://#{NAME}.elv.es"
42
+ s.executables = []
43
+ s.required_ruby_version = ">= 1.8.4"
44
+ s.files = %w(COPYING README Rakefile) + Dir.glob("{bin,doc,spec,lib,tools,scripts,data}/**/*")
45
+ s.require_path = "lib"
46
+ s.bindir = "bin"
47
+ end
48
+
49
+ Rake::GemPackageTask.new(spec) do |p|
50
+ p.need_tar = true
51
+ p.gem_spec = spec
52
+ end
53
+
54
+ desc "Install #{NAME} gem"
55
+ task :install do
56
+ sh %{rake package}
57
+ sh %{sudo gem1.8 install pkg/#{NAME}-#{VERS}}
58
+ end
59
+
60
+ desc "Regenerate proto classes"
61
+ task :protoc do
62
+ sh %{rprotoc --out=lib/arpie arpie.proto}
63
+ end
64
+
65
+ desc "Install #{NAME} gem without docs"
66
+ task :install_no_docs do
67
+ sh %{rake package}
68
+ sh %{sudo gem1.8 install pkg/#{NAME}-#{VERS} --no-rdoc --no-ri}
69
+ end
70
+
71
+ desc "Uninstall #{NAME} gem"
72
+ task :uninstall => [:clean] do
73
+ sh %{sudo gem1.8 uninstall #{NAME}}
74
+ end
75
+
76
+ desc "Upload #{NAME} gem to rubyforge"
77
+ task :release => [:package] do
78
+ sh %{rubyforge login}
79
+ sh %{rubyforge add_release #{NAME} #{NAME} #{VERS} pkg/#{NAME}-#{VERS}.tgz}
80
+ sh %{rubyforge add_file #{NAME} #{NAME} #{VERS} pkg/#{NAME}-#{VERS}.gem}
81
+ end
82
+
83
+ require "spec/rake/spectask"
84
+
85
+ desc "Run specs with coverage"
86
+ Spec::Rake::SpecTask.new("spec") do |t|
87
+ t.spec_files = FileList["spec/*_spec.rb"]
88
+ t.spec_opts = File.read("spec/spec.opts").split("\n")
89
+ t.rcov_opts = File.read("spec/rcov.opts").split("\n")
90
+ t.rcov = true
91
+ end
92
+
93
+ desc "Run specs without coverage"
94
+ task :default => [:spec_no_cov]
95
+ Spec::Rake::SpecTask.new("spec_no_cov") do |t|
96
+ t.spec_files = FileList["spec/*_spec.rb"]
97
+ t.spec_opts = File.read("spec/spec.opts").split("\n")
98
+ end
99
+
100
+ desc "Run rcov only"
101
+ Spec::Rake::SpecTask.new("rcov") do |t|
102
+ t.rcov_opts = File.read("spec/rcov.opts").split("\n")
103
+ t.spec_opts = File.read("spec/spec.opts").split("\n")
104
+ t.spec_files = FileList["spec/*_spec.rb"]
105
+ t.rcov = true
106
+ end
107
+
108
+ desc "check documentation coverage"
109
+ task :dcov do
110
+ sh "find lib -name '*.rb' | xargs dcov"
111
+ end
@@ -0,0 +1,4 @@
1
+ require 'arpie/protocol'
2
+ require 'arpie/transport'
3
+ require 'arpie/endpoint'
4
+ require 'arpie/proxy'
@@ -0,0 +1,94 @@
1
+ module Arpie
2
+
3
+ # A Endpoint is the server-side part of a RPC setup.
4
+ # It accepts connections (via the acceptor), and handles
5
+ # incoming RPC calls on them.
6
+ #
7
+ # There will be one Thread per connection, so order of
8
+ # execution with multiple threads is not guaranteed.
9
+ class Endpoint
10
+
11
+ # Create a new Endpoint with the given +Protocol+.
12
+ # You will need to define a handler, and an acceptor
13
+ # before the endpoint becomes operational.
14
+ def initialize protocol
15
+ @protocol = protocol
16
+ @clients = []
17
+
18
+ @handler = lambda {|endpoint, message| raise ArgumentError, "No handler defined." }
19
+ end
20
+
21
+ # Provide an acceptor; this will be run in a a loop
22
+ # to get IO objects.
23
+ #
24
+ # Example:
25
+ # listener = TCPServer.new(12345)
26
+ # my_endpoint.accept do
27
+ # listener.accept
28
+ # end
29
+ def accept &acceptor
30
+ @acceptor = acceptor
31
+ Thread.new { _acceptor_thread }
32
+ end
33
+
34
+ # Set a message handler, which is a proc that will receive
35
+ # two parameters: the endpoint, and the message.
36
+ # Its return value will be sent as the reply.
37
+ #
38
+ # Example:
39
+ # my_endpoint.handle do |endpoint, message|
40
+ # puts "Got a message: #{message.inspect}"
41
+ # "ok"
42
+ # end
43
+ def handle &handler
44
+ raise ArgumentError, "need a block" unless block_given?
45
+ @handler = handler
46
+ end
47
+
48
+ private
49
+
50
+ def _handle message
51
+ @handler.call(self, message)
52
+ end
53
+
54
+ def _acceptor_thread
55
+ loop do
56
+ client = @acceptor.call(self)
57
+ @clients << client
58
+ Thread.new { _read_thread(client) }
59
+ end
60
+ end
61
+
62
+ def _read_thread client
63
+ loop do
64
+ break if client.eof?
65
+
66
+ message, answer = nil, nil
67
+ begin
68
+ message = @protocol.read_message(client)
69
+ rescue => e
70
+ $stderr.puts "client went away while reading the message: #{e.to_s}"
71
+ break
72
+ end
73
+
74
+ begin
75
+ answer = _handle(message)
76
+ rescue Exception => e
77
+ $stderr.puts "Error in handler: #{e.message.to_s}"
78
+ $stderr.puts e.backtrace.join("\n")
79
+ $stderr.puts "Returning exception for this call."
80
+ answer = e
81
+ end
82
+
83
+ begin
84
+ @protocol.write_message(client, answer)
85
+ rescue => e
86
+ puts "client went away while writing the answer:: #{e.to_s}"
87
+ break
88
+ end
89
+ end
90
+
91
+ @clients.delete(client)
92
+ end
93
+ end
94
+ end
@@ -0,0 +1,51 @@
1
+ module Arpie
2
+
3
+ # A Protocol converts messages (which are arbitary objects)
4
+ # to a suitable on-the-wire format, and back.
5
+ class Protocol
6
+ private_class_method :new
7
+
8
+ # Read a message from +io+. Block until a message
9
+ # has been received.
10
+ def read_message io
11
+ end
12
+
13
+ # Write a message to +io+.
14
+ def write_message io, message
15
+ end
16
+ end
17
+
18
+ # A sample binary protocol, upon which others can expand.
19
+ # The on the wire format is simply the data, prefixed
20
+ # with data.size.
21
+ class SizedProtocol < Protocol
22
+ def initialize
23
+ @max_message_size = 1024 * 1024
24
+ end
25
+
26
+ def read_message io
27
+ sz = io.read(8)
28
+ expect = sz.unpack("Q")[0]
29
+ data = io.read(expect)
30
+ end
31
+
32
+ def write_message io, message
33
+ io.write([message.size, message].pack("Qa*"))
34
+ end
35
+ end
36
+
37
+ # A procotol that simply Marshals all data sent over
38
+ # this protocol. Served as an example, but a viable
39
+ # choice for ruby-only production code.
40
+ class MarshalProtocol < SizedProtocol
41
+ public_class_method :new
42
+
43
+ def read_message io
44
+ Marshal.load super(io)
45
+ end
46
+
47
+ def write_message io, message
48
+ super io, Marshal.dump(message)
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,46 @@
1
+ module Arpie
2
+
3
+ # The RPC call encapsulation used by ProxyEndpoint and Proxy.
4
+ class ProxyCall < Struct.new(:method, :argv); end
5
+
6
+ # A Endpoint which supports arbitary objects as handlers,
7
+ # instead of a proc.
8
+ #
9
+ # Note that this will only export public instance method
10
+ # of the class as they are defined.
11
+ class ProxyEndpoint < Endpoint
12
+ def handle handler
13
+ @handler = handler
14
+ @interface = @handler.class.public_instance_methods(false)
15
+ end
16
+
17
+ private
18
+
19
+ def _handle message
20
+ @interface.index(message.method.to_s) or raise NoMethodError,
21
+ "Unknown method."
22
+ @handler.send(message.method, *message.argv)
23
+ end
24
+ end
25
+
26
+ # A Proxy is a wrapper around a transport, which transparently tunnels
27
+ # method calls to the remote ProxyEndpoint.
28
+ class Proxy
29
+
30
+ # Create a new Proxy.
31
+ def initialize transport
32
+ @transport = transport
33
+ end
34
+
35
+ def method_missing method, *argv # :nodoc:
36
+ call = ProxyCall.new(method, argv)
37
+ ret = @transport.request(call)
38
+ case ret
39
+ when Exception
40
+ raise ret
41
+ else
42
+ ret
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,37 @@
1
+ module Arpie
2
+
3
+ # A Transport is a connection manager, and acts as the
4
+ # glue between a user-defined medium (for example, a TCP
5
+ # socket), and a protocol.
6
+ #
7
+ # See README for examples.
8
+ class Transport
9
+ attr_reader :protocol
10
+
11
+ def initialize protocol
12
+ @protocol = protocol
13
+ @io = nil
14
+ end
15
+
16
+ # Provide a connector block, which will be called
17
+ # each time a connection is needed.
18
+ # Set +connect_immediately+ to true to connect
19
+ # immediately, instead on the first message.
20
+ def connect connect_immediately = false, &connector
21
+ @connector = connector
22
+ _connect if connect_immediately
23
+ end
24
+
25
+ # Send a message and receive a reply.
26
+ def request message
27
+ _connect
28
+ @protocol.write_message(@io, message)
29
+ @protocol.read_message(@io)
30
+ end
31
+
32
+ private
33
+ def _connect
34
+ @io ||= @connector.call(self)
35
+ end
36
+ end
37
+ end
File without changes
File without changes
@@ -0,0 +1,57 @@
1
+ require 'rubygems'
2
+ require 'socket'
3
+ require 'arpie'
4
+ require 'benchmark'
5
+ require 'drb'
6
+ require 'xmlrpc/server'
7
+ require 'xmlrpc/client'
8
+
9
+ class Wrap
10
+ def reverse x
11
+ x.reverse
12
+ end
13
+ end
14
+
15
+ include Arpie
16
+
17
+ server = TCPServer.new(51210)
18
+
19
+ endpoint = ProxyEndpoint.new MarshalProtocol.new
20
+ endpoint.handle Wrap.new
21
+
22
+ endpoint.accept do
23
+ server.accept
24
+ end
25
+
26
+ $transport = Transport.new MarshalProtocol.new
27
+ $transport.connect(false) do |transport|
28
+ TCPSocket.new("127.0.0.1", 51210)
29
+ end
30
+ $proxy = Proxy.new $transport
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 "ruby xmlrpc/server - too slow to benchmark"
44
+ #server = XMLRPC::Server.new(51211, "127.0.0.1", 4, nil, false)
45
+ #server.add_handler(XMLRPC::iPIMethods("wrap"), Wrap.new)
46
+ #server_thread = Thread.new { server.serve }
47
+ #client = XMLRPC::Client.new( "127.0.0.1", "/", 51211)
48
+ #b.report(" 1") { 1.times { client.call("wrap.reverse", "benchmark") } }
49
+ #b.report("1000") { 1000.times { client.call("wrap.reverse", "benchmark") } }
50
+ #server.shutdown
51
+ #server_thread.join
52
+
53
+ puts ""
54
+ puts "Arpie: proxied MarshalProtocol"
55
+ b.report(" 1") { 1.times { $proxy.reverse "benchmark" } }
56
+ b.report("1000") { 1000.times { $proxy.reverse "benchmark" } }
57
+ }
metadata ADDED
@@ -0,0 +1,73 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: arpie
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Bernhard Stoeckner
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-01-18 00:00:00 +01:00
13
+ default_executable:
14
+ dependencies: []
15
+
16
+ description: a synchronous RPC library based on google protobuf
17
+ email: elven@swordcoast.net
18
+ executables: []
19
+
20
+ extensions: []
21
+
22
+ extra_rdoc_files:
23
+ - README
24
+ - COPYING
25
+ files:
26
+ - COPYING
27
+ - README
28
+ - Rakefile
29
+ - spec/spec.opts
30
+ - spec/rcov.opts
31
+ - lib/arpie.rb
32
+ - lib/arpie
33
+ - lib/arpie/protocol.rb
34
+ - lib/arpie/transport.rb
35
+ - lib/arpie/endpoint.rb
36
+ - lib/arpie/proxy.rb
37
+ - tools/benchmark.rb
38
+ has_rdoc: true
39
+ homepage: http://arpie.elv.es
40
+ post_install_message:
41
+ rdoc_options:
42
+ - --quiet
43
+ - --line-numbers
44
+ - --inline-source
45
+ - --title
46
+ - "arpie: A high-performing layered RPC framework. Simple to use, simple to extend."
47
+ - --main
48
+ - README
49
+ - --exclude
50
+ - ^(examples|extras)/
51
+ require_paths:
52
+ - lib
53
+ required_ruby_version: !ruby/object:Gem::Requirement
54
+ requirements:
55
+ - - ">="
56
+ - !ruby/object:Gem::Version
57
+ version: 1.8.4
58
+ version:
59
+ required_rubygems_version: !ruby/object:Gem::Requirement
60
+ requirements:
61
+ - - ">="
62
+ - !ruby/object:Gem::Version
63
+ version: "0"
64
+ version:
65
+ requirements: []
66
+
67
+ rubyforge_project: arpie
68
+ rubygems_version: 1.3.0
69
+ signing_key:
70
+ specification_version: 2
71
+ summary: a synchronous RPC library based on google protobuf
72
+ test_files: []
73
+