arpie 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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
+