philotic 0.0.1 → 0.1.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.
- checksums.yaml +4 -4
- data/.travis.yml +6 -1
- data/Gemfile +0 -7
- data/README.md +2 -0
- data/Rakefile +1 -1
- data/examples/README.md +1 -1
- data/examples/creating_named_queues/manually.rb +8 -21
- data/examples/creating_named_queues/with_rake.rb +2 -2
- data/examples/publishing/publish.rb +26 -33
- data/examples/subscribing/acks.rb +25 -0
- data/examples/subscribing/anonymous_queue.rb +6 -10
- data/examples/subscribing/multiple_named_queues.rb +9 -24
- data/examples/subscribing/named_queue.rb +7 -17
- data/lib/philotic.rb +37 -81
- data/lib/philotic/config.rb +63 -24
- data/lib/philotic/connection.rb +35 -51
- data/lib/philotic/constants.rb +49 -0
- data/lib/philotic/event.rb +27 -14
- data/lib/philotic/logging.rb +8 -0
- data/lib/philotic/logging/event.rb +18 -0
- data/lib/philotic/logging/logger.rb +39 -0
- data/lib/philotic/publisher.rb +28 -31
- data/lib/philotic/routable.rb +40 -40
- data/lib/philotic/subscriber.rb +61 -42
- data/lib/philotic/tasks/init_queues.rb +4 -14
- data/lib/philotic/version.rb +1 -1
- data/philotic.gemspec +21 -10
- data/spec/philotic/config_spec.rb +40 -0
- data/spec/philotic/connection_spec.rb +58 -0
- data/spec/philotic/event_spec.rb +69 -0
- data/spec/philotic/logging/logger_spec.rb +26 -0
- data/spec/philotic/publisher_spec.rb +99 -0
- data/spec/{routable_spec.rb → philotic/routable_spec.rb} +15 -14
- data/spec/philotic/subscriber_spec.rb +111 -0
- data/spec/philotic_spec.rb +66 -0
- data/spec/spec_helper.rb +12 -4
- data/tasks/bump.rake +10 -10
- metadata +173 -36
- data/spec/connection_spec.rb +0 -19
- data/spec/event_spec.rb +0 -44
- data/spec/publisher_spec.rb +0 -102
- data/spec/subscriber_spec.rb +0 -72
data/lib/philotic/config.rb
CHANGED
@@ -2,6 +2,8 @@ require 'yaml'
|
|
2
2
|
require 'json'
|
3
3
|
require 'singleton'
|
4
4
|
require 'forwardable'
|
5
|
+
require 'cgi'
|
6
|
+
require 'bunny/session'
|
5
7
|
|
6
8
|
module Philotic
|
7
9
|
module Config
|
@@ -11,33 +13,29 @@ module Philotic
|
|
11
13
|
|
12
14
|
DEFAULT_DISABLE_PUBLISH = false
|
13
15
|
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
DEFAULT_EXCHANGE_NAME
|
21
|
-
DEFAULT_CONNECTION_FAILED_HANDLER = Proc.new { |settings| Philotic.logger.error "RabbitMQ connection failure; host:#{rabbit_host}" }
|
22
|
-
DEFAULT_CONNECTION_LOSS_HANDLER = Proc.new { |conn, settings| Philotic.logger.warn "RabbitMQ connection loss; host:#{rabbit_host}"; conn.reconnect(false, 2) }
|
23
|
-
DEFAULT_MESSAGE_RETURN_HANDLER = Proc.new { |basic_return, metadata, payload| Philotic.logger.warn "Philotic message #{JSON.parse payload} was returned! reply_code = #{basic_return.reply_code}, reply_text = #{basic_return.reply_text} headers = #{metadata.properties}"; }
|
16
|
+
DEFAULT_RABBIT_HOST = 'localhost'
|
17
|
+
DEFAULT_RABBIT_PORT = 5672
|
18
|
+
DEFAULT_RABBIT_USER = 'guest'
|
19
|
+
DEFAULT_RABBIT_PASSWORD = 'guest'
|
20
|
+
DEFAULT_RABBIT_VHOST = '%2f' # '/'
|
21
|
+
DEFAULT_RABBIT_URL = "amqp://#{DEFAULT_RABBIT_USER}:#{DEFAULT_RABBIT_PASSWORD}@#{DEFAULT_RABBIT_HOST}:#{DEFAULT_RABBIT_PORT}/#{DEFAULT_RABBIT_VHOST}"
|
22
|
+
DEFAULT_EXCHANGE_NAME = 'philotic.headers'
|
24
23
|
DEFAULT_TIMEOUT = 2
|
25
|
-
|
26
|
-
|
27
|
-
DEFAULT_PERSISTENT = true
|
24
|
+
DEFAULT_ROUTING_KEY = nil
|
25
|
+
DEFAULT_PERSISTENT = true
|
28
26
|
# DEFAULT_IMMEDIATE = false
|
29
|
-
DEFAULT_MANDATORY
|
30
|
-
DEFAULT_CONTENT_TYPE
|
27
|
+
DEFAULT_MANDATORY = true
|
28
|
+
DEFAULT_CONTENT_TYPE = nil
|
31
29
|
DEFAULT_CONTENT_ENCODING = nil
|
32
|
-
DEFAULT_PRIORITY
|
33
|
-
DEFAULT_MESSAGE_ID
|
34
|
-
DEFAULT_CORRELATION_ID
|
35
|
-
DEFAULT_REPLY_TO
|
36
|
-
DEFAULT_TYPE
|
37
|
-
DEFAULT_USER_ID
|
38
|
-
DEFAULT_APP_ID
|
39
|
-
DEFAULT_TIMESTAMP
|
40
|
-
DEFAULT_EXPIRATION
|
30
|
+
DEFAULT_PRIORITY = nil
|
31
|
+
DEFAULT_MESSAGE_ID = nil
|
32
|
+
DEFAULT_CORRELATION_ID = nil
|
33
|
+
DEFAULT_REPLY_TO = nil
|
34
|
+
DEFAULT_TYPE = nil
|
35
|
+
DEFAULT_USER_ID = nil
|
36
|
+
DEFAULT_APP_ID = nil
|
37
|
+
DEFAULT_TIMESTAMP = nil
|
38
|
+
DEFAULT_EXPIRATION = nil
|
41
39
|
|
42
40
|
def defaults
|
43
41
|
@defaults ||= Hash[Config.constants.select { |c| c.to_s.start_with? 'DEFAULT_' }.collect do |c|
|
@@ -62,6 +60,47 @@ module Philotic
|
|
62
60
|
end
|
63
61
|
end
|
64
62
|
|
63
|
+
attr_reader :connection_failed_handler, :connection_loss_handler, :message_return_handler
|
64
|
+
|
65
|
+
def connection_failed_handler
|
66
|
+
@connection_failed_handler ||= lambda do |settings|
|
67
|
+
Philotic.logger.error "RabbitMQ connection failure; host:#{rabbit_host}"
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def connection_loss_handler
|
72
|
+
@connection_loss_handler ||= lambda do |conn, settings|
|
73
|
+
Philotic.logger.warn "RabbitMQ connection loss; host:#{rabbit_host}"; conn.reconnect(false, 2)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def message_return_handler
|
78
|
+
@message_return_handler ||= lambda do |basic_return, metadata, payload|
|
79
|
+
puts "Philotic message #{JSON.parse payload} was returned! reply_code = #{basic_return.reply_code}, reply_text = #{basic_return.reply_text} headers = #{metadata[:headers]}"
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def self.parse_rabbit_uri
|
84
|
+
settings = Bunny::Session.parse_uri(@rabbit_url || defaults[:rabbit_url])
|
85
|
+
settings[:password] = settings.delete(:pass)
|
86
|
+
|
87
|
+
%w[host port user password vhost].each do |setting|
|
88
|
+
setting = setting.to_sym
|
89
|
+
current_value = send("rabbit_#{setting}")
|
90
|
+
|
91
|
+
# only use the value from the URI if the existing value is nil or the default
|
92
|
+
if settings[setting] && [const_get("default_rabbit_#{setting}".upcase), nil].include?(current_value)
|
93
|
+
send("rabbit_#{setting}=", settings[setting])
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
end
|
98
|
+
|
99
|
+
def self.rabbit_url
|
100
|
+
self.parse_rabbit_uri
|
101
|
+
"amqp://#{rabbit_user}:#{rabbit_password}@#{rabbit_host}:#{rabbit_port}/#{CGI.escape rabbit_vhost}"
|
102
|
+
end
|
103
|
+
|
65
104
|
def load(config)
|
66
105
|
Philotic.logger # ensure the logger can be created, so we crash early if it can't
|
67
106
|
|
data/lib/philotic/connection.rb
CHANGED
@@ -1,8 +1,8 @@
|
|
1
1
|
require 'singleton'
|
2
2
|
require 'json'
|
3
|
-
require '
|
4
|
-
|
5
|
-
require '
|
3
|
+
require 'bunny'
|
4
|
+
|
5
|
+
require 'philotic/config'
|
6
6
|
|
7
7
|
module Philotic
|
8
8
|
module Connection
|
@@ -13,70 +13,54 @@ module Philotic
|
|
13
13
|
Philotic::Config
|
14
14
|
end
|
15
15
|
|
16
|
-
def connect!
|
17
|
-
if connected?
|
18
|
-
Philotic.logger.info "already connected to RabbitMQ; host:#{config.rabbit_host}"
|
19
|
-
block.call if block
|
20
|
-
return
|
21
|
-
end
|
22
|
-
|
23
|
-
AMQP::Utilities::EventLoopHelper.run do
|
24
|
-
connection_settings = {
|
25
|
-
host: config.rabbit_host,
|
26
|
-
port: config.rabbit_port,
|
27
|
-
user: config.rabbit_user,
|
28
|
-
password: config.rabbit_password,
|
29
|
-
vhost: config.rabbit_vhost,
|
30
|
-
timeout: config.timeout,
|
31
|
-
on_tcp_connection_failure: config.connection_failed_handler,
|
32
|
-
}
|
33
|
-
|
34
|
-
AMQP.start(connection_settings, logging: true) do |connection, connect_ok|
|
35
|
-
@connection = connection
|
16
|
+
def connect!
|
17
|
+
return if connected?
|
36
18
|
|
37
|
-
|
38
|
-
Philotic.logger.info "connected to RabbitMQ; host:#{config.rabbit_host}"
|
39
|
-
else
|
40
|
-
Philotic.logger.warn "failed connected to RabbitMQ; host:#{config.rabbit_host}"
|
41
|
-
end
|
19
|
+
start_connection!
|
42
20
|
|
21
|
+
if connected?
|
22
|
+
Philotic.logger.info "connected to RabbitMQ: #{config.rabbit_host}:#{config.rabbit_port}"
|
23
|
+
set_exchange_return_handler!
|
24
|
+
true
|
25
|
+
else
|
26
|
+
Philotic.logger.error "failed connected to RabbitMQ; host:#{config.rabbit_host}"
|
27
|
+
false
|
28
|
+
end
|
29
|
+
end
|
43
30
|
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
end
|
49
|
-
|
50
|
-
@connection.after_recovery do |conn, settings|
|
51
|
-
Philotic.logger.info "Connection recovered, now connected to #{config.rabbit_host}"
|
52
|
-
end
|
53
|
-
|
54
|
-
setup_exchange_handler!
|
55
|
-
block.call if block
|
31
|
+
def start_connection!
|
32
|
+
@connection = Bunny.new(config.rabbit_url, connection_settings)
|
33
|
+
@connection.start
|
34
|
+
end
|
56
35
|
|
57
|
-
|
58
|
-
|
36
|
+
def connection_settings
|
37
|
+
{
|
38
|
+
timeout: config.timeout.to_i,
|
39
|
+
automatically_recover: true,
|
40
|
+
on_tcp_connection_failure: config.connection_failed_handler,
|
41
|
+
}
|
59
42
|
end
|
60
43
|
|
61
|
-
def close
|
62
|
-
|
63
|
-
|
64
|
-
else
|
65
|
-
block.call
|
66
|
-
end
|
44
|
+
def close
|
45
|
+
Philotic.logger.info "closing connection to RabbitMQ; host:#{config.rabbit_host}"
|
46
|
+
connection.close if connected?
|
67
47
|
end
|
68
48
|
|
69
49
|
def connected?
|
70
50
|
connection && connection.connected?
|
71
51
|
end
|
72
52
|
|
53
|
+
def channel
|
54
|
+
@channel ||= connection.create_channel
|
55
|
+
end
|
56
|
+
|
73
57
|
def exchange
|
74
|
-
|
58
|
+
@exchange ||= channel.headers(config.exchange_name, durable: true)
|
75
59
|
end
|
76
60
|
|
77
|
-
def
|
61
|
+
def set_exchange_return_handler!
|
78
62
|
exchange.on_return do |basic_return, metadata, payload|
|
79
|
-
config.
|
63
|
+
config.message_return_handler.call(basic_return, metadata, payload)
|
80
64
|
end
|
81
65
|
end
|
82
66
|
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
module Philotic
|
2
|
+
|
3
|
+
CONNECTION_OPTIONS = [
|
4
|
+
:rabbit_host,
|
5
|
+
:connection_failed_handler,
|
6
|
+
:connection_loss_handler,
|
7
|
+
:timeout,
|
8
|
+
]
|
9
|
+
EXCHANGE_OPTIONS = [
|
10
|
+
:exchange_name,
|
11
|
+
:message_return_handler,
|
12
|
+
]
|
13
|
+
MESSAGE_OPTIONS = [
|
14
|
+
:routing_key,
|
15
|
+
:persistent,
|
16
|
+
# :immediate,
|
17
|
+
:mandatory,
|
18
|
+
:content_type,
|
19
|
+
:content_encoding,
|
20
|
+
:priority,
|
21
|
+
:message_id,
|
22
|
+
:correlation_id,
|
23
|
+
:reply_to,
|
24
|
+
:type,
|
25
|
+
:user_id,
|
26
|
+
:app_id,
|
27
|
+
:timestamp,
|
28
|
+
:expiration,
|
29
|
+
]
|
30
|
+
|
31
|
+
PHILOTIC_HEADERS = [
|
32
|
+
:philotic_firehose,
|
33
|
+
:philotic_product,
|
34
|
+
:philotic_component,
|
35
|
+
:philotic_event_type,
|
36
|
+
]
|
37
|
+
|
38
|
+
DEFAULT_NAMED_QUEUE_OPTIONS = {
|
39
|
+
auto_delete: false,
|
40
|
+
durable: true
|
41
|
+
}
|
42
|
+
DEFAULT_ANONYMOUS_QUEUE_OPTIONS = {
|
43
|
+
auto_delete: true,
|
44
|
+
durable: false
|
45
|
+
}
|
46
|
+
|
47
|
+
DEFAULT_SUBSCRIBE_OPTIONS = {}
|
48
|
+
|
49
|
+
end
|
data/lib/philotic/event.rb
CHANGED
@@ -1,4 +1,6 @@
|
|
1
|
+
require 'philotic/constants'
|
1
2
|
require 'philotic/connection'
|
3
|
+
require 'philotic/routable'
|
2
4
|
|
3
5
|
module Philotic
|
4
6
|
class Event
|
@@ -36,25 +38,36 @@ module Philotic
|
|
36
38
|
if self.respond_to?(:"#{key}=")
|
37
39
|
send(:"#{key}=", value)
|
38
40
|
elsif self.class == Philotic::Event
|
39
|
-
|
40
|
-
self.class.send("attr_#{type}_writers").concat([:"#{key}="])
|
41
|
-
|
42
|
-
setter = Proc.new do |v|
|
43
|
-
instance_variable_set(:"@#{key}", v)
|
44
|
-
end
|
45
|
-
getter = Proc.new do
|
46
|
-
instance_variable_get(:"@#{key}")
|
47
|
-
end
|
48
|
-
self.class.send :define_method, :"#{key}=", setter
|
49
|
-
self.send(:"#{key}=", value)
|
50
|
-
self.class.send :define_method, :"#{key}", getter
|
41
|
+
set_event_attribute(type, key, value)
|
51
42
|
end
|
52
43
|
end
|
53
44
|
end
|
54
45
|
|
46
|
+
def set_event_attribute(type, key, value)
|
47
|
+
set_event_attribute_setter(key, type, value)
|
48
|
+
set_event_attribute_getter(key, type)
|
49
|
+
end
|
50
|
+
|
51
|
+
def set_event_attribute_getter(key, type)
|
52
|
+
self.class.send("attr_#{type}_readers").concat([key])
|
53
|
+
getter = lambda do
|
54
|
+
instance_variable_get(:"@#{key}")
|
55
|
+
end
|
56
|
+
self.class.send :define_method, :"#{key}", getter
|
57
|
+
end
|
58
|
+
|
59
|
+
def set_event_attribute_setter(key, type, value)
|
60
|
+
self.class.send("attr_#{type}_writers").concat([:"#{key}="])
|
61
|
+
setter = lambda do |v|
|
62
|
+
instance_variable_set(:"@#{key}", v)
|
63
|
+
end
|
64
|
+
self.class.send :define_method, :"#{key}=", setter
|
65
|
+
self.send(:"#{key}=", value)
|
66
|
+
end
|
67
|
+
|
55
68
|
def initialize(routables={}, payloads=nil)
|
56
|
-
payloads
|
57
|
-
self.timestamp
|
69
|
+
payloads ||= {}
|
70
|
+
self.timestamp = Time.now.to_i
|
58
71
|
self.philotic_firehose = true
|
59
72
|
|
60
73
|
# dynamically insert any passed in routables into both attr_routable
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'philotic/event'
|
2
|
+
|
3
|
+
module Philotic
|
4
|
+
module Logging
|
5
|
+
class Event < Philotic::Event
|
6
|
+
attr_routable :severity, :progname
|
7
|
+
attr_payload :message
|
8
|
+
|
9
|
+
def initialize(severity, message = nil, progname = nil)
|
10
|
+
super({})
|
11
|
+
self.severity = severity
|
12
|
+
self.message = message
|
13
|
+
self.progname = progname
|
14
|
+
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
require 'logger'
|
2
|
+
require 'philotic/logging/event'
|
3
|
+
|
4
|
+
module Philotic
|
5
|
+
module Logging
|
6
|
+
class Logger < ::Logger
|
7
|
+
|
8
|
+
attr_writer :event_class
|
9
|
+
|
10
|
+
def event_class
|
11
|
+
@event_class ||= Philotic::Logging::Event
|
12
|
+
end
|
13
|
+
|
14
|
+
def add(severity, message = nil, progname = nil, &block)
|
15
|
+
severity ||= UNKNOWN
|
16
|
+
if @logdev.nil? or severity < @level
|
17
|
+
return true
|
18
|
+
end
|
19
|
+
progname ||= @progname
|
20
|
+
if message.nil?
|
21
|
+
if block_given?
|
22
|
+
message = yield
|
23
|
+
else
|
24
|
+
message = progname
|
25
|
+
progname = @progname
|
26
|
+
end
|
27
|
+
end
|
28
|
+
@logdev.write(
|
29
|
+
format_message(format_severity(severity), Time.now, progname, message))
|
30
|
+
event_class.publish(severity, message, progname)
|
31
|
+
|
32
|
+
true
|
33
|
+
end
|
34
|
+
|
35
|
+
alias log add
|
36
|
+
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
data/lib/philotic/publisher.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
require 'philotic/connection'
|
2
|
+
|
2
3
|
module Philotic
|
3
4
|
module Publisher
|
4
5
|
extend self
|
@@ -7,52 +8,48 @@ module Philotic
|
|
7
8
|
Philotic::Config
|
8
9
|
end
|
9
10
|
|
10
|
-
def publish(event
|
11
|
+
def publish(event)
|
11
12
|
message_metadata = {headers: event.headers}
|
12
13
|
message_metadata.merge!(event.message_metadata) if event.message_metadata
|
13
|
-
|
14
|
+
_publish(event.payload, message_metadata)
|
14
15
|
end
|
15
16
|
|
16
|
-
|
17
|
+
private
|
18
|
+
def _publish(payload, message_metadata = {})
|
19
|
+
Philotic.connect!
|
20
|
+
unless Philotic::Connection.connected?
|
21
|
+
Philotic.log_event_published(:error, message_metadata, payload, 'unable to publish event, not connected to RabbitMQ')
|
22
|
+
return
|
23
|
+
end
|
24
|
+
message_metadata = merge_metadata(message_metadata)
|
25
|
+
|
26
|
+
payload = normalize_payload_times(payload)
|
17
27
|
|
18
|
-
if
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
28
|
+
return if config.disable_publish
|
29
|
+
|
30
|
+
Philotic::Connection.exchange.publish(payload.to_json, message_metadata)
|
31
|
+
Philotic.log_event_published(:debug, message_metadata, payload, 'published event')
|
32
|
+
end
|
33
|
+
|
34
|
+
def normalize_payload_times(payload)
|
35
|
+
payload.each do |k, v|
|
36
|
+
if v.respond_to?(:utc)
|
37
|
+
payload[k] = v.utc
|
38
|
+
elsif v.respond_to?(:to_utc)
|
39
|
+
payload[k] = v.to_utc
|
23
40
|
end
|
24
41
|
end
|
25
42
|
end
|
26
43
|
|
27
|
-
|
28
|
-
def _raw_publish(payload, message_metadata = {}, &block)
|
29
|
-
|
44
|
+
def merge_metadata(message_metadata)
|
30
45
|
publish_defaults = {}
|
31
46
|
Philotic::MESSAGE_OPTIONS.each do |key|
|
32
47
|
publish_defaults[key] = config.send(key.to_s)
|
33
48
|
end
|
34
|
-
message_metadata
|
49
|
+
message_metadata = publish_defaults.merge message_metadata
|
35
50
|
message_metadata[:headers] ||= {}
|
36
51
|
message_metadata[:headers] = {philotic_firehose: true}.merge(message_metadata[:headers])
|
37
|
-
|
38
|
-
|
39
|
-
payload.each { |k, v| payload[k] = v.utc if v.is_a? ActiveSupport::TimeWithZone }
|
40
|
-
|
41
|
-
callback = Proc.new do
|
42
|
-
Philotic.log_event_published(:debug, message_metadata, payload, 'published event')
|
43
|
-
block.call if block
|
44
|
-
end
|
45
|
-
|
46
|
-
if config.disable_publish
|
47
|
-
EventMachine.next_tick(&callback)
|
48
|
-
return
|
49
|
-
end
|
50
|
-
|
51
|
-
unless Philotic::Connection.connected?
|
52
|
-
Philotic.log_event_published(:error, message_metadata, payload, 'unable to publish event, not connected to amqp broker')
|
53
|
-
return
|
54
|
-
end
|
55
|
-
Thread.new { Philotic::Connection.exchange.publish(payload.to_json, message_metadata, &callback) }
|
52
|
+
message_metadata
|
56
53
|
end
|
57
54
|
end
|
58
55
|
end
|