em-postman 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,4 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
data/Gemfile ADDED
@@ -0,0 +1,7 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in em-postman.gemspec
4
+ gemspec
5
+
6
+ gem 'rake'
7
+ gem 'em-synchrony', :git => 'git://github.com/igrigorik/em-synchrony.git'
data/LICENCE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (C) 2011 by Vivien Schilis
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to deal
5
+ in the Software without restriction, including without limitation the rights
6
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in
11
+ all copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ THE SOFTWARE.
20
+
@@ -0,0 +1,66 @@
1
+ Getting started
2
+ ===============
3
+
4
+ Postman is an EventMachine Pub/Sub resilient to failure.
5
+ Postman uses Redis lists (brpop and lpush) to send messages to subscribers into a mailbox.
6
+
7
+ Postman has been designed to be resilient to failure on both side: publisher and subscriber.
8
+ If your postman process dies or need to be restarted, it will be able to collect again messages from its mailbox.
9
+
10
+
11
+ Example
12
+ =======
13
+
14
+ Server postbox: (server.rb)
15
+
16
+ ``` ruby
17
+ require 'em-postman'
18
+
19
+ EM.run {
20
+ postman = EM::Postman.new('server')
21
+ postman.onmessage(:greetings) {|data|
22
+ puts data.inspect
23
+ postman.send_message data['from'], :greetings, {:message => 'hello ' + data['from']}
24
+ }
25
+
26
+ postman.listen
27
+ }
28
+ ```
29
+
30
+ Client postbox: (client.rb)
31
+
32
+ ``` ruby
33
+ require 'em-postman'
34
+
35
+ EM.run {
36
+ postman = EM::Postman.new('client-' + Process.pid.to_s)
37
+ postman.onmessage(:greetings) {|data|
38
+ puts data.inspect
39
+ EM.stop
40
+ }
41
+
42
+ postman.send_message 'server', :greetings, {:message => 'hello server', :from => postman.mailbox}
43
+ postman.listen
44
+ }
45
+ ```
46
+
47
+ em-synchrony
48
+ ============
49
+
50
+ To use em-postman with em-synchrony
51
+
52
+ ``` ruby
53
+ require 'em-synchrony/em-hiredis'
54
+ require 'em-postman/synchrony'
55
+ ```
56
+
57
+ Credits
58
+ =======
59
+
60
+ - This gem has been extracted from http://pandastream.com.
61
+ - Thanks to Jonas Pfenniger(zimbatm) and Martyn Loughran(mloughran)
62
+
63
+ License
64
+ =======
65
+
66
+ The MIT License - Copyright (c) 2011 Vivien Schilis
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -0,0 +1,28 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "em-postman/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "em-postman"
7
+ s.version = EventMachine::Postman::VERSION
8
+ s.authors = ["Vivien Schilis"]
9
+ s.email = ["vivien.schilis@gmail.com"]
10
+ s.homepage = "http://github.com/vivienschilis/em-postman"
11
+ s.summary = %q{EventMachine pub/sub using Redis list, resilient to failure}
12
+ s.description = %q{EventMachine pub/sub using Redis list, resilient to failure}
13
+
14
+ s.rubyforge_project = "em-postman"
15
+
16
+ s.files = `git ls-files`.split("\n")
17
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
18
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
19
+ s.require_paths = ["lib"]
20
+
21
+ s.add_dependency "em-hiredis"
22
+ s.add_dependency "multi_json"
23
+ s.add_dependency "yajl-ruby"
24
+
25
+ s.add_development_dependency "em-spec"
26
+ s.add_development_dependency "rspec"
27
+
28
+ end
@@ -0,0 +1,16 @@
1
+ require 'em-postman'
2
+
3
+ EM.run {
4
+
5
+ postbox = EM::Postman.new('client-' + Process.pid.to_s )
6
+ postbox.onmessage(:greetings) {|data|
7
+ puts data.inspect
8
+ EM.stop
9
+ }
10
+
11
+ 100.times {|i|
12
+ postbox.send_message('server', :greetings, {:message => "hello server, message #{i} ", :from => postbox.mailbox})
13
+ }
14
+
15
+ postbox.listen
16
+ }
@@ -0,0 +1,12 @@
1
+ require 'em-postman'
2
+
3
+ EM.run {
4
+ postman = EM::Postman.new('server')
5
+
6
+ postman.onmessage(:greetings) {|data|
7
+ puts data.inspect
8
+ postman.send_message(data['from'], :greetings, {:message => 'hello ' + data['from']})
9
+ }
10
+
11
+ postman.listen
12
+ }
@@ -0,0 +1,10 @@
1
+ require 'logger'
2
+ require 'eventmachine'
3
+ require 'em-hiredis'
4
+
5
+ require 'multi_json'
6
+ require 'yajl'
7
+ MultiJson.engine = :yajl
8
+
9
+ require 'em-postman/version'
10
+ require 'em-postman/postman'
@@ -0,0 +1,118 @@
1
+ module EventMachine
2
+ class Postman
3
+ attr_accessor :mailbox
4
+ attr_writer :logger
5
+
6
+ def logger
7
+ @logger ||= Logger.new(STDOUT)
8
+ end
9
+
10
+ def handlers
11
+ @handlers ||= {}
12
+ end
13
+
14
+ def initialize(mailbox, options = {})
15
+ @mailbox = mailbox
16
+ @timeout = options[:timeout] || 5
17
+ @redis_options = {:db => 0, :host => 'localhost', :port => 6379}
18
+
19
+ @options = options
20
+ if options[:redis].is_a?(EventMachine::Hiredis::Client)
21
+ @redis = options[:redis]
22
+ @redis_options = extract_redis_options(@redis)
23
+ else
24
+ @redis_options.merge(options[:redis] || {})
25
+ end
26
+ end
27
+
28
+ def redis
29
+ @redis ||= new_redis_client
30
+ end
31
+
32
+ def clear
33
+ redis.del(inbox_name)
34
+ end
35
+
36
+ def onmessage(method, &callback)
37
+ debug "##{method} registered"
38
+
39
+ handlers[method.to_s] ||= []
40
+ handlers[method.to_s] << callback
41
+ end
42
+
43
+ def unlisten(method, callback)
44
+ if cbs = handlers[method.to_s]
45
+ cbs.delete(callback)
46
+ end
47
+ end
48
+
49
+ def send_message(recipient, method, body)
50
+ message = MultiJson.encode({
51
+ 'method' => method,
52
+ 'body' => body
53
+ })
54
+
55
+ debug "-> #{recipient}##{method}: #{body.inspect}"
56
+ redis.lpush("postman:inbox_#{recipient}", message)
57
+ end
58
+
59
+ def listen
60
+ listen_messages
61
+ end
62
+
63
+ private
64
+
65
+ def new_redis_client
66
+ new_redis_client = EventMachine::Hiredis::Client.connect(@redis_options[:host], @redis_options[:port])
67
+ new_redis_client.select(@redis_options[:db])
68
+ new_redis_client
69
+ end
70
+
71
+ def extract_redis_options(redis)
72
+ {
73
+ :host => redis.host,
74
+ :port => redis.port,
75
+ :db => redis.db
76
+ }
77
+ end
78
+
79
+ def inbox_name
80
+ "postman:inbox_#{@mailbox}"
81
+ end
82
+
83
+ def debug(message)
84
+ logger.debug "Postman[#{mailbox}]: #{message}"
85
+ end
86
+
87
+ def listen_messages(redis = new_redis_client)
88
+ EM.next_tick do
89
+ deferable = redis.brpop(inbox_name, @timeout)
90
+ deferable.callback do |channel, message|
91
+ handle_rpop(channel, message) unless channel.nil?
92
+ listen_messages(redis)
93
+ end
94
+
95
+ deferable.errback do |error|
96
+ logger.error "Postman[#{mailbox}]: #{error.inspect}"
97
+ listen_messages(redis)
98
+ end
99
+ end
100
+ end
101
+
102
+ def handle_rpop(channel, message)
103
+ begin
104
+ debug "<- #{message}"
105
+ data = MultiJson.decode(message)
106
+ if (callbacks = handlers[data['method'].to_s]) && !callbacks.empty?
107
+ callbacks.each {|cb| cb.call(data['body'])}
108
+ else
109
+ logger.warn "Postman[#{mailbox}]: no handler found for #{data['method']}"
110
+ end
111
+ rescue MultiJson::DecodeError => error
112
+ logger.error "Postman[#{mailbox}]: unable to parse message #{message}"
113
+ rescue => error
114
+ logger.error "Postman[#{mailbox}]: #{error}"
115
+ end
116
+ end
117
+ end
118
+ end
@@ -0,0 +1,15 @@
1
+ module EventMachine
2
+ class Postman
3
+ def listen_messages(redis = new_redis_client)
4
+ channel, message = redis.brpop(inbox_name, @timeout)
5
+
6
+ handle_rpop(channel, message) if channel && message
7
+
8
+ EM.next_tick do
9
+ Fiber.new {
10
+ listen_messages(redis)
11
+ }.resume
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,5 @@
1
+ module EventMachine
2
+ class Postman
3
+ VERSION = "0.1.0"
4
+ end
5
+ end
@@ -0,0 +1,80 @@
1
+ require 'spec_helper'
2
+
3
+ describe EventMachine::Postman do
4
+ describe "when listening for messages" do
5
+ let(:redis) {
6
+ redis = EventMachine::Hiredis.connect("redis://localhost:6379/0")
7
+ redis.flushall
8
+ redis
9
+ }
10
+
11
+ let(:postman) {
12
+ p = EM::Postman.new('test', :redis => redis)
13
+ p.logger = Logger.new(nil)
14
+ p
15
+ }
16
+
17
+ it "should send and receive messages asynchronously" do
18
+ em(1) do
19
+ postman.send_message("test", 'cb', {:value => 'abc'})
20
+
21
+ responses = []
22
+ postman.onmessage(:cb) do |msg|
23
+ responses << msg
24
+ end
25
+ postman.onmessage(:cb) do |msg|
26
+ responses << msg
27
+ end
28
+ postman.listen
29
+
30
+ EM.add_timer(0.2) do
31
+ responses.should == [{"value"=>"abc"}, {"value"=>"abc"}]
32
+ done
33
+ end
34
+ end
35
+ end
36
+
37
+ it "should unregister handlers" do
38
+ em(1) do
39
+ postman.send_message("test", 'cb', {:value => 'abc'})
40
+ responses = []
41
+ proc = Proc.new {|msg|
42
+ responses << msg
43
+ postman.unlisten(:cb, proc)
44
+ }
45
+ postman.onmessage(:cb, &proc)
46
+
47
+ postman.listen
48
+
49
+ EM.add_timer(0.2) do
50
+ postman.send_message("test", 'cb', {:value => 'cdf'})
51
+ end
52
+
53
+ EM.add_timer(0.4) do
54
+ responses.should == [{"value"=>"abc"}]
55
+ done
56
+ end
57
+ end
58
+ end
59
+
60
+ it "should gracefully handle a Redis.rpop error" do
61
+ em(1) do
62
+ redis.lpush("postman:inbox_test", MultiJson.encode({:method => 'cb', :body => 'a'}))
63
+ redis.lpush("postman:inbox_test", "error")
64
+ redis.lpush("postman:inbox_test", MultiJson.encode({:method => 'cb', :body => 'b'}))
65
+
66
+ responses = []
67
+ postman.onmessage(:cb) do |msg|
68
+ responses << msg
69
+ end
70
+ postman.listen
71
+
72
+ EM.add_timer(0.2) do
73
+ responses.should == ["a", "b"]
74
+ done
75
+ end
76
+ end
77
+ end
78
+
79
+ end
80
+ end
@@ -0,0 +1,8 @@
1
+ $:.unshift File.expand_path(File.dirname(__FILE__) + "/../lib")
2
+ require 'em-postman'
3
+ require 'rspec'
4
+ require 'em-spec/rspec'
5
+
6
+ RSpec.configure do |config|
7
+ config.include EventMachine::SpecHelper
8
+ end
@@ -0,0 +1,33 @@
1
+ require 'spec_helper'
2
+ require 'em-synchrony'
3
+ require 'em-synchrony/em-hiredis'
4
+ require 'em-postman/synchrony'
5
+
6
+ describe EventMachine::Postman do
7
+ describe "when listening for messages" do
8
+
9
+ it "should send and receive messages using em-sychrony" do
10
+ EventMachine.synchrony do
11
+
12
+ redis = EM::Hiredis::Client.connect('localhost', 6379)
13
+ redis.flushall
14
+
15
+ postman = EM::Postman.new('test', :redis => redis)
16
+ postman.logger = Logger.new(nil)
17
+
18
+ postman.send_message("test", 'cb', {:value => 'abc'})
19
+
20
+ responses = []
21
+ postman.onmessage(:cb) do |msg|
22
+ responses << msg
23
+ end
24
+
25
+ postman.listen
26
+
27
+ responses.should == [{"value"=>"abc"}]
28
+ EventMachine.stop
29
+ end
30
+ end
31
+
32
+ end
33
+ end
metadata ADDED
@@ -0,0 +1,124 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: em-postman
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Vivien Schilis
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2011-09-05 00:00:00.000000000Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: em-hiredis
16
+ requirement: &70337885041680 !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: *70337885041680
25
+ - !ruby/object:Gem::Dependency
26
+ name: multi_json
27
+ requirement: &70337885041120 !ruby/object:Gem::Requirement
28
+ none: false
29
+ requirements:
30
+ - - ! '>='
31
+ - !ruby/object:Gem::Version
32
+ version: '0'
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: *70337885041120
36
+ - !ruby/object:Gem::Dependency
37
+ name: yajl-ruby
38
+ requirement: &70337885037840 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ! '>='
42
+ - !ruby/object:Gem::Version
43
+ version: '0'
44
+ type: :runtime
45
+ prerelease: false
46
+ version_requirements: *70337885037840
47
+ - !ruby/object:Gem::Dependency
48
+ name: em-spec
49
+ requirement: &70337885037340 !ruby/object:Gem::Requirement
50
+ none: false
51
+ requirements:
52
+ - - ! '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ type: :development
56
+ prerelease: false
57
+ version_requirements: *70337885037340
58
+ - !ruby/object:Gem::Dependency
59
+ name: rspec
60
+ requirement: &70337885036920 !ruby/object:Gem::Requirement
61
+ none: false
62
+ requirements:
63
+ - - ! '>='
64
+ - !ruby/object:Gem::Version
65
+ version: '0'
66
+ type: :development
67
+ prerelease: false
68
+ version_requirements: *70337885036920
69
+ description: EventMachine pub/sub using Redis list, resilient to failure
70
+ email:
71
+ - vivien.schilis@gmail.com
72
+ executables: []
73
+ extensions: []
74
+ extra_rdoc_files: []
75
+ files:
76
+ - .gitignore
77
+ - Gemfile
78
+ - LICENCE
79
+ - README.md
80
+ - Rakefile
81
+ - em-postman.gemspec
82
+ - examples/client.rb
83
+ - examples/server.rb
84
+ - lib/em-postman.rb
85
+ - lib/em-postman/postman.rb
86
+ - lib/em-postman/synchrony.rb
87
+ - lib/em-postman/version.rb
88
+ - spec/postman_spec.rb
89
+ - spec/spec_helper.rb
90
+ - spec/synchrony_spec.rb
91
+ homepage: http://github.com/vivienschilis/em-postman
92
+ licenses: []
93
+ post_install_message:
94
+ rdoc_options: []
95
+ require_paths:
96
+ - lib
97
+ required_ruby_version: !ruby/object:Gem::Requirement
98
+ none: false
99
+ requirements:
100
+ - - ! '>='
101
+ - !ruby/object:Gem::Version
102
+ version: '0'
103
+ segments:
104
+ - 0
105
+ hash: -2381599360813571691
106
+ required_rubygems_version: !ruby/object:Gem::Requirement
107
+ none: false
108
+ requirements:
109
+ - - ! '>='
110
+ - !ruby/object:Gem::Version
111
+ version: '0'
112
+ segments:
113
+ - 0
114
+ hash: -2381599360813571691
115
+ requirements: []
116
+ rubyforge_project: em-postman
117
+ rubygems_version: 1.8.6
118
+ signing_key:
119
+ specification_version: 3
120
+ summary: EventMachine pub/sub using Redis list, resilient to failure
121
+ test_files:
122
+ - spec/postman_spec.rb
123
+ - spec/spec_helper.rb
124
+ - spec/synchrony_spec.rb