bertrem 0.0.4

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/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
+