sniffer 0.3.1 → 0.5.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.
@@ -4,46 +4,78 @@ module Sniffer
4
4
  module Adapters
5
5
  # HTTP adapter
6
6
  module PatronAdapter
7
- def self.included(base)
8
- base.class_eval do
9
- alias_method :request_without_sniffer, :request
10
- alias_method :request, :request_with_sniffer
11
- end
12
- end
13
-
14
- # rubocop:disable Metrics/AbcSize,Metrics/MethodLength
15
7
  def request_with_sniffer(action_name, url, headers, options = {})
16
- if Sniffer.enabled?
17
- data_item = Sniffer::DataItem.new
18
- uri = URI(base_url)
19
- data_item.request = Sniffer::DataItem::Request.new(host: uri.host,
20
- method: action_name,
21
- query: url,
22
- headers: headers.dup,
23
- body: options[:data].to_s,
24
- port: uri.port)
25
-
26
- Sniffer.store(data_item)
27
- end
8
+ data_item = request_sniffer_before(action_name, url, headers, options)
28
9
 
29
10
  bm = Benchmark.realtime do
30
11
  @res = request_without_sniffer(action_name, url, headers, options)
31
12
  end
32
13
 
33
- if Sniffer.enabled?
34
- data_item.response = Sniffer::DataItem::Response.new(status: @res.status,
35
- headers: @res.headers,
36
- body: @res.body.to_s,
37
- timing: bm)
38
-
39
- data_item.log
40
- end
14
+ request_sniffer_after(data_item, bm)
41
15
 
42
16
  @res
43
17
  end
44
- # rubocop:enable Metrics/AbcSize,Metrics/MethodLength
18
+
19
+ private
20
+
21
+ # rubocop:disable Metrics/MethodLength
22
+ def request_sniffer_before(action_name, url, headers, options)
23
+ return unless Sniffer.enabled?
24
+
25
+ data_item = Sniffer::DataItem.new
26
+ uri = URI(base_url)
27
+ data_item.request = Sniffer::DataItem::Request.new(host: uri.host,
28
+ method: action_name,
29
+ query: url,
30
+ headers: headers.dup,
31
+ body: options[:data].to_s,
32
+ port: uri.port)
33
+
34
+ Sniffer.store(data_item)
35
+
36
+ data_item
37
+ end
38
+ # rubocop:enable Metrics/MethodLength
39
+
40
+ def request_sniffer_after(data_item, benchmark)
41
+ return unless Sniffer.enabled?
42
+
43
+ data_item.response = Sniffer::DataItem::Response.new(status: @res.status,
44
+ headers: @res.headers,
45
+ body: @res.body.to_s,
46
+ timing: benchmark)
47
+
48
+ Sniffer.notify_response(data_item)
49
+ end
50
+
51
+ # Only used when prepending, see all_prepend.rb
52
+ module Prepend
53
+ include PatronAdapter
54
+
55
+ def request(action_name, url, headers, options = {})
56
+ data_item = request_sniffer_before(action_name, url, headers, options)
57
+
58
+ bm = Benchmark.realtime do
59
+ @res = super(action_name, url, headers, options)
60
+ end
61
+
62
+ request_sniffer_after(data_item, bm)
63
+
64
+ @res
65
+ end
66
+ end
45
67
  end
46
68
  end
47
69
  end
48
70
 
49
- ::Patron::Session.send(:include, Sniffer::Adapters::PatronAdapter) if defined?(::Patron::Session)
71
+ if defined?(::Patron::Session)
72
+ if defined?(Sniffer::Adapters::PatronAdapter::PREPEND)
73
+ Patron::Session.prepend Sniffer::Adapters::PatronAdapter::Prepend
74
+ else
75
+ Patron::Session.class_eval do
76
+ include Sniffer::Adapters::PatronAdapter
77
+ alias_method :request_without_sniffer, :request
78
+ alias_method :request, :request_with_sniffer
79
+ end
80
+ end
81
+ end
@@ -1,6 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "anyway_config"
4
+ require_relative "middleware/chain"
5
+ require_relative "middleware/logger"
4
6
 
5
7
  module Sniffer
6
8
  # Sniffer configuration
@@ -24,6 +26,15 @@ module Sniffer
24
26
  url_whitelist: nil,
25
27
  url_blacklist: nil
26
28
 
29
+ def middleware
30
+ @middleware ||= Middleware::Chain.new.tap do |chain|
31
+ chain.add(Sniffer::Middleware::Logger, logger, severity)
32
+ end
33
+
34
+ yield @middleware if block_given?
35
+ @middleware
36
+ end
37
+
27
38
  def capacity?
28
39
  store.is_a?(Hash) && store.key?(:capacity)
29
40
  end
@@ -34,6 +45,7 @@ module Sniffer
34
45
 
35
46
  def rotate?
36
47
  return false unless capacity?
48
+
37
49
  store.fetch(:rotate, true)
38
50
  end
39
51
  end
data/lib/sniffer/data.rb CHANGED
@@ -4,8 +4,6 @@ module Sniffer
4
4
  # Data class stores the data and controls capacity
5
5
  class Data < Array
6
6
  def store(data_item)
7
- return unless data_item.allowed_to_sniff?
8
-
9
7
  if config.rotate?
10
8
  rotate(data_item)
11
9
  else
@@ -1,44 +1,45 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'active_attr'
3
+ require 'dry-initializer'
4
4
  require 'json'
5
5
  require_relative 'request_policy'
6
6
 
7
7
  module Sniffer
8
8
  # Sniffer data item stores a request info
9
9
  class DataItem
10
- include ActiveAttr::MassAssignment
11
- attr_accessor :request, :response
10
+ extend Dry::Initializer
11
+
12
+ attr_writer :request, :response
13
+
14
+ option :request, optional: true
15
+ option :response, optional: true
12
16
 
13
17
  def to_h
14
18
  {
15
- request: request && request.to_h,
16
- response: response && response.to_h
19
+ request: request&.to_h,
20
+ response: response&.to_h
17
21
  }
18
22
  end
19
23
 
20
- def log
21
- return unless Sniffer.logger && allowed_to_sniff?
22
- Sniffer.logger.log(Sniffer.config.severity, to_json)
23
- end
24
-
25
24
  def to_log
26
25
  return {} unless Sniffer.config.logger
26
+
27
27
  request.to_log.merge(response.to_log)
28
28
  end
29
29
 
30
- def to_json
30
+ def to_json(*_args)
31
31
  to_log.to_json
32
32
  end
33
33
 
34
34
  def allowed_to_sniff?
35
35
  return true unless request
36
+
36
37
  RequestPolicy.call(request)
37
38
  end
38
39
 
39
40
  # Basic object for request and response objects
40
41
  class HttpObject
41
- include ActiveAttr::MassAssignment
42
+ extend Dry::Initializer
42
43
 
43
44
  def log_message
44
45
  raise NotImplementedError
@@ -51,7 +52,14 @@ module Sniffer
51
52
 
52
53
  # Stores http request data
53
54
  class Request < HttpObject
54
- attr_accessor :host, :port, :query, :method, :headers, :body
55
+ option :host, optional: true
56
+ option :port, optional: true
57
+ option :query, optional: true
58
+ option :method, optional: true
59
+ option :headers, optional: true
60
+ option :body, optional: true
61
+
62
+ attr_writer :host, :port, :query, :method, :headers, :body
55
63
 
56
64
  def to_h
57
65
  {
@@ -59,7 +67,7 @@ module Sniffer
59
67
  query: query,
60
68
  port: port,
61
69
  headers: headers,
62
- body: body && body.to_s,
70
+ body: body&.to_s,
63
71
  method: method
64
72
  }
65
73
  end
@@ -88,13 +96,18 @@ module Sniffer
88
96
 
89
97
  # Stores http response data
90
98
  class Response < HttpObject
91
- attr_accessor :status, :headers, :body, :timing
99
+ attr_writer :status, :headers, :body, :timing
100
+
101
+ option :status, optional: true
102
+ option :headers, optional: true
103
+ option :body, optional: true
104
+ option :timing, optional: true
92
105
 
93
106
  def to_h
94
107
  {
95
108
  status: status,
96
109
  headers: headers,
97
- body: body && body.to_s,
110
+ body: body&.to_s,
98
111
  timing: timing
99
112
  }
100
113
  end
@@ -0,0 +1,73 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "entry"
4
+
5
+ module Sniffer
6
+ # Middleware is code configured to run before/after
7
+ # storing sniffed request/response
8
+ # To add middleware
9
+ #
10
+ # Sniffer.middleware do |chain|
11
+ # chain.add MyHook
12
+ # end
13
+ #
14
+ # class MyHook
15
+ # def request(data_item)
16
+ # puts "Before request work"
17
+ # yield
18
+ # puts "After request work"
19
+ # end
20
+ #
21
+ # def response(data_item)
22
+ # puts "Before response work"
23
+ # yield
24
+ # puts "After response work"
25
+ # end
26
+ # end
27
+ module Middleware
28
+ # Stores all the middleware configs
29
+ class Chain
30
+ include Enumerable
31
+
32
+ def entries
33
+ @entries ||= []
34
+ end
35
+
36
+ def each(&block)
37
+ entries.each(&block)
38
+ end
39
+
40
+ def add(klass, *args)
41
+ entries.push(Entry.new(klass, *args))
42
+ end
43
+
44
+ def remove(klass)
45
+ entries.delete_if { |entry| entry.klass == klass }
46
+ end
47
+
48
+ def invoke_request(*args)
49
+ chain = map(&:make_new).dup
50
+ traverse_chain = lambda do
51
+ if chain.empty?
52
+ yield
53
+ else
54
+ chain.shift.request(*args, &traverse_chain)
55
+ end
56
+ end
57
+ traverse_chain.call
58
+ end
59
+
60
+ def invoke_response(*args)
61
+ chain = map(&:make_new).dup
62
+ traverse_chain = lambda do
63
+ if chain.empty?
64
+ yield
65
+ else
66
+ chain.shift.response(*args, &traverse_chain)
67
+ end
68
+ end
69
+ traverse_chain.call
70
+ end
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sniffer
4
+ module Middleware
5
+ # Middleware entry, represented with klass and arguments for initializer
6
+ class Entry
7
+ attr_reader :klass
8
+
9
+ def initialize(klass, *args)
10
+ @klass = klass
11
+ @args = args
12
+ end
13
+
14
+ def make_new
15
+ @klass.new(*@args)
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sniffer
4
+ module Middleware
5
+ # Response logging build-in middleware
6
+ class Logger
7
+ attr_reader :logger, :severity
8
+
9
+ def initialize(logger, severity)
10
+ @logger = logger
11
+ @severity = severity
12
+ end
13
+
14
+ def request(_data_item)
15
+ yield
16
+ end
17
+
18
+ def response(data_item)
19
+ yield
20
+
21
+ return unless logger
22
+
23
+ logger.log(severity, data_item.to_json)
24
+ end
25
+ end
26
+ end
27
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Sniffer
4
- VERSION = "0.3.1"
4
+ VERSION = "0.5.0"
5
5
  end
data/lib/sniffer.rb CHANGED
@@ -48,11 +48,20 @@ module Sniffer
48
48
  end
49
49
 
50
50
  def store(data_item)
51
- data.store(data_item) if config.store
51
+ return unless config.store
52
+ return unless data_item.allowed_to_sniff?
53
+
54
+ config.middleware.invoke_request(data_item) do
55
+ data.store(data_item)
56
+ end
52
57
  end
53
58
 
54
- def logger
55
- config.logger
59
+ def notify_response(data_item)
60
+ return unless config.store
61
+ return unless data_item.allowed_to_sniff?
62
+
63
+ config.middleware.invoke_response(data_item) do
64
+ end
56
65
  end
57
66
  end
58
67
  end
data/sniffer.gemspec CHANGED
@@ -19,13 +19,13 @@ Gem::Specification.new do |spec|
19
19
  end
20
20
  spec.require_paths = ["lib"]
21
21
 
22
- spec.add_dependency "anyway_config", "~> 1.0"
23
- spec.add_dependency "active_attr", ">= 0.10.2"
22
+ spec.add_dependency "anyway_config", ">= 1.0"
23
+ spec.add_dependency "dry-initializer", "~> 3"
24
24
 
25
- spec.add_development_dependency "bundler", "~> 1.15"
26
- spec.add_development_dependency "rake", "~> 10.0"
25
+ spec.add_development_dependency "bundler", "~> 2"
26
+ spec.add_development_dependency "rake", ">= 12.3.3"
27
27
  spec.add_development_dependency "rspec", "~> 3.0"
28
- spec.add_development_dependency "rubocop", "~> 0.50"
28
+ spec.add_development_dependency "rubocop"
29
29
  spec.add_development_dependency "pry-byebug"
30
30
  spec.add_development_dependency "sinatra", "~> 2.0"
31
31
  spec.add_development_dependency "puma", ">= 3.10.0"
metadata CHANGED
@@ -1,71 +1,71 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sniffer
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.1
4
+ version: 0.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andrey Deryabin
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2018-02-21 00:00:00.000000000 Z
11
+ date: 2022-05-06 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: anyway_config
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - "~>"
17
+ - - ">="
18
18
  - !ruby/object:Gem::Version
19
19
  version: '1.0'
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
- - - "~>"
24
+ - - ">="
25
25
  - !ruby/object:Gem::Version
26
26
  version: '1.0'
27
27
  - !ruby/object:Gem::Dependency
28
- name: active_attr
28
+ name: dry-initializer
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
- - - ">="
31
+ - - "~>"
32
32
  - !ruby/object:Gem::Version
33
- version: 0.10.2
33
+ version: '3'
34
34
  type: :runtime
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
- - - ">="
38
+ - - "~>"
39
39
  - !ruby/object:Gem::Version
40
- version: 0.10.2
40
+ version: '3'
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: bundler
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
45
  - - "~>"
46
46
  - !ruby/object:Gem::Version
47
- version: '1.15'
47
+ version: '2'
48
48
  type: :development
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
52
  - - "~>"
53
53
  - !ruby/object:Gem::Version
54
- version: '1.15'
54
+ version: '2'
55
55
  - !ruby/object:Gem::Dependency
56
56
  name: rake
57
57
  requirement: !ruby/object:Gem::Requirement
58
58
  requirements:
59
- - - "~>"
59
+ - - ">="
60
60
  - !ruby/object:Gem::Version
61
- version: '10.0'
61
+ version: 12.3.3
62
62
  type: :development
63
63
  prerelease: false
64
64
  version_requirements: !ruby/object:Gem::Requirement
65
65
  requirements:
66
- - - "~>"
66
+ - - ">="
67
67
  - !ruby/object:Gem::Version
68
- version: '10.0'
68
+ version: 12.3.3
69
69
  - !ruby/object:Gem::Dependency
70
70
  name: rspec
71
71
  requirement: !ruby/object:Gem::Requirement
@@ -84,16 +84,16 @@ dependencies:
84
84
  name: rubocop
85
85
  requirement: !ruby/object:Gem::Requirement
86
86
  requirements:
87
- - - "~>"
87
+ - - ">="
88
88
  - !ruby/object:Gem::Version
89
- version: '0.50'
89
+ version: '0'
90
90
  type: :development
91
91
  prerelease: false
92
92
  version_requirements: !ruby/object:Gem::Requirement
93
93
  requirements:
94
- - - "~>"
94
+ - - ">="
95
95
  - !ruby/object:Gem::Version
96
- version: '0.50'
96
+ version: '0'
97
97
  - !ruby/object:Gem::Dependency
98
98
  name: pry-byebug
99
99
  requirement: !ruby/object:Gem::Requirement
@@ -255,10 +255,14 @@ executables: []
255
255
  extensions: []
256
256
  extra_rdoc_files: []
257
257
  files:
258
+ - ".github/ISSUE_TEMPLATE/bug_report.md"
259
+ - ".github/ISSUE_TEMPLATE/feature_request.md"
260
+ - ".github/PULL_REQUEST_TEMPLATE.md"
261
+ - ".github/workflows/rspec.yml"
262
+ - ".github/workflows/rubocop.yml"
258
263
  - ".gitignore"
259
264
  - ".rspec"
260
265
  - ".rubocop.yml"
261
- - ".travis.yml"
262
266
  - CHANGELOG.md
263
267
  - CODE_OF_CONDUCT.md
264
268
  - Gemfile
@@ -268,6 +272,16 @@ files:
268
272
  - assets/demo.gif
269
273
  - bin/console
270
274
  - bin/setup
275
+ - docker-compose.yml
276
+ - lib/all_prepend.rb
277
+ - lib/curb_prepend.rb
278
+ - lib/ethon_prepend.rb
279
+ - lib/eventmachine_prepend.rb
280
+ - lib/excon_prepend.rb
281
+ - lib/http_prepend.rb
282
+ - lib/httpclient_prepend.rb
283
+ - lib/net_http_prepend.rb
284
+ - lib/patron_prepend.rb
271
285
  - lib/sniffer.rb
272
286
  - lib/sniffer/adapters/curb_adapter.rb
273
287
  - lib/sniffer/adapters/ethon_adapter.rb
@@ -280,6 +294,9 @@ files:
280
294
  - lib/sniffer/config.rb
281
295
  - lib/sniffer/data.rb
282
296
  - lib/sniffer/data_item.rb
297
+ - lib/sniffer/middleware/chain.rb
298
+ - lib/sniffer/middleware/entry.rb
299
+ - lib/sniffer/middleware/logger.rb
283
300
  - lib/sniffer/request_policy.rb
284
301
  - lib/sniffer/version.rb
285
302
  - sniffer.gemspec
@@ -302,8 +319,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
302
319
  - !ruby/object:Gem::Version
303
320
  version: '0'
304
321
  requirements: []
305
- rubyforge_project:
306
- rubygems_version: 2.7.4
322
+ rubygems_version: 3.0.3
307
323
  signing_key:
308
324
  specification_version: 4
309
325
  summary: Analyze HTTP Requests
data/.travis.yml DELETED
@@ -1,6 +0,0 @@
1
- sudo: false
2
- language: ruby
3
- rvm:
4
- - 2.2.2
5
- - 2.4.2
6
- - 2.5.0