faye 0.3.4 → 0.5.0

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.

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
@@ -1,42 +1,54 @@
1
1
  module Faye
2
2
  class Connection
3
3
  include EventMachine::Deferrable
4
- include Observable
4
+ include Publisher
5
5
  include Timeouts
6
6
 
7
7
  MAX_DELAY = 0.1
8
- INTERVAL = 1.0
8
+ INTERVAL = 0.0
9
9
  TIMEOUT = 60.0
10
10
 
11
- attr_reader :id
11
+ attr_reader :id, :interval, :timeout
12
12
 
13
13
  def initialize(id, options = {})
14
14
  @id = id
15
15
  @options = options
16
+ @interval = @options[:interval] || INTERVAL
16
17
  @timeout = @options[:timeout] || TIMEOUT
17
18
  @channels = Set.new
18
19
  @inbox = Set.new
19
20
  @connected = false
21
+
22
+ begin_deletion_timeout
20
23
  end
21
24
 
22
- def update(message, event)
23
- return unless message == :message
24
- @inbox.add(event)
25
- begin_delivery_timeout!
25
+ def socket=(socket)
26
+ @connected = true
27
+ @socket = socket
28
+ end
29
+
30
+ def on_message(event)
31
+ return unless @inbox.add?(event)
32
+ @socket.send(JSON.unparse(event)) if @socket
33
+ begin_delivery_timeout
26
34
  end
27
35
 
28
36
  def subscribe(channel)
29
- channel.add_observer(self) if @channels.add?(channel)
37
+ return unless @channels.add?(channel)
38
+ channel.add_subscriber(:message, method(:on_message))
30
39
  end
31
40
 
32
41
  def unsubscribe(channel)
33
42
  return @channels.each(&method(:unsubscribe)) if channel == :all
34
43
  return unless @channels.member?(channel)
35
44
  @channels.delete(channel)
36
- channel.delete_observer(self)
45
+ channel.remove_subscriber(:message, method(:on_message))
37
46
  end
38
47
 
39
- def connect(&block)
48
+ def connect(options, &block)
49
+ options = options || {}
50
+ timeout = options['timeout'] ? options['timeout'] / 1000.0 : @timeout
51
+
40
52
  set_deferred_status(:deferred)
41
53
 
42
54
  callback(&block)
@@ -45,8 +57,8 @@ module Faye
45
57
  @connected = true
46
58
  remove_timeout(:deletion)
47
59
 
48
- begin_delivery_timeout!
49
- begin_connection_timeout!
60
+ begin_delivery_timeout
61
+ begin_connection_timeout(timeout)
50
62
  end
51
63
 
52
64
  def flush!
@@ -67,24 +79,30 @@ module Faye
67
79
 
68
80
  private
69
81
 
70
- def begin_delivery_timeout!
82
+ def release_connection!
83
+ return if @socket
84
+
85
+ remove_timeout(:connection)
86
+ remove_timeout(:delivery)
87
+ @connected = false
88
+
89
+ begin_deletion_timeout
90
+ end
91
+
92
+ def begin_delivery_timeout
71
93
  return unless @connected and not @inbox.empty?
72
94
  add_timeout(:delivery, MAX_DELAY) { flush! }
73
95
  end
74
96
 
75
- def begin_connection_timeout!
97
+ def begin_connection_timeout(timeout)
76
98
  return unless @connected
77
- add_timeout(:connection, @timeout) { flush! }
99
+ add_timeout(:connection, timeout) { flush! }
78
100
  end
79
101
 
80
- def release_connection!
81
- remove_timeout(:connection)
82
- remove_timeout(:delivery)
83
- @connected = false
84
-
85
- add_timeout(:deletion, 10 * INTERVAL) do
86
- changed(true)
87
- notify_observers(:stale_connection, self)
102
+ def begin_deletion_timeout
103
+ return if @connected
104
+ add_timeout(:deletion, TIMEOUT + 10 * @timeout) do
105
+ publish_event(:stale_connection, self)
88
106
  end
89
107
  end
90
108
 
@@ -0,0 +1,47 @@
1
+ module Faye
2
+ module Extensible
3
+
4
+ def add_extension(extension)
5
+ @extensions ||= []
6
+ @extensions << extension
7
+ extension.added if extension.respond_to?(:added)
8
+ end
9
+
10
+ def remove_extension(extension)
11
+ return unless @extensions
12
+ @extensions.delete_if do |ext|
13
+ if ext == extension
14
+ extension.removed if extension.respond_to?(:removed)
15
+ true
16
+ else
17
+ false
18
+ end
19
+ end
20
+ end
21
+
22
+ def pipe_through_extensions(stage, message, &callback)
23
+ return callback.call(message) unless @extensions
24
+ extensions = @extensions.dup
25
+
26
+ pipe = lambda do |message|
27
+ if !message
28
+ callback.call(message)
29
+ else
30
+ extension = extensions.shift
31
+ if (!extension)
32
+ callback.call(message)
33
+ else
34
+ if extension.respond_to?(stage)
35
+ extension.__send__(stage, message, pipe)
36
+ else
37
+ pipe.call(message)
38
+ end
39
+ end
40
+ end
41
+ end
42
+ pipe.call(message)
43
+ end
44
+
45
+ end
46
+ end
47
+
@@ -2,6 +2,7 @@ module Faye
2
2
  class Server
3
3
 
4
4
  include Logging
5
+ include Extensible
5
6
 
6
7
  def initialize(options = {})
7
8
  info('New server created')
@@ -11,28 +12,41 @@ module Faye
11
12
  @namespace = Namespace.new
12
13
  end
13
14
 
14
- # Notifies the server of stale connections that should be deleted
15
- def update(message, connection)
16
- return unless message == :stale_connection
17
- destroy_connection(connection)
18
- end
19
-
20
15
  def client_ids
21
16
  @connections.keys
22
17
  end
23
18
 
24
- def process(messages, local = false, &callback)
19
+ def process(messages, local_or_remote = false, &callback)
20
+ socket = local_or_remote.is_a?(WebSocket) ? local_or_remote : nil
21
+ local = (local_or_remote == true)
22
+
25
23
  debug('Processing messages from ? client', local ? 'LOCAL' : 'REMOTE')
26
24
 
27
25
  messages = [messages].flatten
28
26
  processed, responses = 0, []
29
27
 
28
+ gather_replies = lambda do |replies|
29
+ responses.concat(replies)
30
+ processed += 1
31
+ callback.call(responses.compact) if processed == messages.size
32
+ end
33
+
34
+ handle_reply = lambda do |replies|
35
+ extended, expected = 0, replies.size
36
+ gather_replies.call(replies) if expected == 0
37
+
38
+ replies.each_with_index do |reply, i|
39
+ pipe_through_extensions(:outgoing, reply) do |message|
40
+ replies[i] = message
41
+ extended += 1
42
+ gather_replies.call(replies) if extended == expected
43
+ end
44
+ end
45
+ end
46
+
30
47
  messages.each do |message|
31
- handle(message, local) do |reply|
32
- reply = [reply].flatten
33
- responses.concat(reply)
34
- processed += 1
35
- callback[responses] if processed == messages.size
48
+ pipe_through_extensions(:incoming, message) do |piped_message|
49
+ handle(piped_message, socket, local, &handle_reply)
36
50
  end
37
51
  end
38
52
  end
@@ -49,59 +63,93 @@ module Faye
49
63
  def connection(id)
50
64
  return @connections[id] if @connections.has_key?(id)
51
65
  connection = Connection.new(id, @options)
52
- connection.add_observer(self)
66
+ connection.add_subscriber(:stale_connection, method(:destroy_connection))
53
67
  @connections[id] = connection
54
68
  end
55
69
 
56
70
  def destroy_connection(connection)
57
71
  connection.disconnect!
58
- connection.delete_observer(self)
72
+ connection.remove_subscriber(:stale_connection, method(:destroy_connection))
59
73
  @connections.delete(connection.id)
60
74
  end
61
75
 
62
- def handle(message, local = false, &callback)
63
- channel_name = message['channel']
64
-
65
- @channels.glob(channel_name).each do |channel|
76
+ def make_response(message)
77
+ response = {}
78
+ %w[id clientId channel error].each do |field|
79
+ if message[field]
80
+ response[field] = message[field]
81
+ end
82
+ end
83
+ response['successful'] = !response['error']
84
+ response
85
+ end
86
+
87
+ def distribute_message(message)
88
+ @channels.glob(message['channel']).each do |channel|
66
89
  channel << message
67
90
  info('Publishing message ? from client ? to ?', message['data'], message['clientId'], channel.name)
68
91
  end
92
+ end
93
+
94
+ def handle(message, socket = nil, local = false, &callback)
95
+ return callback.call([]) if !message
96
+ return callback.call([make_response(message)]) if message['error']
97
+
98
+ distribute_message(message)
99
+ channel_name = message['channel']
69
100
 
70
101
  if Channel.meta?(channel_name)
71
- response = __send__(Channel.parse(channel_name)[1], message, local)
72
-
73
- client_id = response['clientId']
74
- response['advice'] ||= {}
75
- response['advice']['reconnect'] ||= @connections.has_key?(client_id) ? 'retry' : 'handshake'
76
- response['advice']['interval'] ||= (Connection::INTERVAL * 1000).floor
77
-
78
- return callback.call(response) unless response['channel'] == Channel::CONNECT and
79
- response['successful'] == true
80
-
81
- info('Accepting connection from ?', response['clientId'])
82
-
83
- return connection(response['clientId']).connect do |events|
84
- info('Sending event messages to ?', response['clientId'])
85
- debug('Events for ?: ?', response['clientId'], events)
86
- callback.call([response] + events)
87
- end
102
+ handle_meta(message, socket, local, &callback)
103
+ elsif message['clientId'].nil?
104
+ callback.call([])
105
+ else
106
+ response = make_response(message)
107
+ response['successful'] = true
108
+ callback.call([response])
88
109
  end
110
+ end
111
+
112
+ def handle_meta(message, socket, local, &callback)
113
+ response = __send__(Channel.parse(message['channel'])[1], message, local)
89
114
 
90
- return callback.call([]) if message['clientId'].nil? or Channel.service?(channel_name)
115
+ advize(response)
91
116
 
92
- response = make_response(message)
93
- response['successful'] = true
94
- callback.call(response)
117
+ if response['channel'] == Channel::CONNECT and response['successful'] == true
118
+ return accept_connection(message['advice'], response, socket, &callback)
119
+ end
120
+
121
+ callback.call([response])
95
122
  end
96
123
 
97
- def make_response(message)
98
- response = {}
99
- %w[id clientId channel].each do |field|
100
- if message[field]
101
- response[field] = message[field]
102
- end
124
+ def accept_connection(options, response, socket, &callback)
125
+ info('Accepting connection from ?', response['clientId'])
126
+
127
+ connection = connection(response['clientId'])
128
+
129
+ # Disabled because CometD doesn't like messages not being
130
+ # delivered as part of a /meta/* response
131
+ # if socket
132
+ # return connection.socket = socket
133
+ # end
134
+
135
+ connection.connect(options) do |events|
136
+ info('Sending event messages to ?', response['clientId'])
137
+ debug('Events for ?: ?', response['clientId'], events)
138
+ callback.call([response] + events)
139
+ end
140
+ end
141
+
142
+ def advize(response)
143
+ connection = @connections[response['clientId']]
144
+
145
+ advice = response['advice'] ||= {}
146
+ if connection
147
+ advice['reconnect'] ||= 'retry'
148
+ advice['interval'] ||= (connection.interval * 1000).floor
149
+ advice['timeout'] ||= (connection.timeout * 1000).floor
150
+ else
151
+ advice['reconnect'] ||= 'handshake'
103
152
  end
104
- response
105
153
  end
106
154
 
107
155
  # MUST contain * version
@@ -142,7 +190,7 @@ module Faye
142
190
  # MAY contain * ext
143
191
  # * id
144
192
  def connect(message, local = false)
145
- response = make_response(message)
193
+ response = make_response(message)
146
194
 
147
195
  client_id = message['clientId']
148
196
  connection = client_id ? @connections[client_id] : nil
@@ -164,7 +212,7 @@ module Faye
164
212
  # MAY contain * ext
165
213
  # * id
166
214
  def disconnect(message, local = false)
167
- response = make_response(message)
215
+ response = make_response(message)
168
216
 
169
217
  client_id = message['clientId']
170
218
  connection = client_id ? @connections[client_id] : nil
@@ -192,9 +240,7 @@ module Faye
192
240
 
193
241
  client_id = message['clientId']
194
242
  connection = client_id ? @connections[client_id] : nil
195
-
196
- subscription = message['subscription']
197
- subscription = [subscription].flatten
243
+ subscription = [message['subscription']].flatten
198
244
 
199
245
  response['error'] = Error.client_unknown(client_id) if connection.nil?
200
246
  response['error'] = Error.parameter_missing('clientId') if client_id.nil?
@@ -227,9 +273,7 @@ module Faye
227
273
 
228
274
  client_id = message['clientId']
229
275
  connection = client_id ? @connections[client_id] : nil
230
-
231
- subscription = message['subscription']
232
- subscription = [subscription].flatten
276
+ subscription = [message['subscription']].flatten
233
277
 
234
278
  response['error'] = Error.client_unknown(client_id) if connection.nil?
235
279
  response['error'] = Error.parameter_missing('clientId') if client_id.nil?
@@ -0,0 +1,23 @@
1
+ module Faye
2
+ class Subscription
3
+
4
+ def initialize(client, channels, callback)
5
+ @client = client
6
+ @channels = channels
7
+ @callback = callback
8
+ @cancelled = false
9
+ end
10
+
11
+ def cancel
12
+ return if @cancelled
13
+ @client.unsubscribe(@channels, &@callback)
14
+ @cancelled = true
15
+ end
16
+
17
+ def unsubscribe
18
+ cancel
19
+ end
20
+
21
+ end
22
+ end
23
+
@@ -0,0 +1,119 @@
1
+ module Faye
2
+ class WebSocket
3
+
4
+ include Publisher
5
+
6
+ CONNECTING = 0
7
+ OPEN = 1
8
+ CLOSING = 2
9
+ CLOSED = 3
10
+
11
+ attr_reader :url, :ready_state, :buffered_amount
12
+ attr_accessor :onopen, :onmessage, :onerror, :onclose
13
+
14
+ def initialize(request)
15
+ @request = request
16
+ @callback = @request.env['async.callback']
17
+ @stream = Stream.new
18
+ @callback.call [200, RackAdapter::TYPE_JSON, @stream]
19
+
20
+ @url = @request.env['websocket.url']
21
+ @ready_state = OPEN
22
+ @buffered_amount = 0
23
+
24
+ event = Event.new
25
+ event.init_event('open', false, false)
26
+ dispatch_event(event)
27
+
28
+ @buffer = []
29
+ @buffering = false
30
+
31
+ @request.env[Thin::Request::WEBSOCKET_RECEIVE_CALLBACK] = lambda do |data|
32
+ data.each_char(&method(:handle_char))
33
+ end
34
+ end
35
+
36
+ def send(data)
37
+ @stream.write("\x00#{ data }\xFF")
38
+ end
39
+
40
+ def close
41
+ end
42
+
43
+ def add_event_listener(type, listener, use_capture)
44
+ add_subscriber(type, listener)
45
+ end
46
+
47
+ def remove_event_listener(type, listener, use_capture)
48
+ remove_subscriber(type, listener)
49
+ end
50
+
51
+ def dispatch_event(event)
52
+ event.target = event.current_target = self
53
+ event.event_phase = Event::AT_TARGET
54
+
55
+ publish_event(event.type, event)
56
+ callback = __send__("on#{ event.type }")
57
+ callback.call(event) if callback
58
+ end
59
+
60
+ private
61
+
62
+ def handle_char(data)
63
+ case data
64
+ when "\x00" then
65
+ @buffering = true
66
+
67
+ when "\xFF" then
68
+ event = Event.new
69
+ event.init_event('message', false, false)
70
+ event.data = @buffer.join('')
71
+
72
+ dispatch_event(event)
73
+
74
+ @buffer = []
75
+ @buffering = false
76
+
77
+ else
78
+ @buffer.push(data) if @buffering
79
+ end
80
+ end
81
+
82
+ end
83
+
84
+ class WebSocket::Stream
85
+ include EventMachine::Deferrable
86
+
87
+ def each(&callback)
88
+ @data_callback = callback
89
+ end
90
+
91
+ def write(data)
92
+ return unless @data_callback
93
+ @data_callback.call(data)
94
+ end
95
+ end
96
+
97
+ class WebSocket::Event
98
+ attr_reader :type, :bubbles, :cancelable
99
+ attr_accessor :target, :current_target, :event_phase, :data
100
+
101
+ CAPTURING_PHASE = 1
102
+ AT_TARGET = 2
103
+ BUBBLING_PHASE = 3
104
+
105
+ def init_event(event_type, can_bubble, cancelable)
106
+ @type = event_type
107
+ @bubbles = can_bubble
108
+ @cancelable = cancelable
109
+ end
110
+
111
+ def stop_propagation
112
+ end
113
+
114
+ def prevent_default
115
+ end
116
+ end
117
+
118
+ end
119
+