juggernaut 0.5.8 → 2.0.0.beta1

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,11 +0,0 @@
1
- Manifest.txt
2
- README.txt
3
- Rakefile
4
- bin/juggernaut
5
- lib/juggernaut.rb
6
- lib/juggernaut/client.rb
7
- lib/juggernaut/message.rb
8
- lib/juggernaut/miscel.rb
9
- lib/juggernaut/runner.rb
10
- lib/juggernaut/server.rb
11
- lib/juggernaut/utils.rb
data/README.txt DELETED
@@ -1,32 +0,0 @@
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.
@@ -1,4 +0,0 @@
1
- #!/usr/bin/env ruby
2
-
3
- require File.join(File.dirname(__FILE__), '..', 'lib', 'juggernaut')
4
- Juggernaut::Runner.run
@@ -1,263 +0,0 @@
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
@@ -1,19 +0,0 @@
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
@@ -1,27 +0,0 @@
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
@@ -1,219 +0,0 @@
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