face-faye 0.8.9

Sign up to get free protection for your applications and to get access to all the features.
Files changed (73) hide show
  1. data/History.txt +304 -0
  2. data/README.rdoc +83 -0
  3. data/lib/faye-browser-min.js +2 -0
  4. data/lib/faye-browser-min.js.map +8 -0
  5. data/lib/faye-browser.js +2194 -0
  6. data/lib/faye.rb +122 -0
  7. data/lib/faye/adapters/rack_adapter.rb +216 -0
  8. data/lib/faye/adapters/static_server.rb +56 -0
  9. data/lib/faye/engines/connection.rb +60 -0
  10. data/lib/faye/engines/memory.rb +112 -0
  11. data/lib/faye/engines/proxy.rb +121 -0
  12. data/lib/faye/error.rb +49 -0
  13. data/lib/faye/mixins/logging.rb +47 -0
  14. data/lib/faye/mixins/publisher.rb +30 -0
  15. data/lib/faye/mixins/timeouts.rb +22 -0
  16. data/lib/faye/protocol/channel.rb +124 -0
  17. data/lib/faye/protocol/client.rb +376 -0
  18. data/lib/faye/protocol/extensible.rb +43 -0
  19. data/lib/faye/protocol/grammar.rb +58 -0
  20. data/lib/faye/protocol/publication.rb +5 -0
  21. data/lib/faye/protocol/server.rb +293 -0
  22. data/lib/faye/protocol/socket.rb +23 -0
  23. data/lib/faye/protocol/subscription.rb +24 -0
  24. data/lib/faye/transport/http.rb +76 -0
  25. data/lib/faye/transport/local.rb +22 -0
  26. data/lib/faye/transport/transport.rb +116 -0
  27. data/lib/faye/transport/web_socket.rb +92 -0
  28. data/lib/faye/util/namespace.rb +20 -0
  29. data/spec/browser.html +45 -0
  30. data/spec/encoding_helper.rb +7 -0
  31. data/spec/install.sh +78 -0
  32. data/spec/javascript/channel_spec.js +15 -0
  33. data/spec/javascript/client_spec.js +729 -0
  34. data/spec/javascript/engine/memory_spec.js +7 -0
  35. data/spec/javascript/engine_spec.js +417 -0
  36. data/spec/javascript/faye_spec.js +34 -0
  37. data/spec/javascript/grammar_spec.js +66 -0
  38. data/spec/javascript/node_adapter_spec.js +307 -0
  39. data/spec/javascript/publisher_spec.js +27 -0
  40. data/spec/javascript/server/connect_spec.js +168 -0
  41. data/spec/javascript/server/disconnect_spec.js +121 -0
  42. data/spec/javascript/server/extensions_spec.js +60 -0
  43. data/spec/javascript/server/handshake_spec.js +145 -0
  44. data/spec/javascript/server/integration_spec.js +131 -0
  45. data/spec/javascript/server/publish_spec.js +85 -0
  46. data/spec/javascript/server/subscribe_spec.js +247 -0
  47. data/spec/javascript/server/unsubscribe_spec.js +245 -0
  48. data/spec/javascript/server_spec.js +121 -0
  49. data/spec/javascript/transport_spec.js +135 -0
  50. data/spec/node.js +55 -0
  51. data/spec/phantom.js +17 -0
  52. data/spec/ruby/channel_spec.rb +17 -0
  53. data/spec/ruby/client_spec.rb +741 -0
  54. data/spec/ruby/engine/memory_spec.rb +7 -0
  55. data/spec/ruby/engine_examples.rb +427 -0
  56. data/spec/ruby/faye_spec.rb +30 -0
  57. data/spec/ruby/grammar_spec.rb +68 -0
  58. data/spec/ruby/publisher_spec.rb +27 -0
  59. data/spec/ruby/rack_adapter_spec.rb +236 -0
  60. data/spec/ruby/server/connect_spec.rb +170 -0
  61. data/spec/ruby/server/disconnect_spec.rb +120 -0
  62. data/spec/ruby/server/extensions_spec.rb +68 -0
  63. data/spec/ruby/server/handshake_spec.rb +143 -0
  64. data/spec/ruby/server/integration_spec.rb +133 -0
  65. data/spec/ruby/server/publish_spec.rb +81 -0
  66. data/spec/ruby/server/subscribe_spec.rb +247 -0
  67. data/spec/ruby/server/unsubscribe_spec.rb +247 -0
  68. data/spec/ruby/server_spec.rb +121 -0
  69. data/spec/ruby/transport_spec.rb +136 -0
  70. data/spec/spec_helper.rb +11 -0
  71. data/spec/testswarm +42 -0
  72. data/spec/thin_proxy.rb +37 -0
  73. metadata +441 -0
@@ -0,0 +1,376 @@
1
+ module Faye
2
+ class Client
3
+
4
+ include EventMachine::Deferrable
5
+ include Publisher
6
+ include Logging
7
+ include Extensible
8
+
9
+ UNCONNECTED = 1
10
+ CONNECTING = 2
11
+ CONNECTED = 3
12
+ DISCONNECTED = 4
13
+
14
+ HANDSHAKE = 'handshake'
15
+ RETRY = 'retry'
16
+ NONE = 'none'
17
+
18
+ CONNECTION_TIMEOUT = 60.0
19
+ DEFAULT_RETRY = 5.0
20
+
21
+ attr_reader :client_id, :endpoint, :endpoints, :retry, :transports
22
+
23
+ def initialize(endpoint = nil, options = {})
24
+ info('New client created for ?', endpoint)
25
+
26
+ @options = options
27
+ @endpoint = endpoint || RackAdapter::DEFAULT_ENDPOINT
28
+ @endpoints = @options[:endpoints] || {}
29
+ @transports = {}
30
+ @cookies = CookieJar::Jar.new
31
+ @headers = {}
32
+ @disabled = []
33
+ @retry = @options[:retry] || DEFAULT_RETRY
34
+
35
+ @state = UNCONNECTED
36
+ @channels = Channel::Set.new
37
+ @message_id = 0
38
+
39
+ @response_callbacks = {}
40
+
41
+ @advice = {
42
+ 'reconnect' => RETRY,
43
+ 'interval' => 1000.0 * (@options[:interval] || Engine::INTERVAL),
44
+ 'timeout' => 1000.0 * (@options[:timeout] || CONNECTION_TIMEOUT)
45
+ }
46
+ end
47
+
48
+ def disable(feature)
49
+ @disabled << feature
50
+ end
51
+
52
+ def set_header(name, value)
53
+ @headers[name.to_s] = value.to_s
54
+ end
55
+
56
+ def state
57
+ case @state
58
+ when UNCONNECTED then :UNCONNECTED
59
+ when CONNECTING then :CONNECTING
60
+ when CONNECTED then :CONNECTED
61
+ when DISCONNECTED then :DISCONNECTED
62
+ end
63
+ end
64
+
65
+ # Request
66
+ # MUST include: * channel
67
+ # * version
68
+ # * supportedConnectionTypes
69
+ # MAY include: * minimumVersion
70
+ # * ext
71
+ # * id
72
+ #
73
+ # Success Response Failed Response
74
+ # MUST include: * channel MUST include: * channel
75
+ # * version * successful
76
+ # * supportedConnectionTypes * error
77
+ # * clientId MAY include: * supportedConnectionTypes
78
+ # * successful * advice
79
+ # MAY include: * minimumVersion * version
80
+ # * advice * minimumVersion
81
+ # * ext * ext
82
+ # * id * id
83
+ # * authSuccessful
84
+ def handshake(&block)
85
+ return if @advice['reconnect'] == NONE
86
+ return if @state != UNCONNECTED
87
+
88
+ @state = CONNECTING
89
+
90
+ info('Initiating handshake with ?', @endpoint)
91
+ select_transport(MANDATORY_CONNECTION_TYPES)
92
+
93
+ send({
94
+ 'channel' => Channel::HANDSHAKE,
95
+ 'version' => BAYEUX_VERSION,
96
+ 'supportedConnectionTypes' => [@transport.connection_type]
97
+
98
+ }) do |response|
99
+
100
+ if response['successful']
101
+ @state = CONNECTED
102
+ @client_id = response['clientId']
103
+
104
+ select_transport(response['supportedConnectionTypes'])
105
+
106
+ info('Handshake successful: ?', @client_id)
107
+
108
+ subscribe(@channels.keys, true)
109
+ block.call if block_given?
110
+
111
+ else
112
+ info('Handshake unsuccessful')
113
+ EventMachine.add_timer(@advice['interval'] / 1000.0) { handshake(&block) }
114
+ @state = UNCONNECTED
115
+ end
116
+ end
117
+ end
118
+
119
+ # Request Response
120
+ # MUST include: * channel MUST include: * channel
121
+ # * clientId * successful
122
+ # * connectionType * clientId
123
+ # MAY include: * ext MAY include: * error
124
+ # * id * advice
125
+ # * ext
126
+ # * id
127
+ # * timestamp
128
+ def connect(&block)
129
+ return if @advice['reconnect'] == NONE or
130
+ @state == DISCONNECTED
131
+
132
+ return handshake { connect(&block) } if @state == UNCONNECTED
133
+
134
+ callback(&block)
135
+ return unless @state == CONNECTED
136
+
137
+ info('Calling deferred actions for ?', @client_id)
138
+ set_deferred_status(:succeeded)
139
+ set_deferred_status(:deferred)
140
+
141
+ return unless @connect_request.nil?
142
+ @connect_request = true
143
+
144
+ info('Initiating connection for ?', @client_id)
145
+
146
+ send({
147
+ 'channel' => Channel::CONNECT,
148
+ 'clientId' => @client_id,
149
+ 'connectionType' => @transport.connection_type
150
+
151
+ }) do
152
+ cycle_connection
153
+ end
154
+ end
155
+
156
+ # Request Response
157
+ # MUST include: * channel MUST include: * channel
158
+ # * clientId * successful
159
+ # MAY include: * ext * clientId
160
+ # * id MAY include: * error
161
+ # * ext
162
+ # * id
163
+ def disconnect
164
+ return unless @state == CONNECTED
165
+ @state = DISCONNECTED
166
+
167
+ info('Disconnecting ?', @client_id)
168
+
169
+ send({
170
+ 'channel' => Channel::DISCONNECT,
171
+ 'clientId' => @client_id
172
+
173
+ }) do |response|
174
+ @transport.close if response['successful']
175
+ end
176
+
177
+ info('Clearing channel listeners for ?', @client_id)
178
+ @channels = Channel::Set.new
179
+ end
180
+
181
+ # Request Response
182
+ # MUST include: * channel MUST include: * channel
183
+ # * clientId * successful
184
+ # * subscription * clientId
185
+ # MAY include: * ext * subscription
186
+ # * id MAY include: * error
187
+ # * advice
188
+ # * ext
189
+ # * id
190
+ # * timestamp
191
+ def subscribe(channel, force = false, &block)
192
+ if Array === channel
193
+ return channel.map { |c| subscribe(c, force, &block) }
194
+ end
195
+
196
+ subscription = Subscription.new(self, channel, block)
197
+ has_subscribe = @channels.has_subscription?(channel)
198
+
199
+ if has_subscribe and not force
200
+ @channels.subscribe([channel], block)
201
+ subscription.set_deferred_status(:succeeded)
202
+ return subscription
203
+ end
204
+
205
+ connect {
206
+ info('Client ? attempting to subscribe to ?', @client_id, channel)
207
+ @channels.subscribe([channel], block) unless force
208
+
209
+ send({
210
+ 'channel' => Channel::SUBSCRIBE,
211
+ 'clientId' => @client_id,
212
+ 'subscription' => channel
213
+
214
+ }) do |response|
215
+ unless response['successful']
216
+ subscription.set_deferred_status(:failed, Error.parse(response['error']))
217
+ next @channels.unsubscribe(channel, block)
218
+ end
219
+
220
+ channels = [response['subscription']].flatten
221
+ info('Subscription acknowledged for ? to ?', @client_id, channels)
222
+ subscription.set_deferred_status(:succeeded)
223
+ end
224
+ }
225
+ subscription
226
+ end
227
+
228
+ # Request Response
229
+ # MUST include: * channel MUST include: * channel
230
+ # * clientId * successful
231
+ # * subscription * clientId
232
+ # MAY include: * ext * subscription
233
+ # * id MAY include: * error
234
+ # * advice
235
+ # * ext
236
+ # * id
237
+ # * timestamp
238
+ def unsubscribe(channel, &block)
239
+ if Array === channel
240
+ return channel.map { |c| unsubscribe(c, &block) }
241
+ end
242
+
243
+ dead = @channels.unsubscribe(channel, block)
244
+ return unless dead
245
+
246
+ connect {
247
+ info('Client ? attempting to unsubscribe from ?', @client_id, channel)
248
+
249
+ send({
250
+ 'channel' => Channel::UNSUBSCRIBE,
251
+ 'clientId' => @client_id,
252
+ 'subscription' => channel
253
+
254
+ }) do |response|
255
+ next unless response['successful']
256
+
257
+ channels = [response['subscription']].flatten
258
+ info('Unsubscription acknowledged for ? from ?', @client_id, channels)
259
+ end
260
+ }
261
+ end
262
+
263
+ # Request Response
264
+ # MUST include: * channel MUST include: * channel
265
+ # * data * successful
266
+ # MAY include: * clientId MAY include: * id
267
+ # * id * error
268
+ # * ext * ext
269
+ def publish(channel, data)
270
+ publication = Publication.new
271
+ connect {
272
+ info('Client ? queueing published message to ?: ?', @client_id, channel, data)
273
+
274
+ send({
275
+ 'channel' => channel,
276
+ 'data' => data,
277
+ 'clientId' => @client_id
278
+ }) do |response|
279
+ if response['successful']
280
+ publication.set_deferred_status(:succeeded)
281
+ else
282
+ publication.set_deferred_status(:failed, Error.parse(response['error']))
283
+ end
284
+ end
285
+ }
286
+ publication
287
+ end
288
+
289
+ def receive_message(message)
290
+ pipe_through_extensions(:incoming, message) do |message|
291
+ next unless message
292
+
293
+ handle_advice(message['advice']) if message['advice']
294
+ deliver_message(message)
295
+
296
+ next unless message.has_key?('successful')
297
+
298
+ callback = @response_callbacks[message['id']]
299
+ next unless callback
300
+
301
+ @response_callbacks.delete(message['id'])
302
+ callback.call(message)
303
+ end
304
+ end
305
+
306
+ private
307
+
308
+ def select_transport(transport_types)
309
+ Transport.get(self, transport_types, @disabled) do |transport|
310
+ debug('Selected ? transport for ?', transport.connection_type, transport.endpoint)
311
+
312
+ @transport = transport
313
+ @transport.cookies = @cookies
314
+ @transport.headers = @headers
315
+
316
+ transport.bind :down do
317
+ if @transport_up.nil? or @transport_up
318
+ @transport_up = false
319
+ trigger('transport:down')
320
+ end
321
+ end
322
+
323
+ transport.bind :up do
324
+ if @transport_up.nil? or not @transport_up
325
+ @transport_up = true
326
+ trigger('transport:up')
327
+ end
328
+ end
329
+ end
330
+ end
331
+
332
+ def send(message, &callback)
333
+ message['id'] = generate_message_id
334
+ @response_callbacks[message['id']] = callback if callback
335
+
336
+ pipe_through_extensions(:outgoing, message) do |message|
337
+ @transport.send(message, @advice['timeout'] / 1000.0) if message
338
+ end
339
+ end
340
+
341
+ def generate_message_id
342
+ @message_id += 1
343
+ @message_id = 0 if @message_id >= 2**32
344
+ @message_id.to_s(36)
345
+ end
346
+
347
+ def handle_advice(advice)
348
+ @advice.update(advice)
349
+
350
+ if @advice['reconnect'] == HANDSHAKE and @state != DISCONNECTED
351
+ @state = UNCONNECTED
352
+ @client_id = nil
353
+ cycle_connection
354
+ end
355
+ end
356
+
357
+ def deliver_message(message)
358
+ return unless message.has_key?('channel') and message.has_key?('data')
359
+ info('Client ? calling listeners for ? with ?', @client_id, message['channel'], message['data'])
360
+ @channels.distribute_message(message)
361
+ end
362
+
363
+ def teardown_connection
364
+ return unless @connect_request
365
+ @connect_request = nil
366
+ info('Closed connection for ?', @client_id)
367
+ end
368
+
369
+ def cycle_connection
370
+ teardown_connection
371
+ EventMachine.add_timer(@advice['interval'] / 1000.0) { connect }
372
+ end
373
+
374
+ end
375
+ end
376
+
@@ -0,0 +1,43 @@
1
+ module Faye
2
+ module Extensible
3
+ include Logging
4
+
5
+ def add_extension(extension)
6
+ @extensions ||= []
7
+ @extensions << extension
8
+ extension.added(self) if extension.respond_to?(:added)
9
+ end
10
+
11
+ def remove_extension(extension)
12
+ return unless @extensions
13
+ @extensions.delete_if do |ext|
14
+ next false unless ext == extension
15
+ extension.removed(self) if extension.respond_to?(:removed)
16
+ true
17
+ end
18
+ end
19
+
20
+ def pipe_through_extensions(stage, message, &callback)
21
+ debug 'Passing through ? extensions: ?', stage, message
22
+
23
+ return callback.call(message) unless @extensions
24
+ extensions = @extensions.dup
25
+
26
+ pipe = lambda do |message|
27
+ next callback.call(message) unless message
28
+
29
+ extension = extensions.shift
30
+ next callback.call(message) unless extension
31
+
32
+ if extension.respond_to?(stage)
33
+ extension.__send__(stage, message, pipe)
34
+ else
35
+ pipe.call(message)
36
+ end
37
+ end
38
+ pipe.call(message)
39
+ end
40
+
41
+ end
42
+ end
43
+
@@ -0,0 +1,58 @@
1
+ module Faye
2
+ module Grammar
3
+
4
+ def self.rule(&block)
5
+ source = instance_eval(&block)
6
+ %r{^#{string(source)}$}
7
+ end
8
+
9
+ def self.choice(*list)
10
+ '(' + list.map(&method(:string)) * '|' + ')'
11
+ end
12
+
13
+ def self.repeat(*pattern)
14
+ '(' + string(pattern) + ')*'
15
+ end
16
+
17
+ def self.oneormore(*pattern)
18
+ '(' + string(pattern) + ')+'
19
+ end
20
+
21
+ def self.string(item)
22
+ return item.map(&method(:string)) * '' if Array === item
23
+ String === item ? item : item.source.gsub(/^\^/, '').gsub(/\$$/, '')
24
+ end
25
+
26
+ LOWALPHA = rule {[ '[a-z]' ]}
27
+ UPALPHA = rule {[ '[A-Z]' ]}
28
+ ALPHA = rule {[ choice(LOWALPHA, UPALPHA) ]}
29
+ DIGIT = rule {[ '[0-9]' ]}
30
+ ALPHANUM = rule {[ choice(ALPHA, DIGIT) ]}
31
+ MARK = rule {[ choice(*%w[\\- \\_ \\! \\~ \\( \\) \\$ \\@]) ]}
32
+ STRING = rule {[ repeat(choice(ALPHANUM, MARK, ' ', '\\/', '\\*', '\\.')) ]}
33
+ TOKEN = rule {[ oneormore(choice(ALPHANUM, MARK)) ]}
34
+ INTEGER = rule {[ oneormore(DIGIT) ]}
35
+
36
+ CHANNEL_SEGMENT = rule {[ TOKEN ]}
37
+ CHANNEL_SEGMENTS = rule {[ CHANNEL_SEGMENT, repeat('\\/', CHANNEL_SEGMENT) ]}
38
+ CHANNEL_NAME = rule {[ '\\/', CHANNEL_SEGMENTS ]}
39
+
40
+ WILD_CARD = rule {[ '\\*{1,2}' ]}
41
+ CHANNEL_PATTERN = rule {[ repeat('\\/', CHANNEL_SEGMENT), '\\/', WILD_CARD ]}
42
+
43
+ VERSION_ELEMENT = rule {[ ALPHANUM, repeat(choice(ALPHANUM, '\\-', '\\_')) ]}
44
+ VERSION = rule {[ INTEGER, repeat('\\.', VERSION_ELEMENT) ]}
45
+
46
+ CLIENT_ID = rule {[ oneormore(ALPHANUM) ]}
47
+
48
+ ID = rule {[ oneormore(ALPHANUM) ]}
49
+
50
+ ERROR_MESSAGE = rule {[ STRING ]}
51
+ ERROR_ARGS = rule {[ STRING, repeat(',', STRING) ]}
52
+ ERROR_CODE = rule {[ DIGIT, DIGIT, DIGIT ]}
53
+ ERROR = rule {[ choice(string([ERROR_CODE, ':', ERROR_ARGS, ':', ERROR_MESSAGE]),
54
+ string([ERROR_CODE, ':', ':', ERROR_MESSAGE])) ]}
55
+
56
+ end
57
+ end
58
+