faye 0.3.1 → 0.3.2
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 +12 -0
- data/Manifest.txt +5 -0
- data/README.txt +25 -7
- data/Rakefile +1 -0
- data/build/faye-client-min.js +1 -1
- data/build/faye.js +367 -186
- data/examples/node/faye-client-min.js +1 -1
- data/examples/node/faye.js +367 -186
- data/examples/shared/public/index.html +1 -0
- data/examples/shared/public/mootools.js +4329 -0
- data/examples/shared/public/prototype.js +4874 -0
- data/jake.yml +3 -1
- data/lib/faye-client-min.js +1 -1
- data/lib/faye.rb +5 -2
- data/lib/faye/channel.rb +4 -0
- data/lib/faye/client.rb +53 -29
- data/lib/faye/connection.rb +16 -37
- data/lib/faye/rack_adapter.rb +33 -28
- data/lib/faye/server.rb +24 -23
- data/lib/faye/timeouts.rb +21 -0
- data/lib/faye/transport.rb +17 -4
- data/test/scenario.js +73 -37
- data/test/scenario.rb +104 -0
- data/test/test_channel.rb +7 -1
- data/test/test_clients.js +28 -10
- data/test/test_clients.rb +154 -0
- data/test/test_grammar.rb +1 -1
- data/test/test_server.rb +8 -7
- metadata +17 -11
@@ -0,0 +1,21 @@
|
|
1
|
+
module Faye
|
2
|
+
module Timeouts
|
3
|
+
def add_timeout(name, delay, &block)
|
4
|
+
@timeouts ||= {}
|
5
|
+
return if @timeouts.has_key?(name)
|
6
|
+
@timeouts[name] = EventMachine.add_timer(delay) do
|
7
|
+
@timeouts.delete(name)
|
8
|
+
block.call
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
def remove_timeout(name)
|
13
|
+
@timeouts ||= {}
|
14
|
+
timeout = @timeouts[name]
|
15
|
+
return if timeout.nil?
|
16
|
+
EventMachine.cancel_timer(timeout)
|
17
|
+
@timeouts.delete(name)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
data/lib/faye/transport.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
require 'em-http'
|
2
2
|
require 'json'
|
3
|
+
require 'uri'
|
3
4
|
|
4
5
|
module Faye
|
5
6
|
|
@@ -20,10 +21,11 @@ module Faye
|
|
20
21
|
|
21
22
|
request(message) { |responses|
|
22
23
|
if block_given?
|
24
|
+
messages, deliverable = [], true
|
23
25
|
[responses].flatten.each do |response|
|
24
26
|
|
25
27
|
if message.is_a?(Hash) and response['id'] == message['id']
|
26
|
-
block.call(response)
|
28
|
+
deliverable = false if block.call(response) == false
|
27
29
|
end
|
28
30
|
|
29
31
|
if response['advice']
|
@@ -31,10 +33,12 @@ module Faye
|
|
31
33
|
end
|
32
34
|
|
33
35
|
if response['data'] and response['channel']
|
34
|
-
|
36
|
+
messages << response
|
35
37
|
end
|
36
38
|
|
37
39
|
end
|
40
|
+
|
41
|
+
@client.deliver_messages(messages) if deliverable
|
38
42
|
end
|
39
43
|
}
|
40
44
|
end
|
@@ -77,8 +81,17 @@ module Faye
|
|
77
81
|
end
|
78
82
|
|
79
83
|
def request(message, &block)
|
80
|
-
|
81
|
-
|
84
|
+
content = JSON.unparse(message)
|
85
|
+
params = {
|
86
|
+
:head => {
|
87
|
+
'Content-Type' => 'application/json',
|
88
|
+
'host' => URI.parse(@endpoint).host,
|
89
|
+
'Content-Length' => content.length
|
90
|
+
},
|
91
|
+
:body => content,
|
92
|
+
:timeout => -1
|
93
|
+
}
|
94
|
+
request = EventMachine::HttpRequest.new(@endpoint).post(params)
|
82
95
|
request.callback do
|
83
96
|
block.call(JSON.parse(request.response))
|
84
97
|
end
|
data/test/scenario.js
CHANGED
@@ -4,42 +4,46 @@ var sys = require('sys'),
|
|
4
4
|
assert = require('assert'),
|
5
5
|
faye = require('../build/faye');
|
6
6
|
|
7
|
-
|
8
|
-
|
7
|
+
Faye.Logging.logLevel = 'info';
|
8
|
+
|
9
|
+
AsyncScenario = Faye.Class({
|
10
|
+
initialize: function(name) {
|
9
11
|
this._name = name;
|
10
|
-
this._block = block;
|
11
12
|
this._clients = {};
|
12
13
|
this._inbox = {};
|
13
14
|
this._pool = 0;
|
14
15
|
},
|
15
16
|
|
16
|
-
|
17
|
-
|
18
|
-
sys.puts('----------------------------------------------------------------');
|
19
|
-
this._block.call(this);
|
17
|
+
wait: function(time, Continue) {
|
18
|
+
setTimeout(Continue, 1000 * time);
|
20
19
|
},
|
21
20
|
|
22
|
-
server: function(port) {
|
23
|
-
sys.puts('Starting server on port ' + port);
|
21
|
+
server: function(port, Continue) {
|
24
22
|
this._endpoint = 'http://0.0.0.0:' + port + '/comet';
|
25
23
|
var comet = this._comet = new faye.NodeAdapter({mount: '/comet', timeout: 30});
|
26
24
|
this._server = http.createServer(function(request, response) {
|
27
25
|
comet.call(request, response);
|
28
26
|
});
|
29
27
|
this._server.listen(port);
|
28
|
+
Continue();
|
29
|
+
},
|
30
|
+
|
31
|
+
killServer: function(Continue) {
|
32
|
+
if (this._server) this._server.close();
|
33
|
+
this._server = undefined;
|
34
|
+
setTimeout(Continue, 500);
|
30
35
|
},
|
31
36
|
|
32
|
-
httpClient: function(name, channels) {
|
33
|
-
this._setupClient(new faye.Client(this._endpoint), name, channels);
|
37
|
+
httpClient: function(name, channels, Continue) {
|
38
|
+
this._setupClient(new faye.Client(this._endpoint, {timeout: 5}), name, channels, Continue);
|
34
39
|
},
|
35
40
|
|
36
|
-
localClient: function(name, channels) {
|
37
|
-
this._setupClient(this._comet.getClient(), name, channels);
|
41
|
+
localClient: function(name, channels, Continue) {
|
42
|
+
this._setupClient(this._comet.getClient(), name, channels, Continue);
|
38
43
|
},
|
39
44
|
|
40
|
-
_setupClient: function(client, name, channels) {
|
45
|
+
_setupClient: function(client, name, channels, Continue) {
|
41
46
|
Faye.each(channels, function(channel) {
|
42
|
-
sys.puts('Client ' + name + ' subscribing to ' + channel);
|
43
47
|
client.subscribe(channel, function(message) {
|
44
48
|
var box = this._inbox[name];
|
45
49
|
box[channel] = box[channel] || [];
|
@@ -49,35 +53,67 @@ Scenario = Faye.Class({
|
|
49
53
|
this._clients[name] = client;
|
50
54
|
this._inbox[name] = {};
|
51
55
|
this._pool += 1;
|
56
|
+
setTimeout(Continue, 500 * channels.length);
|
52
57
|
},
|
53
58
|
|
54
|
-
|
55
|
-
|
56
|
-
setTimeout(
|
57
|
-
var displayMessage = JSON.stringify(message);
|
58
|
-
sys.puts('Client ' + from + ' publishing ' + displayMessage + ' to ' + channel);
|
59
|
-
self._clients[from].publish(channel, message);
|
60
|
-
}, 500);
|
59
|
+
publish: function(from, channel, message, Continue) {
|
60
|
+
this._clients[from].publish(channel, message);
|
61
|
+
setTimeout(Continue, 500);
|
61
62
|
},
|
62
63
|
|
63
|
-
checkInbox: function(expectedInbox) {
|
64
|
-
|
65
|
-
|
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);
|
64
|
+
checkInbox: function(expectedInbox, Continue) {
|
65
|
+
assert.deepEqual(this._inbox, expectedInbox);
|
66
|
+
Continue();
|
72
67
|
},
|
73
68
|
|
74
|
-
|
75
|
-
|
76
|
-
|
69
|
+
finish: function(Continue) {
|
70
|
+
Faye.each(this._clients, function(name, client) { client.disconnect() });
|
71
|
+
this._server.close();
|
72
|
+
Continue();
|
73
|
+
},
|
74
|
+
});
|
75
|
+
|
76
|
+
SyncScenario = Faye.Class({
|
77
|
+
initialize: function(name, block) {
|
78
|
+
this._name = name;
|
79
|
+
this._commands = [];
|
80
|
+
this._scenario = new AsyncScenario();
|
81
|
+
block.call(this);
|
82
|
+
},
|
83
|
+
|
84
|
+
run: function() {
|
85
|
+
sys.puts('\n' + this._name);
|
86
|
+
sys.puts('----------------------------------------------------------------');
|
87
|
+
this._runNextCommand();
|
88
|
+
},
|
89
|
+
|
90
|
+
_runNextCommand: function() {
|
91
|
+
if (this._commands.length === 0) return this._finish();
|
92
|
+
|
93
|
+
var command = this._commands.shift(),
|
94
|
+
method = command[0],
|
95
|
+
args = Array.prototype.slice.call(command[1]),
|
96
|
+
self = this;
|
97
|
+
|
98
|
+
this._scenario[method].apply(this._scenario, args.concat(function() {
|
99
|
+
self._runNextCommand();
|
100
|
+
}));
|
101
|
+
},
|
102
|
+
|
103
|
+
_finish: function() {
|
104
|
+
sys.puts('No errors; Shutting down server\n');
|
105
|
+
this._scenario.finish(function() { SyncScenario.runNext() });
|
106
|
+
}
|
107
|
+
});
|
108
|
+
|
109
|
+
['wait', 'server', 'killServer', 'httpClient', 'localClient', 'publish', 'checkInbox'].
|
110
|
+
forEach(function(method) {
|
111
|
+
SyncScenario.prototype[method] = function() {
|
112
|
+
this._commands.push([method, arguments]);
|
77
113
|
}
|
78
114
|
});
|
79
115
|
|
80
|
-
Faye.extend(
|
116
|
+
Faye.extend(SyncScenario, {
|
81
117
|
_queue: [],
|
82
118
|
|
83
119
|
enqueue: function(name, block) {
|
@@ -93,7 +129,7 @@ Faye.extend(Scenario, {
|
|
93
129
|
});
|
94
130
|
|
95
131
|
exports.run = function(name, block) {
|
96
|
-
|
97
|
-
if (!
|
132
|
+
SyncScenario.enqueue(name, block);
|
133
|
+
if (!SyncScenario.running) SyncScenario.runNext();
|
98
134
|
};
|
99
135
|
|
data/test/scenario.rb
ADDED
@@ -0,0 +1,104 @@
|
|
1
|
+
$VERBOSE = false
|
2
|
+
|
3
|
+
module Scenario
|
4
|
+
module ClassMethods
|
5
|
+
def scenario(name, &block)
|
6
|
+
define_method("test: #{name}", &block)
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.included(klass)
|
11
|
+
klass.extend ClassMethods
|
12
|
+
end
|
13
|
+
|
14
|
+
def setup
|
15
|
+
Thread.new { EM.run }
|
16
|
+
while not EM.reactor_running?; end
|
17
|
+
@scenario = AsyncScenario.new
|
18
|
+
@commands = []
|
19
|
+
@started = false
|
20
|
+
end
|
21
|
+
|
22
|
+
def teardown
|
23
|
+
while EM.reactor_running?; end
|
24
|
+
end
|
25
|
+
|
26
|
+
def run(runner)
|
27
|
+
@runner = runner
|
28
|
+
super
|
29
|
+
end
|
30
|
+
|
31
|
+
def method_missing(sym, *args)
|
32
|
+
@commands << [sym, args]
|
33
|
+
EM.next_tick { run_next_command unless @started }
|
34
|
+
end
|
35
|
+
|
36
|
+
def run_next_command
|
37
|
+
@started = true
|
38
|
+
command = @commands.shift
|
39
|
+
return EM.stop if command.nil?
|
40
|
+
begin
|
41
|
+
@scenario.__send__(command.first, *command.last) do
|
42
|
+
run_next_command
|
43
|
+
end
|
44
|
+
rescue Object => e
|
45
|
+
@passed = false
|
46
|
+
add_failure(e.message, e.backtrace)
|
47
|
+
@runner.puke(self.class, self.name, e) if @runner.respond_to?(:puke)
|
48
|
+
block.call
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
class AsyncScenario
|
53
|
+
include Test::Unit::Assertions
|
54
|
+
|
55
|
+
def initialize
|
56
|
+
@clients = {}
|
57
|
+
@inbox = {}
|
58
|
+
@pool = 0
|
59
|
+
end
|
60
|
+
|
61
|
+
def check_inbox(expected_inbox, &block)
|
62
|
+
assert_equal expected_inbox, @inbox
|
63
|
+
block.call
|
64
|
+
end
|
65
|
+
|
66
|
+
def server(port, &block)
|
67
|
+
@endpoint = "http://0.0.0.0:#{port}/comet"
|
68
|
+
@comet = Faye::RackAdapter.new(:mount => '/comet', :timeout => 30)
|
69
|
+
Rack::Handler.get('thin').run(@comet, :Port => port) do |server|
|
70
|
+
@server = server
|
71
|
+
EM.next_tick(&block)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def http_client(name, channels, &block)
|
76
|
+
setup_client(Faye::Client.new(@endpoint), name, channels, &block)
|
77
|
+
end
|
78
|
+
|
79
|
+
def local_client(name, channels, &block)
|
80
|
+
setup_client(@comet.get_client, name, channels, &block)
|
81
|
+
end
|
82
|
+
|
83
|
+
def setup_client(client, name, channels, &block)
|
84
|
+
channels.each do |channel|
|
85
|
+
client.subscribe(channel) do |message|
|
86
|
+
box = @inbox[name]
|
87
|
+
box[channel] ||= []
|
88
|
+
box[channel] << message
|
89
|
+
end
|
90
|
+
end
|
91
|
+
@clients[name] = client
|
92
|
+
@inbox[name] = {}
|
93
|
+
@pool += 1
|
94
|
+
EM.add_timer(0.5 * channels.size, &block)
|
95
|
+
end
|
96
|
+
|
97
|
+
def publish(from, channel, message, &block)
|
98
|
+
@clients[from].publish(channel, message)
|
99
|
+
EM.add_timer(2, &block)
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
end
|
104
|
+
|
data/test/test_channel.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
require "test/unit"
|
2
|
-
require "faye"
|
2
|
+
require File.dirname(__FILE__) + "/../lib/faye"
|
3
3
|
|
4
4
|
class TestChannel < Test::Unit::TestCase
|
5
5
|
include Faye
|
@@ -15,6 +15,12 @@ class TestChannel < Test::Unit::TestCase
|
|
15
15
|
assert_equal 3, tree['/va()$$lid/name']
|
16
16
|
end
|
17
17
|
|
18
|
+
def test_keys
|
19
|
+
tree = Channel::Tree.new
|
20
|
+
tree['/foo'] = tree['/bar'] = tree['/foo/bar'] = true
|
21
|
+
assert_equal %w[/bar /foo /foo/bar], tree.keys.sort
|
22
|
+
end
|
23
|
+
|
18
24
|
def test_globbing
|
19
25
|
tree = Channel::Tree.new
|
20
26
|
tree['/foo/bar'] = 1
|
data/test/test_clients.js
CHANGED
@@ -20,12 +20,30 @@ var Scenario = require('./scenario'),
|
|
20
20
|
assert.deepEqual(tree.glob('/channels/**').sort(), ['A','B','C']);
|
21
21
|
})();
|
22
22
|
|
23
|
+
Scenario.run("Server goes away, subscriptions should be revived",
|
24
|
+
function() { with(this) {
|
25
|
+
server(8000);
|
26
|
+
httpClient('A', ['/channels/a']);
|
27
|
+
httpClient('B', []);
|
28
|
+
killServer();
|
29
|
+
wait(6);
|
30
|
+
server(8000);
|
31
|
+
wait(22);
|
32
|
+
publish('B', '/channels/a', {hello: 'world'});
|
33
|
+
checkInbox({
|
34
|
+
A: {
|
35
|
+
'/channels/a': [{hello: 'world'}]
|
36
|
+
},
|
37
|
+
B: {}
|
38
|
+
});
|
39
|
+
}});
|
40
|
+
|
23
41
|
Scenario.run("Two HTTP clients, no messages delivered",
|
24
42
|
function() { with(this) {
|
25
43
|
server(8000);
|
26
44
|
httpClient('A', ['/channels/a']);
|
27
45
|
httpClient('B', []);
|
28
|
-
|
46
|
+
publish('B', '/channels/b', {hello: 'world'});
|
29
47
|
checkInbox({
|
30
48
|
A: {},
|
31
49
|
B: {}
|
@@ -37,7 +55,7 @@ function() { with(this) {
|
|
37
55
|
server(8000);
|
38
56
|
httpClient('A', ['/channels/a']);
|
39
57
|
httpClient('B', []);
|
40
|
-
|
58
|
+
publish('B', '/channels/a', {hello: 'world'});
|
41
59
|
checkInbox({
|
42
60
|
A: {
|
43
61
|
'/channels/a': [{hello: 'world'}]
|
@@ -51,7 +69,7 @@ function() { with(this) {
|
|
51
69
|
server(8000);
|
52
70
|
httpClient('A', ['/channels/a', '/channels/*']);
|
53
71
|
httpClient('B', []);
|
54
|
-
|
72
|
+
publish('B', '/channels/a', {hello: 'world'});
|
55
73
|
checkInbox({
|
56
74
|
A: {
|
57
75
|
'/channels/a': [{hello: 'world'}],
|
@@ -67,7 +85,7 @@ function() { with(this) {
|
|
67
85
|
httpClient('A', ['/channels/a']);
|
68
86
|
httpClient('B', []);
|
69
87
|
httpClient('C', ['/channels/c']);
|
70
|
-
|
88
|
+
publish('B', '/channels/a', {chunky: 'bacon'});
|
71
89
|
checkInbox({
|
72
90
|
A: {
|
73
91
|
'/channels/a': [{chunky: 'bacon'}]
|
@@ -83,7 +101,7 @@ function() { with(this) {
|
|
83
101
|
httpClient('A', ['/channels/shared']);
|
84
102
|
httpClient('B', []);
|
85
103
|
httpClient('C', ['/channels/shared']);
|
86
|
-
|
104
|
+
publish('B', '/channels/shared', {chunky: 'bacon'});
|
87
105
|
checkInbox({
|
88
106
|
A: {
|
89
107
|
'/channels/shared': [{chunky: 'bacon'}]
|
@@ -100,7 +118,7 @@ function() { with(this) {
|
|
100
118
|
server(8000);
|
101
119
|
httpClient('A', ['/channels/*']);
|
102
120
|
httpClient('B', []);
|
103
|
-
|
121
|
+
publish('B', '/channels/anything', {msg: 'hey'});
|
104
122
|
checkInbox({
|
105
123
|
A: {
|
106
124
|
'/channels/*': [{msg: 'hey'}]
|
@@ -114,7 +132,7 @@ function() { with(this) {
|
|
114
132
|
server(8000);
|
115
133
|
httpClient('A', ['/channels/name', '/channels/hello', '/channels/nested/hello']);
|
116
134
|
httpClient('B', []);
|
117
|
-
|
135
|
+
publish('B', '/channels/*', {msg: 'hey'});
|
118
136
|
checkInbox({
|
119
137
|
A: {
|
120
138
|
'/channels/name': [{msg: 'hey'}],
|
@@ -129,7 +147,7 @@ function() { with(this) {
|
|
129
147
|
server(8000);
|
130
148
|
httpClient('A', ['/channels/*']);
|
131
149
|
httpClient('B', []);
|
132
|
-
|
150
|
+
publish('B', '/channels/*', {msg: 'hey'});
|
133
151
|
checkInbox({
|
134
152
|
A: {
|
135
153
|
'/channels/*': [{msg: 'hey'}]
|
@@ -143,7 +161,7 @@ function() { with(this) {
|
|
143
161
|
server(8000);
|
144
162
|
localClient('A', ['/channels/name', '/channels/hello', '/channels/nested/hello']);
|
145
163
|
localClient('B', []);
|
146
|
-
|
164
|
+
publish('B', '/channels/**', {msg: 'hey'});
|
147
165
|
checkInbox({
|
148
166
|
A: {
|
149
167
|
'/channels/name': [{msg: 'hey'}],
|
@@ -160,7 +178,7 @@ function() { with(this) {
|
|
160
178
|
localClient('A', ['/channels/hello', '/channels/nested/hello']);
|
161
179
|
localClient('B', []);
|
162
180
|
httpClient('C', ['/channels/name', '/channels/foo/**']);
|
163
|
-
|
181
|
+
publish('B', '/channels/**', {msg: 'hey'});
|
164
182
|
checkInbox({
|
165
183
|
A: {
|
166
184
|
'/channels/hello': [{msg: 'hey'}],
|