bbk-app 1.0.0.72899

Sign up to get free protection for your applications and to get access to all the features.
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
+