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.
- data/History.txt +13 -0
- data/Manifest.txt +16 -33
- data/README.txt +9 -274
- data/Rakefile +4 -4
- data/lib/faye-browser-min.js +1 -0
- data/lib/faye.rb +26 -9
- data/lib/faye/{rack_adapter.rb → adapters/rack_adapter.rb} +38 -25
- data/lib/faye/error.rb +0 -7
- data/lib/faye/{logging.rb → mixins/logging.rb} +0 -0
- data/lib/faye/mixins/publisher.rb +29 -0
- data/lib/faye/{timeouts.rb → mixins/timeouts.rb} +1 -0
- data/lib/faye/{transport.rb → network/transport.rb} +32 -43
- data/lib/faye/{channel.rb → protocol/channel.rb} +23 -3
- data/lib/faye/{client.rb → protocol/client.rb} +124 -90
- data/lib/faye/{connection.rb → protocol/connection.rb} +41 -23
- data/lib/faye/protocol/extensible.rb +47 -0
- data/lib/faye/{grammar.rb → protocol/grammar.rb} +0 -0
- data/lib/faye/{server.rb → protocol/server.rb} +98 -54
- data/lib/faye/protocol/subscription.rb +23 -0
- data/lib/faye/{namespace.rb → util/namespace.rb} +0 -0
- data/lib/faye/util/web_socket.rb +119 -0
- data/lib/thin_extensions.rb +86 -0
- data/test/scenario.rb +68 -14
- data/test/test_clients.rb +215 -2
- data/test/test_server.rb +10 -10
- metadata +102 -71
- data/Jakefile +0 -13
- data/build/faye-client-min.js +0 -1
- data/build/faye.js +0 -1488
- data/examples/README.rdoc +0 -41
- data/examples/node/app.js +0 -26
- data/examples/node/client.js +0 -23
- data/examples/node/faye-client-min.js +0 -1
- data/examples/node/faye.js +0 -1488
- data/examples/rack/app.rb +0 -16
- data/examples/rack/client.rb +0 -25
- data/examples/rack/config.ru +0 -8
- data/examples/shared/public/favicon.ico +0 -0
- data/examples/shared/public/index.html +0 -49
- data/examples/shared/public/jquery.js +0 -19
- data/examples/shared/public/mootools.js +0 -4329
- data/examples/shared/public/prototype.js +0 -4874
- data/examples/shared/public/robots.txt +0 -0
- data/examples/shared/public/soapbox.js +0 -100
- data/examples/shared/public/style.css +0 -43
- data/jake.yml +0 -40
- data/lib/faye-client-min.js +0 -1
- data/test/scenario.js +0 -138
- 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
|
4
|
+
include Publisher
|
5
5
|
include Timeouts
|
6
6
|
|
7
7
|
MAX_DELAY = 0.1
|
8
|
-
INTERVAL =
|
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
|
23
|
-
|
24
|
-
@
|
25
|
-
|
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
|
-
|
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.
|
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
|
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,
|
99
|
+
add_timeout(:connection, timeout) { flush! }
|
78
100
|
end
|
79
101
|
|
80
|
-
def
|
81
|
-
|
82
|
-
|
83
|
-
|
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
|
+
|
File without changes
|
@@ -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,
|
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
|
-
|
32
|
-
|
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.
|
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.
|
72
|
+
connection.remove_subscriber(:stale_connection, method(:destroy_connection))
|
59
73
|
@connections.delete(connection.id)
|
60
74
|
end
|
61
75
|
|
62
|
-
def
|
63
|
-
|
64
|
-
|
65
|
-
|
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
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
response
|
76
|
-
response['
|
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
|
-
|
115
|
+
advize(response)
|
91
116
|
|
92
|
-
response
|
93
|
-
|
94
|
-
|
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
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
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
|
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
|
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
|
+
|
File without changes
|
@@ -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
|
+
|