sniffer 0.0.1 → 0.1.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 22daa9fd347dedf98632f9ade85208a09770bb63
4
- data.tar.gz: d254ffb4370a0f83882f83aae05ed662bc1143a2
3
+ metadata.gz: e61d38c32a8f74c8fabdd9113a45892685be34c8
4
+ data.tar.gz: 5ab43be1021421ec32cde7df4d2f33692318fda9
5
5
  SHA512:
6
- metadata.gz: 9b796b5f06b841997f856afbd800ded12e7a2c21fddd2c0c69472c77af1875be0581f4b1ba1fafc5a9a5b623ac9ec82d50961309e9397867a7af99bee66e52fa
7
- data.tar.gz: bcd3c000a02b25d620c4698660310edd6458457fbee9de227b27628029049c279f14fa17fa6d12b1d1b1d4272f7282ca29d620eb85ef134b938ba56975f3e401
6
+ metadata.gz: 66ed0687f93be90af88b3a6b440350163f1f25284827e8359ed36dec78554c4fc8f55607f365090a358539f4a1e4e8db7ba4ac4f7b993c1a7d6c477c6c47fe55
7
+ data.tar.gz: ae3bb49ab20fd5fb84f9fe7e70c05c00e16141f43ee04aec53b627ec2e9f0269a2feb3fd54cd66a2de897390ebb96bfecd392f1e9bc4ce7945a75b7058002463
data/.rubocop.yml CHANGED
@@ -13,7 +13,7 @@ AllCops:
13
13
  StyleGuideCopsOnly: false
14
14
  TargetRubyVersion: 2.4
15
15
 
16
- Style/AccessorMethodName:
16
+ Naming/AccessorMethodName:
17
17
  Enabled: false
18
18
 
19
19
  Style/PercentLiteralDelimiters:
@@ -26,7 +26,7 @@ Style/Documentation:
26
26
  Exclude:
27
27
  - 'spec/**/*.rb'
28
28
 
29
- Style/StringLiterals:
29
+ Style/StringLiterals:
30
30
  Enabled: false
31
31
 
32
32
  Style/BlockDelimiters:
@@ -44,7 +44,7 @@ Metrics/MethodLength:
44
44
  - 'spec/**/*.rb'
45
45
 
46
46
  Metrics/LineLength:
47
- Max: 100
47
+ Max: 120
48
48
  Exclude:
49
49
  - 'spec/**/*.rb'
50
50
 
@@ -56,4 +56,7 @@ Rails/Date:
56
56
  Enabled: false
57
57
 
58
58
  Rails/TimeZone:
59
- Enabled: false
59
+ Enabled: false
60
+
61
+ Security/YAMLLoad:
62
+ Enabled: false
data/.travis.yml CHANGED
@@ -1,5 +1,14 @@
1
1
  sudo: false
2
2
  language: ruby
3
+ env:
4
+ global:
5
+ - CC_TEST_REPORTER_ID=bc054bdf814431bf40559f1cd62a86956fa5666296895400e1282a47ec6d1c69
3
6
  rvm:
4
7
  - 2.4.2
5
8
  before_install: gem install bundler -v 1.15.4
9
+ before_script:
10
+ - curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ./cc-test-reporter
11
+ - chmod +x ./cc-test-reporter
12
+ - ./cc-test-reporter before-build
13
+ after_script:
14
+ - ./cc-test-reporter after-build --exit-code $TRAVIS_TEST_RESULT
data/Gemfile CHANGED
@@ -4,5 +4,3 @@ git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }
4
4
 
5
5
  # Specify your gem's dependencies in sniffer.gemspec
6
6
  gemspec
7
-
8
- gem 'rubocop'
data/README.md CHANGED
@@ -1,8 +1,28 @@
1
- # Sniffer
1
+ # Sniffer [![Build Status](https://travis-ci.org/aderyabin/sniffer.svg?branch=master)](https://travis-ci.org/aderyabin/sniffer) [![Gem Version](https://badge.fury.io/rb/sniffer.svg)](https://rubygems.org/gems/sniffer) [![Maintainability](https://api.codeclimate.com/v1/badges/640cb17b3d748a49653f/maintainability)](https://codeclimate.com/github/aderyabin/sniffer/maintainability)
2
2
 
3
- Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/sniffer`. To experiment with that code, run `bin/console` for an interactive prompt.
4
3
 
5
- TODO: Delete this and the text above, and describe your gem
4
+ Sniffer aims to help:
5
+
6
+ * Log outgoing HTTP requests. Sniffer logs as JSON format for export to ELK, Logentries and etc.
7
+ * Debug requests. Sniffer allows to save all requests/responses in storage for future debugging
8
+
9
+ Sniffer supports most common HTTP accessing libraries:
10
+
11
+ * [Net::HTTP](http://ruby-doc.org/stdlib-2.4.2/libdoc/net/http/rdoc/Net/HTTP.html)
12
+ * [HTTP](https://github.com/httprb/http)
13
+ * [HTTPClient](https://github.com/nahi/httpclient)
14
+ * [Patron](https://github.com/toland/patron)
15
+ * [Curb](https://github.com/taf2/curb/)
16
+ * [Ethon](https://github.com/typhoeus/ethon)
17
+ * [Typhoeus](https://github.com/typhoeus/typhoeus)
18
+
19
+
20
+ <a href="https://evilmartians.com/">
21
+ <img src="https://evilmartians.com/badges/sponsored-by-evil-martians.svg" alt="Sponsored by Evil Martians" width="236" height="54"></a>
22
+
23
+ ## Demo
24
+
25
+ ![demo](https://github.com/aderyabin/sniffer/blob/master/assets/demo.gif?raw=true)
6
26
 
7
27
  ## Installation
8
28
 
@@ -20,9 +40,94 @@ Or install it yourself as:
20
40
 
21
41
  $ gem install sniffer
22
42
 
43
+ ## Configuration
44
+
45
+ Sniffer default options:
46
+
47
+ ```ruby
48
+ Sniffer.config do
49
+ logger: Logger.new($stdout),
50
+ severity: Logger::Severity::DEBUG,
51
+ # HTTP options to log
52
+ log: {
53
+ request_url: true,
54
+ request_headers: true,
55
+ request_body: true,
56
+ request_method: true,
57
+ response_status: true,
58
+ response_headers: true,
59
+ response_body: true,
60
+ timing: true
61
+ },
62
+ store: true, # save requests/responses to Sniffer.data
63
+ enabled: false # Sniffer disabled by default
64
+ end
65
+ ```
66
+
23
67
  ## Usage
24
68
 
25
- TODO: Write usage instructions here
69
+ Here's some simple examples to get you started:
70
+
71
+ ```ruby
72
+ require 'http'
73
+ require 'sniffer'
74
+
75
+ Sniffer.enable!
76
+
77
+ HTTP.get('http://example.com/?lang=ruby&author=matz')
78
+ Sniffer.data[0].to_h
79
+ # => {:request=>
80
+ # {:host=>"example.com",
81
+ # :query=>"/?lang=ruby&author=matz",
82
+ # :port=>80,
83
+ # :headers=>{"Accept-Encoding"=>"gzip;q=1.0,deflate;q=0.6,identity;q=0.3", "Connection"=>"close"},
84
+ # :body=>"",
85
+ # :method=>:get},
86
+ # :response=>
87
+ # {:status=>200,
88
+ # :headers=>
89
+ # {"Content-Encoding"=>"gzip",
90
+ # "Cache-Control"=>"max-age=604800",
91
+ # "Content-Type"=>"text/html",
92
+ # "Date"=>"Thu, 26 Oct 2017 13:47:00 GMT",
93
+ # "Etag"=>"\"359670651+gzip\"",
94
+ # "Expires"=>"Thu, 02 Nov 2017 13:47:00 GMT",
95
+ # "Last-Modified"=>"Fri, 09 Aug 2013 23:54:35 GMT",
96
+ # "Server"=>"ECS (lga/1372)",
97
+ # "Vary"=>"Accept-Encoding",
98
+ # "X-Cache"=>"HIT",
99
+ # "Content-Length"=>"606",
100
+ # "Connection"=>"close"},
101
+ # :body=> "OK",
102
+ # :timing=>0.23753299983218312}}
103
+ ```
104
+
105
+ You can clear saved data
106
+
107
+ ```
108
+ Sniffer.clear!
109
+ ```
110
+
111
+ You can reset config to default
112
+
113
+ ```
114
+ Sniffer.reset!
115
+ ```
116
+
117
+ You can enable and disable Sniffer
118
+
119
+ ```
120
+ Sniffer.enable!
121
+ Sniffer.disable!
122
+ ```
123
+
124
+ By default output log looks like that:
125
+
126
+ ```
127
+ D, [2017-10-26T16:47:14.007152 #59511] DEBUG -- : {"port":80,"host":"example.com","query":"/?lang=ruby&author=matz","rq_connection":"close","method":"get","request_body":"","status":200,"rs_accept_ranges":"bytes","rs_cache_control":"max-age=604800","rs_content_type":"text/html","rs_date":"Thu, 26 Oct 2017 13:47:13 GMT","rs_etag":"\"359670651+gzip\"","rs_expires":"Thu, 02 Nov 2017 13:47:13 GMT","rs_last_modified":"Fri, 09 Aug 2013 23:54:35 GMT","rs_server":"ECS (lga/1385)","rs_vary":"Accept-Encoding","rs_x_cache":"HIT","rs_content_length":"1270","rs_connection":"close","timing":0.513012999901548,"response_body":"OK"}
128
+ ```
129
+ where `rq_xxx` is request header and `rs_xxx` - response header
130
+
26
131
 
27
132
  ## Development
28
133
 
data/assets/demo.gif ADDED
Binary file
@@ -0,0 +1,84 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sniffer
4
+ module Adapters
5
+ # Curl adapter
6
+ module CurlAdapter
7
+ def self.included(base)
8
+ base.class_eval do
9
+ alias_method :http_without_sniffer, :http
10
+ alias_method :http, :http_with_sniffer
11
+
12
+ alias_method :http_post_without_sniffer, :http_post
13
+ alias_method :http_post, :http_post_with_sniffer
14
+ end
15
+ end
16
+
17
+ # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
18
+ def http_with_sniffer(verb)
19
+ sniffer_request(verb)
20
+
21
+ http_without_sniffer(verb)
22
+
23
+ bm = Benchmark.realtime do
24
+ @res = http_without_sniffer(verb)
25
+ end
26
+
27
+ sniffer_response(bm)
28
+
29
+ @res
30
+ end
31
+
32
+ def http_post_with_sniffer(*args)
33
+ sniffer_request(:POST, *args)
34
+
35
+ bm = Benchmark.realtime do
36
+ @res = http_post_without_sniffer(*args)
37
+ end
38
+
39
+ sniffer_response(bm)
40
+
41
+ @res
42
+ end
43
+
44
+ private
45
+
46
+ def data_item
47
+ @data_item ||= Sniffer::DataItem.new if Sniffer.enabled?
48
+ end
49
+
50
+ def sniffer_request(verb, *args)
51
+ return unless data_item
52
+
53
+ uri = URI(url)
54
+ query = uri.path
55
+ query += "?#{uri.query}" if uri.query
56
+
57
+ data_item.request = Sniffer::DataItem::Request.new(host: uri.host,
58
+ method: verb,
59
+ query: query,
60
+ headers: headers.collect.to_h,
61
+ body: args.join("&"),
62
+ port: uri.port)
63
+
64
+ Sniffer.store(data_item)
65
+ end
66
+
67
+ def sniffer_response(timing)
68
+ return unless data_item
69
+
70
+ _, *http_headers = header_str.split(/[\r\n]+/).map(&:strip)
71
+ http_headers = Hash[http_headers.flat_map { |s| s.scan(/^(\S+): (.+)/) }]
72
+
73
+ data_item.response = Sniffer::DataItem::Response.new(status: status.to_i,
74
+ headers: http_headers,
75
+ body: body_str,
76
+ timing: timing)
77
+
78
+ data_item.log
79
+ end
80
+ end
81
+ end
82
+ end
83
+
84
+ Curl::Easy.send(:include, Sniffer::Adapters::CurlAdapter) if defined?(::Curl::Easy)
@@ -0,0 +1,89 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sniffer
4
+ module Adapters
5
+ # Ethon adapter
6
+ module EthonAdapter
7
+ # overrides http_request method
8
+ module Http
9
+ def self.included(base)
10
+ base.class_eval do
11
+ alias_method :http_request_without_sniffer, :http_request
12
+ alias_method :http_request, :http_request_with_sniffer
13
+ end
14
+ end
15
+
16
+ # rubocop:disable Metrics/AbcSize,Metrics/MethodLength
17
+ def http_request_with_sniffer(url, action_name, options = {})
18
+ if Sniffer.enabled?
19
+ @data_item = Sniffer::DataItem.new
20
+ uri = URI("http://" + url)
21
+
22
+ @data_item.request = Sniffer::DataItem::Request.new(host: uri.host,
23
+ method: action_name.upcase,
24
+ port: options[:port] || uri.port,
25
+ headers: options[:headers].to_h,
26
+ body: options[:body].to_s)
27
+
28
+ Sniffer.store(@data_item)
29
+ end
30
+
31
+ http_request_without_sniffer(url, action_name, options)
32
+ end
33
+ end
34
+ # rubocop:enable Metrics/AbcSize,Metrics/MethodLength
35
+
36
+ # overrides perform method
37
+ module Operations
38
+ def self.included(base)
39
+ base.class_eval do
40
+ alias_method :perform_without_sniffer, :perform
41
+ alias_method :perform, :perform_with_sniffer
42
+ end
43
+ end
44
+
45
+ # rubocop:disable Metrics/AbcSize,Metrics/MethodLength
46
+ def perform_with_sniffer
47
+ bm = Benchmark.realtime do
48
+ @return_code = Ethon::Curl.easy_perform(handle)
49
+ end
50
+
51
+ if Sniffer.enabled?
52
+ uri = URI("http://" + @url)
53
+ query = uri.path
54
+ query += "?#{uri.query}" if uri.query
55
+ @data_item.request.query = query
56
+
57
+ status = @response_headers.scan(%r{HTTP\/... (\d{3})}).flatten[0].to_i
58
+ hash_headers = @response_headers
59
+ .split(/\r?\n/)[1..-1]
60
+ .each_with_object({}) do |item, res|
61
+ k, v = item.split(": ")
62
+ res[k] = v
63
+ end
64
+
65
+ @data_item.response = Sniffer::DataItem::Response.new(status: status,
66
+ headers: hash_headers,
67
+ body: @response_body,
68
+ timing: bm)
69
+ @data_item.log
70
+
71
+ end
72
+
73
+ if Ethon.logger.debug?
74
+ Ethon.logger.debug { "ETHON: performed #{log_inspect}" }
75
+ end
76
+ complete
77
+
78
+ @return_code
79
+ end
80
+ # rubocop:enable Metrics/AbcSize,Metrics/MethodLength
81
+ end
82
+ end
83
+ end
84
+ end
85
+
86
+ if defined?(::Ethon::Easy)
87
+ Ethon::Easy::Http.send(:include, Sniffer::Adapters::EthonAdapter::Http)
88
+ Ethon::Easy::Operations.send(:include, Sniffer::Adapters::EthonAdapter::Operations)
89
+ end
@@ -0,0 +1,72 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sniffer
4
+ module Adapters
5
+ # HTTP adapter
6
+ module HTTPAdapter
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
+ # private
15
+
16
+ # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
17
+ def request_with_sniffer(verb, uri, opts = {})
18
+ opts = @default_options.merge(opts)
19
+ uri = make_request_uri(uri, opts)
20
+ headers = make_request_headers(opts)
21
+ body = make_request_body(opts, headers)
22
+ proxy = opts.proxy
23
+
24
+ req = HTTP::Request.new(
25
+ verb: verb,
26
+ uri: uri,
27
+ headers: headers,
28
+ proxy: proxy,
29
+ body: body,
30
+ auto_deflate: opts.feature(:auto_deflate)
31
+ )
32
+
33
+ if Sniffer.enabled?
34
+ data_item = Sniffer::DataItem.new
35
+ query = uri.path
36
+ query += "?#{uri.query}" if uri.query
37
+
38
+ data_item.request = Sniffer::DataItem::Request.new(host: uri.host,
39
+ method: verb,
40
+ query: query,
41
+ headers: headers.collect.to_h,
42
+ body: body.to_s,
43
+ port: uri.port)
44
+
45
+ Sniffer.store(data_item)
46
+ end
47
+
48
+ bm = Benchmark.realtime do
49
+ @res = perform(req, opts)
50
+ end
51
+
52
+ if Sniffer.enabled?
53
+ data_item.response = Sniffer::DataItem::Response.new(status: @res.code,
54
+ headers: @res.headers.collect.to_h,
55
+ body: @res.body.to_s,
56
+ timing: bm)
57
+
58
+ data_item.log
59
+ end
60
+
61
+ return @res unless opts.follow
62
+
63
+ HTTP::Redirector.new(opts.follow).perform(req, @res) do |request|
64
+ perform(request, opts)
65
+ end
66
+ end
67
+ # rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength
68
+ end
69
+ end
70
+ end
71
+
72
+ HTTP::Client.send(:include, Sniffer::Adapters::HTTPAdapter) if defined?(::HTTP::Client)
@@ -0,0 +1,59 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sniffer
4
+ module Adapters
5
+ # HttpClient adapter
6
+ module HTTPClientAdapter
7
+ def self.included(base)
8
+ base.class_eval do
9
+ alias_method :do_get_block_without_sniffer, :do_get_block
10
+ alias_method :do_get_block, :do_get_block_with_sniffer
11
+ end
12
+ end
13
+
14
+ # private
15
+
16
+ # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
17
+ def do_get_block_with_sniffer(req, proxy, conn, &block)
18
+ if Sniffer.enabled?
19
+ data_item = Sniffer::DataItem.new
20
+ data_item.request = Sniffer::DataItem::Request.new(host: req.header.request_uri.host,
21
+ query: req.header.create_query_uri,
22
+ method: req.header.request_method,
23
+ headers: req.headers,
24
+ body: req.body,
25
+ port: req.header.request_uri.port)
26
+
27
+ Sniffer.store(data_item)
28
+ end
29
+
30
+ retryable_response = nil
31
+
32
+ bm = Benchmark.realtime do
33
+ begin
34
+ do_get_block_without_sniffer(req, proxy, conn, &block)
35
+ rescue HTTPClient::RetryableResponse => e
36
+ retryable_response = e
37
+ end
38
+ end
39
+
40
+ if Sniffer.enabled?
41
+ res = conn.pop
42
+ data_item.response = Sniffer::DataItem::Response.new(status: res.status_code.to_i,
43
+ headers: res.headers,
44
+ body: res.body,
45
+ timing: bm)
46
+
47
+ conn.push(res)
48
+
49
+ data_item.log
50
+ end
51
+
52
+ raise retryable_response unless retryable_response.nil?
53
+ end
54
+ # rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength
55
+ end
56
+ end
57
+ end
58
+
59
+ HTTPClient.send(:include, Sniffer::Adapters::HTTPClientAdapter) if defined?(::HTTPClient)
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'net/http'
4
+ require 'benchmark'
5
+
6
+ module Sniffer
7
+ module Adapters
8
+ # Net::HTTP adapter
9
+ module NetHttpAdapter
10
+ def self.included(base)
11
+ base.class_eval do
12
+ alias_method :request_without_sniffer, :request
13
+ alias_method :request, :request_with_sniffer
14
+ end
15
+ end
16
+
17
+ # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
18
+ def request_with_sniffer(req, body = nil, &block)
19
+ if started? && Sniffer.enabled?
20
+ data_item = Sniffer::DataItem.new
21
+ data_item.request = Sniffer::DataItem::Request.new(host: @address,
22
+ method: req.method,
23
+ query: req.path,
24
+ port: @port,
25
+ headers: req.each_header.collect.to_h,
26
+ body: req.body.to_s)
27
+
28
+ Sniffer.store(data_item)
29
+ end
30
+
31
+ bm = Benchmark.realtime do
32
+ @response = request_without_sniffer(req, body, &block)
33
+ end
34
+
35
+ if started? && Sniffer.enabled?
36
+ data_item.response = Sniffer::DataItem::Response.new(status: @response.code.to_i,
37
+ headers: @response.each_header.collect.to_h,
38
+ body: @response.body.to_s,
39
+ timing: bm)
40
+
41
+ data_item.log
42
+ end
43
+
44
+ @response
45
+ end
46
+ # rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength
47
+ end
48
+ end
49
+ end
50
+ Net::HTTP.send(:include, Sniffer::Adapters::NetHttpAdapter)
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sniffer
4
+ module Adapters
5
+ # HTTP adapter
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
+ 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
28
+
29
+ bm = Benchmark.realtime do
30
+ @res = request_without_sniffer(action_name, url, headers, options)
31
+ end
32
+
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
41
+
42
+ @res
43
+ end
44
+ # rubocop:enable Metrics/AbcSize,Metrics/MethodLength
45
+ end
46
+ end
47
+ end
48
+
49
+ ::Patron::Session.send(:include, Sniffer::Adapters::PatronAdapter) if defined?(::Patron::Session)
@@ -8,13 +8,18 @@ module Sniffer
8
8
  config_name :sniffer
9
9
 
10
10
  attr_config logger: Logger.new($stdout),
11
- request_headers: false,
12
- requst_body: true,
13
- response_status: true,
14
- response_headers: false,
15
- response_body: true,
16
- whitelist_url: /.*/,
17
- blacklist_url: nil,
18
- store: true
11
+ severity: Logger::Severity::DEBUG,
12
+ log: {
13
+ request_url: true,
14
+ request_headers: true,
15
+ request_body: true,
16
+ request_method: true,
17
+ response_status: true,
18
+ response_headers: true,
19
+ response_body: true,
20
+ timing: true
21
+ },
22
+ store: true,
23
+ enabled: false
19
24
  end
20
25
  end
@@ -1,9 +1,113 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'active_attr'
4
+ require 'json'
5
+
3
6
  module Sniffer
4
7
  # Sniffer data item stores a request info
5
8
  class DataItem
6
- attr_accessor :request_headers, :requst_body,
7
- :response_status, :response_headers, :response_body
9
+ include ActiveAttr::MassAssignment
10
+ attr_accessor :request, :response
11
+
12
+ def to_h
13
+ {
14
+ request: request&.to_h,
15
+ response: response&.to_h
16
+ }
17
+ end
18
+
19
+ def log
20
+ Sniffer.logger.log(Sniffer.config.severity, to_json)
21
+ end
22
+
23
+ def to_log
24
+ return {} unless Sniffer.config.logger
25
+ request.to_log.merge(response.to_log)
26
+ end
27
+
28
+ def to_json
29
+ to_log.to_json
30
+ end
31
+
32
+ # Basic object for request and response objects
33
+ class HttpObject
34
+ include ActiveAttr::MassAssignment
35
+
36
+ def log_message
37
+ raise NotImplementedError
38
+ end
39
+
40
+ def log_settings
41
+ Sniffer.config.log || {}
42
+ end
43
+ end
44
+
45
+ # Stores http request data
46
+ class Request < HttpObject
47
+ attr_accessor :host, :port, :query, :method, :headers, :body
48
+
49
+ def to_h
50
+ {
51
+ host: host,
52
+ query: query,
53
+ port: port,
54
+ headers: headers,
55
+ body: body,
56
+ method: method
57
+ }
58
+ end
59
+
60
+ # rubocop:disable Metrics/AbcSize,Metrics/MethodLength
61
+ def to_log
62
+ {}.tap do |hash|
63
+ if log_settings["request_url"]
64
+ hash[:port] = port
65
+ hash[:host] = host
66
+ hash[:query] = query
67
+ end
68
+
69
+ if log_settings["request_headers"]
70
+ headers.each do |(k, v)|
71
+ hash[:"rq_#{k.to_s.tr("-", '_').downcase}"] = v
72
+ end
73
+ end
74
+
75
+ hash[:method] = method if log_settings["request_method"]
76
+ hash[:request_body] = body if log_settings["request_body"]
77
+ end
78
+ end
79
+ end
80
+ # rubocop:enable Metrics/AbcSize,Metrics/MethodLength
81
+
82
+ # Stores http response data
83
+ class Response < HttpObject
84
+ attr_accessor :status, :headers, :body, :timing
85
+
86
+ def to_h
87
+ {
88
+ status: status,
89
+ headers: headers,
90
+ body: body,
91
+ timing: timing
92
+ }
93
+ end
94
+
95
+ # rubocop:disable Metrics/AbcSize
96
+ def to_log
97
+ {}.tap do |hash|
98
+ hash[:status] = status if log_settings["response_status"]
99
+
100
+ if log_settings["response_headers"]
101
+ headers.each do |(k, v)|
102
+ hash[:"rs_#{k.to_s.tr("-", '_').downcase}"] = v
103
+ end
104
+ end
105
+
106
+ hash[:timing] = timing if log_settings["timing"]
107
+ hash[:response_body] = body if log_settings["response_body"]
108
+ end
109
+ end
110
+ # rubocop:enable Metrics/AbcSize
111
+ end
8
112
  end
9
113
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Sniffer
4
- VERSION = "0.0.1"
4
+ VERSION = "0.1.0"
5
5
  end
data/lib/sniffer.rb CHANGED
@@ -2,15 +2,33 @@
2
2
 
3
3
  require "logger"
4
4
 
5
- require "sniffer/version"
6
- require "sniffer/config"
7
- require "sniffer/data_item"
5
+ require_relative "sniffer/version"
6
+ require_relative "sniffer/config"
7
+ require_relative "sniffer/data_item"
8
8
 
9
9
  # Sniffer allows to log http requests
10
10
  module Sniffer
11
+ @data = Set.new
12
+
11
13
  class << self
14
+ attr_reader :data
15
+
12
16
  def config
13
17
  @config ||= Config.new
18
+ yield @config if block_given?
19
+ @config
20
+ end
21
+
22
+ def enable!
23
+ config.enabled = true
24
+ end
25
+
26
+ def disable!
27
+ config.enabled = false
28
+ end
29
+
30
+ def enabled?
31
+ config.enabled
14
32
  end
15
33
 
16
34
  def configure
@@ -18,16 +36,27 @@ module Sniffer
18
36
  end
19
37
 
20
38
  def clear!
21
- @data = []
39
+ data.clear
22
40
  end
23
41
 
24
- def data
25
- @data ||= []
42
+ def reset!
43
+ @config = Config.new
44
+ clear!
26
45
  end
27
46
 
28
47
  def store(data_item)
29
- data
30
- @data << data_item
48
+ @data.add(data_item) if config.store
49
+ end
50
+
51
+ def logger
52
+ config.logger
31
53
  end
32
54
  end
33
55
  end
56
+
57
+ require_relative "sniffer/adapters/net_http_adapter"
58
+ require_relative "sniffer/adapters/httpclient_adapter"
59
+ require_relative "sniffer/adapters/http_adapter"
60
+ require_relative "sniffer/adapters/patron_adapter"
61
+ require_relative "sniffer/adapters/curb_adapter"
62
+ require_relative "sniffer/adapters/ethon_adapter"
data/sniffer.gemspec CHANGED
@@ -17,14 +17,22 @@ Gem::Specification.new do |spec|
17
17
  spec.files = `git ls-files -z`.split("\x0").reject do |f|
18
18
  f.match(%r{^(test|spec|features)/})
19
19
  end
20
- spec.bindir = "exe"
21
- spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
22
20
  spec.require_paths = ["lib"]
23
21
 
24
22
  spec.add_dependency "anyway_config", "~> 1.0"
23
+ spec.add_dependency "active_attr", ">= 0.10.2"
25
24
 
26
25
  spec.add_development_dependency "bundler", "~> 1.15"
27
26
  spec.add_development_dependency "rake", "~> 10.0"
28
27
  spec.add_development_dependency "rspec", "~> 3.0"
29
28
  spec.add_development_dependency "rubocop", "~> 0.50"
29
+ spec.add_development_dependency "pry-byebug"
30
+ spec.add_development_dependency "sinatra", "~> 2.0"
31
+ spec.add_development_dependency "puma", ">= 3.10.0"
32
+ spec.add_development_dependency "httpclient", ">= 2.8.3"
33
+ spec.add_development_dependency "http", ">= 3.0.0"
34
+ spec.add_development_dependency "patron", ">= 0.10.0"
35
+ spec.add_development_dependency "curb", ">= 0.9.4"
36
+ spec.add_development_dependency "ethon", ">= 0.11.0"
37
+ spec.add_development_dependency "typhoeus", ">= 0.9.0"
30
38
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sniffer
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andrey Deryabin
8
8
  autorequire:
9
- bindir: exe
9
+ bindir: bin
10
10
  cert_chain: []
11
- date: 2017-10-18 00:00:00.000000000 Z
11
+ date: 2017-11-14 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: anyway_config
@@ -24,6 +24,20 @@ dependencies:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
26
  version: '1.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: active_attr
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: 0.10.2
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: 0.10.2
27
41
  - !ruby/object:Gem::Dependency
28
42
  name: bundler
29
43
  requirement: !ruby/object:Gem::Requirement
@@ -80,6 +94,132 @@ dependencies:
80
94
  - - "~>"
81
95
  - !ruby/object:Gem::Version
82
96
  version: '0.50'
97
+ - !ruby/object:Gem::Dependency
98
+ name: pry-byebug
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ - !ruby/object:Gem::Dependency
112
+ name: sinatra
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - "~>"
116
+ - !ruby/object:Gem::Version
117
+ version: '2.0'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - "~>"
123
+ - !ruby/object:Gem::Version
124
+ version: '2.0'
125
+ - !ruby/object:Gem::Dependency
126
+ name: puma
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - ">="
130
+ - !ruby/object:Gem::Version
131
+ version: 3.10.0
132
+ type: :development
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - ">="
137
+ - !ruby/object:Gem::Version
138
+ version: 3.10.0
139
+ - !ruby/object:Gem::Dependency
140
+ name: httpclient
141
+ requirement: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - ">="
144
+ - !ruby/object:Gem::Version
145
+ version: 2.8.3
146
+ type: :development
147
+ prerelease: false
148
+ version_requirements: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - ">="
151
+ - !ruby/object:Gem::Version
152
+ version: 2.8.3
153
+ - !ruby/object:Gem::Dependency
154
+ name: http
155
+ requirement: !ruby/object:Gem::Requirement
156
+ requirements:
157
+ - - ">="
158
+ - !ruby/object:Gem::Version
159
+ version: 3.0.0
160
+ type: :development
161
+ prerelease: false
162
+ version_requirements: !ruby/object:Gem::Requirement
163
+ requirements:
164
+ - - ">="
165
+ - !ruby/object:Gem::Version
166
+ version: 3.0.0
167
+ - !ruby/object:Gem::Dependency
168
+ name: patron
169
+ requirement: !ruby/object:Gem::Requirement
170
+ requirements:
171
+ - - ">="
172
+ - !ruby/object:Gem::Version
173
+ version: 0.10.0
174
+ type: :development
175
+ prerelease: false
176
+ version_requirements: !ruby/object:Gem::Requirement
177
+ requirements:
178
+ - - ">="
179
+ - !ruby/object:Gem::Version
180
+ version: 0.10.0
181
+ - !ruby/object:Gem::Dependency
182
+ name: curb
183
+ requirement: !ruby/object:Gem::Requirement
184
+ requirements:
185
+ - - ">="
186
+ - !ruby/object:Gem::Version
187
+ version: 0.9.4
188
+ type: :development
189
+ prerelease: false
190
+ version_requirements: !ruby/object:Gem::Requirement
191
+ requirements:
192
+ - - ">="
193
+ - !ruby/object:Gem::Version
194
+ version: 0.9.4
195
+ - !ruby/object:Gem::Dependency
196
+ name: ethon
197
+ requirement: !ruby/object:Gem::Requirement
198
+ requirements:
199
+ - - ">="
200
+ - !ruby/object:Gem::Version
201
+ version: 0.11.0
202
+ type: :development
203
+ prerelease: false
204
+ version_requirements: !ruby/object:Gem::Requirement
205
+ requirements:
206
+ - - ">="
207
+ - !ruby/object:Gem::Version
208
+ version: 0.11.0
209
+ - !ruby/object:Gem::Dependency
210
+ name: typhoeus
211
+ requirement: !ruby/object:Gem::Requirement
212
+ requirements:
213
+ - - ">="
214
+ - !ruby/object:Gem::Version
215
+ version: 0.9.0
216
+ type: :development
217
+ prerelease: false
218
+ version_requirements: !ruby/object:Gem::Requirement
219
+ requirements:
220
+ - - ">="
221
+ - !ruby/object:Gem::Version
222
+ version: 0.9.0
83
223
  description: Analyze HTTP Requests
84
224
  email:
85
225
  - aderyabin@evilmartians.com
@@ -97,9 +237,16 @@ files:
97
237
  - LICENSE.txt
98
238
  - README.md
99
239
  - Rakefile
240
+ - assets/demo.gif
100
241
  - bin/console
101
242
  - bin/setup
102
243
  - lib/sniffer.rb
244
+ - lib/sniffer/adapters/curb_adapter.rb
245
+ - lib/sniffer/adapters/ethon_adapter.rb
246
+ - lib/sniffer/adapters/http_adapter.rb
247
+ - lib/sniffer/adapters/httpclient_adapter.rb
248
+ - lib/sniffer/adapters/net_http_adapter.rb
249
+ - lib/sniffer/adapters/patron_adapter.rb
103
250
  - lib/sniffer/config.rb
104
251
  - lib/sniffer/data_item.rb
105
252
  - lib/sniffer/version.rb