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
@@ -1,5 +1,6 @@
1
1
  module Faye
2
2
  module Extensible
3
+ include Logging
3
4
 
4
5
  def add_extension(extension)
5
6
  @extensions ||= []
@@ -20,6 +21,8 @@ module Faye
20
21
  end
21
22
 
22
23
  def pipe_through_extensions(stage, message, &callback)
24
+ debug 'Passing through ? extensions: ?', stage, message
25
+
23
26
  return callback.call(message) unless @extensions
24
27
  extensions = @extensions.dup
25
28
 
@@ -5,30 +5,35 @@ module Faye
5
5
  include Extensible
6
6
 
7
7
  def initialize(options = {})
8
- info('New server created')
9
- @options = options
10
- @channels = Channel::Tree.new
11
- @connections = {}
12
- @namespace = Namespace.new
8
+ @options = options || {}
9
+ engine_opts = @options[:engine] || {}
10
+ engine_opts[:timeout] = @options[:timeout]
11
+ @engine = Faye::Engine.get(engine_opts)
12
+
13
+ info 'Created new server: ?', @options
13
14
  end
14
15
 
15
- def client_ids
16
- @connections.keys
16
+ def flush_connection(messages)
17
+ [messages].flatten.each do |message|
18
+ client_id = message["clientId"]
19
+ info 'Flushing connection for ?', client_id
20
+ @engine.flush(client_id) if client_id
21
+ end
17
22
  end
18
23
 
19
- def process(messages, local_or_remote = false, &callback)
20
- socket = local_or_remote.is_a?(WebSocket) ? local_or_remote : nil
21
- local = (local_or_remote == true)
22
-
23
- debug('Processing messages from ? client', local ? 'LOCAL' : 'REMOTE')
24
-
24
+ def process(messages, local = false, &callback)
25
25
  messages = [messages].flatten
26
+ info 'Processing messages: ? (local: ?)', messages, local
27
+
28
+ return callback.call([]) if messages.size == 0
26
29
  processed, responses = 0, []
27
30
 
28
31
  gather_replies = lambda do |replies|
29
32
  responses.concat(replies)
30
33
  processed += 1
31
- callback.call(responses.compact) if processed == messages.size
34
+ responses.compact!
35
+ info 'Returning replies: ?', responses
36
+ callback.call(responses) if processed == messages.size
32
37
  end
33
38
 
34
39
  handle_reply = lambda do |replies|
@@ -36,6 +41,7 @@ module Faye
36
41
  gather_replies.call(replies) if expected == 0
37
42
 
38
43
  replies.each_with_index do |reply, i|
44
+ debug 'Processing reply: ?', reply
39
45
  pipe_through_extensions(:outgoing, reply) do |message|
40
46
  replies[i] = message
41
47
  extended += 1
@@ -46,33 +52,11 @@ module Faye
46
52
 
47
53
  messages.each do |message|
48
54
  pipe_through_extensions(:incoming, message) do |piped_message|
49
- handle(piped_message, socket, local, &handle_reply)
55
+ handle(piped_message, local, &handle_reply)
50
56
  end
51
57
  end
52
58
  end
53
59
 
54
- def flush_connection(messages)
55
- [messages].flatten.each do |message|
56
- connection = @connections[message['clientId']]
57
- connection.flush! if connection
58
- end
59
- end
60
-
61
- private
62
-
63
- def connection(id)
64
- return @connections[id] if @connections.has_key?(id)
65
- connection = Connection.new(id, @options)
66
- connection.add_subscriber(:stale_connection, method(:destroy_connection))
67
- @connections[id] = connection
68
- end
69
-
70
- def destroy_connection(connection)
71
- connection.disconnect!
72
- connection.remove_subscriber(:stale_connection, method(:destroy_connection))
73
- @connections.delete(connection.id)
74
- end
75
-
76
60
  def make_response(message)
77
61
  response = {}
78
62
  %w[id clientId channel error].each do |field|
@@ -84,22 +68,15 @@ module Faye
84
68
  response
85
69
  end
86
70
 
87
- def distribute_message(message)
88
- return if message['error']
89
- @channels.glob(message['channel']).each do |channel|
90
- channel << message
91
- info('Publishing message ? from client ? to ?', message['data'], message['clientId'], channel.name)
92
- end
93
- end
94
-
95
- def handle(message, socket = nil, local = false, &callback)
71
+ def handle(message, local = false, &callback)
96
72
  return callback.call([]) if !message
73
+ info 'Handling message: ? (local: ?)', message, local
97
74
 
98
- distribute_message(message)
99
75
  channel_name = message['channel']
76
+ @engine.publish(message) unless message['error'] or Grammar::CHANNEL_NAME !~ channel_name
100
77
 
101
78
  if Channel.meta?(channel_name)
102
- handle_meta(message, socket, local, &callback)
79
+ handle_meta(message, local, &callback)
103
80
  elsif message['clientId'].nil?
104
81
  callback.call([])
105
82
  else
@@ -109,46 +86,24 @@ module Faye
109
86
  end
110
87
  end
111
88
 
112
- def handle_meta(message, socket, local, &callback)
113
- response = __send__(Channel.parse(message['channel'])[1], message, local)
89
+ def handle_meta(message, local, &callback)
90
+ method = Channel.parse(message['channel'])[1]
114
91
 
115
- advize(response)
116
-
117
- if response['channel'] == Channel::CONNECT and response['successful'] == true
118
- return accept_connection(message['advice'], response, socket, &callback)
119
- end
120
-
121
- callback.call([response])
122
- end
123
-
124
- def accept_connection(options, response, socket, &callback)
125
- info('Accepting connection from ?', response['clientId'])
126
-
127
- connection = connection(response['clientId'])
128
-
129
- # Disabled because CometD doesn't like messages not being
130
- # delivered as part of a /meta/* response
131
- # if socket
132
- # return connection.socket = socket
133
- # end
134
-
135
- connection.connect(options) do |events|
136
- info('Sending event messages to ?', response['clientId'])
137
- debug('Events for ?: ?', response['clientId'], events)
138
- callback.call([response] + events)
92
+ __send__(method, message, local) do |responses|
93
+ responses = [responses].flatten
94
+ responses.each(&method(:advize))
95
+ callback.call(responses)
139
96
  end
140
97
  end
141
98
 
142
99
  def advize(response)
143
- connection = @connections[response['clientId']]
144
-
145
100
  advice = response['advice'] ||= {}
146
- if connection
147
- advice['reconnect'] ||= 'retry'
148
- advice['interval'] ||= (connection.interval * 1000).floor
149
- advice['timeout'] ||= (connection.timeout * 1000).floor
150
- else
101
+ if response['error']
151
102
  advice['reconnect'] ||= 'handshake'
103
+ else
104
+ advice['reconnect'] ||= 'retry'
105
+ advice['interval'] ||= (@engine.interval * 1000).floor
106
+ advice['timeout'] ||= (@engine.timeout * 1000).floor
152
107
  end
153
108
  end
154
109
 
@@ -157,7 +112,7 @@ module Faye
157
112
  # MAY contain * minimumVersion
158
113
  # * ext
159
114
  # * id
160
- def handshake(message, local = false)
115
+ def handshake(message, local = false, &callback)
161
116
  response = make_response(message)
162
117
  response['version'] = BAYEUX_VERSION
163
118
 
@@ -177,128 +132,123 @@ module Faye
177
132
  end
178
133
 
179
134
  response['successful'] = response['error'].nil?
180
- return response unless response['successful']
135
+ return callback.call(response) unless response['successful']
181
136
 
182
- client_id = @namespace.generate
183
- response['clientId'] = connection(client_id).id
184
- info('Accepting handshake from client ?', response['clientId'])
185
- response
137
+ @engine.create_client do |client_id|
138
+ response['clientId'] = client_id
139
+ callback.call(response)
140
+ end
186
141
  end
187
142
 
188
143
  # MUST contain * clientId
189
144
  # * connectionType
190
145
  # MAY contain * ext
191
146
  # * id
192
- def connect(message, local = false)
193
- response = make_response(message)
194
-
195
- client_id = message['clientId']
196
- connection = client_id ? @connections[client_id] : nil
147
+ def connect(message, local = false, &callback)
148
+ response = make_response(message)
149
+ client_id = message['clientId']
197
150
  connection_type = message['connectionType']
198
151
 
199
- response['error'] = Error.client_unknown(client_id) if connection.nil?
200
- response['error'] = Error.parameter_missing('clientId') if client_id.nil?
201
- response['error'] = Error.parameter_missing('connectionType') if connection_type.nil?
202
-
203
- response['successful'] = response['error'].nil?
204
- response.delete('clientId') unless response['successful']
205
- return response unless response['successful']
206
-
207
- response['clientId'] = connection.id
208
- response
152
+ @engine.client_exists(client_id) do |exists|
153
+ response['error'] = Error.client_unknown(client_id) unless exists
154
+ response['error'] = Error.parameter_missing('clientId') if client_id.nil?
155
+
156
+ unless CONNECTION_TYPES.include?(connection_type)
157
+ response['error'] = Error.conntype_mismatch(connection_type)
158
+ end
159
+
160
+ response['error'] = Error.parameter_missing('connectionType') if connection_type.nil?
161
+
162
+ response['successful'] = response['error'].nil?
163
+
164
+ if response['successful']
165
+ @engine.connect(response['clientId'], message['advice']) do |events|
166
+ callback.call([response] + events)
167
+ end
168
+ else
169
+ response.delete('clientId')
170
+ callback.call(response)
171
+ end
172
+ end
209
173
  end
210
174
 
211
175
  # MUST contain * clientId
212
176
  # MAY contain * ext
213
177
  # * id
214
- def disconnect(message, local = false)
215
- response = make_response(message)
216
-
178
+ def disconnect(message, local = false, &callback)
179
+ response = make_response(message)
217
180
  client_id = message['clientId']
218
- connection = client_id ? @connections[client_id] : nil
219
-
220
- response['error'] = Error.client_unknown(client_id) if connection.nil?
221
- response['error'] = Error.parameter_missing('clientId') if client_id.nil?
222
-
223
- response['successful'] = response['error'].nil?
224
- response.delete('clientId') unless response['successful']
225
- return response unless response['successful']
226
181
 
227
- destroy_connection(connection)
228
-
229
- info('Disconnected client: ?', client_id)
230
- response['clientId'] = client_id
231
- response
182
+ @engine.client_exists(client_id) do |exists|
183
+ response['error'] = Error.client_unknown(client_id) unless exists
184
+ response['error'] = Error.parameter_missing('clientId') if client_id.nil?
185
+
186
+ response['successful'] = response['error'].nil?
187
+ response.delete('clientId') unless response['successful']
188
+
189
+ @engine.destroy_client(client_id) if response['successful']
190
+ callback.call(response)
191
+ end
232
192
  end
233
193
 
234
194
  # MUST contain * clientId
235
195
  # * subscription
236
196
  # MAY contain * ext
237
197
  # * id
238
- def subscribe(message, local = false)
239
- response = make_response(message)
240
-
241
- client_id = message['clientId']
242
- connection = client_id ? @connections[client_id] : nil
243
- subscription = [message['subscription']].flatten
244
-
245
- response['error'] = Error.client_unknown(client_id) if connection.nil?
246
- response['error'] = Error.parameter_missing('clientId') if client_id.nil?
247
- response['error'] = Error.parameter_missing('subscription') if message['subscription'].nil?
248
-
249
- response['subscription'] = subscription.compact
250
-
251
- subscription.each do |channel_name|
252
- next if response['error']
253
- response['error'] = Error.channel_forbidden(channel_name) unless local or Channel.subscribable?(channel_name)
254
- response['error'] = Error.channel_invalid(channel_name) unless Channel.valid?(channel_name)
198
+ def subscribe(message, local = false, &callback)
199
+ response = make_response(message)
200
+ client_id = message['clientId']
201
+ subscription = [message['subscription']].flatten
202
+
203
+ @engine.client_exists(client_id) do |exists|
204
+ response['error'] = Error.client_unknown(client_id) unless exists
205
+ response['error'] = Error.parameter_missing('clientId') if client_id.nil?
206
+ response['error'] = Error.parameter_missing('subscription') if message['subscription'].nil?
255
207
 
256
- next if response['error']
257
- channel = @channels[channel_name] ||= Channel.new(channel_name)
208
+ response['subscription'] = message['subscription'] || []
258
209
 
259
- info('Subscribing client ? to ?', client_id, channel.name)
260
- connection.subscribe(channel)
210
+ subscription.each do |channel|
211
+ next if response['error']
212
+ response['error'] = Error.channel_forbidden(channel) unless local or Channel.subscribable?(channel)
213
+ response['error'] = Error.channel_invalid(channel) unless Channel.valid?(channel)
214
+
215
+ next if response['error']
216
+ @engine.subscribe(client_id, channel)
217
+ end
218
+
219
+ response['successful'] = response['error'].nil?
220
+ callback.call(response)
261
221
  end
262
-
263
- response['successful'] = response['error'].nil?
264
- response
265
222
  end
266
223
 
267
224
  # MUST contain * clientId
268
225
  # * subscription
269
226
  # MAY contain * ext
270
227
  # * id
271
- def unsubscribe(message, local = false)
272
- response = make_response(message)
273
-
274
- client_id = message['clientId']
275
- connection = client_id ? @connections[client_id] : nil
276
- subscription = [message['subscription']].flatten
277
-
278
- response['error'] = Error.client_unknown(client_id) if connection.nil?
279
- response['error'] = Error.parameter_missing('clientId') if client_id.nil?
280
- response['error'] = Error.parameter_missing('subscription') if message['subscription'].nil?
281
-
282
- response['subscription'] = subscription.compact
283
-
284
- subscription.each do |channel_name|
285
- next if response['error']
228
+ def unsubscribe(message, local = false, &callback)
229
+ response = make_response(message)
230
+ client_id = message['clientId']
231
+ subscription = [message['subscription']].flatten
232
+
233
+ @engine.client_exists(client_id) do |exists|
234
+ response['error'] = Error.client_unknown(client_id) unless exists
235
+ response['error'] = Error.parameter_missing('clientId') if client_id.nil?
236
+ response['error'] = Error.parameter_missing('subscription') if message['subscription'].nil?
286
237
 
287
- unless Channel.valid?(channel_name)
288
- response['error'] = Error.channel_invalid(channel_name)
289
- next
290
- end
238
+ response['subscription'] = message['subscription'] || []
291
239
 
292
- channel = @channels[channel_name]
293
- next unless channel
240
+ subscription.each do |channel|
241
+ next if response['error']
242
+ response['error'] = Error.channel_forbidden(channel) unless local or Channel.subscribable?(channel)
243
+ response['error'] = Error.channel_invalid(channel) unless Channel.valid?(channel)
244
+
245
+ next if response['error']
246
+ @engine.unsubscribe(client_id, channel)
247
+ end
294
248
 
295
- info('Unsubscribing client ? from ?', client_id, channel.name)
296
- connection.unsubscribe(channel)
297
- @channels.remove(channel_name) if channel.unused?
249
+ response['successful'] = response['error'].nil?
250
+ callback.call(response)
298
251
  end
299
-
300
- response['successful'] = response['error'].nil?
301
- response
302
252
  end
303
253
 
304
254
  end
@@ -0,0 +1,45 @@
1
+ require 'em-http'
2
+ require 'em-http/version'
3
+
4
+ module Faye
5
+
6
+ class Transport::Http < Transport
7
+ def self.usable?(endpoint)
8
+ endpoint.is_a?(String)
9
+ end
10
+
11
+ def request(message, timeout)
12
+ retry_block = retry_block(message, timeout)
13
+
14
+ content = JSON.unparse(message)
15
+ params = {
16
+ :head => {
17
+ 'Content-Type' => 'application/json',
18
+ 'host' => URI.parse(@endpoint).host,
19
+ 'Content-Length' => content.length
20
+ },
21
+ :body => content,
22
+ :timeout => -1 # for em-http-request < 1.0
23
+ }
24
+ if EventMachine::HttpRequest::VERSION.split('.')[0].to_i >= 1
25
+ options = { # for em-http-request >= 1.0
26
+ :inactivity_timeout => 0, # connection inactivity (post-setup) timeout (0 = disable timeout)
27
+ }
28
+ request = EventMachine::HttpRequest.new(@endpoint, options).post(params)
29
+ else
30
+ request = EventMachine::HttpRequest.new(@endpoint).post(params)
31
+ end
32
+ request.callback do
33
+ begin
34
+ receive(JSON.parse(request.response))
35
+ rescue
36
+ retry_block.call
37
+ end
38
+ end
39
+ request.errback { retry_block.call }
40
+ end
41
+ end
42
+
43
+ Transport.register 'long-polling', Transport::Http
44
+
45
+ end