servitude 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +17 -0
- data/.ruby-gemset +1 -0
- data/.ruby-version +1 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +229 -0
- data/Rakefile +1 -0
- data/examples/1_simple_server +48 -0
- data/examples/2_echo_server +71 -0
- data/lib/servitude/actor.rb +13 -0
- data/lib/servitude/base.rb +54 -0
- data/lib/servitude/cli.rb +136 -0
- data/lib/servitude/configuration.rb +58 -0
- data/lib/servitude/daemon.rb +176 -0
- data/lib/servitude/logging.rb +21 -0
- data/lib/servitude/server.rb +62 -0
- data/lib/servitude/server_logging.rb +80 -0
- data/lib/servitude/server_threaded.rb +62 -0
- data/lib/servitude/supervision_error.rb +5 -0
- data/lib/servitude/version.rb +3 -0
- data/lib/servitude.rb +24 -0
- data/servitude.gemspec +31 -0
- metadata +194 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 0875adaf0bae960908e33467f31e8a15dc79bf5e
|
4
|
+
data.tar.gz: cbde282dc8d7eb9964168bfadcab4bc564d51ea2
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: ed319f26554dc51dc9bcdbe5db9e03db4c67fce36cab1fe85de5f8924f7b5102167532803781d608b9e2bb4b8b2cd1c727eeda4a8b754bd8f6430c427a318899
|
7
|
+
data.tar.gz: ed3c76b26403ed32f0eb10437c039b7e53b3625fbf2c8f57a99c0d0b4661e33c3c954151bfc8213d399ddf62e77a122d3c015699e7008946a12ac0bd99f79910
|
data/.gitignore
ADDED
data/.ruby-gemset
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
servitude
|
data/.ruby-version
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
ruby-2.1.0
|
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2014 Jason Harrelson
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,229 @@
|
|
1
|
+
# Servitude
|
2
|
+
|
3
|
+
A set of tools for writing single or multi-threaded Ruby servers.
|
4
|
+
|
5
|
+
|
6
|
+
## Installation
|
7
|
+
|
8
|
+
Add this line to your application's Gemfile:
|
9
|
+
|
10
|
+
gem 'servitude'
|
11
|
+
|
12
|
+
And then execute:
|
13
|
+
|
14
|
+
$ bundle
|
15
|
+
|
16
|
+
Or install it yourself as:
|
17
|
+
|
18
|
+
$ gem install servitude
|
19
|
+
|
20
|
+
|
21
|
+
## Usage
|
22
|
+
|
23
|
+
For executable examples see the [examples folder](https://github.com/midas/servitude/tree/master/examples).
|
24
|
+
|
25
|
+
To build a server with Servitude only a couple of steps are required.
|
26
|
+
|
27
|
+
* Include the Servitude::Base module in the base module of your server project.
|
28
|
+
* Create a server class and include the Servitude::Server module (also include Servitude::ServerThreaded if you want multi-threaded server).
|
29
|
+
* Create a CLI class and include the Servitude::Cli module.
|
30
|
+
* If a single threaded server, implement your functionality in the Server#run method.
|
31
|
+
* If a multi-threaded server, implement your functionality in a handler class that includes the Servitude::Actor module and call the handler from the Server#run method.
|
32
|
+
|
33
|
+
For more details see the examples folder.
|
34
|
+
|
35
|
+
The rest of this document will discuss the functionality each module provides.
|
36
|
+
|
37
|
+
### Servitude::Actor
|
38
|
+
|
39
|
+
In order to achieve well abstracted multi-threaded functionality Servitude employs the [Celluloid](https://github.com/celluloid/celluloid) gem. The actor module simply
|
40
|
+
abstracts some of the details of creating an actor away so you may concentrate on the functionality. For example:
|
41
|
+
|
42
|
+
module AwesomeServer
|
43
|
+
class MessageHandler
|
44
|
+
include Servitude::Actor
|
45
|
+
|
46
|
+
def call( options )
|
47
|
+
# some neat functionality ...
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
While the #call method is not a Celluloid concept, in order to integrate with the Servitude::Server's default implementation, the #call method is the
|
53
|
+
expected entry point to the actor.
|
54
|
+
|
55
|
+
The [Celluloid wiki](https://github.com/celluloid/celluloid/wiki) does a very good job of explaining the actor pattern. In summary, an actor is a concurrent object
|
56
|
+
that runs in its own thread.
|
57
|
+
|
58
|
+
### Servitude::Base
|
59
|
+
|
60
|
+
The Base module provides core functionality for your Ruby server and should be inlcuded in the outermost namespace of your server project. In addition to including
|
61
|
+
the Base module, you must call the ::boot method and provide the required arguments to it. Note, the arguments for ::boot are Ruby "required keyword arguments"
|
62
|
+
and not a Hash.
|
63
|
+
|
64
|
+
If you do not call ::boot, an error is raised before your server can be started.
|
65
|
+
|
66
|
+
module AwesomeServer
|
67
|
+
include Servitude::Base
|
68
|
+
|
69
|
+
boot host_namespace: AwesomeServer,
|
70
|
+
app_id: 'awesome-server',
|
71
|
+
app_name: 'Aswesome Server',
|
72
|
+
company: 'Awesome, Inc.',
|
73
|
+
default_config_path: "/etc/awesome/awesome-server.conf",
|
74
|
+
default_log_path: "/var/log/awesome/awesome-server.log",
|
75
|
+
default_pid_path: "/var/run/awesome/awesome-server.pid",
|
76
|
+
default_thread_count: 1,
|
77
|
+
version_copyright: "v#{VERSION} \u00A9#{Time.now.year} Awesome, Inc."
|
78
|
+
end
|
79
|
+
|
80
|
+
### Servitude::Cli
|
81
|
+
|
82
|
+
The Cli module provides the functionality of a Command Line Interface for your server.
|
83
|
+
|
84
|
+
module AwesomeServer
|
85
|
+
class Cli
|
86
|
+
include Servitude::Cli
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
In your CLI file (bin/awesome-server):
|
91
|
+
|
92
|
+
#!/usr/bin/env ruby
|
93
|
+
require 'awesome_server'
|
94
|
+
AwesomeServer::Cli.new( ARGV ).run
|
95
|
+
|
96
|
+
### Servitude::Configuration
|
97
|
+
|
98
|
+
The Configuration module provides functionality for creating a configuration class. You must call the ::configurations method and provide configuration
|
99
|
+
attributes to it. The Configuration module also provides a ::from_file method that allows a configuration to be read from a JSON config file.
|
100
|
+
|
101
|
+
module AwesomeServer
|
102
|
+
class Configuration
|
103
|
+
include Servitude::Configuration
|
104
|
+
|
105
|
+
configurations :some_config, # attribute with no default
|
106
|
+
[:another, 'some value'] # attribute with a default
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
If you want to load your configuration from a JSON config file you may do so by registering a callback (most likely an after_initialize callback). If the
|
111
|
+
configuration file does not exist an error will be raised by Servitude.
|
112
|
+
|
113
|
+
module AwesomeServer
|
114
|
+
class Server
|
115
|
+
include Servitude::Server
|
116
|
+
|
117
|
+
after_initialize do
|
118
|
+
Configuration.from_file( options[:config] )
|
119
|
+
end
|
120
|
+
|
121
|
+
# ...
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
### Servitude::Server
|
126
|
+
|
127
|
+
The Server module provides the base functionality for implementing a server, such as configuring the loggers, setting up Unix signal handling, outputting a
|
128
|
+
startup banner, etc. You must override the #run method in order to implement your functionality
|
129
|
+
|
130
|
+
module AwesomeServer
|
131
|
+
class Server
|
132
|
+
include Servitude::Server
|
133
|
+
|
134
|
+
def run
|
135
|
+
info 'Running ...'
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
#### Callbacks
|
141
|
+
|
142
|
+
The Server module provides callbacks to utilize in your server implementation:
|
143
|
+
|
144
|
+
* __before_initialize__: executes just before the initilaization of the server
|
145
|
+
* __after_initialize__: executes immediately after initilaization of the server
|
146
|
+
* __before_sleep__: executes just before the main thread sleeps to avoid exiting
|
147
|
+
* __finalize__: executes before server exits
|
148
|
+
|
149
|
+
You can provide one or more method names or procs to the callbacks to be executed.
|
150
|
+
|
151
|
+
module AwesomeServer
|
152
|
+
class Server
|
153
|
+
after_initialize :configure_server
|
154
|
+
|
155
|
+
finalize :cleanup
|
156
|
+
|
157
|
+
finalize do
|
158
|
+
info "Shutting down ..."
|
159
|
+
end
|
160
|
+
|
161
|
+
protected
|
162
|
+
|
163
|
+
def configure_server
|
164
|
+
# configuration code here ...
|
165
|
+
end
|
166
|
+
|
167
|
+
def cleanup
|
168
|
+
# cleanup code here ...
|
169
|
+
end
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
You can also define callbacks on your server and use them. The callback/hook functionality is provided by the [hooks gem](https://github.com/apotonick/hooks).
|
174
|
+
|
175
|
+
module AwesomeServer
|
176
|
+
class Server
|
177
|
+
define_hook :before_run
|
178
|
+
|
179
|
+
before_run do
|
180
|
+
# do something ...
|
181
|
+
end
|
182
|
+
|
183
|
+
def run
|
184
|
+
run_hook :before_run
|
185
|
+
# do something ...
|
186
|
+
end
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
### Servitude::ServerThreaded
|
191
|
+
|
192
|
+
The ServerThreaded module extends server functionality to be multi-threaded, providing several convenience methods to abstract away correctly handling certain
|
193
|
+
situations Celluloid actors present. The ServerThreaded module must be included after the Server module.
|
194
|
+
|
195
|
+
module AwesomeServer
|
196
|
+
class Server
|
197
|
+
include Servitude::Server
|
198
|
+
include Servitude::ServerThreaded
|
199
|
+
end
|
200
|
+
end
|
201
|
+
|
202
|
+
The ServerThreaded module assumes you will use the Celluloid actor pattern to implement your functionality. Al you must do to implement the threaded functionality
|
203
|
+
is override the #handler\_class method to specify the class that will act as your handler (actor) and utilize the #with_supervision block and
|
204
|
+
\#call_handler_respecting_thread_count method providing the options to pass to your handler's #call method.
|
205
|
+
|
206
|
+
The #with_supervision block implements error handling/retry logic required to correctly interact with Celluloid supervision without bombing due to dead actor errors.
|
207
|
+
|
208
|
+
module AwesomeServer
|
209
|
+
class Server
|
210
|
+
include Servitude::Server
|
211
|
+
include Servitude::ServerThreaded
|
212
|
+
|
213
|
+
def run
|
214
|
+
some_event_generated_block do |event_args|
|
215
|
+
with_supervision do
|
216
|
+
call_handler_respecting_thread_count( info: event_args.info )
|
217
|
+
end
|
218
|
+
end
|
219
|
+
end
|
220
|
+
|
221
|
+
def handler_class
|
222
|
+
AwesomeServer::MessageHandler
|
223
|
+
end
|
224
|
+
end
|
225
|
+
end
|
226
|
+
|
227
|
+
The #some_event_generated_block method call in the code block above represents some even that happend that needs to be processed. All servers sleep until an event
|
228
|
+
happens and then do some work, respond and then go back to sleep. Some good examples are receiving packets form a TCP/UDP socket or receiving a message from a
|
229
|
+
message queue.
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
@@ -0,0 +1,48 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'servitude'
|
5
|
+
|
6
|
+
# The simple server is as actually not a true server at all as it does nothing except for start and run (serving no
|
7
|
+
# requests).
|
8
|
+
#
|
9
|
+
# Use CTRL-c (INT signal) to stop the server. Additionally send the process an INT or TERM signal using the kill comand
|
10
|
+
# or your # OS's # process monitoring application. All 3 strategies result in a graceful shutdown as displayed by the
|
11
|
+
# 'Shutting down ...' which occurs due to the finalize block.
|
12
|
+
#
|
13
|
+
# Usage:
|
14
|
+
# bundle exec examples/1_simple_server
|
15
|
+
#
|
16
|
+
module SimpleServer
|
17
|
+
|
18
|
+
include Servitude::Base
|
19
|
+
|
20
|
+
APP_FOLDER = 'simple-server'
|
21
|
+
VERSION = '1.0.0'
|
22
|
+
|
23
|
+
boot host_namespace: SimpleServer,
|
24
|
+
app_id: 'simple-server',
|
25
|
+
app_name: 'Simple Server',
|
26
|
+
company: 'LFE',
|
27
|
+
default_config_path: "/usr/local/etc/#{APP_FOLDER}/#{APP_FOLDER}.conf",
|
28
|
+
default_log_path: "/usr/local/var/log/#{APP_FOLDER}/#{APP_FOLDER}.log",
|
29
|
+
default_pid_path: "/usr/local/var/run/#{APP_FOLDER}/#{APP_FOLDER}.pid",
|
30
|
+
default_thread_count: 1,
|
31
|
+
version_copyright: "v#{VERSION} \u00A9#{Time.now.year} LFE"
|
32
|
+
|
33
|
+
class Server
|
34
|
+
|
35
|
+
include Servitude::Server
|
36
|
+
|
37
|
+
finalize do
|
38
|
+
info 'Shutting down ...'
|
39
|
+
end
|
40
|
+
|
41
|
+
def run
|
42
|
+
info "Running ..."
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
SimpleServer::Server.new.start
|
@@ -0,0 +1,71 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'servitude'
|
5
|
+
require 'socket'
|
6
|
+
require 'pry'
|
7
|
+
|
8
|
+
# The echo server accepts a message and repeats it back. It is the first real server in the examples.
|
9
|
+
#
|
10
|
+
# Note: Due to TcpServer#accept's implementation, the server is not currently gracefully shutting down as the trap of INT
|
11
|
+
# appears to never happen.
|
12
|
+
#
|
13
|
+
# Usage:
|
14
|
+
# bundle exec examples/2_echo_server
|
15
|
+
#
|
16
|
+
# Then use telent to exercise the server:
|
17
|
+
# $ telnet localhost 1234
|
18
|
+
# Hello World!
|
19
|
+
# You said: Hello World!
|
20
|
+
# Connection closed by foreign host.
|
21
|
+
#
|
22
|
+
module EchoServer
|
23
|
+
|
24
|
+
include Servitude::Base
|
25
|
+
|
26
|
+
APP_FOLDER = 'echo-server'
|
27
|
+
VERSION = '1.0.0'
|
28
|
+
|
29
|
+
boot host_namespace: EchoServer,
|
30
|
+
app_id: 'echo-server',
|
31
|
+
app_name: 'Echo Server',
|
32
|
+
company: 'LFE',
|
33
|
+
default_config_path: "/usr/local/etc/#{APP_FOLDER}/#{APP_FOLDER}.conf",
|
34
|
+
default_log_path: "/usr/local/var/log/#{APP_FOLDER}/#{APP_FOLDER}.log",
|
35
|
+
default_pid_path: "/usr/local/var/run/#{APP_FOLDER}/#{APP_FOLDER}.pid",
|
36
|
+
default_thread_count: 1,
|
37
|
+
version_copyright: "v#{VERSION} \u00A9#{Time.now.year} LFE"
|
38
|
+
|
39
|
+
class Server
|
40
|
+
|
41
|
+
include Servitude::Server
|
42
|
+
|
43
|
+
after_initialize do
|
44
|
+
@tcp_server = TCPServer.open( 'localhost', '1234' )
|
45
|
+
end
|
46
|
+
|
47
|
+
finalize do
|
48
|
+
binding.pry
|
49
|
+
info 'Shutting down ...'
|
50
|
+
end
|
51
|
+
|
52
|
+
def run
|
53
|
+
while client = tcp_server.accept
|
54
|
+
line = client.gets
|
55
|
+
info "Received '#{line.strip}'"
|
56
|
+
response = "You said: #{line.strip}"
|
57
|
+
client.puts response
|
58
|
+
info "Responded with '#{response}'"
|
59
|
+
info "Closing connection"
|
60
|
+
client.close
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
private
|
65
|
+
|
66
|
+
attr_reader :tcp_server
|
67
|
+
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
EchoServer::Server.new.start
|
@@ -0,0 +1,54 @@
|
|
1
|
+
require 'servitude'
|
2
|
+
|
3
|
+
module Servitude
|
4
|
+
module Base
|
5
|
+
|
6
|
+
def self.included( base )
|
7
|
+
base.extend( ClassMethods )
|
8
|
+
base.class_eval do
|
9
|
+
class << self
|
10
|
+
attr_accessor :logger
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
module ClassMethods
|
16
|
+
|
17
|
+
def boot( host_namespace:,
|
18
|
+
app_id:,
|
19
|
+
app_name:,
|
20
|
+
company:,
|
21
|
+
default_config_path:,
|
22
|
+
default_log_path:,
|
23
|
+
default_pid_path:,
|
24
|
+
default_thread_count:,
|
25
|
+
version_copyright:)
|
26
|
+
Servitude::const_set :NS, host_namespace
|
27
|
+
|
28
|
+
const_set :APP_ID, app_id
|
29
|
+
const_set :APP_NAME, app_name
|
30
|
+
const_set :COMPANY, company
|
31
|
+
const_set :DEFAULT_CONFIG_PATH, default_config_path
|
32
|
+
const_set :DEFAULT_LOG_PATH, default_log_path
|
33
|
+
const_set :DEFAULT_PID_PATH, default_pid_path
|
34
|
+
const_set :DEFAULT_THREAD_COUNT, default_thread_count
|
35
|
+
const_set :VERSION_COPYRIGHT, version_copyright
|
36
|
+
|
37
|
+
Servitude::boot_called = true
|
38
|
+
end
|
39
|
+
|
40
|
+
def configuration
|
41
|
+
@configuration ||= Servitude::NS::Configuration.new
|
42
|
+
end
|
43
|
+
|
44
|
+
def configuration=( configuration )
|
45
|
+
@configuration = configuration
|
46
|
+
end
|
47
|
+
|
48
|
+
def configure
|
49
|
+
yield( configuration ) if block_given?
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,136 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'servitude'
|
3
|
+
require 'trollop'
|
4
|
+
require 'yell'
|
5
|
+
|
6
|
+
module Servitude
|
7
|
+
module Cli
|
8
|
+
|
9
|
+
def self.included( base )
|
10
|
+
base.class_eval do
|
11
|
+
attr_reader :cmd,
|
12
|
+
:options
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
SUB_COMMANDS = %w(
|
17
|
+
restart
|
18
|
+
start
|
19
|
+
status
|
20
|
+
stop
|
21
|
+
)
|
22
|
+
|
23
|
+
def initialize( args )
|
24
|
+
unless Servitude.boot_called
|
25
|
+
raise 'You must call boot before starting server'
|
26
|
+
end
|
27
|
+
|
28
|
+
Trollop::options do
|
29
|
+
version Servitude::NS::VERSION_COPYRIGHT
|
30
|
+
banner <<-EOS
|
31
|
+
#{Servitude::NS::APP_NAME} #{Servitude::NS::VERSION_COPYRIGHT}
|
32
|
+
|
33
|
+
Usage:
|
34
|
+
#{Servitude::NS::APP_ID} [command] [options]
|
35
|
+
|
36
|
+
commands:
|
37
|
+
#{SUB_COMMANDS.map { |sub_cmd| " #{sub_cmd}" }.join( "\n" )}
|
38
|
+
|
39
|
+
(For help with a command: #{Servitude::NS::APP_ID} [command] -h)
|
40
|
+
|
41
|
+
options:
|
42
|
+
EOS
|
43
|
+
stop_on SUB_COMMANDS
|
44
|
+
end
|
45
|
+
|
46
|
+
# Get the sub-command and its options
|
47
|
+
#
|
48
|
+
@cmd = ARGV.shift || ''
|
49
|
+
@options = case( cmd )
|
50
|
+
when ''
|
51
|
+
Trollop::die 'No command provided'
|
52
|
+
when "restart"
|
53
|
+
Trollop::options do
|
54
|
+
opt :config, "The path for the config file", :type => String, :short => '-c', :default => Servitude::NS::DEFAULT_CONFIG_PATH
|
55
|
+
opt :log_level, "The log level", :type => String, :default => 'info', :short => '-o'
|
56
|
+
opt :log, "The path for the log file", :type => String, :short => '-l', :default => Servitude::NS::DEFAULT_LOG_PATH
|
57
|
+
opt :pid, "The path for the PID file", :type => String, :default => Servitude::NS::DEFAULT_PID_PATH
|
58
|
+
opt :threads, "The number of threads", :type => Integer, :default => Servitude::NS::DEFAULT_THREAD_COUNT, :short => '-t'
|
59
|
+
end
|
60
|
+
when "start"
|
61
|
+
Trollop::options do
|
62
|
+
opt :config, "The path for the config file", :type => String, :short => '-c', :default => Servitude::NS::DEFAULT_CONFIG_PATH
|
63
|
+
opt :interactive, "Execute the server in interactive mode", :short => '-i'
|
64
|
+
opt :log_level, "The log level", :type => String, :default => 'info', :short => '-o'
|
65
|
+
opt :log, "The path for the log file", :type => String, :short => '-l', :default => Servitude::NS::DEFAULT_LOG_PATH
|
66
|
+
opt :pid, "The path for the PID file", :type => String, :default => Servitude::NS::DEFAULT_PID_PATH
|
67
|
+
opt :threads, "The number of threads", :type => Integer, :default => Servitude::NS::DEFAULT_THREAD_COUNT, :short => '-t'
|
68
|
+
end
|
69
|
+
when "status"
|
70
|
+
Trollop::options do
|
71
|
+
opt :pid, "The path for the PID file", :type => String, :default => Servitude::NS::DEFAULT_PID_PATH
|
72
|
+
end
|
73
|
+
when "stop"
|
74
|
+
Trollop::options do
|
75
|
+
opt :pid, "The path for the PID file", :type => String, :default => Servitude::NS::DEFAULT_PID_PATH
|
76
|
+
end
|
77
|
+
else
|
78
|
+
Trollop::die "unknown command #{cmd.inspect}"
|
79
|
+
end
|
80
|
+
|
81
|
+
if cmd == 'start'
|
82
|
+
unless options[:interactive]
|
83
|
+
Trollop::die( :config, "is required when running as daemon" ) unless options[:config]
|
84
|
+
Trollop::die( :log, "is required when running as daemon" ) unless options[:log]
|
85
|
+
Trollop::die( :pid, "is required when running as daemon" ) unless options[:pid]
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
if %w(restart status stop).include?( cmd )
|
90
|
+
Trollop::die( :pid, "is required" ) unless options[:pid]
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
def self.commands( &block )
|
95
|
+
end
|
96
|
+
|
97
|
+
def run
|
98
|
+
send( cmd )
|
99
|
+
end
|
100
|
+
|
101
|
+
protected
|
102
|
+
|
103
|
+
def start
|
104
|
+
if options[:interactive]
|
105
|
+
start_interactive
|
106
|
+
else
|
107
|
+
start_daemon
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
def start_interactive
|
112
|
+
server = Servitude::NS::Server.new( options.merge( log: nil ))
|
113
|
+
server.start
|
114
|
+
end
|
115
|
+
|
116
|
+
def start_daemon
|
117
|
+
server = Servitude::Daemon.new( options )
|
118
|
+
server.start
|
119
|
+
end
|
120
|
+
|
121
|
+
def stop
|
122
|
+
server = Servitude::Daemon.new( options )
|
123
|
+
server.stop
|
124
|
+
end
|
125
|
+
|
126
|
+
def restart
|
127
|
+
stop
|
128
|
+
start_daemon
|
129
|
+
end
|
130
|
+
|
131
|
+
def status
|
132
|
+
Servitude::Daemon.new( options ).status
|
133
|
+
end
|
134
|
+
|
135
|
+
end
|
136
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
module Servitude
|
2
|
+
module Configuration
|
3
|
+
|
4
|
+
def self.included( base )
|
5
|
+
base.extend ClassMethods
|
6
|
+
|
7
|
+
base.class_eval do
|
8
|
+
class << self
|
9
|
+
attr_accessor :attributes,
|
10
|
+
:configured
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
module ClassMethods
|
16
|
+
|
17
|
+
def configurations( *attrs )
|
18
|
+
raise 'Already configured: cannot call configurations more than once' if self.configured
|
19
|
+
|
20
|
+
self.attributes = attrs.map { |attr| Array( attr ).first.to_s }
|
21
|
+
|
22
|
+
class_eval do
|
23
|
+
attr_accessor( *self.attributes )
|
24
|
+
end
|
25
|
+
|
26
|
+
attrs.select { |attr| attr.is_a?( Array ) }.
|
27
|
+
each do |k, v|
|
28
|
+
|
29
|
+
define_method k do
|
30
|
+
instance_variable_get( "@#{k}" ) ||
|
31
|
+
instance_variable_set( "@#{k}", v )
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
|
36
|
+
self.configured = true
|
37
|
+
end
|
38
|
+
|
39
|
+
def from_file( file_path )
|
40
|
+
unless File.exists?( file_path )
|
41
|
+
raise "Configuration file #{file_path} does not exist"
|
42
|
+
end
|
43
|
+
|
44
|
+
options = Oj.load( File.read( file_path ))
|
45
|
+
Servitude::NS::configuration = Servitude::NS::Configuration.new
|
46
|
+
|
47
|
+
attributes.each do |c|
|
48
|
+
if options[c]
|
49
|
+
Servitude::NS::configuration.send( :"#{c}=", options[c] )
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
options[:config_loaded] = true
|
54
|
+
end
|
55
|
+
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,176 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'fileutils'
|
3
|
+
require 'timeout'
|
4
|
+
|
5
|
+
module Servitude
|
6
|
+
class Daemon
|
7
|
+
|
8
|
+
attr_reader :name,
|
9
|
+
:options,
|
10
|
+
:pid,
|
11
|
+
:pid_path,
|
12
|
+
:script,
|
13
|
+
:timeout
|
14
|
+
|
15
|
+
def initialize( options )
|
16
|
+
@options = options
|
17
|
+
@name = options[:name] || Servitude::NS::APP_NAME
|
18
|
+
@pid_path = options[:pid] || '.'
|
19
|
+
@pid = get_pid
|
20
|
+
@timeout = options[:timeout] || 10
|
21
|
+
end
|
22
|
+
|
23
|
+
def start
|
24
|
+
abort "Process already running!" if process_exists?
|
25
|
+
|
26
|
+
pid = fork do
|
27
|
+
exit if fork
|
28
|
+
Process.setsid
|
29
|
+
exit if fork
|
30
|
+
store_pid( Process.pid )
|
31
|
+
File.umask 0000
|
32
|
+
redirect_output!
|
33
|
+
run
|
34
|
+
end
|
35
|
+
|
36
|
+
Process.waitpid( pid )
|
37
|
+
end
|
38
|
+
|
39
|
+
def run
|
40
|
+
Servitude::NS::Server.new( options ).start
|
41
|
+
end
|
42
|
+
|
43
|
+
def stop
|
44
|
+
case kill_process
|
45
|
+
when :success
|
46
|
+
remove_pid
|
47
|
+
when :failed_to_stop
|
48
|
+
when :does_not_exist
|
49
|
+
puts "#{Servitude::NS::APP_NAME} process is not running"
|
50
|
+
prompt_and_remove_pid_file if pid_file_exists?
|
51
|
+
else
|
52
|
+
raise 'Unknown return code from #kill_process'
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def status
|
57
|
+
if process_exists?
|
58
|
+
puts "#{Servitude::NS::APP_NAME} process running with PID: #{pid}"
|
59
|
+
else
|
60
|
+
puts "#{Servitude::NS::APP_NAME} process does not exist"
|
61
|
+
prompt_and_remove_pid_file if pid_file_exists?
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
protected
|
66
|
+
|
67
|
+
def prompt_and_remove_pid_file
|
68
|
+
puts "PID file still exists at '#{pid_path}', would you like to remove it (y/n)?"
|
69
|
+
answer = $stdin.gets.strip
|
70
|
+
if answer == 'y'
|
71
|
+
remove_pid
|
72
|
+
puts "Removed PID file"
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def remove_pid
|
77
|
+
FileUtils.rm( pid_path ) if File.exists?( pid_path )
|
78
|
+
end
|
79
|
+
|
80
|
+
def store_pid( pid )
|
81
|
+
File.open( pid_path, 'w' ) do |f|
|
82
|
+
f.puts pid
|
83
|
+
end
|
84
|
+
rescue => e
|
85
|
+
$stderr.puts "Unable to open #{pid_path} for writing:\n\t(#{e.class}) #{e.message}"
|
86
|
+
exit!
|
87
|
+
end
|
88
|
+
|
89
|
+
def get_pid
|
90
|
+
return nil unless File.exists?( pid_path )
|
91
|
+
|
92
|
+
pid = nil
|
93
|
+
|
94
|
+
File.open( @pid_path, 'r' ) do |f|
|
95
|
+
pid = f.readline.to_s.gsub( /[^0-9]/, '' )
|
96
|
+
end
|
97
|
+
|
98
|
+
pid.to_i
|
99
|
+
rescue Errno::ENOENT
|
100
|
+
nil
|
101
|
+
end
|
102
|
+
|
103
|
+
def remove_pidfile
|
104
|
+
File.unlink( pid_path )
|
105
|
+
rescue => e
|
106
|
+
$stderr.puts "Unable to unlink #{pid_path}:\n\t(#{e.class}) #{e.message}"
|
107
|
+
exit
|
108
|
+
end
|
109
|
+
|
110
|
+
def kill_process
|
111
|
+
return :does_not_exist unless process_exists?
|
112
|
+
|
113
|
+
$stdout.write "Attempting to stop #{Servitude::NS::APP_NAME} process #{pid}..."
|
114
|
+
Process.kill INT, pid
|
115
|
+
|
116
|
+
iteration_num = 0
|
117
|
+
while process_exists? && iteration_num < 10
|
118
|
+
sleep 1
|
119
|
+
$stdout.write "."
|
120
|
+
iteration_num += 1
|
121
|
+
end
|
122
|
+
|
123
|
+
if process_exists?
|
124
|
+
$stderr.puts "\nFailed to stop #{Servitude::NS::APP_NAME} process #{pid}"
|
125
|
+
return :failed_to_stop
|
126
|
+
else
|
127
|
+
$stdout.puts "\nSuccessfuly stopped #{Servitude::NS::APP_NAME} process #{pid}"
|
128
|
+
end
|
129
|
+
|
130
|
+
return :success
|
131
|
+
rescue Errno::EPERM
|
132
|
+
$stderr.puts "No permission to query #{pid}!";
|
133
|
+
end
|
134
|
+
|
135
|
+
def pid_file_exists?
|
136
|
+
File.exists?( pid_path )
|
137
|
+
end
|
138
|
+
|
139
|
+
def process_exists?
|
140
|
+
return false unless pid
|
141
|
+
Process.kill( 0, pid )
|
142
|
+
true
|
143
|
+
rescue Errno::ESRCH, TypeError # "PID is NOT running or is zombied
|
144
|
+
false
|
145
|
+
rescue Errno::EPERM
|
146
|
+
$stderr.puts "No permission to query #{pid}!";
|
147
|
+
false
|
148
|
+
end
|
149
|
+
|
150
|
+
def redirect_output!
|
151
|
+
if log_path = options[:log]
|
152
|
+
#puts "redirecting to log"
|
153
|
+
# if the log directory doesn't exist, create it
|
154
|
+
FileUtils.mkdir_p( File.dirname( log_path ), :mode => 0755 )
|
155
|
+
# touch the log file to create it
|
156
|
+
FileUtils.touch( log_path )
|
157
|
+
# Set permissions on the log file
|
158
|
+
File.chmod( 0644, log_path )
|
159
|
+
# Reopen $stdout (NOT +STDOUT+) to start writing to the log file
|
160
|
+
$stdout.reopen( log_path, 'a' )
|
161
|
+
# Redirect $stderr to $stdout
|
162
|
+
$stderr.reopen $stdout
|
163
|
+
$stdout.sync = true
|
164
|
+
else
|
165
|
+
#puts "redirecting to /dev/null"
|
166
|
+
# We're not bothering to sync if we're dumping to /dev/null
|
167
|
+
# because /dev/null doesn't care about buffered output
|
168
|
+
$stdin.reopen '/dev/null'
|
169
|
+
$stdout.reopen '/dev/null', 'a'
|
170
|
+
$stderr.reopen $stdout
|
171
|
+
end
|
172
|
+
log_path = options[:log] ? options[:log] : '/dev/null'
|
173
|
+
end
|
174
|
+
|
175
|
+
end
|
176
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Servitude
|
2
|
+
module Logging
|
3
|
+
|
4
|
+
%w(
|
5
|
+
debug
|
6
|
+
error
|
7
|
+
fatal
|
8
|
+
info
|
9
|
+
warn
|
10
|
+
).each do |level|
|
11
|
+
|
12
|
+
define_method level do |*messages|
|
13
|
+
messages.each do |message|
|
14
|
+
Servitude::NS.logger.send level, message
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
require 'servitude'
|
2
|
+
require 'hooks'
|
3
|
+
|
4
|
+
module Servitude
|
5
|
+
module Server
|
6
|
+
|
7
|
+
def self.included( base )
|
8
|
+
base.class_eval do
|
9
|
+
include Logging
|
10
|
+
include ServerLogging
|
11
|
+
include Hooks
|
12
|
+
|
13
|
+
define_hook :after_initialize,
|
14
|
+
:before_initialize,
|
15
|
+
:before_sleep,
|
16
|
+
:finalize
|
17
|
+
|
18
|
+
attr_reader :options
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def initialize( options={} )
|
23
|
+
unless Servitude.boot_called
|
24
|
+
raise 'You must call boot before starting server'
|
25
|
+
end
|
26
|
+
|
27
|
+
@options = options
|
28
|
+
|
29
|
+
run_hook :before_initialize
|
30
|
+
initialize_loggers
|
31
|
+
run_hook :after_initialize
|
32
|
+
end
|
33
|
+
|
34
|
+
def start
|
35
|
+
log_startup
|
36
|
+
run
|
37
|
+
|
38
|
+
trap( INT ) { stop }
|
39
|
+
trap( TERM ) { stop }
|
40
|
+
|
41
|
+
run_hook :before_sleep
|
42
|
+
sleep
|
43
|
+
end
|
44
|
+
|
45
|
+
protected
|
46
|
+
|
47
|
+
def run
|
48
|
+
raise NotImplementedError
|
49
|
+
end
|
50
|
+
|
51
|
+
private
|
52
|
+
|
53
|
+
def finalize
|
54
|
+
run_hook :finalize
|
55
|
+
end
|
56
|
+
|
57
|
+
def stop
|
58
|
+
Thread.new { finalize; exit }.join
|
59
|
+
end
|
60
|
+
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
require 'yell'
|
2
|
+
|
3
|
+
# Provides logging services for the base server.
|
4
|
+
#
|
5
|
+
module Servitude
|
6
|
+
module ServerLogging
|
7
|
+
|
8
|
+
protected
|
9
|
+
|
10
|
+
def initialize_loggers
|
11
|
+
Servitude::NS.logger = Yell.new do |l|
|
12
|
+
l.level = log_level
|
13
|
+
l.adapter $stdout, :level => [:debug, :info, :warn]
|
14
|
+
l.adapter $stderr, :level => [:error, :fatal]
|
15
|
+
end
|
16
|
+
|
17
|
+
#Celluloid.logger = Yell.new do |l|
|
18
|
+
#l.level = :info
|
19
|
+
#l.adapter :file, File.join( File.dirname( options[:log] ), "#{APP_ID}-celluloid.log" )
|
20
|
+
#end
|
21
|
+
end
|
22
|
+
|
23
|
+
def log_startup
|
24
|
+
start_banner( options ).each do |line|
|
25
|
+
Servitude::NS.logger.info line
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def start_banner( options )
|
30
|
+
configuration_attributes = Servitude::NS::Configuration.attributes rescue nil
|
31
|
+
[
|
32
|
+
"",
|
33
|
+
"***",
|
34
|
+
"* #{Servitude::NS::APP_NAME} started",
|
35
|
+
"*",
|
36
|
+
"* #{Servitude::NS::VERSION_COPYRIGHT}",
|
37
|
+
"*",
|
38
|
+
"* Configuration:",
|
39
|
+
(options[:config_loaded] ? "* file: #{options[:config]}" : nil),
|
40
|
+
Array( configuration_attributes ).map { |a| "* #{a}: #{config_value a}" },
|
41
|
+
"*",
|
42
|
+
"***",
|
43
|
+
].flatten.reject( &:nil? )
|
44
|
+
end
|
45
|
+
|
46
|
+
def log_level
|
47
|
+
options.fetch( :log_level, :info ).to_sym
|
48
|
+
end
|
49
|
+
|
50
|
+
|
51
|
+
def config_value( key )
|
52
|
+
value = Servitude::NS.configuration.send( key )
|
53
|
+
|
54
|
+
return value unless value.is_a?( Hash )
|
55
|
+
|
56
|
+
return redacted_hash( value )
|
57
|
+
end
|
58
|
+
|
59
|
+
def redacted_hash( hash )
|
60
|
+
redacted_hash = {}
|
61
|
+
|
62
|
+
hash.keys.
|
63
|
+
collect( &:to_s ).
|
64
|
+
grep( /#{redacted_keys}/i ).
|
65
|
+
each do |blacklisted_key|
|
66
|
+
value = hash[blacklisted_key]
|
67
|
+
redacted_hash[blacklisted_key] = value.nil? ? nil : '[REDACTED]'
|
68
|
+
end
|
69
|
+
|
70
|
+
hash.merge( redacted_hash )
|
71
|
+
end
|
72
|
+
|
73
|
+
def redacted_keys
|
74
|
+
%w(
|
75
|
+
password
|
76
|
+
).join( '|' )
|
77
|
+
end
|
78
|
+
|
79
|
+
end
|
80
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
module Servitude
|
2
|
+
module ServerThreaded
|
3
|
+
|
4
|
+
def self.included( base )
|
5
|
+
base.class_eval do
|
6
|
+
after_initialize :initialize_thread
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
protected
|
11
|
+
|
12
|
+
def with_supervision( &block )
|
13
|
+
begin
|
14
|
+
block.call
|
15
|
+
rescue Servitude::SupervisionError
|
16
|
+
# supervisor is restarting actor
|
17
|
+
#warn ANSI.cyan { "RETRYING due to waiting on supervisor to restart actor ..." }
|
18
|
+
retry
|
19
|
+
rescue Celluloid::DeadActorError
|
20
|
+
# supervisor has yet to begin restarting actor
|
21
|
+
#warn ANSI.blue { "RETRYING due to Celluloid::DeadActorError ..." }
|
22
|
+
retry
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
# Correctly calls a single supervised actor when the threads configuraiton is set
|
27
|
+
# to 1, or a pool of actors if threads configuration is > 1. Also protects against
|
28
|
+
# a supervised actor from being nil if the supervisor is reinitializing when access
|
29
|
+
# is attempted.
|
30
|
+
#
|
31
|
+
def call_handler_respecting_thread_count( options )
|
32
|
+
if options[:threads] > 1
|
33
|
+
pool.async.call( options )
|
34
|
+
else
|
35
|
+
raise Servitude::SupervisionError unless handler
|
36
|
+
handler.call( options )
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def handler_class
|
41
|
+
raise NotImplementedError
|
42
|
+
end
|
43
|
+
|
44
|
+
def run
|
45
|
+
raise NotImplementedError
|
46
|
+
end
|
47
|
+
|
48
|
+
def pool
|
49
|
+
@pool ||= handler_class.pool( size: options[:threads] )
|
50
|
+
end
|
51
|
+
|
52
|
+
def handler
|
53
|
+
Celluloid::Actor[:handler]
|
54
|
+
end
|
55
|
+
|
56
|
+
def initialize_thread
|
57
|
+
return unless options[:threads] == 1
|
58
|
+
handler_class.supervise_as :handler
|
59
|
+
end
|
60
|
+
|
61
|
+
end
|
62
|
+
end
|
data/lib/servitude.rb
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'servitude/version'
|
2
|
+
require 'rainbow'
|
3
|
+
|
4
|
+
module Servitude
|
5
|
+
|
6
|
+
autoload :Actor, 'servitude/actor'
|
7
|
+
autoload :Base, 'servitude/base'
|
8
|
+
autoload :Cli, 'servitude/cli'
|
9
|
+
autoload :Configuration, 'servitude/configuration'
|
10
|
+
autoload :Daemon, 'servitude/daemon'
|
11
|
+
autoload :Logging, 'servitude/logging'
|
12
|
+
autoload :ServerLogging, 'servitude/server_logging'
|
13
|
+
autoload :Server, 'servitude/server'
|
14
|
+
autoload :ServerThreaded, 'servitude/server_threaded'
|
15
|
+
autoload :SupervisionError, 'servitude/supervision_error'
|
16
|
+
|
17
|
+
INT = "INT"
|
18
|
+
TERM = "TERM"
|
19
|
+
|
20
|
+
class << self
|
21
|
+
attr_accessor :boot_called
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
data/servitude.gemspec
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'servitude/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "servitude"
|
8
|
+
spec.version = Servitude::VERSION
|
9
|
+
spec.authors = ["Jason Harrelson"]
|
10
|
+
spec.email = ["jason@lookforwardenterprises.com"]
|
11
|
+
spec.summary = %q{A set of utilities to aid in building multithreaded Ruby servers utilizing Celluloid.}
|
12
|
+
spec.description = %q{A set of utilities to aid in building multithreaded Ruby servers utilizing Celluloid. See README for more details.}
|
13
|
+
spec.homepage = ""
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files`.split($/)
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_development_dependency "bundler", "~> 1.5"
|
22
|
+
spec.add_development_dependency "pry-debugger"
|
23
|
+
spec.add_development_dependency "rake"
|
24
|
+
|
25
|
+
spec.add_dependency "celluloid", "~> 0"
|
26
|
+
spec.add_dependency "hooks", "~> 0"
|
27
|
+
spec.add_dependency "oj", "~> 2"
|
28
|
+
spec.add_dependency "rainbow", "~> 2"
|
29
|
+
spec.add_dependency "trollop", "~> 2"
|
30
|
+
spec.add_dependency "yell", "~> 1"
|
31
|
+
end
|
metadata
ADDED
@@ -0,0 +1,194 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: servitude
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Jason Harrelson
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-09-11 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.5'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.5'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: pry-debugger
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rake
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: celluloid
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :runtime
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: hooks
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :runtime
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: oj
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - "~>"
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '2'
|
90
|
+
type: :runtime
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - "~>"
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '2'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: rainbow
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - "~>"
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '2'
|
104
|
+
type: :runtime
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - "~>"
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '2'
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: trollop
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - "~>"
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '2'
|
118
|
+
type: :runtime
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - "~>"
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '2'
|
125
|
+
- !ruby/object:Gem::Dependency
|
126
|
+
name: yell
|
127
|
+
requirement: !ruby/object:Gem::Requirement
|
128
|
+
requirements:
|
129
|
+
- - "~>"
|
130
|
+
- !ruby/object:Gem::Version
|
131
|
+
version: '1'
|
132
|
+
type: :runtime
|
133
|
+
prerelease: false
|
134
|
+
version_requirements: !ruby/object:Gem::Requirement
|
135
|
+
requirements:
|
136
|
+
- - "~>"
|
137
|
+
- !ruby/object:Gem::Version
|
138
|
+
version: '1'
|
139
|
+
description: A set of utilities to aid in building multithreaded Ruby servers utilizing
|
140
|
+
Celluloid. See README for more details.
|
141
|
+
email:
|
142
|
+
- jason@lookforwardenterprises.com
|
143
|
+
executables: []
|
144
|
+
extensions: []
|
145
|
+
extra_rdoc_files: []
|
146
|
+
files:
|
147
|
+
- ".gitignore"
|
148
|
+
- ".ruby-gemset"
|
149
|
+
- ".ruby-version"
|
150
|
+
- Gemfile
|
151
|
+
- LICENSE.txt
|
152
|
+
- README.md
|
153
|
+
- Rakefile
|
154
|
+
- examples/1_simple_server
|
155
|
+
- examples/2_echo_server
|
156
|
+
- lib/servitude.rb
|
157
|
+
- lib/servitude/actor.rb
|
158
|
+
- lib/servitude/base.rb
|
159
|
+
- lib/servitude/cli.rb
|
160
|
+
- lib/servitude/configuration.rb
|
161
|
+
- lib/servitude/daemon.rb
|
162
|
+
- lib/servitude/logging.rb
|
163
|
+
- lib/servitude/server.rb
|
164
|
+
- lib/servitude/server_logging.rb
|
165
|
+
- lib/servitude/server_threaded.rb
|
166
|
+
- lib/servitude/supervision_error.rb
|
167
|
+
- lib/servitude/version.rb
|
168
|
+
- servitude.gemspec
|
169
|
+
homepage: ''
|
170
|
+
licenses:
|
171
|
+
- MIT
|
172
|
+
metadata: {}
|
173
|
+
post_install_message:
|
174
|
+
rdoc_options: []
|
175
|
+
require_paths:
|
176
|
+
- lib
|
177
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
178
|
+
requirements:
|
179
|
+
- - ">="
|
180
|
+
- !ruby/object:Gem::Version
|
181
|
+
version: '0'
|
182
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
183
|
+
requirements:
|
184
|
+
- - ">="
|
185
|
+
- !ruby/object:Gem::Version
|
186
|
+
version: '0'
|
187
|
+
requirements: []
|
188
|
+
rubyforge_project:
|
189
|
+
rubygems_version: 2.2.1
|
190
|
+
signing_key:
|
191
|
+
specification_version: 4
|
192
|
+
summary: A set of utilities to aid in building multithreaded Ruby servers utilizing
|
193
|
+
Celluloid.
|
194
|
+
test_files: []
|