hotot 0.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|