meerkat 0.3.2 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- meerkat (0.3.2)
4
+ meerkat (0.4.0)
5
5
  em-hiredis
6
6
  eventmachine
7
7
  pg
data/README.md CHANGED
@@ -3,13 +3,18 @@ Meerkat
3
3
 
4
4
  Rack middleware for [Server-Sent Events (HTML5 SSE)](http://www.html5rocks.com/en/tutorials/eventsource/basics/).
5
5
 
6
- Requires an evented server, like [Thin](http://code.macournoyer.com/thin/).
6
+ Requires an [EventMachine](https://github.com/eventmachine/eventmachine#readme) backed server, like [Thin](http://code.macournoyer.com/thin/) or [Rainbows](http://rainbows.rubyforge.org/) (with the EventMachine backend only).
7
7
 
8
8
  Supported backends:
9
9
 
10
10
  * In memory, using [EventMachine Channels](http://eventmachine.rubyforge.org/EventMachine/Channel.html), good for single server usage.
11
11
  * Redis, using [em-hiredis](https://github.com/mloughran/em-hiredis#readme) and the [Pub/Sub API](http://redis.io/topics/pubsub).
12
- * Postgres, using the [Notify/Listen API](http://www.postgresql.org/docs/9.1/static/sql-notify.html). Note, this is fully async, no polling. Although, it requires PostgreSQL 9.0 or higher, so unfortunately Heroku's Shared Database can't be used (8.3) but their dedicated database offerings can.
12
+ * Postgres, using the [Notify/Listen API](http://www.postgresql.org/docs/9.1/static/sql-notify.html).
13
+ * When a message is published the topic and json payload is inserted into the 'meerkat_pubsub' table, and then a NOTIFY is issued.
14
+ * Listening clients recivies the notification and reads the message from the table and writes it to the Event Stream of its clients.
15
+ * On the next publish all messages older than 5 seconds are deleted.
16
+ * No polling is ever done.
17
+ * This works with PostgreSQL 8 and higher (tested with 8.3 and 9.1).
13
18
 
14
19
  Usage
15
20
  -----
@@ -86,4 +91,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
86
91
  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
87
92
  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
88
93
  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
89
- THE SOFTWARE.
94
+ THE SOFTWARE.
@@ -7,13 +7,15 @@ module Meerkat
7
7
  @channel = EventMachine::Channel.new
8
8
  end
9
9
 
10
- def publish(route, json)
11
- @channel.push({:route => route, :json => json})
10
+ def publish(topic, json)
11
+ @channel.push({:topic => topic, :json => json})
12
12
  end
13
13
 
14
- def subscribe(route, &callback)
14
+ def subscribe(topic, &callback)
15
15
  @channel.subscribe do |msg|
16
- callback.call(msg[:json]) if msg[:route] == route
16
+ if File.fnmatch? topic, msg[:topic]
17
+ callback.call msg[:topic], msg[:json]
18
+ end
17
19
  end
18
20
  end
19
21
 
@@ -16,10 +16,10 @@ module Meerkat
16
16
 
17
17
  def publish(route, json)
18
18
  @pg.transaction do |conn|
19
- conn.exec "DELETE FROM #{TABLENAME} WHERE timestamp < now() - interval '5 seconds'"
20
19
  conn.exec "INSERT INTO #{TABLENAME} (topic, json) VALUES ($1, $2)", [route, json]
21
20
  conn.exec "NOTIFY #{TABLENAME}"
22
21
  end
22
+ @pg.async_exec "DELETE FROM #{TABLENAME} WHERE timestamp < now() - interval '5 seconds'"
23
23
  end
24
24
 
25
25
  def subscribe(route, &callback)
@@ -56,8 +56,8 @@ module Meerkat
56
56
  rows.each do |row|
57
57
  @last_check = row['timestamp']
58
58
  @subs.each do |topic, callbacks|
59
- if topic == row['topic']
60
- callbacks.each { |cb| cb.call row['json'] }
59
+ if File.fnmatch? topic, row['topic']
60
+ callbacks.each { |cb| cb.call row['topic'], row['json'] }
61
61
  end
62
62
  end
63
63
  end
@@ -12,30 +12,34 @@ module Meerkat
12
12
  }
13
13
  end
14
14
 
15
- def publish(route, json)
16
- @pub.publish route, json
15
+ def publish(topic, json)
16
+ @pub.publish topic, json
17
17
  end
18
18
 
19
- def subscribe(route, &callback)
20
- if @subs[route]
21
- @subs[route] << callback
19
+ def subscribe(topic, &callback)
20
+ if @subs[topic]
21
+ @subs[topic] << callback
22
22
  else
23
- @subs[route] = [ callback ]
23
+ @subs[topic] = [ callback ]
24
24
  EM.next_tick {
25
- @sub.subscribe route
26
- @sub.on :message do |channel, message|
27
- @subs[route].each { |c| c.call message }
25
+ @sub.psubscribe topic
26
+ @sub.on :pmessage do |topic, channel, message|
27
+ @subs[topic].each { |c| c.call channel, message }
28
28
  end
29
29
  }
30
30
  end
31
- [route, callback]
31
+ [topic, callback]
32
32
  end
33
33
 
34
34
  def unsubscribe(sub)
35
- @subs[sub[0]].delete sub[1]
36
- EM.next_tick {
37
- @sub.unsubscribe(sub[0]) if @subs[sub[0]].empty?
38
- }
35
+ topic, cb = sub
36
+ @subs[topic].delete cb
37
+ if @subs[topic].empty?
38
+ EM.next_tick do
39
+ @subs.delete topic
40
+ @sub.punsubscribe(topic)
41
+ end
42
+ end
39
43
  end
40
44
  end
41
45
  end
@@ -1,10 +1,13 @@
1
1
  module Meerkat
2
2
  class RackAdapter
3
- attr_accessor :keep_alive
4
3
  attr_accessor :retry
5
4
  attr_accessor :timeout
5
+ attr_accessor :keep_alive
6
6
 
7
7
  def initialize(app = nil, &blk)
8
+ @retry = 3000
9
+ @timeout = false
10
+ @keep_alive = 20
8
11
  blk.call(self) if blk
9
12
  end
10
13
 
@@ -14,25 +17,19 @@ module Meerkat
14
17
  EM.next_tick {
15
18
  env['async.callback'].call [200, {'Content-Type' => 'text/event-stream'}, body]
16
19
  }
17
-
18
- EM.next_tick {
19
- body << "retry: #{@retry || 3000}\n"
20
- }
20
+ EM.next_tick { body << "retry: #{@retry}\n" }
21
+ EM.add_periodic_timer(@keep_alive) { body << ":\n" }
22
+ EM.add_timer(@timeout) { body.succeed } if @timeout
21
23
 
22
24
  path_info = Rack::Utils.unescape env["PATH_INFO"]
23
- sub = Meerkat.subscribe(path_info) do |message|
24
- body << "data: #{message}\n\n"
25
+ sub = Meerkat.subscribe(path_info) do |topic, json|
26
+ body << "event: #{topic}\n"
27
+ body << "data: #{json}\n\n"
25
28
  end
26
- body.errback {
29
+ env['async.close'].callback do
27
30
  Meerkat.unsubscribe sub
28
- }
29
-
30
- EM.add_periodic_timer(@keep_alive || 20) do
31
- body << ":\n"
32
31
  end
33
32
 
34
- EM.add_timer(@timeout) { body.succeed } if @timeout
35
-
36
33
  [-1, {}, []]
37
34
  end
38
35
 
@@ -1,3 +1,3 @@
1
1
  module Meerkat
2
- VERSION = "0.3.2"
2
+ VERSION = "0.4.0"
3
3
  end
@@ -5,26 +5,30 @@ require './lib/meerkat/backend/inmemory'
5
5
  describe 'The in memory backend' do
6
6
  include EM::MiniTest::Spec
7
7
 
8
+ before do
9
+ @im = Meerkat::Backend::InMemory.new
10
+ end
11
+
8
12
  it 'can publish and subscribe' do
9
- im = Meerkat::Backend::InMemory.new
10
- im.subscribe 'route' do |msg|
11
- @recivied = msg
13
+ @im.subscribe 'route' do |topic, msg|
14
+ assert_equal 'route', topic
15
+ assert_equal 'foo', msg
16
+ done!
12
17
  end
13
- im.publish 'route', 'messsage'
14
- assert_equal 'messsage', @recivied
18
+ @im.publish 'route', 'foo'
19
+ wait!
15
20
  end
16
- it 'can does only subscribe to specific routes' do
17
- im = Meerkat::Backend::InMemory.new
18
- im.subscribe 'route' do |msg|
19
- @recivied = msg
21
+ it 'can subscribe to wildcards' do
22
+ @im.subscribe '/foo/*' do |topic, msg|
23
+ assert_equal '/foo/bar', topic
24
+ assert_equal 'barfoo', msg
25
+ done!
20
26
  end
21
- im.publish 'route2', 'messsage'
22
- assert_equal nil, @recivied
27
+ @im.publish '/foo/bar', 'barfoo'
28
+ wait!
23
29
  end
24
30
  it 'can unbsubscribe' do
25
- im = Meerkat::Backend::InMemory.new
26
- sid = im.subscribe 'route' do |msg|
27
- end
28
- im.unsubscribe sid
31
+ sid = @im.subscribe 'route' do |topic, msg| end
32
+ @im.unsubscribe sid
29
33
  end
30
34
  end
@@ -5,34 +5,53 @@ require './lib/meerkat/backend/pg'
5
5
  describe 'Postgres backend' do
6
6
  include EM::MiniTest::Spec
7
7
 
8
+ before do
9
+ @b = Meerkat::Backend::PG.new :dbname => 'postgres'
10
+ end
11
+
12
+ it 'can subscribe to partial wildcard' do
13
+ @b.subscribe '/foo/*' do |topic, msg|
14
+ assert_equal '/foo/bar', topic
15
+ assert_equal 'messsage', msg
16
+ done!
17
+ end
18
+ @b.publish '/foo/bar', 'messsage'
19
+ wait!
20
+ end
21
+
22
+ it 'can subscribe to wildcard' do
23
+ @b.subscribe '*' do |topic, msg|
24
+ assert_equal 'messsage', msg
25
+ done!
26
+ end
27
+ @b.publish '/', 'messsage'
28
+ wait!
29
+ end
30
+
8
31
  it 'can publish and subscribe' do
9
- b = Meerkat::Backend::PG.new :dbname => 'postgres'
10
- b.subscribe '/' do |msg|
32
+ @b.subscribe '/' do |topic, msg|
11
33
  assert_equal 'messsage', msg
12
34
  done!
13
35
  end
14
- b.publish '/', 'messsage'
36
+ @b.publish '/', 'messsage'
15
37
  wait!
16
38
  end
17
39
 
18
40
  it 'can publish and subscribe multiple messages' do
19
- b = Meerkat::Backend::PG.new :dbname => 'postgres'
20
41
  i = 5
21
42
  j = 0
22
- b.subscribe '/' do |msg|
43
+ @b.subscribe '/' do |topic, msg|
23
44
  j += 1
24
45
  assert_equal 'messsage', msg
25
46
  done! if j == i
26
47
  end
27
- i.times { b.publish '/', 'messsage' }
48
+ i.times { @b.publish '/', 'messsage' }
28
49
  wait!
29
50
  end
30
51
 
31
52
  it 'can unsubscribe' do
32
- b = Meerkat::Backend::PG.new :dbname => 'postgres'
33
- sid = b.subscribe 'route' do |msg|
34
- end
35
- b.unsubscribe sid
53
+ sid = @b.subscribe 'route' do |topic, msg| end
54
+ @b.unsubscribe sid
36
55
  end
37
56
  end
38
57
 
@@ -5,9 +5,22 @@ require './lib/meerkat/backend/redis'
5
5
  describe 'Redis backend' do
6
6
  include EM::MiniTest::Spec
7
7
 
8
+ it 'can publish and subscribe to wildcards' do
9
+ b = Meerkat::Backend::Redis.new
10
+ b.subscribe '/foo/*' do |topic, msg|
11
+ assert_equal '/foo/bar', topic
12
+ assert_equal 'messsage', msg
13
+ done!
14
+ end
15
+ EM.next_tick {
16
+ b.publish '/foo/bar', 'messsage'
17
+ }
18
+ wait!
19
+ end
20
+
8
21
  it 'can publish and subscribe' do
9
22
  b = Meerkat::Backend::Redis.new
10
- b.subscribe '/' do |msg|
23
+ b.subscribe '/' do |topic, msg|
11
24
  assert_equal 'messsage', msg
12
25
  done!
13
26
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: meerkat
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.2
4
+ version: 0.4.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -13,7 +13,7 @@ date: 2011-10-25 00:00:00.000000000Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: minitest
16
- requirement: &70298242914000 !ruby/object:Gem::Requirement
16
+ requirement: &70273991486880 !ruby/object:Gem::Requirement
17
17
  none: false
18
18
  requirements:
19
19
  - - ! '>='
@@ -21,10 +21,10 @@ dependencies:
21
21
  version: '0'
22
22
  type: :development
23
23
  prerelease: false
24
- version_requirements: *70298242914000
24
+ version_requirements: *70273991486880
25
25
  - !ruby/object:Gem::Dependency
26
26
  name: rack-test
27
- requirement: &70298242910980 !ruby/object:Gem::Requirement
27
+ requirement: &70273991486440 !ruby/object:Gem::Requirement
28
28
  none: false
29
29
  requirements:
30
30
  - - ! '>='
@@ -32,10 +32,10 @@ dependencies:
32
32
  version: '0'
33
33
  type: :development
34
34
  prerelease: false
35
- version_requirements: *70298242910980
35
+ version_requirements: *70273991486440
36
36
  - !ruby/object:Gem::Dependency
37
37
  name: thin_async
38
- requirement: &70298242907840 !ruby/object:Gem::Requirement
38
+ requirement: &70273991486000 !ruby/object:Gem::Requirement
39
39
  none: false
40
40
  requirements:
41
41
  - - ! '>='
@@ -43,10 +43,10 @@ dependencies:
43
43
  version: '0'
44
44
  type: :development
45
45
  prerelease: false
46
- version_requirements: *70298242907840
46
+ version_requirements: *70273991486000
47
47
  - !ruby/object:Gem::Dependency
48
48
  name: thin-async-test
49
- requirement: &70298242903320 !ruby/object:Gem::Requirement
49
+ requirement: &70273991485580 !ruby/object:Gem::Requirement
50
50
  none: false
51
51
  requirements:
52
52
  - - ! '>='
@@ -54,10 +54,10 @@ dependencies:
54
54
  version: '0'
55
55
  type: :development
56
56
  prerelease: false
57
- version_requirements: *70298242903320
57
+ version_requirements: *70273991485580
58
58
  - !ruby/object:Gem::Dependency
59
59
  name: em-minitest-spec
60
- requirement: &70298242899220 !ruby/object:Gem::Requirement
60
+ requirement: &70273991485160 !ruby/object:Gem::Requirement
61
61
  none: false
62
62
  requirements:
63
63
  - - ! '>='
@@ -65,10 +65,10 @@ dependencies:
65
65
  version: '0'
66
66
  type: :development
67
67
  prerelease: false
68
- version_requirements: *70298242899220
68
+ version_requirements: *70273991485160
69
69
  - !ruby/object:Gem::Dependency
70
70
  name: yajl-ruby
71
- requirement: &70298242898200 !ruby/object:Gem::Requirement
71
+ requirement: &70273991484740 !ruby/object:Gem::Requirement
72
72
  none: false
73
73
  requirements:
74
74
  - - ! '>='
@@ -76,10 +76,10 @@ dependencies:
76
76
  version: '0'
77
77
  type: :runtime
78
78
  prerelease: false
79
- version_requirements: *70298242898200
79
+ version_requirements: *70273991484740
80
80
  - !ruby/object:Gem::Dependency
81
81
  name: eventmachine
82
- requirement: &70298242896720 !ruby/object:Gem::Requirement
82
+ requirement: &70273991484320 !ruby/object:Gem::Requirement
83
83
  none: false
84
84
  requirements:
85
85
  - - ! '>='
@@ -87,10 +87,10 @@ dependencies:
87
87
  version: '0'
88
88
  type: :runtime
89
89
  prerelease: false
90
- version_requirements: *70298242896720
90
+ version_requirements: *70273991484320
91
91
  - !ruby/object:Gem::Dependency
92
92
  name: em-hiredis
93
- requirement: &70298242895440 !ruby/object:Gem::Requirement
93
+ requirement: &70273991483900 !ruby/object:Gem::Requirement
94
94
  none: false
95
95
  requirements:
96
96
  - - ! '>='
@@ -98,10 +98,10 @@ dependencies:
98
98
  version: '0'
99
99
  type: :runtime
100
100
  prerelease: false
101
- version_requirements: *70298242895440
101
+ version_requirements: *70273991483900
102
102
  - !ruby/object:Gem::Dependency
103
103
  name: pg
104
- requirement: &70298242893980 !ruby/object:Gem::Requirement
104
+ requirement: &70273991483480 !ruby/object:Gem::Requirement
105
105
  none: false
106
106
  requirements:
107
107
  - - ! '>='
@@ -109,7 +109,7 @@ dependencies:
109
109
  version: '0'
110
110
  type: :runtime
111
111
  prerelease: false
112
- version_requirements: *70298242893980
112
+ version_requirements: *70273991483480
113
113
  description: Requires an evented Ruby dispatcher, like Thin
114
114
  email:
115
115
  - carl.hoerberg@gmail.com
@@ -151,7 +151,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
151
151
  version: '0'
152
152
  segments:
153
153
  - 0
154
- hash: 2476941847532448728
154
+ hash: -2704897263514401478
155
155
  required_rubygems_version: !ruby/object:Gem::Requirement
156
156
  none: false
157
157
  requirements:
@@ -160,7 +160,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
160
160
  version: '0'
161
161
  segments:
162
162
  - 0
163
- hash: 2476941847532448728
163
+ hash: -2704897263514401478
164
164
  requirements: []
165
165
  rubyforge_project: meerkat
166
166
  rubygems_version: 1.8.6