_bushido-faye 0.8.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (69) hide show
  1. data/History.txt +247 -0
  2. data/README.rdoc +92 -0
  3. data/lib/faye-browser-min.js +1 -0
  4. data/lib/faye.rb +121 -0
  5. data/lib/faye/adapters/rack_adapter.rb +210 -0
  6. data/lib/faye/engines/connection.rb +60 -0
  7. data/lib/faye/engines/memory.rb +112 -0
  8. data/lib/faye/engines/proxy.rb +111 -0
  9. data/lib/faye/error.rb +49 -0
  10. data/lib/faye/mixins/logging.rb +47 -0
  11. data/lib/faye/mixins/publisher.rb +30 -0
  12. data/lib/faye/mixins/timeouts.rb +22 -0
  13. data/lib/faye/protocol/channel.rb +124 -0
  14. data/lib/faye/protocol/client.rb +378 -0
  15. data/lib/faye/protocol/extensible.rb +43 -0
  16. data/lib/faye/protocol/grammar.rb +58 -0
  17. data/lib/faye/protocol/publication.rb +5 -0
  18. data/lib/faye/protocol/server.rb +282 -0
  19. data/lib/faye/protocol/subscription.rb +24 -0
  20. data/lib/faye/transport/http.rb +76 -0
  21. data/lib/faye/transport/local.rb +22 -0
  22. data/lib/faye/transport/transport.rb +115 -0
  23. data/lib/faye/transport/web_socket.rb +99 -0
  24. data/lib/faye/util/namespace.rb +20 -0
  25. data/spec/browser.html +45 -0
  26. data/spec/encoding_helper.rb +7 -0
  27. data/spec/install.sh +78 -0
  28. data/spec/javascript/channel_spec.js +15 -0
  29. data/spec/javascript/client_spec.js +714 -0
  30. data/spec/javascript/engine/memory_spec.js +7 -0
  31. data/spec/javascript/engine_spec.js +417 -0
  32. data/spec/javascript/faye_spec.js +15 -0
  33. data/spec/javascript/grammar_spec.js +66 -0
  34. data/spec/javascript/node_adapter_spec.js +307 -0
  35. data/spec/javascript/publisher_spec.js +27 -0
  36. data/spec/javascript/server/connect_spec.js +168 -0
  37. data/spec/javascript/server/disconnect_spec.js +121 -0
  38. data/spec/javascript/server/extensions_spec.js +60 -0
  39. data/spec/javascript/server/handshake_spec.js +145 -0
  40. data/spec/javascript/server/integration_spec.js +124 -0
  41. data/spec/javascript/server/publish_spec.js +85 -0
  42. data/spec/javascript/server/subscribe_spec.js +247 -0
  43. data/spec/javascript/server/unsubscribe_spec.js +245 -0
  44. data/spec/javascript/server_spec.js +110 -0
  45. data/spec/javascript/transport_spec.js +130 -0
  46. data/spec/node.js +55 -0
  47. data/spec/phantom.js +17 -0
  48. data/spec/ruby/channel_spec.rb +17 -0
  49. data/spec/ruby/client_spec.rb +724 -0
  50. data/spec/ruby/engine/memory_spec.rb +7 -0
  51. data/spec/ruby/engine_examples.rb +427 -0
  52. data/spec/ruby/faye_spec.rb +14 -0
  53. data/spec/ruby/grammar_spec.rb +68 -0
  54. data/spec/ruby/publisher_spec.rb +27 -0
  55. data/spec/ruby/rack_adapter_spec.rb +236 -0
  56. data/spec/ruby/server/connect_spec.rb +170 -0
  57. data/spec/ruby/server/disconnect_spec.rb +120 -0
  58. data/spec/ruby/server/extensions_spec.rb +68 -0
  59. data/spec/ruby/server/handshake_spec.rb +143 -0
  60. data/spec/ruby/server/integration_spec.rb +126 -0
  61. data/spec/ruby/server/publish_spec.rb +81 -0
  62. data/spec/ruby/server/subscribe_spec.rb +247 -0
  63. data/spec/ruby/server/unsubscribe_spec.rb +247 -0
  64. data/spec/ruby/server_spec.rb +110 -0
  65. data/spec/ruby/transport_spec.rb +134 -0
  66. data/spec/spec_helper.rb +11 -0
  67. data/spec/testswarm +29 -0
  68. data/spec/thin_proxy.rb +37 -0
  69. metadata +302 -0
@@ -0,0 +1,7 @@
1
+ JS.ENV.Engine.MemorySpec = JS.Test.describe("Memory engine", function() { with(this) {
2
+ before(function() {
3
+ this.engineOpts = {type: Faye.Engine.Memory}
4
+ })
5
+
6
+ itShouldBehaveLike("faye engine")
7
+ }})
@@ -0,0 +1,417 @@
1
+ JS.ENV.EngineSteps = JS.Test.asyncSteps({
2
+ create_client: function(name, resume) {
3
+ var inboxes = this._inboxes = this._inboxes || {}
4
+ var clients = this._clients = this._clients || {}
5
+ this.engine.createClient(function(clientId) {
6
+ clients[name] = clientId
7
+ inboxes[name] = inboxes[name] || []
8
+ resume()
9
+ })
10
+ },
11
+
12
+ connect: function(name, engine, resume) {
13
+ var clientId = this._clients[name]
14
+ var inboxes = this._inboxes
15
+ engine.connect(clientId, {}, function(messages) {
16
+ for (var i = 0, n = messages.length; i < n; i++) {
17
+ delete messages[i].id
18
+ inboxes[name].push(messages[i])
19
+ }
20
+ })
21
+ setTimeout(resume, 10)
22
+ },
23
+
24
+ destroy_client: function(name, resume) {
25
+ this.engine.destroyClient(this._clients[name], resume)
26
+ },
27
+
28
+ check_client_id: function(name, pattern, resume) {
29
+ this.assertMatch(pattern, this._clients[name])
30
+ resume()
31
+ },
32
+
33
+ check_num_clients: function(n, resume) {
34
+ var ids = new JS.Set()
35
+ for (var key in this._clients) ids.add(this._clients[key])
36
+ this.assertEqual(n, ids.count())
37
+ resume()
38
+ },
39
+
40
+ check_client_exists: function(name, exists, resume) {
41
+ var tc = this
42
+ tc.engine.clientExists(tc._clients[name], function(actual) {
43
+ tc.assertEqual(exists, actual)
44
+ resume()
45
+ })
46
+ },
47
+
48
+ subscribe: function(name, channel, resume) {
49
+ this.engine.subscribe(this._clients[name], channel, resume)
50
+ },
51
+
52
+ unsubscribe: function(name, channel, resume) {
53
+ this.engine.unsubscribe(this._clients[name], channel, resume)
54
+ },
55
+
56
+ publish: function(messages, resume) {
57
+ messages = [].concat(messages)
58
+ for (var i = 0, n = messages.length; i < n; i++) {
59
+ var message = Faye.extend({id: Faye.random()}, messages[i])
60
+ this.engine.publish(message)
61
+ }
62
+ setTimeout(resume, 20)
63
+ },
64
+
65
+ publish_by: function(name, message, resume) {
66
+ message = Faye.extend({clientId: this._clients[name], id: Faye.random()}, message)
67
+ this.engine.publish(message)
68
+ setTimeout(resume, 10)
69
+ },
70
+
71
+ ping: function(name, resume) {
72
+ this.engine.ping(this._clients[name])
73
+ resume()
74
+ },
75
+
76
+ clock_tick: function(time, resume) {
77
+ setTimeout(resume, time)
78
+ },
79
+
80
+ expect_event: function(name, event, args, resume) {
81
+ var params = [this._clients[name]].concat(args),
82
+ handler = function() {}
83
+
84
+ this.engine.bind(event, handler)
85
+ this.expect(handler, "apply").given(undefined, params)
86
+ resume()
87
+ },
88
+
89
+ expect_no_event: function(name, event, args, resume) {
90
+ var params = [this._clients[name]].concat(args),
91
+ handler = function() {}
92
+
93
+ this.engine.bind(event, handler)
94
+ this.expect(handler, "apply").exactly(0)
95
+ resume()
96
+ },
97
+
98
+ expect_message: function(name, messages, resume) {
99
+ this.assertEqual(messages, this._inboxes[name])
100
+ resume()
101
+ },
102
+
103
+ expect_no_message: function(name, resume) {
104
+ this.assertEqual([], this._inboxes[name])
105
+ resume()
106
+ },
107
+
108
+ check_different_messages: function(a, b, resume) {
109
+ this.assertNotSame(this._inboxes[a][0], this._inboxes[b][0])
110
+ resume()
111
+ }
112
+ })
113
+
114
+ JS.ENV.EngineSpec = JS.Test.describe("Pub/sub engines", function() { with(this) {
115
+ sharedExamplesFor("faye engine", function() { with(this) {
116
+ include(JS.Test.Helpers)
117
+ include(EngineSteps)
118
+
119
+ define("create_engine", function() { with(this) {
120
+ var opts = Faye.extend(options(), engineOpts)
121
+ return new Faye.Engine.Proxy(opts)
122
+ }})
123
+
124
+ define("options", function() { return {timeout: 1} })
125
+
126
+ before(function() { with(this) {
127
+ this.engine = create_engine()
128
+ create_client("alice")
129
+ create_client("bob")
130
+ create_client("carol")
131
+ }})
132
+
133
+ describe("createClient", function() { with(this) {
134
+ it("returns a client id", function() { with(this) {
135
+ create_client("dave")
136
+ check_client_id("dave", /^[a-z0-9]+$/)
137
+ }})
138
+
139
+ it("returns a different id every time", function() { with(this) {
140
+ $R(1,7).forEach(function(i) { create_client("client" + i) })
141
+ check_num_clients(10)
142
+ }})
143
+
144
+ it("publishes an event", function() { with(this) {
145
+ expect(engine, "trigger").given("handshake", match(/^[a-z0-9]+$/))
146
+ create_client("dave")
147
+ }})
148
+ }})
149
+
150
+ describe("clientExists", function() { with(this) {
151
+ it("returns true if the client id exists", function() { with(this) {
152
+ check_client_exists("alice", true)
153
+ }})
154
+
155
+ it("returns false if the client id does not exist", function() { with(this) {
156
+ check_client_exists("anything", false)
157
+ }})
158
+ }})
159
+
160
+ describe("ping", function() { with(this) {
161
+ define("options", function() { return {timeout: 0.3, gc: 0.08} })
162
+
163
+ it("removes a client if it does not ping often enough", function() { with(this) {
164
+ clock_tick(700)
165
+ check_client_exists("alice", false)
166
+ }})
167
+
168
+ it("prolongs the life of a client", function() { with(this) {
169
+ clock_tick(450)
170
+ ping("alice")
171
+ clock_tick(450)
172
+ check_client_exists("alice", true)
173
+ clock_tick(450)
174
+ check_client_exists("alice", false)
175
+ }})
176
+ }})
177
+
178
+ describe("destroyClient", function() { with(this) {
179
+ it("removes the given client", function() { with(this) {
180
+ destroy_client("alice")
181
+ check_client_exists("alice", false)
182
+ }})
183
+
184
+ it("publishes an event", function() { with(this) {
185
+ expect_event("alice", "disconnect", [])
186
+ destroy_client("alice")
187
+ }})
188
+
189
+ describe("when the client has subscriptions", function() { with(this) {
190
+ before(function() { with(this) {
191
+ this.message = {"channel": "/messages/foo", "data": "ok"}
192
+ subscribe("alice", "/messages/foo")
193
+ }})
194
+
195
+ it("stops the client receiving messages", function() { with(this) {
196
+ connect("alice", engine)
197
+ destroy_client("alice")
198
+ publish(message)
199
+ expect_no_message("alice")
200
+ }})
201
+
202
+ it("publishes an event", function() { with(this) {
203
+ expect_event("alice", "disconnect", [])
204
+ destroy_client("alice")
205
+ }})
206
+ }})
207
+ }})
208
+
209
+ describe("subscribe", function() { with(this) {
210
+ it("publishes an event", function() { with(this) {
211
+ expect_event("alice", "subscribe", ["/messages/foo"])
212
+ subscribe("alice", "/messages/foo")
213
+ }})
214
+
215
+ describe("when the client is subscribed to the channel", function() { with(this) {
216
+ before(function() { this.subscribe("alice", "/messages/foo") })
217
+
218
+ it("does not publish an event", function() { with(this) {
219
+ expect_no_event("alice", "subscribe", ["/messages/foo"])
220
+ subscribe("alice", "/messages/foo")
221
+ }})
222
+ }})
223
+ }})
224
+
225
+
226
+ describe("unsubscribe", function() { with(this) {
227
+ before(function() { this.subscribe("alice", "/messages/bar") })
228
+
229
+ it("does not publish an event", function() { with(this) {
230
+ expect_no_event("alice", "unsubscribe", ["/messages/foo"])
231
+ unsubscribe("alice", "/messages/foo")
232
+ }})
233
+
234
+ describe("when the client is subscribed to the channel", function() { with(this) {
235
+ before(function() { this.subscribe("alice", "/messages/foo") })
236
+
237
+ it("publishes an event", function() { with(this) {
238
+ expect_event("alice", "unsubscribe", ["/messages/foo"])
239
+ unsubscribe("alice", "/messages/foo")
240
+ }})
241
+ }})
242
+ }})
243
+
244
+ describe("publish", function() { with(this) {
245
+ before(function() { with(this) {
246
+ this.message = {"channel": "/messages/foo", "data": "ok"}
247
+ connect("alice", engine)
248
+ connect("bob", engine)
249
+ connect("carol", engine)
250
+ }})
251
+
252
+ describe("with no subscriptions", function() { with(this) {
253
+ it("delivers no messages", function() { with(this) {
254
+ publish(message)
255
+ expect_no_message("alice")
256
+ expect_no_message("bob")
257
+ expect_no_message("carol")
258
+ }})
259
+
260
+ it("publishes a :publish event with a clientId", function() { with(this) {
261
+ expect_event("bob", "publish", ["/messages/foo", "ok"])
262
+ publish_by("bob", message)
263
+ }})
264
+
265
+ it("publishes a :publish event with no clientId", function() { with(this) {
266
+ expect_event(null, "publish", ["/messages/foo", "ok"])
267
+ publish(message)
268
+ }})
269
+ }})
270
+
271
+ describe("with a subscriber", function() { with(this) {
272
+ before(function() { with(this) {
273
+ subscribe("alice", "/messages/foo")
274
+ }})
275
+
276
+ it("delivers multibyte messages correctly", function() { with(this) {
277
+ message.data = "Apple = "
278
+ publish(message)
279
+ expect_message("alice", [message])
280
+ }})
281
+
282
+ it("delivers messages to the subscribed client", function() { with(this) {
283
+ publish(message)
284
+ expect_message("alice", [message])
285
+ }})
286
+
287
+ it("publishes a :publish event with a clientId", function() { with(this) {
288
+ expect_event("bob", "publish", ["/messages/foo", "ok"])
289
+ publish_by("bob", message)
290
+ }})
291
+ }})
292
+
293
+ describe("with a subscriber that is removed", function() { with(this) {
294
+ before(function() { with(this) {
295
+ subscribe("alice", "/messages/foo")
296
+ unsubscribe("alice", "/messages/foo")
297
+ }})
298
+
299
+ it("does not deliver messages to unsubscribed clients", function() { with(this) {
300
+ publish(message)
301
+ expect_no_message("alice")
302
+ expect_no_message("bob")
303
+ expect_no_message("carol")
304
+ }})
305
+
306
+ it("publishes a :publish event with a clientId", function() { with(this) {
307
+ expect_event("bob", "publish", ["/messages/foo", "ok"])
308
+ publish_by("bob", message)
309
+ }})
310
+ }})
311
+
312
+ describe("with multiple subscribers", function() { with(this) {
313
+ before(function() { with(this) {
314
+ subscribe("alice", "/messages/foo")
315
+ subscribe("bob", "/messages/bar")
316
+ subscribe("carol", "/messages/foo")
317
+ }})
318
+
319
+ it("delivers messages to the subscribed clients", function() { with(this) {
320
+ publish(message)
321
+ expect_message("alice", [message])
322
+ expect_no_message("bob")
323
+ expect_message("carol", [message])
324
+ }})
325
+ }})
326
+
327
+ describe("with a single wildcard", function() { with(this) {
328
+ before(function() { with(this) {
329
+ subscribe("alice", "/messages/*")
330
+ subscribe("bob", "/messages/bar")
331
+ subscribe("carol", "/*")
332
+ }})
333
+
334
+ it("delivers messages to matching subscriptions", function() { with(this) {
335
+ publish(message)
336
+ expect_message("alice", [message])
337
+ expect_no_message("bob")
338
+ expect_no_message("carol")
339
+ }})
340
+ }})
341
+
342
+ describe("with a double wildcard", function() { with(this) {
343
+ before(function() { with(this) {
344
+ subscribe("alice", "/messages/**")
345
+ subscribe("bob", "/messages/bar")
346
+ subscribe("carol", "/**")
347
+ }})
348
+
349
+ it("delivers messages to matching subscriptions", function() { with(this) {
350
+ publish(message)
351
+ expect_message("alice", [message])
352
+ expect_no_message("bob")
353
+ expect_message("carol", [message])
354
+ }})
355
+
356
+ it("delivers a unique copy of the message to each client", function() { with(this) {
357
+ publish(message)
358
+ check_different_messages("alice", "carol")
359
+ }})
360
+ }})
361
+
362
+ describe("with multiple matching subscriptions for the same client", function() { with(this) {
363
+ before(function() { with(this) {
364
+ subscribe("alice", "/messages/*")
365
+ subscribe("alice", "/messages/foo")
366
+ }})
367
+
368
+ it("delivers each message once to each client", function() { with(this) {
369
+ publish(message)
370
+ expect_message("alice", [message])
371
+ }})
372
+
373
+ it("delivers the message as many times as it is published", function() { with(this) {
374
+ publish([message, message])
375
+ expect_message("alice", [message, message])
376
+ }})
377
+ }})
378
+ }})
379
+ }})
380
+
381
+ sharedBehavior("distributed engine", function() { with(this) {
382
+ include(JS.Test.Helpers)
383
+ include(EngineSteps)
384
+
385
+ define("create_engine", function() { with(this) {
386
+ var opts = Faye.extend(options(), engineOpts)
387
+ return new Faye.Engine.Proxy(opts)
388
+ }})
389
+
390
+ define("options", function() { return {timeout: 1} })
391
+
392
+ before(function() { with(this) {
393
+ this.left = create_engine()
394
+ this.right = create_engine()
395
+ this.engine = left
396
+
397
+ create_client("alice")
398
+ create_client("bob")
399
+
400
+ connect("alice", left)
401
+ }})
402
+
403
+ describe("publish", function() { with(this) {
404
+ before(function() { with(this) {
405
+ subscribe("alice", "/foo")
406
+ publish({channel: "/foo", data: "first"})
407
+ }})
408
+
409
+ it("only delivers each message once", function() { with(this) {
410
+ expect_message("alice", [{channel: "/foo", data: "first"}])
411
+ publish({channel: "/foo", data: "second"})
412
+ connect("alice", right)
413
+ expect_message("alice", [{channel: "/foo", data: "first"}, {channel: "/foo", data: "second"}])
414
+ }})
415
+ }})
416
+ }})
417
+ }})
@@ -0,0 +1,15 @@
1
+ JS.ENV.FayeSpec = JS.Test.describe("Faye", function() { with(this) {
2
+ include(JS.Test.Helpers)
3
+
4
+ describe("random", function() { with(this) {
5
+ it("returns a 128-bit random number in base 36", function() { with(this) {
6
+ assertMatch( /^[a-z0-9]+$/, Faye.random() )
7
+ }})
8
+
9
+ it("always produces the same length of string", function() { with(this) {
10
+ var ids = $R(1,100).map(function() { return Faye.random().length })
11
+ var expected = $R(1,100).map(function() { return 28 })
12
+ assertEqual( expected, ids )
13
+ }})
14
+ }})
15
+ }})
@@ -0,0 +1,66 @@
1
+ JS.ENV.GrammarSpec = JS.Test.describe("Grammar", function() { with(this) {
2
+ describe("CHANNEL_NAME", function() { with(this) {
3
+ it("matches valid channel names", function() { with(this) {
4
+ assertMatch( Faye.Grammar.CHANNEL_NAME, "/fo_o/$@()bar" )
5
+ }})
6
+
7
+ it("does not match channel patterns", function() { with(this) {
8
+ assertNoMatch( Faye.Grammar.CHANNEL_NAME, "/foo/**" )
9
+ }})
10
+
11
+ it("does not match invalid channel names", function() { with(this) {
12
+ assertNoMatch( Faye.Grammar.CHANNEL_NAME, "foo/$@()bar" )
13
+ assertNoMatch( Faye.Grammar.CHANNEL_NAME, "/foo/$@()bar/" )
14
+ assertNoMatch( Faye.Grammar.CHANNEL_NAME, "/fo o/$@()bar" )
15
+ }})
16
+ }})
17
+
18
+ describe("CHANNEL_PATTERN", function() { with(this) {
19
+ it("does not match channel names", function() { with(this) {
20
+ assertNoMatch( Faye.Grammar.CHANNEL_PATTERN, "/fo_o/$@()bar" )
21
+ }})
22
+
23
+ it("matches valid channel patterns", function() { with(this) {
24
+ assertMatch( Faye.Grammar.CHANNEL_PATTERN, "/foo/**" )
25
+ assertMatch( Faye.Grammar.CHANNEL_PATTERN, "/foo/*" )
26
+ }})
27
+
28
+ it("does not match invalid channel patterns", function() { with(this) {
29
+ assertNoMatch( Faye.Grammar.CHANNEL_PATTERN, "/foo/**/*" )
30
+ }})
31
+ }})
32
+
33
+ describe("ERROR", function() { with(this) {
34
+ it("matches an error with an argument", function() { with(this) {
35
+ assertMatch( Faye.Grammar.ERROR, "402:xj3sjdsjdsjad:Unknown Client ID" )
36
+ }})
37
+
38
+ it("matches an error with many arguments", function() { with(this) {
39
+ assertMatch( Faye.Grammar.ERROR, "403:xj3sjdsjdsjad,/foo/bar:Subscription denied" )
40
+ }})
41
+
42
+ it("matches an error with no arguments", function() { with(this) {
43
+ assertMatch( Faye.Grammar.ERROR, "402::Unknown Client ID" )
44
+ }})
45
+
46
+ it("does not match an error with no code", function() { with(this) {
47
+ assertNoMatch( Faye.Grammar.ERROR, ":xj3sjdsjdsjad:Unknown Client ID" )
48
+ }})
49
+
50
+ it("does not match an error with an invalid code", function() { with(this) {
51
+ assertNoMatch( Faye.Grammar.ERROR, "40:xj3sjdsjdsjad:Unknown Client ID" )
52
+ }})
53
+ }})
54
+
55
+ describe("VERSION", function() { with(this) {
56
+ it("matches a version number", function() { with(this) {
57
+ assertMatch( Faye.Grammar.VERSION, "9" )
58
+ assertMatch( Faye.Grammar.VERSION, "9.0.a-delta1" )
59
+ }})
60
+
61
+ it("does not match invalid version numbers", function() { with(this) {
62
+ assertNoMatch( Faye.Grammar.VERSION, "9.0.a-delta1." )
63
+ assertNoMatch( Faye.Grammar.VERSION, "" )
64
+ }})
65
+ }})
66
+ }})