hotot 0.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/hotot/class_attribute_accessors.rb +20 -0
- data/lib/hotot/configurable.rb +23 -0
- data/lib/hotot/message/base.rb +42 -0
- data/lib/hotot/message_producer.rb +9 -0
- data/lib/hotot/synchronous_connection.rb +106 -0
- data/lib/hotot/version.rb +5 -0
- data/lib/hotot.rb +27 -0
- data/spec/hotot/configurable_spec.rb +34 -0
- data/spec/hotot/message/base_spec.rb +40 -0
- data/spec/hotot/message_producer_spec.rb +20 -0
- data/spec/hotot/synchronous_connection_spec.rb +75 -0
- data/spec/hotot_spec.rb +37 -0
- data/spec/minitest_helper.rb +4 -0
- metadata +82 -0
@@ -0,0 +1,20 @@
|
|
1
|
+
module Hotot
|
2
|
+
module ClassAttributeAccessors
|
3
|
+
|
4
|
+
def cattr_reader(*syms)
|
5
|
+
syms.each do |sym|
|
6
|
+
raise NameError.new("invalid class attribute name: #{sym}") unless sym =~ /^[_A-Za-z]\w*$/
|
7
|
+
class_eval(<<-EOS, __FILE__, __LINE__ + 1)
|
8
|
+
unless defined? @@#{sym}
|
9
|
+
@@#{sym} = nil
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.#{sym}
|
13
|
+
@@#{sym}
|
14
|
+
end
|
15
|
+
EOS
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'erb'
|
2
|
+
require 'yaml'
|
3
|
+
|
4
|
+
module Hotot
|
5
|
+
module Configurable
|
6
|
+
|
7
|
+
private
|
8
|
+
|
9
|
+
def load_config(config_file = nil)
|
10
|
+
if config_file && config_file.file?
|
11
|
+
YAML.load(ERB.new(config_file.read).result)
|
12
|
+
elsif defined?(::Rails)
|
13
|
+
config_file = ::Rails.root.join('config/hotot.yml')
|
14
|
+
if config_file.file?
|
15
|
+
YAML.load(ERB.new(config_file.read).result)
|
16
|
+
else
|
17
|
+
nil
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require 'time'
|
2
|
+
require 'json' unless defined?(::Rails)
|
3
|
+
|
4
|
+
module Hotot
|
5
|
+
module Message
|
6
|
+
class Base
|
7
|
+
|
8
|
+
# Use rails default json if it exists
|
9
|
+
if defined?(::Rails)
|
10
|
+
include ActiveModel::Serializers::JSON
|
11
|
+
else
|
12
|
+
define_method :to_json do
|
13
|
+
self.as_json.to_json
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
attr_reader :metadata
|
18
|
+
|
19
|
+
def initialize(routing_key=nil)
|
20
|
+
raise StandardError, "routing_key cannot be nil" if routing_key.nil?
|
21
|
+
@metadata = {:host => Socket.gethostbyname(Socket.gethostname).first,
|
22
|
+
:app => Hotot.app_name, # configure this...
|
23
|
+
:key => routing_key,
|
24
|
+
:created => DateTime.now.new_offset(0).to_time.utc.iso8601}
|
25
|
+
end
|
26
|
+
|
27
|
+
def routing_key
|
28
|
+
@metadata[:key]
|
29
|
+
end
|
30
|
+
|
31
|
+
def as_json(options={})
|
32
|
+
hash = {:metadata => {:host => @metadata[:host],
|
33
|
+
:app => @metadata[:app],
|
34
|
+
:key => @metadata[:key],
|
35
|
+
:created => @metadata[:created]}}
|
36
|
+
hash
|
37
|
+
end
|
38
|
+
|
39
|
+
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,106 @@
|
|
1
|
+
require 'bunny'
|
2
|
+
require_relative 'configurable'
|
3
|
+
require_relative 'class_attribute_accessors'
|
4
|
+
|
5
|
+
module Hotot
|
6
|
+
class SynchronousConnection
|
7
|
+
extend Configurable
|
8
|
+
extend ClassAttributeAccessors
|
9
|
+
|
10
|
+
cattr_reader :bunny, :exchange
|
11
|
+
|
12
|
+
@@setup = false
|
13
|
+
@@connected = false
|
14
|
+
|
15
|
+
def self.setup(config = nil, env = (defined?(::Rails) ? Rails.env : nil) )
|
16
|
+
if !self.setup?
|
17
|
+
@@config = load_config(config)
|
18
|
+
@@env_config = @@config[env]
|
19
|
+
raise StandardError, "Env #{env} not found in config" if @@env_config.nil?
|
20
|
+
|
21
|
+
# symbolize the keys, which Bunny expects
|
22
|
+
@@env_config.keys.each {|key| @@env_config[(key.to_sym rescue key) || key] = @@env_config.delete(key) }
|
23
|
+
raise StandardError, "'exchange' key not found in config" if !@@env_config.has_key?(:exchange)
|
24
|
+
|
25
|
+
@@bunny = Bunny.new(@@env_config)
|
26
|
+
@@setup = true
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
# Whether the underlying connection has been set up
|
31
|
+
def self.setup?
|
32
|
+
@@setup
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.subscribe(routing_key, &block)
|
36
|
+
q = @@bunny.queue.bind(@@env_config[:exchange], :routing_key => routing_key)
|
37
|
+
|
38
|
+
q.subscribe do |delivery_info, metadata, payload|
|
39
|
+
block.call delivery_info, metadata, payload
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
43
|
+
|
44
|
+
# Establish a connection to the underlying exchange
|
45
|
+
def self.connect
|
46
|
+
raise StandardError, "AMQP not setup. Call setup before calling connect" if !self.setup?
|
47
|
+
@@bunny.start
|
48
|
+
|
49
|
+
# use defualt channel for exchange
|
50
|
+
@@exchange = @@bunny.exchange(@@env_config[:exchange], :type => :topic, :durable => true)
|
51
|
+
@@connected = true
|
52
|
+
end
|
53
|
+
|
54
|
+
# Disconnect from the underlying exchange
|
55
|
+
def self.disconnect
|
56
|
+
begin
|
57
|
+
@@bunny.stop
|
58
|
+
rescue
|
59
|
+
# if this is being called because the underlying connection went bad
|
60
|
+
# calling stop will raise an error. that's ok....
|
61
|
+
ensure
|
62
|
+
@@connected = false
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
# Re-connects to the underlying exchange
|
67
|
+
def self.reconnect
|
68
|
+
self.disconnect
|
69
|
+
@@setup = false
|
70
|
+
@@bunny = Bunny.new(@@env_config)
|
71
|
+
@@setup = true
|
72
|
+
self.connect
|
73
|
+
end
|
74
|
+
|
75
|
+
# Whether the underlying connection has been established
|
76
|
+
def self.connected?
|
77
|
+
@@connected
|
78
|
+
end
|
79
|
+
|
80
|
+
# Produces a message to the underlying exchange
|
81
|
+
def self.produce(message)
|
82
|
+
if !self.setup? || !self.connected?
|
83
|
+
Hotot.logger.error "AMQP not setup or connected. Call setup and connect before calling produce"
|
84
|
+
else
|
85
|
+
begin
|
86
|
+
@@exchange.publish(message.to_json, :routing_key => message.routing_key, :mandatory => false, :immediate => false, :persistent => true, :content_type => "application/json")
|
87
|
+
rescue Bunny::ServerDownError
|
88
|
+
# the connection went south, try to reconnect and try one more time
|
89
|
+
begin
|
90
|
+
self.reconnect
|
91
|
+
@@exchange.publish(message.to_json, :routing_key => message.routing_key, :mandatory => false, :immediate => false, :persistent => true, :content_type => "application/json")
|
92
|
+
rescue => err
|
93
|
+
Hotot.logger.error "Unexpected error producing AMQP messsage: (#{message.to_json})"
|
94
|
+
Hotot.logger.error "#{err.message}"
|
95
|
+
Hotot.logger.error err.backtrace.join("\n")
|
96
|
+
end
|
97
|
+
rescue => err
|
98
|
+
Hotot.logger.error "Unexpected error producing AMQP messsage: (#{message.to_json})"
|
99
|
+
Hotot.logger.error "#{err.message}"
|
100
|
+
Hotot.logger.error err.backtrace.join("\n")
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
end #SynchronousConnection
|
106
|
+
end #Hotot
|
data/lib/hotot.rb
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'logger'
|
2
|
+
|
3
|
+
module Hotot
|
4
|
+
|
5
|
+
require_relative 'hotot/synchronous_connection'
|
6
|
+
require_relative 'hotot/message_producer'
|
7
|
+
require_relative 'hotot/message/base'
|
8
|
+
|
9
|
+
def self.logger=(logger)
|
10
|
+
@logger = logger
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.logger
|
14
|
+
@logger ||= Logger.new($stdout).tap do |log|
|
15
|
+
log.progname = 'Hotot'
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.app_name=(app_name)
|
20
|
+
@app_name = app_name
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.app_name
|
24
|
+
@app_name ||= "Hotot"
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'minitest_helper'
|
2
|
+
|
3
|
+
module MyConfig
|
4
|
+
extend Hotot::Configurable
|
5
|
+
|
6
|
+
def self.setup(config)
|
7
|
+
load_config(config)
|
8
|
+
end
|
9
|
+
|
10
|
+
end
|
11
|
+
|
12
|
+
describe "Hotot::Configurable" do
|
13
|
+
|
14
|
+
it "Configurable should receive load_config" do
|
15
|
+
MyConfig.stub :load_config, true do
|
16
|
+
MyConfig.setup "config"
|
17
|
+
assert_send [MyConfig, :load_config, nil]
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
before do
|
22
|
+
@config_file = Pathname.new File.expand_path("../../fixtures/hotot.yml", __FILE__)
|
23
|
+
@config = MyConfig.setup @config_file
|
24
|
+
end
|
25
|
+
|
26
|
+
it "loads config file" do
|
27
|
+
test_config = @config['test']
|
28
|
+
test_config['host'].must_equal 'localhost'
|
29
|
+
test_config['vhost'].must_equal '/'
|
30
|
+
test_config['exchange'].must_equal 'heyook.topic'
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
|
@@ -0,0 +1,40 @@
|
|
1
|
+
require 'minitest_helper'
|
2
|
+
|
3
|
+
describe "Hotot::Message::Base" do
|
4
|
+
|
5
|
+
describe "default base" do
|
6
|
+
|
7
|
+
before do
|
8
|
+
@base = Hotot::Message::Base.new("rkey")
|
9
|
+
@hash = @base.as_json
|
10
|
+
end
|
11
|
+
|
12
|
+
it "sets metadata" do
|
13
|
+
metadata = @hash[:metadata]
|
14
|
+
metadata[:app].must_equal "Hotot"
|
15
|
+
metadata[:key].must_equal "rkey"
|
16
|
+
metadata[:host].wont_be :empty?
|
17
|
+
metadata[:created].wont_be :empty?
|
18
|
+
end
|
19
|
+
|
20
|
+
it "respond to to_json" do
|
21
|
+
assert_respond_to @base, :to_json
|
22
|
+
end
|
23
|
+
|
24
|
+
it "converts to json" do
|
25
|
+
json = @base.to_json
|
26
|
+
metadata = (JSON.parse json)['metadata']
|
27
|
+
|
28
|
+
metadata['app'].must_equal "Hotot"
|
29
|
+
metadata['key'].must_equal "rkey"
|
30
|
+
metadata['host'].wont_be :empty?
|
31
|
+
metadata['created'].wont_be :empty?
|
32
|
+
end
|
33
|
+
|
34
|
+
it "gets routing_key" do
|
35
|
+
@base.routing_key.must_equal "rkey"
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'minitest_helper'
|
2
|
+
|
3
|
+
class MyProducer
|
4
|
+
include Hotot::MessageProducer
|
5
|
+
end
|
6
|
+
|
7
|
+
describe "Hotot::MessageProducer" do
|
8
|
+
|
9
|
+
before do
|
10
|
+
@mp = MyProducer.new
|
11
|
+
end
|
12
|
+
|
13
|
+
it "SynchronousConnection should receive produce" do
|
14
|
+
Hotot::SynchronousConnection.stub :produce, true do
|
15
|
+
@mp.produce "hello rabbit"
|
16
|
+
assert_send [Hotot::SynchronousConnection, :produce, "hello rabbit"]
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
require 'minitest_helper'
|
2
|
+
|
3
|
+
class TestMessage < Hotot::Message::Base
|
4
|
+
ROUTING_KEY = "heyook.message.test"
|
5
|
+
def initialize
|
6
|
+
super ROUTING_KEY
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
describe "Hotot::SynchronousConnection" do
|
11
|
+
|
12
|
+
before do
|
13
|
+
@config_file = Pathname.new File.expand_path("../../fixtures/hotot.yml", __FILE__)
|
14
|
+
@bunny = Minitest::Mock.new
|
15
|
+
@exchange = Minitest::Mock.new
|
16
|
+
end
|
17
|
+
|
18
|
+
it "raise error if not setup yet" do
|
19
|
+
assert_raises(StandardError) { Hotot::SynchronousConnection.connect }
|
20
|
+
end
|
21
|
+
|
22
|
+
describe "setup" do
|
23
|
+
|
24
|
+
it "setup Hotot::SynchronousConnection" do
|
25
|
+
Hotot::SynchronousConnection.setup @config_file, 'test'
|
26
|
+
Hotot::SynchronousConnection.setup?.must_equal true
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
|
31
|
+
describe "connect" do
|
32
|
+
|
33
|
+
before do
|
34
|
+
Hotot::SynchronousConnection.setup @config_file, 'test'
|
35
|
+
@bunny = Hotot::SynchronousConnection.bunny
|
36
|
+
end
|
37
|
+
|
38
|
+
after do
|
39
|
+
Hotot::SynchronousConnection.disconnect
|
40
|
+
end
|
41
|
+
|
42
|
+
it "calls start and exchange" do
|
43
|
+
Hotot::SynchronousConnection.connect
|
44
|
+
Hotot::SynchronousConnection.connected?.must_equal true
|
45
|
+
assert_send [@bunny, :start]
|
46
|
+
assert_send [@bunny, :exchange, "heyook.topic", :type => :topic, :durable => true]
|
47
|
+
end
|
48
|
+
|
49
|
+
describe "publish" do
|
50
|
+
|
51
|
+
it "let exchange to publish" do
|
52
|
+
Hotot::SynchronousConnection.connect
|
53
|
+
@exchange = Hotot::SynchronousConnection.exchange
|
54
|
+
|
55
|
+
message = TestMessage.new
|
56
|
+
Hotot::SynchronousConnection.produce message
|
57
|
+
assert_send [@exchange, :publish, message.to_json, :routing_key => "heyook.message.test", :mandatory => false, :immediate => false, :persistent => true, :content_type => "application/json"]
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|
61
|
+
|
62
|
+
end
|
63
|
+
|
64
|
+
describe "disconnect" do
|
65
|
+
|
66
|
+
it "disconnects and set connected to false" do
|
67
|
+
Bunny.stub :new, @bunny do
|
68
|
+
Hotot::SynchronousConnection.disconnect
|
69
|
+
Hotot::SynchronousConnection.connected?.must_equal false
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
end
|
74
|
+
|
75
|
+
end
|
data/spec/hotot_spec.rb
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'minitest_helper'
|
2
|
+
|
3
|
+
describe Hotot do
|
4
|
+
|
5
|
+
describe "default settings" do
|
6
|
+
|
7
|
+
before do
|
8
|
+
Hotot.logger = nil
|
9
|
+
Hotot.app_name = nil
|
10
|
+
end
|
11
|
+
|
12
|
+
it "uses default logger" do
|
13
|
+
assert_kind_of Logger, Hotot.logger
|
14
|
+
end
|
15
|
+
|
16
|
+
it "uses default app name" do
|
17
|
+
Hotot.app_name.must_equal "Hotot"
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
21
|
+
|
22
|
+
describe "get/set settings" do
|
23
|
+
|
24
|
+
it "sets logger" do
|
25
|
+
logger_klass = Object.const_set("MyLogger", Class.new )
|
26
|
+
Hotot.logger = logger_klass.new
|
27
|
+
assert_kind_of logger_klass, Hotot.logger
|
28
|
+
end
|
29
|
+
|
30
|
+
it "sets app name" do
|
31
|
+
Hotot.app_name = "New Rabbit"
|
32
|
+
Hotot.app_name.must_equal "New Rabbit"
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
metadata
ADDED
@@ -0,0 +1,82 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: hotot
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Qi He
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2014-04-22 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: bunny
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ~>
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '1.2'
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ~>
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '1.2'
|
30
|
+
description: ! ' Hotot is a kind of rabbit that wraps bunny gem.
|
31
|
+
|
32
|
+
'
|
33
|
+
email: qihe229@gmail.com
|
34
|
+
executables: []
|
35
|
+
extensions: []
|
36
|
+
extra_rdoc_files: []
|
37
|
+
files:
|
38
|
+
- lib/hotot/class_attribute_accessors.rb
|
39
|
+
- lib/hotot/configurable.rb
|
40
|
+
- lib/hotot/message/base.rb
|
41
|
+
- lib/hotot/message_producer.rb
|
42
|
+
- lib/hotot/synchronous_connection.rb
|
43
|
+
- lib/hotot/version.rb
|
44
|
+
- lib/hotot.rb
|
45
|
+
- spec/hotot/configurable_spec.rb
|
46
|
+
- spec/hotot/message/base_spec.rb
|
47
|
+
- spec/hotot/message_producer_spec.rb
|
48
|
+
- spec/hotot/synchronous_connection_spec.rb
|
49
|
+
- spec/hotot_spec.rb
|
50
|
+
- spec/minitest_helper.rb
|
51
|
+
homepage: http://github.com/he9qi/hotot
|
52
|
+
licenses:
|
53
|
+
- MIT
|
54
|
+
post_install_message:
|
55
|
+
rdoc_options: []
|
56
|
+
require_paths:
|
57
|
+
- lib
|
58
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
59
|
+
none: false
|
60
|
+
requirements:
|
61
|
+
- - ! '>='
|
62
|
+
- !ruby/object:Gem::Version
|
63
|
+
version: '0'
|
64
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
65
|
+
none: false
|
66
|
+
requirements:
|
67
|
+
- - ! '>='
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: '0'
|
70
|
+
requirements: []
|
71
|
+
rubyforge_project:
|
72
|
+
rubygems_version: 1.8.23
|
73
|
+
signing_key:
|
74
|
+
specification_version: 3
|
75
|
+
summary: A bunny wrapper.
|
76
|
+
test_files:
|
77
|
+
- spec/hotot/configurable_spec.rb
|
78
|
+
- spec/hotot/message/base_spec.rb
|
79
|
+
- spec/hotot/message_producer_spec.rb
|
80
|
+
- spec/hotot/synchronous_connection_spec.rb
|
81
|
+
- spec/hotot_spec.rb
|
82
|
+
- spec/minitest_helper.rb
|