faye-huboard 1.0.4

Sign up to get free protection for your applications and to get access to all the features.
@@ -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