faye-huboard 1.0.4
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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +351 -0
- data/README.md +35 -0
- data/lib/faye-browser-min.js +3 -0
- data/lib/faye-browser-min.js.map +1 -0
- data/lib/faye-browser.js +2659 -0
- data/lib/faye.rb +127 -0
- data/lib/faye/adapters/rack_adapter.rb +248 -0
- data/lib/faye/adapters/static_server.rb +56 -0
- data/lib/faye/engines/connection.rb +58 -0
- data/lib/faye/engines/memory.rb +121 -0
- data/lib/faye/engines/proxy.rb +126 -0
- data/lib/faye/error.rb +48 -0
- data/lib/faye/mixins/deferrable.rb +14 -0
- data/lib/faye/mixins/logging.rb +35 -0
- data/lib/faye/mixins/publisher.rb +18 -0
- data/lib/faye/mixins/timeouts.rb +26 -0
- data/lib/faye/protocol/channel.rb +123 -0
- data/lib/faye/protocol/client.rb +334 -0
- data/lib/faye/protocol/dispatcher.rb +146 -0
- data/lib/faye/protocol/extensible.rb +45 -0
- data/lib/faye/protocol/grammar.rb +57 -0
- data/lib/faye/protocol/publication.rb +5 -0
- data/lib/faye/protocol/server.rb +291 -0
- data/lib/faye/protocol/socket.rb +24 -0
- data/lib/faye/protocol/subscription.rb +23 -0
- data/lib/faye/transport/http.rb +69 -0
- data/lib/faye/transport/local.rb +21 -0
- data/lib/faye/transport/transport.rb +155 -0
- data/lib/faye/transport/web_socket.rb +134 -0
- data/lib/faye/util/namespace.rb +19 -0
- metadata +400 -0
@@ -0,0 +1,334 @@
|
|
1
|
+
module Faye
|
2
|
+
class Client
|
3
|
+
|
4
|
+
include Deferrable
|
5
|
+
include Publisher
|
6
|
+
include Logging
|
7
|
+
include Extensible
|
8
|
+
|
9
|
+
UNCONNECTED = 1
|
10
|
+
CONNECTING = 2
|
11
|
+
CONNECTED = 3
|
12
|
+
DISCONNECTED = 4
|
13
|
+
|
14
|
+
HANDSHAKE = 'handshake'
|
15
|
+
RETRY = 'retry'
|
16
|
+
NONE = 'none'
|
17
|
+
|
18
|
+
CONNECTION_TIMEOUT = 60.0
|
19
|
+
|
20
|
+
extend Forwardable
|
21
|
+
def_delegators :@dispatcher, :disable, :set_header
|
22
|
+
|
23
|
+
def initialize(endpoint = nil, options = {})
|
24
|
+
super()
|
25
|
+
info('New client created for ?', endpoint)
|
26
|
+
|
27
|
+
@endpoint = endpoint || RackAdapter::DEFAULT_ENDPOINT
|
28
|
+
@channels = Channel::Set.new
|
29
|
+
@dispatcher = Dispatcher.new(self, @endpoint, options)
|
30
|
+
|
31
|
+
@message_id = 0
|
32
|
+
@state = UNCONNECTED
|
33
|
+
|
34
|
+
@response_callbacks = {}
|
35
|
+
|
36
|
+
@advice = {
|
37
|
+
'reconnect' => RETRY,
|
38
|
+
'interval' => 1000.0 * (options[:interval] || Engine::INTERVAL),
|
39
|
+
'timeout' => 1000.0 * (options[:timeout] || CONNECTION_TIMEOUT)
|
40
|
+
}
|
41
|
+
@dispatcher.timeout = @advice['timeout'] / 1000.0
|
42
|
+
|
43
|
+
@dispatcher.bind(:message, &method(:receive_message))
|
44
|
+
end
|
45
|
+
|
46
|
+
# Request
|
47
|
+
# MUST include: * channel
|
48
|
+
# * version
|
49
|
+
# * supportedConnectionTypes
|
50
|
+
# MAY include: * minimumVersion
|
51
|
+
# * ext
|
52
|
+
# * id
|
53
|
+
#
|
54
|
+
# Success Response Failed Response
|
55
|
+
# MUST include: * channel MUST include: * channel
|
56
|
+
# * version * successful
|
57
|
+
# * supportedConnectionTypes * error
|
58
|
+
# * clientId MAY include: * supportedConnectionTypes
|
59
|
+
# * successful * advice
|
60
|
+
# MAY include: * minimumVersion * version
|
61
|
+
# * advice * minimumVersion
|
62
|
+
# * ext * ext
|
63
|
+
# * id * id
|
64
|
+
# * authSuccessful
|
65
|
+
def handshake(&block)
|
66
|
+
return if @advice['reconnect'] == NONE
|
67
|
+
return if @state != UNCONNECTED
|
68
|
+
|
69
|
+
@state = CONNECTING
|
70
|
+
|
71
|
+
info('Initiating handshake with ?', @endpoint)
|
72
|
+
@dispatcher.select_transport(MANDATORY_CONNECTION_TYPES)
|
73
|
+
|
74
|
+
send_message({
|
75
|
+
'channel' => Channel::HANDSHAKE,
|
76
|
+
'version' => BAYEUX_VERSION,
|
77
|
+
'supportedConnectionTypes' => [@dispatcher.connection_type]
|
78
|
+
|
79
|
+
}, {}) do |response|
|
80
|
+
|
81
|
+
if response['successful']
|
82
|
+
@state = CONNECTED
|
83
|
+
@dispatcher.client_id = response['clientId']
|
84
|
+
|
85
|
+
@dispatcher.select_transport(response['supportedConnectionTypes'])
|
86
|
+
|
87
|
+
info('Handshake successful: ?', @dispatcher.client_id)
|
88
|
+
|
89
|
+
subscribe(@channels.keys, true)
|
90
|
+
block.call if block_given?
|
91
|
+
|
92
|
+
else
|
93
|
+
info('Handshake unsuccessful')
|
94
|
+
EventMachine.add_timer(@dispatcher.retry) { handshake(&block) }
|
95
|
+
@state = UNCONNECTED
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
# Request Response
|
101
|
+
# MUST include: * channel MUST include: * channel
|
102
|
+
# * clientId * successful
|
103
|
+
# * connectionType * clientId
|
104
|
+
# MAY include: * ext MAY include: * error
|
105
|
+
# * id * advice
|
106
|
+
# * ext
|
107
|
+
# * id
|
108
|
+
# * timestamp
|
109
|
+
def connect(&block)
|
110
|
+
return if @advice['reconnect'] == NONE or
|
111
|
+
@state == DISCONNECTED
|
112
|
+
|
113
|
+
return handshake { connect(&block) } if @state == UNCONNECTED
|
114
|
+
|
115
|
+
callback(&block)
|
116
|
+
return unless @state == CONNECTED
|
117
|
+
|
118
|
+
info('Calling deferred actions for ?', @dispatcher.client_id)
|
119
|
+
set_deferred_status(:succeeded)
|
120
|
+
set_deferred_status(:unknown)
|
121
|
+
|
122
|
+
return unless @connect_request.nil?
|
123
|
+
@connect_request = true
|
124
|
+
|
125
|
+
info('Initiating connection for ?', @dispatcher.client_id)
|
126
|
+
|
127
|
+
send_message({
|
128
|
+
'channel' => Channel::CONNECT,
|
129
|
+
'clientId' => @dispatcher.client_id,
|
130
|
+
'connectionType' => @dispatcher.connection_type
|
131
|
+
|
132
|
+
}, {}) do
|
133
|
+
cycle_connection
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
# Request Response
|
138
|
+
# MUST include: * channel MUST include: * channel
|
139
|
+
# * clientId * successful
|
140
|
+
# MAY include: * ext * clientId
|
141
|
+
# * id MAY include: * error
|
142
|
+
# * ext
|
143
|
+
# * id
|
144
|
+
def disconnect
|
145
|
+
return unless @state == CONNECTED
|
146
|
+
@state = DISCONNECTED
|
147
|
+
|
148
|
+
info('Disconnecting ?', @dispatcher.client_id)
|
149
|
+
|
150
|
+
send_message({
|
151
|
+
'channel' => Channel::DISCONNECT,
|
152
|
+
'clientId' => @dispatcher.client_id
|
153
|
+
|
154
|
+
}, {}) do |response|
|
155
|
+
@dispatcher.close if response['successful']
|
156
|
+
end
|
157
|
+
|
158
|
+
info('Clearing channel listeners for ?', @dispatcher.client_id)
|
159
|
+
@channels = Channel::Set.new
|
160
|
+
end
|
161
|
+
|
162
|
+
# Request Response
|
163
|
+
# MUST include: * channel MUST include: * channel
|
164
|
+
# * clientId * successful
|
165
|
+
# * subscription * clientId
|
166
|
+
# MAY include: * ext * subscription
|
167
|
+
# * id MAY include: * error
|
168
|
+
# * advice
|
169
|
+
# * ext
|
170
|
+
# * id
|
171
|
+
# * timestamp
|
172
|
+
def subscribe(channel, force = false, &block)
|
173
|
+
if Array === channel
|
174
|
+
return channel.map { |c| subscribe(c, force, &block) }
|
175
|
+
end
|
176
|
+
|
177
|
+
subscription = Subscription.new(self, channel, block)
|
178
|
+
has_subscribe = @channels.has_subscription?(channel)
|
179
|
+
|
180
|
+
if has_subscribe and not force
|
181
|
+
@channels.subscribe([channel], block)
|
182
|
+
subscription.set_deferred_status(:succeeded)
|
183
|
+
return subscription
|
184
|
+
end
|
185
|
+
|
186
|
+
connect {
|
187
|
+
info('Client ? attempting to subscribe to ?', @dispatcher.client_id, channel)
|
188
|
+
@channels.subscribe([channel], block) unless force
|
189
|
+
|
190
|
+
send_message({
|
191
|
+
'channel' => Channel::SUBSCRIBE,
|
192
|
+
'clientId' => @dispatcher.client_id,
|
193
|
+
'subscription' => channel
|
194
|
+
|
195
|
+
}, {}) do |response|
|
196
|
+
unless response['successful']
|
197
|
+
subscription.set_deferred_status(:failed, Error.parse(response['error']))
|
198
|
+
next @channels.unsubscribe(channel, block)
|
199
|
+
end
|
200
|
+
|
201
|
+
channels = [response['subscription']].flatten
|
202
|
+
info('Subscription acknowledged for ? to ?', @dispatcher.client_id, channels)
|
203
|
+
subscription.set_deferred_status(:succeeded)
|
204
|
+
end
|
205
|
+
}
|
206
|
+
subscription
|
207
|
+
end
|
208
|
+
|
209
|
+
# Request Response
|
210
|
+
# MUST include: * channel MUST include: * channel
|
211
|
+
# * clientId * successful
|
212
|
+
# * subscription * clientId
|
213
|
+
# MAY include: * ext * subscription
|
214
|
+
# * id MAY include: * error
|
215
|
+
# * advice
|
216
|
+
# * ext
|
217
|
+
# * id
|
218
|
+
# * timestamp
|
219
|
+
def unsubscribe(channel, &block)
|
220
|
+
if Array === channel
|
221
|
+
return channel.map { |c| unsubscribe(c, &block) }
|
222
|
+
end
|
223
|
+
|
224
|
+
dead = @channels.unsubscribe(channel, block)
|
225
|
+
return unless dead
|
226
|
+
|
227
|
+
connect {
|
228
|
+
info('Client ? attempting to unsubscribe from ?', @dispatcher.client_id, channel)
|
229
|
+
|
230
|
+
send_message({
|
231
|
+
'channel' => Channel::UNSUBSCRIBE,
|
232
|
+
'clientId' => @dispatcher.client_id,
|
233
|
+
'subscription' => channel
|
234
|
+
|
235
|
+
}, {}) do |response|
|
236
|
+
next unless response['successful']
|
237
|
+
|
238
|
+
channels = [response['subscription']].flatten
|
239
|
+
info('Unsubscription acknowledged for ? from ?', @dispatcher.client_id, channels)
|
240
|
+
end
|
241
|
+
}
|
242
|
+
end
|
243
|
+
|
244
|
+
# Request Response
|
245
|
+
# MUST include: * channel MUST include: * channel
|
246
|
+
# * data * successful
|
247
|
+
# MAY include: * clientId MAY include: * id
|
248
|
+
# * id * error
|
249
|
+
# * ext * ext
|
250
|
+
def publish(channel, data, options = {})
|
251
|
+
publication = Publication.new
|
252
|
+
connect {
|
253
|
+
info('Client ? queueing published message to ?: ?', @dispatcher.client_id, channel, data)
|
254
|
+
|
255
|
+
send_message({
|
256
|
+
'channel' => channel,
|
257
|
+
'data' => data,
|
258
|
+
'clientId' => @dispatcher.client_id
|
259
|
+
|
260
|
+
}, options) do |response|
|
261
|
+
if response['successful']
|
262
|
+
publication.set_deferred_status(:succeeded)
|
263
|
+
else
|
264
|
+
publication.set_deferred_status(:failed, Error.parse(response['error']))
|
265
|
+
end
|
266
|
+
end
|
267
|
+
}
|
268
|
+
publication
|
269
|
+
end
|
270
|
+
|
271
|
+
private
|
272
|
+
|
273
|
+
def send_message(message, options, &callback)
|
274
|
+
message['id'] = generate_message_id
|
275
|
+
|
276
|
+
timeout = [nil, 0].include?(@advice['timeout']) ?
|
277
|
+
1.2 * @dispatcher.retry :
|
278
|
+
1.2 * @advice['timeout'] / 1000.0
|
279
|
+
|
280
|
+
pipe_through_extensions(:outgoing, message, nil) do |message|
|
281
|
+
next unless message
|
282
|
+
@response_callbacks[message['id']] = callback if callback
|
283
|
+
@dispatcher.send_message(message, timeout, options)
|
284
|
+
end
|
285
|
+
end
|
286
|
+
|
287
|
+
def generate_message_id
|
288
|
+
@message_id += 1
|
289
|
+
@message_id = 0 if @message_id >= 2**32
|
290
|
+
@message_id.to_s(36)
|
291
|
+
end
|
292
|
+
|
293
|
+
def receive_message(message)
|
294
|
+
id = message['id']
|
295
|
+
|
296
|
+
if message.has_key?('successful')
|
297
|
+
callback = @response_callbacks.delete(id)
|
298
|
+
end
|
299
|
+
|
300
|
+
pipe_through_extensions(:incoming, message, nil) do |message|
|
301
|
+
next unless message
|
302
|
+
handle_advice(message['advice']) if message['advice']
|
303
|
+
deliver_message(message)
|
304
|
+
callback.call(message) if callback
|
305
|
+
end
|
306
|
+
end
|
307
|
+
|
308
|
+
def handle_advice(advice)
|
309
|
+
@advice.update(advice)
|
310
|
+
@dispatcher.timeout = @advice['timeout'] / 1000.0
|
311
|
+
|
312
|
+
if @advice['reconnect'] == HANDSHAKE and @state != DISCONNECTED
|
313
|
+
@state = UNCONNECTED
|
314
|
+
@dispatcher.client_id = nil
|
315
|
+
cycle_connection
|
316
|
+
end
|
317
|
+
end
|
318
|
+
|
319
|
+
def deliver_message(message)
|
320
|
+
return unless message.has_key?('channel') and message.has_key?('data')
|
321
|
+
info('Client ? calling listeners for ? with ?', @dispatcher.client_id, message['channel'], message['data'])
|
322
|
+
@channels.distribute_message(message)
|
323
|
+
end
|
324
|
+
|
325
|
+
def cycle_connection
|
326
|
+
if @connect_request
|
327
|
+
@connect_request = nil
|
328
|
+
info('Closed connection for ?', @dispatcher.client_id)
|
329
|
+
end
|
330
|
+
EventMachine.add_timer(@advice['interval'] / 1000.0) { connect }
|
331
|
+
end
|
332
|
+
|
333
|
+
end
|
334
|
+
end
|
@@ -0,0 +1,146 @@
|
|
1
|
+
module Faye
|
2
|
+
class Dispatcher
|
3
|
+
|
4
|
+
class Envelope < Struct.new(:message, :timeout, :attempts, :deadline, :request, :timer)
|
5
|
+
end
|
6
|
+
|
7
|
+
MAX_REQUEST_SIZE = 2048
|
8
|
+
DEFAULT_RETRY = 5.0
|
9
|
+
|
10
|
+
UP = 1
|
11
|
+
DOWN = 2
|
12
|
+
|
13
|
+
include Publisher
|
14
|
+
include Logging
|
15
|
+
extend Forwardable
|
16
|
+
def_delegators :@transport, :connection_type
|
17
|
+
|
18
|
+
attr_accessor :client_id, :timeout
|
19
|
+
attr_reader :cookies, :endpoint, :headers, :max_request_size, :retry, :transports
|
20
|
+
|
21
|
+
def initialize(client, endpoint, options)
|
22
|
+
super()
|
23
|
+
|
24
|
+
@client = client
|
25
|
+
@endpoint = Faye.parse_url(endpoint)
|
26
|
+
@alternates = options[:endpoints] || {}
|
27
|
+
|
28
|
+
@cookies = CookieJar::Jar.new
|
29
|
+
@disabled = []
|
30
|
+
@envelopes = {}
|
31
|
+
@headers = {}
|
32
|
+
@retry = options[:retry] || DEFAULT_RETRY
|
33
|
+
@state = 0
|
34
|
+
@transports = {}
|
35
|
+
|
36
|
+
@alternates.each do |type, url|
|
37
|
+
@alternates[type] = Faye.parse_url(url)
|
38
|
+
end
|
39
|
+
|
40
|
+
@max_request_size = MAX_REQUEST_SIZE
|
41
|
+
end
|
42
|
+
|
43
|
+
def endpoint_for(connection_type)
|
44
|
+
@alternates[connection_type] || @endpoint
|
45
|
+
end
|
46
|
+
|
47
|
+
def disable(feature)
|
48
|
+
@disabled << feature
|
49
|
+
end
|
50
|
+
|
51
|
+
def set_header(name, value)
|
52
|
+
@headers[name.to_s] = value.to_s
|
53
|
+
end
|
54
|
+
|
55
|
+
def close
|
56
|
+
transport = @transport
|
57
|
+
@transport = nil
|
58
|
+
transport.close if transport
|
59
|
+
end
|
60
|
+
|
61
|
+
def select_transport(transport_types)
|
62
|
+
Transport.get(self, transport_types, @disabled) do |transport|
|
63
|
+
debug('Selected ? transport for ?', transport.connection_type, transport.endpoint)
|
64
|
+
|
65
|
+
next if transport == @transport
|
66
|
+
@transport.close if @transport
|
67
|
+
|
68
|
+
@transport = transport
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def send_message(message, timeout, options = {})
|
73
|
+
return unless @transport
|
74
|
+
|
75
|
+
id = message['id']
|
76
|
+
attempts = options[:attempts]
|
77
|
+
deadline = options[:deadline] && Time.now.to_f + options[:deadline]
|
78
|
+
envelope = @envelopes[id] ||= Envelope.new(message, timeout, attempts, deadline, nil, nil)
|
79
|
+
|
80
|
+
return if envelope.request or envelope.timer
|
81
|
+
|
82
|
+
if attempts_exhausted(envelope) or deadline_passed(envelope)
|
83
|
+
@envelopes.delete(id)
|
84
|
+
return
|
85
|
+
end
|
86
|
+
|
87
|
+
envelope.timer = EventMachine.add_timer(timeout) do
|
88
|
+
handle_error(message)
|
89
|
+
end
|
90
|
+
|
91
|
+
envelope.request = @transport.send_message(message)
|
92
|
+
end
|
93
|
+
|
94
|
+
def handle_response(reply)
|
95
|
+
if reply.has_key?('successful') and envelope = @envelopes.delete(reply['id'])
|
96
|
+
EventMachine.cancel_timer(envelope.timer) if envelope.timer
|
97
|
+
end
|
98
|
+
|
99
|
+
trigger(:message, reply)
|
100
|
+
|
101
|
+
return if @state == UP
|
102
|
+
@state = UP
|
103
|
+
@client.trigger('transport:up')
|
104
|
+
end
|
105
|
+
|
106
|
+
def handle_error(message, immediate = false)
|
107
|
+
return unless envelope = @envelopes[message['id']]
|
108
|
+
return unless request = envelope.request
|
109
|
+
|
110
|
+
request.callback do |req|
|
111
|
+
req.close if req.respond_to?(:close)
|
112
|
+
end
|
113
|
+
|
114
|
+
EventMachine.cancel_timer(envelope.timer)
|
115
|
+
envelope.request = envelope.timer = nil
|
116
|
+
|
117
|
+
if immediate
|
118
|
+
send_message(envelope.message, envelope.timeout)
|
119
|
+
else
|
120
|
+
envelope.timer = EventMachine.add_timer(@retry) do
|
121
|
+
envelope.timer = nil
|
122
|
+
send_message(envelope.message, envelope.timeout)
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
return if @state == DOWN
|
127
|
+
@state = DOWN
|
128
|
+
@client.trigger('transport:down')
|
129
|
+
end
|
130
|
+
|
131
|
+
private
|
132
|
+
|
133
|
+
def attempts_exhausted(envelope)
|
134
|
+
return false unless envelope.attempts
|
135
|
+
envelope.attempts -= 1
|
136
|
+
return false if envelope.attempts >= 0
|
137
|
+
return true
|
138
|
+
end
|
139
|
+
|
140
|
+
def deadline_passed(envelope)
|
141
|
+
return false unless deadline = envelope.deadline
|
142
|
+
return false if Time.now.to_f <= deadline
|
143
|
+
return true
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|