juggernaut_rails 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.
@@ -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,25 @@
1
+ require "yaml"
2
+
3
+ module Juggernaut
4
+ module Rails
5
+ class <<self
6
+ attr_writer :hosts
7
+
8
+ def default_options
9
+ @default_options ||= {
10
+ :config_path => File.join(::Rails.root, 'config', 'juggernaut.yml'),
11
+ :log_path => File.join(::Rails.root, 'log', 'juggernaut.log'),
12
+ :pid_path => File.join(::Rails.root, 'tmp', 'pids', 'juggernaut.5001.pid')
13
+ }
14
+ end
15
+
16
+ def hosts
17
+ @hosts ||= YAML::load(ERB.new(IO.read("#{::Rails.root}/config/juggernaut_hosts.yml")).result)[:hosts].select {|h| !h[:environment] or h[:environment] == ::Rails.env.to_sym }
18
+ end
19
+
20
+ def random_host
21
+ hosts[rand(hosts.length)]
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,137 @@
1
+ require "socket"
2
+
3
+ module Juggernaut::Rails
4
+ module ConvenienceMethods
5
+
6
+ def self.included(base) #:nodoc:
7
+ #raise "included jugs"
8
+ base.class_eval do
9
+ extend ClassMethods
10
+ end
11
+ end
12
+
13
+ module ClassMethods
14
+
15
+ def send_to_all(data)
16
+ fc = {
17
+ :command => :broadcast,
18
+ :body => data,
19
+ :type => :to_channels,
20
+ :channels => []
21
+ }
22
+ send_data(fc)
23
+ end
24
+
25
+ def send_to_channels(data, channels)
26
+ fc = {
27
+ :command => :broadcast,
28
+ :body => data,
29
+ :type => :to_channels,
30
+ :channels => channels
31
+ }
32
+ send_data(fc)
33
+ end
34
+ alias send_to_channel send_to_channels
35
+
36
+ def send_to_clients(data, client_ids)
37
+ fc = {
38
+ :command => :broadcast,
39
+ :body => data,
40
+ :type => :to_clients,
41
+ :client_ids => client_ids
42
+ }
43
+ send_data(fc)
44
+ end
45
+ alias send_to_client send_to_clients
46
+
47
+ def send_to_clients_on_channels(data, client_ids, channels)
48
+ fc = {
49
+ :command => :broadcast,
50
+ :body => data,
51
+ :type => :to_clients,
52
+ :client_ids => client_ids,
53
+ :channels => channels
54
+ }
55
+ send_data(fc)
56
+ end
57
+ alias send_to_clients_on_channel send_to_clients_on_channels
58
+ alias send_to_client_on_channels send_to_clients_on_channels
59
+
60
+ def remove_channels_from_clients(client_ids, channels)
61
+ fc = {
62
+ :command => :query,
63
+ :type => :remove_channels_from_client,
64
+ :client_ids => client_ids,
65
+ :channels => channels
66
+ }
67
+ send_data(fc)
68
+ end
69
+ alias remove_channel_from_client remove_channels_from_clients
70
+ alias remove_channels_from_client remove_channels_from_clients
71
+
72
+ def remove_all_channels(channels)
73
+ fc = {
74
+ :command => :query,
75
+ :type => :remove_all_channels,
76
+ :channels => channels
77
+ }
78
+ send_data(fc)
79
+ end
80
+
81
+ def show_clients
82
+ fc = {
83
+ :command => :query,
84
+ :type => :show_clients
85
+ }
86
+ send_data(fc, true).flatten
87
+ end
88
+
89
+ def show_client(client_id)
90
+ fc = {
91
+ :command => :query,
92
+ :type => :show_client,
93
+ :client_id => client_id
94
+ }
95
+ send_data(fc, true).flatten[0]
96
+ end
97
+
98
+ def show_clients_for_channels(channels)
99
+ fc = {
100
+ :command => :query,
101
+ :type => :show_clients_for_channels,
102
+ :channels => channels
103
+ }
104
+ send_data(fc, true).flatten
105
+ end
106
+ alias show_clients_for_channel show_clients_for_channels
107
+
108
+ def send_data(hash, response = false)
109
+ hash[:channels] = Array(hash[:channels]) if hash[:channels]
110
+ hash[:client_ids] = Array(hash[:client_ids]) if hash[:client_ids]
111
+
112
+ res = []
113
+ hosts.each do |address|
114
+ begin
115
+ hash[:secret_key] = address[:secret_key] if address[:secret_key]
116
+
117
+ @socket = TCPSocket.new(address[:host], address[:port])
118
+ # the \0 is to mirror flash
119
+ @socket.print(hash.to_json + Juggernaut::Server::CR)
120
+ @socket.flush
121
+ res << @socket.readline(Juggernaut::Server::CR) if response
122
+ ensure
123
+ @socket.close if @socket and !@socket.closed?
124
+ end
125
+ end
126
+ res.collect {|r| ActiveSupport::JSON.decode(r.chomp!(Juggernaut::Server::CR)) } if response
127
+ end
128
+
129
+ private
130
+
131
+ def hosts
132
+ Juggernaut::Rails.hosts
133
+ end
134
+
135
+ end
136
+ end
137
+ end
@@ -0,0 +1,27 @@
1
+ module Juggernaut # :nodoc:
2
+ module Rails
3
+ module Helpers
4
+
5
+ def juggernaut(options = {})
6
+ random_host = Juggernaut::Rails.random_host
7
+ options = {
8
+ :host => (random_host[:public_host] || random_host[:host]),
9
+ :port => (random_host[:public_port] || random_host[:port]),
10
+ :width => '0px',
11
+ :height => '0px',
12
+ :session_id => request.session_options[:id],
13
+ :swf_address => "/juggernaut/juggernaut.swf",
14
+ :ei_swf_address => "/juggernaut/expressinstall.swf",
15
+ :flash_version => 8,
16
+ :flash_color => "#fff",
17
+ :swf_name => "juggernaut_flash",
18
+ :bridge_name => "juggernaut",
19
+ :debug => (RAILS_ENV == 'development'),
20
+ :reconnect_attempts => 3,
21
+ :reconnect_intervals => 3
22
+ }.merge(options)
23
+ javascript_tag "new Juggernaut(#{options.to_json});"
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,77 @@
1
+ module Juggernaut::Rails
2
+ module RenderExtension
3
+ def self.included(base)
4
+ base.send :include, InstanceMethods
5
+ end
6
+
7
+ module InstanceMethods
8
+ # We can't protect these as ActionMailer complains
9
+
10
+ def render_with_juggernaut(options = nil, extra_options = {}, &block)
11
+ if options == :juggernaut or (options.is_a?(Hash) and options[:juggernaut])
12
+ begin
13
+ if @template.respond_to?(:_evaluate_assigns_and_ivars, true)
14
+ @template.send(:_evaluate_assigns_and_ivars)
15
+ else
16
+ @template.send(:evaluate_assigns)
17
+ end
18
+
19
+ generator = ActionView::Helpers::PrototypeHelper::JavaScriptGenerator.new(@template, &block)
20
+ render_for_juggernaut(generator.to_s, options.is_a?(Hash) ? options[:juggernaut] : nil)
21
+ ensure
22
+ erase_render_results
23
+ reset_variables_added_to_assigns
24
+ end
25
+ else
26
+ render_without_juggernaut(options, extra_options, &block)
27
+ end
28
+ end
29
+
30
+ def render_juggernaut(*args)
31
+ juggernaut_options = args.last.is_a?(Hash) ? args.pop : {}
32
+ render_for_juggernaut(render_to_string(*args), juggernaut_options)
33
+ end
34
+
35
+ def render_for_juggernaut(data, options = {})
36
+ if !options or !options.is_a?(Hash)
37
+ return Juggernaut.send_to_all(data)
38
+ end
39
+
40
+ case options[:type]
41
+ when :send_to_all
42
+ Juggernaut.send_to_all(data)
43
+ when :send_to_channels
44
+ juggernaut_needs options, :channels
45
+ Juggernaut.send_to_channels(data, options[:channels])
46
+ when :send_to_channel
47
+ juggernaut_needs options, :channel
48
+ Juggernaut.send_to_channel(data, options[:channel])
49
+ when :send_to_client
50
+ juggernaut_needs options, :client_id
51
+ Juggernaut.send_to_client(data, options[:client_id])
52
+ when :send_to_clients
53
+ juggernaut_needs options, :client_ids
54
+ Juggernaut.send_to_clients(data, options[:client_ids])
55
+ when :send_to_client_on_channel
56
+ juggernaut_needs options, :client_id, :channel
57
+ Juggernaut.send_to_clients_on_channel(data, options[:client_id], options[:channel])
58
+ when :send_to_clients_on_channel
59
+ juggernaut_needs options, :client_ids, :channel
60
+ Juggernaut.send_to_clients_on_channel(data, options[:client_ids], options[:channel])
61
+ when :send_to_client_on_channels
62
+ juggernaut_needs options, :client_ids, :channels
63
+ Juggernaut.send_to_clients_on_channel(data, options[:client_id], options[:channels])
64
+ when :send_to_clients_on_channels
65
+ juggernaut_needs options, :client_ids, :channels
66
+ Juggernaut.send_to_clients_on_channel(data, options[:client_ids], options[:channels])
67
+ end
68
+ end
69
+
70
+ def juggernaut_needs(options, *args)
71
+ args.each do |a|
72
+ raise "You must specify #{a}" unless options[a]
73
+ end
74
+ end
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,66 @@
1
+ require 'escape'
2
+
3
+ namespace :juggernaut do
4
+ desc "Install the Juggernaut js and swf files into your Rails application."
5
+ task :install => :environment do
6
+ require 'fileutils'
7
+
8
+ here = File.join(File.dirname(__FILE__), '../../../')
9
+ there = ::Rails.root
10
+
11
+ FileUtils.mkdir_p("#{there}/public/javascripts/juggernaut/")
12
+ FileUtils.mkdir_p("#{there}/public/juggernaut/")
13
+
14
+ puts "Installing Juggernaut..."
15
+ FileUtils.cp("#{here}/media/swfobject.js", "#{there}/public/javascripts/juggernaut/")
16
+ FileUtils.cp("#{here}/media/juggernaut.js", "#{there}/public/javascripts/juggernaut/")
17
+ FileUtils.cp("#{here}/media/juggernaut.swf", "#{there}/public/juggernaut/")
18
+ FileUtils.cp("#{here}/media/expressinstall.swf", "#{there}/public/juggernaut/")
19
+
20
+ FileUtils.cp("#{here}/media/juggernaut.yml", "#{there}/config/") unless File.exist?("#{there}/config/juggernaut.yml")
21
+ FileUtils.cp("#{here}/media/juggernaut_hosts.yml", "#{there}/config/") unless File.exist?("#{there}/config/juggernaut_hosts.yml")
22
+ puts "Juggernaut has been successfully installed."
23
+ puts
24
+ puts "Please refer to the readme file #{File.expand_path(here)}/README"
25
+ end
26
+
27
+ namespace :install do
28
+ desc "Install the Juggernaut jQuery JavaScript files into your Rails application."
29
+ task :jquery => :environment do
30
+ require 'fileutils'
31
+
32
+ here = File.join(File.dirname(__FILE__), '../../../')
33
+ there = ::Rails.root
34
+
35
+ FileUtils.cp("#{here}/media/jquerynaut.js", "#{there}/public/javascripts/juggernaut/")
36
+ FileUtils.cp("#{here}/media/json.js", "#{there}/public/javascripts/juggernaut/")
37
+ puts "Installed the Juggernaut jQuery JavaScript files to public/javascripts/juggernaut/"
38
+ end
39
+ end
40
+
41
+ desc 'Compile the juggernaut flash file'
42
+ task :compile_flash do
43
+ `mtasc -version 8 -header 1:1:1 -main -swf media/juggernaut.swf media/juggernaut.as`
44
+ end
45
+
46
+ desc "Start the Juggernaut server"
47
+ task 'start' => :environment do
48
+ run_juggernaut('-d')
49
+ end
50
+
51
+ desc "Stop the Juggernaut server"
52
+ task 'stop' => :environment do
53
+ run_juggernaut('-k')
54
+ end
55
+
56
+ def run_juggernaut(extra_options=[])
57
+ defaults = Juggernaut::Rails.default_options
58
+ extra_options = [extra_options] unless extra_options.is_a?(Array)
59
+ extra_options << '-e' if Rails.env.development?
60
+ FileUtils.cd(Rails.root) do
61
+ command = ['juggernaut', '-c', defaults[:config_path], '-P', defaults[:pid_path], '-l', defaults[:log_path]] + extra_options
62
+ ENV['RAILS_ENV'] = Rails.env.to_s
63
+ system(Escape.shell_command(command))
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,221 @@
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 or rake juggernaut:install)"
35
+ exit
36
+ end
37
+
38
+ config_options = YAML::load(ERB.new(IO.read(config_path)).result)
39
+ config_options = config_options[ENV['RAILS_ENV']] if config_options.has_key?(ENV['RAILS_ENV'])
40
+ options.merge!(config_options)
41
+
42
+ if options.include?(:kill)
43
+ kill_pid(options[:kill] || '*')
44
+ end
45
+
46
+ Process.euid = options[:user] if options[:user]
47
+ Process.egid = options[:group] if options[:group]
48
+
49
+ if !options[:daemonize]
50
+ start
51
+ else
52
+ daemonize
53
+ end
54
+ end
55
+
56
+ def start
57
+ puts "Starting Juggernaut server #{Juggernaut::VERSION} on port: #{options[:port]}..."
58
+
59
+ trap("INT") {
60
+ stop
61
+ exit
62
+ }
63
+ trap("TERM"){
64
+ stop
65
+ exit
66
+ }
67
+
68
+ if options[:descriptor_table_size]
69
+ EM.epoll
70
+ new_size = EM.set_descriptor_table_size( options[:descriptor_table_size] )
71
+ logger.debug "New descriptor-table size is #{new_size}"
72
+ end
73
+
74
+ EventMachine::run {
75
+ EventMachine::add_periodic_timer( options[:cleanup_timer] || 2 ) { Juggernaut::Client.send_logouts_after_timeout }
76
+ EventMachine::start_server(options[:host], options[:port].to_i, Juggernaut::Server)
77
+ EM.set_effective_user( options[:user] ) if options[:user]
78
+ }
79
+ end
80
+
81
+ def stop
82
+ puts "Stopping Juggernaut server"
83
+ Juggernaut::Client.send_logouts_to_all_clients
84
+ EventMachine::stop
85
+ end
86
+
87
+ def parse_options(argv)
88
+ OptionParser.new do |opts|
89
+ opts.summary_width = 25
90
+ opts.banner = "Juggernaut (#{VERSION})\n\n",
91
+ "Usage: juggernaut [-h host] [-p port] [-P file]\n",
92
+ " [-d] [-k port] [-l file] [-e]\n",
93
+ " juggernaut --help\n",
94
+ " juggernaut --version\n"
95
+
96
+ opts.separator ""
97
+ opts.separator ""; opts.separator "Configuration:"
98
+
99
+ opts.on("-g", "--generate FILE", String, "Generate config file", "(default: #{options[:config_path]})") do |v|
100
+ options[:config_path] = File.expand_path(v) if v
101
+ generate_config_file
102
+ end
103
+
104
+ opts.on("-c", "--config FILE", String, "Path to configuration file.", "(default: #{options[:config_path]})") do |v|
105
+ options[:config_path] = File.expand_path(v)
106
+ end
107
+
108
+ opts.separator ""; opts.separator "Network:"
109
+
110
+ opts.on("-h", "--host HOST", String, "Specify host", "(default: #{options[:host]})") do |v|
111
+ options[:host] = v
112
+ end
113
+
114
+ opts.on("-p", "--port PORT", Integer, "Specify port", "(default: #{options[:port]})") do |v|
115
+ options[:port] = v
116
+ end
117
+
118
+ 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|
119
+ options[:descriptor_table_size] = v
120
+ end
121
+
122
+ opts.separator ""; opts.separator "Daemonization:"
123
+
124
+ opts.on("-P", "--pid FILE", String, "save PID in FILE when using -d option.", "(default: #{options[:pid_path]})") do |v|
125
+ options[:pid_path] = File.expand_path(v)
126
+ end
127
+
128
+ opts.on("-d", "--daemon", "Daemonize mode") do |v|
129
+ options[:daemonize] = v
130
+ end
131
+
132
+ opts.on("-k", "--kill PORT", String, :OPTIONAL, "Kill specified running daemons - leave blank to kill all.") do |v|
133
+ options[:kill] = v
134
+ end
135
+
136
+ opts.separator ""; opts.separator "Logging:"
137
+
138
+ opts.on("-l", "--log [FILE]", String, "Path to print debugging information.", "(default: #{options[:log_path]})") do |v|
139
+ options[:log_path] = File.expand_path(v)
140
+ end
141
+
142
+ opts.on("-e", "--debug", "Run in debug mode", "(default: #{options[:debug]})") do |v|
143
+ options[:debug] = v
144
+ end
145
+
146
+ opts.separator ""; opts.separator "Permissions:"
147
+
148
+ opts.on("-u", "--user USER", Integer, "User to run as") do |user|
149
+ options[:user] = user
150
+ end
151
+
152
+ opts.on("-G", "--group GROUP", String, "Group to run as") do |group|
153
+ options[:group] = group
154
+ end
155
+
156
+ opts.separator ""; opts.separator "Miscellaneous:"
157
+
158
+ opts.on_tail("-?", "--help", "Display this usage information.") do
159
+ puts "#{opts}\n"
160
+ exit
161
+ end
162
+
163
+ opts.on_tail("-v", "--version", "Display version") do |v|
164
+ puts "Juggernaut #{VERSION}"
165
+ exit
166
+ end
167
+ end.parse!(argv)
168
+ options
169
+ end
170
+
171
+ private
172
+
173
+ def generate_config_file
174
+ if File.exists?(config_path)
175
+ puts "Config file already exists. You must remove it before generating a new one."
176
+ exit
177
+ end
178
+ puts "Generating config file...."
179
+ File.open(config_path, 'w+') do |file|
180
+ file.write DEFAULT_CONFIG_FILE.gsub('your_secret_key_here', Digest::SHA1.hexdigest("--#{Time.now.to_s.split(//).sort_by {rand}.join}--"))
181
+ end
182
+ puts "Config file generated at #{config_path}"
183
+ exit
184
+ end
185
+
186
+ def store_pid(pid)
187
+ FileUtils.mkdir_p(File.dirname(pid_path))
188
+ File.open(pid_path, 'w'){|f| f.write("#{pid}\n")}
189
+ end
190
+
191
+ def kill_pid(k)
192
+ Dir[options[:pid_path]||File.join(File.dirname(pid_dir), "juggernaut.#{k}.pid")].each do |f|
193
+ begin
194
+ puts f
195
+ pid = IO.read(f).chomp.to_i
196
+ FileUtils.rm f
197
+ Process.kill(9, pid)
198
+ puts "killed PID: #{pid}"
199
+ rescue => e
200
+ puts "Failed to kill! #{k}: #{e}"
201
+ end
202
+ end
203
+ exit
204
+ end
205
+
206
+ def daemonize
207
+ fork do
208
+ Process.setsid
209
+ exit if fork
210
+ store_pid(Process.pid)
211
+ # Dir.chdir "/" # Mucks up logs
212
+ File.umask 0000
213
+ STDIN.reopen "/dev/null"
214
+ STDOUT.reopen "/dev/null", "a"
215
+ STDERR.reopen STDOUT
216
+ start
217
+ end
218
+ end
219
+
220
+ end
221
+ end