servitude 0.1.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.
- 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: []
|