active_endpoint 0.2.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.
Files changed (67) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +55 -0
  3. data/.rspec +2 -0
  4. data/.rubocop.yml +60 -0
  5. data/.travis.yml +5 -0
  6. data/CODE_OF_CONDUCT.md +74 -0
  7. data/Gemfile +25 -0
  8. data/Gemfile.lock +140 -0
  9. data/LICENSE +21 -0
  10. data/README.md +233 -0
  11. data/Rakefile +6 -0
  12. data/active_endpoint.gemspec +28 -0
  13. data/app/assets/config/active_endpoint_manifest.js +2 -0
  14. data/app/assets/javascripts/active_endpoint/application.js +2 -0
  15. data/app/assets/stylesheets/active_endpoint/application.css +10 -0
  16. data/app/controllers/active_endpoint/application_controller.rb +5 -0
  17. data/app/controllers/active_endpoint/dashboard_controller.rb +7 -0
  18. data/app/controllers/active_endpoint/probes_controller.rb +22 -0
  19. data/app/controllers/active_endpoint/unregistred_probes_controller.rb +14 -0
  20. data/app/helpers/active_endpoint/application_helper.rb +105 -0
  21. data/app/models/active_endpoint/application_record.rb +5 -0
  22. data/app/models/active_endpoint/probe.rb +52 -0
  23. data/app/models/active_endpoint/unregistred_probe.rb +3 -0
  24. data/app/views/active_endpoint/application/_flashes.html.erb +5 -0
  25. data/app/views/active_endpoint/application/_header.html.erb +18 -0
  26. data/app/views/active_endpoint/dashboard/_blacklist.html.erb +57 -0
  27. data/app/views/active_endpoint/dashboard/_constraints.html.erb +117 -0
  28. data/app/views/active_endpoint/dashboard/_settings.html.erb +13 -0
  29. data/app/views/active_endpoint/dashboard/_tags.html.erb +20 -0
  30. data/app/views/active_endpoint/dashboard/index.html.erb +8 -0
  31. data/app/views/active_endpoint/probes/index.html.erb +28 -0
  32. data/app/views/active_endpoint/probes/show.html.erb +43 -0
  33. data/app/views/active_endpoint/probes/show_response.html.erb +9 -0
  34. data/app/views/active_endpoint/unregistred_probes/index.html.erb +28 -0
  35. data/app/views/layouts/active_endpoint/application.html.erb +39 -0
  36. data/bin/console +14 -0
  37. data/bin/rails +13 -0
  38. data/bin/setup +8 -0
  39. data/config/routes.rb +8 -0
  40. data/lib/active_endpoint.rb +60 -0
  41. data/lib/active_endpoint/concerns/configurable.rb +29 -0
  42. data/lib/active_endpoint/concerns/constraintable.rb +51 -0
  43. data/lib/active_endpoint/concerns/optionable.rb +41 -0
  44. data/lib/active_endpoint/concerns/rails_routable.rb +49 -0
  45. data/lib/active_endpoint/engine.rb +15 -0
  46. data/lib/active_endpoint/extentions/active_record.rb +30 -0
  47. data/lib/active_endpoint/logger.rb +28 -0
  48. data/lib/active_endpoint/proxy.rb +64 -0
  49. data/lib/active_endpoint/rails/middleware.rb +17 -0
  50. data/lib/active_endpoint/rails/railtie.rb +13 -0
  51. data/lib/active_endpoint/request.rb +79 -0
  52. data/lib/active_endpoint/response.rb +17 -0
  53. data/lib/active_endpoint/routes/blacklist.rb +67 -0
  54. data/lib/active_endpoint/routes/cache/proxy.rb +22 -0
  55. data/lib/active_endpoint/routes/cache/proxy/redis_store_proxy.rb +41 -0
  56. data/lib/active_endpoint/routes/cache/store.rb +73 -0
  57. data/lib/active_endpoint/routes/constraint_rule.rb +38 -0
  58. data/lib/active_endpoint/routes/constraints.rb +81 -0
  59. data/lib/active_endpoint/routes/matcher.rb +51 -0
  60. data/lib/active_endpoint/routes/momento.rb +81 -0
  61. data/lib/active_endpoint/storage.rb +112 -0
  62. data/lib/active_endpoint/tags.rb +15 -0
  63. data/lib/active_endpoint/version.rb +3 -0
  64. data/lib/generators/active_endpoint/install_generator.rb +35 -0
  65. data/lib/generators/templates/active_endpoint.rb +40 -0
  66. data/lib/generators/templates/migration.erb +30 -0
  67. metadata +109 -0
@@ -0,0 +1,38 @@
1
+ module ActiveEndpoint
2
+ module Routes
3
+ class ConstraintRule
4
+ include Optionable
5
+ include Constraintable
6
+
7
+ def initialize(request)
8
+ @request = request
9
+ @rules = ActiveEndpoint.constraints
10
+ end
11
+
12
+ def rule
13
+ {
14
+ key: "#{prefix}:#{@request[:endpoint]}"
15
+ }.merge(fetch_constraints)
16
+ end
17
+
18
+ private
19
+
20
+ def prefix
21
+ return :endpoints if @rules.present_endpoint?(@request)
22
+ return :resources if @rules.present_resource?(@request)
23
+ return :actions if @rules.present_action?(@request)
24
+ return :scopes if @rules.present_scope?(@request)
25
+ :defaults
26
+ end
27
+
28
+ def fetch_constraints
29
+ if prefix == :defaults
30
+ default_constraints
31
+ else
32
+ constraints = @rules.public_send(prefix)[@request[:endpoint]]
33
+ constraints.present? ? constraints : default_constraints
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,81 @@
1
+ module ActiveEndpoint
2
+ module Routes
3
+ class Constraints < Momento
4
+ include Constraintable
5
+
6
+ def initialize
7
+ super(Hash)
8
+ end
9
+
10
+ def add(options)
11
+ super(options) do |options|
12
+ if fetch_rule(options).nil?
13
+ message = "Constraints can't have empty limit and period!\n"
14
+ raise ActiveEndpoint::Routes::Constraints::Error.new(message)
15
+ end
16
+ end
17
+ end
18
+
19
+ def present_endpoint?(request)
20
+ @endpoints.keys.include?(request[:endpoint])
21
+ end
22
+
23
+ def present_resource?(request)
24
+ reduce_state(@resources.keys, request)
25
+ end
26
+
27
+ def present_action?(request)
28
+ @actions.keys.include?(request[:endpoint])
29
+ end
30
+
31
+ def present_scope?(request)
32
+ reduce_state(@scopes.keys, request)
33
+ end
34
+
35
+ private
36
+
37
+ def add_endpoint(options)
38
+ @endpoints[fetch_endpoint(options)] = constraints(options)
39
+ end
40
+
41
+ def add_resources(options)
42
+ resources = fetch_resources(options)
43
+ actions = fetch_actions(options)
44
+ scope = fetch_scope(options)
45
+
46
+ if actions.present? && actions.any?
47
+ temp_actions = {}
48
+ if resources.is_a?(Array)
49
+ resources.each do |controller_name|
50
+ actions.each { |action| temp_actions["#{controller_name}##{action}"] = constraints(options) }
51
+ end
52
+ else
53
+ actions.each { |action| temp_actions["#{resources}##{action}"] = constraints(options) }
54
+ end
55
+ @actions = @actions.merge(apply(scope, temp_actions))
56
+ else
57
+ temp_resources = {}
58
+ if resources.is_a?(Array)
59
+ resources.each { |resource| temp_resources[resource] = constraints(options) }
60
+ else
61
+ temp_resources[resources] = constraints(options)
62
+ end
63
+ @resources = @resources.merge(apply(scope, temp_resources))
64
+ end
65
+ end
66
+
67
+ def add_scopes(options)
68
+ @scopes[fetch_scope(options)] = constraints(options)
69
+ end
70
+
71
+ def apply(scope, collection)
72
+ return collection unless scope.present?
73
+
74
+ collection.inject({}) do |hash, (key, value)|
75
+ hash["#{scope}/#{key}"] = value
76
+ hash
77
+ end
78
+ end
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,51 @@
1
+ module ActiveEndpoint
2
+ module Routes
3
+ class Matcher
4
+ include RailsRoutable
5
+
6
+ def initialize
7
+ @blacklist = ActiveEndpoint.blacklist
8
+ @cache_store = ActiveEndpoint::Routes::Cache::Store.new
9
+ end
10
+
11
+ def whitelisted?(request)
12
+ trackable?(request) && rails_action?(request)
13
+ end
14
+
15
+ def blacklisted?(probe)
16
+ @blacklist.include?(probe)
17
+ end
18
+
19
+ def unregistred?(request)
20
+ trackable?(request) && !rails_action(request)
21
+ end
22
+
23
+ def assets?(request)
24
+ request.path.start_with?('/assets')
25
+ end
26
+
27
+ def allow_account?(request)
28
+ @constraint_rule = ActiveEndpoint::Routes::ConstraintRule.new(request).rule
29
+ @cache_store.allow?(@constraint_rule)
30
+ end
31
+
32
+ def allow_register?(request)
33
+ @cache_store.unregistred?(request.probe)
34
+ end
35
+
36
+ private
37
+
38
+ def favicon?(request)
39
+ request.path == '/favicon.ico'
40
+ end
41
+
42
+ def engine?(request)
43
+ request.path.include?('active_endpoint')
44
+ end
45
+
46
+ def trackable?(request)
47
+ !(engine?(request) || assets?(request) || favicon?(request) || blacklisted?(request.probe))
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,81 @@
1
+ module ActiveEndpoint
2
+ module Routes
3
+ class Momento
4
+ include Configurable
5
+ include Optionable
6
+
7
+ class Error < ::StandardError; end
8
+
9
+ attr_reader :endpoints, :resources, :actions, :scopes
10
+
11
+ def initialize(structure_class)
12
+ @endpoints = structure_class.new
13
+ @resources = structure_class.new
14
+ @actions = structure_class.new
15
+ @scopes = structure_class.new
16
+ end
17
+
18
+ def include?(request)
19
+ any_presented? [
20
+ present_endpoint?(request),
21
+ present_resource?(request),
22
+ present_action?(request),
23
+ present_scope?(request)
24
+ ]
25
+ end
26
+
27
+ def add(**options, &block)
28
+
29
+ yield(options) if block_given?
30
+
31
+ add_endpoint(options) if fetch_endpoint(options).present?
32
+ add_resources(options) if fetch_resources(options).present?
33
+ add_scopes(options) if fetch_scope(options).present?
34
+ end
35
+
36
+ private
37
+
38
+ def add_endpoint(_options)
39
+ raise NotImplementedError
40
+ end
41
+
42
+ def add_resource(_options)
43
+ raise NotImplementedError
44
+ end
45
+
46
+ def add_scopes(_options)
47
+ raise NotImplementedError
48
+ end
49
+
50
+ def present_endpoint?(_request)
51
+ raise NotImplementedError
52
+ end
53
+
54
+ def present_resource?(_request)
55
+ raise NotImplementedError
56
+ end
57
+
58
+ def present_action?(_request)
59
+ raise NotImplementedError
60
+ end
61
+
62
+ def present_scope?(_request)
63
+ raise NotImplementedError
64
+ end
65
+
66
+ def apply(_scope, _collection)
67
+ raise NotImplementedError
68
+ end
69
+
70
+ def any_presented?(chain)
71
+ chain.reduce(false) { |state, responder_state| state || responder_state }
72
+ end
73
+
74
+ def reduce_state(collection, request)
75
+ collection.reduce(false) do |state, subject|
76
+ state || request[:endpoint].present? && request[:endpoint].start_with?(subject)
77
+ end
78
+ end
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,112 @@
1
+ module ActiveEndpoint
2
+ class Storage
3
+ STORING_FIELDS = [
4
+ :body,
5
+ :response,
6
+ :started_at,
7
+ :finished_at,
8
+ :duration,
9
+ :endpoint,
10
+ :ip,
11
+ :params,
12
+ :path,
13
+ :request_method,
14
+ :url,
15
+ :xhr
16
+ ].freeze
17
+
18
+ LOGGING_FIELDS = [
19
+ :base_url,
20
+ :content_charset,
21
+ :content_length,
22
+ :content_type,
23
+ :fullpath,
24
+ :http_version,
25
+ :http_connection,
26
+ :http_accept_encoding,
27
+ :http_accept_language,
28
+ :media_type,
29
+ :media_type_params,
30
+ :method,
31
+ :path_info,
32
+ :pattern,
33
+ :port,
34
+ :protocol,
35
+ :server_name,
36
+ :ssl
37
+ ].freeze
38
+
39
+ class << self
40
+ def notifier
41
+ ActiveSupport::Notifications
42
+ end
43
+
44
+ def probe_params(transaction_id, probe)
45
+ request = probe.delete(:request)
46
+ request_body = request.delete(:body)
47
+
48
+ response = probe.delete(:response)
49
+ response_body = response.present? ? response[:body] : nil
50
+
51
+ params = {
52
+ uuid: transaction_id,
53
+ response: response_body ? Base64.encode64(response_body) : '',
54
+ started_at: probe[:created_at],
55
+ finished_at: probe[:finished_at],
56
+ duration: probe[:finished_at] ? (probe[:finished_at] - probe[:created_at]).second.round(3) : 0,
57
+ body: request_body.is_a?(Puma::NullIO) ? '' : request_body
58
+ }.merge(request)
59
+
60
+ [params.dup.except(*LOGGING_FIELDS), params.dup.except(*STORING_FIELDS)]
61
+ end
62
+
63
+ def store!(params)
64
+ handle_creation(ActiveEndpoint::Probe.new(params))
65
+ end
66
+
67
+ def register!(params)
68
+ handle_creation(ActiveEndpoint::UnregistredProbe.new(params.merge(endpoint: :unregistred)))
69
+ end
70
+
71
+ def handle_creation(probe)
72
+ probe.save
73
+ rescue => error
74
+ ActiveEndpoint.logger.error('ActiveEndpoint::Probe', error)
75
+ end
76
+
77
+ def clean!(endpoint, period)
78
+ ActiveEndpoint::Probe.registred.probe(endpoint).taken_before(period).destroy_all
79
+ end
80
+ end
81
+
82
+ notifier.subscribe('active_endpoint.tracked_probe') do |_name, _start, _ending, id, payload|
83
+ store_params, logging_params = probe_params(id, payload[:probe])
84
+
85
+ ActiveEndpoint.logger.info('ActiveEndpoint::Storage', logging_params, ActiveEndpoint.log_probe_info)
86
+
87
+ store!(store_params)
88
+ end
89
+
90
+ notifier.subscribe('active_endpoint.unregistred_probe') do |_name, _start, _ending, id, payload|
91
+ store_params, logging_params = probe_params(id, payload[:probe])
92
+
93
+ if ActiveEndpoint.log_debug_info
94
+ ActiveEndpoint.logger.debug('ActiveEndpoint::Storage', store_params.inspect)
95
+ ActiveEndpoint.logger.debug('ActiveEndpoint::Storage', logging_params.inspect)
96
+ end
97
+
98
+ register!(store_params)
99
+ end
100
+
101
+ notifier.subscribe('active_endpoint.clean_expired') do |_name, _start, _ending, id, payload|
102
+ key = payload[:expired][:key].split(':').last
103
+ period = DateTime.now - (payload[:expired][:period] * ActiveEndpoint.storage_keep_periods).seconds
104
+
105
+ if ActiveEndpoint.log_debug_info
106
+ ActiveEndpoint.logger.info('ActiveEndpoint::Storage', { key: key, period: period, uuid: id }.inspect)
107
+ end
108
+
109
+ clean!(key, period)
110
+ end
111
+ end
112
+ end
@@ -0,0 +1,15 @@
1
+ module ActiveEndpoint
2
+ class Tags
3
+ include Configurable
4
+
5
+ attr_reader :definition
6
+
7
+ def initialize
8
+ @definition = {}
9
+ end
10
+
11
+ def add(tag, condition)
12
+ @definition[tag] = condition
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,3 @@
1
+ module ActiveEndpoint
2
+ VERSION = '0.2.0'.freeze
3
+ end
@@ -0,0 +1,35 @@
1
+ module ActiveEndpoint
2
+ module Generators
3
+ class InstallGenerator < ::Rails::Generators::Base
4
+ include ::Rails::Generators::Migration
5
+
6
+ source_root File.expand_path('../templates', __dir__)
7
+
8
+ class << self
9
+ def next_migration_number(_dirname)
10
+ Time.current.strftime('%Y%m%d%H%M%S')
11
+ end
12
+ end
13
+
14
+ puts '=> Creates a ActiveEndpoint initializer, migration and migrate data base for your application.'
15
+
16
+ def copy_initializer_file
17
+ puts '=> Copy initializer file to config/initializers/active_endpoint.rb'
18
+ copy_file 'active_endpoint.rb', 'config/initializers/active_endpoint.rb'
19
+ end
20
+
21
+ def copy_migration_file
22
+ puts '=> Create migration file in db/migrate/***_create_active_endpoint_probes.rb'
23
+ migration_template 'migration.erb', 'db/migrate/create_active_endpoint_probes.rb',
24
+ migration_version: migration_version
25
+ end
26
+
27
+ private
28
+
29
+ def migration_version
30
+ last_rails = ::Rails.version.start_with?('5')
31
+ "[#{::Rails::VERSION::MAJOR}.#{::Rails::VERSION::MINOR}]" if last_rails
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,40 @@
1
+ ActiveEndpoint.configure do |endpoint|
2
+ endpoint.blacklist.configure do |list|
3
+ # list.add(endpoint: 'web/welcome#index')
4
+ # list.add(resources: 'web/users', actions: ['show'])
5
+ # list.add(scope: 'web', resources: 'users', actions: ['show'])
6
+ # list.add(scope: 'web')
7
+ end
8
+
9
+ endpoint.constraint_limit = 100
10
+ endpoint.constraint_period = 1.minute
11
+
12
+ endpoint.storage_limit = 1000
13
+ endpoint.storage_period = 1.day
14
+ endpoint.storage_keep_periods = 2
15
+
16
+ endpoint.constraints.configure do |constraints|
17
+ # constraints.add(endpoint: 'web/welcome#index',
18
+ # rule: { limit: 100, period: 1.minute },
19
+ # storage: { limit: 1000, period: 1.week }
20
+ # })
21
+ # constraints.add(scope: 'web', resources: 'users', rule: { limit: 100 })
22
+ # constraints.add(scope: 'web', resources: ['users'],
23
+ # actions: [:show, :edit, :update],
24
+ # rule: { limit: 10, period: 1.minute })
25
+ # constraints.add(scope: 'web', rule: { limit: 100, period: 1.minute })
26
+ end
27
+
28
+ endpoint.tags.configure do |tags|
29
+ # tags.add(:fast, { less_than: 250 })
30
+ # tags.add(:normal, { greater_than_or_equal_to: 250, less_than: 500 })
31
+ # tags.add(:slow, { greater_than_or_equal_to: 250, less_than: 500 })
32
+ # tags.add(:acceptable, { greater_than_or_equal_to: 500, less_than: 100 })
33
+ # tags.add(:need_optimization, { greater_than_or_equal_to: 1000 })
34
+ end
35
+
36
+ define_setting :logger, ActiveEndpoint::Logger
37
+
38
+ define_setting :log_probe_info, false
39
+ define_setting :log_debug_info, false
40
+ end