site-inspector 3.1.1 → 4.0.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 +5 -5
- data/.github/workflows/ci.yml +30 -0
- data/.gitignore +1 -1
- data/.rubocop.yml +14 -10
- data/.rubocop_todo.yml +245 -0
- data/.ruby-version +1 -1
- data/CHANGELOG.md +71 -0
- data/Gemfile +2 -0
- data/Guardfile +2 -0
- data/README.md +8 -14
- data/Rakefile +2 -0
- data/bin/site-inspector +16 -15
- data/lib/cliver/dependency_ext.rb +8 -5
- data/lib/data/well-known.yml +51 -0
- data/lib/site-inspector/domain.rb +61 -43
- data/lib/site-inspector/domain_parser.rb +11 -0
- data/lib/site-inspector/{checks → endpoint}/accessibility.rb +35 -25
- data/lib/site-inspector/{checks → endpoint}/check.rb +9 -3
- data/lib/site-inspector/endpoint/content.rb +151 -0
- data/lib/site-inspector/endpoint/cookies.rb +53 -0
- data/lib/site-inspector/{checks → endpoint}/dns.rb +52 -26
- data/lib/site-inspector/endpoint/headers.rb +55 -0
- data/lib/site-inspector/endpoint/hsts.rb +112 -0
- data/lib/site-inspector/{checks → endpoint}/https.rb +3 -1
- data/lib/site-inspector/endpoint/wappalyzer.rb +68 -0
- data/lib/site-inspector/endpoint/well_known.rb +95 -0
- data/lib/site-inspector/endpoint/whois.rb +56 -0
- data/lib/site-inspector/endpoint.rb +80 -44
- data/lib/site-inspector/formatter.rb +118 -0
- data/lib/site-inspector/version.rb +3 -1
- data/lib/site-inspector.rb +42 -31
- data/package-lock.json +1152 -0
- data/package.json +2 -1
- data/renovate.json +4 -0
- data/script/benchmark +8 -0
- data/script/cibuild +2 -2
- data/script/console +1 -1
- data/script/pa11y-version +2 -1
- data/script/release +11 -4
- data/script/update-well-known +18 -0
- data/site-inspector.gemspec +33 -19
- data/spec/cliver/dependency_ext_spec.rb +21 -0
- data/spec/fixtures/wappalyzer.json +125 -0
- data/spec/{site_inspector_domain_spec.rb → site_inspector/domain_spec.rb} +105 -43
- data/spec/{checks/site_inspector_endpoint_accessibility_spec.rb → site_inspector/endpoint/accessibility_spec.rb} +24 -20
- data/spec/{checks/site_inspector_endpoint_check_spec.rb → site_inspector/endpoint/check_spec.rb} +9 -7
- data/spec/{checks/site_inspector_endpoint_content_spec.rb → site_inspector/endpoint/content_spec.rb} +43 -24
- data/spec/{checks/site_inspector_endpoint_cookies_spec.rb → site_inspector/endpoint/cookies_spec.rb} +37 -22
- data/spec/site_inspector/endpoint/dns_spec.rb +262 -0
- data/spec/{checks/site_inspector_endpoint_headers_spec.rb → site_inspector/endpoint/headers_spec.rb} +10 -15
- data/spec/site_inspector/endpoint/hsts_spec.rb +92 -0
- data/spec/{checks/site_inspector_endpoint_https_spec.rb → site_inspector/endpoint/https_spec.rb} +12 -10
- data/spec/site_inspector/endpoint/wappalyzer_spec.rb +20 -0
- data/spec/site_inspector/endpoint/well_known_spec.rb +12 -0
- data/spec/site_inspector/endpoint/whois_spec.rb +27 -0
- data/spec/{site_inspector_endpoint_spec.rb → site_inspector/endpoint_spec.rb} +102 -45
- data/spec/site_inspector_spec.rb +67 -18
- data/spec/spec_helper.rb +11 -2
- metadata +178 -100
- data/.travis.yml +0 -9
- data/lib/site-inspector/cache.rb +0 -15
- data/lib/site-inspector/checks/content.rb +0 -74
- data/lib/site-inspector/checks/cookies.rb +0 -43
- data/lib/site-inspector/checks/headers.rb +0 -66
- data/lib/site-inspector/checks/hsts.rb +0 -77
- data/lib/site-inspector/checks/sniffer.rb +0 -64
- data/lib/site-inspector/disk_cache.rb +0 -40
- data/lib/site-inspector/rails_cache.rb +0 -11
- data/spec/checks/site_inspector_endpoint_dns_spec.rb +0 -182
- data/spec/checks/site_inspector_endpoint_hsts_spec.rb +0 -90
- data/spec/checks/site_inspector_endpoint_sniffer_spec.rb +0 -148
- data/spec/site_inspector_cache_spec.rb +0 -13
- data/spec/site_inspector_disk_cache_spec.rb +0 -37
|
@@ -1,3 +1,17 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'site-inspector/endpoint/check'
|
|
4
|
+
require 'site-inspector/endpoint/accessibility'
|
|
5
|
+
require 'site-inspector/endpoint/content'
|
|
6
|
+
require 'site-inspector/endpoint/dns'
|
|
7
|
+
require 'site-inspector/endpoint/headers'
|
|
8
|
+
require 'site-inspector/endpoint/hsts'
|
|
9
|
+
require 'site-inspector/endpoint/https'
|
|
10
|
+
require 'site-inspector/endpoint/cookies'
|
|
11
|
+
require 'site-inspector/endpoint/well_known'
|
|
12
|
+
require 'site-inspector/endpoint/whois'
|
|
13
|
+
require 'site-inspector/endpoint/wappalyzer'
|
|
14
|
+
|
|
1
15
|
class SiteInspector
|
|
2
16
|
# Every domain has four possible "endpoints" to evaluate
|
|
3
17
|
#
|
|
@@ -12,23 +26,32 @@ class SiteInspector
|
|
|
12
26
|
class Endpoint
|
|
13
27
|
attr_accessor :host, :uri, :domain
|
|
14
28
|
|
|
29
|
+
def self.build(domain, https: true, www: false)
|
|
30
|
+
uri = Addressable::URI.new
|
|
31
|
+
uri.host = www ? "www.#{domain.host}" : domain.host.to_s
|
|
32
|
+
uri.scheme = https ? 'https' : 'http'
|
|
33
|
+
|
|
34
|
+
new(uri.to_s, domain:)
|
|
35
|
+
end
|
|
36
|
+
|
|
15
37
|
# Initatiate a new Endpoint object
|
|
16
38
|
#
|
|
17
39
|
# endpoint - (string) the endpoint to query (e.g., `https://example.com`)
|
|
18
40
|
# options - A hash of options
|
|
19
41
|
# domain - the parent domain object, if passed, facilitates caching of redirects
|
|
20
42
|
def initialize(host, options = {})
|
|
21
|
-
|
|
43
|
+
host = host.downcase if host.is_a?(String)
|
|
44
|
+
@uri = Addressable::URI.parse(host)
|
|
22
45
|
# The root URL always has an implict path of "/", even if not requested
|
|
23
46
|
# Make it explicit to facilitate caching and prevent a potential redirect
|
|
24
47
|
@uri.path = '/'
|
|
25
|
-
@host =
|
|
48
|
+
@host = DomainParser.parse(host)
|
|
26
49
|
@checks = {}
|
|
27
50
|
@domain = options[:domain]
|
|
28
51
|
end
|
|
29
52
|
|
|
30
53
|
def www?
|
|
31
|
-
|
|
54
|
+
@host.trd == 'www'
|
|
32
55
|
end
|
|
33
56
|
|
|
34
57
|
def root?
|
|
@@ -48,13 +71,21 @@ class SiteInspector
|
|
|
48
71
|
end
|
|
49
72
|
|
|
50
73
|
def request(options = {})
|
|
51
|
-
|
|
52
|
-
request = Typhoeus::Request.new(target, SiteInspector.typhoeus_defaults.merge(options))
|
|
74
|
+
request = build_request(options)
|
|
53
75
|
hydra.queue(request)
|
|
54
76
|
hydra.run
|
|
55
77
|
request.response
|
|
56
78
|
end
|
|
57
79
|
|
|
80
|
+
def build_request(options)
|
|
81
|
+
target = options[:path] ? join(options.delete(:path)) : uri
|
|
82
|
+
Typhoeus::Request.new(target, SiteInspector.typhoeus_defaults.merge(options))
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def join(*args)
|
|
86
|
+
URI.join(uri, *args)
|
|
87
|
+
end
|
|
88
|
+
|
|
58
89
|
# Makes a GET request of the given host
|
|
59
90
|
#
|
|
60
91
|
# Retutns the Typhoeus::Response object
|
|
@@ -67,12 +98,14 @@ class SiteInspector
|
|
|
67
98
|
end
|
|
68
99
|
|
|
69
100
|
def timed_out?
|
|
70
|
-
response
|
|
101
|
+
response&.timed_out?
|
|
71
102
|
end
|
|
72
103
|
|
|
73
104
|
# Does the endpoint return a 2xx or 3xx response code?
|
|
74
105
|
def up?
|
|
75
|
-
|
|
106
|
+
return false unless response
|
|
107
|
+
|
|
108
|
+
response_code.start_with?('2', '3')
|
|
76
109
|
end
|
|
77
110
|
|
|
78
111
|
# Does the server respond at all?
|
|
@@ -84,18 +117,16 @@ class SiteInspector
|
|
|
84
117
|
def redirect
|
|
85
118
|
return unless response && response_code.start_with?('3')
|
|
86
119
|
|
|
87
|
-
|
|
88
|
-
|
|
120
|
+
location = headers['location']
|
|
121
|
+
return if location.nil? || location.empty?
|
|
89
122
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
redirect.scheme = scheme
|
|
95
|
-
end
|
|
123
|
+
@redirect ||= begin
|
|
124
|
+
# Resolve per RFC 3986, so protocol-relative (`//host`), query-only
|
|
125
|
+
# (`?q`), and dot-segment (`../x`) locations become absolute URIs
|
|
126
|
+
redirect = uri.join(location)
|
|
96
127
|
|
|
97
128
|
# This was a redirect to a subpath or back to itself, which we don't care about
|
|
98
|
-
return if redirect.host == host && redirect.scheme == scheme
|
|
129
|
+
return if redirect.host == host.to_s && redirect.scheme == scheme
|
|
99
130
|
|
|
100
131
|
# Init a new endpoint representing the redirect
|
|
101
132
|
find_or_create_by_uri(redirect.to_s)
|
|
@@ -119,22 +150,18 @@ class SiteInspector
|
|
|
119
150
|
response = request(followlocation: true)
|
|
120
151
|
|
|
121
152
|
# Workaround for Webmock not playing nicely with Typhoeus redirects
|
|
122
|
-
if response.mock?
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
end
|
|
128
|
-
else
|
|
129
|
-
url = response.effective_url
|
|
130
|
-
end
|
|
153
|
+
url = if response.mock?
|
|
154
|
+
response.headers['Location'] || response.request.url
|
|
155
|
+
else
|
|
156
|
+
response.effective_url
|
|
157
|
+
end
|
|
131
158
|
|
|
132
159
|
find_or_create_by_uri(url)
|
|
133
160
|
end
|
|
134
161
|
end
|
|
135
162
|
|
|
136
163
|
def external_redirect?
|
|
137
|
-
host != resolves_to.host
|
|
164
|
+
host.to_s != resolves_to.host.to_s
|
|
138
165
|
end
|
|
139
166
|
|
|
140
167
|
def to_s
|
|
@@ -155,36 +182,40 @@ class SiteInspector
|
|
|
155
182
|
#
|
|
156
183
|
# Returns the hash representing the endpoint and its checks
|
|
157
184
|
def to_h(options = {})
|
|
158
|
-
hash
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
185
|
+
return @hash if defined?(@hash) && options == {}
|
|
186
|
+
|
|
187
|
+
@hash = {
|
|
188
|
+
uri: uri.to_s,
|
|
189
|
+
host: host.to_s,
|
|
190
|
+
www: www?,
|
|
191
|
+
https: https?,
|
|
192
|
+
scheme:,
|
|
193
|
+
up: up?,
|
|
194
|
+
responds: responds?,
|
|
195
|
+
timed_out: timed_out?,
|
|
196
|
+
redirect: redirect?,
|
|
197
|
+
resolves_to: resolves_to.to_s,
|
|
168
198
|
external_redirect: external_redirect?
|
|
169
199
|
}
|
|
170
200
|
|
|
171
201
|
# Either they've specifically asked for a check, or we throw everything at them
|
|
172
|
-
checks = SiteInspector::Endpoint.checks.select { |c| options.
|
|
202
|
+
checks = SiteInspector::Endpoint.checks.select { |c| options.key?(c.name) }
|
|
173
203
|
checks = SiteInspector::Endpoint.checks if checks.empty?
|
|
174
204
|
|
|
175
|
-
Parallel.each(checks,
|
|
176
|
-
hash[check.name] = send(check.name).to_h
|
|
205
|
+
Parallel.each(checks, in_processes: 0) do |check|
|
|
206
|
+
@hash[check.name] = send(check.name).to_h
|
|
177
207
|
end
|
|
178
208
|
|
|
179
|
-
hash
|
|
209
|
+
@hash
|
|
180
210
|
end
|
|
181
211
|
|
|
182
212
|
def self.checks
|
|
183
213
|
return @checks if defined? @checks
|
|
184
|
-
|
|
214
|
+
|
|
215
|
+
@checks = ObjectSpace.each_object(Class).select { |klass| klass < Check }.select(&:enabled?).sort_by(&:name)
|
|
185
216
|
end
|
|
186
217
|
|
|
187
|
-
def method_missing(method_sym, *arguments, &
|
|
218
|
+
def method_missing(method_sym, *arguments, &)
|
|
188
219
|
check = SiteInspector::Endpoint.checks.find { |c| c.name == method_sym }
|
|
189
220
|
if check
|
|
190
221
|
@checks[method_sym] ||= check.new(self)
|
|
@@ -193,14 +224,19 @@ class SiteInspector
|
|
|
193
224
|
end
|
|
194
225
|
end
|
|
195
226
|
|
|
196
|
-
def
|
|
197
|
-
if checks.
|
|
227
|
+
def respond_to_missing?(method_sym, include_private = false)
|
|
228
|
+
if checks.key?(method_sym)
|
|
198
229
|
true
|
|
199
230
|
else
|
|
200
231
|
super
|
|
201
232
|
end
|
|
202
233
|
end
|
|
203
234
|
|
|
235
|
+
def eql?(other)
|
|
236
|
+
other.class == self.class && other.uri == uri
|
|
237
|
+
end
|
|
238
|
+
alias == eql?
|
|
239
|
+
|
|
204
240
|
private
|
|
205
241
|
|
|
206
242
|
def hydra
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'active_support/core_ext/string/inflections'
|
|
4
|
+
require 'nokogiri'
|
|
5
|
+
|
|
6
|
+
class SiteInspector
|
|
7
|
+
module Formatter
|
|
8
|
+
ACRONYMS = %w[
|
|
9
|
+
acme cdn cms crm dns dnssec dnt hsts http https id ip ipv6 json paas pki sld ssl tld tls trd txt ui uri url whois www xml xss
|
|
10
|
+
].freeze
|
|
11
|
+
|
|
12
|
+
def format_table(hash)
|
|
13
|
+
build_fragment do |f|
|
|
14
|
+
f.table(class: 'table table-striped') do
|
|
15
|
+
hash.each do |key, value|
|
|
16
|
+
f << format_key_value(key, value)
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def format_key(string)
|
|
23
|
+
capitalize_acronyms(string.to_s.gsub(/^x-/, '').tr('-', ' ').humanize)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def format_value(value)
|
|
27
|
+
case value
|
|
28
|
+
when Hash
|
|
29
|
+
format_hash(value)
|
|
30
|
+
when Array
|
|
31
|
+
format_array(value)
|
|
32
|
+
else
|
|
33
|
+
format_string(value)
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def format_key_value(key, value, check = nil)
|
|
38
|
+
c = class_for_value(value)
|
|
39
|
+
|
|
40
|
+
build_fragment do |f|
|
|
41
|
+
f.tr do
|
|
42
|
+
f.th { f.text format_key(key) }
|
|
43
|
+
f.td(c) do
|
|
44
|
+
uri = check&.uri_for(key)
|
|
45
|
+
|
|
46
|
+
if value == true && uri
|
|
47
|
+
f.a({ href: uri }.merge(c)) { f << format_value(value) }
|
|
48
|
+
else
|
|
49
|
+
f << format_value(value)
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
private
|
|
57
|
+
|
|
58
|
+
def class_for_value(value)
|
|
59
|
+
if value.instance_of?(TrueClass)
|
|
60
|
+
{ class: 'text-success' }
|
|
61
|
+
elsif value.instance_of?(FalseClass)
|
|
62
|
+
{ class: 'text-danger' }
|
|
63
|
+
else
|
|
64
|
+
{}
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def build_fragment(&block)
|
|
69
|
+
fragment = Nokogiri::HTML::DocumentFragment.parse ''
|
|
70
|
+
Nokogiri::HTML::Builder.with(fragment, &block)
|
|
71
|
+
fragment.to_s
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def capitalize_acronyms(string)
|
|
75
|
+
string.gsub(/\b(#{ACRONYMS.join("|")})\b/i) do
|
|
76
|
+
Regexp.last_match(1).to_s.upcase
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def format_string(value)
|
|
81
|
+
value = CGI.escapeHTML(value.to_s)
|
|
82
|
+
|
|
83
|
+
if %r{^https?:/}.match?(value)
|
|
84
|
+
value = build_fragment do |f|
|
|
85
|
+
f.a(href: value) { f.text value }
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
value
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def format_hash(hash)
|
|
93
|
+
build_fragment do |f|
|
|
94
|
+
f.ul do
|
|
95
|
+
hash.each do |key, value|
|
|
96
|
+
f.li do
|
|
97
|
+
f.span(class: 'font-weight-bold') { f.text format_key(key) }
|
|
98
|
+
f.text ': '
|
|
99
|
+
f << format_value(value)
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def format_array(array)
|
|
107
|
+
return format_value(array[0]) if array.length == 1
|
|
108
|
+
|
|
109
|
+
build_fragment do |f|
|
|
110
|
+
f.ol do
|
|
111
|
+
array.each do |el|
|
|
112
|
+
f.li { f << format_value(el) }
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
end
|
data/lib/site-inspector.rb
CHANGED
|
@@ -1,40 +1,41 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require 'open-uri'
|
|
2
4
|
require 'addressable/uri'
|
|
3
5
|
require 'public_suffix'
|
|
4
6
|
require 'typhoeus'
|
|
7
|
+
require 'typhoeus/cache/rails'
|
|
8
|
+
require 'active_support'
|
|
9
|
+
require 'active_support/cache'
|
|
5
10
|
require 'parallel'
|
|
6
11
|
require 'cliver'
|
|
12
|
+
require 'whois'
|
|
7
13
|
require 'cgi'
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
require_relative 'site-inspector/disk_cache'
|
|
11
|
-
require_relative 'site-inspector/rails_cache'
|
|
12
|
-
require_relative 'site-inspector/domain'
|
|
13
|
-
require_relative 'site-inspector/checks/check'
|
|
14
|
-
require_relative 'site-inspector/checks/accessibility'
|
|
15
|
-
require_relative 'site-inspector/checks/content'
|
|
16
|
-
require_relative 'site-inspector/checks/dns'
|
|
17
|
-
require_relative 'site-inspector/checks/headers'
|
|
18
|
-
require_relative 'site-inspector/checks/hsts'
|
|
19
|
-
require_relative 'site-inspector/checks/https'
|
|
20
|
-
require_relative 'site-inspector/checks/sniffer'
|
|
21
|
-
require_relative 'site-inspector/checks/cookies'
|
|
22
|
-
require_relative 'site-inspector/endpoint'
|
|
23
|
-
require_relative 'site-inspector/version'
|
|
14
|
+
require 'resolv'
|
|
15
|
+
require 'naughty_or_nice'
|
|
24
16
|
require_relative 'cliver/dependency_ext'
|
|
25
17
|
|
|
26
18
|
class SiteInspector
|
|
19
|
+
autoload :Formatter, 'site-inspector/formatter'
|
|
20
|
+
autoload :Domain, 'site-inspector/domain'
|
|
21
|
+
autoload :DomainParser, 'site-inspector/domain_parser'
|
|
22
|
+
autoload :Endpoint, 'site-inspector/endpoint'
|
|
23
|
+
autoload :VERSION, 'site-inspector/version'
|
|
24
|
+
|
|
27
25
|
class << self
|
|
28
26
|
attr_writer :timeout, :cache, :typhoeus_options
|
|
29
27
|
|
|
28
|
+
# The Typhoeus response cache. Set CACHE to a directory to persist
|
|
29
|
+
# responses to disk; inside Rails, Rails.cache is used; otherwise
|
|
30
|
+
# responses are kept in memory for the life of the process.
|
|
30
31
|
def cache
|
|
31
32
|
@cache ||= if ENV['CACHE']
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
33
|
+
Typhoeus::Cache::Rails.new(ActiveSupport::Cache::FileStore.new(ENV.fetch('CACHE')))
|
|
34
|
+
elsif Object.const_defined?(:Rails)
|
|
35
|
+
Typhoeus::Cache::Rails.new
|
|
36
|
+
else
|
|
37
|
+
Typhoeus::Cache::Rails.new(ActiveSupport::Cache::MemoryStore.new)
|
|
38
|
+
end
|
|
38
39
|
end
|
|
39
40
|
|
|
40
41
|
def timeout
|
|
@@ -47,13 +48,10 @@ class SiteInspector
|
|
|
47
48
|
|
|
48
49
|
def typhoeus_defaults
|
|
49
50
|
defaults = {
|
|
50
|
-
followlocation:
|
|
51
|
-
timeout:
|
|
51
|
+
followlocation: false,
|
|
52
|
+
timeout: SiteInspector.timeout,
|
|
52
53
|
accept_encoding: 'gzip',
|
|
53
|
-
method:
|
|
54
|
-
headers: {
|
|
55
|
-
'User-Agent' => "Mozilla/5.0 (compatible; SiteInspector/#{SiteInspector::VERSION}; +https://github.com/benbalter/site-inspector)"
|
|
56
|
-
}
|
|
54
|
+
method: :head
|
|
57
55
|
}
|
|
58
56
|
defaults.merge! @typhoeus_options if @typhoeus_options
|
|
59
57
|
defaults
|
|
@@ -61,16 +59,29 @@ class SiteInspector
|
|
|
61
59
|
|
|
62
60
|
# Returns a thread-safe, memoized hydra instance
|
|
63
61
|
def hydra
|
|
64
|
-
Typhoeus::Hydra.hydra
|
|
62
|
+
@hydra ||= Typhoeus::Hydra.hydra
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def logger
|
|
66
|
+
@logger ||= Logger.new($stdout)
|
|
65
67
|
end
|
|
66
68
|
end
|
|
67
69
|
end
|
|
68
70
|
|
|
69
71
|
if ENV['DEBUG']
|
|
70
|
-
Ethon.logger =
|
|
72
|
+
Ethon.logger = SiteInspector.logger
|
|
71
73
|
Ethon.logger.level = Logger::DEBUG
|
|
72
74
|
Typhoeus::Config.verbose = true
|
|
73
75
|
end
|
|
74
76
|
|
|
75
|
-
Typhoeus::Config.memoize = true
|
|
76
77
|
Typhoeus::Config.cache = SiteInspector.cache
|
|
78
|
+
Typhoeus::Config.memoize = true
|
|
79
|
+
Typhoeus::Config.user_agent = "Mozilla/5.0 (compatible; SiteInspector/#{SiteInspector::VERSION}; +https://github.com/benbalter/site-inspector)"
|
|
80
|
+
|
|
81
|
+
if ENV['VERBOSE']
|
|
82
|
+
Typhoeus.before do |req|
|
|
83
|
+
hit = Typhoeus::Config.cache.get(req)
|
|
84
|
+
SiteInspector.logger.info "#{req.options[:method]} #{req.base_url} (CACHE #{hit ? 'HIT' : 'MISS'})"
|
|
85
|
+
req
|
|
86
|
+
end
|
|
87
|
+
end
|