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.
@@ -1,4 +1,3 @@
1
- History.txt
2
1
  Manifest.txt
3
2
  README.txt
4
3
  Rakefile
@@ -9,4 +8,4 @@ lib/juggernaut/message.rb
9
8
  lib/juggernaut/miscel.rb
10
9
  lib/juggernaut/runner.rb
11
10
  lib/juggernaut/server.rb
12
- lib/juggernaut/utils.rb
11
+ lib/juggernaut/utils.rb
data/README.txt CHANGED
@@ -1,26 +1,10 @@
1
1
  Juggernaut
2
- by Alex MacCaw
3
- http://www.eribium.org
2
+ by Alex MacCaw http://www.eribium.org
3
+ minor fixes and unit tests by Ripta Pasay
4
4
 
5
5
  == DESCRIPTION:
6
-
7
- FIX (describe your package)
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
File without changes
@@ -5,7 +5,7 @@ require 'json'
5
5
  $:.unshift(File.dirname(__FILE__))
6
6
 
7
7
  module Juggernaut
8
- VERSION = '0.5.7'
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
- 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?(@@loggger)
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
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
- def log_path
135
- options[:log_path] || File.join(%w( / var run juggernaut.log ))
136
- end
134
+ def logger=(logger)
135
+ @@logger = logger
136
+ end
137
137
 
138
- def pid_path
139
- options[:pid_path] || File.join(%w( / var run ), "juggernaut.#{options[:port]}.pid" )
140
- end
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
- def config_path
143
- options[:config_path] || File.join(%w( / var run juggernaut.yml ))
144
- end
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'
@@ -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
- attr_reader :id
7
- attr_accessor :session_id
8
- attr_reader :connections
9
- @@clients = []
10
-
11
- class << self
12
- # Actually does a find_or_create_by_id
13
- def find_or_create(subscriber, request)
14
- if client = find_by_id(request[:client_id])
15
- client.session_id = request[:session_id]
16
- client.add_new_connection(subscriber)
17
- client
18
- else
19
- self.new(subscriber, request)
20
- end
21
- end
22
-
23
- def add_client(client)
24
- @@clients << client unless @@clients.include?(client)
25
- end
26
-
27
- # Client find methods
28
-
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 {|client|
44
- client.connections.select {|connection| connection.signature == signature }.any?
45
- }.first
46
- end
47
-
48
- def find_by_channels(channels)
49
- find {|client|
50
- client.has_channels?(channels)
51
- }
52
- end
53
-
54
- def find_by_id_and_channels(id, channels)
55
- find {|client|
56
- client.has_channels?(channels) && client.id == id
57
- }.first
58
- end
59
-
60
- def send_logouts_after_timeout
61
- @@clients.each do |client|
62
- client.logout_request if !client.alive? and client.give_up? and !client.sent_logout?
63
- end
64
- end
65
-
66
- # Called when the server is shutting down
67
- def send_logouts_to_all_clients
68
- @@clients.each do |client|
69
- client.logout_request if !client.sent_logout?
70
- end
71
- end
72
- end
73
-
74
- def initialize(subscriber, request)
75
- @connections = []
76
- @id = request[:client_id]
77
- @session_id = request[:session_id]
78
- add_new_connection(subscriber)
79
- end
80
-
81
- def to_json
82
- {
83
- :id => @id,
84
- :session_id => @session_id
85
- }.to_json
86
- end
87
-
88
- def add_new_connection(subscriber)
89
- @connections << subscriber
90
- end
91
-
92
- def subscription_request(channels)
93
- return true unless options[:subscription_url]
94
- post_request(options[:subscription_url], channels)
95
- end
96
-
97
- def logout_connection_request(channels)
98
- return true unless options[:logout_connection_url]
99
- post_request(options[:logout_connection_url], channels)
100
- end
101
-
102
- def logout_request
103
- return true unless options[:logout_url]
104
- @sent_logout = true
105
- post_request(options[:logout_url])
106
- end
107
-
108
- def sent_logout?
109
- !!@sent_logout
110
- end
111
-
112
- def send_message(msg, channels = nil)
113
- @connections.each do |em|
114
- em.broadcast(msg) if !channels or channels.empty? or em.has_channels?(channels)
115
- end
116
- end
117
-
118
- def has_channels?(channels)
119
- @connections.each do |em|
120
- return true if em.has_channels?(channels)
121
- end
122
- false
123
- end
124
-
125
- def remove_channels!(channels)
126
- @connections.each do |em|
127
- em.remove_channels!(channels)
128
- end
129
- end
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