schleyfox-ernie 2.2.1

Sign up to get free protection for your applications and to get access to all the features.
data/examples/ext.rb ADDED
@@ -0,0 +1,39 @@
1
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
2
+ require 'ernie'
3
+
4
+ module Ext
5
+ # Add two numbers together
6
+ def add(a, b)
7
+ a + b
8
+ end
9
+
10
+ def fib(n)
11
+ if n == 0 || n == 1
12
+ 1
13
+ else
14
+ fib(n - 1) + fib(n - 2)
15
+ end
16
+ end
17
+
18
+ def shadow(x)
19
+ "ruby"
20
+ end
21
+
22
+ # Return the given number of bytes
23
+ def bytes(n)
24
+ 'x' * n
25
+ end
26
+
27
+ # Sleep for +sec+ and then return :ok
28
+ def slow(sec)
29
+ sleep(sec)
30
+ :ok
31
+ end
32
+
33
+ # Throw an error
34
+ def error
35
+ raise "abandon hope!"
36
+ end
37
+ end
38
+
39
+ Ernie.expose(:ext, Ext)
data/examples/nat.erl ADDED
@@ -0,0 +1,12 @@
1
+ -module(nat).
2
+ -export([add/2, fib/1, die/1]).
3
+
4
+ add(A, B) ->
5
+ A + B.
6
+
7
+ fib(0) -> 1;
8
+ fib(1) -> 1;
9
+ fib(N) when N > 1 -> fib(N - 1) + fib(N - 2).
10
+
11
+ die(X) ->
12
+ X = 10.
data/ext/Makefile ADDED
@@ -0,0 +1,2 @@
1
+ install:
2
+ erlc -o ../ebin ../elib/*.erl
data/ext/extconf.rb ADDED
@@ -0,0 +1 @@
1
+ # does nothing, Makefile is handwritten
data/lib/ernie.rb ADDED
@@ -0,0 +1,225 @@
1
+ require 'rubygems'
2
+ require 'bert'
3
+ require 'logger'
4
+
5
+ class Ernie
6
+ class << self
7
+ attr_accessor :mods, :current_mod, :log
8
+ attr_accessor :auto_start
9
+ attr_accessor :count, :virgin_procline
10
+ end
11
+
12
+ self.count = 0
13
+ self.virgin_procline = $0
14
+ self.mods = {}
15
+ self.current_mod = nil
16
+ self.log = Logger.new(STDOUT)
17
+ self.log.level = Logger::FATAL
18
+ self.auto_start = true
19
+
20
+ # Record a module.
21
+ # +name+ is the module Symbol
22
+ # +block+ is the Block containing function definitions
23
+ #
24
+ # Returns nothing
25
+ def self.mod(name, block)
26
+ m = Mod.new(name)
27
+ self.current_mod = m
28
+ self.mods[name] = m
29
+ block.call
30
+ end
31
+
32
+ # Record a function.
33
+ # +name+ is the function Symbol
34
+ # +block+ is the Block to associate
35
+ #
36
+ # Returns nothing
37
+ def self.fun(name, block)
38
+ self.current_mod.fun(name, block)
39
+ end
40
+
41
+ # Expose all public methods in a Ruby module:
42
+ # +name+ is the ernie module Symbol
43
+ # +mixin+ is the ruby module whose public methods are exposed
44
+ #
45
+ # Returns nothing
46
+ def self.expose(name, mixin)
47
+ context = Object.new
48
+ context.extend mixin
49
+ mod(name, lambda {
50
+ mixin.public_instance_methods.each do |meth|
51
+ fun(meth.to_sym, context.method(meth))
52
+ end
53
+ })
54
+ context
55
+ end
56
+
57
+ # Set the logfile to given path.
58
+ # +file+ is the String path to the logfile
59
+ #
60
+ # Returns nothing
61
+ def self.logfile(file)
62
+ self.log = Logger.new(file)
63
+ end
64
+
65
+ # Set the log level.
66
+ # +level+ is the Logger level (Logger::WARN, etc)
67
+ #
68
+ # Returns nothing
69
+ def self.loglevel(level)
70
+ self.log.level = level
71
+ end
72
+
73
+ # Dispatch the request to the proper mod:fun.
74
+ # +mod+ is the module Symbol
75
+ # +fun+ is the function Symbol
76
+ # +args+ is the Array of arguments
77
+ #
78
+ # Returns the Ruby object response
79
+ def self.dispatch(mod, fun, args)
80
+ self.mods[mod] || raise(ServerError.new("No such module '#{mod}'"))
81
+ self.mods[mod].funs[fun] || raise(ServerError.new("No such function '#{mod}:#{fun}'"))
82
+ self.mods[mod].funs[fun].call(*args)
83
+ end
84
+
85
+ # Read the length header from the wire.
86
+ # +input+ is the IO from which to read
87
+ #
88
+ # Returns the size Integer if one was read
89
+ # Returns nil otherwise
90
+ def self.read_4(input)
91
+ raw = input.read(4)
92
+ return nil unless raw
93
+ raw.unpack('N').first
94
+ end
95
+
96
+ # Read a BERP from the wire and decode it to a Ruby object.
97
+ # +input+ is the IO from which to read
98
+ #
99
+ # Returns a Ruby object if one could be read
100
+ # Returns nil otherwise
101
+ def self.read_berp(input)
102
+ packet_size = self.read_4(input)
103
+ return nil unless packet_size
104
+ bert = input.read(packet_size)
105
+ BERT.decode(bert)
106
+ end
107
+
108
+ # Write the given Ruby object to the wire as a BERP.
109
+ # +output+ is the IO on which to write
110
+ # +ruby+ is the Ruby object to encode
111
+ #
112
+ # Returns nothing
113
+ def self.write_berp(output, ruby)
114
+ data = BERT.encode(ruby)
115
+ output.write([data.length].pack("N"))
116
+ output.write(data)
117
+ end
118
+
119
+ # Start the processing loop.
120
+ #
121
+ # Loops forever
122
+ def self.start
123
+ self.procline('starting')
124
+ self.log.info("(#{Process.pid}) Starting")
125
+ self.log.debug(self.mods.inspect)
126
+
127
+ input = IO.new(3)
128
+ output = IO.new(4)
129
+ input.sync = true
130
+ output.sync = true
131
+
132
+ loop do
133
+ self.procline('waiting')
134
+ iruby = self.read_berp(input)
135
+ self.count += 1
136
+
137
+ unless iruby
138
+ puts "Could not read BERP length header. Ernie server may have gone away. Exiting now."
139
+ self.log.info("(#{Process.pid}) Could not read BERP length header. Ernie server may have gone away. Exiting now.")
140
+ exit!
141
+ end
142
+
143
+ if iruby.size == 4 && iruby[0] == :call
144
+ mod, fun, args = iruby[1..3]
145
+ self.procline("#{mod}:#{fun}(#{args})")
146
+ self.log.info("-> " + iruby.inspect)
147
+ begin
148
+ res = self.dispatch(mod, fun, args)
149
+ oruby = t[:reply, res]
150
+ self.log.debug("<- " + oruby.inspect)
151
+ write_berp(output, oruby)
152
+ rescue ServerError => e
153
+ oruby = t[:error, t[:server, 0, e.class.to_s, e.message, e.backtrace]]
154
+ self.log.error("<- " + oruby.inspect)
155
+ self.log.error(e.backtrace.join("\n"))
156
+ write_berp(output, oruby)
157
+ rescue Object => e
158
+ oruby = t[:error, t[:user, 0, e.class.to_s, e.message, e.backtrace]]
159
+ self.log.error("<- " + oruby.inspect)
160
+ self.log.error(e.backtrace.join("\n"))
161
+ write_berp(output, oruby)
162
+ end
163
+ elsif iruby.size == 4 && iruby[0] == :cast
164
+ mod, fun, args = iruby[1..3]
165
+ self.procline("#{mod}:#{fun}(#{args})")
166
+ self.log.info("-> " + [:cast, mod, fun, args].inspect)
167
+ begin
168
+ self.dispatch(mod, fun, args)
169
+ rescue Object => e
170
+ # ignore
171
+ end
172
+ write_berp(output, t[:noreply])
173
+ else
174
+ self.procline("invalid request")
175
+ self.log.error("-> " + iruby.inspect)
176
+ oruby = t[:error, t[:server, 0, "Invalid request: #{iruby.inspect}"]]
177
+ self.log.error("<- " + oruby.inspect)
178
+ write_berp(output, oruby)
179
+ end
180
+ end
181
+ end
182
+
183
+ def self.procline(msg)
184
+ $0 = "ernie handler #{VERSION} (ruby) - #{self.virgin_procline} - [#{self.count}] #{msg}"[0..159]
185
+ end
186
+
187
+ def self.version
188
+ yml = YAML.load(File.read(File.join(File.dirname(__FILE__), *%w[.. VERSION.yml])))
189
+ "#{yml[:major]}.#{yml[:minor]}.#{yml[:patch]}"
190
+ rescue
191
+ 'unknown'
192
+ end
193
+
194
+ VERSION = self.version
195
+ end
196
+
197
+ class Ernie::ServerError < StandardError; end
198
+
199
+ class Ernie::Mod
200
+ attr_accessor :name, :funs
201
+
202
+ def initialize(name)
203
+ self.name = name
204
+ self.funs = {}
205
+ end
206
+
207
+ def fun(name, block)
208
+ raise TypeError, "block required" if block.nil?
209
+ self.funs[name] = block
210
+ end
211
+ end
212
+
213
+ # Root level calls
214
+
215
+ def logfile(name)
216
+ Ernie.logfile(name)
217
+ end
218
+
219
+ def loglevel(level)
220
+ Ernie.loglevel(level)
221
+ end
222
+
223
+ at_exit do
224
+ Ernie.start if Ernie.auto_start
225
+ end
@@ -0,0 +1,77 @@
1
+ require File.dirname(__FILE__) + '/helper'
2
+
3
+ PORT = 27118
4
+
5
+ class ErnieServerTest < Test::Unit::TestCase
6
+ context "An Ernie Server" do
7
+ setup do
8
+ `#{ERNIE_ROOT}/bin/ernie -c #{ERNIE_ROOT}/test/sample/sample.cfg \
9
+ -P /tmp/ernie.pid \
10
+ -p #{PORT} \
11
+ -d`
12
+ @svc = BERTRPC::Service.new('localhost', PORT)
13
+ loop do
14
+ begin
15
+ @svc.call.ext.zeronary
16
+ break
17
+ rescue Object => e
18
+ sleep 0.1
19
+ end
20
+ end
21
+ end
22
+
23
+ context "call" do
24
+ should "handle zeronary" do
25
+ assert_equal :foo, @svc.call.ext.zeronary
26
+ end
27
+
28
+ should "handle unary" do
29
+ assert_equal 5, @svc.call.ext.unary(5)
30
+ end
31
+
32
+ should "handle binary" do
33
+ assert_equal 7, @svc.call.ext.binary(5, 2)
34
+ end
35
+
36
+ should "handle ternary" do
37
+ assert_equal 10, @svc.call.ext.ternary(5, 2, 3)
38
+ end
39
+
40
+ should "handle massive binaries" do
41
+ assert_equal 8 * 1024 * 1024, @svc.call.ext.big(8 * 1024 * 1024).size
42
+ end
43
+
44
+ should "get an error on missing module" do
45
+ begin
46
+ @svc.call.failboat.mcfail(:fail)
47
+ fail "Expected a BERTRPC::ServerError"
48
+ rescue BERTRPC::ServerError => e
49
+ assert_equal "No such module 'failboat'", e.message
50
+ end
51
+ end
52
+
53
+ should "get an error on missing function" do
54
+ begin
55
+ @svc.call.ext.mcfail(:fail)
56
+ fail "Expected a BERTRPC::ServerError"
57
+ rescue BERTRPC::ServerError => e
58
+ assert_equal "No such function 'ext:mcfail'", e.message
59
+ end
60
+ end
61
+ end
62
+
63
+ context "cast" do
64
+ should "be received and return immediately" do
65
+ t0 = Time.now
66
+ assert_equal nil, @svc.cast.ext.set_state(7)
67
+ assert Time.now - t0 < 1
68
+ assert_equal 7, @svc.call.ext.get_state
69
+ end
70
+ end
71
+
72
+ teardown do
73
+ pid = File.read('/tmp/ernie.pid')
74
+ `kill -9 #{pid}`
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,43 @@
1
+ require File.dirname(__FILE__) + '/helper'
2
+
3
+ class ErnieTest < Test::Unit::TestCase
4
+ module TestExposingModule
5
+ def foo
6
+ end
7
+
8
+ def bar(a, b, c=nil)
9
+ [a, b, c]
10
+ end
11
+
12
+ protected
13
+ def baz
14
+ end
15
+
16
+ private
17
+ def bling
18
+ end
19
+ end
20
+
21
+ context "expose" do
22
+ setup { Ernie.expose :expo, TestExposingModule }
23
+ teardown { Ernie.mods.clear }
24
+
25
+ should "add all public methods from the module" do
26
+ assert_not_nil Ernie.mods[:expo].funs[:foo]
27
+ assert_not_nil Ernie.mods[:expo].funs[:bar]
28
+ end
29
+
30
+ should "not expose protected methods" do
31
+ assert_nil Ernie.mods[:expo].funs[:baz]
32
+ end
33
+
34
+ should "not expose private methods" do
35
+ assert_nil Ernie.mods[:expo].funs[:bling]
36
+ end
37
+
38
+ should "dispatch to module methods properly" do
39
+ res = Ernie.dispatch(:expo, :bar, ['a', :b, { :fiz => 42 }])
40
+ assert_equal ['a', :b, { :fiz => 42 }], res
41
+ end
42
+ end
43
+ end
data/test/helper.rb ADDED
@@ -0,0 +1,17 @@
1
+ require 'rubygems'
2
+ require 'test/unit'
3
+ require 'shoulda'
4
+
5
+ ERNIE_ROOT = File.join(File.dirname(__FILE__), *%w[..])
6
+
7
+ $:.unshift(File.join(ERNIE_ROOT, 'lib'))
8
+
9
+ require 'ernie'
10
+ Ernie.auto_start = false
11
+
12
+ begin
13
+ require 'bertrpc'
14
+ rescue LoadError
15
+ puts "You need bertrpc gem installed to run tests."
16
+ exit!(1)
17
+ end
data/test/load.rb ADDED
@@ -0,0 +1,18 @@
1
+ require 'bertrpc'
2
+
3
+ $stdout.sync = true
4
+
5
+ threads = []
6
+ svc = BERTRPC::Service.new('localhost', 8000)
7
+
8
+ 8.times do
9
+ threads << Thread.new do
10
+ i = 0
11
+ 10.times { i += svc.call.calc.add(1, 2); print '.'; $stdout.flush }
12
+ print "(#{i})"
13
+ end
14
+ end
15
+
16
+ threads.each { |t| t.join }
17
+
18
+ puts
@@ -0,0 +1,42 @@
1
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', '..', 'lib'))
2
+ require 'ernie'
3
+
4
+ module Ext
5
+ @@state = 0
6
+
7
+ def zeronary
8
+ :foo
9
+ end
10
+
11
+ def unary(a)
12
+ a
13
+ end
14
+
15
+ def binary(a, b)
16
+ a + b
17
+ end
18
+
19
+ def ternary(a, b, c)
20
+ a + b + c
21
+ end
22
+
23
+ def set_state(x)
24
+ @@state = x
25
+ sleep 5
26
+ nil
27
+ end
28
+
29
+ def get_state
30
+ @@state
31
+ end
32
+
33
+ def big(x)
34
+ 'a' * x
35
+ end
36
+
37
+ def cry
38
+ raise "abandon hope!"
39
+ end
40
+ end
41
+
42
+ Ernie.expose(:ext, Ext)
@@ -0,0 +1,4 @@
1
+ [{module, ext},
2
+ {type, external},
3
+ {command, "ruby /Users/tom/dev/mojombo/ernie/test/sample/ext.rb"},
4
+ {count, 1}].
metadata ADDED
@@ -0,0 +1,134 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: schleyfox-ernie
3
+ version: !ruby/object:Gem::Version
4
+ prerelease: false
5
+ segments:
6
+ - 2
7
+ - 2
8
+ - 1
9
+ version: 2.2.1
10
+ platform: ruby
11
+ authors:
12
+ - Tom Preston-Werner
13
+ autorequire:
14
+ bindir: bin
15
+ cert_chain: []
16
+
17
+ date: 2010-03-12 00:00:00 -05:00
18
+ default_executable: ernie
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
21
+ name: bert
22
+ prerelease: false
23
+ requirement: &id001 !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - ">="
26
+ - !ruby/object:Gem::Version
27
+ segments:
28
+ - 1
29
+ - 1
30
+ - 0
31
+ version: 1.1.0
32
+ type: :runtime
33
+ version_requirements: *id001
34
+ - !ruby/object:Gem::Dependency
35
+ name: bertrpc
36
+ prerelease: false
37
+ requirement: &id002 !ruby/object:Gem::Requirement
38
+ requirements:
39
+ - - ">="
40
+ - !ruby/object:Gem::Version
41
+ segments:
42
+ - 1
43
+ - 0
44
+ - 0
45
+ version: 1.0.0
46
+ type: :runtime
47
+ version_requirements: *id002
48
+ description: Ernie is an Erlang/Ruby hybrid BERT-RPC server implementation packaged as a gem.
49
+ email: tom@mojombo.com
50
+ executables:
51
+ - ernie
52
+ extensions:
53
+ - ext/extconf.rb
54
+ - ext/extconf.rb
55
+ extra_rdoc_files:
56
+ - LICENSE
57
+ - README.md
58
+ files:
59
+ - .document
60
+ - .gitignore
61
+ - History.txt
62
+ - LICENSE
63
+ - README.md
64
+ - Rakefile
65
+ - VERSION.yml
66
+ - bin/ernie
67
+ - contrib/ebench.erl
68
+ - ebin/ernie_server_app.app
69
+ - elib/asset_pool.erl
70
+ - elib/asset_pool_sup.erl
71
+ - elib/bert.erl
72
+ - elib/ernie.hrl
73
+ - elib/ernie_access_logger.erl
74
+ - elib/ernie_access_logger_sup.erl
75
+ - elib/ernie_admin.erl
76
+ - elib/ernie_config.erl
77
+ - elib/ernie_native.erl
78
+ - elib/ernie_server.erl
79
+ - elib/ernie_server_app.erl
80
+ - elib/ernie_server_sup.erl
81
+ - elib/logger.erl
82
+ - elib/logger_sup.erl
83
+ - elib/port_wrapper.erl
84
+ - ernie.gemspec
85
+ - examples/example.cfg
86
+ - examples/ext.erl
87
+ - examples/ext.rb
88
+ - examples/nat.erl
89
+ - ext/Makefile
90
+ - ext/extconf.rb
91
+ - lib/ernie.rb
92
+ - test/ernie_server_test.rb
93
+ - test/ernie_test.rb
94
+ - test/helper.rb
95
+ - test/load.rb
96
+ - test/sample/ext.rb
97
+ - test/sample/sample.cfg
98
+ has_rdoc: true
99
+ homepage: http://github.com/mojombo/ernie
100
+ licenses: []
101
+
102
+ post_install_message:
103
+ rdoc_options:
104
+ - --charset=UTF-8
105
+ require_paths:
106
+ - lib
107
+ required_ruby_version: !ruby/object:Gem::Requirement
108
+ requirements:
109
+ - - ">="
110
+ - !ruby/object:Gem::Version
111
+ segments:
112
+ - 0
113
+ version: "0"
114
+ required_rubygems_version: !ruby/object:Gem::Requirement
115
+ requirements:
116
+ - - ">="
117
+ - !ruby/object:Gem::Version
118
+ segments:
119
+ - 0
120
+ version: "0"
121
+ requirements: []
122
+
123
+ rubyforge_project: ernie
124
+ rubygems_version: 1.3.6
125
+ signing_key:
126
+ specification_version: 3
127
+ summary: Ernie is a BERT-RPC server implementation.
128
+ test_files:
129
+ - test/ernie_server_test.rb
130
+ - test/ernie_test.rb
131
+ - test/helper.rb
132
+ - test/load.rb
133
+ - test/sample/ext.rb
134
+ - examples/ext.rb