faye-ouvrages 1.1.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,344 @@
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, :add_websocket_extension, :disable, :set_header
22
+
23
+ def initialize(endpoint = nil, options = {})
24
+ ::WebSocket::Driver.validate_options(options, [:interval, :timeout, :endpoints, :proxy, :retry, :scheduler, :websocket_extensions])
25
+ super()
26
+ info('New client created for ?', endpoint)
27
+
28
+ @channels = Channel::Set.new
29
+ @dispatcher = Dispatcher.new(self, endpoint || RackAdapter::DEFAULT_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 ?', @dispatcher.endpoint.to_s)
72
+ @dispatcher.select_transport(MANDATORY_CONNECTION_TYPES)
73
+
74
+ send_message({
75
+ 'channel' => Channel::HANDSHAKE,
76
+ 'version' => BAYEUX_VERSION,
77
+ 'supportedConnectionTypes' => @dispatcher.connection_types
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
+ promise = Publication.new
150
+
151
+ send_message({
152
+ 'channel' => Channel::DISCONNECT,
153
+ 'clientId' => @dispatcher.client_id
154
+
155
+ }, {}) do |response|
156
+ if response['successful']
157
+ @dispatcher.close
158
+ promise.set_deferred_status(:succeeded)
159
+ else
160
+ promise.set_deferred_status(:failed, Error.parse(response['error']))
161
+ end
162
+ end
163
+
164
+ info('Clearing channel listeners for ?', @dispatcher.client_id)
165
+ @channels = Channel::Set.new
166
+
167
+ promise
168
+ end
169
+
170
+ # Request Response
171
+ # MUST include: * channel MUST include: * channel
172
+ # * clientId * successful
173
+ # * subscription * clientId
174
+ # MAY include: * ext * subscription
175
+ # * id MAY include: * error
176
+ # * advice
177
+ # * ext
178
+ # * id
179
+ # * timestamp
180
+ def subscribe(channel, force = false, &block)
181
+ if Array === channel
182
+ return channel.map { |c| subscribe(c, force, &block) }
183
+ end
184
+
185
+ subscription = Subscription.new(self, channel, block)
186
+ has_subscribe = @channels.has_subscription?(channel)
187
+
188
+ if has_subscribe and not force
189
+ @channels.subscribe([channel], block)
190
+ subscription.set_deferred_status(:succeeded)
191
+ return subscription
192
+ end
193
+
194
+ connect {
195
+ info('Client ? attempting to subscribe to ?', @dispatcher.client_id, channel)
196
+ @channels.subscribe([channel], block) unless force
197
+
198
+ send_message({
199
+ 'channel' => Channel::SUBSCRIBE,
200
+ 'clientId' => @dispatcher.client_id,
201
+ 'subscription' => channel
202
+
203
+ }, {}) do |response|
204
+ unless response['successful']
205
+ subscription.set_deferred_status(:failed, Error.parse(response['error']))
206
+ next @channels.unsubscribe(channel, block)
207
+ end
208
+
209
+ channels = [response['subscription']].flatten
210
+ info('Subscription acknowledged for ? to ?', @dispatcher.client_id, channels)
211
+ subscription.set_deferred_status(:succeeded)
212
+ end
213
+ }
214
+ subscription
215
+ end
216
+
217
+ # Request Response
218
+ # MUST include: * channel MUST include: * channel
219
+ # * clientId * successful
220
+ # * subscription * clientId
221
+ # MAY include: * ext * subscription
222
+ # * id MAY include: * error
223
+ # * advice
224
+ # * ext
225
+ # * id
226
+ # * timestamp
227
+ def unsubscribe(channel, &block)
228
+ if Array === channel
229
+ return channel.map { |c| unsubscribe(c, &block) }
230
+ end
231
+
232
+ dead = @channels.unsubscribe(channel, block)
233
+ return unless dead
234
+
235
+ connect {
236
+ info('Client ? attempting to unsubscribe from ?', @dispatcher.client_id, channel)
237
+
238
+ send_message({
239
+ 'channel' => Channel::UNSUBSCRIBE,
240
+ 'clientId' => @dispatcher.client_id,
241
+ 'subscription' => channel
242
+
243
+ }, {}) do |response|
244
+ next unless response['successful']
245
+
246
+ channels = [response['subscription']].flatten
247
+ info('Unsubscription acknowledged for ? from ?', @dispatcher.client_id, channels)
248
+ end
249
+ }
250
+ end
251
+
252
+ # Request Response
253
+ # MUST include: * channel MUST include: * channel
254
+ # * data * successful
255
+ # MAY include: * clientId MAY include: * id
256
+ # * id * error
257
+ # * ext * ext
258
+ def publish(channel, data, options = {})
259
+ ::WebSocket::Driver.validate_options(options, [:attempts, :deadline])
260
+
261
+ publication = Publication.new
262
+ connect {
263
+ info('Client ? queueing published message to ?: ?', @dispatcher.client_id, channel, data)
264
+
265
+ send_message({
266
+ 'channel' => channel,
267
+ 'data' => data,
268
+ 'clientId' => @dispatcher.client_id
269
+
270
+ }, options) do |response|
271
+ if response['successful']
272
+ publication.set_deferred_status(:succeeded)
273
+ else
274
+ publication.set_deferred_status(:failed, Error.parse(response['error']))
275
+ end
276
+ end
277
+ }
278
+ publication
279
+ end
280
+
281
+ private
282
+
283
+ def send_message(message, options, &callback)
284
+ message['id'] = generate_message_id
285
+
286
+ timeout = [nil, 0].include?(@advice['timeout']) ?
287
+ 1.2 * @dispatcher.retry :
288
+ 1.2 * @advice['timeout'] / 1000.0
289
+
290
+ pipe_through_extensions(:outgoing, message, nil) do |message|
291
+ next unless message
292
+ @response_callbacks[message['id']] = callback if callback
293
+ @dispatcher.send_message(message, timeout, options)
294
+ end
295
+ end
296
+
297
+ def generate_message_id
298
+ @message_id += 1
299
+ @message_id = 0 if @message_id >= 2**32
300
+ @message_id.to_s(36)
301
+ end
302
+
303
+ def receive_message(message)
304
+ id = message['id']
305
+
306
+ if message.has_key?('successful')
307
+ callback = @response_callbacks.delete(id)
308
+ end
309
+
310
+ pipe_through_extensions(:incoming, message, nil) do |message|
311
+ next unless message
312
+ handle_advice(message['advice']) if message['advice']
313
+ deliver_message(message)
314
+ callback.call(message) if callback
315
+ end
316
+ end
317
+
318
+ def handle_advice(advice)
319
+ @advice.update(advice)
320
+ @dispatcher.timeout = @advice['timeout'] / 1000.0
321
+
322
+ if @advice['reconnect'] == HANDSHAKE and @state != DISCONNECTED
323
+ @state = UNCONNECTED
324
+ @dispatcher.client_id = nil
325
+ cycle_connection
326
+ end
327
+ end
328
+
329
+ def deliver_message(message)
330
+ return unless message.has_key?('channel') and message.has_key?('data')
331
+ info('Client ? calling listeners for ? with ?', @dispatcher.client_id, message['channel'], message['data'])
332
+ @channels.distribute_message(message)
333
+ end
334
+
335
+ def cycle_connection
336
+ if @connect_request
337
+ @connect_request = nil
338
+ info('Closed connection for ?', @dispatcher.client_id)
339
+ end
340
+ EventMachine.add_timer(@advice['interval'] / 1000.0) { connect }
341
+ end
342
+
343
+ end
344
+ end
@@ -0,0 +1,169 @@
1
+ module Faye
2
+ class Dispatcher
3
+
4
+ class Envelope < Struct.new(:message, :scheduler, :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, :proxy, :retry, :transports, :ws_extensions
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
+ @scheduler = options[:scheduler] || Faye::Scheduler
34
+ @state = 0
35
+ @transports = {}
36
+ @ws_extensions = []
37
+
38
+ @proxy = options[:proxy] || {}
39
+ @proxy = {:origin => @proxy} if String === @proxy
40
+
41
+ [*options[:websocket_extensions]].each do |extension|
42
+ add_websocket_extension(extension)
43
+ end
44
+
45
+ @alternates.each do |type, url|
46
+ @alternates[type] = Faye.parse_url(url)
47
+ end
48
+
49
+ @max_request_size = MAX_REQUEST_SIZE
50
+ end
51
+
52
+ def endpoint_for(connection_type)
53
+ @alternates[connection_type] || @endpoint
54
+ end
55
+
56
+ def add_websocket_extension(extension)
57
+ @ws_extensions << extension
58
+ end
59
+
60
+ def disable(feature)
61
+ @disabled << feature
62
+ end
63
+
64
+ def set_header(name, value)
65
+ @headers[name.to_s] = value.to_s
66
+ end
67
+
68
+ def close
69
+ transport = @transport
70
+ @transport = nil
71
+ transport.close if transport
72
+ end
73
+
74
+ def connection_types
75
+ Transport.connection_types
76
+ end
77
+
78
+ def select_transport(transport_types)
79
+ Transport.get(self, transport_types, @disabled) do |transport|
80
+ debug('Selected ? transport for ?', transport.connection_type, transport.endpoint)
81
+
82
+ next if transport == @transport
83
+ @transport.close if @transport
84
+
85
+ @transport = transport
86
+ end
87
+ end
88
+
89
+ def send_message(message, timeout, options = {})
90
+ id = message['id']
91
+ attempts = options[:attempts]
92
+ deadline = options[:deadline] && Time.now.to_f + options[:deadline]
93
+ envelope = @envelopes[id]
94
+
95
+ unless envelope
96
+ scheduler = @scheduler.new(message, :timeout => timeout, :interval => @retry, :attempts => attempts, :deadline => deadline)
97
+ envelope = @envelopes[id] = Envelope.new(message, scheduler, nil, nil)
98
+ end
99
+
100
+ send_envelope(envelope)
101
+ end
102
+
103
+ def send_envelope(envelope)
104
+ return unless @transport
105
+ return if envelope.request or envelope.timer
106
+
107
+ message = envelope.message
108
+ scheduler = envelope.scheduler
109
+
110
+ unless scheduler.deliverable?
111
+ scheduler.abort!
112
+ @envelopes.delete(message['id'])
113
+ return
114
+ end
115
+
116
+ envelope.timer = EventMachine.add_timer(scheduler.timeout) do
117
+ handle_error(message)
118
+ end
119
+
120
+ scheduler.send!
121
+ envelope.request = @transport.send_message(message)
122
+ end
123
+ private :send_envelope
124
+
125
+ def handle_response(reply)
126
+ envelope = @envelopes.delete(reply['id'])
127
+
128
+ if reply.has_key?('successful') and envelope
129
+ envelope.scheduler.succeed!
130
+ EventMachine.cancel_timer(envelope.timer) if envelope.timer
131
+ end
132
+
133
+ trigger(:message, reply)
134
+
135
+ return if @state == UP
136
+ @state = UP
137
+ @client.trigger('transport:up')
138
+ end
139
+
140
+ def handle_error(message, immediate = false)
141
+ return unless envelope = @envelopes[message['id']]
142
+ return unless request = envelope.request
143
+
144
+ request.callback do |req|
145
+ req.close if req.respond_to?(:close)
146
+ end
147
+
148
+ scheduler = envelope.scheduler
149
+ scheduler.fail!
150
+
151
+ EventMachine.cancel_timer(envelope.timer)
152
+ envelope.request = envelope.timer = nil
153
+
154
+ if immediate
155
+ send_envelope(envelope)
156
+ else
157
+ envelope.timer = EventMachine.add_timer(scheduler.interval) do
158
+ envelope.timer = nil
159
+ send_envelope(envelope)
160
+ end
161
+ end
162
+
163
+ return if @state == DOWN
164
+ @state = DOWN
165
+ @client.trigger('transport:down')
166
+ end
167
+
168
+ end
169
+ end