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