faye 0.5.5 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of faye might be problematic. Click here for more details.

Files changed (62) hide show
  1. data/History.txt +14 -0
  2. data/README.rdoc +98 -0
  3. data/Rakefile +17 -15
  4. data/lib/faye-browser-min.js +1 -1
  5. data/lib/faye.rb +14 -5
  6. data/lib/faye/adapters/rack_adapter.rb +12 -5
  7. data/lib/faye/engines/base.rb +62 -0
  8. data/lib/faye/engines/connection.rb +63 -0
  9. data/lib/faye/engines/memory.rb +89 -0
  10. data/lib/faye/engines/redis.rb +141 -0
  11. data/lib/faye/error.rb +16 -4
  12. data/lib/faye/mixins/publisher.rb +6 -0
  13. data/lib/faye/protocol/channel.rb +34 -86
  14. data/lib/faye/protocol/client.rb +36 -52
  15. data/lib/faye/protocol/extensible.rb +3 -0
  16. data/lib/faye/protocol/server.rb +119 -169
  17. data/lib/faye/transport/http.rb +45 -0
  18. data/lib/faye/transport/local.rb +15 -0
  19. data/lib/faye/{network → transport}/transport.rb +36 -49
  20. data/spec/browser.html +35 -0
  21. data/spec/install.sh +48 -0
  22. data/spec/javascript/channel_spec.js +15 -0
  23. data/spec/javascript/client_spec.js +610 -0
  24. data/spec/javascript/engine_spec.js +319 -0
  25. data/spec/javascript/faye_spec.js +15 -0
  26. data/spec/javascript/grammar_spec.js +66 -0
  27. data/spec/javascript/node_adapter_spec.js +276 -0
  28. data/spec/javascript/server/connect_spec.js +168 -0
  29. data/spec/javascript/server/disconnect_spec.js +121 -0
  30. data/spec/javascript/server/extensions_spec.js +60 -0
  31. data/spec/javascript/server/handshake_spec.js +153 -0
  32. data/spec/javascript/server/subscribe_spec.js +245 -0
  33. data/spec/javascript/server/unsubscribe_spec.js +245 -0
  34. data/spec/javascript/server_spec.js +146 -0
  35. data/spec/javascript/transport_spec.js +130 -0
  36. data/spec/node.js +34 -0
  37. data/spec/ruby/channel_spec.rb +17 -0
  38. data/spec/ruby/client_spec.rb +615 -0
  39. data/spec/ruby/engine_spec.rb +312 -0
  40. data/spec/ruby/faye_spec.rb +14 -0
  41. data/spec/ruby/grammar_spec.rb +68 -0
  42. data/spec/ruby/rack_adapter_spec.rb +209 -0
  43. data/spec/ruby/server/connect_spec.rb +170 -0
  44. data/spec/ruby/server/disconnect_spec.rb +120 -0
  45. data/spec/ruby/server/extensions_spec.rb +69 -0
  46. data/spec/ruby/server/handshake_spec.rb +151 -0
  47. data/spec/ruby/server/subscribe_spec.rb +247 -0
  48. data/spec/ruby/server/unsubscribe_spec.rb +247 -0
  49. data/spec/ruby/server_spec.rb +138 -0
  50. data/spec/ruby/transport_spec.rb +128 -0
  51. data/spec/spec_helper.rb +5 -0
  52. data/spec/testswarm.pl +200 -0
  53. data/spec/thin_proxy.rb +36 -0
  54. metadata +119 -84
  55. data/Manifest.txt +0 -27
  56. data/README.txt +0 -98
  57. data/lib/faye/protocol/connection.rb +0 -111
  58. data/test/scenario.rb +0 -172
  59. data/test/test_channel.rb +0 -54
  60. data/test/test_clients.rb +0 -381
  61. data/test/test_grammar.rb +0 -86
  62. data/test/test_server.rb +0 -488
@@ -0,0 +1,141 @@
1
+ module Faye
2
+ module Engine
3
+
4
+ class Redis < Base
5
+ DEFAULT_HOST = 'localhost'
6
+ DEFAULT_PORT = 6379
7
+
8
+ def init
9
+ return if @redis
10
+ require 'em-hiredis'
11
+
12
+ host = @options[:host] || DEFAULT_HOST
13
+ port = @options[:port] || DEFAULT_PORT
14
+
15
+ @redis = EventMachine::Hiredis::Client.connect(host, port)
16
+ @subscriber = EventMachine::Hiredis::Client.connect(host, port)
17
+
18
+ @subscriber.subscribe('/notifications')
19
+ @subscriber.on(:message) do |topic, message|
20
+ empty_queue(message) if topic == '/notifications'
21
+ end
22
+ end
23
+
24
+ def disconnect
25
+ @subscriber.unsubscribe('/notifications')
26
+ end
27
+
28
+ def create_client(&callback)
29
+ init
30
+ client_id = Faye.random
31
+ @redis.sadd('/clients', client_id) do |added|
32
+ if added == 0
33
+ create_client(&callback)
34
+ else
35
+ debug 'Created new client ?', client_id
36
+ ping(client_id)
37
+ callback.call(client_id)
38
+ end
39
+ end
40
+ end
41
+
42
+ def destroy_client(client_id, &callback)
43
+ init
44
+ @redis.srem('/clients', client_id)
45
+ @redis.del("/clients/#{client_id}/messages")
46
+ @redis.smembers("/clients/#{client_id}/channels") do |channels|
47
+ n, i = channels.size, 0
48
+ if n == 0
49
+ debug 'Destroyed client ?', client_id
50
+ callback.call if callback
51
+ else
52
+ channels.each do |channel|
53
+ unsubscribe(client_id, channel) do
54
+ i += 1
55
+ if i == n
56
+ debug 'Destroyed client ?', client_id
57
+ callback.call if callback
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
64
+
65
+ def client_exists(client_id, &callback)
66
+ init
67
+ @redis.sismember('/clients', client_id) do |exists|
68
+ callback.call(exists != 0)
69
+ end
70
+ end
71
+
72
+ def ping(client_id)
73
+ timeout = @options[:timeout]
74
+ time = Time.now.to_i.to_s
75
+
76
+ return unless Numeric === timeout
77
+
78
+ debug 'Ping ?, ?', client_id, timeout
79
+ remove_timeout(client_id)
80
+ @redis.set("/clients/#{client_id}/ping", time)
81
+ add_timeout(client_id, 2 * timeout) do
82
+ @redis.get("/clients/#{client_id}/ping") do |ping|
83
+ destroy_client(client_id) if ping == time
84
+ end
85
+ end
86
+ end
87
+
88
+ def subscribe(client_id, channel, &callback)
89
+ init
90
+ @redis.sadd("/clients/#{client_id}/channels", channel)
91
+ @redis.sadd("/channels#{channel}", client_id) do
92
+ debug 'Subscribed client ? to channel ?', client_id, channel
93
+ callback.call if callback
94
+ end
95
+ end
96
+
97
+ def unsubscribe(client_id, channel, &callback)
98
+ init
99
+ @redis.srem("/clients/#{client_id}/channels", channel)
100
+ @redis.srem("/channels#{channel}", client_id) do
101
+ debug 'Unsubscribed client ? from channel ?', client_id, channel
102
+ callback.call if callback
103
+ end
104
+ end
105
+
106
+ def publish(message)
107
+ init
108
+ debug 'Publishing message ?', message
109
+ json_message = JSON.dump(message)
110
+ channels = Channel.expand(message['channel'])
111
+ channels.each do |channel|
112
+ @redis.smembers("/channels#{channel}") do |clients|
113
+ clients.each do |client_id|
114
+ debug 'Queueing for client ?: ?', client_id, message
115
+ @redis.sadd("/clients/#{client_id}/messages", json_message)
116
+ @redis.publish('/notifications', client_id)
117
+ end
118
+ end
119
+ end
120
+ end
121
+
122
+ private
123
+
124
+ def empty_queue(client_id)
125
+ return unless conn = connection(client_id, false)
126
+ init
127
+
128
+ key = "/clients/#{client_id}/messages"
129
+ @redis.smembers(key) do |json_messages|
130
+ json_messages.each do |json_message|
131
+ @redis.srem(key, json_message)
132
+ conn.deliver(JSON.parse(json_message))
133
+ end
134
+ end
135
+ end
136
+ end
137
+
138
+ register 'redis', Redis
139
+
140
+ end
141
+ end
data/lib/faye/error.rb CHANGED
@@ -6,16 +6,28 @@ module Faye
6
6
  new(code[0], args, code[1]).to_s
7
7
  end
8
8
 
9
- attr_reader :code, :args, :message
9
+ def self.parse(message)
10
+ message ||= ''
11
+ return new(nil, [], message) unless Grammar::ERROR =~ message
12
+
13
+ parts = message.split(':')
14
+ code = parts[0].to_i
15
+ params = parts[1].split(',')
16
+ message = parts[2]
17
+
18
+ new(code, params, message)
19
+ end
20
+
21
+ attr_reader :code, :params, :message
10
22
 
11
- def initialize(code, args, message)
23
+ def initialize(code, params, message)
12
24
  @code = code
13
- @args = args
25
+ @params = params
14
26
  @message = message
15
27
  end
16
28
 
17
29
  def to_s
18
- "#{ @code }:#{ @args * ',' }:#{ @message }"
30
+ "#{ @code }:#{ @params * ',' }:#{ @message }"
19
31
  end
20
32
 
21
33
  # http://code.google.com/p/cometd/wiki/BayeuxCodes
@@ -14,9 +14,15 @@ module Faye
14
14
 
15
15
  def remove_subscriber(event_type, listener)
16
16
  return unless @subscribers and @subscribers[event_type]
17
+ return @subscribers.delete(event_type) unless listener
18
+
17
19
  @subscribers[event_type].delete_if(&listener.method(:==))
18
20
  end
19
21
 
22
+ def remove_subscribers
23
+ @subscribers = {}
24
+ end
25
+
20
26
  def publish_event(event_type, *args)
21
27
  return unless @subscribers and @subscribers[event_type]
22
28
  @subscribers[event_type].each do |listener|
@@ -26,6 +26,23 @@ module Faye
26
26
  SERVICE = :service
27
27
 
28
28
  class << self
29
+ def expand(name)
30
+ segments = parse(name)
31
+ channels = ['/**', name]
32
+
33
+ copy = segments.dup
34
+ copy[copy.size - 1] = '*'
35
+ channels << unparse(copy)
36
+
37
+ 1.upto(segments.size - 1) do |i|
38
+ copy = segments[0...i]
39
+ copy << '**'
40
+ channels << unparse(copy)
41
+ end
42
+
43
+ channels
44
+ end
45
+
29
46
  def valid?(name)
30
47
  Grammar::CHANNEL_NAME =~ name or
31
48
  Grammar::CHANNEL_PATTERN =~ name
@@ -36,6 +53,10 @@ module Faye
36
53
  name.split('/')[1..-1].map { |s| s.to_sym }
37
54
  end
38
55
 
56
+ def unparse(segments)
57
+ '/' + segments.join('/')
58
+ end
59
+
39
60
  def meta?(name)
40
61
  segments = parse(name)
41
62
  segments ? (segments.first == META) : nil
@@ -52,110 +73,35 @@ module Faye
52
73
  end
53
74
  end
54
75
 
55
- class Tree
56
- include Enumerable
57
- attr_accessor :value
58
-
76
+ class Set
59
77
  def initialize(parent = nil, value = nil)
60
- @parent = parent
61
- @value = value
62
- @children = {}
63
- end
64
-
65
- def each_child
66
- @children.each { |key, subtree| yield(key, subtree) }
67
- end
68
-
69
- def each(prefix = [], &block)
70
- each_child { |path, subtree| subtree.each(prefix + [path], &block) }
71
- yield(prefix, @value) unless @value.nil?
78
+ @channels = {}
72
79
  end
73
80
 
74
81
  def keys
75
- map { |key, value| '/' + key * '/' }
76
- end
77
-
78
- def [](name)
79
- subtree = traverse(name)
80
- subtree ? subtree.value : nil
81
- end
82
-
83
- def []=(name, value)
84
- subtree = traverse(name, true)
85
- subtree.value = value unless subtree.nil?
82
+ @channels.keys
86
83
  end
87
84
 
88
- def remove(name = nil)
89
- if name
90
- subtree = traverse(name)
91
- subtree.remove unless subtree.nil?
92
- else
93
- return unless @parent
94
- @parent.remove_child(self)
95
- @parent = @value = nil
96
- end
97
- end
98
-
99
- def remove_child(subtree)
100
- each_child do |key, child|
101
- @children.delete(key) if child == subtree
102
- end
103
- remove if @children.empty? and @value.nil?
104
- end
105
-
106
- def traverse(path, create_if_absent = false)
107
- path = Channel.parse(path) if String === path
108
-
109
- return nil if path.nil?
110
- return self if path.empty?
111
-
112
- subtree = @children[path.first]
113
- return nil if subtree.nil? and not create_if_absent
114
- subtree = @children[path.first] = Tree.new(self) if subtree.nil?
115
-
116
- subtree.traverse(path[1..-1], create_if_absent)
85
+ def remove(name)
86
+ @channels.delete(name)
117
87
  end
118
88
 
119
- def glob(path = [])
120
- path = Channel.parse(path) if String === path
121
-
122
- return [] if path.nil?
123
- return @value.nil? ? [] : [@value] if path.empty?
124
-
125
- if path == [:*]
126
- return @children.inject([]) do |list, (key, subtree)|
127
- list << subtree.value unless subtree.value.nil?
128
- list
129
- end
130
- end
131
-
132
- if path == [:**]
133
- list = map { |key, value| value }
134
- list.pop unless @value.nil?
135
- return list
136
- end
137
-
138
- list = @children.values_at(path.first, :*).
139
- compact.
140
- map { |t| t.glob(path[1..-1]) }
141
-
142
- list << @children[:**].value if @children[:**]
143
- list.flatten
89
+ def has_subscription?(name)
90
+ @channels.has_key?(name)
144
91
  end
145
92
 
146
93
  def subscribe(names, callback)
147
94
  return unless callback
148
95
  names.each do |name|
149
- channel = self[name] ||= Channel.new(name)
96
+ channel = @channels[name] ||= Channel.new(name)
150
97
  channel.add_subscriber(:message, callback)
151
98
  end
152
99
  end
153
100
 
154
101
  def unsubscribe(name, callback)
155
- channel = self[name]
102
+ channel = @channels[name]
156
103
  return false unless channel
157
104
  channel.remove_subscriber(:message, callback)
158
-
159
105
  if channel.unused?
160
106
  remove(name)
161
107
  true
@@ -165,8 +111,10 @@ module Faye
165
111
  end
166
112
 
167
113
  def distribute_message(message)
168
- glob(message['channel']).each do |channel|
169
- channel.publish_event(:message, message['data'])
114
+ channels = Channel.expand(message['channel'])
115
+ channels.each do |name|
116
+ channel = @channels[name]
117
+ channel.publish_event(:message, message['data']) if channel
170
118
  end
171
119
  end
172
120
  end
@@ -2,7 +2,6 @@ module Faye
2
2
  class Client
3
3
 
4
4
  include EventMachine::Deferrable
5
- include Timeouts
6
5
  include Logging
7
6
  include Extensible
8
7
 
@@ -27,19 +26,27 @@ module Faye
27
26
 
28
27
  @transport = Transport.get(self, MANDATORY_CONNECTION_TYPES)
29
28
  @state = UNCONNECTED
30
- @outbox = []
31
- @channels = Channel::Tree.new
29
+ @channels = Channel::Set.new
32
30
 
33
31
  @namespace = Namespace.new
34
32
  @response_callbacks = {}
35
33
 
36
34
  @advice = {
37
35
  'reconnect' => RETRY,
38
- 'interval' => 1000.0 * (@options[:interval] || Connection::INTERVAL),
39
- 'timeout' => 1000.0 * (@options[:timeout] || CONNECTION_TIMEOUT)
36
+ 'interval' => 1000.0 * (@options[:interval] || Engine::INTERVAL),
37
+ 'timeout' => 1000.0 * (@options[:timeout] || CONNECTION_TIMEOUT)
40
38
  }
41
39
  end
42
40
 
41
+ def state
42
+ case @state
43
+ when UNCONNECTED then :UNCONNECTED
44
+ when CONNECTING then :CONNECTING
45
+ when CONNECTED then :CONNECTED
46
+ when DISCONNECTED then :DISCONNECTED
47
+ end
48
+ end
49
+
43
50
  # Request
44
51
  # MUST include: * channel
45
52
  # * version
@@ -81,7 +88,7 @@ module Faye
81
88
 
82
89
  info('Handshake successful: ?', @client_id)
83
90
 
84
- subscribe(@channels.keys)
91
+ subscribe(@channels.keys, true)
85
92
  block.call if block_given?
86
93
 
87
94
  else
@@ -148,7 +155,7 @@ module Faye
148
155
  })
149
156
 
150
157
  info('Clearing channel listeners for ?', @client_id)
151
- @channels = Channel::Tree.new
158
+ @channels = Channel::Set.new
152
159
  end
153
160
 
154
161
  # Request Response
@@ -161,16 +168,21 @@ module Faye
161
168
  # * ext
162
169
  # * id
163
170
  # * timestamp
164
- def subscribe(channels, &block)
171
+ def subscribe(channels, force = false, &block)
165
172
  if Array === channels
166
173
  return channels.each do |channel|
167
- subscribe(channel, &block)
174
+ subscribe(channel, force, &block)
168
175
  end
169
176
  end
170
177
 
171
- validate_channel(channels)
172
178
  subscription = Subscription.new(self, channels, block)
173
179
 
180
+ if not force and @channels.has_subscription?(channels)
181
+ @channels.subscribe([channels], block)
182
+ subscription.set_deferred_status(:succeeded)
183
+ return subscription
184
+ end
185
+
174
186
  connect {
175
187
  info('Client ? attempting to subscribe to ?', @client_id, channels)
176
188
 
@@ -187,6 +199,8 @@ module Faye
187
199
  @channels.subscribe(channels, block)
188
200
 
189
201
  subscription.set_deferred_status(:succeeded)
202
+ else
203
+ subscription.set_deferred_status(:failed, Error.parse(response['error']))
190
204
  end
191
205
  end
192
206
  }
@@ -210,8 +224,6 @@ module Faye
210
224
  end
211
225
  end
212
226
 
213
- validate_channel(channels)
214
-
215
227
  dead = @channels.unsubscribe(channels, block)
216
228
  return unless dead
217
229
 
@@ -240,7 +252,9 @@ module Faye
240
252
  # * id * error
241
253
  # * ext * ext
242
254
  def publish(channel, data)
243
- validate_channel(channel)
255
+ unless Grammar::CHANNEL_NAME =~ channel
256
+ raise "Cannot publish: '#{channel}' is not a valid channel name"
257
+ end
244
258
 
245
259
  connect {
246
260
  info('Client ? queueing published message to ?: ?', @client_id, channel, data)
@@ -271,6 +285,15 @@ module Faye
271
285
 
272
286
  private
273
287
 
288
+ def send(message, &callback)
289
+ message['id'] = @namespace.generate
290
+ @response_callbacks[message['id']] = callback if callback
291
+
292
+ pipe_through_extensions(:outgoing, message) do |message|
293
+ @transport.send(message, @advice['timeout'] / 1000.0) if message
294
+ end
295
+ end
296
+
274
297
  def handle_advice(advice)
275
298
  @advice.update(advice)
276
299
 
@@ -298,45 +321,6 @@ module Faye
298
321
  EventMachine.add_timer(@advice['interval'] / 1000.0) { connect }
299
322
  end
300
323
 
301
- def send(message, &callback)
302
- message['id'] = @namespace.generate
303
- @response_callbacks[message['id']] = callback if callback
304
-
305
- pipe_through_extensions(:outgoing, message) do |message|
306
- if message
307
- if message['channel'] == Channel::HANDSHAKE
308
- @transport.send(message, @advice['timeout'] / 1000.0)
309
- else
310
- @outbox << message
311
-
312
- if message['channel'] == Channel::CONNECT
313
- @connect_message = message
314
- end
315
-
316
- add_timeout(:publish, Connection::MAX_DELAY) { flush! }
317
- end
318
- end
319
- end
320
- end
321
-
322
- def flush!
323
- remove_timeout(:publish)
324
-
325
- if @outbox.size > 1 and @connect_message
326
- @connect_message['advice'] = {'timeout' => 0}
327
- end
328
-
329
- @connect_message = nil
330
-
331
- @transport.send(@outbox, @advice['timeout'] / 1000.0)
332
- @outbox = []
333
- end
334
-
335
- def validate_channel(channel)
336
- raise "'#{ channel }' is not a valid channel name" unless Channel.valid?(channel)
337
- raise "Clients may not subscribe to channel '#{ channel }'" unless Channel.subscribable?(channel)
338
- end
339
-
340
324
  end
341
325
  end
342
326