dima-exe-juggernaut 0.5.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/Manifest.txt +11 -0
- data/README.txt +32 -0
- data/Rakefile +16 -0
- data/bin/juggernaut +4 -0
- data/lib/juggernaut/client.rb +263 -0
- data/lib/juggernaut/message.rb +19 -0
- data/lib/juggernaut/miscel.rb +27 -0
- data/lib/juggernaut/runner.rb +219 -0
- data/lib/juggernaut/server.rb +393 -0
- data/lib/juggernaut/utils.rb +11 -0
- data/lib/juggernaut.rb +158 -0
- metadata +94 -0
data/Manifest.txt
ADDED
data/README.txt
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
Juggernaut
|
2
|
+
by Alex MacCaw http://www.eribium.org
|
3
|
+
minor fixes and unit tests by Ripta Pasay
|
4
|
+
|
5
|
+
== DESCRIPTION:
|
6
|
+
See Plugin README:
|
7
|
+
http://juggernaut.rubyforge.org/svn/trunk/juggernaut/README
|
8
|
+
|
9
|
+
== LICENSE:
|
10
|
+
|
11
|
+
(The MIT License)
|
12
|
+
|
13
|
+
Copyright (c) 2008 Alex MacCaw
|
14
|
+
|
15
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
16
|
+
a copy of this software and associated documentation files (the
|
17
|
+
'Software'), to deal in the Software without restriction, including
|
18
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
19
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
20
|
+
permit persons to whom the Software is furnished to do so, subject to
|
21
|
+
the following conditions:
|
22
|
+
|
23
|
+
The above copyright notice and this permission notice shall be
|
24
|
+
included in all copies or substantial portions of the Software.
|
25
|
+
|
26
|
+
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
27
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
28
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
29
|
+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
30
|
+
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
31
|
+
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
32
|
+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/Rakefile
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
# -*- ruby -*-
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'hoe'
|
5
|
+
require './lib/juggernaut.rb'
|
6
|
+
|
7
|
+
Hoe.new('juggernaut', Juggernaut::VERSION) do |p|
|
8
|
+
p.rubyforge_name = 'juggernaut'
|
9
|
+
p.author = 'Alex MacCaw'
|
10
|
+
p.email = 'info@eribium.org'
|
11
|
+
p.url = 'http://juggernaut.rubyforge.org'
|
12
|
+
p.extra_deps << ['eventmachine', '>=0.10.0']
|
13
|
+
p.extra_deps << ['json', '>=1.1.2']
|
14
|
+
end
|
15
|
+
|
16
|
+
# vim: syntax=Ruby
|
data/bin/juggernaut
ADDED
@@ -0,0 +1,263 @@
|
|
1
|
+
require 'timeout'
|
2
|
+
require 'net/http'
|
3
|
+
require 'net/https'
|
4
|
+
require 'uri'
|
5
|
+
|
6
|
+
module Juggernaut
|
7
|
+
class Client
|
8
|
+
include Juggernaut::Miscel
|
9
|
+
|
10
|
+
@@clients = [ ]
|
11
|
+
|
12
|
+
attr_reader :id
|
13
|
+
attr_accessor :session_id
|
14
|
+
attr_reader :connections
|
15
|
+
|
16
|
+
class << self
|
17
|
+
# Actually does a find_or_create_by_id
|
18
|
+
def find_or_create(subscriber, request)
|
19
|
+
if client = find_by_id(request[:client_id])
|
20
|
+
client.session_id = request[:session_id]
|
21
|
+
client.add_new_connection(subscriber)
|
22
|
+
client
|
23
|
+
else
|
24
|
+
self.new(subscriber, request)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
# Client find methods
|
29
|
+
def find_all
|
30
|
+
@@clients
|
31
|
+
end
|
32
|
+
|
33
|
+
def find(&block)
|
34
|
+
@@clients.select(&block).uniq
|
35
|
+
end
|
36
|
+
|
37
|
+
def find_by_id(id)
|
38
|
+
find { |client| client.id == id }.first
|
39
|
+
end
|
40
|
+
|
41
|
+
def find_by_signature(signature)
|
42
|
+
# signature should be unique
|
43
|
+
find do |client|
|
44
|
+
client.connections.select { |connection| connection.signature == signature }.any?
|
45
|
+
end.first
|
46
|
+
end
|
47
|
+
|
48
|
+
def find_by_channels(channels)
|
49
|
+
find do |client|
|
50
|
+
client.has_channels?(channels)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def find_by_id_and_channels(id, channels)
|
55
|
+
find do |client|
|
56
|
+
client.has_channels?(channels) && client.id == id
|
57
|
+
end.first
|
58
|
+
end
|
59
|
+
|
60
|
+
def send_logouts_after_timeout
|
61
|
+
@@clients.each do |client|
|
62
|
+
if client.give_up?
|
63
|
+
client.logout_request
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
# Called when the server is shutting down
|
69
|
+
def send_logouts_to_all_clients
|
70
|
+
@@clients.each do |client|
|
71
|
+
client.logout_request
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def reset!
|
76
|
+
@@clients.clear
|
77
|
+
end
|
78
|
+
|
79
|
+
def register_client(client)
|
80
|
+
@@clients << client unless @@clients.include?(client)
|
81
|
+
end
|
82
|
+
|
83
|
+
def client_registered?(client)
|
84
|
+
@@clients.include?(client)
|
85
|
+
end
|
86
|
+
|
87
|
+
def unregister_client(client)
|
88
|
+
@@clients.delete(client)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def initialize(subscriber, request)
|
93
|
+
@connections = []
|
94
|
+
@id = request[:client_id]
|
95
|
+
@session_id = request[:session_id]
|
96
|
+
@messages = []
|
97
|
+
@logout_timeout = 0
|
98
|
+
self.register
|
99
|
+
add_new_connection(subscriber)
|
100
|
+
end
|
101
|
+
|
102
|
+
def to_json
|
103
|
+
{
|
104
|
+
:client_id => @id,
|
105
|
+
:num_connections => @connections.size,
|
106
|
+
:session_id => @session_id
|
107
|
+
}.to_json
|
108
|
+
end
|
109
|
+
|
110
|
+
def add_new_connection(subscriber)
|
111
|
+
@connections << subscriber
|
112
|
+
end
|
113
|
+
|
114
|
+
def friendly_id
|
115
|
+
if self.id
|
116
|
+
"with ID #{self.id}"
|
117
|
+
else
|
118
|
+
"session #{self.session_id}"
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
def subscription_request(channels)
|
123
|
+
return true unless options[:subscription_url]
|
124
|
+
post_request(options[:subscription_url], channels, :timeout => options[:post_request_timeout] || 5)
|
125
|
+
end
|
126
|
+
|
127
|
+
def logout_connection_request(channels)
|
128
|
+
return true unless options[:logout_connection_url]
|
129
|
+
post_request(options[:logout_connection_url], channels, :timeout => options[:post_request_timeout] || 5)
|
130
|
+
end
|
131
|
+
|
132
|
+
def logout_request
|
133
|
+
self.unregister
|
134
|
+
logger.debug("Timed out client #{friendly_id}")
|
135
|
+
return true unless options[:logout_url]
|
136
|
+
post_request(options[:logout_url], [ ], :timeout => options[:post_request_timeout] || 5)
|
137
|
+
end
|
138
|
+
|
139
|
+
def remove_connection(connection)
|
140
|
+
@connections.delete(connection)
|
141
|
+
self.reset_logout_timeout!
|
142
|
+
self.logout_request if self.give_up?
|
143
|
+
end
|
144
|
+
|
145
|
+
def send_message(msg, channels = nil)
|
146
|
+
store_message(msg, channels) if options[:store_messages]
|
147
|
+
send_message_to_connections(msg, channels)
|
148
|
+
end
|
149
|
+
|
150
|
+
# Send messages that are queued up for this particular client.
|
151
|
+
# Messages are only queued for previously-connected clients.
|
152
|
+
def send_queued_messages(connection)
|
153
|
+
self.expire_queued_messages!
|
154
|
+
|
155
|
+
# Weird looping because we don't want to send messages that get
|
156
|
+
# added to the array after we start iterating (since those will
|
157
|
+
# get sent to the client anyway).
|
158
|
+
@length = @messages.length
|
159
|
+
|
160
|
+
logger.debug("Sending #{@length} queued message(s) to client #{friendly_id}")
|
161
|
+
|
162
|
+
@length.times do |id|
|
163
|
+
message = @messages[id]
|
164
|
+
send_message_to_connection(connection, message[:message], message[:channels])
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
def channels
|
169
|
+
@connections.collect { |em| em.channels }.flatten.uniq
|
170
|
+
end
|
171
|
+
|
172
|
+
def has_channels?(channels)
|
173
|
+
@connections.each do |em|
|
174
|
+
return true if em.has_channels?(channels)
|
175
|
+
end
|
176
|
+
false
|
177
|
+
end
|
178
|
+
|
179
|
+
def remove_channels!(channels)
|
180
|
+
@connections.each do |em|
|
181
|
+
em.remove_channels!(channels)
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
def alive?
|
186
|
+
@connections.select { |em| em.alive? }.any?
|
187
|
+
end
|
188
|
+
|
189
|
+
# This client is only dead if there are no connections and we are
|
190
|
+
# past the timeout (if we are within the timeout, the user could
|
191
|
+
# just be doing a page reload or going to a new page)
|
192
|
+
def give_up?
|
193
|
+
!alive? and (Time.now > @logout_timeout)
|
194
|
+
end
|
195
|
+
|
196
|
+
protected
|
197
|
+
|
198
|
+
def register
|
199
|
+
self.class.register_client(self)
|
200
|
+
end
|
201
|
+
|
202
|
+
def registered?
|
203
|
+
self.class.client_registered?(self)
|
204
|
+
end
|
205
|
+
|
206
|
+
def unregister
|
207
|
+
self.class.unregister_client(self)
|
208
|
+
end
|
209
|
+
|
210
|
+
def post_request(url, channels = [ ], options = { })
|
211
|
+
uri = URI.parse(url)
|
212
|
+
uri.path = '/' if uri.path == ''
|
213
|
+
params = []
|
214
|
+
params << "client_id=#{id}" if id
|
215
|
+
params << "session_id=#{session_id}" if session_id
|
216
|
+
channels.each {|chan| params << "channels[]=#{chan}" }
|
217
|
+
headers = {"User-Agent" => "Ruby/#{RUBY_VERSION}"}
|
218
|
+
begin
|
219
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
220
|
+
if uri.scheme == 'https'
|
221
|
+
http.use_ssl = true
|
222
|
+
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
223
|
+
end
|
224
|
+
http.read_timeout = options[:timeout] || 5
|
225
|
+
resp, data = http.post(uri.path, params.join('&'), headers)
|
226
|
+
return resp.is_a?(Net::HTTPOK)
|
227
|
+
rescue => e
|
228
|
+
logger.error("Bad request #{url.to_s} (#{e.class}: #{e.message})")
|
229
|
+
return false
|
230
|
+
rescue Timeout::Error
|
231
|
+
logger.error("#{url.to_s} timeout")
|
232
|
+
return false
|
233
|
+
end
|
234
|
+
end
|
235
|
+
|
236
|
+
def send_message_to_connections(msg, channels)
|
237
|
+
@connections.each do |connection|
|
238
|
+
send_message_to_connection(connection, msg, channels)
|
239
|
+
end
|
240
|
+
end
|
241
|
+
|
242
|
+
def send_message_to_connection(connection, msg, channels)
|
243
|
+
connection.broadcast(msg) if !channels or channels.empty? or connection.has_channels?(channels)
|
244
|
+
end
|
245
|
+
|
246
|
+
# Queued messages are stored until a timeout is reached which is the
|
247
|
+
# same as the connection timeout. This takes care of messages that
|
248
|
+
# come in between page loads or ones that come in right when you are
|
249
|
+
# clicking off one page and loading the next one.
|
250
|
+
def store_message(msg, channels)
|
251
|
+
self.expire_queued_messages!
|
252
|
+
@messages << { :channels => channels, :message => msg, :timeout => Time.now + options[:timeout] }
|
253
|
+
end
|
254
|
+
|
255
|
+
def expire_queued_messages!
|
256
|
+
@messages.reject! { |message| Time.now > message[:timeout] }
|
257
|
+
end
|
258
|
+
|
259
|
+
def reset_logout_timeout!
|
260
|
+
@logout_timeout = Time.now + options[:timeout]
|
261
|
+
end
|
262
|
+
end
|
263
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Juggernaut
|
2
|
+
class Message
|
3
|
+
attr_accessor :id
|
4
|
+
attr_accessor :signature
|
5
|
+
attr_accessor :body
|
6
|
+
attr_reader :created_at
|
7
|
+
|
8
|
+
def initialize(id, body, signature)
|
9
|
+
@id = id
|
10
|
+
@body = body
|
11
|
+
@signature = signature
|
12
|
+
@created_at = Time.now
|
13
|
+
end
|
14
|
+
|
15
|
+
def to_s
|
16
|
+
{ :id => @id.to_s, :body => @body, :signature => @signature }.to_json
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module Juggernaut
|
2
|
+
module Miscel
|
3
|
+
def options
|
4
|
+
Juggernaut.options
|
5
|
+
end
|
6
|
+
|
7
|
+
def options=(ob)
|
8
|
+
Juggernaut.options = ob
|
9
|
+
end
|
10
|
+
|
11
|
+
def log_path
|
12
|
+
Juggernaut.log_path
|
13
|
+
end
|
14
|
+
|
15
|
+
def pid_path
|
16
|
+
Juggernaut.pid_path
|
17
|
+
end
|
18
|
+
|
19
|
+
def config_path
|
20
|
+
Juggernaut.config_path
|
21
|
+
end
|
22
|
+
|
23
|
+
def logger
|
24
|
+
Juggernaut.logger
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,219 @@
|
|
1
|
+
require 'optparse'
|
2
|
+
require 'yaml'
|
3
|
+
require 'erb'
|
4
|
+
|
5
|
+
module Juggernaut
|
6
|
+
class Runner
|
7
|
+
include Juggernaut::Miscel
|
8
|
+
|
9
|
+
class << self
|
10
|
+
def run(argv = ARGV)
|
11
|
+
self.new(argv)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def initialize(argv = ARGV)
|
16
|
+
self.options = {
|
17
|
+
:host => "0.0.0.0",
|
18
|
+
:port => 5001,
|
19
|
+
:debug => false,
|
20
|
+
:cleanup_timer => 2,
|
21
|
+
:timeout => 10,
|
22
|
+
:store_messages => false
|
23
|
+
}
|
24
|
+
|
25
|
+
self.options.merge!({
|
26
|
+
:pid_path => pid_path,
|
27
|
+
:log_path => log_path,
|
28
|
+
:config_path => config_path
|
29
|
+
})
|
30
|
+
|
31
|
+
parse_options(argv)
|
32
|
+
|
33
|
+
if !File.exists?(config_path)
|
34
|
+
puts "You must generate a config file (juggernaut -g filename.yml)"
|
35
|
+
exit
|
36
|
+
end
|
37
|
+
|
38
|
+
options.merge!(YAML::load(ERB.new(IO.read(config_path)).result))
|
39
|
+
|
40
|
+
if options.include?(:kill)
|
41
|
+
kill_pid(options[:kill] || '*')
|
42
|
+
end
|
43
|
+
|
44
|
+
Process.euid = options[:user] if options[:user]
|
45
|
+
Process.egid = options[:group] if options[:group]
|
46
|
+
|
47
|
+
if !options[:daemonize]
|
48
|
+
start
|
49
|
+
else
|
50
|
+
daemonize
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def start
|
55
|
+
puts "Starting Juggernaut server #{Juggernaut::VERSION} on port: #{options[:port]}..."
|
56
|
+
|
57
|
+
trap("INT") {
|
58
|
+
stop
|
59
|
+
exit
|
60
|
+
}
|
61
|
+
trap("TERM"){
|
62
|
+
stop
|
63
|
+
exit
|
64
|
+
}
|
65
|
+
|
66
|
+
if options[:descriptor_table_size]
|
67
|
+
EM.epoll
|
68
|
+
new_size = EM.set_descriptor_table_size( options[:descriptor_table_size] )
|
69
|
+
logger.debug "New descriptor-table size is #{new_size}"
|
70
|
+
end
|
71
|
+
|
72
|
+
EventMachine::run {
|
73
|
+
EventMachine::add_periodic_timer( options[:cleanup_timer] || 2 ) { Juggernaut::Client.send_logouts_after_timeout }
|
74
|
+
EventMachine::start_server(options[:host], options[:port].to_i, Juggernaut::Server)
|
75
|
+
EM.set_effective_user( options[:user] ) if options[:user]
|
76
|
+
}
|
77
|
+
end
|
78
|
+
|
79
|
+
def stop
|
80
|
+
puts "Stopping Juggernaut server"
|
81
|
+
Juggernaut::Client.send_logouts_to_all_clients
|
82
|
+
EventMachine::stop
|
83
|
+
end
|
84
|
+
|
85
|
+
def parse_options(argv)
|
86
|
+
OptionParser.new do |opts|
|
87
|
+
opts.summary_width = 25
|
88
|
+
opts.banner = "Juggernaut (#{VERSION})\n\n",
|
89
|
+
"Usage: juggernaut [-h host] [-p port] [-P file]\n",
|
90
|
+
" [-d] [-k port] [-l file] [-e]\n",
|
91
|
+
" juggernaut --help\n",
|
92
|
+
" juggernaut --version\n"
|
93
|
+
|
94
|
+
opts.separator ""
|
95
|
+
opts.separator ""; opts.separator "Configuration:"
|
96
|
+
|
97
|
+
opts.on("-g", "--generate FILE", String, "Generate config file", "(default: #{options[:config_path]})") do |v|
|
98
|
+
options[:config_path] = File.expand_path(v) if v
|
99
|
+
generate_config_file
|
100
|
+
end
|
101
|
+
|
102
|
+
opts.on("-c", "--config FILE", String, "Path to configuration file.", "(default: #{options[:config_path]})") do |v|
|
103
|
+
options[:config_path] = File.expand_path(v)
|
104
|
+
end
|
105
|
+
|
106
|
+
opts.separator ""; opts.separator "Network:"
|
107
|
+
|
108
|
+
opts.on("-h", "--host HOST", String, "Specify host", "(default: #{options[:host]})") do |v|
|
109
|
+
options[:host] = v
|
110
|
+
end
|
111
|
+
|
112
|
+
opts.on("-p", "--port PORT", Integer, "Specify port", "(default: #{options[:port]})") do |v|
|
113
|
+
options[:port] = v
|
114
|
+
end
|
115
|
+
|
116
|
+
opts.on("-s", "--fdsize SIZE", Integer, "Set the file descriptor size an user epoll() on Linux", "(default: use select() which is limited to 1024 clients)") do |v|
|
117
|
+
options[:descriptor_table_size] = v
|
118
|
+
end
|
119
|
+
|
120
|
+
opts.separator ""; opts.separator "Daemonization:"
|
121
|
+
|
122
|
+
opts.on("-P", "--pid FILE", String, "save PID in FILE when using -d option.", "(default: #{options[:pid_path]})") do |v|
|
123
|
+
options[:pid_path] = File.expand_path(v)
|
124
|
+
end
|
125
|
+
|
126
|
+
opts.on("-d", "--daemon", "Daemonize mode") do |v|
|
127
|
+
options[:daemonize] = v
|
128
|
+
end
|
129
|
+
|
130
|
+
opts.on("-k", "--kill PORT", String, :OPTIONAL, "Kill specified running daemons - leave blank to kill all.") do |v|
|
131
|
+
options[:kill] = v
|
132
|
+
end
|
133
|
+
|
134
|
+
opts.separator ""; opts.separator "Logging:"
|
135
|
+
|
136
|
+
opts.on("-l", "--log [FILE]", String, "Path to print debugging information.", "(default: #{options[:log_path]})") do |v|
|
137
|
+
options[:log_path] = File.expand_path(v)
|
138
|
+
end
|
139
|
+
|
140
|
+
opts.on("-e", "--debug", "Run in debug mode", "(default: #{options[:debug]})") do |v|
|
141
|
+
options[:debug] = v
|
142
|
+
end
|
143
|
+
|
144
|
+
opts.separator ""; opts.separator "Permissions:"
|
145
|
+
|
146
|
+
opts.on("-u", "--user USER", Integer, "User to run as") do |user|
|
147
|
+
options[:user] = user
|
148
|
+
end
|
149
|
+
|
150
|
+
opts.on("-G", "--group GROUP", String, "Group to run as") do |group|
|
151
|
+
options[:group] = group
|
152
|
+
end
|
153
|
+
|
154
|
+
opts.separator ""; opts.separator "Miscellaneous:"
|
155
|
+
|
156
|
+
opts.on_tail("-?", "--help", "Display this usage information.") do
|
157
|
+
puts "#{opts}\n"
|
158
|
+
exit
|
159
|
+
end
|
160
|
+
|
161
|
+
opts.on_tail("-v", "--version", "Display version") do |v|
|
162
|
+
puts "Juggernaut #{VERSION}"
|
163
|
+
exit
|
164
|
+
end
|
165
|
+
end.parse!(argv)
|
166
|
+
options
|
167
|
+
end
|
168
|
+
|
169
|
+
private
|
170
|
+
|
171
|
+
def generate_config_file
|
172
|
+
if File.exists?(config_path)
|
173
|
+
puts "Config file already exists. You must remove it before generating a new one."
|
174
|
+
exit
|
175
|
+
end
|
176
|
+
puts "Generating config file...."
|
177
|
+
File.open(config_path, 'w+') do |file|
|
178
|
+
file.write DEFAULT_CONFIG_FILE.gsub('your_secret_key_here', Digest::SHA1.hexdigest("--#{Time.now.to_s.split(//).sort_by {rand}.join}--"))
|
179
|
+
end
|
180
|
+
puts "Config file generated at #{config_path}"
|
181
|
+
exit
|
182
|
+
end
|
183
|
+
|
184
|
+
def store_pid(pid)
|
185
|
+
FileUtils.mkdir_p(File.dirname(pid_path))
|
186
|
+
File.open(pid_path, 'w'){|f| f.write("#{pid}\n")}
|
187
|
+
end
|
188
|
+
|
189
|
+
def kill_pid(k)
|
190
|
+
Dir[options[:pid_path]||File.join(File.dirname(pid_dir), "juggernaut.#{k}.pid")].each do |f|
|
191
|
+
begin
|
192
|
+
puts f
|
193
|
+
pid = IO.read(f).chomp.to_i
|
194
|
+
FileUtils.rm f
|
195
|
+
Process.kill(9, pid)
|
196
|
+
puts "killed PID: #{pid}"
|
197
|
+
rescue => e
|
198
|
+
puts "Failed to kill! #{k}: #{e}"
|
199
|
+
end
|
200
|
+
end
|
201
|
+
exit
|
202
|
+
end
|
203
|
+
|
204
|
+
def daemonize
|
205
|
+
fork do
|
206
|
+
Process.setsid
|
207
|
+
exit if fork
|
208
|
+
store_pid(Process.pid)
|
209
|
+
# Dir.chdir "/" # Mucks up logs
|
210
|
+
File.umask 0000
|
211
|
+
STDIN.reopen "/dev/null"
|
212
|
+
STDOUT.reopen "/dev/null", "a"
|
213
|
+
STDERR.reopen STDOUT
|
214
|
+
start
|
215
|
+
end
|
216
|
+
end
|
217
|
+
|
218
|
+
end
|
219
|
+
end
|
@@ -0,0 +1,393 @@
|
|
1
|
+
require 'eventmachine'
|
2
|
+
require 'socket'
|
3
|
+
require 'json'
|
4
|
+
require 'open-uri'
|
5
|
+
require 'fileutils'
|
6
|
+
require 'digest/sha1'
|
7
|
+
|
8
|
+
module Juggernaut
|
9
|
+
class Server < EventMachine::Connection
|
10
|
+
include Juggernaut::Miscel
|
11
|
+
|
12
|
+
class InvalidRequest < Juggernaut::JuggernautError #:nodoc:
|
13
|
+
end
|
14
|
+
|
15
|
+
class InvalidCommand < Juggernaut::JuggernautError #:nodoc:
|
16
|
+
end
|
17
|
+
|
18
|
+
class CorruptJSON < Juggernaut::JuggernautError #:nodoc:
|
19
|
+
end
|
20
|
+
|
21
|
+
class MalformedBroadcast < Juggernaut::JuggernautError #:nodoc:
|
22
|
+
end
|
23
|
+
|
24
|
+
class MalformedSubscribe < Juggernaut::JuggernautError #:nodoc:
|
25
|
+
end
|
26
|
+
|
27
|
+
class UnauthorisedSubscription < Juggernaut::JuggernautError #:nodoc:
|
28
|
+
end
|
29
|
+
|
30
|
+
class MalformedQuery < Juggernaut::JuggernautError #:nodoc:
|
31
|
+
end
|
32
|
+
|
33
|
+
class UnauthorisedBroadcast < Juggernaut::JuggernautError #:nodoc:
|
34
|
+
end
|
35
|
+
|
36
|
+
class UnauthorisedQuery < Juggernaut::JuggernautError #:nodoc:
|
37
|
+
end
|
38
|
+
|
39
|
+
POLICY_FILE = <<-EOF
|
40
|
+
<cross-domain-policy>
|
41
|
+
<allow-access-from domain="*" to-ports="PORT" />
|
42
|
+
</cross-domain-policy>
|
43
|
+
EOF
|
44
|
+
|
45
|
+
POLICY_REQUEST = "<policy-file-request/>"
|
46
|
+
|
47
|
+
CR = "\0"
|
48
|
+
|
49
|
+
attr_reader :current_msg_id
|
50
|
+
attr_reader :messages
|
51
|
+
attr_reader :connected
|
52
|
+
attr_reader :logout_timeout
|
53
|
+
attr_reader :status
|
54
|
+
attr_reader :channels
|
55
|
+
attr_reader :client
|
56
|
+
|
57
|
+
# EM methods
|
58
|
+
|
59
|
+
def post_init
|
60
|
+
logger.debug "New client [#{client_ip}]"
|
61
|
+
@client = nil
|
62
|
+
@channels = []
|
63
|
+
@current_msg_id = 0
|
64
|
+
@connected = true
|
65
|
+
@logout_timeout = nil
|
66
|
+
@buffer = ''
|
67
|
+
@http_request = false
|
68
|
+
end
|
69
|
+
|
70
|
+
# Juggernaut packets are terminated with "\0"
|
71
|
+
# so we need to buffer the data until we find the
|
72
|
+
# terminating "\0"
|
73
|
+
def receive_data(data)
|
74
|
+
@buffer << data
|
75
|
+
@buffer = process_whole_messages(@buffer)
|
76
|
+
end
|
77
|
+
|
78
|
+
def strip_http_if_need(data)
|
79
|
+
return data if data !~ /^POST /
|
80
|
+
out = data.split("\r\n\r\n")
|
81
|
+
if out.size == 2
|
82
|
+
out = out.last
|
83
|
+
@http_request = true
|
84
|
+
out.strip + "\0"
|
85
|
+
else
|
86
|
+
data
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
# process any whole messages in the buffer,
|
91
|
+
# and return the new contents of the buffer
|
92
|
+
def process_whole_messages(data)
|
93
|
+
data = strip_http_if_need(data)
|
94
|
+
return data if data !~ /\0/ # only process if data contains a \0 char
|
95
|
+
messages = data.split("\0")
|
96
|
+
if data =~ /\0$/
|
97
|
+
data = ''
|
98
|
+
else
|
99
|
+
# remove the last message from the list (because it is incomplete) before processing
|
100
|
+
data = messages.pop
|
101
|
+
end
|
102
|
+
messages.each {|message| process_message(message.strip)}
|
103
|
+
return data
|
104
|
+
end
|
105
|
+
|
106
|
+
def process_message(ln)
|
107
|
+
logger.debug "Processing message: #{ln}"
|
108
|
+
@request = nil
|
109
|
+
|
110
|
+
if ln == POLICY_REQUEST
|
111
|
+
logger.debug "Sending crossdomain file"
|
112
|
+
send_data POLICY_FILE.gsub('PORT', (options[:public_port]||options[:port]).to_s)
|
113
|
+
close_connection_after_writing
|
114
|
+
return
|
115
|
+
end
|
116
|
+
|
117
|
+
begin
|
118
|
+
@request = JSON.parse(ln) unless ln.empty?
|
119
|
+
rescue
|
120
|
+
raise CorruptJSON, ln
|
121
|
+
end
|
122
|
+
|
123
|
+
raise InvalidRequest, ln if !@request
|
124
|
+
|
125
|
+
@request.symbolize_keys!
|
126
|
+
|
127
|
+
# For debugging
|
128
|
+
@request[:ip] = client_ip
|
129
|
+
|
130
|
+
@request[:channels] = (@request[:channels] || []).compact.select {|c| !!c && c != '' }.uniq
|
131
|
+
|
132
|
+
if @request[:client_ids]
|
133
|
+
@request[:client_ids] = @request[:client_ids].to_a.compact.select {|c| !!c && c != '' }.uniq
|
134
|
+
end
|
135
|
+
|
136
|
+
case @request[:command].to_sym
|
137
|
+
when :broadcast: broadcast_command
|
138
|
+
when :subscribe: subscribe_command
|
139
|
+
when :query: query_command
|
140
|
+
when :noop: noop_command
|
141
|
+
else
|
142
|
+
raise InvalidCommand, @request
|
143
|
+
end
|
144
|
+
|
145
|
+
rescue JuggernautError => e
|
146
|
+
logger.error("#{e} - #{e.message.inspect}")
|
147
|
+
close_connection
|
148
|
+
# So as to stop em quitting
|
149
|
+
rescue => e
|
150
|
+
logger ? logger.error(e) : puts(e)
|
151
|
+
end
|
152
|
+
|
153
|
+
def unbind
|
154
|
+
if @client
|
155
|
+
# todo - should be called after timeout?
|
156
|
+
@client.logout_connection_request(@channels)
|
157
|
+
logger.debug "Lost client #{@client.friendly_id}"
|
158
|
+
end
|
159
|
+
mark_dead('Unbind called')
|
160
|
+
end
|
161
|
+
|
162
|
+
def add_http_response_headers(msg)
|
163
|
+
h = []
|
164
|
+
h << "HTTP/1.1 200 OK"
|
165
|
+
h << "Cache-Control: private, max-age=0, must-revalidate"
|
166
|
+
h << "Content-Type: application/javascript"
|
167
|
+
h = h.join("\r\n")
|
168
|
+
h + "\r\n\r\n" + msg.to_s
|
169
|
+
end
|
170
|
+
|
171
|
+
# As far as I'm aware, send_data
|
172
|
+
# never throws an exception
|
173
|
+
def publish(msg)
|
174
|
+
logger.debug "Sending msg: #{msg.to_s} to client #{@request[:client_id]} (session #{@request[:session_id]})"
|
175
|
+
send_data(msg.to_s + CR)
|
176
|
+
end
|
177
|
+
|
178
|
+
# Connection methods
|
179
|
+
|
180
|
+
def broadcast(bdy)
|
181
|
+
msg = Juggernaut::Message.new(@current_msg_id += 1, bdy, self.signature)
|
182
|
+
if @http_request
|
183
|
+
msg = add_http_response_headers(msg)
|
184
|
+
send_data(msg)
|
185
|
+
close_connection_after_writing
|
186
|
+
else
|
187
|
+
publish(msg)
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
def mark_dead(reason = "Unknown error")
|
192
|
+
# Once dead, a client never recovers since a reconnection
|
193
|
+
# attempt would hook onto a new em instance. A client
|
194
|
+
# usually dies through an unbind
|
195
|
+
@connected = false
|
196
|
+
@client.remove_connection(self) if @client
|
197
|
+
end
|
198
|
+
|
199
|
+
def alive?
|
200
|
+
@connected == true
|
201
|
+
end
|
202
|
+
|
203
|
+
def has_channels?(channels)
|
204
|
+
channels.each {|channel|
|
205
|
+
return true if has_channel?(channel)
|
206
|
+
}
|
207
|
+
false
|
208
|
+
end
|
209
|
+
|
210
|
+
def has_channel?(channel)
|
211
|
+
@channels.include?(channel)
|
212
|
+
end
|
213
|
+
|
214
|
+
def add_channel(chan_name)
|
215
|
+
return if !chan_name or chan_name == ''
|
216
|
+
@channels << chan_name unless has_channel?(chan_name)
|
217
|
+
end
|
218
|
+
|
219
|
+
def add_channels(chan_names)
|
220
|
+
chan_names.to_a.each do |chan_name|
|
221
|
+
add_channel(chan_name)
|
222
|
+
end
|
223
|
+
end
|
224
|
+
|
225
|
+
def remove_channel!(chan_name)
|
226
|
+
@channels.delete(chan_name)
|
227
|
+
end
|
228
|
+
|
229
|
+
def remove_channels!(chan_names)
|
230
|
+
chan_names.to_a.each do |chan_name|
|
231
|
+
remove_channel!(chan_name)
|
232
|
+
end
|
233
|
+
end
|
234
|
+
|
235
|
+
protected
|
236
|
+
|
237
|
+
# Commands
|
238
|
+
|
239
|
+
def broadcast_command
|
240
|
+
raise MalformedBroadcast, @request unless @request[:type]
|
241
|
+
|
242
|
+
raise UnauthorisedBroadcast, @request unless authenticate_broadcast_or_query
|
243
|
+
|
244
|
+
case @request[:type].to_sym
|
245
|
+
when :to_channels
|
246
|
+
# if channels is a blank array, sends to everybody!
|
247
|
+
broadcast_to_channels(@request[:body], @request[:channels])
|
248
|
+
when :to_clients
|
249
|
+
broadcast_needs :client_ids
|
250
|
+
@request[:client_ids].each do |client_id|
|
251
|
+
# if channels aren't empty, scopes broadcast to clients on those channels
|
252
|
+
broadcast_to_client(@request[:body], client_id, @request[:channels])
|
253
|
+
end
|
254
|
+
else
|
255
|
+
raise MalformedBroadcast, @request
|
256
|
+
end
|
257
|
+
end
|
258
|
+
|
259
|
+
def query_command
|
260
|
+
raise MalformedQuery, @request unless @request[:type]
|
261
|
+
|
262
|
+
raise UnauthorisedQuery, @request unless authenticate_broadcast_or_query
|
263
|
+
|
264
|
+
case @request[:type].to_sym
|
265
|
+
when :remove_channels_from_all_clients
|
266
|
+
query_needs :channels
|
267
|
+
clients = Juggernaut::Client.find_all
|
268
|
+
clients.each {|client| client.remove_channels!(@request[:channels]) }
|
269
|
+
when :remove_channels_from_client
|
270
|
+
query_needs :client_ids, :channels
|
271
|
+
@request[:client_ids].each do |client_id|
|
272
|
+
client = Juggernaut::Client.find_by_id(client_id)
|
273
|
+
client.remove_channels!(@request[:channels]) if client
|
274
|
+
end
|
275
|
+
when :show_channels_for_client
|
276
|
+
query_needs :client_id
|
277
|
+
if client = Juggernaut::Client.find_by_id(@request[:client_id])
|
278
|
+
publish client.channels.to_json
|
279
|
+
else
|
280
|
+
publish nil.to_json
|
281
|
+
end
|
282
|
+
when :show_clients
|
283
|
+
if @request[:client_ids] and @request[:client_ids].any?
|
284
|
+
clients = @request[:client_ids].collect{ |client_id| Client.find_by_id(client_id) }.compact.uniq
|
285
|
+
else
|
286
|
+
clients = Juggernaut::Client.find_all
|
287
|
+
end
|
288
|
+
publish clients.to_json
|
289
|
+
when :show_client
|
290
|
+
query_needs :client_id
|
291
|
+
publish Juggernaut::Client.find_by_id(@request[:client_id]).to_json
|
292
|
+
when :show_clients_for_channels
|
293
|
+
query_needs :channels
|
294
|
+
publish Juggernaut::Client.find_by_channels(@request[:channels]).to_json
|
295
|
+
else
|
296
|
+
raise MalformedQuery, @request
|
297
|
+
end
|
298
|
+
end
|
299
|
+
|
300
|
+
def noop_command
|
301
|
+
logger.debug "NOOP"
|
302
|
+
end
|
303
|
+
|
304
|
+
def subscribe_command
|
305
|
+
logger.debug "SUBSCRIBE: #{@request.inspect}"
|
306
|
+
|
307
|
+
if channels = @request[:channels]
|
308
|
+
add_channels(channels)
|
309
|
+
end
|
310
|
+
|
311
|
+
@client = Juggernaut::Client.find_or_create(self, @request)
|
312
|
+
|
313
|
+
if !@client.subscription_request(@channels)
|
314
|
+
raise UnauthorisedSubscription, @client
|
315
|
+
end
|
316
|
+
|
317
|
+
if options[:store_messages]
|
318
|
+
@client.send_queued_messages(self)
|
319
|
+
end
|
320
|
+
end
|
321
|
+
|
322
|
+
private
|
323
|
+
|
324
|
+
# Different broadcast types
|
325
|
+
|
326
|
+
def broadcast_to_channels(msg, channels = [])
|
327
|
+
Juggernaut::Client.find_all.each {|client| client.send_message(msg, channels) }
|
328
|
+
end
|
329
|
+
|
330
|
+
def broadcast_to_client(body, client_id, channels)
|
331
|
+
client = Juggernaut::Client.find_by_id(client_id)
|
332
|
+
client.send_message(body, channels) if client
|
333
|
+
end
|
334
|
+
|
335
|
+
# Helper methods
|
336
|
+
|
337
|
+
def broadcast_needs(*args)
|
338
|
+
args.each do |arg|
|
339
|
+
raise MalformedBroadcast, @request unless @request.has_key?(arg)
|
340
|
+
end
|
341
|
+
end
|
342
|
+
|
343
|
+
def subscribe_needs(*args)
|
344
|
+
args.each do |arg|
|
345
|
+
raise MalformedSubscribe, @request unless @request.has_key?(arg)
|
346
|
+
end
|
347
|
+
end
|
348
|
+
|
349
|
+
def query_needs(*args)
|
350
|
+
args.each do |arg|
|
351
|
+
raise MalformedQuery, @request unless @request.has_key?(arg)
|
352
|
+
end
|
353
|
+
end
|
354
|
+
|
355
|
+
def authenticate_broadcast_or_query
|
356
|
+
if options[:allowed_ips]
|
357
|
+
return true if options[:allowed_ips].include?(client_ip)
|
358
|
+
elsif !@request[:secret_key]
|
359
|
+
return true if broadcast_query_request
|
360
|
+
elsif options[:secret_key]
|
361
|
+
return true if @request[:secret_key] == options[:secret_key]
|
362
|
+
end
|
363
|
+
if !options[:allowed_ips] and !options[:secret_key] and !options[:broadcast_query_login_url]
|
364
|
+
return true
|
365
|
+
end
|
366
|
+
false
|
367
|
+
end
|
368
|
+
|
369
|
+
def broadcast_query_request
|
370
|
+
return false unless options[:broadcast_query_login_url]
|
371
|
+
url = URI.parse(options[:broadcast_query_login_url])
|
372
|
+
params = []
|
373
|
+
params << "client_id=#{@request[:client_id]}" if @request[:client_id]
|
374
|
+
params << "session_id=#{URI.escape(@request[:session_id])}" if @request[:session_id]
|
375
|
+
params << "type=#{@request[:type]}"
|
376
|
+
params << "command=#{@request[:command]}"
|
377
|
+
(@request[:channels] || []).each {|chan| params << "channels[]=#{chan}" }
|
378
|
+
url.query = params.join('&')
|
379
|
+
begin
|
380
|
+
open(url.to_s, "User-Agent" => "Ruby/#{RUBY_VERSION}")
|
381
|
+
rescue Timeout::Error
|
382
|
+
return false
|
383
|
+
rescue
|
384
|
+
return false
|
385
|
+
end
|
386
|
+
true
|
387
|
+
end
|
388
|
+
|
389
|
+
def client_ip
|
390
|
+
Socket.unpack_sockaddr_in(get_peername)[1] rescue nil
|
391
|
+
end
|
392
|
+
end
|
393
|
+
end
|
data/lib/juggernaut.rb
ADDED
@@ -0,0 +1,158 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'logger'
|
3
|
+
require 'eventmachine'
|
4
|
+
require 'json'
|
5
|
+
$:.unshift(File.dirname(__FILE__))
|
6
|
+
|
7
|
+
module Juggernaut
|
8
|
+
VERSION = '0.5.8'
|
9
|
+
|
10
|
+
class JuggernautError < StandardError #:nodoc:
|
11
|
+
end
|
12
|
+
|
13
|
+
@@options = {}
|
14
|
+
|
15
|
+
DEFAULT_CONFIG_FILE = <<-EOF
|
16
|
+
# ======================
|
17
|
+
# Juggernaut Options
|
18
|
+
# ======================
|
19
|
+
|
20
|
+
# === Subscription authentication ===
|
21
|
+
# Leave all subscription options uncommented to allow anyone to subscribe.
|
22
|
+
|
23
|
+
# If specified, subscription_url is called everytime a client subscribes.
|
24
|
+
# Parameters passed are: session_id, client_id and an array of channels.
|
25
|
+
#
|
26
|
+
# The server should check that the session_id matches up to the client_id
|
27
|
+
# and that the client is allowed to access the specified channels.
|
28
|
+
#
|
29
|
+
# If a status code other than 200 is encountered, the subscription_request fails
|
30
|
+
# and the client is disconnected.
|
31
|
+
#
|
32
|
+
# :subscription_url: http://localhost:3000/sessions/juggernaut_subscription
|
33
|
+
|
34
|
+
# === Broadcast and query authentication ===
|
35
|
+
# Leave all broadcast/query options uncommented to allow anyone to broadcast/query.
|
36
|
+
#
|
37
|
+
# Broadcast authentication in a production environment is very importantant since broadcasters
|
38
|
+
# can execute JavaScript on subscribed clients, leaving you vulnerable to cross site scripting
|
39
|
+
# attacks if broadcasters aren't authenticated.
|
40
|
+
|
41
|
+
# 1) Via IP address
|
42
|
+
#
|
43
|
+
# If specified, if a client has an ip that is specified in allowed_ips, than it is automatically
|
44
|
+
# authenticated, even if a secret_key isn't provided.
|
45
|
+
#
|
46
|
+
# This is the recommended method for broadcast authentication.
|
47
|
+
#
|
48
|
+
:allowed_ips:
|
49
|
+
- 127.0.0.1
|
50
|
+
# - 192.168.0.1
|
51
|
+
|
52
|
+
# 2) Via HTTP request
|
53
|
+
#
|
54
|
+
# If specified, if a client attempts a broadcast/query, without a secret_key or using an IP
|
55
|
+
# no included in allowed_ips, then broadcast_query_login_url will be called.
|
56
|
+
# Parameters passed, if given, are: session_id, client_id, channels and type.
|
57
|
+
#
|
58
|
+
# The server should check that the session_id matches up to the client id, and the client
|
59
|
+
# is allowed to perform that particular type of broadcast/query.
|
60
|
+
#
|
61
|
+
# If a status code other than 200 is encountered, the broadcast_query_login_url fails
|
62
|
+
# and the client is disconnected.
|
63
|
+
#
|
64
|
+
# :broadcast_query_login_url: http://localhost:3000/sessions/juggernaut_broadcast
|
65
|
+
|
66
|
+
# 3) Via shared secret key
|
67
|
+
#
|
68
|
+
# This secret key must be sent with any query/broadcast commands.
|
69
|
+
# It must be the same as the one in the Rails config file.
|
70
|
+
#
|
71
|
+
# You shouldn't authenticate broadcasts from subscribed clients using this method
|
72
|
+
# since the secret_key will be easily visible in the page (and not so secret any more)!
|
73
|
+
#
|
74
|
+
# :secret_key: your_secret_key_here
|
75
|
+
|
76
|
+
# == Subscription Logout ==
|
77
|
+
|
78
|
+
# If specified, logout_connection_url is called everytime a specific connection from a subscribed client disconnects.
|
79
|
+
# Parameters passed are session_id, client_id and an array of channels specific to that connection.
|
80
|
+
#
|
81
|
+
# :logout_connection_url: http://localhost:3000/sessions/juggernaut_connection_logout
|
82
|
+
|
83
|
+
# Logout url is called when all connections from a subscribed client are closed.
|
84
|
+
# Parameters passed are session_id and client_id.
|
85
|
+
#
|
86
|
+
# :logout_url: http://localhost:3000/sessions/juggernaut_logout
|
87
|
+
|
88
|
+
# === Miscellaneous ===
|
89
|
+
|
90
|
+
# timeout defaults to 10. A timeout is the time between when a client closes a connection
|
91
|
+
# and a logout_request or logout_connection_request is made. The reason for this is that a client
|
92
|
+
# may only temporarily be disconnected, and may attempt a reconnect very soon.
|
93
|
+
#
|
94
|
+
# :timeout: 10
|
95
|
+
|
96
|
+
# store_messages defaults to false. If this option is true, messages send to connections will be stored.
|
97
|
+
# This is useful since a client can then receive broadcasted message that it has missed (perhaps it was disconnected).
|
98
|
+
#
|
99
|
+
# :store_messages: false
|
100
|
+
|
101
|
+
# === Server ===
|
102
|
+
|
103
|
+
# Host defaults to "0.0.0.0". You shouldn't need to change this.
|
104
|
+
# :host: 0.0.0.0
|
105
|
+
|
106
|
+
# Port is mandatory
|
107
|
+
:port: 5001
|
108
|
+
|
109
|
+
# Defaults to value of :port. If you are doing port forwarding you'll need to configure this to the same
|
110
|
+
# value as :public_port in the juggernaut_hosts.yml file
|
111
|
+
# :public_port: 5001
|
112
|
+
|
113
|
+
EOF
|
114
|
+
|
115
|
+
class << self
|
116
|
+
def options
|
117
|
+
@@options
|
118
|
+
end
|
119
|
+
|
120
|
+
def options=(val)
|
121
|
+
@@options = val
|
122
|
+
end
|
123
|
+
|
124
|
+
def logger
|
125
|
+
return @@logger if defined?(@@logger) && !@@logger.nil?
|
126
|
+
FileUtils.mkdir_p(File.dirname(log_path))
|
127
|
+
@@logger = Logger.new(log_path)
|
128
|
+
@@logger.level = Logger::INFO if options[:debug] == false
|
129
|
+
@@logger
|
130
|
+
rescue
|
131
|
+
@@logger = Logger.new(STDOUT)
|
132
|
+
end
|
133
|
+
|
134
|
+
def logger=(logger)
|
135
|
+
@@logger = logger
|
136
|
+
end
|
137
|
+
|
138
|
+
def log_path
|
139
|
+
options[:log_path] || File.join(%w( / var run juggernaut.log ))
|
140
|
+
end
|
141
|
+
|
142
|
+
def pid_path
|
143
|
+
options[:pid_path] || File.join(%w( / var run ), "juggernaut.#{options[:port]}.pid" )
|
144
|
+
end
|
145
|
+
|
146
|
+
def config_path
|
147
|
+
options[:config_path] || File.join(%w( / var run juggernaut.yml ))
|
148
|
+
end
|
149
|
+
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
require 'juggernaut/utils'
|
154
|
+
require 'juggernaut/miscel'
|
155
|
+
require 'juggernaut/message'
|
156
|
+
require 'juggernaut/client'
|
157
|
+
require 'juggernaut/server'
|
158
|
+
require 'juggernaut/runner'
|
metadata
ADDED
@@ -0,0 +1,94 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: dima-exe-juggernaut
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.5.9
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Alex MacCaw
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2008-12-06 00:00:00 -08:00
|
13
|
+
default_executable: juggernaut
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: eventmachine
|
17
|
+
type: :runtime
|
18
|
+
version_requirement:
|
19
|
+
version_requirements: !ruby/object:Gem::Requirement
|
20
|
+
requirements:
|
21
|
+
- - ">="
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: 0.10.0
|
24
|
+
version:
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: json
|
27
|
+
type: :runtime
|
28
|
+
version_requirement:
|
29
|
+
version_requirements: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 1.1.2
|
34
|
+
version:
|
35
|
+
- !ruby/object:Gem::Dependency
|
36
|
+
name: hoe
|
37
|
+
type: :development
|
38
|
+
version_requirement:
|
39
|
+
version_requirements: !ruby/object:Gem::Requirement
|
40
|
+
requirements:
|
41
|
+
- - ">="
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: 1.7.0
|
44
|
+
version:
|
45
|
+
description: "See Plugin README: http://juggernaut.rubyforge.org/svn/trunk/juggernaut/README"
|
46
|
+
email: info@eribium.org
|
47
|
+
executables:
|
48
|
+
- juggernaut
|
49
|
+
extensions: []
|
50
|
+
|
51
|
+
extra_rdoc_files:
|
52
|
+
- Manifest.txt
|
53
|
+
- README.txt
|
54
|
+
files:
|
55
|
+
- Manifest.txt
|
56
|
+
- README.txt
|
57
|
+
- Rakefile
|
58
|
+
- bin/juggernaut
|
59
|
+
- lib/juggernaut.rb
|
60
|
+
- lib/juggernaut/client.rb
|
61
|
+
- lib/juggernaut/message.rb
|
62
|
+
- lib/juggernaut/miscel.rb
|
63
|
+
- lib/juggernaut/runner.rb
|
64
|
+
- lib/juggernaut/server.rb
|
65
|
+
- lib/juggernaut/utils.rb
|
66
|
+
has_rdoc: true
|
67
|
+
homepage: http://juggernaut.rubyforge.org
|
68
|
+
post_install_message:
|
69
|
+
rdoc_options:
|
70
|
+
- --main
|
71
|
+
- README.txt
|
72
|
+
require_paths:
|
73
|
+
- lib
|
74
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
75
|
+
requirements:
|
76
|
+
- - ">="
|
77
|
+
- !ruby/object:Gem::Version
|
78
|
+
version: "0"
|
79
|
+
version:
|
80
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
81
|
+
requirements:
|
82
|
+
- - ">="
|
83
|
+
- !ruby/object:Gem::Version
|
84
|
+
version: "0"
|
85
|
+
version:
|
86
|
+
requirements: []
|
87
|
+
|
88
|
+
rubyforge_project: juggernaut
|
89
|
+
rubygems_version: 1.2.0
|
90
|
+
signing_key:
|
91
|
+
specification_version: 2
|
92
|
+
summary: "See Plugin README: http://juggernaut.rubyforge.org/svn/trunk/juggernaut/README"
|
93
|
+
test_files: []
|
94
|
+
|