sniffer 0.0.1 → 0.1.0

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