vcr 1.2.0 → 1.3.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.
- data/CHANGELOG.md +12 -0
- data/FullBuildRakeFile +22 -0
- data/Gemfile +8 -0
- data/Gemfile.lock +46 -25
- data/Guardfile +9 -0
- data/README.md +10 -371
- data/Rakefile +6 -2
- data/TODO.md +4 -0
- data/features/support/env.rb +5 -1
- data/full_build +1 -0
- data/lib/vcr.rb +17 -7
- data/lib/vcr/basic_object.rb +39 -0
- data/lib/vcr/config.rb +4 -7
- data/lib/vcr/deprecations.rb +14 -0
- data/lib/vcr/http_stubbing_adapters/common.rb +44 -13
- data/lib/vcr/http_stubbing_adapters/fakeweb.rb +17 -28
- data/lib/vcr/http_stubbing_adapters/multi_object_proxy.rb +43 -0
- data/lib/vcr/http_stubbing_adapters/typhoeus.rb +101 -0
- data/lib/vcr/http_stubbing_adapters/webmock.rb +16 -37
- data/lib/vcr/internet_connection.rb +2 -1
- data/lib/vcr/structs.rb +32 -0
- data/lib/vcr/version.rb +1 -1
- data/spec/config_spec.rb +5 -25
- data/spec/deprecations_spec.rb +31 -3
- data/spec/extensions/net_http_spec.rb +1 -0
- data/spec/fixtures/1.9.1/fake_example.com_responses.yml +32 -1
- data/spec/fixtures/not_1.9.1/fake_example.com_responses.yml +32 -1
- data/spec/http_stubbing_adapters/fakeweb_spec.rb +9 -24
- data/spec/http_stubbing_adapters/multi_object_proxy_spec.rb +101 -0
- data/spec/http_stubbing_adapters/typhoeus_spec.rb +28 -0
- data/spec/http_stubbing_adapters/webmock_spec.rb +8 -26
- data/spec/internet_connection_spec.rb +25 -7
- data/spec/spec_helper.rb +3 -1
- data/spec/structs_spec.rb +30 -6
- data/spec/support/http_library_adapters.rb +50 -15
- data/spec/support/http_stubbing_adapter.rb +65 -56
- data/spec/support/version_checker.rb +29 -0
- data/spec/vcr_spec.rb +30 -12
- data/vcr.gemspec +5 -4
- metadata +44 -15
data/Rakefile
CHANGED
@@ -23,10 +23,13 @@ task :cleanup_rcov_files do
|
|
23
23
|
end
|
24
24
|
|
25
25
|
permutations = {
|
26
|
-
'fakeweb'
|
27
|
-
'
|
26
|
+
'fakeweb' => %w( net/http typhoeus ),
|
27
|
+
'typhoeus' => %w( typhoeus ),
|
28
|
+
'webmock' => %w( net/http typhoeus httpclient patron em-http-request curb )
|
28
29
|
}
|
29
30
|
|
31
|
+
permutations.delete('typhoeus') if RUBY_INTERPRETER == :jruby
|
32
|
+
|
30
33
|
require 'cucumber/rake/task'
|
31
34
|
namespace :features do
|
32
35
|
permutations.each do |http_stubbing_adapter, http_libraries|
|
@@ -35,6 +38,7 @@ namespace :features do
|
|
35
38
|
namespace http_stubbing_adapter do
|
36
39
|
http_libraries.each do |http_lib|
|
37
40
|
next if RUBY_INTERPRETER != :mri && %w( patron em-http-request curb ).include?(http_lib)
|
41
|
+
next if RUBY_INTERPRETER == :jruby && http_lib == 'typhoeus'
|
38
42
|
|
39
43
|
sanitized_http_lib = http_lib.gsub('/', '_')
|
40
44
|
features_subtasks << "features:#{http_stubbing_adapter}:#{sanitized_http_lib}"
|
data/TODO.md
ADDED
data/features/support/env.rb
CHANGED
@@ -43,7 +43,11 @@ YAML_SERIALIZATION_VERSION = RUBY_VERSION == '1.9.1' ? '1.9.1' : 'not_1.9.1'
|
|
43
43
|
|
44
44
|
VCR.config do |c|
|
45
45
|
c.cassette_library_dir = File.join(File.dirname(__FILE__), '..', 'fixtures', 'vcr_cassettes', YAML_SERIALIZATION_VERSION)
|
46
|
-
|
46
|
+
|
47
|
+
stubs = [ENV['HTTP_STUBBING_ADAPTER'].to_sym, :typhoeus].uniq
|
48
|
+
stubs.delete(:typhoeus) if RUBY_INTERPRETER == :jruby
|
49
|
+
|
50
|
+
c.stub_with *stubs
|
47
51
|
end
|
48
52
|
|
49
53
|
VCR.module_eval do
|
data/full_build
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
rvm 1.8.6,1.8.7,1.9.1,1.9.2,ree,jruby,rbx-head rake -f FullBuildRakeFile build | tee tmp/full_build.out
|
data/lib/vcr.rb
CHANGED
@@ -9,6 +9,7 @@ require 'vcr/http_stubbing_adapters/common'
|
|
9
9
|
module VCR
|
10
10
|
extend self
|
11
11
|
|
12
|
+
autoload :BasicObject, 'vcr/basic_object'
|
12
13
|
autoload :CucumberTags, 'vcr/cucumber_tags'
|
13
14
|
autoload :InternetConnection, 'vcr/internet_connection'
|
14
15
|
autoload :RSpec, 'vcr/rspec'
|
@@ -54,13 +55,22 @@ module VCR
|
|
54
55
|
end
|
55
56
|
|
56
57
|
def http_stubbing_adapter
|
57
|
-
@http_stubbing_adapter ||=
|
58
|
-
|
59
|
-
VCR
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
58
|
+
@http_stubbing_adapter ||= begin
|
59
|
+
if [:fakeweb, :webmock].all? { |l| VCR::Config.http_stubbing_libraries.include?(l) }
|
60
|
+
raise ArgumentError.new("You have configured VCR to use both :fakeweb and :webmock. You cannot use both.")
|
61
|
+
end
|
62
|
+
|
63
|
+
adapters = VCR::Config.http_stubbing_libraries.map do |lib|
|
64
|
+
case lib
|
65
|
+
when :fakeweb; HttpStubbingAdapters::FakeWeb
|
66
|
+
when :webmock; HttpStubbingAdapters::WebMock
|
67
|
+
when :typhoeus; HttpStubbingAdapters::Typhoeus
|
68
|
+
else raise ArgumentError.new("#{lib.inspect} is not a supported HTTP stubbing library.")
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
raise ArgumentError.new("The http stubbing library is not configured.") if adapters.empty?
|
73
|
+
HttpStubbingAdapters::MultiObjectProxy.for(*adapters)
|
64
74
|
end
|
65
75
|
end
|
66
76
|
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module VCR
|
2
|
+
# taken directly from backports:
|
3
|
+
# https://github.com/marcandre/backports/blob/v1.18.2/lib/backports/basic_object.rb
|
4
|
+
class BasicObject
|
5
|
+
KEEP = [:instance_eval, :instance_exec, :__send__,
|
6
|
+
"instance_eval", "instance_exec", "__send__"]
|
7
|
+
# undefine almost all instance methods
|
8
|
+
begin
|
9
|
+
old_verbose, $VERBOSE = $VERBOSE, nil # silence the warning for undefining __id__
|
10
|
+
(instance_methods - KEEP).each do |method|
|
11
|
+
undef_method method
|
12
|
+
end
|
13
|
+
ensure
|
14
|
+
$VERBOSE = old_verbose
|
15
|
+
end
|
16
|
+
|
17
|
+
class << self
|
18
|
+
def === (cmp)
|
19
|
+
true
|
20
|
+
end
|
21
|
+
|
22
|
+
# Let's try to keep things clean, in case methods have been added to Object
|
23
|
+
# either directly or through an included module.
|
24
|
+
# We'll do this whenever a class is derived from BasicObject
|
25
|
+
# Ideally, we'd do this by trapping Object.method_added
|
26
|
+
# and M.method_added for any module M included in Object or a submodule
|
27
|
+
# Seems really though to get right, but pull requests welcome ;-)
|
28
|
+
def inherited(sub)
|
29
|
+
BasicObject.class_eval do
|
30
|
+
(instance_methods - KEEP).each do |method|
|
31
|
+
if Object.method_defined?(method) && instance_method(method).owner == Object.instance_method(method).owner
|
32
|
+
undef_method method
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
data/lib/vcr/config.rb
CHANGED
@@ -16,12 +16,9 @@ module VCR
|
|
16
16
|
@default_cassette_options
|
17
17
|
end
|
18
18
|
|
19
|
-
|
20
|
-
def
|
21
|
-
@
|
22
|
-
defined_constants = [:FakeWeb, :WebMock].select { |c| Object.const_defined?(c) }
|
23
|
-
defined_constants[0].to_s.downcase.to_sym if defined_constants.size == 1
|
24
|
-
end
|
19
|
+
attr_reader :http_stubbing_libraries
|
20
|
+
def stub_with(*http_stubbing_libraries)
|
21
|
+
@http_stubbing_libraries = http_stubbing_libraries
|
25
22
|
end
|
26
23
|
|
27
24
|
def ignore_localhost=(value)
|
@@ -34,4 +31,4 @@ module VCR
|
|
34
31
|
end
|
35
32
|
end
|
36
33
|
end
|
37
|
-
end
|
34
|
+
end
|
data/lib/vcr/deprecations.rb
CHANGED
@@ -1,4 +1,18 @@
|
|
1
1
|
module VCR
|
2
|
+
class Config
|
3
|
+
class << self
|
4
|
+
def http_stubbing_library
|
5
|
+
warn "WARNING: `VCR::Config.http_stubbing_library` is deprecated. Use `VCR::Config.http_stubbing_libraries` instead."
|
6
|
+
@http_stubbing_libraries && @http_stubbing_libraries.first
|
7
|
+
end
|
8
|
+
|
9
|
+
def http_stubbing_library=(library)
|
10
|
+
warn "WARNING: `VCR::Config.http_stubbing_library = #{library.inspect}` is deprecated. Use `VCR::Config.stub_with #{library.inspect}` instead."
|
11
|
+
stub_with library
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
2
16
|
class Cassette
|
3
17
|
def allow_real_http_requests_to?(uri)
|
4
18
|
warn "WARNING: VCR::Cassette#allow_real_http_requests_to? is deprecated and should no longer be used."
|
@@ -1,38 +1,69 @@
|
|
1
1
|
module VCR
|
2
2
|
module HttpStubbingAdapters
|
3
|
-
autoload :FakeWeb,
|
4
|
-
autoload :
|
3
|
+
autoload :FakeWeb, 'vcr/http_stubbing_adapters/fakeweb'
|
4
|
+
autoload :MultiObjectProxy, 'vcr/http_stubbing_adapters/multi_object_proxy'
|
5
|
+
autoload :Typhoeus, 'vcr/http_stubbing_adapters/typhoeus'
|
6
|
+
autoload :WebMock, 'vcr/http_stubbing_adapters/webmock'
|
5
7
|
|
6
8
|
class UnsupportedRequestMatchAttributeError < ArgumentError; end
|
7
9
|
|
8
10
|
module Common
|
11
|
+
def self.add_vcr_info_to_exception_message(exception_klass)
|
12
|
+
exception_klass.class_eval do
|
13
|
+
def message
|
14
|
+
super + ". You can use VCR to automatically record this request and replay it later. " +
|
15
|
+
"For more details, visit the VCR wiki at: http://github.com/myronmarston/vcr/wiki"
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
9
20
|
def check_version!
|
10
21
|
version_too_low, version_too_high = compare_version
|
11
22
|
|
12
23
|
if version_too_low
|
13
|
-
raise "You are using #{library_name} #{version}. VCR requires version #{
|
24
|
+
raise "You are using #{library_name} #{version}. VCR requires version #{version_requirement}."
|
14
25
|
elsif version_too_high
|
15
|
-
warn "You are using #{library_name} #{version}. VCR is known to work with #{library_name}
|
26
|
+
warn "You are using #{library_name} #{version}. VCR is known to work with #{library_name} #{version_requirement}. It may not work with this version."
|
16
27
|
end
|
17
28
|
end
|
18
29
|
|
30
|
+
def library_name
|
31
|
+
@library_name ||= self.to_s.split('::').last
|
32
|
+
end
|
33
|
+
|
19
34
|
private
|
20
35
|
|
21
36
|
def compare_version
|
22
|
-
major, minor, patch =
|
23
|
-
|
37
|
+
major, minor, patch = parse_version(version)
|
38
|
+
min_major, min_minor, min_patch = parse_version(self::MINIMUM_VERSION)
|
39
|
+
max_major, max_minor = parse_version(self::MAXIMUM_VERSION)
|
24
40
|
|
25
|
-
return true, false if major <
|
26
|
-
return false, true if major >
|
41
|
+
return true, false if major < min_major
|
42
|
+
return false, true if major > max_major
|
27
43
|
|
28
|
-
return true, false if minor <
|
29
|
-
return false, true if minor >
|
44
|
+
return true, false if minor < min_minor
|
45
|
+
return false, true if minor > max_minor
|
30
46
|
|
31
|
-
return patch <
|
47
|
+
return patch < min_patch, false
|
32
48
|
end
|
33
49
|
|
34
|
-
def
|
35
|
-
|
50
|
+
def version_requirement
|
51
|
+
max_major, max_minor = parse_version(self::MAXIMUM_VERSION)
|
52
|
+
">= #{self::MINIMUM_VERSION}, < #{max_major}.#{max_minor + 1}"
|
53
|
+
end
|
54
|
+
|
55
|
+
def parse_version(version)
|
56
|
+
version.split('.').map { |v| v.to_i }
|
57
|
+
end
|
58
|
+
|
59
|
+
def grouped_responses(http_interactions, match_attributes)
|
60
|
+
responses = Hash.new { |h,k| h[k] = [] }
|
61
|
+
|
62
|
+
http_interactions.each do |i|
|
63
|
+
responses[i.request.matcher(match_attributes)] << i.response
|
64
|
+
end
|
65
|
+
|
66
|
+
responses
|
36
67
|
end
|
37
68
|
end
|
38
69
|
end
|
@@ -10,26 +10,31 @@ module VCR
|
|
10
10
|
UNSUPPORTED_REQUEST_MATCH_ATTRIBUTES = [:body, :headers].freeze
|
11
11
|
LOCALHOST_REGEX = %r|\Ahttps?://((\w+:)?\w+@)?(#{VCR::LOCALHOST_ALIASES.map { |a| Regexp.escape(a) }.join('|')})(:\d+)?/|i
|
12
12
|
|
13
|
-
|
13
|
+
MINIMUM_VERSION = '1.3.0'
|
14
|
+
MAXIMUM_VERSION = '1.3'
|
15
|
+
|
16
|
+
def http_connections_allowed=(value)
|
17
|
+
@http_connections_allowed = value
|
18
|
+
update_fakeweb_allow_net_connect
|
19
|
+
end
|
14
20
|
|
15
21
|
def http_connections_allowed?
|
16
22
|
!!::FakeWeb.allow_net_connect?("http://some.url/besides/localhost")
|
17
23
|
end
|
18
24
|
|
19
|
-
def
|
20
|
-
@
|
25
|
+
def ignore_localhost=(value)
|
26
|
+
@ignore_localhost = value
|
21
27
|
update_fakeweb_allow_net_connect
|
22
28
|
end
|
23
29
|
|
30
|
+
def ignore_localhost?
|
31
|
+
!!@ignore_localhost
|
32
|
+
end
|
33
|
+
|
24
34
|
def stub_requests(http_interactions, match_attributes)
|
25
35
|
validate_match_attributes(match_attributes)
|
26
|
-
requests = Hash.new { |h,k| h[k] = [] }
|
27
|
-
|
28
|
-
http_interactions.each do |i|
|
29
|
-
requests[i.request.matcher(match_attributes)] << i.response
|
30
|
-
end
|
31
36
|
|
32
|
-
|
37
|
+
grouped_responses(http_interactions, match_attributes).each do |request_matcher, responses|
|
33
38
|
::FakeWeb.register_uri(
|
34
39
|
request_matcher.method || :any,
|
35
40
|
request_matcher.uri,
|
@@ -48,22 +53,13 @@ module VCR
|
|
48
53
|
|
49
54
|
def request_stubbed?(request, match_attributes)
|
50
55
|
validate_match_attributes(match_attributes)
|
51
|
-
|
56
|
+
!!::FakeWeb.registered_uri?(request.method, request.uri)
|
52
57
|
end
|
53
58
|
|
54
59
|
def request_uri(net_http, request)
|
55
60
|
::FakeWeb::Utility.request_uri_as_string(net_http, request)
|
56
61
|
end
|
57
62
|
|
58
|
-
def ignore_localhost=(value)
|
59
|
-
@ignore_localhost = value
|
60
|
-
update_fakeweb_allow_net_connect
|
61
|
-
end
|
62
|
-
|
63
|
-
def ignore_localhost?
|
64
|
-
@ignore_localhost
|
65
|
-
end
|
66
|
-
|
67
63
|
private
|
68
64
|
|
69
65
|
def version
|
@@ -101,12 +97,5 @@ module VCR
|
|
101
97
|
end
|
102
98
|
end
|
103
99
|
|
104
|
-
|
105
|
-
|
106
|
-
class NetConnectNotAllowedError
|
107
|
-
def message
|
108
|
-
super + ". You can use VCR to automatically record this request and replay it later. For more details, see the VCR README at: http://github.com/myronmarston/vcr/tree/v#{VCR.version}"
|
109
|
-
end
|
110
|
-
end
|
111
|
-
end
|
112
|
-
end
|
100
|
+
VCR::HttpStubbingAdapters::Common.add_vcr_info_to_exception_message(FakeWeb::NetConnectNotAllowedError)
|
101
|
+
|
@@ -0,0 +1,43 @@
|
|
1
|
+
module VCR
|
2
|
+
module HttpStubbingAdapters
|
3
|
+
class MultiObjectProxy < defined?(::BasicObject) ? ::BasicObject : VCR::BasicObject
|
4
|
+
|
5
|
+
def self.for(*objects)
|
6
|
+
return objects.first if objects.size == 1
|
7
|
+
new(*objects)
|
8
|
+
end
|
9
|
+
|
10
|
+
attr_reader :proxied_objects
|
11
|
+
|
12
|
+
def initialize(*objects)
|
13
|
+
::Kernel.raise ::ArgumentError.new("You must pass at least one object to proxy to") if objects.empty?
|
14
|
+
::Kernel.raise ::ArgumentError.new("Cannot proxy to nil") if objects.any? { |o| o.nil? }
|
15
|
+
|
16
|
+
@proxied_objects = objects
|
17
|
+
end
|
18
|
+
|
19
|
+
def respond_to?(message)
|
20
|
+
proxied_objects.any? { |o| o.respond_to?(message) }
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def method_missing(name, *args)
|
26
|
+
responding_proxied_objects = proxied_objects.select { |o| o.respond_to?(name) }
|
27
|
+
return super if responding_proxied_objects.empty?
|
28
|
+
|
29
|
+
uniq_return_vals = responding_proxied_objects.map { |o| o.__send__(name, *args) }.uniq
|
30
|
+
|
31
|
+
return nil unless method_return_val_important?(name)
|
32
|
+
return uniq_return_vals.first if uniq_return_vals.size == 1
|
33
|
+
|
34
|
+
::Kernel.raise "The proxied objects returned different values for calls to #{name}: #{uniq_return_vals.inspect}"
|
35
|
+
end
|
36
|
+
|
37
|
+
def method_return_val_important?(method_name)
|
38
|
+
method_name == :request_uri || method_name.to_s =~ /\?$/
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
@@ -0,0 +1,101 @@
|
|
1
|
+
require 'typhoeus'
|
2
|
+
|
3
|
+
module VCR
|
4
|
+
module HttpStubbingAdapters
|
5
|
+
module Typhoeus
|
6
|
+
include VCR::HttpStubbingAdapters::Common
|
7
|
+
extend self
|
8
|
+
|
9
|
+
MINIMUM_VERSION = '0.2.0'
|
10
|
+
MAXIMUM_VERSION = '0.2'
|
11
|
+
|
12
|
+
def http_connections_allowed=(value)
|
13
|
+
::Typhoeus::Hydra.allow_net_connect = value
|
14
|
+
end
|
15
|
+
|
16
|
+
def http_connections_allowed?
|
17
|
+
!!::Typhoeus::Hydra.allow_net_connect?
|
18
|
+
end
|
19
|
+
|
20
|
+
def ignore_localhost=(value)
|
21
|
+
::Typhoeus::Hydra.ignore_localhost = value
|
22
|
+
end
|
23
|
+
|
24
|
+
def ignore_localhost?
|
25
|
+
!!::Typhoeus::Hydra.ignore_localhost?
|
26
|
+
end
|
27
|
+
|
28
|
+
def stub_requests(http_interactions, match_attributes)
|
29
|
+
grouped_responses(http_interactions, match_attributes).each do |request_matcher, responses|
|
30
|
+
::Typhoeus::Hydra.stub(
|
31
|
+
request_matcher.method || :any,
|
32
|
+
request_matcher.uri,
|
33
|
+
request_hash(request_matcher)
|
34
|
+
).and_return(
|
35
|
+
responses.map do |response|
|
36
|
+
::Typhoeus::Response.new(
|
37
|
+
:code => response.status.code,
|
38
|
+
:body => response.body,
|
39
|
+
:headers_hash => response.headers
|
40
|
+
)
|
41
|
+
end
|
42
|
+
)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def create_stubs_checkpoint(checkpoint_name)
|
47
|
+
checkpoints[checkpoint_name] = ::Typhoeus::Hydra.stubs.dup
|
48
|
+
end
|
49
|
+
|
50
|
+
def restore_stubs_checkpoint(checkpoint_name)
|
51
|
+
::Typhoeus::Hydra.stubs = checkpoints.delete(checkpoint_name)
|
52
|
+
end
|
53
|
+
|
54
|
+
private
|
55
|
+
|
56
|
+
def version
|
57
|
+
::Typhoeus::VERSION
|
58
|
+
end
|
59
|
+
|
60
|
+
def checkpoints
|
61
|
+
@checkpoints ||= {}
|
62
|
+
end
|
63
|
+
|
64
|
+
def request_hash(request_matcher)
|
65
|
+
hash = {}
|
66
|
+
|
67
|
+
hash[:body] = request_matcher.body if request_matcher.match_requests_on?(:body)
|
68
|
+
hash[:headers] = request_matcher.headers if request_matcher.match_requests_on?(:headers)
|
69
|
+
|
70
|
+
hash
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
Typhoeus::Hydra.after_request_before_on_complete do |request|
|
77
|
+
unless request.response.mock?
|
78
|
+
http_interaction = VCR::HTTPInteraction.new(
|
79
|
+
VCR::Request.new(
|
80
|
+
request.method,
|
81
|
+
request.url,
|
82
|
+
request.body,
|
83
|
+
request.headers
|
84
|
+
),
|
85
|
+
VCR::Response.new(
|
86
|
+
VCR::ResponseStatus.new(
|
87
|
+
request.response.code,
|
88
|
+
request.response.status_message
|
89
|
+
),
|
90
|
+
request.response.headers_hash,
|
91
|
+
request.response.body,
|
92
|
+
request.response.http_version
|
93
|
+
)
|
94
|
+
)
|
95
|
+
|
96
|
+
VCR.record_http_interaction(http_interaction)
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
VCR::HttpStubbingAdapters::Common.add_vcr_info_to_exception_message(Typhoeus::Hydra::NetConnectNotAllowedError)
|
101
|
+
|