juggernaut 0.5.8 → 2.0.0.beta1

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,368 +0,0 @@
1
- require 'eventmachine'
2
- require 'socket'
3
- require 'json'
4
- require 'open-uri'
5
- require 'fileutils'
6
- require 'digest/sha1'
7
-
8
- module Juggernaut
9
- class Server < EventMachine::Connection
10
- include Juggernaut::Miscel
11
-
12
- class InvalidRequest < Juggernaut::JuggernautError #:nodoc:
13
- end
14
-
15
- class InvalidCommand < Juggernaut::JuggernautError #:nodoc:
16
- end
17
-
18
- class CorruptJSON < Juggernaut::JuggernautError #:nodoc:
19
- end
20
-
21
- class MalformedBroadcast < Juggernaut::JuggernautError #:nodoc:
22
- end
23
-
24
- class MalformedSubscribe < Juggernaut::JuggernautError #:nodoc:
25
- end
26
-
27
- class UnauthorisedSubscription < Juggernaut::JuggernautError #:nodoc:
28
- end
29
-
30
- class MalformedQuery < Juggernaut::JuggernautError #:nodoc:
31
- end
32
-
33
- class UnauthorisedBroadcast < Juggernaut::JuggernautError #:nodoc:
34
- end
35
-
36
- class UnauthorisedQuery < Juggernaut::JuggernautError #:nodoc:
37
- end
38
-
39
- POLICY_FILE = <<-EOF
40
- <cross-domain-policy>
41
- <allow-access-from domain="*" to-ports="PORT" />
42
- </cross-domain-policy>
43
- EOF
44
-
45
- POLICY_REQUEST = "<policy-file-request/>"
46
-
47
- CR = "\0"
48
-
49
- attr_reader :current_msg_id
50
- attr_reader :messages
51
- attr_reader :connected
52
- attr_reader :logout_timeout
53
- attr_reader :status
54
- attr_reader :channels
55
- attr_reader :client
56
-
57
- # EM methods
58
-
59
- def post_init
60
- logger.debug "New client [#{client_ip}]"
61
- @client = nil
62
- @channels = []
63
- @current_msg_id = 0
64
- @connected = true
65
- @logout_timeout = nil
66
- @buffer = ''
67
- end
68
-
69
- # Juggernaut packets are terminated with "\0"
70
- # so we need to buffer the data until we find the
71
- # terminating "\0"
72
- def receive_data(data)
73
- @buffer << data
74
- @buffer = process_whole_messages(@buffer)
75
- end
76
-
77
- # process any whole messages in the buffer,
78
- # and return the new contents of the buffer
79
- def process_whole_messages(data)
80
- return data if data !~ /\0/ # only process if data contains a \0 char
81
- messages = data.split("\0")
82
- if data =~ /\0$/
83
- data = ''
84
- else
85
- # remove the last message from the list (because it is incomplete) before processing
86
- data = messages.pop
87
- end
88
- messages.each {|message| process_message(message.strip)}
89
- return data
90
- end
91
-
92
- def process_message(ln)
93
- logger.debug "Processing message: #{ln}"
94
- @request = nil
95
-
96
- if ln == POLICY_REQUEST
97
- logger.debug "Sending crossdomain file"
98
- send_data POLICY_FILE.gsub('PORT', (options[:public_port]||options[:port]).to_s)
99
- close_connection_after_writing
100
- return
101
- end
102
-
103
- begin
104
- @request = JSON.parse(ln) unless ln.empty?
105
- rescue
106
- raise CorruptJSON, ln
107
- end
108
-
109
- raise InvalidRequest, ln if !@request
110
-
111
- @request.symbolize_keys!
112
-
113
- # For debugging
114
- @request[:ip] = client_ip
115
-
116
- @request[:channels] = (@request[:channels] || []).compact.select {|c| !!c && c != '' }.uniq
117
-
118
- if @request[:client_ids]
119
- @request[:client_ids] = @request[:client_ids].to_a.compact.select {|c| !!c && c != '' }.uniq
120
- end
121
-
122
- case @request[:command].to_sym
123
- when :broadcast
124
- broadcast_command
125
- when :subscribe
126
- subscribe_command
127
- when :query
128
- query_command
129
- when :noop
130
- noop_command
131
- else
132
- raise InvalidCommand, @request
133
- end
134
-
135
- rescue JuggernautError => e
136
- logger.error("#{e} - #{e.message.inspect}")
137
- close_connection
138
- # So as to stop em quitting
139
- rescue => e
140
- logger ? logger.error(e) : puts(e)
141
- end
142
-
143
- def unbind
144
- if @client
145
- # todo - should be called after timeout?
146
- @client.logout_connection_request(@channels)
147
- logger.debug "Lost client #{@client.friendly_id}"
148
- end
149
- mark_dead('Unbind called')
150
- end
151
-
152
- # As far as I'm aware, send_data
153
- # never throws an exception
154
- def publish(msg)
155
- logger.debug "Sending msg: #{msg.to_s} to client #{@request[:client_id]} (session #{@request[:session_id]})"
156
- send_data(msg.to_s + CR)
157
- end
158
-
159
- # Connection methods
160
-
161
- def broadcast(bdy)
162
- msg = Juggernaut::Message.new(@current_msg_id += 1, bdy, self.signature)
163
- publish(msg)
164
- end
165
-
166
- def mark_dead(reason = "Unknown error")
167
- # Once dead, a client never recovers since a reconnection
168
- # attempt would hook onto a new em instance. A client
169
- # usually dies through an unbind
170
- @connected = false
171
- @client.remove_connection(self) if @client
172
- end
173
-
174
- def alive?
175
- @connected == true
176
- end
177
-
178
- def has_channels?(channels)
179
- channels.each {|channel|
180
- return true if has_channel?(channel)
181
- }
182
- false
183
- end
184
-
185
- def has_channel?(channel)
186
- @channels.include?(channel)
187
- end
188
-
189
- def add_channel(chan_name)
190
- return if !chan_name or chan_name == ''
191
- @channels << chan_name unless has_channel?(chan_name)
192
- end
193
-
194
- def add_channels(chan_names)
195
- chan_names.to_a.each do |chan_name|
196
- add_channel(chan_name)
197
- end
198
- end
199
-
200
- def remove_channel!(chan_name)
201
- @channels.delete(chan_name)
202
- end
203
-
204
- def remove_channels!(chan_names)
205
- chan_names.to_a.each do |chan_name|
206
- remove_channel!(chan_name)
207
- end
208
- end
209
-
210
- protected
211
-
212
- # Commands
213
-
214
- def broadcast_command
215
- raise MalformedBroadcast, @request unless @request[:type]
216
-
217
- raise UnauthorisedBroadcast, @request unless authenticate_broadcast_or_query
218
-
219
- case @request[:type].to_sym
220
- when :to_channels
221
- # if channels is a blank array, sends to everybody!
222
- broadcast_to_channels(@request[:body], @request[:channels])
223
- when :to_clients
224
- broadcast_needs :client_ids
225
- @request[:client_ids].each do |client_id|
226
- # if channels aren't empty, scopes broadcast to clients on those channels
227
- broadcast_to_client(@request[:body], client_id, @request[:channels])
228
- end
229
- else
230
- raise MalformedBroadcast, @request
231
- end
232
- end
233
-
234
- def query_command
235
- raise MalformedQuery, @request unless @request[:type]
236
-
237
- raise UnauthorisedQuery, @request unless authenticate_broadcast_or_query
238
-
239
- case @request[:type].to_sym
240
- when :remove_channels_from_all_clients
241
- query_needs :channels
242
- clients = Juggernaut::Client.find_all
243
- clients.each {|client| client.remove_channels!(@request[:channels]) }
244
- when :remove_channels_from_client
245
- query_needs :client_ids, :channels
246
- @request[:client_ids].each do |client_id|
247
- client = Juggernaut::Client.find_by_id(client_id)
248
- client.remove_channels!(@request[:channels]) if client
249
- end
250
- when :show_channels_for_client
251
- query_needs :client_id
252
- if client = Juggernaut::Client.find_by_id(@request[:client_id])
253
- publish client.channels.to_json
254
- else
255
- publish nil.to_json
256
- end
257
- when :show_clients
258
- if @request[:client_ids] and @request[:client_ids].any?
259
- clients = @request[:client_ids].collect{ |client_id| Client.find_by_id(client_id) }.compact.uniq
260
- else
261
- clients = Juggernaut::Client.find_all
262
- end
263
- publish clients.to_json
264
- when :show_client
265
- query_needs :client_id
266
- publish Juggernaut::Client.find_by_id(@request[:client_id]).to_json
267
- when :show_clients_for_channels
268
- query_needs :channels
269
- publish Juggernaut::Client.find_by_channels(@request[:channels]).to_json
270
- else
271
- raise MalformedQuery, @request
272
- end
273
- end
274
-
275
- def noop_command
276
- logger.debug "NOOP"
277
- end
278
-
279
- def subscribe_command
280
- logger.debug "SUBSCRIBE: #{@request.inspect}"
281
-
282
- if channels = @request[:channels]
283
- add_channels(channels)
284
- end
285
-
286
- @client = Juggernaut::Client.find_or_create(self, @request)
287
-
288
- if !@client.subscription_request(@channels)
289
- raise UnauthorisedSubscription, @client
290
- end
291
-
292
- if options[:store_messages]
293
- @client.send_queued_messages(self)
294
- end
295
- end
296
-
297
- private
298
-
299
- # Different broadcast types
300
-
301
- def broadcast_to_channels(msg, channels = [])
302
- Juggernaut::Client.find_all.each {|client| client.send_message(msg, channels) }
303
- end
304
-
305
- def broadcast_to_client(body, client_id, channels)
306
- client = Juggernaut::Client.find_by_id(client_id)
307
- client.send_message(body, channels) if client
308
- end
309
-
310
- # Helper methods
311
-
312
- def broadcast_needs(*args)
313
- args.each do |arg|
314
- raise MalformedBroadcast, @request unless @request.has_key?(arg)
315
- end
316
- end
317
-
318
- def subscribe_needs(*args)
319
- args.each do |arg|
320
- raise MalformedSubscribe, @request unless @request.has_key?(arg)
321
- end
322
- end
323
-
324
- def query_needs(*args)
325
- args.each do |arg|
326
- raise MalformedQuery, @request unless @request.has_key?(arg)
327
- end
328
- end
329
-
330
- def authenticate_broadcast_or_query
331
- if options[:allowed_ips]
332
- return true if options[:allowed_ips].include?(client_ip)
333
- elsif !@request[:secret_key]
334
- return true if broadcast_query_request
335
- elsif options[:secret_key]
336
- return true if @request[:secret_key] == options[:secret_key]
337
- end
338
- if !options[:allowed_ips] and !options[:secret_key] and !options[:broadcast_query_login_url]
339
- return true
340
- end
341
- false
342
- end
343
-
344
- def broadcast_query_request
345
- return false unless options[:broadcast_query_login_url]
346
- url = URI.parse(options[:broadcast_query_login_url])
347
- params = []
348
- params << "client_id=#{@request[:client_id]}" if @request[:client_id]
349
- params << "session_id=#{URI.escape(@request[:session_id])}" if @request[:session_id]
350
- params << "type=#{@request[:type]}"
351
- params << "command=#{@request[:command]}"
352
- (@request[:channels] || []).each {|chan| params << "channels[]=#{chan}" }
353
- url.query = params.join('&')
354
- begin
355
- open(url.to_s, "User-Agent" => "Ruby/#{RUBY_VERSION}")
356
- rescue Timeout::Error
357
- return false
358
- rescue
359
- return false
360
- end
361
- true
362
- end
363
-
364
- def client_ip
365
- Socket.unpack_sockaddr_in(get_peername)[1] rescue nil
366
- end
367
- end
368
- end
@@ -1,11 +0,0 @@
1
- class Hash
2
- def symbolize_keys!
3
- keys.each do |key|
4
- unless key.is_a?(Symbol) || (new_key = key.to_sym).nil?
5
- self[new_key] = self[key]
6
- delete(key)
7
- end
8
- end
9
- self
10
- end
11
- end
@@ -1,176 +0,0 @@
1
-
2
- $:.unshift "../lib"
3
- require "juggernaut"
4
- require "test/unit"
5
- require "shoulda"
6
- require "mocha"
7
-
8
- class TestClient < Test::Unit::TestCase
9
-
10
- CONFIG = File.join(File.dirname(__FILE__), "files", "juggernaut.yml")
11
-
12
- class DummySubscriber; end
13
-
14
- EXAMPLE_URL = "http://localhost:5000/callbacks/example"
15
- SECURE_URL = "https://localhost:5000/callbacks/example"
16
-
17
- context "Client" do
18
-
19
- setup do
20
- Juggernaut.options = {
21
- :logout_connection_url => "http://localhost:5000/callbacks/logout_connection",
22
- :logout_url => "http://localhost:5000/callbacks/logout",
23
- :subscription_url => "http://localhost:5000/callbacks/subscription"
24
- }
25
- @s1 = DummySubscriber.new
26
- @request = {
27
- :client_id => "jonny",
28
- :session_id => rand(1_000_000).to_s(16)
29
- }
30
- @client = Juggernaut::Client.find_or_create(@s1, @request)
31
- end
32
-
33
- teardown do
34
- Juggernaut::Client.reset!
35
- end
36
-
37
- should "have correct JSON representation" do
38
- assert_nothing_raised do
39
- json = {
40
- "client_id" => "jonny",
41
- "num_connections" => 1,
42
- "session_id" => @request[:session_id]
43
- }
44
- assert_equal json, JSON.parse(@client.to_json)
45
- end
46
- end
47
-
48
- should "return the client based on subscriber's signature" do
49
- @s1.stubs(:signature).returns("012345")
50
- assert_equal @client, Juggernaut::Client.find_by_signature("012345")
51
- end
52
-
53
- should "return the client based on client ID and channel list" do
54
- @client.stubs(:has_channels?).with(%w(a couple of channels)).returns(true)
55
- assert_equal @client, Juggernaut::Client.find_by_id_and_channels("jonny", %w(a couple of channels))
56
- assert_nil Juggernaut::Client.find_by_id_and_channels("peter", %w(a couple of channels))
57
- @client.stubs(:has_channels?).with(%w(something else)).returns(false)
58
- assert_nil Juggernaut::Client.find_by_id_and_channels("jonny", %w(something else))
59
- end
60
-
61
- should "automatically be registered, and can unregister" do
62
- assert @client.send(:registered?)
63
- assert_equal @client, @client.send(:unregister)
64
- assert_equal false, @client.send(:registered?)
65
- end
66
-
67
- should "be alive if at least one subscriber is alive" do
68
- @s1.stubs(:alive?).returns(true)
69
- @s2 = DummySubscriber.new
70
- @client.add_new_connection(@s2)
71
- @s2.stubs(:alive?).returns(false)
72
- assert @client.alive?
73
- end
74
-
75
- should "not be alive if no subscriber is alive" do
76
- @s1.stubs(:alive?).returns(false)
77
- @s2 = DummySubscriber.new
78
- @client.add_new_connection(@s2)
79
- @s2.stubs(:alive?).returns(false)
80
- assert_equal false, @client.alive?
81
- end
82
-
83
- should "not give up if within the timeout period" do
84
- Juggernaut.options[:timeout] = 10
85
- @s1.stubs(:alive?).returns(false)
86
- @client.send(:reset_logout_timeout!)
87
- assert_equal false, @client.give_up?
88
- end
89
-
90
- should "not give up if at least one subscriber is alive" do
91
- Juggernaut.options[:timeout] = 0
92
- @s1.stubs(:alive?).returns(true)
93
- @client.send(:reset_logout_timeout!)
94
- assert_equal false, @client.give_up?
95
- end
96
-
97
- should "send logouts after timeout" do
98
- Juggernaut.options[:timeout] = 0
99
- @s1.stubs(:alive?).returns(false)
100
- @client.send(:reset_logout_timeout!)
101
- @client.expects(:logout_request).once
102
- Juggernaut::Client.send_logouts_after_timeout
103
- end
104
-
105
- %w(subscription logout_connection).each do |type|
106
-
107
- context "#{type} request" do
108
-
109
- should "post to the correct URL" do
110
- @client.expects(:post_request).with(Juggernaut.options[:"#{type}_url"], %w(master), :timeout => 5).returns(true)
111
- assert_equal true, @client.send("#{type}_request", %w(master))
112
- end
113
-
114
- should "not raise exceptions if posting raises an exception" do
115
- @client.expects(:post_request).with(Juggernaut.options[:"#{type}_url"], %w(master), :timeout => 5).returns(false)
116
- assert_nothing_raised {
117
- assert_equal false, @client.send("#{type}_request", %w(master))
118
- }
119
- end
120
-
121
- end
122
-
123
- end
124
-
125
- context "post to URL" do
126
-
127
- should "return true when successful" do
128
- Net::HTTP.any_instance.
129
- expects(:post).
130
- with("/callbacks/example", "client_id=jonny&session_id=#{@request[:session_id]}&channels[]=master&channels[]=slave", {"User-Agent" => "Ruby/#{RUBY_VERSION}"}).
131
- returns([Net::HTTPOK.new('1.1', '200', 'OK'), ''])
132
- assert_equal true, @client.send(:post_request, EXAMPLE_URL, %w(master slave))
133
- end
134
-
135
- should "return false on an internal server error" do
136
- Net::HTTP.any_instance.expects(:post).returns([Net::HTTPInternalServerError.new('1.1', '500', 'Internal Server Error'), ''])
137
- assert_equal false, @client.send(:post_request, EXAMPLE_URL, %w(master slave))
138
- end
139
-
140
- should "return false when a runtime error is caught" do
141
- Net::HTTP.any_instance.expects(:post).raises(RuntimeError)
142
- assert_equal false, @client.send(:post_request, EXAMPLE_URL, %w(master slave))
143
- end
144
-
145
- should "return false when callback times out" do
146
- Net::HTTP.any_instance.expects(:post).raises(Timeout::Error)
147
- assert_equal false, @client.send(:post_request, EXAMPLE_URL, %w(master slave))
148
- end
149
-
150
- context "using a secure URL" do
151
-
152
- should "return true when successful" do
153
- Net::HTTP.any_instance.expects(:post).returns([Net::HTTPOK.new('1.1', '200', 'OK'), ''])
154
- Net::HTTP.any_instance.expects(:use_ssl=).with(true).returns(true)
155
- assert_equal true, @client.send(:post_request, SECURE_URL, %w(master slave))
156
- end
157
-
158
- end
159
-
160
- end
161
-
162
- context "channel list" do
163
-
164
- should "be the unique list of all channels in the subscribers" do
165
- @s1.stubs(:channels).returns(%w(master slave1))
166
- @s2 = DummySubscriber.new
167
- @s2.stubs(:channels).returns(%w(master slave2))
168
- @client.add_new_connection(@s2)
169
- assert_same_elements %w(master slave1 slave2), @client.channels
170
- end
171
-
172
- end
173
-
174
- end
175
-
176
- end