juggernaut 0.5.7 → 0.5.8
Sign up to get free protection for your applications and to get access to all the features.
- 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
|