face-faye 0.8.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.
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
+