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
@@ -5,6 +5,7 @@ describe Faye::Client do
5
5
  transport = mock("transport")
6
6
  transport.stub(:connection_type).and_return "fake"
7
7
  transport.stub(:send)
8
+ transport.extend(Faye::Publisher)
8
9
  transport
9
10
  end
10
11
 
@@ -18,7 +19,7 @@ describe Faye::Client do
18
19
  end
19
20
 
20
21
  def create_client
21
- Faye::Transport.stub(:get).and_return(transport)
22
+ Faye::Transport.stub(:get).and_yield(transport)
22
23
  @client = Faye::Client.new("http://localhost/")
23
24
  end
24
25
 
@@ -106,7 +107,7 @@ describe Faye::Client do
106
107
  stub_response "channel" => "/meta/handshake",
107
108
  "successful" => true,
108
109
  "version" => "1.0",
109
- "supportedConnectionTypes" => ["websocket"],
110
+ "supportedConnectionTypes" => ["long-polling", "websocket"],
110
111
  "clientId" => "fakeid"
111
112
  end
112
113
 
@@ -119,16 +120,26 @@ describe Faye::Client do
119
120
  @client.handshake
120
121
  @client.state.should == :CONNECTED
121
122
  end
122
-
123
+
124
+ it "registers any pre-existing subscriptions" do
125
+ @client.should_receive(:subscribe).with([], true)
126
+ @client.handshake
127
+ end
128
+
123
129
  it "selects a new transport based on what the server supports" do
124
- Faye::Transport.should_receive(:get).with(instance_of(Faye::Client), ["websocket"]).
130
+ Faye::Transport.should_receive(:get).with(instance_of(Faye::Client), ["long-polling", "websocket"]).
125
131
  and_return(transport)
126
132
  @client.handshake
127
133
  end
128
-
129
- it "registers any pre-existing subscriptions" do
130
- @client.should_receive(:subscribe).with([], true)
131
- @client.handshake
134
+
135
+ describe "with websocket disabled" do
136
+ before { @client.disable("websocket") }
137
+
138
+ it "selects a new transport, excluding websocket" do
139
+ Faye::Transport.should_receive(:get).with(instance_of(Faye::Client), ["long-polling"]).
140
+ and_return(transport)
141
+ @client.handshake
142
+ end
132
143
  end
133
144
  end
134
145
 
@@ -257,6 +268,7 @@ describe Faye::Client do
257
268
  before { create_connected_client }
258
269
 
259
270
  it "sends a disconnect message to the server" do
271
+ transport.stub(:close)
260
272
  transport.should_receive(:send).with({
261
273
  "channel" => "/meta/disconnect",
262
274
  "clientId" => "fakeid",
@@ -266,9 +278,23 @@ describe Faye::Client do
266
278
  end
267
279
 
268
280
  it "puts the client in the DISCONNECTED state" do
281
+ transport.stub(:close)
269
282
  @client.disconnect
270
283
  @client.state.should == :DISCONNECTED
271
284
  end
285
+
286
+ describe "on successful response" do
287
+ before do
288
+ stub_response "channel" => "/meta/disconnect",
289
+ "successful" => true,
290
+ "clientId" => "fakeid"
291
+ end
292
+
293
+ it "closes the transport" do
294
+ transport.should_receive(:close)
295
+ @client.disconnect
296
+ end
297
+ end
272
298
  end
273
299
 
274
300
  describe :subscribe do
@@ -567,6 +593,29 @@ describe Faye::Client do
567
593
  lambda { @client.publish("/messages/*", "hello" => "world") }.should raise_error
568
594
  end
569
595
 
596
+ describe "on publish failure" do
597
+ before do
598
+ stub_response "channel" => "/messages/foo",
599
+ "error" => "407:/messages/foo:Failed to publish",
600
+ "successful" => false,
601
+ "clientId" => "fakeid"
602
+ end
603
+
604
+ it "should not be published" do
605
+ published = false
606
+ @client.publish("/messages/foo", "text" => "hi").callback { published = true }
607
+ published.should be_false
608
+ end
609
+
610
+ it "reports the error through an errback" do
611
+ error = nil
612
+ @client.publish("/messages/foo", "text" => "hi").errback { |e| error = e }
613
+ error.code.should == 407
614
+ error.params.should == ["/messages/foo"]
615
+ error.message.should == "Failed to publish"
616
+ end
617
+ end
618
+
570
619
  describe "with an outgoing extension installed" do
571
620
  before do
572
621
  extension = Class.new do
@@ -612,4 +661,48 @@ describe Faye::Client do
612
661
  end
613
662
  end
614
663
  end
664
+
665
+ describe "network notifications" do
666
+ before { create_client }
667
+
668
+ describe "in the default state" do
669
+ it "broadcasts a down notification" do
670
+ @client.should_receive(:trigger).with("transport:down")
671
+ transport.trigger(:down)
672
+ end
673
+
674
+ it "broadcasts an up notification" do
675
+ @client.should_receive(:trigger).with("transport:up")
676
+ transport.trigger(:up)
677
+ end
678
+ end
679
+
680
+ describe "when the transport is up" do
681
+ before { transport.trigger(:up) }
682
+
683
+ it "broadcasts a down notification" do
684
+ @client.should_receive(:trigger).with("transport:down")
685
+ transport.trigger(:down)
686
+ end
687
+
688
+ it "does not broadcast an up notification" do
689
+ @client.should_not_receive(:trigger)
690
+ transport.trigger(:up)
691
+ end
692
+ end
693
+
694
+ describe "when the transport is down" do
695
+ before { transport.trigger(:down) }
696
+
697
+ it "does not broadcast a down notification" do
698
+ @client.should_not_receive(:trigger)
699
+ transport.trigger(:down)
700
+ end
701
+
702
+ it "broadcasts an up notification" do
703
+ @client.should_receive(:trigger).with("transport:up")
704
+ transport.trigger(:up)
705
+ end
706
+ end
707
+ end
615
708
  end
@@ -63,6 +63,12 @@ EngineSteps = EM::RSpec.async_steps do
63
63
  EM.add_timer(0.01, &resume)
64
64
  end
65
65
 
66
+ def publish_by(name, message, &resume)
67
+ message = {"clientId" => @clients[name], "id" => Faye.random}.merge(message)
68
+ engine.publish(message)
69
+ EM.add_timer(0.01, &resume)
70
+ end
71
+
66
72
  def ping(name, &resume)
67
73
  engine.ping(@clients[name])
68
74
  resume.call
@@ -73,6 +79,22 @@ EngineSteps = EM::RSpec.async_steps do
73
79
  resume.call
74
80
  end
75
81
 
82
+ def expect_event(name, event, args, &resume)
83
+ params = [@clients[name]] + args
84
+ handler = lambda { |*a| }
85
+ engine.bind(event, &handler)
86
+ handler.should_receive(:call).with(*params)
87
+ resume.call
88
+ end
89
+
90
+ def expect_no_event(name, event, args, &resume)
91
+ params = [@clients[name]] + args
92
+ handler = lambda { |*a| }
93
+ engine.bind(event, &handler)
94
+ handler.should_not_receive(:call).with(*params)
95
+ resume.call
96
+ end
97
+
76
98
  def expect_message(name, messages, &resume)
77
99
  @inboxes[name].should == messages
78
100
  resume.call
@@ -83,6 +105,11 @@ EngineSteps = EM::RSpec.async_steps do
83
105
  resume.call
84
106
  end
85
107
 
108
+ def check_different_messages(a, b, &resume)
109
+ @inboxes[a].first.should_not be_equal(@inboxes[b].first)
110
+ resume.call
111
+ end
112
+
86
113
  def clean_redis_db(&resume)
87
114
  engine.disconnect
88
115
  redis = EM::Hiredis::Client.connect
@@ -119,6 +146,11 @@ describe "Pub/sub engines" do
119
146
  1.upto(7) { |i| create_client "client#{i}" }
120
147
  check_num_clients 10
121
148
  end
149
+
150
+ it "publishes an event" do
151
+ engine.should_receive(:trigger).with(:handshake, match(/^[a-z0-9]+$/)).exactly(4)
152
+ create_client :dave
153
+ end
122
154
  end
123
155
 
124
156
  describe :client_exists do
@@ -153,6 +185,11 @@ describe "Pub/sub engines" do
153
185
  check_client_exists :alice, false
154
186
  end
155
187
 
188
+ it "publishes an event" do
189
+ expect_event :alice, :disconnect, []
190
+ destroy_client :alice
191
+ end
192
+
156
193
  describe "when the client has subscriptions" do
157
194
  before do
158
195
  @message = {"channel" => "/messages/foo", "data" => "ok"}
@@ -165,6 +202,45 @@ describe "Pub/sub engines" do
165
202
  publish @message
166
203
  expect_no_message :alice
167
204
  end
205
+
206
+ it "publishes an event" do
207
+ expect_event :alice, :disconnect, []
208
+ destroy_client :alice
209
+ end
210
+ end
211
+ end
212
+
213
+ describe :subscribe do
214
+ it "publishes an event" do
215
+ expect_event :alice, :subscribe, ["/messages/foo"]
216
+ subscribe :alice, "/messages/foo"
217
+ end
218
+
219
+ describe "when the client is subscribed to the channel" do
220
+ before { subscribe :alice, "/messages/foo" }
221
+
222
+ it "does not publish an event" do
223
+ expect_no_event :alice, :subscribe, ["/messages/foo"]
224
+ subscribe :alice, "/messages/foo"
225
+ end
226
+ end
227
+ end
228
+
229
+ describe :unsubscribe do
230
+ before { subscribe :alice, "/messages/bar" }
231
+
232
+ it "does not publish an event" do
233
+ expect_no_event :alice, :unsubscribe, ["/messages/foo"]
234
+ unsubscribe :alice, "/messages/foo"
235
+ end
236
+
237
+ describe "when the client is subscribed to the channel" do
238
+ before { subscribe :alice, "/messages/foo" }
239
+
240
+ it "publishes an event" do
241
+ expect_event :alice, :unsubscribe, ["/messages/foo"]
242
+ unsubscribe :alice, "/messages/foo"
243
+ end
168
244
  end
169
245
  end
170
246
 
@@ -183,6 +259,16 @@ describe "Pub/sub engines" do
183
259
  expect_no_message :bob
184
260
  expect_no_message :carol
185
261
  end
262
+
263
+ it "publishes a :publish event with a clientId" do
264
+ expect_event :bob, :publish, ["/messages/foo", "ok"]
265
+ publish_by :bob, @message
266
+ end
267
+
268
+ it "publishes a :publish event with no clientId" do
269
+ expect_event nil, :publish, ["/messages/foo", "ok"]
270
+ publish @message
271
+ end
186
272
  end
187
273
 
188
274
  describe "with a subscriber" do
@@ -198,6 +284,11 @@ describe "Pub/sub engines" do
198
284
  publish @message
199
285
  expect_message :alice, [@message]
200
286
  end
287
+
288
+ it "publishes a :publish event" do
289
+ expect_event :bob, :publish, ["/messages/foo", "ok"]
290
+ publish_by :bob, @message
291
+ end
201
292
  end
202
293
 
203
294
  describe "with a subscriber that is removed" do
@@ -212,6 +303,11 @@ describe "Pub/sub engines" do
212
303
  expect_no_message :bob
213
304
  expect_no_message :carol
214
305
  end
306
+
307
+ it "publishes a :publish event" do
308
+ expect_event :bob, :publish, ["/messages/foo", "ok"]
309
+ publish_by :bob, @message
310
+ end
215
311
  end
216
312
 
217
313
  describe "with multiple subscribers" do
@@ -257,6 +353,11 @@ describe "Pub/sub engines" do
257
353
  expect_no_message :bob
258
354
  expect_message :carol, [@message]
259
355
  end
356
+
357
+ it "delivers a unique copy of the message to each client" do
358
+ publish @message
359
+ check_different_messages :alice, :carol
360
+ end
260
361
  end
261
362
 
262
363
  describe "with multiple matching subscriptions for the same client" do
@@ -322,6 +423,11 @@ describe "Pub/sub engines" do
322
423
  after { clean_redis_db }
323
424
  it_should_behave_like "faye engine"
324
425
  it_should_behave_like "distributed engine"
426
+
427
+ describe "using a Unix socket" do
428
+ before { engine_opts[:socket] = "/tmp/redis.sock" }
429
+ it_should_behave_like "faye engine"
430
+ end
325
431
  end
326
432
  end
327
433
 
@@ -92,18 +92,6 @@ describe "server handshake" do
92
92
  }
93
93
  end
94
94
  end
95
-
96
- it "returns a successful response for local clients" do
97
- engine.stub(:create_client).and_yield "clientid"
98
- server.handshake(message, true) do |response|
99
- response.should == {
100
- "channel" => "/meta/handshake",
101
- "successful" => true,
102
- "version" => "1.0",
103
- "clientId" => "clientid"
104
- }
105
- end
106
- end
107
95
  end
108
96
 
109
97
  describe "with no matching supportedConnectionTypes" do
@@ -6,10 +6,17 @@ require "thin"
6
6
  Thin::Logging.silent = true
7
7
 
8
8
  IntegrationSteps = EM::RSpec.async_steps do
9
- def server(port, &callback)
9
+ def server(port, ssl, &callback)
10
+ shared = File.dirname(__FILE__) + '/../../../examples/shared'
11
+
12
+ options = ssl ?
13
+ { :key => shared + '/server.key', :cert => shared + '/server.crt' } :
14
+ nil
15
+
10
16
  @adapter = Faye::RackAdapter.new(:mount => "/bayeux", :timeout => 25)
11
- @adapter.listen(port)
17
+ @adapter.listen(port, options)
12
18
  @port = port
19
+ @secure = ssl
13
20
  EM.next_tick(&callback)
14
21
  end
15
22
 
@@ -19,13 +26,14 @@ IntegrationSteps = EM::RSpec.async_steps do
19
26
  end
20
27
 
21
28
  def client(name, channels, &callback)
29
+ scheme = @secure ? "https" : "http"
22
30
  @clients ||= {}
23
31
  @inboxes ||= {}
24
- @clients[name] = Faye::Client.new("http://0.0.0.0:#{@port}/bayeux")
32
+ @clients[name] = Faye::Client.new("#{scheme}://0.0.0.0:#{@port}/bayeux")
25
33
  @inboxes[name] = {}
26
34
 
27
35
  n = channels.size
28
- return callback.call if n.zero?
36
+ return @clients[name].connect(&callback) if n.zero?
29
37
 
30
38
  channels.each do |channel|
31
39
  subscription = @clients[name].subscribe(channel) do |message|
@@ -57,7 +65,7 @@ describe "server integration" do
57
65
 
58
66
  before do
59
67
  Faye.ensure_reactor_running!
60
- server 8000
68
+ server 8000, server_options[:ssl]
61
69
  client :alice, []
62
70
  client :bob, ["/foo"]
63
71
  sync
@@ -65,24 +73,54 @@ describe "server integration" do
65
73
 
66
74
  after { stop }
67
75
 
68
- it "delivers a message between clients" do
69
- publish :alice, "/foo", {"hello" => "world"}
70
- check_inbox :bob, "/foo", [{"hello" => "world"}]
76
+ shared_examples_for "message bus" do
77
+ it "delivers a message between clients" do
78
+ publish :alice, "/foo", {"hello" => "world"}
79
+ check_inbox :bob, "/foo", [{"hello" => "world"}]
80
+ end
81
+
82
+ it "does not deliver messages for unsubscribed channels" do
83
+ publish :alice, "/bar", {"hello" => "world"}
84
+ check_inbox :bob, "/foo", []
85
+ end
86
+
87
+ it "delivers multiple messages" do
88
+ publish :alice, "/foo", {"hello" => "world"}
89
+ publish :alice, "/foo", {"hello" => "world"}
90
+ check_inbox :bob, "/foo", [{"hello" => "world"}, {"hello" => "world"}]
91
+ end
92
+
93
+ it "delivers multibyte strings" do
94
+ publish :alice, "/foo", {"hello" => encode("Apple = ")}
95
+ check_inbox :bob, "/foo", [{"hello" => encode("Apple = ")}]
96
+ end
71
97
  end
72
98
 
73
- it "does not deliver messages for unsubscribed channels" do
74
- publish :alice, "/bar", {"hello" => "world"}
75
- check_inbox :bob, "/foo", []
99
+ shared_examples_for "network transports" do
100
+ describe "with HTTP transport" do
101
+ before do
102
+ Faye::Transport::WebSocket.stub(:usable?).and_yield(false)
103
+ end
104
+
105
+ it_should_behave_like "message bus"
106
+ end
107
+
108
+ describe "with WebSocket transport" do
109
+ before do
110
+ Faye::Transport::WebSocket.stub(:usable?).and_yield(false)
111
+ end
112
+
113
+ it_should_behave_like "message bus"
114
+ end
76
115
  end
77
116
 
78
- it "delivers multiple messages" do
79
- publish :alice, "/foo", {"hello" => "world"}
80
- publish :alice, "/foo", {"hello" => "world"}
81
- check_inbox :bob, "/foo", [{"hello" => "world"}, {"hello" => "world"}]
117
+ describe "with HTTP server" do
118
+ let(:server_options) { {:ssl => false} }
119
+ it_should_behave_like "network transports"
82
120
  end
83
121
 
84
- it "delivers multibyte strings" do
85
- publish :alice, "/foo", {"hello" => encode("Apple = ")}
86
- check_inbox :bob, "/foo", [{"hello" => encode("Apple = ")}]
122
+ describe "with HTTPS server" do
123
+ let(:server_options) { {:ssl => true} }
124
+ it_should_behave_like "network transports"
87
125
  end
88
126
  end