faye 0.6.8 → 0.7.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 (43) hide show
  1. data/History.txt +10 -3
  2. data/README.rdoc +1 -2
  3. data/lib/faye-browser-min.js +1 -1
  4. data/lib/faye.rb +89 -32
  5. data/lib/faye/adapters/rack_adapter.rb +20 -26
  6. data/lib/faye/engines/base.rb +5 -0
  7. data/lib/faye/engines/memory.rb +9 -3
  8. data/lib/faye/engines/redis.rb +26 -11
  9. data/lib/faye/mixins/publisher.rb +4 -8
  10. data/lib/faye/protocol/channel.rb +8 -8
  11. data/lib/faye/protocol/client.rb +45 -4
  12. data/lib/faye/protocol/publication.rb +5 -0
  13. data/lib/faye/protocol/server.rb +10 -19
  14. data/lib/faye/thin_extensions.rb +1 -1
  15. data/lib/faye/transport/http.rb +17 -8
  16. data/lib/faye/transport/local.rb +6 -3
  17. data/lib/faye/transport/transport.rb +23 -9
  18. data/lib/faye/transport/web_socket.rb +102 -0
  19. data/lib/faye/util/web_socket.rb +34 -80
  20. data/lib/faye/util/web_socket/api.rb +103 -0
  21. data/lib/faye/util/web_socket/client.rb +82 -0
  22. data/lib/faye/util/web_socket/draft75_parser.rb +3 -5
  23. data/lib/faye/util/web_socket/draft76_parser.rb +5 -7
  24. data/lib/faye/util/web_socket/protocol8_parser.rb +111 -46
  25. data/spec/javascript/client_spec.js +99 -7
  26. data/spec/javascript/engine_spec.js +116 -3
  27. data/spec/javascript/node_adapter_spec.js +2 -4
  28. data/spec/javascript/server/handshake_spec.js +0 -12
  29. data/spec/javascript/server/integration_spec.js +74 -29
  30. data/spec/javascript/server_spec.js +0 -11
  31. data/spec/javascript/web_socket/client_spec.js +121 -0
  32. data/spec/javascript/web_socket/protocol8parser_spec.js +26 -3
  33. data/spec/node.js +2 -0
  34. data/spec/redis.conf +10 -280
  35. data/spec/ruby/client_spec.rb +101 -8
  36. data/spec/ruby/engine_spec.rb +106 -0
  37. data/spec/ruby/server/handshake_spec.rb +0 -12
  38. data/spec/ruby/server/integration_spec.rb +56 -18
  39. data/spec/ruby/server_spec.rb +1 -12
  40. data/spec/ruby/transport_spec.rb +14 -8
  41. data/spec/ruby/web_socket/client_spec.rb +126 -0
  42. data/spec/ruby/web_socket/protocol8_parser_spec.rb +28 -3
  43. metadata +96 -150
@@ -1,6 +1,7 @@
1
1
  JS.ENV.ClientSpec = JS.Test.describe("Client", function() { with(this) {
2
2
  before(function() { with(this) {
3
3
  this.transport = {connectionType: "fake", send: function() {}}
4
+ Faye.extend(transport, Faye.Publisher)
4
5
  stub(Faye.Transport, "get").yields([transport])
5
6
  }})
6
7
 
@@ -103,7 +104,7 @@ JS.ENV.ClientSpec = JS.Test.describe("Client", function() { with(this) {
103
104
  stubResponse({channel: "/meta/handshake",
104
105
  successful: true,
105
106
  version: "1.0",
106
- supportedConnectionTypes: ["websocket"],
107
+ supportedConnectionTypes: ["long-polling", "websocket"],
107
108
  clientId: "fakeid" })
108
109
  }})
109
110
 
@@ -116,16 +117,26 @@ JS.ENV.ClientSpec = JS.Test.describe("Client", function() { with(this) {
116
117
  client.handshake()
117
118
  assertEqual( "CONNECTED", client.getState() )
118
119
  }})
119
-
120
+
121
+ it("registers any pre-existing subscriptions", function() { with(this) {
122
+ expect(client, "subscribe").given([], true)
123
+ client.handshake()
124
+ }})
125
+
120
126
  it("selects a new transport based on what the server supports", function() { with(this) {
121
- expect(Faye.Transport, "get").given(instanceOf(Faye.Client), ["websocket"])
127
+ expect(Faye.Transport, "get").given(instanceOf(Faye.Client), ["long-polling", "websocket"])
122
128
  .yielding([transport])
123
129
  client.handshake()
124
130
  }})
125
-
126
- it("registers any pre-existing subscriptions", function() { with(this) {
127
- expect(client, "subscribe").given([], true)
128
- client.handshake()
131
+
132
+ describe("with websocket disabled", function() { with(this) {
133
+ before(function() { this.client.disable('websocket') })
134
+
135
+ it("selects a new transport, excluding websocket", function() { with(this) {
136
+ expect(Faye.Transport, "get").given(instanceOf(Faye.Client), ["long-polling"])
137
+ .yielding([transport])
138
+ client.handshake()
139
+ }})
129
140
  }})
130
141
  }})
131
142
 
@@ -264,9 +275,23 @@ JS.ENV.ClientSpec = JS.Test.describe("Client", function() { with(this) {
264
275
  }})
265
276
 
266
277
  it("puts the client in the DISCONNECTED state", function() { with(this) {
278
+ stub(transport, "close")
267
279
  client.disconnect()
268
280
  assertEqual( "DISCONNECTED", client.getState() )
269
281
  }})
282
+
283
+ describe("on successful response", function() { with(this) {
284
+ before(function() { with(this) {
285
+ stubResponse({channel: "/meta/disconnect",
286
+ successful: true,
287
+ clientId: "fakeid" })
288
+ }})
289
+
290
+ it("closes the transport", function() { with(this) {
291
+ expect(transport, "close")
292
+ client.disconnect()
293
+ }})
294
+ }})
270
295
  }})
271
296
 
272
297
  describe("subscribe", function() { with(this) {
@@ -563,6 +588,29 @@ JS.ENV.ClientSpec = JS.Test.describe("Client", function() { with(this) {
563
588
  assertThrows(Error, function() { client.publish("/messages/*", {hello: "world"}) })
564
589
  }})
565
590
 
591
+ describe("on publish failure", function() { with(this) {
592
+ before(function() { with(this) {
593
+ stubResponse({channel: "/messages/foo",
594
+ error: "407:/messages/foo:Failed to publish",
595
+ successful: false,
596
+ clientId: "fakeid" })
597
+ }})
598
+
599
+ it("should not be published", function() { with(this) {
600
+ var published = false
601
+ client.publish("/messages/foo", {text: "hi"}).callback(function() { published = true })
602
+ assert( !published )
603
+ }})
604
+
605
+ it("reports the error through an errback", function() { with(this) {
606
+ var error = null
607
+ client.publish("/messages/foo", {text: "hi"}).errback(function(e) { error = e })
608
+ assertEqual( 407, error.code )
609
+ assertEqual( ["/messages/foo"], error.params )
610
+ assertEqual( "Failed to publish", error.message )
611
+ }})
612
+ }})
613
+
566
614
  describe("with an outgoing extension installed", function() { with(this) {
567
615
  before(function() { with(this) {
568
616
  var extension = {
@@ -608,4 +656,48 @@ JS.ENV.ClientSpec = JS.Test.describe("Client", function() { with(this) {
608
656
  }})
609
657
  }})
610
658
  }})
659
+
660
+ describe("network notifications", function() { with(this) {
661
+ before(function() { this.createClient() })
662
+
663
+ describe("in the default state", function() { with(this) {
664
+ it("broadcasts a down notification", function() { with(this) {
665
+ expect(client, "trigger").given("transport:down")
666
+ transport.trigger("down")
667
+ }})
668
+
669
+ it("broadcasts an up notification", function() { with(this) {
670
+ expect(client, "trigger").given("transport:up")
671
+ transport.trigger("up")
672
+ }})
673
+ }})
674
+
675
+ describe("when the transport is up", function() { with(this) {
676
+ before(function() { this.transport.trigger("up") })
677
+
678
+ it("broadcasts a down notification", function() { with(this) {
679
+ expect(client, "trigger").given("transport:down")
680
+ transport.trigger("down")
681
+ }})
682
+
683
+ it("does not broadcast an up notification", function() { with(this) {
684
+ expect(client, "trigger").exactly(0)
685
+ transport.trigger("up")
686
+ }})
687
+ }})
688
+
689
+ describe("when the transport is down", function() { with(this) {
690
+ before(function() { this.transport.trigger("down") })
691
+
692
+ it("does not broadcast a down notification", function() { with(this) {
693
+ expect(client, "trigger").exactly(0)
694
+ transport.trigger("down")
695
+ }})
696
+
697
+ it("broadcasts an up notification", function() { with(this) {
698
+ expect(client, "trigger").given("transport:up")
699
+ transport.trigger("up")
700
+ }})
701
+ }})
702
+ }})
611
703
  }})
@@ -62,6 +62,12 @@ JS.ENV.EngineSteps = JS.Test.asyncSteps({
62
62
  setTimeout(resume, 20)
63
63
  },
64
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
+
65
71
  ping: function(name, resume) {
66
72
  this.engine.ping(this._clients[name])
67
73
  resume()
@@ -71,6 +77,24 @@ JS.ENV.EngineSteps = JS.Test.asyncSteps({
71
77
  setTimeout(resume, time)
72
78
  },
73
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
+
74
98
  expect_message: function(name, messages, resume) {
75
99
  this.assertEqual(messages, this._inboxes[name])
76
100
  resume()
@@ -81,6 +105,11 @@ JS.ENV.EngineSteps = JS.Test.asyncSteps({
81
105
  resume()
82
106
  },
83
107
 
108
+ check_different_messages: function(a, b, resume) {
109
+ this.assertNotSame(this._inboxes[a][0], this._inboxes[b][0])
110
+ resume()
111
+ },
112
+
84
113
  clean_redis_db: function(resume) {
85
114
  this.engine.disconnect()
86
115
  var redis = require('redis').createClient(6379, 'localhost', {no_ready_check: true})
@@ -122,6 +151,11 @@ JS.ENV.EngineSpec = JS.Test.describe("Pub/sub engines", function() { with(this)
122
151
  $R(1,7).forEach(function(i) { create_client("client" + i) })
123
152
  check_num_clients(10)
124
153
  }})
154
+
155
+ it("publishes an event", function() { with(this) {
156
+ expect(engine, "trigger").given("handshake", match(/^[a-z0-9]+$/))
157
+ create_client("dave")
158
+ }})
125
159
  }})
126
160
 
127
161
  describe("clientExists", function() { with(this) {
@@ -143,11 +177,11 @@ JS.ENV.EngineSpec = JS.Test.describe("Pub/sub engines", function() { with(this)
143
177
  }})
144
178
 
145
179
  it("prolongs the life of a client", function() { with(this) {
146
- clock_tick(330)
180
+ clock_tick(450)
147
181
  ping("alice")
148
- clock_tick(330)
182
+ clock_tick(450)
149
183
  check_client_exists("alice", true)
150
- clock_tick(330)
184
+ clock_tick(450)
151
185
  check_client_exists("alice", false)
152
186
  }})
153
187
  }})
@@ -157,6 +191,11 @@ JS.ENV.EngineSpec = JS.Test.describe("Pub/sub engines", function() { with(this)
157
191
  destroy_client("alice")
158
192
  check_client_exists("alice", false)
159
193
  }})
194
+
195
+ it("publishes an event", function() { with(this) {
196
+ expect_event("alice", "disconnect", [])
197
+ destroy_client("alice")
198
+ }})
160
199
 
161
200
  describe("when the client has subscriptions", function() { with(this) {
162
201
  before(function() { with(this) {
@@ -170,6 +209,46 @@ JS.ENV.EngineSpec = JS.Test.describe("Pub/sub engines", function() { with(this)
170
209
  publish(message)
171
210
  expect_no_message("alice")
172
211
  }})
212
+
213
+ it("publishes an event", function() { with(this) {
214
+ expect_event("alice", "disconnect", [])
215
+ destroy_client("alice")
216
+ }})
217
+ }})
218
+ }})
219
+
220
+ describe("subscribe", function() { with(this) {
221
+ it("publishes an event", function() { with(this) {
222
+ expect_event("alice", "subscribe", ["/messages/foo"])
223
+ subscribe("alice", "/messages/foo")
224
+ }})
225
+
226
+ describe("when the client is subscribed to the channel", function() { with(this) {
227
+ before(function() { this.subscribe("alice", "/messages/foo") })
228
+
229
+ it("does not publish an event", function() { with(this) {
230
+ expect_no_event("alice", "subscribe", ["/messages/foo"])
231
+ subscribe("alice", "/messages/foo")
232
+ }})
233
+ }})
234
+ }})
235
+
236
+
237
+ describe("unsubscribe", function() { with(this) {
238
+ before(function() { this.subscribe("alice", "/messages/bar") })
239
+
240
+ it("does not publish an event", function() { with(this) {
241
+ expect_no_event("alice", "unsubscribe", ["/messages/foo"])
242
+ unsubscribe("alice", "/messages/foo")
243
+ }})
244
+
245
+ describe("when the client is subscribed to the channel", function() { with(this) {
246
+ before(function() { this.subscribe("alice", "/messages/foo") })
247
+
248
+ it("publishes an event", function() { with(this) {
249
+ expect_event("alice", "unsubscribe", ["/messages/foo"])
250
+ unsubscribe("alice", "/messages/foo")
251
+ }})
173
252
  }})
174
253
  }})
175
254
 
@@ -188,6 +267,16 @@ JS.ENV.EngineSpec = JS.Test.describe("Pub/sub engines", function() { with(this)
188
267
  expect_no_message("bob")
189
268
  expect_no_message("carol")
190
269
  }})
270
+
271
+ it("publishes a :publish event with a clientId", function() { with(this) {
272
+ expect_event("bob", "publish", ["/messages/foo", "ok"])
273
+ publish_by("bob", message)
274
+ }})
275
+
276
+ it("publishes a :publish event with no clientId", function() { with(this) {
277
+ expect_event(null, "publish", ["/messages/foo", "ok"])
278
+ publish(message)
279
+ }})
191
280
  }})
192
281
 
193
282
  describe("with a subscriber", function() { with(this) {
@@ -205,6 +294,11 @@ JS.ENV.EngineSpec = JS.Test.describe("Pub/sub engines", function() { with(this)
205
294
  publish(message)
206
295
  expect_message("alice", [message])
207
296
  }})
297
+
298
+ it("publishes a :publish event with a clientId", function() { with(this) {
299
+ expect_event("bob", "publish", ["/messages/foo", "ok"])
300
+ publish_by("bob", message)
301
+ }})
208
302
  }})
209
303
 
210
304
  describe("with a subscriber that is removed", function() { with(this) {
@@ -219,6 +313,11 @@ JS.ENV.EngineSpec = JS.Test.describe("Pub/sub engines", function() { with(this)
219
313
  expect_no_message("bob")
220
314
  expect_no_message("carol")
221
315
  }})
316
+
317
+ it("publishes a :publish event with a clientId", function() { with(this) {
318
+ expect_event("bob", "publish", ["/messages/foo", "ok"])
319
+ publish_by("bob", message)
320
+ }})
222
321
  }})
223
322
 
224
323
  describe("with multiple subscribers", function() { with(this) {
@@ -264,6 +363,11 @@ JS.ENV.EngineSpec = JS.Test.describe("Pub/sub engines", function() { with(this)
264
363
  expect_no_message("bob")
265
364
  expect_message("carol", [message])
266
365
  }})
366
+
367
+ it("delivers a unique copy of the message to each client", function() { with(this) {
368
+ publish(message)
369
+ check_different_messages("alice", "carol")
370
+ }})
267
371
  }})
268
372
 
269
373
  describe("with multiple matching subscriptions for the same client", function() { with(this) {
@@ -332,8 +436,17 @@ JS.ENV.EngineSpec = JS.Test.describe("Pub/sub engines", function() { with(this)
332
436
  after(function() { this.clean_redis_db() })
333
437
 
334
438
  itShouldBehaveLike("faye engine")
439
+
335
440
  describe("distribution", function() { with(this) {
336
441
  itShouldBehaveLike("distributed engine")
337
442
  }})
443
+
444
+ describe("using a Unix socket", function() { with(this) {
445
+ before(function() { with(this) {
446
+ this.engineOpts.socket = "/tmp/redis.sock"
447
+ }})
448
+
449
+ itShouldBehaveLike("faye engine")
450
+ }})
338
451
  }})
339
452
  }})
@@ -5,13 +5,11 @@ JS.ENV.NodeAdapterSteps = JS.Test.asyncSteps({
5
5
  start_server: function(port, resume) {
6
6
  this._port = port
7
7
  this._app = new Faye.NodeAdapter(this.options())
8
- this._app.listen(port)
9
- setTimeout(resume, 50)
8
+ this._app.listen(port, {}, resume)
10
9
  },
11
10
 
12
11
  stop_server: function(resume) {
13
- this._app.stop()
14
- resume()
12
+ this._app.stop(resume)
15
13
  },
16
14
 
17
15
  header: function(key, value, resume) {
@@ -90,18 +90,6 @@ JS.ENV.Server.HandshakeSpec = JS.Test.describe("Server handshake", function() {
90
90
  }, response)
91
91
  })
92
92
  }})
93
-
94
- it("returns a successful response for local clients", function() { with(this) {
95
- expect(engine, "createClient").yields(["clientid"])
96
- server.handshake(message, true, function(response) {
97
- assertEqual({
98
- channel: "/meta/handshake",
99
- successful: true,
100
- version: "1.0",
101
- clientId: "clientid"
102
- }, response)
103
- })
104
- }})
105
93
  }})
106
94
 
107
95
  describe("with no matching supportedConnectionTypes", function() { with(this) {
@@ -1,24 +1,32 @@
1
1
  JS.ENV.IntegrationSteps = JS.Test.asyncSteps({
2
- server: function(port, callback) {
3
- this._adapter = new Faye.NodeAdapter({mount: "/bayeux", timeout: 25})
4
- this._adapter.listen(port)
2
+ server: function(port, ssl, callback) {
3
+ var shared = __dirname + '/../../../examples/shared',
4
+
5
+ options = ssl
6
+ ? { key: shared + '/server.key', cert: shared + '/server.crt' }
7
+ : null
8
+
9
+ this._adapter = new Faye.NodeAdapter({mount: "/bayeux", timeout: 2})
5
10
  this._port = port
6
- setTimeout(callback, 100)
11
+ this._secure = ssl
12
+ this._adapter.listen(port, options, callback)
7
13
  },
8
14
 
9
15
  stop: function(callback) {
10
- this._adapter.stop()
11
- setTimeout(callback, 100)
16
+ for (var id in this._clients) this._clients[id].disconnect()
17
+ var self = this
18
+ setTimeout(function() { self._adapter.stop(callback) }, 100)
12
19
  },
13
20
 
14
21
  client: function(name, channels, callback) {
22
+ var scheme = this._secure ? "https" : "http"
15
23
  this._clients = this._clients || {}
16
24
  this._inboxes = this._inboxes || {}
17
- this._clients[name] = new Faye.Client("http://0.0.0.0:" + this._port + "/bayeux")
25
+ this._clients[name] = new Faye.Client(scheme + "://0.0.0.0:" + this._port + "/bayeux")
18
26
  this._inboxes[name] = {}
19
27
 
20
28
  var n = channels.length
21
- if (n === 0) return callback()
29
+ if (n === 0) return this._clients[name].connect(callback)
22
30
 
23
31
  Faye.each(channels, function(channel) {
24
32
  var subscription = this._clients[name].subscribe(channel, function(message) {
@@ -47,32 +55,69 @@ JS.ENV.IntegrationSteps = JS.Test.asyncSteps({
47
55
  JS.ENV.Server.IntegrationSpec = JS.Test.describe("Server integration", function() { with(this) {
48
56
  include(IntegrationSteps)
49
57
 
50
- before(function() { with(this) {
51
- server(8000)
52
- client("alice", [])
53
- client("bob", ["/foo"])
54
- }})
55
-
56
- after(function() { this.stop() })
57
-
58
- it("delivers a message between clients", function() { with(this) {
59
- publish("alice", "/foo", {hello: "world"})
60
- check_inbox("bob", "/foo", [{hello: "world"}])
58
+ sharedExamplesFor("message bus", function() { with(this) {
59
+ before(function() { with(this) {
60
+ server(8000, serverOptions.ssl)
61
+ client("alice", [])
62
+ client("bob", ["/foo"])
63
+ }})
64
+
65
+ after(function() { this.stop() })
66
+
67
+ it("delivers a message between clients", function() { with(this) {
68
+ publish("alice", "/foo", {hello: "world"})
69
+ check_inbox("bob", "/foo", [{hello: "world"}])
70
+ }})
71
+
72
+ it("does not deliver messages for unsubscribed channels", function() { with(this) {
73
+ publish("alice", "/bar", {hello: "world"})
74
+ check_inbox("bob", "/foo", [])
75
+ }})
76
+
77
+ it("delivers multiple messages", function() { with(this) {
78
+ publish("alice", "/foo", {hello: "world"})
79
+ publish("alice", "/foo", {hello: "world"})
80
+ check_inbox("bob", "/foo", [{hello: "world"},{hello: "world"}])
81
+ }})
82
+
83
+ it("delivers multibyte strings", function() { with(this) {
84
+ publish("alice", "/foo", {hello: "Apple = "})
85
+ check_inbox("bob", "/foo", [{hello: "Apple = "}])
86
+ }})
61
87
  }})
62
88
 
63
- it("does not deliver messages for unsubscribed channels", function() { with(this) {
64
- publish("alice", "/bar", {hello: "world"})
65
- check_inbox("bob", "/foo", [])
89
+ sharedExamplesFor("network transports", function() { with(this) {
90
+ describe("with HTTP transport", function() { with(this) {
91
+ before(function() { with(this) {
92
+ stub(Faye.Transport.WebSocket, "isUsable").yields([false])
93
+ }})
94
+
95
+ itShouldBehaveLike("message bus")
96
+ }})
97
+
98
+ describe("with WebSocket transport", function() { with(this) {
99
+ before(function() { with(this) {
100
+ stub(Faye.Transport.WebSocket, "isUsable").yields([true])
101
+ }})
102
+
103
+ itShouldBehaveLike("message bus")
104
+ }})
66
105
  }})
67
106
 
68
- it("delivers multiple messages", function() { with(this) {
69
- publish("alice", "/foo", {hello: "world"})
70
- publish("alice", "/foo", {hello: "world"})
71
- check_inbox("bob", "/foo", [{hello: "world"},{hello: "world"}])
107
+ describe("with HTTP server", function() { with(this) {
108
+ before(function() { with(this) {
109
+ this.serverOptions = {ssl: false}
110
+ }})
111
+
112
+ itShouldBehaveLike("network transports")
72
113
  }})
73
114
 
74
- it("delivers multibyte strings", function() { with(this) {
75
- publish("alice", "/foo", {hello: "Apple = "})
76
- check_inbox("bob", "/foo", [{hello: "Apple = "}])
115
+ describe("with HTTPS server", function() { with(this) {
116
+ before(function() { with(this) {
117
+ this.serverOptions = {ssl: true}
118
+ }})
119
+
120
+ itShouldBehaveLike("network transports")
77
121
  }})
78
122
  }})
123
+