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,121 @@
|
|
1
|
+
module Faye
|
2
|
+
module Engine
|
3
|
+
|
4
|
+
class Memory
|
5
|
+
include Timeouts
|
6
|
+
|
7
|
+
def self.create(server, options)
|
8
|
+
new(server, options)
|
9
|
+
end
|
10
|
+
|
11
|
+
def initialize(server, options)
|
12
|
+
@server = server
|
13
|
+
@options = options
|
14
|
+
reset
|
15
|
+
end
|
16
|
+
|
17
|
+
def disconnect
|
18
|
+
reset
|
19
|
+
remove_all_timeouts
|
20
|
+
end
|
21
|
+
|
22
|
+
def reset
|
23
|
+
@namespace = Namespace.new
|
24
|
+
@clients = {}
|
25
|
+
@channels = {}
|
26
|
+
@messages = {}
|
27
|
+
end
|
28
|
+
|
29
|
+
def create_client(&callback)
|
30
|
+
client_id = @namespace.generate
|
31
|
+
@server.debug('Created new client ?', client_id)
|
32
|
+
ping(client_id)
|
33
|
+
@server.trigger(:handshake, client_id)
|
34
|
+
callback.call(client_id)
|
35
|
+
end
|
36
|
+
|
37
|
+
def destroy_client(client_id, &callback)
|
38
|
+
return unless @namespace.exists?(client_id)
|
39
|
+
|
40
|
+
if @clients.has_key?(client_id)
|
41
|
+
@clients[client_id].each { |channel| unsubscribe(client_id, channel) }
|
42
|
+
end
|
43
|
+
|
44
|
+
remove_timeout(client_id)
|
45
|
+
@namespace.release(client_id)
|
46
|
+
@messages.delete(client_id)
|
47
|
+
@server.debug('Destroyed client ?', client_id)
|
48
|
+
@server.trigger(:disconnect, client_id)
|
49
|
+
@server.trigger(:close, client_id)
|
50
|
+
callback.call if callback
|
51
|
+
end
|
52
|
+
|
53
|
+
def client_exists(client_id, &callback)
|
54
|
+
callback.call(@namespace.exists?(client_id))
|
55
|
+
end
|
56
|
+
|
57
|
+
def ping(client_id)
|
58
|
+
timeout = @server.timeout
|
59
|
+
return unless Numeric === timeout
|
60
|
+
@server.debug('Ping ?, ?', client_id, timeout)
|
61
|
+
remove_timeout(client_id)
|
62
|
+
add_timeout(client_id, 2 * timeout) { destroy_client(client_id) }
|
63
|
+
end
|
64
|
+
|
65
|
+
def subscribe(client_id, channel, &callback)
|
66
|
+
@clients[client_id] ||= Set.new
|
67
|
+
should_trigger = @clients[client_id].add?(channel)
|
68
|
+
|
69
|
+
@channels[channel] ||= Set.new
|
70
|
+
@channels[channel].add(client_id)
|
71
|
+
|
72
|
+
@server.debug('Subscribed client ? to channel ?', client_id, channel)
|
73
|
+
@server.trigger(:subscribe, client_id, channel) if should_trigger
|
74
|
+
callback.call(true) if callback
|
75
|
+
end
|
76
|
+
|
77
|
+
def unsubscribe(client_id, channel, &callback)
|
78
|
+
if @clients.has_key?(client_id)
|
79
|
+
should_trigger = @clients[client_id].delete?(channel)
|
80
|
+
@clients.delete(client_id) if @clients[client_id].empty?
|
81
|
+
end
|
82
|
+
|
83
|
+
if @channels.has_key?(channel)
|
84
|
+
@channels[channel].delete(client_id)
|
85
|
+
@channels.delete(channel) if @channels[channel].empty?
|
86
|
+
end
|
87
|
+
|
88
|
+
@server.debug('Unsubscribed client ? from channel ?', client_id, channel)
|
89
|
+
@server.trigger(:unsubscribe, client_id, channel) if should_trigger
|
90
|
+
callback.call(true) if callback
|
91
|
+
end
|
92
|
+
|
93
|
+
def publish(message, channels)
|
94
|
+
@server.debug('Publishing message ?', message)
|
95
|
+
|
96
|
+
clients = Set.new
|
97
|
+
|
98
|
+
channels.each do |channel|
|
99
|
+
next unless subs = @channels[channel]
|
100
|
+
subs.each(&clients.method(:add))
|
101
|
+
end
|
102
|
+
|
103
|
+
clients.each do |client_id|
|
104
|
+
@server.debug('Queueing for client ?: ?', client_id, message)
|
105
|
+
@messages[client_id] ||= []
|
106
|
+
@messages[client_id] << Faye.copy_object(message)
|
107
|
+
empty_queue(client_id)
|
108
|
+
end
|
109
|
+
|
110
|
+
@server.trigger(:publish, message['clientId'], message['channel'], message['data'])
|
111
|
+
end
|
112
|
+
|
113
|
+
def empty_queue(client_id)
|
114
|
+
return unless @server.has_connection?(client_id)
|
115
|
+
@server.deliver(client_id, @messages[client_id])
|
116
|
+
@messages.delete(client_id)
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
end
|
121
|
+
end
|
@@ -0,0 +1,126 @@
|
|
1
|
+
module Faye
|
2
|
+
module Engine
|
3
|
+
|
4
|
+
METHODS = %w[create_client client_exists destroy_client ping subscribe unsubscribe]
|
5
|
+
MAX_DELAY = 0.0
|
6
|
+
INTERVAL = 0.0
|
7
|
+
TIMEOUT = 60.0
|
8
|
+
ID_LENGTH = 160
|
9
|
+
|
10
|
+
autoload :Connection, File.expand_path('../connection', __FILE__)
|
11
|
+
autoload :Memory, File.expand_path('../memory', __FILE__)
|
12
|
+
|
13
|
+
def self.ensure_reactor_running!
|
14
|
+
Thread.new { EventMachine.run } unless EventMachine.reactor_running?
|
15
|
+
Thread.pass until EventMachine.reactor_running?
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.get(options)
|
19
|
+
Proxy.new(options)
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.random(bitlength = ID_LENGTH)
|
23
|
+
limit = 2 ** bitlength
|
24
|
+
max_size = (bitlength * Math.log(2) / Math.log(36)).ceil
|
25
|
+
string = SecureRandom.random_number(limit).to_s(36)
|
26
|
+
string = '0' + string while string.size < max_size
|
27
|
+
string
|
28
|
+
end
|
29
|
+
|
30
|
+
class Proxy
|
31
|
+
include Publisher
|
32
|
+
include Logging
|
33
|
+
|
34
|
+
attr_reader :interval, :timeout
|
35
|
+
|
36
|
+
extend Forwardable
|
37
|
+
def_delegators :@engine, *METHODS
|
38
|
+
|
39
|
+
def initialize(options)
|
40
|
+
super()
|
41
|
+
|
42
|
+
@options = options
|
43
|
+
@connections = {}
|
44
|
+
@interval = @options[:interval] || INTERVAL
|
45
|
+
@timeout = @options[:timeout] || TIMEOUT
|
46
|
+
|
47
|
+
engine_class = @options[:type] || Memory
|
48
|
+
@engine = engine_class.create(self, @options)
|
49
|
+
|
50
|
+
bind :close do |client_id|
|
51
|
+
EventMachine.next_tick { flush_connection(client_id) }
|
52
|
+
end
|
53
|
+
|
54
|
+
debug('Created new engine: ?', @options)
|
55
|
+
end
|
56
|
+
|
57
|
+
def connect(client_id, options = {}, &callback)
|
58
|
+
debug('Accepting connection from ?', client_id)
|
59
|
+
@engine.ping(client_id)
|
60
|
+
conn = connection(client_id, true)
|
61
|
+
conn.connect(options, &callback)
|
62
|
+
@engine.empty_queue(client_id)
|
63
|
+
end
|
64
|
+
|
65
|
+
def has_connection?(client_id)
|
66
|
+
@connections.has_key?(client_id)
|
67
|
+
end
|
68
|
+
|
69
|
+
def connection(client_id, create)
|
70
|
+
conn = @connections[client_id]
|
71
|
+
return conn if conn or not create
|
72
|
+
@connections[client_id] = Connection.new(self, client_id)
|
73
|
+
trigger('connection:open', client_id)
|
74
|
+
@connections[client_id]
|
75
|
+
end
|
76
|
+
|
77
|
+
def close_connection(client_id)
|
78
|
+
debug('Closing connection for ?', client_id)
|
79
|
+
return unless conn = @connections[client_id]
|
80
|
+
conn.socket.close if conn.socket
|
81
|
+
trigger('connection:close', client_id)
|
82
|
+
@connections.delete(client_id)
|
83
|
+
end
|
84
|
+
|
85
|
+
def open_socket(client_id, socket)
|
86
|
+
conn = connection(client_id, true)
|
87
|
+
conn.socket = socket
|
88
|
+
end
|
89
|
+
|
90
|
+
def deliver(client_id, messages)
|
91
|
+
return if !messages || messages.empty?
|
92
|
+
return false unless conn = connection(client_id, false)
|
93
|
+
messages.each(&conn.method(:deliver))
|
94
|
+
true
|
95
|
+
end
|
96
|
+
|
97
|
+
def generate_id
|
98
|
+
Engine.random
|
99
|
+
end
|
100
|
+
|
101
|
+
def flush_connection(client_id, close = true)
|
102
|
+
return unless client_id
|
103
|
+
debug('Flushing connection for ?', client_id)
|
104
|
+
return unless conn = connection(client_id, false)
|
105
|
+
conn.socket = nil unless close
|
106
|
+
conn.flush
|
107
|
+
close_connection(client_id)
|
108
|
+
end
|
109
|
+
|
110
|
+
def close
|
111
|
+
@connections.keys.each { |client_id| flush_connection(client_id) }
|
112
|
+
@engine.disconnect
|
113
|
+
end
|
114
|
+
|
115
|
+
def disconnect
|
116
|
+
@engine.disconnect if @engine.respond_to?(:disconnect)
|
117
|
+
end
|
118
|
+
|
119
|
+
def publish(message)
|
120
|
+
channels = Channel.expand(message['channel'])
|
121
|
+
@engine.publish(message, channels)
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
end
|
126
|
+
end
|
data/lib/faye/error.rb
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
module Faye
|
2
|
+
class Error
|
3
|
+
|
4
|
+
def self.method_missing(type, *args)
|
5
|
+
code = const_get(type.to_s.upcase)
|
6
|
+
new(code[0], args, code[1]).to_s
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.parse(message)
|
10
|
+
message ||= ''
|
11
|
+
return new(nil, [], message) unless Grammar::ERROR =~ message
|
12
|
+
|
13
|
+
parts = message.split(':')
|
14
|
+
code = parts[0].to_i
|
15
|
+
params = parts[1].split(',')
|
16
|
+
message = parts[2]
|
17
|
+
|
18
|
+
new(code, params, message)
|
19
|
+
end
|
20
|
+
|
21
|
+
attr_reader :code, :params, :message
|
22
|
+
|
23
|
+
def initialize(code, params, message)
|
24
|
+
@code = code
|
25
|
+
@params = params
|
26
|
+
@message = message
|
27
|
+
end
|
28
|
+
|
29
|
+
def to_s
|
30
|
+
"#{ @code }:#{ @params * ',' }:#{ @message }"
|
31
|
+
end
|
32
|
+
|
33
|
+
# http://code.google.com/p/cometd/wiki/BayeuxCodes
|
34
|
+
VERSION_MISMATCH = [300, 'Version mismatch']
|
35
|
+
CONNTYPE_MISMATCH = [301, 'Connection types not supported']
|
36
|
+
EXT_MISMATCH = [302, 'Extension mismatch']
|
37
|
+
BAD_REQUEST = [400, 'Bad request']
|
38
|
+
CLIENT_UNKNOWN = [401, 'Unknown client']
|
39
|
+
PARAMETER_MISSING = [402, 'Missing required parameter']
|
40
|
+
CHANNEL_FORBIDDEN = [403, 'Forbidden channel']
|
41
|
+
CHANNEL_UNKNOWN = [404, 'Unknown channel']
|
42
|
+
CHANNEL_INVALID = [405, 'Invalid channel']
|
43
|
+
EXT_UNKNOWN = [406, 'Unknown extension']
|
44
|
+
PUBLISH_FAILED = [407, 'Failed to publish']
|
45
|
+
SERVER_ERROR = [500, 'Internal server error']
|
46
|
+
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module Faye
|
2
|
+
module Logging
|
3
|
+
|
4
|
+
LOG_LEVELS = {
|
5
|
+
:fatal => 4,
|
6
|
+
:error => 3,
|
7
|
+
:warn => 2,
|
8
|
+
:info => 1,
|
9
|
+
:debug => 0
|
10
|
+
}
|
11
|
+
|
12
|
+
LOG_LEVELS.each do |level, value|
|
13
|
+
define_method(level) { |*args| write_log(args, level) }
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
def write_log(message_args, level)
|
19
|
+
return unless Faye.logger
|
20
|
+
|
21
|
+
message = message_args.shift.gsub(/\?/) do
|
22
|
+
Faye.to_json(message_args.shift)
|
23
|
+
end
|
24
|
+
|
25
|
+
banner = "[#{ self.class.name }] "
|
26
|
+
|
27
|
+
if Faye.logger.respond_to?(level)
|
28
|
+
Faye.logger.__send__(level, banner + message)
|
29
|
+
elsif Faye.logger.respond_to?(:call)
|
30
|
+
Faye.logger.call(banner + message)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module Faye
|
2
|
+
module Publisher
|
3
|
+
|
4
|
+
include ::WebSocket::Driver::EventEmitter
|
5
|
+
|
6
|
+
alias :bind :add_listener
|
7
|
+
alias :trigger :emit
|
8
|
+
|
9
|
+
def unbind(event, &listener)
|
10
|
+
if listener
|
11
|
+
remove_listener(event, &listener)
|
12
|
+
else
|
13
|
+
remove_all_listeners(event)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module Faye
|
2
|
+
module Timeouts
|
3
|
+
def add_timeout(name, delay, &block)
|
4
|
+
Engine.ensure_reactor_running!
|
5
|
+
@timeouts ||= {}
|
6
|
+
return if @timeouts.has_key?(name)
|
7
|
+
@timeouts[name] = EventMachine.add_timer(delay) do
|
8
|
+
@timeouts.delete(name)
|
9
|
+
block.call
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def remove_timeout(name)
|
14
|
+
@timeouts ||= {}
|
15
|
+
timeout = @timeouts[name]
|
16
|
+
return if timeout.nil?
|
17
|
+
EventMachine.cancel_timer(timeout)
|
18
|
+
@timeouts.delete(name)
|
19
|
+
end
|
20
|
+
|
21
|
+
def remove_all_timeouts
|
22
|
+
@timeouts ||= {}
|
23
|
+
@timeouts.keys.each { |name| remove_timeout(name) }
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,123 @@
|
|
1
|
+
module Faye
|
2
|
+
class Channel
|
3
|
+
|
4
|
+
include Publisher
|
5
|
+
attr_reader :name
|
6
|
+
|
7
|
+
def initialize(name)
|
8
|
+
super()
|
9
|
+
@name = name
|
10
|
+
end
|
11
|
+
|
12
|
+
def <<(message)
|
13
|
+
trigger(:message, message)
|
14
|
+
end
|
15
|
+
|
16
|
+
def unused?
|
17
|
+
listener_count(:message).zero?
|
18
|
+
end
|
19
|
+
|
20
|
+
HANDSHAKE = '/meta/handshake'
|
21
|
+
CONNECT = '/meta/connect'
|
22
|
+
SUBSCRIBE = '/meta/subscribe'
|
23
|
+
UNSUBSCRIBE = '/meta/unsubscribe'
|
24
|
+
DISCONNECT = '/meta/disconnect'
|
25
|
+
|
26
|
+
META = 'meta'
|
27
|
+
SERVICE = 'service'
|
28
|
+
|
29
|
+
class << self
|
30
|
+
def expand(name)
|
31
|
+
segments = parse(name)
|
32
|
+
channels = ['/**', name]
|
33
|
+
|
34
|
+
copy = segments.dup
|
35
|
+
copy[copy.size - 1] = '*'
|
36
|
+
channels << unparse(copy)
|
37
|
+
|
38
|
+
1.upto(segments.size - 1) do |i|
|
39
|
+
copy = segments[0...i]
|
40
|
+
copy << '**'
|
41
|
+
channels << unparse(copy)
|
42
|
+
end
|
43
|
+
|
44
|
+
channels
|
45
|
+
end
|
46
|
+
|
47
|
+
def valid?(name)
|
48
|
+
Grammar::CHANNEL_NAME =~ name or
|
49
|
+
Grammar::CHANNEL_PATTERN =~ name
|
50
|
+
end
|
51
|
+
|
52
|
+
def parse(name)
|
53
|
+
return nil unless valid?(name)
|
54
|
+
name.split('/')[1..-1]
|
55
|
+
end
|
56
|
+
|
57
|
+
def unparse(segments)
|
58
|
+
'/' + segments.join('/')
|
59
|
+
end
|
60
|
+
|
61
|
+
def meta?(name)
|
62
|
+
segments = parse(name)
|
63
|
+
segments ? (segments.first == META) : nil
|
64
|
+
end
|
65
|
+
|
66
|
+
def service?(name)
|
67
|
+
segments = parse(name)
|
68
|
+
segments ? (segments.first == SERVICE) : nil
|
69
|
+
end
|
70
|
+
|
71
|
+
def subscribable?(name)
|
72
|
+
return nil unless valid?(name)
|
73
|
+
not meta?(name) and not service?(name)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
class Set
|
78
|
+
def initialize
|
79
|
+
@channels = {}
|
80
|
+
end
|
81
|
+
|
82
|
+
def keys
|
83
|
+
@channels.keys
|
84
|
+
end
|
85
|
+
|
86
|
+
def remove(name)
|
87
|
+
@channels.delete(name)
|
88
|
+
end
|
89
|
+
|
90
|
+
def has_subscription?(name)
|
91
|
+
@channels.has_key?(name)
|
92
|
+
end
|
93
|
+
|
94
|
+
def subscribe(names, callback)
|
95
|
+
names.each do |name|
|
96
|
+
channel = @channels[name] ||= Channel.new(name)
|
97
|
+
channel.bind(:message, &callback) if callback
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
def unsubscribe(name, callback)
|
102
|
+
channel = @channels[name]
|
103
|
+
return false unless channel
|
104
|
+
channel.unbind(:message, &callback)
|
105
|
+
if channel.unused?
|
106
|
+
remove(name)
|
107
|
+
true
|
108
|
+
else
|
109
|
+
false
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
def distribute_message(message)
|
114
|
+
channels = Channel.expand(message['channel'])
|
115
|
+
channels.each do |name|
|
116
|
+
channel = @channels[name]
|
117
|
+
channel.trigger(:message, message['data']) if channel
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
end
|
123
|
+
end
|