faye 0.3.4 → 0.5.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 (49) hide show
  1. data/History.txt +13 -0
  2. data/Manifest.txt +16 -33
  3. data/README.txt +9 -274
  4. data/Rakefile +4 -4
  5. data/lib/faye-browser-min.js +1 -0
  6. data/lib/faye.rb +26 -9
  7. data/lib/faye/{rack_adapter.rb → adapters/rack_adapter.rb} +38 -25
  8. data/lib/faye/error.rb +0 -7
  9. data/lib/faye/{logging.rb → mixins/logging.rb} +0 -0
  10. data/lib/faye/mixins/publisher.rb +29 -0
  11. data/lib/faye/{timeouts.rb → mixins/timeouts.rb} +1 -0
  12. data/lib/faye/{transport.rb → network/transport.rb} +32 -43
  13. data/lib/faye/{channel.rb → protocol/channel.rb} +23 -3
  14. data/lib/faye/{client.rb → protocol/client.rb} +124 -90
  15. data/lib/faye/{connection.rb → protocol/connection.rb} +41 -23
  16. data/lib/faye/protocol/extensible.rb +47 -0
  17. data/lib/faye/{grammar.rb → protocol/grammar.rb} +0 -0
  18. data/lib/faye/{server.rb → protocol/server.rb} +98 -54
  19. data/lib/faye/protocol/subscription.rb +23 -0
  20. data/lib/faye/{namespace.rb → util/namespace.rb} +0 -0
  21. data/lib/faye/util/web_socket.rb +119 -0
  22. data/lib/thin_extensions.rb +86 -0
  23. data/test/scenario.rb +68 -14
  24. data/test/test_clients.rb +215 -2
  25. data/test/test_server.rb +10 -10
  26. metadata +102 -71
  27. data/Jakefile +0 -13
  28. data/build/faye-client-min.js +0 -1
  29. data/build/faye.js +0 -1488
  30. data/examples/README.rdoc +0 -41
  31. data/examples/node/app.js +0 -26
  32. data/examples/node/client.js +0 -23
  33. data/examples/node/faye-client-min.js +0 -1
  34. data/examples/node/faye.js +0 -1488
  35. data/examples/rack/app.rb +0 -16
  36. data/examples/rack/client.rb +0 -25
  37. data/examples/rack/config.ru +0 -8
  38. data/examples/shared/public/favicon.ico +0 -0
  39. data/examples/shared/public/index.html +0 -49
  40. data/examples/shared/public/jquery.js +0 -19
  41. data/examples/shared/public/mootools.js +0 -4329
  42. data/examples/shared/public/prototype.js +0 -4874
  43. data/examples/shared/public/robots.txt +0 -0
  44. data/examples/shared/public/soapbox.js +0 -100
  45. data/examples/shared/public/style.css +0 -43
  46. data/jake.yml +0 -40
  47. data/lib/faye-client-min.js +0 -1
  48. data/test/scenario.js +0 -138
  49. data/test/test_clients.js +0 -195
@@ -0,0 +1,86 @@
1
+ # WebSocket extensions for Thin
2
+ # Taken from the Cramp project
3
+ # http://github.com/lifo/cramp
4
+
5
+ # Copyright (c) 2009-2010 Pratik Naik
6
+ #
7
+ # Permission is hereby granted, free of charge, to any person obtaining
8
+ # a copy of this software and associated documentation files (the
9
+ # "Software"), to deal in the Software without restriction, including
10
+ # without limitation the rights to use, copy, modify, merge, publish,
11
+ # distribute, sublicense, and/or sell copies of the Software, and to
12
+ # permit persons to whom the Software is furnished to do so, subject to
13
+ # the following conditions:
14
+ #
15
+ # The above copyright notice and this permission notice shall be
16
+ # included in all copies or substantial portions of the Software.
17
+ #
18
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
19
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
20
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
21
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
22
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
23
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
24
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
25
+
26
+ class Thin::Connection
27
+ def receive_data(data)
28
+ trace { data }
29
+
30
+ case @serving
31
+ when :websocket
32
+ callback = @request.env[Thin::Request::WEBSOCKET_RECEIVE_CALLBACK]
33
+ callback.call(data) if callback
34
+ else
35
+ if @request.parse(data)
36
+ if @request.websocket?
37
+ @response.persistent!
38
+ @response.websocket_upgrade_data = @request.websocket_upgrade_data
39
+ @serving = :websocket
40
+ end
41
+
42
+ process
43
+ end
44
+ end
45
+ rescue Thin::InvalidRequest => e
46
+ log "!! Invalid request"
47
+ log_error e
48
+ close_connection
49
+ end
50
+ end
51
+
52
+ class Thin::Request
53
+ WEBSOCKET_RECEIVE_CALLBACK = 'websocket.receive_callback'.freeze
54
+
55
+ def websocket?
56
+ @env['HTTP_CONNECTION'] == 'Upgrade' && @env['HTTP_UPGRADE'] == 'WebSocket'
57
+ end
58
+
59
+ def websocket_url
60
+ @env['websocket.url'] = "ws://#{@env['HTTP_HOST']}#{@env['REQUEST_PATH']}"
61
+ end
62
+
63
+ def websocket_upgrade_data
64
+ upgrade = "HTTP/1.1 101 Web Socket Protocol Handshake\r\n"
65
+ upgrade << "Upgrade: WebSocket\r\n"
66
+ upgrade << "Connection: Upgrade\r\n"
67
+ upgrade << "WebSocket-Origin: #{@env['HTTP_ORIGIN']}\r\n"
68
+ upgrade << "WebSocket-Location: #{websocket_url}\r\n\r\n"
69
+ upgrade
70
+ end
71
+ end
72
+
73
+ class Thin::Response
74
+ # Headers for sending Websocket upgrade
75
+ attr_accessor :websocket_upgrade_data
76
+
77
+ def each
78
+ websocket_upgrade_data ? yield(websocket_upgrade_data) : yield(head)
79
+ if @body.is_a?(String)
80
+ yield @body
81
+ else
82
+ @body.each { |chunk| yield chunk }
83
+ end
84
+ end
85
+ end
86
+
@@ -59,18 +59,28 @@ module Scenario
59
59
  def initialize
60
60
  @clients = {}
61
61
  @inbox = {}
62
+ @errors = {}
62
63
  @pool = 0
63
64
  end
65
+
66
+ def wait(time, &block)
67
+ EM.add_timer(time, &block)
68
+ end
64
69
 
65
70
  def check_inbox(expected_inbox, &block)
66
71
  assert_equal expected_inbox, @inbox
67
- block.call
72
+ EM.next_tick(&block)
73
+ end
74
+
75
+ def check_errors(name, expected_errors, &block)
76
+ assert_equal expected_errors, @errors[name]
77
+ EM.next_tick(&block)
68
78
  end
69
79
 
70
80
  def server(port, &block)
71
- @endpoint = "http://0.0.0.0:#{port}/comet"
72
- @comet = Faye::RackAdapter.new(:mount => '/comet', :timeout => 30)
73
- Rack::Handler.get('thin').run(@comet, :Port => port) do |server|
81
+ @endpoint = "http://0.0.0.0:#{port}/bayeux"
82
+ @bayeux = Faye::RackAdapter.new(:mount => '/bayeux', :timeout => 30)
83
+ Rack::Handler.get('thin').run(@bayeux, :Port => port) do |server|
74
84
  @server = server
75
85
  EM.next_tick(&block)
76
86
  end
@@ -81,25 +91,69 @@ module Scenario
81
91
  end
82
92
 
83
93
  def local_client(name, channels, &block)
84
- setup_client(@comet.get_client, name, channels, &block)
94
+ setup_client(@bayeux.get_client, name, channels, &block)
85
95
  end
86
96
 
87
- def setup_client(client, name, channels, &block)
88
- channels.each do |channel|
89
- client.subscribe(channel) do |message|
90
- box = @inbox[name]
91
- box[channel] ||= []
92
- box[channel] << message
93
- end
97
+ def extend_server(stage, extension, &block)
98
+ object = Object.new
99
+ def object.added
100
+ @active = true
101
+ end
102
+ (class << object; self; end).send(:define_method, stage) do |*args|
103
+ extension.call(*args) if @active
94
104
  end
105
+ @bayeux.add_extension(object)
106
+ block.call
107
+ end
108
+
109
+ def extend_client(name, stage, extension, &block)
110
+ object = Object.new
111
+ (class << object; self; end).send(:define_method, stage, &extension)
112
+ @clients[name].add_extension(object)
113
+ block.call
114
+ end
115
+
116
+ def listen_for_errors(name, &block)
117
+ @errors[name] = []
118
+ errors = @errors
119
+
120
+ extend_client(name, :incoming, lambda { |message, callback|
121
+ if message['successful'] == false
122
+ errors[name] << message['error']
123
+ end
124
+ callback.call(message)
125
+ }, &block)
126
+ end
127
+
128
+ def setup_client(client, name, channels, &block)
95
129
  @clients[name] = client
96
130
  @inbox[name] = {}
97
131
  @pool += 1
132
+
133
+ channels.each { |channel| subscribe(name, channel) }
98
134
  EM.add_timer(0.5 * channels.size, &block)
99
135
  end
100
136
 
101
- def publish(from, channel, message, &block)
102
- @clients[from].publish(channel, message)
137
+ def subscribe(name, channel, &block)
138
+ client = @clients[name]
139
+
140
+ @last_sub = client.subscribe(channel) do |message|
141
+ box = @inbox[name]
142
+ box[channel] ||= []
143
+ box[channel] << message
144
+ end
145
+
146
+ EM.add_timer(0.5, &block)
147
+ end
148
+
149
+ def cancel_last_subscription(&block)
150
+ @last_sub.cancel
151
+ EM.add_timer(0.5, &block)
152
+ end
153
+
154
+ def publish(from, channel, messages, &block)
155
+ messages = [messages].flatten
156
+ messages.each { |msg| @clients[from].publish(channel, msg) }
103
157
  EM.add_timer(2, &block)
104
158
  end
105
159
 
@@ -6,6 +6,157 @@ class TestClients < Test::Unit::TestCase
6
6
  include Faye
7
7
  include Scenario
8
8
 
9
+ scenario "Client modifies incoming messages" do
10
+ server 8000
11
+ http_client :A, ['/channels/a']
12
+ http_client :B, ['/channels/b']
13
+
14
+ extend_client :A, :incoming, lambda { |message, callback|
15
+ if message['data']
16
+ message['data']['modified'] = 'hi'
17
+ end
18
+ callback.call(message)
19
+ }
20
+
21
+ publish :B, '/channels/a', 'welcome' => 'message'
22
+ check_inbox(
23
+ :A => {
24
+ '/channels/a' => ['welcome' => 'message', 'modified' => 'hi']
25
+ },
26
+ :B => {}
27
+ )
28
+ end
29
+
30
+ scenario "Client blocks incoming messages" do
31
+ server 8000
32
+ http_client :A, ['/channels/a']
33
+ http_client :B, ['/channels/b']
34
+
35
+ extend_client :A, :incoming, lambda { |message, callback|
36
+ callback.call(nil)
37
+ }
38
+
39
+ publish :B, '/channels/a', 'welcome' => 'message'
40
+ check_inbox( :A => {}, :B => {} )
41
+ end
42
+
43
+ scenario "Server requires authentication" do
44
+ server 8000
45
+ http_client :A, ['/channels/a']
46
+ http_client :B, ['/channels/b']
47
+
48
+ extend_server :incoming, lambda { |message, callback|
49
+ if message['ext'] and message['ext']['password']
50
+ callback.call(message)
51
+ end
52
+ }
53
+
54
+ extend_client :B, :outgoing, lambda { |message, callback|
55
+ message['ext'] = {'password' => true}
56
+ callback.call(message)
57
+ }
58
+
59
+ publish :A, '/channels/b', 'message_for' => 'B'
60
+ check_inbox( :A => {}, :B => {} )
61
+
62
+ publish :B, '/channels/a', 'message_for' => 'A'
63
+ check_inbox(
64
+ :A => {
65
+ '/channels/a' => ['message_for' => 'A']
66
+ },
67
+ :B => {}
68
+ )
69
+ end
70
+
71
+ scenario "Server blocks a message by setting an error" do
72
+ server 8000
73
+ http_client :A, ['/channels/a']
74
+ http_client :B, ['/channels/b']
75
+
76
+ extend_server :incoming, lambda { |message, callback|
77
+ if message['data']
78
+ message['error'] = Faye::Error.ext_mismatch
79
+ end
80
+ callback.call(message)
81
+ }
82
+
83
+ listen_for_errors :A
84
+
85
+ publish :A, '/channels/b', 'message_for' => 'B'
86
+ check_inbox( :A => {}, :B => {} )
87
+ check_errors :A, ['302::Extension mismatch']
88
+ end
89
+
90
+ scenario "Server modifies outgoing message" do
91
+ server 8000
92
+ http_client :A, []
93
+ http_client :B, ['/channels/b']
94
+
95
+ extend_server :outgoing, lambda { |message, callback|
96
+ message['data']['addition'] = 56 if message['data']
97
+ callback.call(message)
98
+ }
99
+
100
+ publish :A, '/channels/b', 'message_for' => 'B'
101
+ check_inbox(
102
+ :A => {},
103
+ :B => {
104
+ '/channels/b' => ['message_for' => 'B', 'addition' => 56]
105
+ }
106
+ )
107
+ end
108
+
109
+ [:incoming, :outgoing].each do |direction|
110
+ scenario "Server delays #{ direction } message" do
111
+ server 8000
112
+ http_client :A, []
113
+ http_client :B, ['/channels/b']
114
+
115
+ extend_server direction, lambda { |message, callback|
116
+ timeout = message['data'] ? 5 : 0
117
+ EM.add_timer(timeout) { callback.call(message) }
118
+ }
119
+
120
+ publish :A, '/channels/b', 'message_for' => 'B'
121
+ check_inbox( :A => {}, :B => {} )
122
+
123
+ wait 3
124
+ check_inbox( :A => {}, :B => {} )
125
+
126
+ wait 1
127
+
128
+ check_inbox(
129
+ :A => {},
130
+ :B => {
131
+ '/channels/b' => ['message_for' => 'B']
132
+ }
133
+ )
134
+ end
135
+ end
136
+
137
+ scenario "Server blocks outgoing message" do
138
+ server 8000
139
+ http_client :A, []
140
+ http_client :B, ['/channels/b']
141
+
142
+ extend_server :outgoing, lambda { |message, callback|
143
+ if !message['data'] or message['data']['deliver'] == 'yes'
144
+ callback.call(message)
145
+ else
146
+ callback.call(nil)
147
+ end
148
+ }
149
+
150
+ publish :A, '/channels/b', [{'deliver' => 'no'}, {'deliver' => 'yes'}]
151
+
152
+ check_inbox(
153
+ :A => {},
154
+ :B => {
155
+ '/channels/b' => ['deliver' => 'yes']
156
+ }
157
+ )
158
+ end
159
+
9
160
  scenario "Two HTTP clients, no messages delivered" do
10
161
  server 8000
11
162
  http_client :A, ['/channels/a']
@@ -29,6 +180,40 @@ class TestClients < Test::Unit::TestCase
29
180
  :B => {}
30
181
  )
31
182
  end
183
+
184
+ scenario "Two HTTP clients, two identical messages sent together" do
185
+ server 8000
186
+ http_client :A, ['/channels/a']
187
+ http_client :B, []
188
+ publish :B, '/channels/a', [{'hello' => 'world'}, {'hello' => 'world'}]
189
+ check_inbox(
190
+ :A => {
191
+ '/channels/a' => [{'hello' => 'world'}, {'hello' => 'world'}]
192
+ },
193
+ :B => {}
194
+ )
195
+ end
196
+
197
+ scenario "Two HTTP clients, two message deliveries" do
198
+ server 8000
199
+ http_client :A, ['/channels/a']
200
+ http_client :B, []
201
+ publish :B, '/channels/a', 'hello' => 'world'
202
+ check_inbox(
203
+ :A => {
204
+ '/channels/a' => ['hello' => 'world']
205
+ },
206
+ :B => {}
207
+ )
208
+ wait 1
209
+ publish :B, '/channels/a', 'hello' => 'world'
210
+ check_inbox(
211
+ :A => {
212
+ '/channels/a' => [{'hello' => 'world'}, {'hello' => 'world'}]
213
+ },
214
+ :B => {}
215
+ )
216
+ end
32
217
 
33
218
  scenario "Two HTTP clients, multiple subscriptions" do
34
219
  server 8000
@@ -44,6 +229,35 @@ class TestClients < Test::Unit::TestCase
44
229
  )
45
230
  end
46
231
 
232
+ scenario "Two HTTP clients, two subscriptions on the same channel" do
233
+ server 8000
234
+ http_client :A, ['/channels/a']
235
+ http_client :B, []
236
+ subscribe :A, '/channels/a'
237
+ publish :B, '/channels/a', 'hello' => 'world'
238
+ check_inbox(
239
+ :A => {
240
+ '/channels/a' => [{'hello' => 'world'}, {'hello' => 'world'}]
241
+ },
242
+ :B => {}
243
+ )
244
+ end
245
+
246
+ scenario "Two HTTP clients, two subscriptions and one unsubscription" do
247
+ server 8000
248
+ http_client :A, ['/channels/a']
249
+ http_client :B, []
250
+ subscribe :A, '/channels/a'
251
+ cancel_last_subscription
252
+ publish :B, '/channels/a', 'another' => 'message'
253
+ check_inbox(
254
+ :A => {
255
+ '/channels/a' => [{'another' => 'message'}]
256
+ },
257
+ :B => {}
258
+ )
259
+ end
260
+
47
261
  scenario "Three HTTP clients, single receiver" do
48
262
  server 8000
49
263
  http_client :A, ['/channels/a']
@@ -145,8 +359,7 @@ class TestClients < Test::Unit::TestCase
145
359
  :B => {},
146
360
  :C => {
147
361
  '/channels/name' => ['msg' => 'hey'],
148
- '/channels/foo/**' => ['msg' => 'hey'],
149
- '/channels/name' => ['msg' => 'hey'],
362
+ '/channels/foo/**' => ['msg' => 'hey']
150
363
  }
151
364
  )
152
365
  end
@@ -30,7 +30,7 @@ class TestServer < Test::Unit::TestCase
30
30
  # MUST
31
31
  assert_equal '/meta/handshake', @r['channel']
32
32
  assert_not_equal nil, @r['version']
33
- assert_equal %w[long-polling callback-polling], @r['supportedConnectionTypes']
33
+ assert_equal %w[long-polling callback-polling websocket], @r['supportedConnectionTypes']
34
34
  assert_match( /[a-z0-9]+/, @r['clientId'] )
35
35
  assert_equal true, @r['successful']
36
36
  # MAY
@@ -62,7 +62,7 @@ class TestServer < Test::Unit::TestCase
62
62
  assert_equal '402:version:Missing required parameter', @r['error']
63
63
  # MAY
64
64
  assert_not_equal nil, @r['version']
65
- assert_equal %w[long-polling callback-polling], @r['supportedConnectionTypes']
65
+ assert_equal %w[long-polling callback-polling websocket], @r['supportedConnectionTypes']
66
66
  # MAY include advice, minimumVersion, ext, id
67
67
 
68
68
  #================================================================
@@ -73,7 +73,7 @@ class TestServer < Test::Unit::TestCase
73
73
  assert_equal false, @r['successful']
74
74
  assert_equal '301:iframe,flash:Connection types not supported', @r['error']
75
75
  # MAY
76
- assert_equal %w[long-polling callback-polling], @r['supportedConnectionTypes']
76
+ assert_equal %w[long-polling callback-polling websocket], @r['supportedConnectionTypes']
77
77
  assert_equal nil, @r['id']
78
78
 
79
79
  #================================================================
@@ -84,7 +84,7 @@ class TestServer < Test::Unit::TestCase
84
84
  assert_equal false, @r['successful']
85
85
  assert_equal '402:supportedConnectionTypes:Missing required parameter', @r['error']
86
86
  # MAY
87
- assert_equal %w[long-polling callback-polling], @r['supportedConnectionTypes']
87
+ assert_equal %w[long-polling callback-polling websocket], @r['supportedConnectionTypes']
88
88
  assert_equal 'foo', @r['id']
89
89
 
90
90
  #================================================================
@@ -448,16 +448,16 @@ class TestServer < Test::Unit::TestCase
448
448
 
449
449
  def test_advice
450
450
  @server.send :handle, 'channel' => '/meta/subscribe', 'subscription' => '/foo', 'clientId' => 'fake' do |r|
451
- assert_equal '401:fake:Unknown client', r['error']
452
- assert_equal 'handshake', r['advice']['reconnect']
453
- assert_equal 1000, r['advice']['interval']
451
+ assert_equal '401:fake:Unknown client', r.first['error']
452
+ assert_equal 'handshake', r.first['advice']['reconnect']
454
453
  end
455
454
 
456
455
  id = get_client_id
457
456
  @server.send :handle, 'channel' => '/meta/subscribe', 'subscription' => '/foo', 'clientId' => id do |r|
458
- assert_equal true, r['successful']
459
- assert_equal 'retry', r['advice']['reconnect']
460
- assert_equal 1000, r['advice']['interval']
457
+ assert_equal true, r.first['successful']
458
+ assert_equal 'retry', r.first['advice']['reconnect']
459
+ assert_equal 0, r.first['advice']['interval']
460
+ assert_equal 60000, r.first['advice']['timeout']
461
461
  end
462
462
  end
463
463
  end