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.

@@ -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
+
@@ -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
- @client.send_to_subscribers(response)
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
- params = {:message => JSON.unparse(message)}
81
- request = EventMachine::HttpRequest.new(@endpoint).post(:body => params, :timeout => -1)
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
@@ -4,42 +4,46 @@ var sys = require('sys'),
4
4
  assert = require('assert'),
5
5
  faye = require('../build/faye');
6
6
 
7
- Scenario = Faye.Class({
8
- initialize: function(name, block) {
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
- run: function() {
17
- sys.puts('\n' + this._name);
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
- 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);
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
- 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);
64
+ checkInbox: function(expectedInbox, Continue) {
65
+ assert.deepEqual(this._inbox, expectedInbox);
66
+ Continue();
72
67
  },
73
68
 
74
- _checkInbox: function(expectedInbox) {
75
- sys.puts(JSON.stringify(this._inbox));
76
- assert.deepEqual(this._inbox, expectedInbox);
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(Scenario, {
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
- Scenario.enqueue(name, block);
97
- if (!Scenario.running) Scenario.runNext();
132
+ SyncScenario.enqueue(name, block);
133
+ if (!SyncScenario.running) SyncScenario.runNext();
98
134
  };
99
135
 
@@ -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
+
@@ -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
@@ -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
- send('B', '/channels/b', {hello: 'world'});
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
- send('B', '/channels/a', {hello: 'world'});
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
- send('B', '/channels/a', {hello: 'world'});
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
- send('B', '/channels/a', {chunky: 'bacon'});
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
- send('B', '/channels/shared', {chunky: 'bacon'});
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
- send('B', '/channels/anything', {msg: 'hey'});
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
- send('B', '/channels/*', {msg: 'hey'});
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
- send('B', '/channels/*', {msg: 'hey'});
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
- send('B', '/channels/**', {msg: 'hey'});
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
- send('B', '/channels/**', {msg: 'hey'});
181
+ publish('B', '/channels/**', {msg: 'hey'});
164
182
  checkInbox({
165
183
  A: {
166
184
  '/channels/hello': [{msg: 'hey'}],