face-faye 0.8.9

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.
Files changed (73) hide show
  1. data/History.txt +304 -0
  2. data/README.rdoc +83 -0
  3. data/lib/faye-browser-min.js +2 -0
  4. data/lib/faye-browser-min.js.map +8 -0
  5. data/lib/faye-browser.js +2194 -0
  6. data/lib/faye.rb +122 -0
  7. data/lib/faye/adapters/rack_adapter.rb +216 -0
  8. data/lib/faye/adapters/static_server.rb +56 -0
  9. data/lib/faye/engines/connection.rb +60 -0
  10. data/lib/faye/engines/memory.rb +112 -0
  11. data/lib/faye/engines/proxy.rb +121 -0
  12. data/lib/faye/error.rb +49 -0
  13. data/lib/faye/mixins/logging.rb +47 -0
  14. data/lib/faye/mixins/publisher.rb +30 -0
  15. data/lib/faye/mixins/timeouts.rb +22 -0
  16. data/lib/faye/protocol/channel.rb +124 -0
  17. data/lib/faye/protocol/client.rb +376 -0
  18. data/lib/faye/protocol/extensible.rb +43 -0
  19. data/lib/faye/protocol/grammar.rb +58 -0
  20. data/lib/faye/protocol/publication.rb +5 -0
  21. data/lib/faye/protocol/server.rb +293 -0
  22. data/lib/faye/protocol/socket.rb +23 -0
  23. data/lib/faye/protocol/subscription.rb +24 -0
  24. data/lib/faye/transport/http.rb +76 -0
  25. data/lib/faye/transport/local.rb +22 -0
  26. data/lib/faye/transport/transport.rb +116 -0
  27. data/lib/faye/transport/web_socket.rb +92 -0
  28. data/lib/faye/util/namespace.rb +20 -0
  29. data/spec/browser.html +45 -0
  30. data/spec/encoding_helper.rb +7 -0
  31. data/spec/install.sh +78 -0
  32. data/spec/javascript/channel_spec.js +15 -0
  33. data/spec/javascript/client_spec.js +729 -0
  34. data/spec/javascript/engine/memory_spec.js +7 -0
  35. data/spec/javascript/engine_spec.js +417 -0
  36. data/spec/javascript/faye_spec.js +34 -0
  37. data/spec/javascript/grammar_spec.js +66 -0
  38. data/spec/javascript/node_adapter_spec.js +307 -0
  39. data/spec/javascript/publisher_spec.js +27 -0
  40. data/spec/javascript/server/connect_spec.js +168 -0
  41. data/spec/javascript/server/disconnect_spec.js +121 -0
  42. data/spec/javascript/server/extensions_spec.js +60 -0
  43. data/spec/javascript/server/handshake_spec.js +145 -0
  44. data/spec/javascript/server/integration_spec.js +131 -0
  45. data/spec/javascript/server/publish_spec.js +85 -0
  46. data/spec/javascript/server/subscribe_spec.js +247 -0
  47. data/spec/javascript/server/unsubscribe_spec.js +245 -0
  48. data/spec/javascript/server_spec.js +121 -0
  49. data/spec/javascript/transport_spec.js +135 -0
  50. data/spec/node.js +55 -0
  51. data/spec/phantom.js +17 -0
  52. data/spec/ruby/channel_spec.rb +17 -0
  53. data/spec/ruby/client_spec.rb +741 -0
  54. data/spec/ruby/engine/memory_spec.rb +7 -0
  55. data/spec/ruby/engine_examples.rb +427 -0
  56. data/spec/ruby/faye_spec.rb +30 -0
  57. data/spec/ruby/grammar_spec.rb +68 -0
  58. data/spec/ruby/publisher_spec.rb +27 -0
  59. data/spec/ruby/rack_adapter_spec.rb +236 -0
  60. data/spec/ruby/server/connect_spec.rb +170 -0
  61. data/spec/ruby/server/disconnect_spec.rb +120 -0
  62. data/spec/ruby/server/extensions_spec.rb +68 -0
  63. data/spec/ruby/server/handshake_spec.rb +143 -0
  64. data/spec/ruby/server/integration_spec.rb +133 -0
  65. data/spec/ruby/server/publish_spec.rb +81 -0
  66. data/spec/ruby/server/subscribe_spec.rb +247 -0
  67. data/spec/ruby/server/unsubscribe_spec.rb +247 -0
  68. data/spec/ruby/server_spec.rb +121 -0
  69. data/spec/ruby/transport_spec.rb +136 -0
  70. data/spec/spec_helper.rb +11 -0
  71. data/spec/testswarm +42 -0
  72. data/spec/thin_proxy.rb +37 -0
  73. metadata +441 -0
@@ -0,0 +1,112 @@
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
+ @namespace = Namespace.new
15
+ @clients = {}
16
+ @channels = {}
17
+ @messages = {}
18
+ end
19
+
20
+ def create_client(&callback)
21
+ client_id = @namespace.generate
22
+ @server.debug 'Created new client ?', client_id
23
+ ping(client_id)
24
+ @server.trigger(:handshake, client_id)
25
+ callback.call(client_id)
26
+ end
27
+
28
+ def destroy_client(client_id, &callback)
29
+ return unless @namespace.exists?(client_id)
30
+
31
+ if @clients.has_key?(client_id)
32
+ @clients[client_id].each { |channel| unsubscribe(client_id, channel) }
33
+ end
34
+
35
+ remove_timeout(client_id)
36
+ @namespace.release(client_id)
37
+ @messages.delete(client_id)
38
+ @server.debug 'Destroyed client ?', client_id
39
+ @server.trigger(:disconnect, client_id)
40
+ callback.call if callback
41
+ end
42
+
43
+ def client_exists(client_id, &callback)
44
+ callback.call(@namespace.exists?(client_id))
45
+ end
46
+
47
+ def ping(client_id)
48
+ timeout = @server.timeout
49
+ return unless Numeric === timeout
50
+ @server.debug 'Ping ?, ?', client_id, timeout
51
+ remove_timeout(client_id)
52
+ add_timeout(client_id, 2 * timeout) { destroy_client(client_id) }
53
+ end
54
+
55
+ def subscribe(client_id, channel, &callback)
56
+ @clients[client_id] ||= Set.new
57
+ should_trigger = @clients[client_id].add?(channel)
58
+
59
+ @channels[channel] ||= Set.new
60
+ @channels[channel].add(client_id)
61
+
62
+ @server.debug 'Subscribed client ? to channel ?', client_id, channel
63
+ @server.trigger(:subscribe, client_id, channel) if should_trigger
64
+ callback.call(true) if callback
65
+ end
66
+
67
+ def unsubscribe(client_id, channel, &callback)
68
+ if @clients.has_key?(client_id)
69
+ should_trigger = @clients[client_id].delete?(channel)
70
+ @clients.delete(client_id) if @clients[client_id].empty?
71
+ end
72
+
73
+ if @channels.has_key?(channel)
74
+ @channels[channel].delete(client_id)
75
+ @channels.delete(channel) if @channels[channel].empty?
76
+ end
77
+
78
+ @server.debug 'Unsubscribed client ? from channel ?', client_id, channel
79
+ @server.trigger(:unsubscribe, client_id, channel) if should_trigger
80
+ callback.call(true) if callback
81
+ end
82
+
83
+ def publish(message, channels)
84
+ @server.debug 'Publishing message ?', message
85
+
86
+ clients = Set.new
87
+
88
+ channels.each do |channel|
89
+ next unless subs = @channels[channel]
90
+ subs.each(&clients.method(:add))
91
+ end
92
+
93
+ clients.each do |client_id|
94
+ @server.debug 'Queueing for client ?: ?', client_id, message
95
+ @messages[client_id] ||= []
96
+ @messages[client_id] << Faye.copy_object(message)
97
+ empty_queue(client_id)
98
+ end
99
+
100
+ @server.trigger(:publish, message['clientId'], message['channel'], message['data'])
101
+ end
102
+
103
+ def empty_queue(client_id)
104
+ return unless @server.has_connection?(client_id)
105
+ @server.deliver(client_id, @messages[client_id])
106
+ @messages.delete(client_id)
107
+ end
108
+ end
109
+
110
+ end
111
+ end
112
+
@@ -0,0 +1,121 @@
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 - 1
24
+ max_size = limit.to_s(36).size
25
+ string = rand(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
+ @options = options
41
+ @connections = {}
42
+ @interval = @options[:interval] || INTERVAL
43
+ @timeout = @options[:timeout] || TIMEOUT
44
+
45
+ engine_class = @options[:type] || Memory
46
+ @engine = engine_class.create(self, @options)
47
+
48
+ bind :disconnect do |client_id|
49
+ EventMachine.next_tick { close_connection(client_id) }
50
+ end
51
+
52
+ debug 'Created new engine: ?', @options
53
+ end
54
+
55
+ def connect(client_id, options = {}, &callback)
56
+ debug 'Accepting connection from ?', client_id
57
+ @engine.ping(client_id)
58
+ conn = connection(client_id, true)
59
+ conn.connect(options, &callback)
60
+ @engine.empty_queue(client_id)
61
+ end
62
+
63
+ def has_connection?(client_id)
64
+ @connections.has_key?(client_id)
65
+ end
66
+
67
+ def connection(client_id, create)
68
+ conn = @connections[client_id]
69
+ return conn if conn or not create
70
+ @connections[client_id] = Connection.new(self, client_id)
71
+ trigger('connection:open', client_id)
72
+ @connections[client_id]
73
+ end
74
+
75
+ def close_connection(client_id)
76
+ debug 'Closing connection for ?', client_id
77
+ conn = @connections[client_id]
78
+ return unless conn
79
+ conn.socket.close if conn.socket
80
+ trigger('connection:close', client_id)
81
+ @connections.delete(client_id)
82
+ end
83
+
84
+ def open_socket(client_id, socket)
85
+ return unless client_id
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
+ conn = connection(client_id, false)
93
+ return false unless conn
94
+ messages.each(&conn.method(:deliver))
95
+ true
96
+ end
97
+
98
+ def generate_id
99
+ Engine.random
100
+ end
101
+
102
+ def flush(client_id)
103
+ return unless client_id
104
+ debug 'Flushing connection for ?', client_id
105
+ conn = connection(client_id, false)
106
+ conn.flush!(true) if conn
107
+ end
108
+
109
+ def disconnect
110
+ @engine.disconnect if @engine.respond_to?(:disconnect)
111
+ end
112
+
113
+ def publish(message)
114
+ channels = Channel.expand(message['channel'])
115
+ @engine.publish(message, channels)
116
+ end
117
+ end
118
+
119
+ end
120
+ end
121
+
@@ -0,0 +1,49 @@
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
49
+
@@ -0,0 +1,47 @@
1
+ module Faye
2
+ module Logging
3
+
4
+ DEFAULT_LOG_LEVEL = :error
5
+
6
+ LOG_LEVELS = {
7
+ :error => 3,
8
+ :warn => 2,
9
+ :info => 1,
10
+ :debug => 0
11
+ }
12
+
13
+ class << self
14
+ attr_writer :log_level
15
+
16
+ def log_level
17
+ @log_level || DEFAULT_LOG_LEVEL
18
+ end
19
+ end
20
+
21
+ attr_writer :log_level
22
+
23
+ def log_level
24
+ @log_level || Logging.log_level
25
+ end
26
+
27
+ def log(message_args, level)
28
+ return unless Faye.logger
29
+ return if LOG_LEVELS[log_level] > LOG_LEVELS[level]
30
+
31
+ message = message_args.shift.gsub(/\?/) do
32
+ Faye.to_json(message_args.shift)
33
+ end
34
+
35
+ timestamp = Time.now.strftime('%Y-%m-%d %H:%M:%S')
36
+ banner = " [#{ level.to_s.upcase }] [#{ self.class.name }] "
37
+
38
+ Faye.logger.call(timestamp + banner + message)
39
+ end
40
+
41
+ LOG_LEVELS.each do |level, value|
42
+ define_method(level) { |*args| log(args, level) }
43
+ end
44
+
45
+ end
46
+ end
47
+
@@ -0,0 +1,30 @@
1
+ module Faye
2
+ module Publisher
3
+
4
+ def count_listeners(event_type)
5
+ return 0 unless @subscribers and @subscribers[event_type]
6
+ @subscribers[event_type].size
7
+ end
8
+
9
+ def bind(event_type, &listener)
10
+ @subscribers ||= {}
11
+ list = @subscribers[event_type] ||= []
12
+ list << listener
13
+ end
14
+
15
+ def unbind(event_type, &listener)
16
+ return unless @subscribers and @subscribers[event_type]
17
+ return @subscribers.delete(event_type) unless listener
18
+
19
+ @subscribers[event_type].delete_if(&listener.method(:==))
20
+ end
21
+
22
+ def trigger(event_type, *args)
23
+ return unless @subscribers and @subscribers[event_type]
24
+ listeners = @subscribers[event_type].dup
25
+ listeners.each { |listener| listener.call(*args) }
26
+ end
27
+
28
+ end
29
+ end
30
+
@@ -0,0 +1,22 @@
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
+ end
21
+ end
22
+
@@ -0,0 +1,124 @@
1
+ module Faye
2
+ class Channel
3
+
4
+ include Publisher
5
+ attr_reader :name
6
+
7
+ def initialize(name)
8
+ @name = name
9
+ end
10
+
11
+ def <<(message)
12
+ trigger(:message, message)
13
+ end
14
+
15
+ def unused?
16
+ count_listeners(:message).zero?
17
+ end
18
+
19
+ HANDSHAKE = '/meta/handshake'
20
+ CONNECT = '/meta/connect'
21
+ SUBSCRIBE = '/meta/subscribe'
22
+ UNSUBSCRIBE = '/meta/unsubscribe'
23
+ DISCONNECT = '/meta/disconnect'
24
+
25
+ META = 'meta'
26
+ SERVICE = 'service'
27
+
28
+ class << self
29
+ def expand(name)
30
+ segments = parse(name)
31
+ channels = ['/**', name]
32
+
33
+ copy = segments.dup
34
+ copy[copy.size - 1] = '*'
35
+ channels << unparse(copy)
36
+
37
+ 1.upto(segments.size - 1) do |i|
38
+ copy = segments[0...i]
39
+ copy << '**'
40
+ channels << unparse(copy)
41
+ end
42
+
43
+ channels
44
+ end
45
+
46
+ def valid?(name)
47
+ Grammar::CHANNEL_NAME =~ name or
48
+ Grammar::CHANNEL_PATTERN =~ name
49
+ end
50
+
51
+ def parse(name)
52
+ return nil unless valid?(name)
53
+ name.split('/')[1..-1]
54
+ end
55
+
56
+ def unparse(segments)
57
+ '/' + segments.join('/')
58
+ end
59
+
60
+ def meta?(name)
61
+ segments = parse(name)
62
+ segments ? (segments.first == META) : nil
63
+ end
64
+
65
+ def service?(name)
66
+ segments = parse(name)
67
+ segments ? (segments.first == SERVICE) : nil
68
+ end
69
+
70
+ def subscribable?(name)
71
+ return nil unless valid?(name)
72
+ not meta?(name) and not service?(name)
73
+ end
74
+ end
75
+
76
+ class Set
77
+ def initialize(parent = nil, value = nil)
78
+ @channels = {}
79
+ end
80
+
81
+ def keys
82
+ @channels.keys
83
+ end
84
+
85
+ def remove(name)
86
+ @channels.delete(name)
87
+ end
88
+
89
+ def has_subscription?(name)
90
+ @channels.has_key?(name)
91
+ end
92
+
93
+ def subscribe(names, callback)
94
+ return unless callback
95
+ names.each do |name|
96
+ channel = @channels[name] ||= Channel.new(name)
97
+ channel.bind(:message, &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
124
+