faye 0.2.2 → 0.3.0

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.

Potentially problematic release.


This version of faye might be problematic. Click here for more details.

@@ -0,0 +1,16 @@
1
+ module Faye
2
+ class Namespace
3
+
4
+ def initialize
5
+ @used = {}
6
+ end
7
+
8
+ def generate
9
+ name = Faye.random
10
+ name = Faye.random while @used.has_key?(name)
11
+ @used[name] = name
12
+ end
13
+
14
+ end
15
+ end
16
+
@@ -9,6 +9,7 @@ module Faye
9
9
  ASYNC_RESPONSE = [-1, {}, []].freeze
10
10
 
11
11
  DEFAULT_ENDPOINT = '/bayeux'
12
+ SCRIPT_PATH = File.join(ROOT, 'faye-client-min.js')
12
13
 
13
14
  TYPE_JSON = {'Content-Type' => 'text/json'}
14
15
  TYPE_SCRIPT = {'Content-Type' => 'text/javascript'}
@@ -23,6 +24,15 @@ module Faye
23
24
  @server = Server.new(@options)
24
25
  end
25
26
 
27
+ def get_client
28
+ @client ||= Client.new(@server)
29
+ end
30
+
31
+ def run(port)
32
+ handler = Rack::Handler.get('thin')
33
+ handler.run(self, :Port => port)
34
+ end
35
+
26
36
  def call(env)
27
37
  request = Rack::Request.new(env)
28
38
  case request.path_info
@@ -40,11 +50,11 @@ module Faye
40
50
  response
41
51
  end
42
52
  rescue
43
- [400, {'Content-Type' => 'text/plain'}, 'Bad request']
53
+ [400, TYPE_TEXT, 'Bad request']
44
54
  end
45
55
 
46
56
  when @script then
47
- [200, TYPE_SCRIPT, File.new(CLIENT_SCRIPT)]
57
+ [200, TYPE_SCRIPT, File.new(SCRIPT_PATH)]
48
58
 
49
59
  else
50
60
  env['faye.server'] = @server
data/lib/faye/server.rb CHANGED
@@ -1,9 +1,10 @@
1
1
  module Faye
2
2
  class Server
3
3
  def initialize(options = {})
4
- @options = options
5
- @channels = Channel::Tree.new
6
- @clients = {}
4
+ @options = options
5
+ @channels = Channel::Tree.new
6
+ @clients = {}
7
+ @namespace = Namespace.new
7
8
  end
8
9
 
9
10
  # Notifies the server of stale connections that should be deleted
@@ -39,12 +40,6 @@ module Faye
39
40
 
40
41
  private
41
42
 
42
- def generate_id
43
- id = Faye.random
44
- id = Faye.random while @clients.has_key?(id)
45
- connection(id).id
46
- end
47
-
48
43
  def connection(id)
49
44
  return @clients[id] if @clients.has_key?(id)
50
45
  client = Connection.new(id, @options)
@@ -62,6 +57,8 @@ module Faye
62
57
  client_id = message['clientId']
63
58
  channel = message['channel']
64
59
 
60
+ @channels.glob(channel).each { |c| c << message }
61
+
65
62
  if Channel.meta?(channel)
66
63
  response = __send__(Channel.parse(channel)[1], message, local)
67
64
 
@@ -82,8 +79,6 @@ module Faye
82
79
 
83
80
  return callback[[]] if message['clientId'].nil? or Channel.service?(channel)
84
81
 
85
- @channels.glob(channel).each { |c| c << message }
86
-
87
82
  callback[ { 'channel' => channel,
88
83
  'successful' => true,
89
84
  'id' => message['id'] } ]
@@ -97,23 +92,27 @@ module Faye
97
92
  def handshake(message, local = false)
98
93
  response = { 'channel' => Channel::HANDSHAKE,
99
94
  'version' => BAYEUX_VERSION,
100
- 'supportedConnectionTypes' => CONNECTION_TYPES,
101
95
  'id' => message['id'] }
102
96
 
103
97
  response['error'] = Error.parameter_missing('version') if message['version'].nil?
104
98
 
105
- client_conns = message['supportedConnectionTypes']
106
- if client_conns
107
- common_conns = client_conns.select { |c| CONNECTION_TYPES.include?(c) }
108
- response['error'] = Error.conntype_mismatch(*client_conns) if common_conns.empty?
109
- else
110
- response['error'] = Error.parameter_missing('supportedConnectionTypes')
99
+ unless local
100
+ response['supportedConnectionTypes'] = CONNECTION_TYPES
101
+
102
+ client_conns = message['supportedConnectionTypes']
103
+ if client_conns
104
+ common_conns = client_conns.select { |c| CONNECTION_TYPES.include?(c) }
105
+ response['error'] = Error.conntype_mismatch(*client_conns) if common_conns.empty?
106
+ else
107
+ response['error'] = Error.parameter_missing('supportedConnectionTypes')
108
+ end
111
109
  end
112
110
 
113
111
  response['successful'] = response['error'].nil?
114
112
  return response unless response['successful']
115
113
 
116
- response['clientId'] = generate_id
114
+ client_id = @namespace.generate
115
+ response['clientId'] = connection(client_id).id
117
116
  response
118
117
  end
119
118
 
@@ -184,7 +183,7 @@ module Faye
184
183
 
185
184
  subscription.each do |channel|
186
185
  next if response['error']
187
- response['error'] = Error.channel_forbidden(channel) unless Channel.subscribable?(channel)
186
+ response['error'] = Error.channel_forbidden(channel) unless local or Channel.subscribable?(channel)
188
187
  response['error'] = Error.channel_invalid(channel) unless Channel.valid?(channel)
189
188
 
190
189
  next if response['error']
@@ -0,0 +1,103 @@
1
+ require 'em-http'
2
+ require 'json'
3
+
4
+ module Faye
5
+
6
+ class Transport
7
+ def initialize(client, endpoint)
8
+ @client = client
9
+ @endpoint = endpoint
10
+ end
11
+
12
+ def connection_type
13
+ self.class.connection_type
14
+ end
15
+
16
+ def send(message, &block)
17
+ if message.is_a?(Hash) and not message.has_key?('id')
18
+ message['id'] = @client.namespace.generate
19
+ end
20
+
21
+ request(message) { |responses|
22
+ if block_given?
23
+ [responses].flatten.each do |response|
24
+
25
+ if message.is_a?(Hash) and response['id'] == message['id']
26
+ block.call(response)
27
+ end
28
+
29
+ if response['advice']
30
+ @client.handle_advice(response['advice'])
31
+ end
32
+
33
+ if response['data'] and response['channel']
34
+ @client.send_to_subscribers(response)
35
+ end
36
+
37
+ end
38
+ end
39
+ }
40
+ end
41
+
42
+ @transports = {}
43
+
44
+ class << self
45
+ attr_accessor :connection_type
46
+
47
+ def get(client, connection_types = nil)
48
+ endpoint = client.endpoint
49
+ connection_types ||= supported_connection_types
50
+
51
+ candidate_class = @transports.find do |type, klass|
52
+ connection_types.include?(type) and
53
+ klass.usable?(endpoint)
54
+ end
55
+
56
+ unless candidate_class
57
+ raise "Could not find a usable connection type for #{ endpoint }"
58
+ end
59
+
60
+ candidate_class.last.new(client, endpoint)
61
+ end
62
+
63
+ def register(type, klass)
64
+ @transports[type] = klass
65
+ klass.connection_type = type
66
+ end
67
+
68
+ def supported_connection_types
69
+ @transports.keys
70
+ end
71
+ end
72
+ end
73
+
74
+ class HttpTransport < Transport
75
+ def self.usable?(endpoint)
76
+ endpoint.is_a?(String)
77
+ end
78
+
79
+ def request(message, &block)
80
+ params = {:message => JSON.unparse(message)}
81
+ request = EventMachine::HttpRequest.new(@endpoint).post(:body => params, :timeout => -1)
82
+ request.callback do
83
+ block.call(JSON.parse(request.response))
84
+ end
85
+ end
86
+ end
87
+ Transport.register 'long-polling', HttpTransport
88
+
89
+ class LocalTransport < Transport
90
+ def self.usable?(endpoint)
91
+ endpoint.is_a?(Server)
92
+ end
93
+
94
+ def request(message, &block)
95
+ @endpoint.process(message, true) do |response|
96
+ block.call(response)
97
+ end
98
+ end
99
+ end
100
+ Transport.register 'in-process', LocalTransport
101
+
102
+ end
103
+
data/test/scenario.js ADDED
@@ -0,0 +1,99 @@
1
+ var sys = require('sys'),
2
+ path = require('path'),
3
+ http = require('http'),
4
+ assert = require('assert'),
5
+ faye = require('../build/faye');
6
+
7
+ Scenario = Faye.Class({
8
+ initialize: function(name, block) {
9
+ this._name = name;
10
+ this._block = block;
11
+ this._clients = {};
12
+ this._inbox = {};
13
+ this._pool = 0;
14
+ },
15
+
16
+ run: function() {
17
+ sys.puts('\n' + this._name);
18
+ sys.puts('----------------------------------------------------------------');
19
+ this._block.call(this);
20
+ },
21
+
22
+ server: function(port) {
23
+ sys.puts('Starting server on port ' + port);
24
+ this._endpoint = 'http://0.0.0.0:' + port + '/comet';
25
+ var comet = this._comet = new faye.NodeAdapter({mount: '/comet', timeout: 30});
26
+ this._server = http.createServer(function(request, response) {
27
+ comet.call(request, response);
28
+ });
29
+ this._server.listen(port);
30
+ },
31
+
32
+ httpClient: function(name, channels) {
33
+ this._setupClient(new faye.Client(this._endpoint), name, channels);
34
+ },
35
+
36
+ localClient: function(name, channels) {
37
+ this._setupClient(this._comet.getClient(), name, channels);
38
+ },
39
+
40
+ _setupClient: function(client, name, channels) {
41
+ Faye.each(channels, function(channel) {
42
+ sys.puts('Client ' + name + ' subscribing to ' + channel);
43
+ client.subscribe(channel, function(message) {
44
+ var box = this._inbox[name];
45
+ box[channel] = box[channel] || [];
46
+ box[channel].push(message);
47
+ }, this);
48
+ }, this);
49
+ this._clients[name] = client;
50
+ this._inbox[name] = {};
51
+ this._pool += 1;
52
+ },
53
+
54
+ send: function(from, channel, message) {
55
+ var self = this;
56
+ setTimeout(function() {
57
+ var displayMessage = JSON.stringify(message);
58
+ sys.puts('Client ' + from + ' publishing ' + displayMessage + ' to ' + channel);
59
+ self._clients[from].publish(channel, message);
60
+ }, 500);
61
+ },
62
+
63
+ checkInbox: function(expectedInbox) {
64
+ var self = this;
65
+ setTimeout(function() {
66
+ self._checkInbox(expectedInbox);
67
+ sys.puts('Shutting down server\n');
68
+ Faye.each(this._clients, function(name, client) { client.disconnect() });
69
+ self._server.close();
70
+ Scenario.runNext();
71
+ }, 1000);
72
+ },
73
+
74
+ _checkInbox: function(expectedInbox) {
75
+ sys.puts(JSON.stringify(this._inbox));
76
+ assert.deepEqual(this._inbox, expectedInbox);
77
+ }
78
+ });
79
+
80
+ Faye.extend(Scenario, {
81
+ _queue: [],
82
+
83
+ enqueue: function(name, block) {
84
+ this._queue.push(new this(name, block));
85
+ },
86
+
87
+ runNext: function() {
88
+ this.running = true;
89
+ var scenario = this._queue.shift();
90
+ if (!scenario) return;
91
+ scenario.run();
92
+ }
93
+ });
94
+
95
+ exports.run = function(name, block) {
96
+ Scenario.enqueue(name, block);
97
+ if (!Scenario.running) Scenario.runNext();
98
+ };
99
+
data/test/test_channel.rb CHANGED
@@ -16,19 +16,25 @@ class TestChannel < Test::Unit::TestCase
16
16
  end
17
17
 
18
18
  def test_globbing
19
- globber = Channel::Tree.new
20
- globber['/foo/bar'] = 1
21
- globber['/foo/boo'] = 2
22
- globber['/foo'] = 3
23
- globber['/foobar'] = 4
24
- globber['/foo/bar/boo'] = 5
25
- globber['/foobar/boo'] = 6
26
- globber['/foo/*'] = 7
27
- globber['/foo/**'] = 8
19
+ tree = Channel::Tree.new
20
+ tree['/foo/bar'] = 1
21
+ tree['/foo/boo'] = 2
22
+ tree['/foo'] = 3
23
+ tree['/foobar'] = 4
24
+ tree['/foo/bar/boo'] = 5
25
+ tree['/foobar/boo'] = 6
26
+ tree['/foo/*'] = 7
27
+ tree['/foo/**'] = 8
28
+
29
+ assert_equal [1,2,7,8], tree.glob('/foo/*').sort
30
+ assert_equal [1,7,8], tree.glob('/foo/bar').sort
31
+ assert_equal [1,2,5,7,8], tree.glob('/foo/**').sort
32
+ assert_equal [5,8], tree.glob('/foo/bar/boo').sort
33
+
34
+ tree['/channels/hello'] = 'A'
35
+ tree['/channels/name'] = 'B'
36
+ tree['/channels/nested/hello'] = 'C'
28
37
 
29
- assert_equal [1,2,7,8], globber.glob('/foo/*').sort
30
- assert_equal [1,7,8], globber.glob('/foo/bar').sort
31
- assert_equal [1,2,5,7,8], globber.glob('/foo/**').sort
32
- assert_equal [5,8], globber.glob('/foo/bar/boo').sort
38
+ assert_equal %w[A B C], tree.glob('/channels/**').sort
33
39
  end
34
40
  end
@@ -0,0 +1,177 @@
1
+ var Scenario = require('./scenario'),
2
+ faye = require('../build/faye'),
3
+ assert = require('assert');
4
+
5
+ (function() {
6
+ var tree = new Faye.Channel.Tree();
7
+ var list = '/foo/bar /foo/boo /foo /foobar /foo/bar/boo /foobar/boo /foo/* /foo/**'.split(' ');
8
+
9
+ Faye.each(list, function(c, i) { tree.set(c, i + 1) });
10
+
11
+ assert.deepEqual(tree.glob('/foo/*').sort(), [1,2,7,8]);
12
+ assert.deepEqual(tree.glob('/foo/bar').sort(), [1,7,8]);
13
+ assert.deepEqual(tree.glob('/foo/**').sort(), [1,2,5,7,8]);
14
+ assert.deepEqual(tree.glob('/foo/bar/boo').sort(), [5,8]);
15
+
16
+ tree.set('/channels/hello', 'A');
17
+ tree.set('/channels/name', 'B');
18
+ tree.set('/channels/nested/hello', 'C');
19
+
20
+ assert.deepEqual(tree.glob('/channels/**').sort(), ['A','B','C']);
21
+ })();
22
+
23
+ Scenario.run("Two HTTP clients, no messages delivered",
24
+ function() { with(this) {
25
+ server(8000);
26
+ httpClient('A', ['/channels/a']);
27
+ httpClient('B', []);
28
+ send('B', '/channels/b', {hello: 'world'});
29
+ checkInbox({
30
+ A: {},
31
+ B: {}
32
+ });
33
+ }});
34
+
35
+ Scenario.run("Two HTTP clients, single subscription",
36
+ function() { with(this) {
37
+ server(8000);
38
+ httpClient('A', ['/channels/a']);
39
+ httpClient('B', []);
40
+ send('B', '/channels/a', {hello: 'world'});
41
+ checkInbox({
42
+ A: {
43
+ '/channels/a': [{hello: 'world'}]
44
+ },
45
+ B: {}
46
+ });
47
+ }});
48
+
49
+ Scenario.run("Two HTTP clients, multiple subscriptions",
50
+ function() { with(this) {
51
+ server(8000);
52
+ httpClient('A', ['/channels/a', '/channels/*']);
53
+ httpClient('B', []);
54
+ send('B', '/channels/a', {hello: 'world'});
55
+ checkInbox({
56
+ A: {
57
+ '/channels/a': [{hello: 'world'}],
58
+ '/channels/*': [{hello: 'world'}]
59
+ },
60
+ B: {}
61
+ });
62
+ }});
63
+
64
+ Scenario.run("Three HTTP clients, single receiver",
65
+ function() { with(this) {
66
+ server(8000);
67
+ httpClient('A', ['/channels/a']);
68
+ httpClient('B', []);
69
+ httpClient('C', ['/channels/c']);
70
+ send('B', '/channels/a', {chunky: 'bacon'});
71
+ checkInbox({
72
+ A: {
73
+ '/channels/a': [{chunky: 'bacon'}]
74
+ },
75
+ B: {},
76
+ C: {}
77
+ });
78
+ }});
79
+
80
+ Scenario.run("Three HTTP clients, multiple receivers",
81
+ function() { with(this) {
82
+ server(8000);
83
+ httpClient('A', ['/channels/shared']);
84
+ httpClient('B', []);
85
+ httpClient('C', ['/channels/shared']);
86
+ send('B', '/channels/shared', {chunky: 'bacon'});
87
+ checkInbox({
88
+ A: {
89
+ '/channels/shared': [{chunky: 'bacon'}]
90
+ },
91
+ B: {},
92
+ C: {
93
+ '/channels/shared': [{chunky: 'bacon'}]
94
+ }
95
+ });
96
+ }});
97
+
98
+ Scenario.run("Two HTTP clients, single wildcard on receiver",
99
+ function() { with(this) {
100
+ server(8000);
101
+ httpClient('A', ['/channels/*']);
102
+ httpClient('B', []);
103
+ send('B', '/channels/anything', {msg: 'hey'});
104
+ checkInbox({
105
+ A: {
106
+ '/channels/*': [{msg: 'hey'}]
107
+ },
108
+ B: {}
109
+ });
110
+ }});
111
+
112
+ Scenario.run("Two HTTP clients, single wildcard on sender",
113
+ function() { with(this) {
114
+ server(8000);
115
+ httpClient('A', ['/channels/name', '/channels/hello', '/channels/nested/hello']);
116
+ httpClient('B', []);
117
+ send('B', '/channels/*', {msg: 'hey'});
118
+ checkInbox({
119
+ A: {
120
+ '/channels/name': [{msg: 'hey'}],
121
+ '/channels/hello': [{msg: 'hey'}]
122
+ },
123
+ B: {}
124
+ });
125
+ }});
126
+
127
+ Scenario.run("Two HTTP clients, single wildcard on both",
128
+ function() { with(this) {
129
+ server(8000);
130
+ httpClient('A', ['/channels/*']);
131
+ httpClient('B', []);
132
+ send('B', '/channels/*', {msg: 'hey'});
133
+ checkInbox({
134
+ A: {
135
+ '/channels/*': [{msg: 'hey'}]
136
+ },
137
+ B: {}
138
+ });
139
+ }});
140
+
141
+ Scenario.run("Two local clients, double wildcard on sender",
142
+ function() { with(this) {
143
+ server(8000);
144
+ localClient('A', ['/channels/name', '/channels/hello', '/channels/nested/hello']);
145
+ localClient('B', []);
146
+ send('B', '/channels/**', {msg: 'hey'});
147
+ checkInbox({
148
+ A: {
149
+ '/channels/name': [{msg: 'hey'}],
150
+ '/channels/hello': [{msg: 'hey'}],
151
+ '/channels/nested/hello': [{msg: 'hey'}]
152
+ },
153
+ B: {}
154
+ });
155
+ }});
156
+
157
+ Scenario.run("Two local clients, one HTTP, double wildcard on sender and one subscription",
158
+ function() { with(this) {
159
+ server(8000);
160
+ localClient('A', ['/channels/hello', '/channels/nested/hello']);
161
+ localClient('B', []);
162
+ httpClient('C', ['/channels/name', '/channels/foo/**']);
163
+ send('B', '/channels/**', {msg: 'hey'});
164
+ checkInbox({
165
+ A: {
166
+ '/channels/hello': [{msg: 'hey'}],
167
+ '/channels/nested/hello': [{msg: 'hey'}]
168
+ },
169
+ B: {},
170
+ C: {
171
+ '/channels/name': [{msg: 'hey'}],
172
+ '/channels/foo/**': [{msg: 'hey'}],
173
+ '/channels/name': [{msg: 'hey'}],
174
+ }
175
+ });
176
+ }});
177
+