ernicorn 1.0.0

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/.gitignore ADDED
@@ -0,0 +1,3 @@
1
+ /.bundle
2
+ /bin
3
+ /vendor
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 Tom Preston-Werner, Aman Gupta
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,72 @@
1
+ Ernicorn
2
+ ========
3
+
4
+ A BERT-RPC server based on [Ernie](https://github.com/mojombo/ernie)'s Ruby
5
+ interface but that uses [Unicorn](http://unicorn.bogomips.org/) for worker
6
+ process management.
7
+
8
+ Ernicorn supports BERT-RPC `call` and `cast` requests. See the full BERT-RPC
9
+ specification at [bert-rpc.org](http://bert-rpc.org) for more information.
10
+
11
+ Like Ernie before it, Ernicorn was developed at GitHub and is currently in
12
+ production use serving millions of RPC requests every day.
13
+
14
+ Installation
15
+ ------------
16
+
17
+ $ gem install ernicorn
18
+
19
+ Starting The Server
20
+ -------------------
21
+
22
+ Use the ernicorn command to start a new RPC server:
23
+
24
+ [rtomayko@iron:ernicorn]$ ernicorn --help
25
+ Ernicorn is an Ruby BERT-RPC Server based on Unicorn.
26
+
27
+ Basic Command Line Usage:
28
+ ernicorn [options] <handler>
29
+
30
+ -c, --config CONFIG Unicorn style config file
31
+ -p, --port PORT Port
32
+ -l, --log-level LOGLEVEL Log level (0-4)
33
+ -d, --detached Run as a daemon
34
+ -P, --pidfile PIDFILE Location to write pid file.
35
+
36
+ The handler must be given and should be a normal Ruby file that sets up the
37
+ environment and calls `Ernicorn.expose` for any server modules. See the
38
+ [examples/handler.rb][h] file for more info.
39
+
40
+ [h]: https://github.com/github/ernicorn/blob/master/examples/handler.rb
41
+
42
+ Control Commands
43
+ ----------------
44
+
45
+ The `ernicorn-ctrl` command can be used to send various control and
46
+ informational commands to the running server process:
47
+
48
+ $ ernicorn-ctrl --help
49
+ Usage: ernicorn-ctrl [-p <port>] <command>
50
+ Issue a control command to an ernicorn server. The port option is used to
51
+ specify a non-default control port.
52
+
53
+ Commands:
54
+ reload-handlers Gracefully reload all handler processes.
55
+ stats Dump handler stats for the server.
56
+ halt Shut down.
57
+
58
+ Ernicorn servers also support most Unicorn signals for managing worker processes
59
+ and whatnot. See the [SIGNALS](http://unicorn.bogomips.org/SIGNALS.html) file
60
+ for more info.
61
+
62
+ Development
63
+ -----------
64
+
65
+ The `script/bootstrap` command is provided to setup a local gem environment for
66
+ development.
67
+
68
+ $ script/bootstrap
69
+
70
+ It installs all gems under `vendor/gems` and creates binstubs under a `bin`
71
+ directory. This is the best way to get setup for running tests and experimenting
72
+ in a sandbox.
data/Rakefile ADDED
@@ -0,0 +1,8 @@
1
+ task :default => :test
2
+
3
+ require 'rake/testtask'
4
+ Rake::TestTask.new(:test) do |test|
5
+ test.libs << 'lib' << 'test'
6
+ test.pattern = 'test/**/test_*.rb'
7
+ test.verbose = true
8
+ end
data/ernicorn.gemspec ADDED
@@ -0,0 +1,30 @@
1
+ Gem::Specification.new do |s|
2
+ s.specification_version = 2 if s.respond_to? :specification_version=
3
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
4
+ s.rubygems_version = '1.3.6'
5
+
6
+ s.name = 'ernicorn'
7
+ s.version = '1.0.0'
8
+ s.date = '2012-06-23'
9
+
10
+ s.summary = "Ernicorn is a BERT-RPC server implementation based on unicorn."
11
+ s.description = "Ernicorn is a BERT-RPC server packaged as a gem."
12
+
13
+ s.authors = ["Tom Preston-Werner", "tmm1", "rtomayko"]
14
+ s.email = 'tmm1@github.com'
15
+ s.homepage = 'https://github.com/github/ernicorn'
16
+
17
+ s.add_runtime_dependency('bert', ">= 1.1.0")
18
+ s.add_runtime_dependency('bertrpc', ">= 1.0.0")
19
+ s.add_runtime_dependency('unicorn', "~> 4.1.1")
20
+
21
+ s.add_development_dependency('shoulda', "~> 2.11.3")
22
+
23
+ s.files = `git ls-files`.split("\n") - %w[Gemfile Gemfile.lock]
24
+ s.test_files = `git ls-files -- test`.split("\n").select { |f| f =~ /_test.rb$/ }
25
+
26
+ s.bindir = "script"
27
+ s.executables = %w[ernicorn ernicorn-ctrl]
28
+ s.require_paths = %w[lib]
29
+ s.extra_rdoc_files = %w[LICENSE README.md]
30
+ end
@@ -0,0 +1,13 @@
1
+ require 'bertrpc'
2
+ service = BERTRPC::Service.new('localhost', 9777)
3
+
4
+ loop do
5
+ begin
6
+ puts ">> add(10, 32)"
7
+ res = service.call.example.send(:add, 10, 32)
8
+ puts "=> #{res.inspect}"
9
+ rescue => boom
10
+ puts "ERROR: #{boom.class} #{boom}"
11
+ end
12
+ sleep 1
13
+ end
@@ -0,0 +1,36 @@
1
+ # Ernicorn configuration
2
+ #
3
+ # Ernicorn config files typically live at config/ernicorn.rb and support all
4
+ # Unicorn config options:
5
+ #
6
+ # http://unicorn.bogomips.org/Unicorn/Configurator.html
7
+ #
8
+ # Example unicorn config files:
9
+ #
10
+ # http://unicorn.bogomips.org/examples/unicorn.conf.rb
11
+ # http://unicorn.bogomips.org/examples/unicorn.conf.minimal.rb
12
+ warn "in unicorn config file: #{__FILE__}"
13
+
14
+ # server options
15
+ listen 9777
16
+ worker_processes 2
17
+ working_directory File.dirname(__FILE__)
18
+
19
+ # ernicorn configuration
20
+ Ernicorn.loglevel 1
21
+
22
+ # enable COW where possible
23
+ GC.respond_to?(:copy_on_write_friendly=) &&
24
+ GC.copy_on_write_friendly = true
25
+
26
+ # load server code and expose modules
27
+ require File.expand_path('../handler', __FILE__)
28
+ Ernicorn.expose(:example, Example)
29
+
30
+ # hook into new child immediately after forking
31
+ after_fork do |server, worker|
32
+ end
33
+
34
+ # hook into master immediately before forking a worker
35
+ before_fork do |server, worker|
36
+ end
@@ -0,0 +1,49 @@
1
+ # An example BERTRPC server module. To start the RPC server use:
2
+ #
3
+ # $ bin/ernicorn -p 9675 examples/handler.rb
4
+ #
5
+ # Then connect and issue commands as follows:
6
+ #
7
+ # $ script/console
8
+ # >> require 'bertrpc'
9
+ # >> service = BERTRPC::Service.new('localhost', 9675)
10
+ # => #<BERTRPC::Service:0x1037e9678 @port=9765, @timeout=nil, @host="localhost">
11
+ # >> service.call.example.send(:add, 1, 2)
12
+ # => 3
13
+ # >> service.call.example.send(:fib, 10)
14
+ # => 89
15
+
16
+ module Example
17
+ # Add two numbers together
18
+ def add(a, b)
19
+ a + b
20
+ end
21
+
22
+ def fib(n)
23
+ if n == 0 || n == 1
24
+ 1
25
+ else
26
+ fib(n - 1) + fib(n - 2)
27
+ end
28
+ end
29
+
30
+ def shadow(x)
31
+ "ruby"
32
+ end
33
+
34
+ # Return the given number of bytes
35
+ def bytes(n)
36
+ 'x' * n
37
+ end
38
+
39
+ # Sleep for +sec+ and then return :ok
40
+ def slow(sec)
41
+ sleep(sec)
42
+ :ok
43
+ end
44
+
45
+ # Throw an error
46
+ def error
47
+ raise "abandon hope!"
48
+ end
49
+ end
data/examples/start.sh ADDED
@@ -0,0 +1,4 @@
1
+ #!/bin/sh
2
+ # Start the example ernicorn server on port 9777.
3
+ cd "$(dirname "$0")"
4
+ exec ernicorn -p 9777 config.rb
@@ -0,0 +1,39 @@
1
+ require 'unicorn'
2
+
3
+ module Ernicorn
4
+ module AdminRPC
5
+ def stats
6
+ queued = active = 0
7
+
8
+ Raindrops::Linux.tcp_listener_stats(Unicorn.listener_names).each do |addr,stats|
9
+ queued += stats.queued
10
+ active += stats.active
11
+ end if defined?(Raindrops::Linux.tcp_listener_stats)
12
+
13
+ return <<STATS
14
+ connections.total=#{Stats.connections_total}
15
+ connections.completed=#{Stats.connections_completed}
16
+ workers.idle=#{Stats.workers_idle}
17
+ workers.busy=#{active}
18
+ queue.high=#{queued}
19
+ queue.low=0
20
+ STATS
21
+ end
22
+
23
+ def reload_handlers
24
+ Process.kill 'USR2', master_pid
25
+ "Sent USR2 to #{master_pid}"
26
+ end
27
+
28
+ def halt
29
+ Process.kill 'QUIT', master_pid
30
+ "Sent QUIT to #{master_pid}"
31
+ end
32
+
33
+ def master_pid
34
+ $ernicorn.master_pid
35
+ end
36
+ end
37
+ end
38
+
39
+ Ernicorn.expose(:__admin__, Ernicorn::AdminRPC)
@@ -0,0 +1,82 @@
1
+ require 'unicorn'
2
+
3
+ module Ernicorn
4
+ Stats = Raindrops::Struct.new(:connections_total,
5
+ :connections_completed,
6
+ :workers_idle).new
7
+
8
+ class Server < Unicorn::HttpServer
9
+
10
+ # Private HttpServer methods we're overriding to
11
+ # implement BertRPC instead of HTTP.
12
+
13
+ def initialize(app, options={})
14
+ super app, options
15
+ end
16
+
17
+ def build_app!
18
+ end
19
+
20
+ def worker_loop(worker)
21
+ Stats.incr_workers_idle
22
+
23
+ if worker.nr == (self.worker_processes - 1)
24
+ old_pid = "#{self.pid}.oldbin"
25
+ if File.exists?(old_pid) && self.pid != old_pid
26
+ begin
27
+ Process.kill("QUIT", File.read(old_pid).to_i)
28
+ rescue Errno::ENOENT, Errno::ESRCH
29
+ # someone else did our job for us
30
+ end
31
+ end
32
+ end
33
+
34
+ super
35
+ ensure
36
+ Stats.decr_workers_idle
37
+ end
38
+
39
+ def process_client(client)
40
+ Stats.decr_workers_idle
41
+ Stats.incr_connections_total
42
+
43
+ @client = client
44
+ # bail out if client only sent EOF
45
+ return if @client.kgio_trypeek(1).nil?
46
+
47
+ iruby, oruby = Ernicorn.process(self, self)
48
+ rescue EOFError
49
+ logger.error("EOF from #{@client.kgio_addr rescue nil}")
50
+ rescue Object => e
51
+ logger.error(e)
52
+
53
+ begin
54
+ error = t[:error, t[:server, 0, e.class.to_s, e.message, e.backtrace]]
55
+ Ernicorn.write_berp(self, error)
56
+ rescue Object => ex
57
+ logger.error(ex)
58
+ end
59
+ ensure
60
+ @client.close rescue nil
61
+ @client = nil
62
+
63
+ Stats.incr_connections_completed
64
+ Stats.incr_workers_idle
65
+ end
66
+
67
+ # We pass ourselves as both input and output to Ernicorn.process, which
68
+ # calls the following blocking read/write methods.
69
+
70
+ def read(len)
71
+ data = ''
72
+ while data.bytesize < len
73
+ data << @client.kgio_read!(len - data.bytesize)
74
+ end
75
+ data
76
+ end
77
+
78
+ def write(data)
79
+ @client.kgio_write(data)
80
+ end
81
+ end
82
+ end
data/lib/ernicorn.rb ADDED
@@ -0,0 +1,252 @@
1
+ require 'bert'
2
+ require 'logger'
3
+
4
+ module Ernicorn
5
+ VERSION = '1.0.0'
6
+
7
+ class << self
8
+ attr_accessor :mods, :current_mod, :log
9
+ attr_accessor :auto_start
10
+ attr_accessor :count, :virgin_procline
11
+ end
12
+
13
+ self.count = 0
14
+ self.virgin_procline = $0
15
+ self.mods = {}
16
+ self.current_mod = nil
17
+ self.log = Logger.new(STDOUT)
18
+ self.log.level = Logger::FATAL
19
+ self.auto_start = true
20
+
21
+ # Record a module.
22
+ # +name+ is the module Symbol
23
+ # +block+ is the Block containing function definitions
24
+ #
25
+ # Returns nothing
26
+ def self.mod(name, block)
27
+ m = Mod.new(name)
28
+ self.current_mod = m
29
+ self.mods[name] = m
30
+ block.call
31
+ end
32
+
33
+ # Record a function.
34
+ # +name+ is the function Symbol
35
+ # +block+ is the Block to associate
36
+ #
37
+ # Returns nothing
38
+ def self.fun(name, block)
39
+ self.current_mod.fun(name, block)
40
+ end
41
+
42
+ # Expose all public methods in a Ruby module:
43
+ # +name+ is the ernie module Symbol
44
+ # +mixin+ is the ruby module whose public methods are exposed
45
+ #
46
+ # Returns nothing
47
+ def self.expose(name, mixin)
48
+ context = Object.new
49
+ context.extend mixin
50
+ mod(name, lambda {
51
+ mixin.public_instance_methods.each do |meth|
52
+ fun(meth.to_sym, context.method(meth))
53
+ end
54
+ })
55
+ if defined? mixin.dispatched
56
+ self.current_mod.logger = mixin.method(:dispatched)
57
+ end
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
+ self.mods[mod] || raise(ServerError.new("No such module '#{mod}'"))
85
+ self.mods[mod].funs[fun] || raise(ServerError.new("No such function '#{mod}:#{fun}'"))
86
+
87
+ start = Time.now
88
+ ret = self.mods[mod].funs[fun].call(*args)
89
+
90
+ begin
91
+ self.mods[mod].logger and
92
+ self.mods[mod].logger.call(Time.now-start, [fun, *args], ret)
93
+ rescue Object => e
94
+ self.log.fatal(e)
95
+ end
96
+
97
+ ret
98
+ end
99
+
100
+ # Read the length header from the wire.
101
+ # +input+ is the IO from which to read
102
+ #
103
+ # Returns the size Integer if one was read
104
+ # Returns nil otherwise
105
+ def self.read_4(input)
106
+ raw = input.read(4)
107
+ return nil unless raw
108
+ raw.unpack('N').first
109
+ end
110
+
111
+ # Read a BERP from the wire and decode it to a Ruby object.
112
+ # +input+ is the IO from which to read
113
+ #
114
+ # Returns a Ruby object if one could be read
115
+ # Returns nil otherwise
116
+ def self.read_berp(input)
117
+ packet_size = self.read_4(input)
118
+ return nil unless packet_size
119
+ bert = input.read(packet_size)
120
+ BERT.decode(bert)
121
+ end
122
+
123
+ # Write the given Ruby object to the wire as a BERP.
124
+ # +output+ is the IO on which to write
125
+ # +ruby+ is the Ruby object to encode
126
+ #
127
+ # Returns nothing
128
+ def self.write_berp(output, ruby)
129
+ data = BERT.encode(ruby)
130
+ output.write([data.length].pack("N"))
131
+ output.write(data)
132
+ end
133
+
134
+ # Start the processing loop.
135
+ #
136
+ # Loops forever
137
+ def self.start
138
+ self.procline('starting')
139
+ self.log.info("(#{Process.pid}) Starting") if self.log.level <= Logger::INFO
140
+ self.log.debug(self.mods.inspect) if self.log.level <= Logger::DEBUG
141
+
142
+ input = IO.new(3)
143
+ output = IO.new(4)
144
+ input.sync = true
145
+ output.sync = true
146
+
147
+ loop do
148
+ process(input, output)
149
+ end
150
+ end
151
+
152
+ # Processes a single BertRPC command.
153
+ #
154
+ # input - The IO to #read command BERP from.
155
+ # output - The IO to #write reply BERP to.
156
+ #
157
+ # Returns a [iruby, oruby] tuple of incoming and outgoing BERPs processed.
158
+ def self.process(input, output)
159
+ self.procline('waiting')
160
+ iruby = self.read_berp(input)
161
+ self.count += 1
162
+
163
+ unless iruby
164
+ puts "Could not read BERP length header. Ernicorn server may have gone away. Exiting now."
165
+ if self.log.level <= Logger::INFO
166
+ self.log.info("(#{Process.pid}) Could not read BERP length header. Ernicorn server may have gone away. Exiting now.")
167
+ end
168
+ exit!
169
+ end
170
+
171
+ if iruby.size == 4 && iruby[0] == :call
172
+ mod, fun, args = iruby[1..3]
173
+ self.procline("#{mod}:#{fun}(#{args})")
174
+ self.log.info("-> " + iruby.inspect) if self.log.level <= Logger::INFO
175
+ begin
176
+ res = self.dispatch(mod, fun, args)
177
+ oruby = t[:reply, res]
178
+ self.log.debug("<- " + oruby.inspect) if self.log.level <= Logger::DEBUG
179
+ write_berp(output, oruby)
180
+ rescue ServerError => e
181
+ oruby = t[:error, t[:server, 0, e.class.to_s, e.message, e.backtrace]]
182
+ self.log.error("<- " + oruby.inspect) if self.log.level <= Logger::ERROR
183
+ self.log.error(e.backtrace.join("\n")) if self.log.level <= Logger::ERROR
184
+ write_berp(output, oruby)
185
+ rescue Object => e
186
+ oruby = t[:error, t[:user, 0, e.class.to_s, e.message, e.backtrace]]
187
+ self.log.error("<- " + oruby.inspect) if self.log.level <= Logger::ERROR
188
+ self.log.error(e.backtrace.join("\n")) if self.log.level <= Logger::ERROR
189
+ write_berp(output, oruby)
190
+ end
191
+ elsif iruby.size == 4 && iruby[0] == :cast
192
+ mod, fun, args = iruby[1..3]
193
+ self.procline("#{mod}:#{fun}(#{args})")
194
+ self.log.info("-> " + [:cast, mod, fun, args].inspect) if self.log.level <= Logger::INFO
195
+ oruby = t[:noreply]
196
+ write_berp(output, oruby)
197
+
198
+ begin
199
+ self.dispatch(mod, fun, args)
200
+ rescue Object => e
201
+ # ignore
202
+ end
203
+ else
204
+ self.procline("invalid request")
205
+ self.log.error("-> " + iruby.inspect) if self.log.level <= Logger::ERROR
206
+ oruby = t[:error, t[:server, 0, "Invalid request: #{iruby.inspect}"]]
207
+ self.log.error("<- " + oruby.inspect) if self.log.level <= Logger::ERROR
208
+ write_berp(output, oruby)
209
+ end
210
+
211
+ self.procline('waiting')
212
+ [iruby, oruby]
213
+ end
214
+
215
+ def self.procline(msg)
216
+ $0 = "ernicorn handler #{VERSION} (ruby) - #{self.virgin_procline} - [#{self.count}] #{msg}"[0..159]
217
+ end
218
+
219
+ def self.version
220
+ VERSION
221
+ end
222
+
223
+ class ServerError < StandardError; end
224
+
225
+ class Ernicorn::Mod
226
+ attr_accessor :name, :funs, :logger
227
+
228
+ def initialize(name)
229
+ self.name = name
230
+ self.funs = {}
231
+ self.logger = nil
232
+ end
233
+
234
+ def fun(name, block)
235
+ raise TypeError, "block required" if block.nil?
236
+ self.funs[name] = block
237
+ end
238
+ end
239
+ end
240
+ # Root level calls
241
+
242
+ def logfile(name)
243
+ Ernicorn.logfile(name)
244
+ end
245
+
246
+ def loglevel(level)
247
+ Ernicorn.loglevel(level)
248
+ end
249
+
250
+ at_exit do
251
+ Ernicorn.start if Ernicorn.auto_start && !defined?(Ernicorn)
252
+ end
data/script/bootstrap ADDED
@@ -0,0 +1,11 @@
1
+ #!/bin/sh
2
+ # Usage: script/bootstrap [--local]
3
+ # Prepare the local gem environment.
4
+ set -e
5
+
6
+ cd "$(dirname "$0")/.."
7
+ if bundle check 1>/dev/null 2>&1; then
8
+ echo "Gem environment up-to-date"
9
+ else
10
+ exec bundle install --binstubs --path vendor/gems "$@"
11
+ fi
data/script/cibuild ADDED
@@ -0,0 +1,23 @@
1
+ #!/bin/sh
2
+ # Usage: script/cibuild
3
+ # CI build script.
4
+ # This is tailored for the janky build machines.
5
+ set -e
6
+
7
+ # change into root dir and setup path
8
+ cd $(dirname "$0")/..
9
+ PATH="$(pwd)/bin:$(pwd)/script:/usr/share/rbenv/shims:$PATH"
10
+
11
+ # Write commit we're building at
12
+ git log -n 1 || true
13
+ echo
14
+
15
+ echo "Building under 1.8.7"
16
+ bootstrap
17
+ testsuite
18
+ echo
19
+
20
+ echo "Building under 1.9.3"
21
+ export RBENV_VERSION="${1:-1.9.3-p0}"
22
+ bootstrap
23
+ testsuite
data/script/ernicorn ADDED
@@ -0,0 +1,51 @@
1
+ #!/usr/bin/env ruby
2
+ #/ Usage: ernicorn [options] [config file]
3
+ #/ Start a Ruby BERT-RPC Server with the given options and config file.
4
+ #/
5
+ #/ Options
6
+ #/ -h, --host=<host> Server address to listen on; default: 0.0.0.0
7
+ #/ -p, --port=<portno> Server port to listen on; default: 8000
8
+ #/ --log-level=0-4 Set the log level
9
+ #/ -d, --detached Run as a daemon
10
+ #/ -P, --pidfile=<file> Location to write pid file
11
+ #/
12
+ #/ See https://github.com/github/ernicorn for more information.
13
+ require 'optparse'
14
+ require 'ernicorn'
15
+ require 'ernicorn/server'
16
+ require 'ernicorn/adminrpc'
17
+
18
+ Ernicorn.auto_start = false
19
+
20
+ host = '0.0.0.0'
21
+ port = 8000
22
+ daemonize = false
23
+ config = 'config/ernicorn.rb'
24
+ pidfile = nil
25
+
26
+ parser = OptionParser.new do |opts|
27
+ opts.on("-c", "--config=val", String) { |config| }
28
+ opts.on("-h", "--host=val", Integer) { |host| }
29
+ opts.on("-p", "--port=val", Integer) { |port| }
30
+ opts.on( "--log-level=val", Integer) { |val| Ernicorn.loglevel(val) }
31
+ opts.on("-d", "--detached") { |daemonize| }
32
+ opts.on("-P", "--pidfile=val") { |pidfile| }
33
+ opts.on_tail("-h", "--help") { exec "grep ^#/<'#{__FILE__}'|cut -c4-" }
34
+ opts.parse!
35
+ end
36
+
37
+ config = ARGV[0] || config
38
+
39
+ options = {
40
+ :pid => pidfile,
41
+ :listeners => ["#{host}:#{port}"],
42
+ :config_file => File.expand_path(config)
43
+ }
44
+
45
+ if daemonize
46
+ require 'unicorn/launcher'
47
+ Unicorn::Launcher.daemonize!(options)
48
+ end
49
+
50
+ $ernicorn = Ernicorn::Server.new(nil, options)
51
+ $ernicorn.start.join
@@ -0,0 +1,37 @@
1
+ #!/usr/bin/env ruby
2
+ #/ Usage: ernicorn-ctrl [-p <port>] <command>
3
+ #/ Issue a control command to an ernicorn server. The port option is used to
4
+ #/ specify a non-default control port.
5
+ #/
6
+ #/ Commands:
7
+ #/ reload-handlers Gracefully reload all handler processes.
8
+ #/ stats Dump handler stats for the server.
9
+ #/ halt Shut down.
10
+ require 'optparse'
11
+ require 'bertrpc'
12
+
13
+ port = 8000
14
+
15
+ def usage(status=0)
16
+ exec "grep ^#/<'#{__FILE__}'|cut -c4-; exit #{status}"
17
+ end
18
+
19
+ # parse arguments
20
+ ARGV.options do |opts|
21
+ opts.on("-p", "--port=val", Integer) { |port| }
22
+ opts.on_tail("-h", "--help") { usage }
23
+ opts.parse!
24
+ end
25
+
26
+ # the control command
27
+ command = ARGV[0]
28
+
29
+ # bail out with usage if the command wasn't specified or is invalid
30
+ if !%w[reload-handlers stats halt].include?(command)
31
+ usage(1)
32
+ end
33
+
34
+ # connect to the adminrpc handler and send the command
35
+ require 'bertrpc'
36
+ service = BERTRPC::Service.new('localhost', port)
37
+ puts service.call.__admin__.send(command.gsub(/-/, '_'))
data/script/testsuite ADDED
@@ -0,0 +1,7 @@
1
+ #!/bin/sh
2
+ set -e
3
+ cd "$(dirname "$0")/.."
4
+
5
+ # run entire test suite
6
+ ruby --version 1>&2
7
+ bundle exec ruby -I "$(pwd)" -e "ARGV.each { |f| require(f) }" -- $(find test -name 'test_*.rb')
data/test/helper.rb ADDED
@@ -0,0 +1,4 @@
1
+ require 'test/unit'
2
+ require 'shoulda'
3
+ require 'ernicorn'
4
+ Ernicorn.auto_start = false
@@ -0,0 +1,69 @@
1
+ require File.expand_path('../helper', __FILE__)
2
+
3
+ class ErnicornTest < 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 { Ernicorn.expose :expo, TestExposingModule }
23
+ teardown { Ernicorn.mods.clear }
24
+
25
+ should "add all public methods from the module" do
26
+ assert_not_nil Ernicorn.mods[:expo].funs[:foo]
27
+ assert_not_nil Ernicorn.mods[:expo].funs[:bar]
28
+ end
29
+
30
+ should "not expose protected methods" do
31
+ assert_nil Ernicorn.mods[:expo].funs[:baz]
32
+ end
33
+
34
+ should "not expose private methods" do
35
+ assert_nil Ernicorn.mods[:expo].funs[:bling]
36
+ end
37
+
38
+ should "dispatch to module methods properly" do
39
+ res = Ernicorn.dispatch(:expo, :bar, ['a', :b, { :fiz => 42 }])
40
+ assert_equal ['a', :b, { :fiz => 42 }], res
41
+ end
42
+ end
43
+
44
+ module TestLoggingModule
45
+ def self.logs() @logs ||= [] end
46
+
47
+ def self.dispatched(*args)
48
+ logs << args
49
+ end
50
+
51
+ def sleepy(time)
52
+ sleep(time)
53
+ time
54
+ end
55
+ end
56
+
57
+ context "logging" do
58
+ setup { Ernicorn.expose :logging, TestLoggingModule; TestLoggingModule.logs.clear }
59
+ teardown { Ernicorn.mods.clear }
60
+
61
+ should "call logger method with timing" do
62
+ ret = Ernicorn.dispatch(:logging, :sleepy, 0.1)
63
+ assert_equal 0.1, ret
64
+
65
+ assert_equal 1, TestLoggingModule.logs.size
66
+ assert_in_delta 0.1, TestLoggingModule.logs.first[0], 0.05
67
+ end
68
+ end
69
+ end
metadata ADDED
@@ -0,0 +1,139 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ernicorn
3
+ version: !ruby/object:Gem::Version
4
+ prerelease: false
5
+ segments:
6
+ - 1
7
+ - 0
8
+ - 0
9
+ version: 1.0.0
10
+ platform: ruby
11
+ authors:
12
+ - Tom Preston-Werner
13
+ - tmm1
14
+ - rtomayko
15
+ autorequire:
16
+ bindir: script
17
+ cert_chain: []
18
+
19
+ date: 2012-06-23 00:00:00 -07:00
20
+ default_executable:
21
+ dependencies:
22
+ - !ruby/object:Gem::Dependency
23
+ name: bert
24
+ prerelease: false
25
+ requirement: &id001 !ruby/object:Gem::Requirement
26
+ requirements:
27
+ - - ">="
28
+ - !ruby/object:Gem::Version
29
+ segments:
30
+ - 1
31
+ - 1
32
+ - 0
33
+ version: 1.1.0
34
+ type: :runtime
35
+ version_requirements: *id001
36
+ - !ruby/object:Gem::Dependency
37
+ name: bertrpc
38
+ prerelease: false
39
+ requirement: &id002 !ruby/object:Gem::Requirement
40
+ requirements:
41
+ - - ">="
42
+ - !ruby/object:Gem::Version
43
+ segments:
44
+ - 1
45
+ - 0
46
+ - 0
47
+ version: 1.0.0
48
+ type: :runtime
49
+ version_requirements: *id002
50
+ - !ruby/object:Gem::Dependency
51
+ name: unicorn
52
+ prerelease: false
53
+ requirement: &id003 !ruby/object:Gem::Requirement
54
+ requirements:
55
+ - - ~>
56
+ - !ruby/object:Gem::Version
57
+ segments:
58
+ - 4
59
+ - 1
60
+ - 1
61
+ version: 4.1.1
62
+ type: :runtime
63
+ version_requirements: *id003
64
+ - !ruby/object:Gem::Dependency
65
+ name: shoulda
66
+ prerelease: false
67
+ requirement: &id004 !ruby/object:Gem::Requirement
68
+ requirements:
69
+ - - ~>
70
+ - !ruby/object:Gem::Version
71
+ segments:
72
+ - 2
73
+ - 11
74
+ - 3
75
+ version: 2.11.3
76
+ type: :development
77
+ version_requirements: *id004
78
+ description: Ernicorn is a BERT-RPC server packaged as a gem.
79
+ email: tmm1@github.com
80
+ executables:
81
+ - ernicorn
82
+ - ernicorn-ctrl
83
+ extensions: []
84
+
85
+ extra_rdoc_files:
86
+ - LICENSE
87
+ - README.md
88
+ files:
89
+ - .gitignore
90
+ - LICENSE
91
+ - README.md
92
+ - Rakefile
93
+ - ernicorn.gemspec
94
+ - examples/client.rb
95
+ - examples/config.rb
96
+ - examples/handler.rb
97
+ - examples/start.sh
98
+ - lib/ernicorn.rb
99
+ - lib/ernicorn/adminrpc.rb
100
+ - lib/ernicorn/server.rb
101
+ - script/bootstrap
102
+ - script/cibuild
103
+ - script/ernicorn
104
+ - script/ernicorn-ctrl
105
+ - script/testsuite
106
+ - test/helper.rb
107
+ - test/test_ernicorn.rb
108
+ has_rdoc: true
109
+ homepage: https://github.com/github/ernicorn
110
+ licenses: []
111
+
112
+ post_install_message:
113
+ rdoc_options: []
114
+
115
+ require_paths:
116
+ - lib
117
+ required_ruby_version: !ruby/object:Gem::Requirement
118
+ requirements:
119
+ - - ">="
120
+ - !ruby/object:Gem::Version
121
+ segments:
122
+ - 0
123
+ version: "0"
124
+ required_rubygems_version: !ruby/object:Gem::Requirement
125
+ requirements:
126
+ - - ">="
127
+ - !ruby/object:Gem::Version
128
+ segments:
129
+ - 0
130
+ version: "0"
131
+ requirements: []
132
+
133
+ rubyforge_project:
134
+ rubygems_version: 1.3.6
135
+ signing_key:
136
+ specification_version: 2
137
+ summary: Ernicorn is a BERT-RPC server implementation based on unicorn.
138
+ test_files: []
139
+