Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Newer
Older
100644 188 lines (164 sloc) 4.558 kb
6dbdd86 @mojombo add ernie class and tests
authored
1 require 'rubygems'
010d1ad @mojombo convert to using bert gem
authored
2 require 'bert'
6dbdd86 @mojombo add ernie class and tests
authored
3 require 'logger'
4
5 class Ernie
6 class << self
7 attr_accessor :mods, :current_mod, :logger
2088ede @rtomayko Ernie.auto_start controls whether the server starts on_exit
rtomayko authored
8 attr_accessor :auto_start
6dbdd86 @mojombo add ernie class and tests
authored
9 end
10
11 self.mods = {}
12 self.current_mod = nil
13 self.logger = nil
2088ede @rtomayko Ernie.auto_start controls whether the server starts on_exit
rtomayko authored
14 self.auto_start = true
6dbdd86 @mojombo add ernie class and tests
authored
15
d581d0c @mojombo remove dependency on erlectricity
authored
16 # Record a module.
17 # +name+ is the module Symbol
18 # +block+ is the Block containing function definitions
19 #
20 # Returns nothing
6dbdd86 @mojombo add ernie class and tests
authored
21 def self.mod(name, block)
22 m = Mod.new(name)
23 self.current_mod = m
24 self.mods[name] = m
25 block.call
26 end
27
d581d0c @mojombo remove dependency on erlectricity
authored
28 # Record a function.
29 # +name+ is the function Symbol
30 # +block+ is the Block to associate
31 #
32 # Returns nothing
6dbdd86 @mojombo add ernie class and tests
authored
33 def self.fun(name, block)
34 self.current_mod.fun(name, block)
35 end
36
d581d0c @mojombo remove dependency on erlectricity
authored
37 # Set the logfile to given path.
38 # +file+ is the String path to the logfile
39 #
40 # Returns nothing
6dbdd86 @mojombo add ernie class and tests
authored
41 def self.logfile(file)
42 self.logger = Logger.new(file)
43 end
44
d581d0c @mojombo remove dependency on erlectricity
authored
45 # If logging is enabled, log the given text.
46 # +text+ is the String to log
47 #
48 # Returns nothing
6dbdd86 @mojombo add ernie class and tests
authored
49 def self.log(text)
50 self.logger.info(text) if self.logger
51 end
52
d581d0c @mojombo remove dependency on erlectricity
authored
53 # Dispatch the request to the proper mod:fun.
54 # +mod+ is the module Symbol
55 # +fun+ is the function Symbol
56 # +args+ is the Array of arguments
57 #
58 # Returns the Ruby object response
6dbdd86 @mojombo add ernie class and tests
authored
59 def self.dispatch(mod, fun, args)
04471d3 @mojombo better test coverage
authored
60 self.mods[mod] || raise(ServerError.new("No such module '#{mod}'"))
e99df33 @mojombo better error messages for missing mod and fun
authored
61 self.mods[mod].funs[fun] || raise(ServerError.new("No such function '#{mod}:#{fun}'"))
d581d0c @mojombo remove dependency on erlectricity
authored
62 self.mods[mod].funs[fun].call(*args)
6dbdd86 @mojombo add ernie class and tests
authored
63 end
64
d581d0c @mojombo remove dependency on erlectricity
authored
65 # Read the length header from the wire.
66 # +input+ is the IO from which to read
67 #
68 # Returns the size Integer if one was read
69 # Returns nil otherwise
70 def self.read_4(input)
71 raw = input.read(4)
72 return nil unless raw
73 raw.unpack('N').first
74 end
75
76 # Read a BERP from the wire and decode it to a Ruby object.
77 # +input+ is the IO from which to read
78 #
79 # Returns a Ruby object if one could be read
80 # Returns nil otherwise
81 def self.read_berp(input)
82 packet_size = self.read_4(input)
83 return nil unless packet_size
84 bert = input.read(packet_size)
85 BERT.decode(bert)
86 end
87
88 # Write the given Ruby object to the wire as a BERP.
89 # +output+ is the IO on which to write
90 # +ruby+ is the Ruby object to encode
91 #
92 # Returns nothing
93 def self.write_berp(output, ruby)
94 data = BERT.encode(ruby)
95 output.write([data.length].pack("N"))
96 output.write(data)
97 end
98
99 # Start the processing loop.
100 #
101 # Loops forever
6dbdd86 @mojombo add ernie class and tests
authored
102 def self.start
103 self.log("Starting")
104 self.log(self.mods.inspect)
d581d0c @mojombo remove dependency on erlectricity
authored
105
106 input = IO.new(3)
107 output = IO.new(4)
108 input.sync = true
109 output.sync = true
110
111 loop do
112 iruby = self.read_berp(input)
113 unless iruby
114 puts "Could not read BERP length header. Ernie server may have gone away. Exiting now."
115 exit!
116 end
117
118 if iruby.size == 4 && iruby[0] == :call
119 mod, fun, args = iruby[1..3]
120 self.log("-> " + iruby.inspect)
6dbdd86 @mojombo add ernie class and tests
authored
121 begin
122 res = self.dispatch(mod, fun, args)
d581d0c @mojombo remove dependency on erlectricity
authored
123 oruby = t[:reply, res]
124 self.log("<- " + oruby.inspect)
125 write_berp(output, oruby)
e99df33 @mojombo better error messages for missing mod and fun
authored
126 rescue ServerError => e
d581d0c @mojombo remove dependency on erlectricity
authored
127 oruby = t[:error, t[:server, 0, e.class.to_s, e.message, e.backtrace]]
128 self.log("<- " + oruby.inspect)
e99df33 @mojombo better error messages for missing mod and fun
authored
129 self.log(e.backtrace.join("\n"))
d581d0c @mojombo remove dependency on erlectricity
authored
130 write_berp(output, oruby)
6dbdd86 @mojombo add ernie class and tests
authored
131 rescue Object => e
d581d0c @mojombo remove dependency on erlectricity
authored
132 oruby = t[:error, t[:user, 0, e.class.to_s, e.message, e.backtrace]]
133 self.log("<- " + oruby.inspect)
6dbdd86 @mojombo add ernie class and tests
authored
134 self.log(e.backtrace.join("\n"))
d581d0c @mojombo remove dependency on erlectricity
authored
135 write_berp(output, oruby)
6dbdd86 @mojombo add ernie class and tests
authored
136 end
d581d0c @mojombo remove dependency on erlectricity
authored
137 elsif iruby.size == 4 && iruby[0] == :cast
138 mod, fun, args = iruby[1..3]
238fdb6 @mojombo introduce the concept of a Request to make way for advanced BERT-RPC fun...
authored
139 self.log("-> " + [:cast, mod, fun, args].inspect)
140 begin
141 self.dispatch(mod, fun, args)
142 rescue Object => e
143 # ignore
144 end
d581d0c @mojombo remove dependency on erlectricity
authored
145 write_berp(output, t[:noreply])
146 else
147 self.log("-> " + iruby.inspect)
148 oruby = t[:error, t[:server, 0, "Invalid request: #{iruby.inspect}"]]
149 self.log("<- " + oruby.inspect)
150 write_berp(output, oruby)
6dbdd86 @mojombo add ernie class and tests
authored
151 end
152 end
153 end
154 end
155
e99df33 @mojombo better error messages for missing mod and fun
authored
156 class Ernie::ServerError < StandardError; end
157
6dbdd86 @mojombo add ernie class and tests
authored
158 class Ernie::Mod
159 attr_accessor :name, :funs
160
161 def initialize(name)
162 self.name = name
163 self.funs = {}
164 end
165
166 def fun(name, block)
167 self.funs[name] = block
168 end
169 end
170
171 # Root level calls
172
173 def mod(name, &block)
174 Ernie.mod(name, block)
175 end
176
177 def fun(name, &block)
178 Ernie.fun(name, block)
179 end
180
181 def logfile(name)
182 Ernie.logfile(name)
183 end
184
185 at_exit do
2088ede @rtomayko Ernie.auto_start controls whether the server starts on_exit
rtomayko authored
186 Ernie.start if Ernie.auto_start
187 end
Something went wrong with that request. Please try again.