bbk-app 1.0.0.72899

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.
Files changed (49) hide show
  1. checksums.yaml +7 -0
  2. data/Gemfile +7 -0
  3. data/Gemfile.lock +177 -0
  4. data/README.md +38 -0
  5. data/bin/console +15 -0
  6. data/bin/setup +8 -0
  7. data/lib/bbk/app/dispatcher/message.rb +47 -0
  8. data/lib/bbk/app/dispatcher/message_stream.rb +42 -0
  9. data/lib/bbk/app/dispatcher/queue_stream_strategy.rb +54 -0
  10. data/lib/bbk/app/dispatcher/result.rb +20 -0
  11. data/lib/bbk/app/dispatcher/route.rb +37 -0
  12. data/lib/bbk/app/dispatcher.rb +210 -0
  13. data/lib/bbk/app/factory.rb +26 -0
  14. data/lib/bbk/app/handler.rb +69 -0
  15. data/lib/bbk/app/matchers/base.rb +50 -0
  16. data/lib/bbk/app/matchers/delivery_info.rb +35 -0
  17. data/lib/bbk/app/matchers/full.rb +41 -0
  18. data/lib/bbk/app/matchers/headers.rb +23 -0
  19. data/lib/bbk/app/matchers/payload.rb +23 -0
  20. data/lib/bbk/app/matchers.rb +28 -0
  21. data/lib/bbk/app/middlewares/active_record_pool.rb +21 -0
  22. data/lib/bbk/app/middlewares/base.rb +20 -0
  23. data/lib/bbk/app/middlewares/from_block.rb +26 -0
  24. data/lib/bbk/app/middlewares/self_killer.rb +66 -0
  25. data/lib/bbk/app/middlewares/watchdog.rb +78 -0
  26. data/lib/bbk/app/middlewares.rb +12 -0
  27. data/lib/bbk/app/processors/base.rb +46 -0
  28. data/lib/bbk/app/processors/ping.rb +26 -0
  29. data/lib/bbk/app/processors/pong.rb +16 -0
  30. data/lib/bbk/app/processors.rb +3 -0
  31. data/lib/bbk/app/proxy_logger.rb +42 -0
  32. data/lib/bbk/app/thread_pool.rb +75 -0
  33. data/lib/bbk/app/version.rb +8 -0
  34. data/lib/bbk/app.rb +23 -0
  35. data/sig/bbk/app/callable.rbs +3 -0
  36. data/sig/bbk/app/dispatcher/message.rbs +33 -0
  37. data/sig/bbk/app/dispatcher/message_stream.rbs +15 -0
  38. data/sig/bbk/app/dispatcher/queue_stream_strategy.rbs +12 -0
  39. data/sig/bbk/app/dispatcher/result.rbs +12 -0
  40. data/sig/bbk/app/dispatcher/route.rbs +18 -0
  41. data/sig/bbk/app/dispatcher/stream_strategy.rbs +13 -0
  42. data/sig/bbk/app/dispatcher.rbs +62 -0
  43. data/sig/bbk/app/factory.rbs +21 -0
  44. data/sig/bbk/app/handler.rbs +19 -0
  45. data/sig/bbk/app/matchers.rbs +12 -0
  46. data/sig/bbk/app/middlewares/self_killer.rbs +26 -0
  47. data/sig/bbk/app/middlewares/watchdog.rbs +40 -0
  48. data/sig/bbk/app/processors/base.rbs +21 -0
  49. metadata +327 -0
@@ -0,0 +1,69 @@
1
+ module BBK
2
+ module App
3
+ class Handler
4
+
5
+ def initialize(&block)
6
+ @handlers = {}
7
+ @default = if block_given?
8
+ block
9
+ else
10
+ lambda do |*_args|
11
+ # delivery_info, properties, body = message
12
+ end
13
+ end
14
+ end
15
+
16
+ # регистрация обработчика
17
+ # тип матчера, парметры матчера, Обработчик | Класс обработчика, [аргументы обработчика]
18
+ def register(*args, **kwargs, &block)
19
+ type, rule, callable = nil
20
+
21
+ if args.first.respond_to?(:rule)
22
+ type, *rule = args.first.rule
23
+ elsif args.first.is_a?(Symbol) || args.first.is_a?(String)
24
+ type = args.shift.to_sym
25
+ rule = args.shift
26
+ if rule.nil?
27
+ $logger&.warn("Not found processor rule in positional arguments. Use keyword arguments #{kwargs} as rule")
28
+ rule = kwargs
29
+ kwargs = {}
30
+ end
31
+ raise "rule is not a Hash: #{args.inspect}" unless rule.is_a?(Hash)
32
+ else
33
+ raise "type and rule or method :rule missing: #{args.inspect}"
34
+ end
35
+ args.push block if block_given?
36
+
37
+ callable = if args.first.is_a?(Class)
38
+ BBK::App::Factory.new(*args, **kwargs)
39
+ elsif args.first.respond_to?(:call)
40
+ args.first
41
+ else
42
+ raise "callable object or class missing: #{args.inspect}"
43
+ end
44
+
45
+ matcher = BBK::App::Matchers.create(type, *[rule].flatten)
46
+ @handlers.each do |m, _c|
47
+ $logger&.warn("Handler with same matcher already registered: #{m.inspect}") if m == matcher
48
+ end
49
+
50
+ @handlers[BBK::App::Matchers.create(type, *[rule].flatten)] = callable
51
+ end
52
+
53
+ def default(&block)
54
+ @default = block
55
+ end
56
+
57
+ def match(metadata, payload, delivery_info)
58
+ @handlers.each_with_object([nil, @default]) do |p, _res|
59
+ m, h = p
60
+ if (match = m.match(metadata, payload, delivery_info))
61
+ return [match, h]
62
+ end
63
+ end
64
+ end
65
+
66
+ end
67
+ end
68
+ end
69
+
@@ -0,0 +1,50 @@
1
+ module BBK
2
+ module App
3
+ module Matchers
4
+ class Base
5
+
6
+ attr_reader :rule
7
+
8
+ def hash
9
+ (self.class.to_s + rule.to_s).hash
10
+ end
11
+
12
+ def ==(other)
13
+ self.class == other.class && rule == other.rule
14
+ end
15
+
16
+ def eql?(other)
17
+ self == other
18
+ end
19
+
20
+ def keys_deep(data)
21
+ data.inject([]) do |res, p|
22
+ k, _v = p
23
+ res.push k
24
+ res += keys_deep(data[k]) if data[k].is_a? Hash
25
+ res
26
+ end
27
+ end
28
+
29
+ def match_impl(rule, data)
30
+ result = rule.each_with_object({}.with_indifferent_access) do |p, res|
31
+ k, v = p
32
+
33
+ if v == :any && data.key?(k.to_sym)
34
+ res[k.to_sym] = data[k.to_sym]
35
+ elsif v.is_a? Hash
36
+ res[k.to_sym] = match_impl(v, data[k.to_sym] || {})
37
+ elsif v == data[k.to_sym]
38
+ res[k.to_sym] = data[k.to_sym]
39
+ end
40
+ end
41
+
42
+ result.keys.size == rule.keys.size && keys_deep(result).count >= keys_deep(rule).count ? result : nil
43
+ end
44
+
45
+
46
+ end
47
+ end
48
+ end
49
+ end
50
+
@@ -0,0 +1,35 @@
1
+ require 'bbk/app/matchers/base'
2
+
3
+ module BBK
4
+ module App
5
+ module Matchers
6
+ class DeliveryInfo < Base
7
+
8
+
9
+ def initialize(rule)
10
+ @rule = rule.with_indifferent_access
11
+ end
12
+
13
+ def match(_headers, _payload, delivery_info, *_args)
14
+ delivery_info = delivery_info.to_hash unless delivery_info.is_a? Hash
15
+ match_impl(@rule, delivery_info.with_indifferent_access)
16
+ rescue StandardError
17
+ nil
18
+ end
19
+
20
+ def match_impl(rule, data)
21
+ result = super
22
+ if !result && (key_rule = rule[:routing_key])
23
+ regexp_rule = Regexp.new("\\A#{key_rule.gsub('.', '\.').gsub('*', '\S+').gsub('#',
24
+ '.*')}\\Z")
25
+ check = regexp_rule.match?(data[:routing_key])
26
+ result = ({ 'routing_key' => data[:routing_key] } if check)
27
+ end
28
+ result
29
+ end
30
+
31
+ end
32
+ end
33
+ end
34
+ end
35
+
@@ -0,0 +1,41 @@
1
+ require 'bbk/app/matchers/headers'
2
+ require 'bbk/app/matchers/payload'
3
+ require 'bbk/app/matchers/delivery_info'
4
+
5
+ module BBK
6
+ module App
7
+ module Matchers
8
+ class Full < Base
9
+
10
+ def initialize(*args)
11
+ if args.size == 1
12
+ arg = args[0]
13
+ hrule = arg.fetch(:headers, {})
14
+ prule = arg.fetch(:payload, {})
15
+ drule = arg.fetch(:delivery_info, {})
16
+ else
17
+ hrule, prule, drule = *args
18
+ end
19
+
20
+ @hm = Headers.new(hrule)
21
+ @pm = Payload.new(prule)
22
+ @dm = DeliveryInfo.new(drule)
23
+ @rule = [hrule, prule, drule]
24
+ end
25
+
26
+ def match(headers, payload, delivery_info, *_args)
27
+ return unless (hr = @hm.match(headers, payload, delivery_info))
28
+ return unless (pr = @pm.match(headers, payload, delivery_info))
29
+ return unless (dr = @dm.match(headers, payload, delivery_info))
30
+
31
+ [hr, pr, dr]
32
+ rescue StandardError
33
+ nil
34
+ end
35
+
36
+
37
+ end
38
+ end
39
+ end
40
+ end
41
+
@@ -0,0 +1,23 @@
1
+ require 'bbk/app/matchers/base'
2
+
3
+ module BBK
4
+ module App
5
+ module Matchers
6
+ class Headers < Base
7
+
8
+ def initialize(rule)
9
+ @rule = rule.with_indifferent_access
10
+ end
11
+
12
+ def match(headers, _payload = nil, _delivery_info = nil, *_args)
13
+ match_impl(@rule, headers.with_indifferent_access)
14
+ rescue StandardError
15
+ nil
16
+ end
17
+
18
+
19
+ end
20
+ end
21
+ end
22
+ end
23
+
@@ -0,0 +1,23 @@
1
+ require 'bbk/app/matchers/base'
2
+
3
+ module BBK
4
+ module App
5
+ module Matchers
6
+ class Payload < Base
7
+
8
+ def initialize(rule)
9
+ @rule = rule.with_indifferent_access
10
+ end
11
+
12
+ def match(_headers, payload, _delivery_info = nil, *_args)
13
+ payload = JSON(payload) if payload&.is_a?(String)
14
+ match_impl(@rule, payload.with_indifferent_access)
15
+ rescue StandardError
16
+ nil
17
+ end
18
+
19
+ end
20
+ end
21
+ end
22
+ end
23
+
@@ -0,0 +1,28 @@
1
+ require 'bbk/app/matchers/headers'
2
+ require 'bbk/app/matchers/payload'
3
+ require 'bbk/app/matchers/delivery_info'
4
+ require 'bbk/app/matchers/full'
5
+
6
+ module BBK
7
+ module App
8
+ module Matchers
9
+
10
+ def self.create(type, *args)
11
+ case type
12
+ when :meta, :headers
13
+ Headers.new(args.first)
14
+ when :payload
15
+ Payload.new(args.first)
16
+ when :delivery, :delivery_info
17
+ DeliveryInfo.new(args.first)
18
+ when :full
19
+ Full.new(*args)
20
+ else
21
+ raise "there is no such matcher: #{type}"
22
+ end
23
+ end
24
+
25
+ end
26
+ end
27
+ end
28
+
@@ -0,0 +1,21 @@
1
+ require 'active_record'
2
+ require 'bbk/app/middlewares/base'
3
+
4
+ module BBK
5
+ module App
6
+ module Middlewares
7
+ class ActiveRecordPool < Base
8
+
9
+
10
+ def call(msg)
11
+ ::ActiveRecord::Base.connection_pool.with_connection do
12
+ app.call(msg)
13
+ end
14
+ end
15
+
16
+
17
+ end
18
+ end
19
+ end
20
+ end
21
+
@@ -0,0 +1,20 @@
1
+ module BBK
2
+ module App
3
+ module Middlewares
4
+ class Base
5
+
6
+ attr_reader :app
7
+
8
+ def initialize(app)
9
+ @app = app
10
+ end
11
+
12
+ def call(_msg)
13
+ raise 'Middleware not implemented!'
14
+ end
15
+
16
+ end
17
+ end
18
+ end
19
+ end
20
+
@@ -0,0 +1,26 @@
1
+ module BBK
2
+ module App
3
+ module Middlewares
4
+ class FromBlock < Base
5
+
6
+ def initialize(&block)
7
+ raise ArgumentError.new('Not passed block') unless block_given?
8
+
9
+ @block = block
10
+ end
11
+
12
+ def build(app)
13
+ @app = app
14
+ self
15
+ end
16
+
17
+ def call(msg)
18
+ @block.call(app, msg)
19
+ end
20
+
21
+
22
+ end
23
+ end
24
+ end
25
+ end
26
+
@@ -0,0 +1,66 @@
1
+ module BBK
2
+ module App
3
+ module Middlewares
4
+ class SelfKiller
5
+
6
+
7
+ SELF_KILLER_LOG_INTERVAL = 300
8
+
9
+ attr_reader :dispatcher, :count, :threshold, :stop_time
10
+
11
+ def initialize(dispatcher, delay: 10 * 60, threshold: 10_000, logger: ::Logger.new(STDOUT))
12
+ @dispatcher = dispatcher
13
+ @threshold = threshold
14
+ @count = 0
15
+ @stop_time = Process.clock_gettime(Process::CLOCK_MONOTONIC) + delay
16
+ @stopping = false
17
+ @logger = logger
18
+ @logger.info "[SelfKiller] Initializing: #{@count}/#{@threshold}"
19
+ reset_log_timer
20
+ end
21
+
22
+ def build(app)
23
+ @app = app
24
+ self
25
+ end
26
+
27
+ def call(msg)
28
+ @count += 1
29
+ if time_exceed?(@log_timer)
30
+ @logger.info "[SelfKiller] Threshold status: #{@count}/#{@threshold}, delayed: #{!time_exceed?}"
31
+ reset_log_timer
32
+ end
33
+ close_dispatcher if stop?
34
+
35
+ @app.call(msg)
36
+ end
37
+
38
+ protected
39
+
40
+ def reset_log_timer
41
+ @log_timer = Process.clock_gettime(Process::CLOCK_MONOTONIC) + SELF_KILLER_LOG_INTERVAL
42
+ end
43
+
44
+ def stop?
45
+ !@stopping && threshold_exceed? && time_exceed?
46
+ end
47
+
48
+ def threshold_exceed?
49
+ @count > @threshold
50
+ end
51
+
52
+ def time_exceed?(time = @stop_time)
53
+ Process.clock_gettime(Process::CLOCK_MONOTONIC) > time
54
+ end
55
+
56
+ def close_dispatcher
57
+ @stopping = true
58
+ @logger.warn '[SelfKiller] Threshold exceeded, closing dispatcher...'
59
+ Thread.new { @dispatcher.close } # Don't block current call
60
+ end
61
+
62
+ end
63
+ end
64
+ end
65
+ end
66
+
@@ -0,0 +1,78 @@
1
+ module BBK
2
+ module App
3
+ module Middlewares
4
+ class Watchdog < Base
5
+
6
+ attr_reader :publisher, :route, :message_factory, :reply_to, :delay, :timeout,
7
+ :watcher_delay, :pinger_thread, :watcher_thread
8
+
9
+ def initialize(publisher, route, message_factory, reply_to, delay: 20, timeout: 60, watcher_delay: 40)
10
+ @publisher = publisher
11
+ @route = route
12
+ @message_factory = message_factory
13
+ @reply_to = reply_to
14
+ @delay = delay
15
+ @timeout = timeout
16
+ @timestamp = Time.now.to_i
17
+ @watcher_delay = watcher_delay
18
+ end
19
+
20
+ def build(app)
21
+ @app = app
22
+ self
23
+ end
24
+
25
+ def call(msg)
26
+ touch
27
+ @app.call(msg)
28
+ end
29
+
30
+ def start
31
+ touch
32
+ start_ping
33
+ start_watch
34
+ self
35
+ end
36
+
37
+ def stop
38
+ @pinger_thread&.kill
39
+ @watcher_thread&.kill
40
+ end
41
+
42
+ protected
43
+
44
+ def start_ping
45
+ @pinger_thread = Thread.new(publisher, delay, route) do |publisher, delay, route|
46
+ Thread.current.name = 'WatchDog::Pinger'
47
+ loop do
48
+ publisher.publish BBK::App::Dispatcher::Result.new(
49
+ route,
50
+ message_factory.build(reply_to)
51
+ )
52
+ sleep delay
53
+ end
54
+ end
55
+ end
56
+
57
+ def start_watch
58
+ @watcher_thread = Thread.new(timeout, watcher_delay) do |timeout, watcher_delay|
59
+ Thread.current.name = 'WatchDog::Watcher'
60
+ msg = "[CRITICAL] watchdog: last ping is more than #{timeout} seconds ago"
61
+
62
+ sleep watcher_delay while (Time.now.to_i - @timestamp) < timeout
63
+ warn msg
64
+ exit!(8)
65
+ end
66
+ end
67
+
68
+ def touch
69
+ @timestamp = Time.now.to_i
70
+ end
71
+
72
+
73
+
74
+ end
75
+ end
76
+ end
77
+ end
78
+
@@ -0,0 +1,12 @@
1
+ require 'bbk/app/middlewares/base'
2
+ require 'bbk/app/middlewares/from_block'
3
+ require 'bbk/app/middlewares/watchdog'
4
+ require 'bbk/app/middlewares/self_killer'
5
+
6
+ module BBK
7
+ module App
8
+ module Middlewares
9
+ end
10
+ end
11
+ end
12
+
@@ -0,0 +1,46 @@
1
+ require 'bbk/utils/proxy_logger'
2
+
3
+ module BBK
4
+ module App
5
+ module Processors
6
+ class Base
7
+
8
+ attr_reader :logger
9
+
10
+ def initialize(*untyped, logger: BBK::App.logger, **)
11
+ logger = logger.respond_to?(:tagged) ? logger : ActiveSupport::TaggedLogging.new(logger)
12
+ @logger = BBK::Utils::ProxyLogger.new(logger, tags: self.class.to_s)
13
+ end
14
+
15
+ def rule
16
+ unless self.class.respond_to?(:rule)
17
+ raise NotImplementedError.new("Not implemented class method rule in #{self.class.name}")
18
+ end
19
+
20
+ self.class.rule
21
+ end
22
+
23
+ def call(message, results: [])
24
+ debug 'processing message...'
25
+
26
+ process(message, results: results)
27
+
28
+ results
29
+ end
30
+
31
+ def process(_message, results: [])
32
+ raise NotImplementedError.new("process method abstract in Processor class. Results count: #{results.count}")
33
+ end
34
+
35
+
36
+ %i[debug info warn error fatal unknown].each do |severity|
37
+ define_method(severity) do |*args|
38
+ logger.public_send(severity, *args)
39
+ end
40
+ end
41
+
42
+ end
43
+ end
44
+ end
45
+ end
46
+
@@ -0,0 +1,26 @@
1
+ require 'bbk/app/processors/base'
2
+
3
+ module BBK
4
+ module App
5
+ module Processors
6
+ class Ping < Base
7
+
8
+ def initialize(pong_message_factory, pong_route, *args, **kwargs)
9
+ super
10
+ @pong_message_factory = pong_message_factory
11
+ @pong_route = pong_route
12
+ end
13
+
14
+ def process(message, results: [])
15
+ results << BBK::App::Dispatcher::Result.new(
16
+ @pong_route,
17
+ @pong_message_factory.build(message)
18
+ )
19
+ results
20
+ end
21
+
22
+ end
23
+ end
24
+ end
25
+ end
26
+
@@ -0,0 +1,16 @@
1
+ require 'bbk/app/processors/ping'
2
+
3
+ module BBK
4
+ module App
5
+ module Processors
6
+ class Pong < Base
7
+
8
+ def process(*)
9
+ logger.debug 'Process Pong message'
10
+ end
11
+
12
+ end
13
+ end
14
+ end
15
+ end
16
+
@@ -0,0 +1,3 @@
1
+ require 'bbk/app/processors/ping'
2
+ require 'bbk/app/processors/pong'
3
+
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BBK
4
+ module App
5
+ class ProxyLogger
6
+
7
+ attr_reader :tags, :logger
8
+
9
+ def initialize(logger, tags:)
10
+ @logger = logger
11
+ @tagged = @logger.respond_to?(:tagged)
12
+ @tags = [tags].flatten
13
+ end
14
+
15
+ def add_tags(*tags)
16
+ @tags += tags.flatten
17
+ @tags = @tags.uniq
18
+ end
19
+
20
+ def method_missing(method, *args, &block)
21
+ super unless logger.respond_to?(method)
22
+
23
+ if @tagged
24
+ current_tags = tags - logger.formatter.current_tags
25
+ logger.tagged(current_tags) { logger.send(method, *args, &block) }
26
+ else
27
+ logger.send(method, *args, &block)
28
+ end
29
+ end
30
+
31
+ def respond_to?(*args)
32
+ logger.send(:respond_to?, *args) || super
33
+ end
34
+
35
+ def respond_to_missing?(method_name, include_private = false)
36
+ logger.send(:respond_to_missing?, method_name, include_private) || super
37
+ end
38
+
39
+ end
40
+ end
41
+ end
42
+