ernicorn 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +3 -0
- data/LICENSE +20 -0
- data/README.md +72 -0
- data/Rakefile +8 -0
- data/ernicorn.gemspec +30 -0
- data/examples/client.rb +13 -0
- data/examples/config.rb +36 -0
- data/examples/handler.rb +49 -0
- data/examples/start.sh +4 -0
- data/lib/ernicorn/adminrpc.rb +39 -0
- data/lib/ernicorn/server.rb +82 -0
- data/lib/ernicorn.rb +252 -0
- data/script/bootstrap +11 -0
- data/script/cibuild +23 -0
- data/script/ernicorn +51 -0
- data/script/ernicorn-ctrl +37 -0
- data/script/testsuite +7 -0
- data/test/helper.rb +4 -0
- data/test/test_ernicorn.rb +69 -0
- metadata +139 -0
data/.gitignore
ADDED
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
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
|
data/examples/client.rb
ADDED
@@ -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
|
data/examples/config.rb
ADDED
@@ -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
|
data/examples/handler.rb
ADDED
@@ -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,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
data/test/helper.rb
ADDED
@@ -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
|
+
|