faye 0.6.8 → 0.7.0
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of faye might be problematic. Click here for more details.
- data/History.txt +10 -3
- data/README.rdoc +1 -2
- data/lib/faye-browser-min.js +1 -1
- data/lib/faye.rb +89 -32
- data/lib/faye/adapters/rack_adapter.rb +20 -26
- data/lib/faye/engines/base.rb +5 -0
- data/lib/faye/engines/memory.rb +9 -3
- data/lib/faye/engines/redis.rb +26 -11
- data/lib/faye/mixins/publisher.rb +4 -8
- data/lib/faye/protocol/channel.rb +8 -8
- data/lib/faye/protocol/client.rb +45 -4
- data/lib/faye/protocol/publication.rb +5 -0
- data/lib/faye/protocol/server.rb +10 -19
- data/lib/faye/thin_extensions.rb +1 -1
- data/lib/faye/transport/http.rb +17 -8
- data/lib/faye/transport/local.rb +6 -3
- data/lib/faye/transport/transport.rb +23 -9
- data/lib/faye/transport/web_socket.rb +102 -0
- data/lib/faye/util/web_socket.rb +34 -80
- data/lib/faye/util/web_socket/api.rb +103 -0
- data/lib/faye/util/web_socket/client.rb +82 -0
- data/lib/faye/util/web_socket/draft75_parser.rb +3 -5
- data/lib/faye/util/web_socket/draft76_parser.rb +5 -7
- data/lib/faye/util/web_socket/protocol8_parser.rb +111 -46
- data/spec/javascript/client_spec.js +99 -7
- data/spec/javascript/engine_spec.js +116 -3
- data/spec/javascript/node_adapter_spec.js +2 -4
- data/spec/javascript/server/handshake_spec.js +0 -12
- data/spec/javascript/server/integration_spec.js +74 -29
- data/spec/javascript/server_spec.js +0 -11
- data/spec/javascript/web_socket/client_spec.js +121 -0
- data/spec/javascript/web_socket/protocol8parser_spec.js +26 -3
- data/spec/node.js +2 -0
- data/spec/redis.conf +10 -280
- data/spec/ruby/client_spec.rb +101 -8
- data/spec/ruby/engine_spec.rb +106 -0
- data/spec/ruby/server/handshake_spec.rb +0 -12
- data/spec/ruby/server/integration_spec.rb +56 -18
- data/spec/ruby/server_spec.rb +1 -12
- data/spec/ruby/transport_spec.rb +14 -8
- data/spec/ruby/web_socket/client_spec.rb +126 -0
- data/spec/ruby/web_socket/protocol8_parser_spec.rb +28 -3
- metadata +96 -150
@@ -9,11 +9,11 @@ module Faye
|
|
9
9
|
end
|
10
10
|
|
11
11
|
def <<(message)
|
12
|
-
|
12
|
+
trigger(:message, message)
|
13
13
|
end
|
14
14
|
|
15
15
|
def unused?
|
16
|
-
|
16
|
+
count_listeners(:message).zero?
|
17
17
|
end
|
18
18
|
|
19
19
|
HANDSHAKE = '/meta/handshake'
|
@@ -22,8 +22,8 @@ module Faye
|
|
22
22
|
UNSUBSCRIBE = '/meta/unsubscribe'
|
23
23
|
DISCONNECT = '/meta/disconnect'
|
24
24
|
|
25
|
-
META =
|
26
|
-
SERVICE =
|
25
|
+
META = :meta
|
26
|
+
SERVICE = :service
|
27
27
|
|
28
28
|
class << self
|
29
29
|
def expand(name)
|
@@ -50,7 +50,7 @@ module Faye
|
|
50
50
|
|
51
51
|
def parse(name)
|
52
52
|
return nil unless valid?(name)
|
53
|
-
name.split('/')[1..-1]
|
53
|
+
name.split('/')[1..-1].map { |s| s.to_sym }
|
54
54
|
end
|
55
55
|
|
56
56
|
def unparse(segments)
|
@@ -94,14 +94,14 @@ module Faye
|
|
94
94
|
return unless callback
|
95
95
|
names.each do |name|
|
96
96
|
channel = @channels[name] ||= Channel.new(name)
|
97
|
-
channel.
|
97
|
+
channel.bind(:message, &callback)
|
98
98
|
end
|
99
99
|
end
|
100
100
|
|
101
101
|
def unsubscribe(name, callback)
|
102
102
|
channel = @channels[name]
|
103
103
|
return false unless channel
|
104
|
-
channel.
|
104
|
+
channel.unbind(:message, &callback)
|
105
105
|
if channel.unused?
|
106
106
|
remove(name)
|
107
107
|
true
|
@@ -114,7 +114,7 @@ module Faye
|
|
114
114
|
channels = Channel.expand(message['channel'])
|
115
115
|
channels.each do |name|
|
116
116
|
channel = @channels[name]
|
117
|
-
channel.
|
117
|
+
channel.trigger(:message, message['data']) if channel
|
118
118
|
end
|
119
119
|
end
|
120
120
|
end
|
data/lib/faye/protocol/client.rb
CHANGED
@@ -2,6 +2,7 @@ module Faye
|
|
2
2
|
class Client
|
3
3
|
|
4
4
|
include EventMachine::Deferrable
|
5
|
+
include Publisher
|
5
6
|
include Logging
|
6
7
|
include Extensible
|
7
8
|
|
@@ -16,6 +17,7 @@ module Faye
|
|
16
17
|
|
17
18
|
CONNECTION_TIMEOUT = 60.0
|
18
19
|
|
20
|
+
attr_accessor :cookies
|
19
21
|
attr_reader :endpoint, :client_id
|
20
22
|
|
21
23
|
def initialize(endpoint = nil, options = {})
|
@@ -23,8 +25,10 @@ module Faye
|
|
23
25
|
|
24
26
|
@endpoint = endpoint || RackAdapter::DEFAULT_ENDPOINT
|
25
27
|
@options = options
|
28
|
+
@disabled = []
|
26
29
|
|
27
|
-
|
30
|
+
select_transport(MANDATORY_CONNECTION_TYPES)
|
31
|
+
|
28
32
|
@state = UNCONNECTED
|
29
33
|
@channels = Channel::Set.new
|
30
34
|
@message_id = 0
|
@@ -38,6 +42,10 @@ module Faye
|
|
38
42
|
}
|
39
43
|
end
|
40
44
|
|
45
|
+
def disable(feature)
|
46
|
+
@disabled << feature
|
47
|
+
end
|
48
|
+
|
41
49
|
def state
|
42
50
|
case @state
|
43
51
|
when UNCONNECTED then :UNCONNECTED
|
@@ -84,7 +92,9 @@ module Faye
|
|
84
92
|
if response['successful']
|
85
93
|
@state = CONNECTED
|
86
94
|
@client_id = response['clientId']
|
87
|
-
|
95
|
+
|
96
|
+
connection_types = response['supportedConnectionTypes'] - @disabled
|
97
|
+
select_transport(connection_types)
|
88
98
|
|
89
99
|
info('Handshake successful: ?', @client_id)
|
90
100
|
|
@@ -152,7 +162,10 @@ module Faye
|
|
152
162
|
send({
|
153
163
|
'channel' => Channel::DISCONNECT,
|
154
164
|
'clientId' => @client_id
|
155
|
-
|
165
|
+
|
166
|
+
}) do |response|
|
167
|
+
@transport.close if response['successful']
|
168
|
+
end
|
156
169
|
|
157
170
|
info('Clearing channel listeners for ?', @client_id)
|
158
171
|
@channels = Channel::Set.new
|
@@ -256,6 +269,7 @@ module Faye
|
|
256
269
|
raise "Cannot publish: '#{channel}' is not a valid channel name"
|
257
270
|
end
|
258
271
|
|
272
|
+
publication = Publication.new
|
259
273
|
connect {
|
260
274
|
info('Client ? queueing published message to ?: ?', @client_id, channel, data)
|
261
275
|
|
@@ -263,8 +277,15 @@ module Faye
|
|
263
277
|
'channel' => channel,
|
264
278
|
'data' => data,
|
265
279
|
'clientId' => @client_id
|
266
|
-
})
|
280
|
+
}) do |response|
|
281
|
+
if response['successful']
|
282
|
+
publication.set_deferred_status(:succeeded)
|
283
|
+
else
|
284
|
+
publication.set_deferred_status(:failed, Error.parse(response['error']))
|
285
|
+
end
|
286
|
+
end
|
267
287
|
}
|
288
|
+
publication
|
268
289
|
end
|
269
290
|
|
270
291
|
def receive_message(message)
|
@@ -285,6 +306,26 @@ module Faye
|
|
285
306
|
|
286
307
|
private
|
287
308
|
|
309
|
+
def select_transport(transport_types)
|
310
|
+
Transport.get(self, transport_types) do |transport|
|
311
|
+
@transport = transport
|
312
|
+
|
313
|
+
transport.bind :down do
|
314
|
+
if @transport_up.nil? or @transport_up
|
315
|
+
@transport_up = false
|
316
|
+
trigger('transport:down')
|
317
|
+
end
|
318
|
+
end
|
319
|
+
|
320
|
+
transport.bind :up do
|
321
|
+
if @transport_up.nil? or not @transport_up
|
322
|
+
@transport_up = true
|
323
|
+
trigger('transport:up')
|
324
|
+
end
|
325
|
+
end
|
326
|
+
end
|
327
|
+
end
|
328
|
+
|
288
329
|
def send(message, &callback)
|
289
330
|
message['id'] = generate_message_id
|
290
331
|
@response_callbacks[message['id']] = callback if callback
|
data/lib/faye/protocol/server.rb
CHANGED
@@ -3,9 +3,9 @@ module Faye
|
|
3
3
|
|
4
4
|
include Logging
|
5
5
|
include Extensible
|
6
|
-
|
7
|
-
META_METHODS = %w[handshake connect disconnect subscribe unsubscribe]
|
8
6
|
|
7
|
+
attr_reader :engine
|
8
|
+
|
9
9
|
def initialize(options = {})
|
10
10
|
@options = options || {}
|
11
11
|
engine_opts = @options[:engine] || {}
|
@@ -91,14 +91,7 @@ module Faye
|
|
91
91
|
|
92
92
|
def handle_meta(message, local, &callback)
|
93
93
|
method = Channel.parse(message['channel'])[1]
|
94
|
-
|
95
|
-
unless META_METHODS.include?(method)
|
96
|
-
response = make_response(message)
|
97
|
-
response['error'] = Faye::Error.channel_forbidden(message['channel'])
|
98
|
-
response['successful'] = false
|
99
|
-
return callback.call([response])
|
100
|
-
end
|
101
|
-
|
94
|
+
|
102
95
|
__send__(method, message, local) do |responses|
|
103
96
|
responses = [responses].flatten
|
104
97
|
responses.each(&method(:advize))
|
@@ -132,15 +125,13 @@ module Faye
|
|
132
125
|
|
133
126
|
client_conns = message['supportedConnectionTypes']
|
134
127
|
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
response['error'] = Error.parameter_missing('supportedConnectionTypes')
|
143
|
-
end
|
128
|
+
response['supportedConnectionTypes'] = CONNECTION_TYPES
|
129
|
+
|
130
|
+
if client_conns
|
131
|
+
common_conns = client_conns.select { |c| CONNECTION_TYPES.include?(c) }
|
132
|
+
response['error'] = Error.conntype_mismatch(*client_conns) if common_conns.empty?
|
133
|
+
else
|
134
|
+
response['error'] = Error.parameter_missing('supportedConnectionTypes')
|
144
135
|
end
|
145
136
|
|
146
137
|
response['successful'] = response['error'].nil?
|
data/lib/faye/thin_extensions.rb
CHANGED
@@ -34,6 +34,7 @@ class Thin::Connection
|
|
34
34
|
else
|
35
35
|
if @request.parse(data)
|
36
36
|
if @request.websocket?
|
37
|
+
@request.env['em.connection'] = self
|
37
38
|
@response.persistent!
|
38
39
|
@response.websocket = true
|
39
40
|
@serving = :websocket
|
@@ -51,7 +52,6 @@ end
|
|
51
52
|
|
52
53
|
class Thin::Request
|
53
54
|
WEBSOCKET_RECEIVE_CALLBACK = 'websocket.receive_callback'.freeze
|
54
|
-
|
55
55
|
def websocket?
|
56
56
|
@env['HTTP_CONNECTION'] and
|
57
57
|
@env['HTTP_CONNECTION'].split(/\s*,\s*/).include?('Upgrade') and
|
data/lib/faye/transport/http.rb
CHANGED
@@ -1,22 +1,23 @@
|
|
1
|
-
require 'em-http'
|
2
|
-
require 'em-http/version'
|
3
|
-
|
4
1
|
module Faye
|
5
2
|
|
6
3
|
class Transport::Http < Transport
|
7
|
-
def self.usable?(endpoint)
|
8
|
-
endpoint.is_a?(String)
|
4
|
+
def self.usable?(endpoint, &callback)
|
5
|
+
callback.call(endpoint.is_a?(String))
|
9
6
|
end
|
10
7
|
|
11
8
|
def request(message, timeout)
|
12
9
|
retry_block = retry_block(message, timeout)
|
13
10
|
|
11
|
+
@client.cookies ||= CookieJar::Jar.new
|
12
|
+
cookies = @client.cookies.get_cookies(@endpoint)
|
13
|
+
|
14
14
|
content = JSON.unparse(message)
|
15
15
|
params = {
|
16
16
|
:head => {
|
17
|
+
'Content-Length' => content.length,
|
17
18
|
'Content-Type' => 'application/json',
|
18
|
-
'
|
19
|
-
'
|
19
|
+
'Cookie' => cookies * '; ',
|
20
|
+
'Host' => URI.parse(@endpoint).host
|
20
21
|
},
|
21
22
|
:body => content,
|
22
23
|
:timeout => -1 # for em-http-request < 1.0
|
@@ -31,12 +32,20 @@ module Faye
|
|
31
32
|
end
|
32
33
|
request.callback do
|
33
34
|
begin
|
35
|
+
cookies = [*request.response_header['SET_COOKIE']]
|
36
|
+
cookies.each do |cookie|
|
37
|
+
@client.cookies.set_cookie(@endpoint, cookie)
|
38
|
+
end
|
34
39
|
receive(JSON.parse(request.response))
|
40
|
+
trigger(:up)
|
35
41
|
rescue
|
36
42
|
retry_block.call
|
37
43
|
end
|
38
44
|
end
|
39
|
-
request.errback
|
45
|
+
request.errback do
|
46
|
+
retry_block.call
|
47
|
+
trigger(:down)
|
48
|
+
end
|
40
49
|
end
|
41
50
|
end
|
42
51
|
|
data/lib/faye/transport/local.rb
CHANGED
@@ -1,8 +1,8 @@
|
|
1
1
|
module Faye
|
2
2
|
|
3
3
|
class Transport::Local < Transport
|
4
|
-
def self.usable?(endpoint)
|
5
|
-
endpoint.is_a?(Server)
|
4
|
+
def self.usable?(endpoint, &callback)
|
5
|
+
callback.call(endpoint.is_a?(Server))
|
6
6
|
end
|
7
7
|
|
8
8
|
def batching?
|
@@ -10,7 +10,10 @@ module Faye
|
|
10
10
|
end
|
11
11
|
|
12
12
|
def request(message, timeout)
|
13
|
-
|
13
|
+
message = Faye.copy_object(message)
|
14
|
+
@endpoint.process(message, true) do |responses|
|
15
|
+
receive(Faye.copy_object(responses))
|
16
|
+
end
|
14
17
|
end
|
15
18
|
end
|
16
19
|
|
@@ -1,10 +1,8 @@
|
|
1
|
-
require 'json'
|
2
|
-
require 'uri'
|
3
|
-
|
4
1
|
module Faye
|
5
2
|
class Transport
|
6
3
|
|
7
4
|
include Logging
|
5
|
+
include Publisher
|
8
6
|
include Timeouts
|
9
7
|
|
10
8
|
def initialize(client, endpoint)
|
@@ -18,6 +16,9 @@ module Faye
|
|
18
16
|
true
|
19
17
|
end
|
20
18
|
|
19
|
+
def close
|
20
|
+
end
|
21
|
+
|
21
22
|
def connection_type
|
22
23
|
self.class.connection_type
|
23
24
|
end
|
@@ -68,20 +69,29 @@ module Faye
|
|
68
69
|
class << self
|
69
70
|
attr_accessor :connection_type
|
70
71
|
|
71
|
-
def get(client, connection_types = nil)
|
72
|
+
def get(client, connection_types = nil, &callback)
|
72
73
|
endpoint = client.endpoint
|
73
74
|
connection_types ||= supported_connection_types
|
74
75
|
|
75
|
-
|
76
|
-
connection_types.include?(
|
77
|
-
|
76
|
+
select = lambda do |(conn_type, klass), resume|
|
77
|
+
if connection_types.include?(conn_type)
|
78
|
+
klass.usable?(endpoint) do |is_usable|
|
79
|
+
if is_usable
|
80
|
+
callback.call(klass.new(client, endpoint))
|
81
|
+
else
|
82
|
+
resume.call
|
83
|
+
end
|
84
|
+
end
|
85
|
+
else
|
86
|
+
resume.call
|
87
|
+
end
|
78
88
|
end
|
79
89
|
|
80
|
-
|
90
|
+
error = lambda do
|
81
91
|
raise "Could not find a usable connection type for #{ endpoint }"
|
82
92
|
end
|
83
93
|
|
84
|
-
|
94
|
+
Faye.async_each(@transports, select, error)
|
85
95
|
end
|
86
96
|
|
87
97
|
def register(type, klass)
|
@@ -94,6 +104,10 @@ module Faye
|
|
94
104
|
end
|
95
105
|
end
|
96
106
|
|
107
|
+
%w[local web_socket http].each do |type|
|
108
|
+
require File.join(ROOT, 'faye', 'transport', type)
|
109
|
+
end
|
110
|
+
|
97
111
|
end
|
98
112
|
end
|
99
113
|
|
@@ -0,0 +1,102 @@
|
|
1
|
+
module Faye
|
2
|
+
|
3
|
+
class Transport::WebSocket < Transport
|
4
|
+
WEBSOCKET_TIMEOUT = 1
|
5
|
+
|
6
|
+
UNCONNECTED = 1
|
7
|
+
CONNECTING = 2
|
8
|
+
CONNECTED = 3
|
9
|
+
|
10
|
+
include EventMachine::Deferrable
|
11
|
+
|
12
|
+
def self.usable?(endpoint, &callback)
|
13
|
+
connected = false
|
14
|
+
called = false
|
15
|
+
socket_url = endpoint.gsub(/^http(s?):/, 'ws\1:')
|
16
|
+
socket = Faye::WebSocket::Client.new(socket_url)
|
17
|
+
|
18
|
+
socket.onopen = lambda do |event|
|
19
|
+
connected = true
|
20
|
+
socket.close
|
21
|
+
callback.call(true)
|
22
|
+
called = true
|
23
|
+
socket = nil
|
24
|
+
end
|
25
|
+
|
26
|
+
notconnected = lambda do |*args|
|
27
|
+
callback.call(false) unless called or connected
|
28
|
+
called = true
|
29
|
+
end
|
30
|
+
|
31
|
+
socket.onclose = socket.onerror = notconnected
|
32
|
+
EventMachine.add_timer(WEBSOCKET_TIMEOUT, ¬connected)
|
33
|
+
end
|
34
|
+
|
35
|
+
def batching?
|
36
|
+
false
|
37
|
+
end
|
38
|
+
|
39
|
+
def request(messages, timeout = nil)
|
40
|
+
@timeout = timeout || @timeout
|
41
|
+
@messages ||= {}
|
42
|
+
messages.each { |message| @messages[message['id']] = message }
|
43
|
+
with_socket { |socket| socket.send(JSON.unparse(messages)) }
|
44
|
+
end
|
45
|
+
|
46
|
+
def with_socket(&resume)
|
47
|
+
callback(&resume)
|
48
|
+
connect
|
49
|
+
end
|
50
|
+
|
51
|
+
def close
|
52
|
+
return if @closed
|
53
|
+
@closed = true
|
54
|
+
@socket.close if @socket
|
55
|
+
end
|
56
|
+
|
57
|
+
def connect
|
58
|
+
return if @closed
|
59
|
+
|
60
|
+
@state ||= UNCONNECTED
|
61
|
+
return unless @state == UNCONNECTED
|
62
|
+
|
63
|
+
@state = CONNECTING
|
64
|
+
|
65
|
+
@socket = Faye::WebSocket::Client.new(@endpoint.gsub(/^http(s?):/, 'ws\1:'))
|
66
|
+
|
67
|
+
@socket.onopen = lambda do |*args|
|
68
|
+
@state = CONNECTED
|
69
|
+
set_deferred_status(:succeeded, @socket)
|
70
|
+
trigger(:up)
|
71
|
+
end
|
72
|
+
|
73
|
+
@socket.onmessage = lambda do |event|
|
74
|
+
messages = [JSON.parse(event.data)].flatten
|
75
|
+
messages.each { |message| @messages.delete(message['id']) }
|
76
|
+
receive(messages)
|
77
|
+
end
|
78
|
+
|
79
|
+
@socket.onclose = lambda do |*args|
|
80
|
+
was_connected = (@state == CONNECTED)
|
81
|
+
set_deferred_status(:deferred)
|
82
|
+
@state = UNCONNECTED
|
83
|
+
@socket = nil
|
84
|
+
|
85
|
+
if was_connected
|
86
|
+
resend
|
87
|
+
else
|
88
|
+
EventMachine.add_timer(@timeout) { connect }
|
89
|
+
@timeout = @timeout * 2
|
90
|
+
trigger(:down)
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
def resend
|
96
|
+
request(@messages.values)
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
Transport.register 'websocket', Transport::WebSocket
|
101
|
+
|
102
|
+
end
|