juggernaut 0.5.7 → 0.5.8
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 +1 -2
- data/README.txt +4 -20
- data/Rakefile +0 -3
- data/bin/juggernaut +0 -0
- data/lib/juggernaut.rb +36 -31
- data/lib/juggernaut/client.rb +258 -152
- data/lib/juggernaut/runner.rb +8 -8
- data/lib/juggernaut/server.rb +34 -38
- data/test/test_client.rb +176 -0
- data/test/test_juggernaut.rb +34 -0
- data/test/test_message.rb +26 -0
- data/test/test_runner.rb +26 -0
- data/test/test_server.rb +573 -0
- data/test/test_utils.rb +26 -0
- metadata +18 -11
- data/History.txt +0 -5
data/Manifest.txt
CHANGED
data/README.txt
CHANGED
@@ -1,26 +1,10 @@
|
|
1
1
|
Juggernaut
|
2
|
-
by Alex MacCaw
|
3
|
-
|
2
|
+
by Alex MacCaw http://www.eribium.org
|
3
|
+
minor fixes and unit tests by Ripta Pasay
|
4
4
|
|
5
5
|
== DESCRIPTION:
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
== FEATURES/PROBLEMS:
|
10
|
-
|
11
|
-
* FIX (list of features or problems)
|
12
|
-
|
13
|
-
== SYNOPSIS:
|
14
|
-
|
15
|
-
FIX (code sample of usage)
|
16
|
-
|
17
|
-
== REQUIREMENTS:
|
18
|
-
|
19
|
-
* FIX (list of requirements)
|
20
|
-
|
21
|
-
== INSTALL:
|
22
|
-
|
23
|
-
* FIX (sudo gem install, anything else)
|
6
|
+
See Plugin README:
|
7
|
+
http://juggernaut.rubyforge.org/svn/trunk/juggernaut/README
|
24
8
|
|
25
9
|
== LICENSE:
|
26
10
|
|
data/Rakefile
CHANGED
@@ -8,10 +8,7 @@ Hoe.new('juggernaut', Juggernaut::VERSION) do |p|
|
|
8
8
|
p.rubyforge_name = 'juggernaut'
|
9
9
|
p.author = 'Alex MacCaw'
|
10
10
|
p.email = 'info@eribium.org'
|
11
|
-
# p.summary = 'FIX'
|
12
|
-
# p.description = p.paragraphs_of('README.txt', 2..5).join("\n\n")
|
13
11
|
p.url = 'http://juggernaut.rubyforge.org'
|
14
|
-
p.changes = p.paragraphs_of('History.txt', 0..1).join("\n\n")
|
15
12
|
p.extra_deps << ['eventmachine', '>=0.10.0']
|
16
13
|
p.extra_deps << ['json', '>=1.1.2']
|
17
14
|
end
|
data/bin/juggernaut
CHANGED
File without changes
|
data/lib/juggernaut.rb
CHANGED
@@ -5,7 +5,7 @@ require 'json'
|
|
5
5
|
$:.unshift(File.dirname(__FILE__))
|
6
6
|
|
7
7
|
module Juggernaut
|
8
|
-
VERSION = '0.5.
|
8
|
+
VERSION = '0.5.8'
|
9
9
|
|
10
10
|
class JuggernautError < StandardError #:nodoc:
|
11
11
|
end
|
@@ -110,38 +110,43 @@ module Juggernaut
|
|
110
110
|
# value as :public_port in the juggernaut_hosts.yml file
|
111
111
|
# :public_port: 5001
|
112
112
|
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
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
133
|
|
134
|
-
|
135
|
-
|
136
|
-
|
134
|
+
def logger=(logger)
|
135
|
+
@@logger = logger
|
136
|
+
end
|
137
137
|
|
138
|
-
|
139
|
-
|
140
|
-
|
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
|
141
145
|
|
142
|
-
|
143
|
-
|
144
|
-
|
146
|
+
def config_path
|
147
|
+
options[:config_path] || File.join(%w( / var run juggernaut.yml ))
|
148
|
+
end
|
149
|
+
|
145
150
|
end
|
146
151
|
end
|
147
152
|
|
@@ -150,4 +155,4 @@ require 'juggernaut/miscel'
|
|
150
155
|
require 'juggernaut/message'
|
151
156
|
require 'juggernaut/client'
|
152
157
|
require 'juggernaut/server'
|
153
|
-
require 'juggernaut/runner'
|
158
|
+
require 'juggernaut/runner'
|
data/lib/juggernaut/client.rb
CHANGED
@@ -1,157 +1,263 @@
|
|
1
|
+
require 'timeout'
|
2
|
+
require 'net/http'
|
3
|
+
require 'net/https'
|
1
4
|
require 'uri'
|
5
|
+
|
2
6
|
module Juggernaut
|
3
7
|
class Client
|
4
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
|
5
138
|
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
def alive?
|
132
|
-
@connections.select{|em| em.alive? }.any?
|
133
|
-
end
|
134
|
-
|
135
|
-
def give_up?
|
136
|
-
@connections.select {|em| em.logout_timeout and Time.now > em.logout_timeout }.any?
|
137
|
-
end
|
138
|
-
|
139
|
-
private
|
140
|
-
|
141
|
-
def post_request(url, channels = [])
|
142
|
-
url = URI.parse(url)
|
143
|
-
params = []
|
144
|
-
params << "client_id=#{id}" if id
|
145
|
-
params << "session_id=#{session_id}" if session_id
|
146
|
-
channels.each {|chan| params << "channels[]=#{chan}" }
|
147
|
-
url.query = params.join('&')
|
148
|
-
begin
|
149
|
-
open(url.to_s, "User-Agent" => "Ruby/#{RUBY_VERSION}")
|
150
|
-
rescue => e
|
151
|
-
logger.debug("Bad response from #{url.to_s}: #{e}")
|
152
|
-
return false
|
153
|
-
end
|
154
|
-
true
|
155
|
-
end
|
156
|
-
end
|
157
|
-
end
|
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
|