bertrem 0.0.4

Sign up to get free protection for your applications and to get access to all the features.
data/LICENSE ADDED
@@ -0,0 +1,23 @@
1
+ Copyright (c) 2009 Benjamin Black
2
+
3
+ Derived in part from work that is
4
+ Copyright (c) 2009 Tom Preston-Werner
5
+
6
+ Permission is hereby granted, free of charge, to any person obtaining
7
+ a copy of this software and associated documentation files (the
8
+ "Software"), to deal in the Software without restriction, including
9
+ without limitation the rights to use, copy, modify, merge, publish,
10
+ distribute, sublicense, and/or sell copies of the Software, and to
11
+ permit persons to whom the Software is furnished to do so, subject to
12
+ the following conditions:
13
+
14
+ The above copyright notice and this permission notice shall be
15
+ included in all copies or substantial portions of the Software.
16
+
17
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
18
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
19
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
20
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
21
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
22
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
23
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,120 @@
1
+ BERTREM
2
+ ======
3
+
4
+ By Benjamin Black (b@b3k.us)
5
+
6
+ BERTREM is a BERT-RPC client and server implementation that uses an EventMachine server to accept incoming connections, and then delegates the request to loadable Ruby handlers. BERTREM is derived from [Ernie](http://github.com/mojombo/ernie), by Tom Preston-Warner.
7
+
8
+ See the full BERT-RPC specification at [bert-rpc.org](http://bert-rpc.org).
9
+
10
+ BERTREM currently supports the following BERT-RPC features:
11
+
12
+ * `call` requests
13
+ * `cast` requests
14
+
15
+
16
+ Installation
17
+ ------------
18
+
19
+ $ gem install bertrem -s http://gemcutter.org
20
+
21
+
22
+ Example Handler
23
+ ---------------
24
+
25
+ A simple Ruby module for use in a BERTREM server:
26
+
27
+ require 'bertrem'
28
+
29
+ module Calc
30
+ def add(a, b)
31
+ a + b
32
+ end
33
+ end
34
+
35
+
36
+ Example Server
37
+ --------------
38
+
39
+ A simple BERTREM server using the Calc module defined above:
40
+
41
+ require 'eventmachine'
42
+ require 'bertrem'
43
+
44
+ EM.run {
45
+ BERTREM::Server.expose(:calc, Calc)
46
+ svc = BERTREM::Server.start('localhost', 9999)
47
+ }
48
+
49
+
50
+ Logging
51
+ -------
52
+
53
+ You can have logging sent to a file by adding these lines to your handler:
54
+
55
+ logfile('/var/log/bertrem.log')
56
+ loglevel(Logger::INFO)
57
+
58
+ This will log startup info, requests, and error messages to the log. Choosing
59
+ Logger::DEBUG will include the response (be careful, doing this can generate
60
+ very large log files).
61
+
62
+
63
+ Using the BERTRPC gem to make calls to BERTREM
64
+ ---------------------------------------------__
65
+
66
+ The BERTREM client supports persistent connections, so you can send multiple requests over the same service connection and responses will return in the order the requests were sent:
67
+
68
+ require 'eventmachine'
69
+ require 'bertrem'
70
+
71
+ EM.run {
72
+ client = BERTREM::Client.service('localhost', 9999, true)
73
+ rpc = client.call.calc.add(6, 2)
74
+ rpc.callback { |res|
75
+ puts "Got response! -> #{res}"
76
+ }
77
+
78
+ rpc = client.call.calc.add(2, 2)
79
+ rpc.callback { |res|
80
+ puts "Got response! -> #{res}"
81
+ }
82
+ }
83
+ # Got response! -> 8
84
+ # Got response! -> 4
85
+
86
+ Alternatively, you can make BERT-RPC calls from Ruby with the [BERTRPC gem](http://github.com/mojombo/bertrpc):
87
+
88
+ require 'bertrpc'
89
+
90
+ svc = BERTRPC::Service.new('localhost', 8000)
91
+ svc.call.calc.add(1, 2)
92
+ # => 3
93
+
94
+
95
+ Contribute
96
+ ----------
97
+
98
+ If you'd like to hack on BERTREM, start by forking my repo on GitHub:
99
+
100
+ http://github.com/b/bertrem
101
+
102
+ To get all of the dependencies, install the gem first
103
+
104
+ The best way to get your changes merged back into core is as follows:
105
+
106
+ 1. Clone down your fork
107
+ 1. Create a topic branch to contain your change
108
+ 1. Hack away
109
+ 1. Add tests and make sure everything still passes by running `rake`
110
+ 1. If you are adding new functionality, document it in the README.md
111
+ 1. Do not change the version number, I will do that on my end
112
+ 1. If necessary, rebase your commits into logical chunks, without errors
113
+ 1. Push the branch up to GitHub
114
+ 1. Send me (b) a pull request for your branch
115
+
116
+
117
+ Copyright
118
+ ---------
119
+
120
+ Copyright (c) 2009 Benjamin Black. See LICENSE for details.
@@ -0,0 +1,24 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+
4
+ begin
5
+ require 'jeweler'
6
+ Jeweler::Tasks.new do |gem|
7
+ gem.name = "bertrem"
8
+ gem.summary = %Q{BERTREM is a Ruby EventMachine BERT-RPC client and server library.}
9
+ gem.email = "b@b3k.us"
10
+ gem.homepage = "http://github.com/b/bertrem"
11
+ gem.authors = ["Benjamin Black"]
12
+ gem.add_dependency('bertrpc', '>= 1.1.2', '< 2.0.0')
13
+ gem.add_dependency('eventmachine')
14
+ # gem is a Gem::Specification...
15
+ # see http://www.rubygems.org/read/chapter/20 for additional settings
16
+ end
17
+
18
+ rescue LoadError
19
+ puts "Jeweler (or a dependency) not available. Install it with: sudo gem install jeweler"
20
+ end
21
+
22
+ task :console do
23
+ exec('irb -Ilib -rbertrpc')
24
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.0.4
@@ -0,0 +1,52 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{bertem}
8
+ s.version = "0.0.4"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Benjamin Black"]
12
+ s.date = %q{2009-12-30}
13
+ s.email = %q{b@b3k.us}
14
+ s.extra_rdoc_files = [
15
+ "LICENSE",
16
+ "README.md"
17
+ ]
18
+ s.files = [
19
+ "LICENSE",
20
+ "README.md",
21
+ "Rakefile",
22
+ "VERSION",
23
+ "bertem.gemspec",
24
+ "lib/bertem.rb",
25
+ "lib/bertem/action.rb",
26
+ "lib/bertem/client.rb",
27
+ "lib/bertem/mod.rb",
28
+ "lib/bertem/server.rb"
29
+ ]
30
+ s.homepage = %q{http://github.com/b/bertem}
31
+ s.rdoc_options = ["--charset=UTF-8"]
32
+ s.require_paths = ["lib"]
33
+ s.rubygems_version = %q{1.3.5}
34
+ s.summary = %q{BERTEM is a Ruby EventMachine BERT-RPC client and server library.}
35
+
36
+ if s.respond_to? :specification_version then
37
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
38
+ s.specification_version = 3
39
+
40
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
41
+ s.add_runtime_dependency(%q<bertrpc>, [">= 1.1.2", "< 2.0.0"])
42
+ s.add_runtime_dependency(%q<eventmachine>, [">= 0"])
43
+ else
44
+ s.add_dependency(%q<bertrpc>, [">= 1.1.2", "< 2.0.0"])
45
+ s.add_dependency(%q<eventmachine>, [">= 0"])
46
+ end
47
+ else
48
+ s.add_dependency(%q<bertrpc>, [">= 1.1.2", "< 2.0.0"])
49
+ s.add_dependency(%q<eventmachine>, [">= 0"])
50
+ end
51
+ end
52
+
@@ -0,0 +1,18 @@
1
+ require 'logger'
2
+ require 'bertrpc'
3
+ require 'eventmachine'
4
+
5
+ require 'bertrem/action'
6
+ require 'bertrem/mod'
7
+ require 'bertrem/client'
8
+ require 'bertrem/server'
9
+
10
+ module BERTREM
11
+ def self.version
12
+ File.read(File.join(File.dirname(__FILE__), *%w[.. VERSION])).chomp
13
+ rescue
14
+ 'unknown'
15
+ end
16
+
17
+ VERSION = self.version
18
+ end
@@ -0,0 +1,35 @@
1
+ require 'eventmachine'
2
+ require 'bertrpc'
3
+
4
+ module BERTRPC
5
+ class Action
6
+
7
+ undef_method :execute
8
+ undef_method :write
9
+ undef_method :transaction
10
+ undef_method :connect_to
11
+
12
+ def execute
13
+ transaction(encode_ruby_request(t[@req.kind, @mod, @fun, @args]))
14
+ @svc.requests.unshift(EM::DefaultDeferrable.new).first
15
+ end
16
+
17
+ def write(bert)
18
+ @svc.send_data([bert.length].pack("N"))
19
+ @svc.send_data(bert)
20
+ end
21
+
22
+ def transaction(bert_request)
23
+ if @req.options
24
+ if @req.options[:cache] && @req.options[:cache][0] == :validation
25
+ token = @req.options[:cache][1]
26
+ info_bert = encode_ruby_request([:info, :cache, [:validation, token]])
27
+ write(info_bert)
28
+ end
29
+ end
30
+
31
+ write(bert_request)
32
+ end
33
+
34
+ end
35
+ end
@@ -0,0 +1,105 @@
1
+ require 'bertrpc'
2
+ require 'logger'
3
+ require 'eventmachine'
4
+
5
+ module BERTREM
6
+ # NOTE: ernie (and all other BERTRPC servers?) closes connections after
7
+ # responding, so we can't send multiple requests per connection.
8
+ # Hence, the default for persistent is false. If you are dealing
9
+ # with a more sophisticated server that supports more than one
10
+ # request per connection, call BERTREM.service with
11
+ # persistent = true and it should Just Work.
12
+
13
+ class Client < EventMachine::Connection
14
+ include BERTRPC::Encodes
15
+
16
+ attr_accessor :requests
17
+
18
+ class Request
19
+ attr_accessor :kind, :options
20
+
21
+ def initialize(svc, kind, options)
22
+ @svc = svc
23
+ @kind = kind
24
+ @options = options
25
+ end
26
+
27
+ def method_missing(cmd, *args)
28
+ BERTRPC::Mod.new(@svc, self, cmd)
29
+ end
30
+
31
+ end
32
+
33
+ class << self
34
+ attr_accessor :persistent
35
+ end
36
+
37
+ self.persistent = false
38
+
39
+ def self.service(host, port, persistent = false, timeout = nil)
40
+ self.persistent = persistent
41
+ c = EM.connect(host, port, self)
42
+ c.pending_connect_timeout = timeout if timeout
43
+ c
44
+ end
45
+
46
+ def post_init
47
+ @requests = []
48
+ end
49
+
50
+ def unbind
51
+ super
52
+ (@requests || []).each {|r| r.fail}
53
+ end
54
+
55
+ def persistent
56
+ Client.persistent
57
+ end
58
+
59
+ def receive_data(bert_response)
60
+ # This needs to be much more intelligent (retain a buffer, append new response data
61
+ # to the buffer, remember the length of the msg it is working with if it is incomplete,
62
+ # etc.)
63
+ while bert_response.length > 4 do
64
+ begin
65
+ raise BERTRPC::ProtocolError.new(BERTRPC::ProtocolError::NO_HEADER) unless bert_response.length > 4
66
+ len = bert_response.slice!(0..3).unpack('N').first # just here to strip the length header
67
+ raise BERTRPC::ProtocolError.new(BERTRPC::ProtocolError::NO_DATA) unless bert_response.length > 0
68
+ rescue Exception => e
69
+ log "Bad BERT message: #{e.message}\n#{e.backtrace.inspect}\n"
70
+ end
71
+
72
+ bert = bert_response.slice!(0..(len - 1))
73
+ @requests.pop.succeed(decode_bert_response(bert))
74
+ unless persistent
75
+ close_connection
76
+ break
77
+ end
78
+ end
79
+ end
80
+
81
+ def call(options = nil)
82
+ verify_options(options)
83
+ Request.new(self, :call, options)
84
+ end
85
+
86
+ def cast(options = nil)
87
+ verify_options(options)
88
+ Request.new(self, :cast, options)
89
+ end
90
+
91
+ def verify_options(options)
92
+ if options
93
+ if cache = options[:cache]
94
+ unless cache[0] == :validation && cache[1].is_a?(String)
95
+ raise BERTRPC::InvalidOption.new("Valid :cache args are [:validation, String]")
96
+ end
97
+ else
98
+ raise BERTRPC::InvalidOption.new("Valid options are :cache")
99
+ end
100
+ end
101
+ end
102
+
103
+ end
104
+
105
+ end
@@ -0,0 +1,18 @@
1
+ module BERTREM
2
+ class Mod
3
+
4
+ attr_accessor :name, :funs
5
+
6
+ def initialize(name)
7
+ self.name = name
8
+ self.funs = {}
9
+ end
10
+
11
+ def fun(name, block)
12
+ raise TypeError, "block required" if block.nil?
13
+ self.funs[name] = block
14
+ end
15
+
16
+ end
17
+
18
+ end
@@ -0,0 +1,164 @@
1
+ require 'bert'
2
+ require 'logger'
3
+ require 'eventmachine'
4
+
5
+ module BERTREM
6
+ class Server < EventMachine::Connection
7
+ include BERTRPC::Encodes
8
+
9
+ # This class derived from Ernie/ernie.rb
10
+
11
+ class << self
12
+ attr_accessor :mods, :current_mod, :log
13
+ end
14
+
15
+ self.mods = {}
16
+ self.current_mod = nil
17
+ self.log = Logger.new(STDOUT)
18
+ self.log.level = Logger::INFO
19
+
20
+ def self.start(host, port)
21
+ EM.start_server(host, port, self)
22
+ end
23
+
24
+ # Record a module.
25
+ # +name+ is the module Symbol
26
+ # +block+ is the Block containing function definitions
27
+ #
28
+ # Returns nothing
29
+ def self.mod(name, block)
30
+ m = Mod.new(name)
31
+ self.current_mod = m
32
+ self.mods[name] = m
33
+ block.call
34
+ end
35
+
36
+ # Record a function.
37
+ # +name+ is the function Symbol
38
+ # +block+ is the Block to associate
39
+ #
40
+ # Returns nothing
41
+ def self.fun(name, block)
42
+ self.current_mod.fun(name, block)
43
+ end
44
+
45
+ # Expose all public methods in a Ruby module:
46
+ # +name+ is the ernie module Symbol
47
+ # +mixin+ is the ruby module whose public methods are exposed
48
+ #
49
+ # Returns nothing
50
+ def self.expose(name, mixin)
51
+ context = Object.new
52
+ context.extend mixin
53
+ self.mod(name, lambda {
54
+ mixin.public_instance_methods.each do |meth|
55
+ self.fun(meth.to_sym, context.method(meth))
56
+ end
57
+ })
58
+ context
59
+ end
60
+
61
+ # Set the logfile to given path.
62
+ # +file+ is the String path to the logfile
63
+ #
64
+ # Returns nothing
65
+ def self.logfile(file)
66
+ self.log = Logger.new(file)
67
+ end
68
+
69
+ # Set the log level.
70
+ # +level+ is the Logger level (Logger::WARN, etc)
71
+ #
72
+ # Returns nothing
73
+ def self.loglevel(level)
74
+ self.log.level = level
75
+ end
76
+
77
+ # Dispatch the request to the proper mod:fun.
78
+ # +mod+ is the module Symbol
79
+ # +fun+ is the function Symbol
80
+ # +args+ is the Array of arguments
81
+ #
82
+ # Returns the Ruby object response
83
+ def self.dispatch(mod, fun, args)
84
+ mods[mod] || raise(ServerError.new("No such module '#{mod}'"))
85
+ mods[mod].funs[fun] || raise(ServerError.new("No such function '#{mod}:#{fun}'"))
86
+ mods[mod].funs[fun].call(*args)
87
+ end
88
+
89
+ # Write the given Ruby object to the wire as a BERP.
90
+ # +output+ is the IO on which to write
91
+ # +ruby+ is the Ruby object to encode
92
+ #
93
+ # Returns nothing
94
+ def write_berp(ruby)
95
+ data = BERT.encode(ruby)
96
+ send_data([data.length].pack("N"))
97
+ send_data(data)
98
+ end
99
+
100
+ def post_init
101
+ Server.log.info("(#{Process.pid}) Starting")
102
+ Server.log.debug(Server.mods.inspect)
103
+ end
104
+
105
+ # Receive data on the connection.
106
+ #
107
+ def receive_data(data)
108
+ # This needs to be much more intelligent (retain a buffer, append new request data
109
+ # to the buffer, remember the length of the msg it is working with if it is incomplete,
110
+ # etc.)
111
+ while data.length > 4 do
112
+ raw = data.slice!(0..3)
113
+ puts "Could not find BERP length header. Weird, huh?" unless raw
114
+ packet_size = raw.unpack('N').first
115
+ puts "Could not understand BERP packet length. What gives?" unless packet_size
116
+ bert = data.slice!(0..(packet_size - 1))
117
+ iruby = BERT.decode(bert)
118
+
119
+ unless iruby
120
+ Server.log.info("(#{Process.pid}) No Ruby in this here packet. On to the next one...")
121
+ next
122
+ end
123
+
124
+ if iruby.size == 4 && iruby[0] == :call
125
+ mod, fun, args = iruby[1..3]
126
+ Server.log.info("-> " + iruby.inspect)
127
+ begin
128
+ res = Server.dispatch(mod, fun, args)
129
+ oruby = t[:reply, res]
130
+ Server.log.debug("<- " + oruby.inspect)
131
+ write_berp(oruby)
132
+ rescue ServerError => e
133
+ oruby = t[:error, t[:server, 0, e.class.to_s, e.message, e.backtrace]]
134
+ Server.log.error("<- " + oruby.inspect)
135
+ Server.log.error(e.backtrace.join("\n"))
136
+ write_berp(oruby)
137
+ rescue Object => e
138
+ oruby = t[:error, t[:user, 0, e.class.to_s, e.message, e.backtrace]]
139
+ Server.log.error("<- " + oruby.inspect)
140
+ Server.log.error(e.backtrace.join("\n"))
141
+ write_berp(oruby)
142
+ end
143
+ elsif iruby.size == 4 && iruby[0] == :cast
144
+ mod, fun, args = iruby[1..3]
145
+ Server.log.info("-> " + [:cast, mod, fun, args].inspect)
146
+ begin
147
+ Server.dispatch(mod, fun, args)
148
+ rescue Object => e
149
+ # ignore
150
+ end
151
+ write_berp(t[:noreply])
152
+ else
153
+ Server.log.error("-> " + iruby.inspect)
154
+ oruby = t[:error, t[:server, 0, "Invalid request: #{iruby.inspect}"]]
155
+ Server.log.error("<- " + oruby.inspect)
156
+ write_berp(oruby)
157
+ end
158
+ end
159
+ end
160
+ end
161
+
162
+ end
163
+
164
+ class BERTREM::ServerError < StandardError; end
metadata ADDED
@@ -0,0 +1,87 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: bertrem
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.4
5
+ platform: ruby
6
+ authors:
7
+ - Benjamin Black
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2010-01-01 00:00:00 -08:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: bertrpc
17
+ type: :runtime
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: 1.1.2
24
+ - - <
25
+ - !ruby/object:Gem::Version
26
+ version: 2.0.0
27
+ version:
28
+ - !ruby/object:Gem::Dependency
29
+ name: eventmachine
30
+ type: :runtime
31
+ version_requirement:
32
+ version_requirements: !ruby/object:Gem::Requirement
33
+ requirements:
34
+ - - ">="
35
+ - !ruby/object:Gem::Version
36
+ version: "0"
37
+ version:
38
+ description:
39
+ email: b@b3k.us
40
+ executables: []
41
+
42
+ extensions: []
43
+
44
+ extra_rdoc_files:
45
+ - LICENSE
46
+ - README.md
47
+ files:
48
+ - LICENSE
49
+ - README.md
50
+ - Rakefile
51
+ - VERSION
52
+ - bertem.gemspec
53
+ - lib/bertrem.rb
54
+ - lib/bertrem/action.rb
55
+ - lib/bertrem/client.rb
56
+ - lib/bertrem/mod.rb
57
+ - lib/bertrem/server.rb
58
+ has_rdoc: true
59
+ homepage: http://github.com/b/bertrem
60
+ licenses: []
61
+
62
+ post_install_message:
63
+ rdoc_options:
64
+ - --charset=UTF-8
65
+ require_paths:
66
+ - lib
67
+ required_ruby_version: !ruby/object:Gem::Requirement
68
+ requirements:
69
+ - - ">="
70
+ - !ruby/object:Gem::Version
71
+ version: "0"
72
+ version:
73
+ required_rubygems_version: !ruby/object:Gem::Requirement
74
+ requirements:
75
+ - - ">="
76
+ - !ruby/object:Gem::Version
77
+ version: "0"
78
+ version:
79
+ requirements: []
80
+
81
+ rubyforge_project:
82
+ rubygems_version: 1.3.5
83
+ signing_key:
84
+ specification_version: 3
85
+ summary: BERTREM is a Ruby EventMachine BERT-RPC client and server library.
86
+ test_files: []
87
+