mimi-messaging 0.1.10
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 +7 -0
- data/.gitignore +9 -0
- data/.rspec +2 -0
- data/.travis.yml +4 -0
- data/CODE_OF_CONDUCT.md +49 -0
- data/Gemfile +3 -0
- data/LICENSE.txt +21 -0
- data/README.md +11 -0
- data/Rakefile +6 -0
- data/bin/console +7 -0
- data/bin/setup +8 -0
- data/lib/mimi/messaging.rb +124 -0
- data/lib/mimi/messaging/connection.rb +181 -0
- data/lib/mimi/messaging/errors.rb +19 -0
- data/lib/mimi/messaging/listener.rb +72 -0
- data/lib/mimi/messaging/message.rb +74 -0
- data/lib/mimi/messaging/mock.rb +13 -0
- data/lib/mimi/messaging/mock/connection.rb +153 -0
- data/lib/mimi/messaging/mock/request.rb +18 -0
- data/lib/mimi/messaging/mock/request_processor.rb +92 -0
- data/lib/mimi/messaging/model.rb +27 -0
- data/lib/mimi/messaging/model_provider.rb +100 -0
- data/lib/mimi/messaging/msgpack/msgpack_ext.rb +14 -0
- data/lib/mimi/messaging/msgpack/type_packer.rb +104 -0
- data/lib/mimi/messaging/notification.rb +35 -0
- data/lib/mimi/messaging/provider.rb +48 -0
- data/lib/mimi/messaging/request.rb +56 -0
- data/lib/mimi/messaging/request_processor.rb +216 -0
- data/lib/mimi/messaging/request_processor/context.rb +39 -0
- data/lib/mimi/messaging/request_processor/dsl.rb +121 -0
- data/lib/mimi/messaging/version.rb +5 -0
- data/lib/tasks/console_ext.rake +6 -0
- data/lib/tasks/console_helpers.rb +116 -0
- data/mimi-messaging.gemspec +39 -0
- metadata +196 -0
@@ -0,0 +1,27 @@
|
|
1
|
+
module Mimi
|
2
|
+
module Messaging
|
3
|
+
class Model < Mimi::Messaging::Message
|
4
|
+
def self.default_queue_name
|
5
|
+
Mimi::Messaging::RequestProcessor.class_name_to_resource_name(self, 'model')
|
6
|
+
end
|
7
|
+
|
8
|
+
methods :create, :update, :show, :destroy, :list
|
9
|
+
|
10
|
+
def update(params = {})
|
11
|
+
self.replace(self.class.update(params.merge(id: id)))
|
12
|
+
end
|
13
|
+
|
14
|
+
def save
|
15
|
+
update(self)
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.find(id)
|
19
|
+
show(id: id)
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.all
|
23
|
+
list.list
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end # module Messaging
|
27
|
+
end # module Mimi
|
@@ -0,0 +1,100 @@
|
|
1
|
+
module Mimi
|
2
|
+
module Messaging
|
3
|
+
class ModelProvider < Mimi::Messaging::Provider
|
4
|
+
abstract!
|
5
|
+
|
6
|
+
def self.model(model_class, options = {})
|
7
|
+
raise "#{self} already serves model #{@model_class}" if @model_class
|
8
|
+
@model_class = model_class
|
9
|
+
@model_options = options
|
10
|
+
@model_methods_only = options[:only] ? [*(options[:only])] : nil
|
11
|
+
@model_methods_except = options[:except] ? [*(options[:except])] : nil
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.exposed_methods
|
15
|
+
model_methods = Mimi::Messaging::ModelProvider.public_instance_methods(false).dup
|
16
|
+
methods_hidden = []
|
17
|
+
methods_hidden = model_methods - model_methods.only(*@model_methods_only) if @model_methods_only
|
18
|
+
methods_hidden = model_methods.only(*@model_methods_except) if @model_methods_except
|
19
|
+
m = super
|
20
|
+
m - methods_hidden
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.model_class
|
24
|
+
@model_class || raise("#{self} has no defined model")
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.serialize(method_name = nil, &block)
|
28
|
+
if method_name && block_given?
|
29
|
+
raise "Only one of method_name or block is accepted by #{self}.serialize"
|
30
|
+
end
|
31
|
+
@serialize = method_name.to_sym if method_name
|
32
|
+
@serialize = block if block_given?
|
33
|
+
@serialize || parent_property(:serialize) || :as_json
|
34
|
+
end
|
35
|
+
|
36
|
+
def self.scope(&block)
|
37
|
+
@scope = block if block_given?
|
38
|
+
@scope || -> (r) { r }
|
39
|
+
end
|
40
|
+
|
41
|
+
def self.permitted_params(*names)
|
42
|
+
@permitted_params = names.map(&:to_sym) if names.size > 0
|
43
|
+
@permitted_params || model_class.attribute_names.map(&:to_sym)
|
44
|
+
end
|
45
|
+
|
46
|
+
def create
|
47
|
+
serialize model_class_scoped.create!(permitted_params)
|
48
|
+
end
|
49
|
+
|
50
|
+
def update(id:)
|
51
|
+
model = model_class_scoped.find(id)
|
52
|
+
model.update!(permitted_params)
|
53
|
+
serialize model
|
54
|
+
end
|
55
|
+
|
56
|
+
def show(id:)
|
57
|
+
serialize model_class_scoped.find(id)
|
58
|
+
end
|
59
|
+
|
60
|
+
def find
|
61
|
+
serialize model_class_scoped.find_by!(params)
|
62
|
+
end
|
63
|
+
|
64
|
+
def destroy(id:)
|
65
|
+
model_class_scoped.find(id)
|
66
|
+
raise "#{self.class}#destroy is not implemented"
|
67
|
+
end
|
68
|
+
|
69
|
+
def list
|
70
|
+
reply list: model_class_scoped.all.map { |v| serialize v }
|
71
|
+
end
|
72
|
+
|
73
|
+
private
|
74
|
+
|
75
|
+
def permitted_params
|
76
|
+
params.except('id').only(*self.class.permitted_params.map(&:to_s)).to_hash
|
77
|
+
end
|
78
|
+
|
79
|
+
def model_class
|
80
|
+
self.class.model_class
|
81
|
+
end
|
82
|
+
|
83
|
+
def model_class_scoped
|
84
|
+
scope_block = self.class.scope
|
85
|
+
__execute(model_class, &scope_block)
|
86
|
+
end
|
87
|
+
|
88
|
+
def serialize(model_instance, opts = nil)
|
89
|
+
opts ||= params
|
90
|
+
if self.class.serialize.is_a?(Symbol)
|
91
|
+
model_instance.send(self.class.serialize, opts)
|
92
|
+
elsif self.class.serialize.is_a?(Proc)
|
93
|
+
self.class.serialize.call(model_instance, opts)
|
94
|
+
else
|
95
|
+
raise "#{self.class}#serialize is neither a Symbol or Proc"
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end # class ModelProvider
|
99
|
+
end # module Messaging
|
100
|
+
end # module Mimi
|
@@ -0,0 +1,14 @@
|
|
1
|
+
#
|
2
|
+
# MessagePack extensions for common types
|
3
|
+
#
|
4
|
+
Mimi::Messaging::TypePacker.register(
|
5
|
+
Time,
|
6
|
+
from_bytes: -> (b) { Time.at(b.unpack('D').first).utc },
|
7
|
+
to_bytes: -> (v) { [v.utc.to_f].pack('D') }
|
8
|
+
)
|
9
|
+
|
10
|
+
Mimi::Messaging::TypePacker.register(
|
11
|
+
BigDecimal,
|
12
|
+
from_bytes: -> (b) { BigDecimal.new(b) },
|
13
|
+
to_bytes: -> (v) { v.to_s }
|
14
|
+
)
|
@@ -0,0 +1,104 @@
|
|
1
|
+
require 'msgpack'
|
2
|
+
|
3
|
+
module Mimi
|
4
|
+
module Messaging
|
5
|
+
class TypePacker
|
6
|
+
APPLICATION_TYPE_EXT = 0x00
|
7
|
+
|
8
|
+
# Registers a new type packer for msgpack.
|
9
|
+
#
|
10
|
+
# @example
|
11
|
+
# TypePacker.register(
|
12
|
+
# Time,
|
13
|
+
# from_bytes: -> (b) { Time.at(b.unpack('D').first) },
|
14
|
+
# to_bytes: -> (v) { [v.to_f].pack('D') }
|
15
|
+
# )
|
16
|
+
#
|
17
|
+
def self.register(type, opts = {})
|
18
|
+
raise ArgumentError, 'Invalid :from_bytes, proc expected' unless opts[:from_bytes].is_a?(Proc)
|
19
|
+
raise ArgumentError, 'Invalid :to_bytes, proc expected' unless opts[:to_bytes].is_a?(Proc)
|
20
|
+
type_name = type.to_s
|
21
|
+
params = opts.dup.merge(type: type, type_name: type_name)
|
22
|
+
type_packers[type] = params
|
23
|
+
type.send(:define_method, :to_msgpack_ext) { Mimi::Messaging::TypePacker.pack(self) }
|
24
|
+
MessagePack::DefaultFactory.register_type(
|
25
|
+
APPLICATION_TYPE_EXT,
|
26
|
+
type,
|
27
|
+
packer: :to_msgpack_ext
|
28
|
+
)
|
29
|
+
|
30
|
+
MessagePack::DefaultFactory.register_type(
|
31
|
+
APPLICATION_TYPE_EXT,
|
32
|
+
self,
|
33
|
+
unpacker: :unpack
|
34
|
+
)
|
35
|
+
end
|
36
|
+
|
37
|
+
# Returns a set of registered type packers
|
38
|
+
#
|
39
|
+
def self.type_packers
|
40
|
+
@type_packers ||= {}
|
41
|
+
end
|
42
|
+
|
43
|
+
# Pack a value using a type packer, registered for the value class
|
44
|
+
#
|
45
|
+
def self.pack(value)
|
46
|
+
type_packer = type_packers.values.find { |p| value.is_a?(p[:type]) }
|
47
|
+
raise "No packer registered for type #{value.class}" unless type_packer
|
48
|
+
bytes = type_packer[:to_bytes].call(value)
|
49
|
+
"#{type_packer[:type_name]}=#{bytes}"
|
50
|
+
end
|
51
|
+
|
52
|
+
# Unpack a value, using a registered type packer
|
53
|
+
#
|
54
|
+
def self.unpack(value)
|
55
|
+
type_name, _, bytes = value.partition('=') # splits "Type=bytes" into ['Type', '=', 'bytes']
|
56
|
+
type_packer = type_packers.values.find { |p| p[:type_name] == type_name }
|
57
|
+
raise "No unpacker registered for type #{type_name}" unless type_packer
|
58
|
+
type_packer[:from_bytes].call(bytes)
|
59
|
+
end
|
60
|
+
|
61
|
+
# def self.register_type_packer!
|
62
|
+
# return if @type_packer_registered
|
63
|
+
# # pk = MessagePack::Packer.new
|
64
|
+
# # pk.register_type(APPLICATION_TYPE_EXT) { |v| TypePacker.pack(v) }
|
65
|
+
# uk = MessagePack::Unpacker.new
|
66
|
+
# uk.register_type(APPLICATION_TYPE_EXT) { |b| TypePacker.unpack(b) }
|
67
|
+
# @type_packer_registered = true
|
68
|
+
# end
|
69
|
+
end # class TypePacker
|
70
|
+
end # module Messaging
|
71
|
+
|
72
|
+
end # module Mimi
|
73
|
+
|
74
|
+
|
75
|
+
|
76
|
+
# class A
|
77
|
+
# class B
|
78
|
+
# attr_reader :value
|
79
|
+
# def initialize(value)
|
80
|
+
# @value = value
|
81
|
+
# end
|
82
|
+
# end # class B
|
83
|
+
# end # class A
|
84
|
+
|
85
|
+
# TypePacker.register(
|
86
|
+
# Time,
|
87
|
+
# from_bytes: -> (b) { Time.at(b.unpack('D').first) },
|
88
|
+
# to_bytes: -> (v) { [v.to_f].pack('D') }
|
89
|
+
# )
|
90
|
+
|
91
|
+
# TypePacker.register(
|
92
|
+
# A::B,
|
93
|
+
# from_bytes: -> (b) { A::B.new(b.unpack('D').first) },
|
94
|
+
# to_bytes: -> (v) { [v.value.to_f].pack('D') }
|
95
|
+
# )
|
96
|
+
|
97
|
+
# TypePacker.register(
|
98
|
+
# DateTime,
|
99
|
+
# from_bytes: -> (b) { Time.at(b.unpack('D').first).to_datetime },
|
100
|
+
# to_bytes: -> (v) { [v.to_time.to_f].pack('D') }
|
101
|
+
# )
|
102
|
+
|
103
|
+
# m = "\x83\xA1a\xC7\r\x00Time=\x1D@\x95\xC7\x80\xCA\xD5A\xA1b\x02\xA1c\xC7\x11\x00DateTime=X@\x95\xC7\x80\xCA\xD5A"
|
104
|
+
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module Mimi
|
2
|
+
module Messaging
|
3
|
+
class Notification < Hashie::Mash
|
4
|
+
def self.notification(name, _opts = {})
|
5
|
+
@notification_name = name
|
6
|
+
end
|
7
|
+
|
8
|
+
def self.notification_name
|
9
|
+
@notification_name || default_notification_name
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.default_notification_name
|
13
|
+
Mimi::Messaging::RequestProcessor.class_name_to_resource_name(self, 'notification')
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.broadcast(name, data = {}, opts = {})
|
17
|
+
headers = {
|
18
|
+
method_name: name.to_s,
|
19
|
+
Mimi::Messaging::CONTEXT_ID_KEY => Mimi::Messaging.logger.context_id
|
20
|
+
}
|
21
|
+
Mimi::Messaging.broadcast(
|
22
|
+
notification_name, Message.encode(data), opts.merge(headers: headers)
|
23
|
+
)
|
24
|
+
end
|
25
|
+
|
26
|
+
def broadcast(name, opts = {})
|
27
|
+
self.class.broadcast(name, self, opts)
|
28
|
+
end
|
29
|
+
|
30
|
+
def to_s
|
31
|
+
to_hash.to_s
|
32
|
+
end
|
33
|
+
end # class Notification
|
34
|
+
end # module Messaging
|
35
|
+
end # module Mimi
|
@@ -0,0 +1,48 @@
|
|
1
|
+
module Mimi
|
2
|
+
module Messaging
|
3
|
+
class Provider < RequestProcessor
|
4
|
+
abstract!
|
5
|
+
queue_options exclusive: false, auto_delete: true
|
6
|
+
|
7
|
+
def self.default_queue_name
|
8
|
+
class_name_to_resource_name(name, 'provider')
|
9
|
+
end
|
10
|
+
|
11
|
+
def initialize(d, m, p)
|
12
|
+
super
|
13
|
+
begin
|
14
|
+
catch(:halt) do
|
15
|
+
reply(@result) unless request.replied?
|
16
|
+
end
|
17
|
+
rescue StandardError => e
|
18
|
+
__execute_error_handlers(e)
|
19
|
+
end
|
20
|
+
if request.get? && !request.replied?
|
21
|
+
logger.error "No response sent to #{request.canonical_name}"
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
# Default error handler for RequestError and its descendants
|
26
|
+
#
|
27
|
+
error RequestError do |e|
|
28
|
+
logger.warn "#{request.canonical_name}: #{e} (#{e.params})"
|
29
|
+
reply error: e.message, params: e.params
|
30
|
+
end
|
31
|
+
|
32
|
+
# Default error handler for StandardError and its descendants
|
33
|
+
#
|
34
|
+
error StandardError do |e|
|
35
|
+
logger.error "#{request.canonical_name}: #{e} (#{e.class})"
|
36
|
+
logger.debug((e.backtrace || ['<no backtrace>']).join("\n"))
|
37
|
+
reply error: e.message
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
def reply(data = {})
|
43
|
+
request.send_response(data)
|
44
|
+
halt
|
45
|
+
end
|
46
|
+
end # class Provider
|
47
|
+
end # module Messaging
|
48
|
+
end # module Mimi
|
@@ -0,0 +1,56 @@
|
|
1
|
+
module Mimi
|
2
|
+
module Messaging
|
3
|
+
class Request
|
4
|
+
attr_reader :request_processor, :delivery_info, :metadata, :raw_message, :params
|
5
|
+
|
6
|
+
def initialize(request_processor, d, m, p)
|
7
|
+
@request_processor = request_processor
|
8
|
+
@delivery_info = d
|
9
|
+
@metadata = m
|
10
|
+
@raw_message = p
|
11
|
+
@params = Params.new(Mimi::Messaging::Message.decode(@raw_message))
|
12
|
+
end
|
13
|
+
|
14
|
+
def method_name
|
15
|
+
metadata.headers && metadata.headers['method_name']
|
16
|
+
end
|
17
|
+
|
18
|
+
def type
|
19
|
+
request_processor.request_type(@delivery_info, @metadata, @raw_message)
|
20
|
+
end
|
21
|
+
|
22
|
+
def canonical_name
|
23
|
+
"#{type.to_s.upcase} #{request_processor.resource_name}/#{method_name}"
|
24
|
+
end
|
25
|
+
|
26
|
+
def params_symbolized
|
27
|
+
Hashie.symbolize_keys(params.to_hash)
|
28
|
+
end
|
29
|
+
|
30
|
+
def get?
|
31
|
+
type == :get
|
32
|
+
end
|
33
|
+
|
34
|
+
def send_response(data = {})
|
35
|
+
return if !get? || replied?
|
36
|
+
raise ArgumentError, 'Invalid response format, Hash is expected' unless data.is_a?(Hash)
|
37
|
+
reply_to_queue_name = metadata[:reply_to]
|
38
|
+
raw_message = Mimi::Messaging::Message.encode(data)
|
39
|
+
request_processor.connection.post(
|
40
|
+
reply_to_queue_name, raw_message, correlation_id: metadata[:correlation_id]
|
41
|
+
)
|
42
|
+
@replied = true
|
43
|
+
end
|
44
|
+
|
45
|
+
def replied?
|
46
|
+
@replied
|
47
|
+
end
|
48
|
+
|
49
|
+
class Params < Hashie::Mash
|
50
|
+
def to_s
|
51
|
+
to_hash.to_s
|
52
|
+
end
|
53
|
+
end # class Params
|
54
|
+
end # class Request
|
55
|
+
end # module Messaging
|
56
|
+
end # module Mimi
|
@@ -0,0 +1,216 @@
|
|
1
|
+
require_relative 'request_processor/dsl'
|
2
|
+
require_relative 'request_processor/context'
|
3
|
+
|
4
|
+
module Mimi
|
5
|
+
module Messaging
|
6
|
+
class RequestProcessor
|
7
|
+
extend DSL
|
8
|
+
include Context
|
9
|
+
|
10
|
+
abstract!
|
11
|
+
queue_options exclusive: false, auto_delete: true
|
12
|
+
|
13
|
+
RequestError = Mimi::Messaging::RequestError
|
14
|
+
|
15
|
+
def self.inherited(request_processor_class)
|
16
|
+
request_processor_class.parent = self
|
17
|
+
Mimi::Messaging.register_request_processor_class(request_processor_class)
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.resource_name
|
21
|
+
queue_name
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.request_type(_d, metadata, _p)
|
25
|
+
metadata.reply_to ? :get : :post
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.started?
|
29
|
+
!@consumer.nil?
|
30
|
+
end
|
31
|
+
|
32
|
+
def self.connection
|
33
|
+
@connection ||= Mimi::Messaging.connection_for(resource_name)
|
34
|
+
end
|
35
|
+
|
36
|
+
def self.channel
|
37
|
+
@channel ||= connection.create_channel(options)
|
38
|
+
end
|
39
|
+
|
40
|
+
def self.construct_queue
|
41
|
+
channel.create_queue(queue_name, queue_options)
|
42
|
+
end
|
43
|
+
|
44
|
+
def self.start
|
45
|
+
return if abstract?
|
46
|
+
raise "#{name} already started" if started?
|
47
|
+
logger.debug "#{self} starting to serve '#{resource_name}' (#{exposed_methods})"
|
48
|
+
@queue = construct_queue
|
49
|
+
@consumer_mutex = Mutex.new
|
50
|
+
@consumer_mutex.synchronize do
|
51
|
+
@consumer = @queue.subscribe(manual_ack: true) do |d, m, p|
|
52
|
+
begin
|
53
|
+
new(d, m, p)
|
54
|
+
rescue StandardError => e
|
55
|
+
logger.error e.to_s
|
56
|
+
logger.debug e.backtrace.join("\n")
|
57
|
+
ensure
|
58
|
+
@consumer_mutex.synchronize do
|
59
|
+
@consumer.channel.ack(d.delivery_tag) if @consumer.channel && @consumer.channel.active
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
# consumer created, mutex released
|
65
|
+
end
|
66
|
+
|
67
|
+
def self.stop
|
68
|
+
return if abstract?
|
69
|
+
raise "#{name} already stopped" unless started?
|
70
|
+
@consumer_mutex.synchronize do
|
71
|
+
@consumer.cancel if @consumer
|
72
|
+
end
|
73
|
+
@consumer = nil
|
74
|
+
@queue = nil
|
75
|
+
@channel = nil
|
76
|
+
@connection = nil
|
77
|
+
end
|
78
|
+
|
79
|
+
def initialize(d, m, p)
|
80
|
+
initialize_logging_context!(m.headers)
|
81
|
+
@request = Mimi::Messaging::Request.new(self.class, d, m, p)
|
82
|
+
@result = nil
|
83
|
+
method_name = request.method_name
|
84
|
+
begin
|
85
|
+
catch(:halt) do
|
86
|
+
@result = __execute_method(method_name)
|
87
|
+
end
|
88
|
+
rescue StandardError => e
|
89
|
+
__execute_error_handlers(e)
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
# Initializes logging context.
|
94
|
+
#
|
95
|
+
# Starts a new logging contenxt or inherits a context id from the message headers.
|
96
|
+
#
|
97
|
+
# @param headers [Hash,nil] message headers
|
98
|
+
#
|
99
|
+
def initialize_logging_context!(headers)
|
100
|
+
context_id = (headers || {})[Mimi::Messaging::CONTEXT_ID_KEY]
|
101
|
+
return logger.new_context! unless context_id
|
102
|
+
logger.context_id = context_id
|
103
|
+
end
|
104
|
+
|
105
|
+
# Request logger
|
106
|
+
#
|
107
|
+
# Usage:
|
108
|
+
# options log_requests: true
|
109
|
+
# Or:
|
110
|
+
# options log_requests: { log_level: :info }
|
111
|
+
#
|
112
|
+
before do
|
113
|
+
opts = self.class.options[:log_requests]
|
114
|
+
next unless opts
|
115
|
+
message = "#{request.canonical_name}: #{params}"
|
116
|
+
level = opts.is_a?(Hash) ? (opts[:log_level] || 'debug') : 'debug'
|
117
|
+
logger.send level.to_sym, message
|
118
|
+
end
|
119
|
+
|
120
|
+
# Request benchmark logger
|
121
|
+
#
|
122
|
+
# Usage:
|
123
|
+
# options log_benchmarks: true
|
124
|
+
# Or:
|
125
|
+
# options log_benchmarks: { log_level: :info }
|
126
|
+
#
|
127
|
+
around do |b|
|
128
|
+
opts = self.class.options[:log_benchmarks]
|
129
|
+
t_start = Time.now
|
130
|
+
b.call
|
131
|
+
next unless opts
|
132
|
+
message = "#{request.canonical_name}: completed in %.1fms" % [(Time.now - t_start) * 1000.0]
|
133
|
+
level = opts.is_a?(Hash) ? (opts[:log_level] || 'debug') : 'debug'
|
134
|
+
logger.send level.to_sym, message
|
135
|
+
end
|
136
|
+
|
137
|
+
# Default error handler for StandardError and its descendants
|
138
|
+
#
|
139
|
+
error StandardError do |e|
|
140
|
+
logger.error "#{request.canonical_name}: #{e} (#{e.class})"
|
141
|
+
logger.debug((e.backtrace || ['<no backtrace>']).join("\n"))
|
142
|
+
halt
|
143
|
+
end
|
144
|
+
|
145
|
+
private
|
146
|
+
|
147
|
+
attr_reader :request
|
148
|
+
|
149
|
+
def __execute_method(method_name)
|
150
|
+
unless method_name && method_name.is_a?(String)
|
151
|
+
raise 'RequestProcessor method name is not specified in the request'
|
152
|
+
end
|
153
|
+
method_name = method_name.to_sym
|
154
|
+
unless self.class.exposed_methods.include?(method_name)
|
155
|
+
raise "RequestProcessor method (\##{method_name}) is not exposed"
|
156
|
+
end
|
157
|
+
|
158
|
+
method = self.class.instance_method(method_name.to_sym)
|
159
|
+
accepted_params = request.params_symbolized.only(*method.parameters.map(&:last))
|
160
|
+
result = nil
|
161
|
+
method_block = proc do
|
162
|
+
args = [method_name]
|
163
|
+
args << accepted_params unless accepted_params.empty?
|
164
|
+
catch(:halt) do
|
165
|
+
result = send(*args)
|
166
|
+
end
|
167
|
+
end
|
168
|
+
self.class.filters(:before).each { |f| __execute(&f[:block]) }
|
169
|
+
wrapped_block = self.class.filters(:around).reduce(method_block) do |a, e|
|
170
|
+
__bind(a, &e[:block])
|
171
|
+
end
|
172
|
+
wrapped_block.call
|
173
|
+
self.class.filters(:after, false).each { |f| __execute(&f[:block]) }
|
174
|
+
result
|
175
|
+
end
|
176
|
+
|
177
|
+
def __execute_error_handlers(error)
|
178
|
+
catch(:halt) do
|
179
|
+
result = self.class.filters(:error, false).reduce(error) do |a, e|
|
180
|
+
if e[:args].any? { |error_klass| a.is_a?(error_klass) }
|
181
|
+
__execute(a, &e[:block])
|
182
|
+
else
|
183
|
+
a
|
184
|
+
end
|
185
|
+
end
|
186
|
+
logger.error "Error '#{error}' (#{error.class}) unprocessed by error handlers " \
|
187
|
+
"(result=#{result.class})"
|
188
|
+
end
|
189
|
+
rescue StandardError => e
|
190
|
+
logger.error "Error raised by error handler '#{request.canonical_name}': #{e}"
|
191
|
+
logger.debug e.backtrace.join("\n")
|
192
|
+
end
|
193
|
+
|
194
|
+
def reply(_data = {})
|
195
|
+
logger.warn "#{self.class}#reply not implemented"
|
196
|
+
halt
|
197
|
+
end
|
198
|
+
|
199
|
+
def halt
|
200
|
+
throw :halt
|
201
|
+
end
|
202
|
+
|
203
|
+
def params
|
204
|
+
request.params
|
205
|
+
end
|
206
|
+
|
207
|
+
def logger
|
208
|
+
Mimi::Messaging.logger
|
209
|
+
end
|
210
|
+
|
211
|
+
def self.logger
|
212
|
+
Mimi::Messaging.logger
|
213
|
+
end
|
214
|
+
end # class RequestProcessor
|
215
|
+
end # module Messaging
|
216
|
+
end # module Mimi
|