httpx 0.15.3 → 0.17.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (109) hide show
  1. checksums.yaml +4 -4
  2. data/doc/release_notes/0_15_4.md +5 -0
  3. data/doc/release_notes/0_16_0.md +93 -0
  4. data/doc/release_notes/0_16_1.md +5 -0
  5. data/doc/release_notes/0_17_0.md +49 -0
  6. data/lib/httpx/adapters/faraday.rb +3 -11
  7. data/lib/httpx/adapters/webmock.rb +2 -2
  8. data/lib/httpx/buffer.rb +1 -1
  9. data/lib/httpx/callbacks.rb +1 -1
  10. data/lib/httpx/chainable.rb +15 -8
  11. data/lib/httpx/connection/http1.rb +18 -10
  12. data/lib/httpx/connection/http2.rb +14 -21
  13. data/lib/httpx/connection.rb +6 -7
  14. data/lib/httpx/errors.rb +11 -11
  15. data/lib/httpx/headers.rb +1 -1
  16. data/lib/httpx/io/ssl.rb +2 -2
  17. data/lib/httpx/io/tls.rb +1 -1
  18. data/lib/httpx/options.rb +108 -81
  19. data/lib/httpx/parser/http1.rb +11 -7
  20. data/lib/httpx/plugins/aws_sigv4.rb +10 -9
  21. data/lib/httpx/plugins/compression.rb +12 -11
  22. data/lib/httpx/plugins/cookies/cookie.rb +4 -2
  23. data/lib/httpx/plugins/cookies/jar.rb +20 -1
  24. data/lib/httpx/plugins/cookies.rb +20 -7
  25. data/lib/httpx/plugins/digest_authentication.rb +19 -15
  26. data/lib/httpx/plugins/expect.rb +19 -15
  27. data/lib/httpx/plugins/follow_redirects.rb +9 -9
  28. data/lib/httpx/plugins/grpc/call.rb +4 -1
  29. data/lib/httpx/plugins/grpc.rb +73 -47
  30. data/lib/httpx/plugins/h2c.rb +7 -3
  31. data/lib/httpx/plugins/multipart/decoder.rb +187 -0
  32. data/lib/httpx/plugins/multipart/mime_type_detector.rb +3 -3
  33. data/lib/httpx/plugins/multipart/part.rb +2 -2
  34. data/lib/httpx/plugins/multipart.rb +14 -0
  35. data/lib/httpx/plugins/ntlm_authentication.rb +12 -10
  36. data/lib/httpx/plugins/proxy/socks4.rb +2 -1
  37. data/lib/httpx/plugins/proxy/socks5.rb +2 -1
  38. data/lib/httpx/plugins/proxy/ssh.rb +20 -13
  39. data/lib/httpx/plugins/proxy.rb +10 -10
  40. data/lib/httpx/plugins/retries.rb +25 -21
  41. data/lib/httpx/plugins/stream.rb +2 -3
  42. data/lib/httpx/plugins/upgrade.rb +7 -6
  43. data/lib/httpx/registry.rb +2 -2
  44. data/lib/httpx/request.rb +10 -19
  45. data/lib/httpx/resolver/https.rb +0 -2
  46. data/lib/httpx/resolver/native.rb +15 -3
  47. data/lib/httpx/resolver/resolver_mixin.rb +2 -1
  48. data/lib/httpx/response.rb +72 -38
  49. data/lib/httpx/selector.rb +6 -7
  50. data/lib/httpx/session.rb +34 -21
  51. data/lib/httpx/session2.rb +23 -0
  52. data/lib/httpx/transcoder/body.rb +1 -1
  53. data/lib/httpx/transcoder/chunker.rb +2 -1
  54. data/lib/httpx/transcoder/form.rb +20 -0
  55. data/lib/httpx/transcoder/json.rb +12 -0
  56. data/lib/httpx/transcoder.rb +62 -1
  57. data/lib/httpx/utils.rb +2 -2
  58. data/lib/httpx/version.rb +1 -1
  59. data/lib/httpx.rb +6 -3
  60. data/sig/buffer.rbs +3 -1
  61. data/sig/chainable.rbs +30 -29
  62. data/sig/connection/http1.rbs +11 -5
  63. data/sig/connection/http2.rbs +16 -5
  64. data/sig/connection.rbs +23 -11
  65. data/sig/errors.rbs +35 -1
  66. data/sig/headers.rbs +20 -19
  67. data/sig/httpx.rbs +4 -1
  68. data/sig/loggable.rbs +3 -1
  69. data/sig/options.rbs +45 -34
  70. data/sig/parser/http1.rbs +3 -3
  71. data/sig/plugins/authentication.rbs +1 -1
  72. data/sig/plugins/aws_sdk_authentication.rbs +5 -1
  73. data/sig/plugins/aws_sigv4.rbs +13 -5
  74. data/sig/plugins/basic_authentication.rbs +1 -1
  75. data/sig/plugins/compression.rbs +4 -6
  76. data/sig/plugins/cookies/cookie.rbs +5 -7
  77. data/sig/plugins/cookies/jar.rbs +9 -10
  78. data/sig/plugins/cookies.rbs +4 -5
  79. data/sig/plugins/digest_authentication.rbs +2 -3
  80. data/sig/plugins/expect.rbs +2 -4
  81. data/sig/plugins/follow_redirects.rbs +3 -5
  82. data/sig/plugins/grpc.rbs +4 -7
  83. data/sig/plugins/h2c.rbs +0 -2
  84. data/sig/plugins/multipart.rbs +64 -10
  85. data/sig/plugins/ntlm_authentication.rbs +2 -3
  86. data/sig/plugins/persistent.rbs +3 -8
  87. data/sig/plugins/proxy/ssh.rbs +4 -4
  88. data/sig/plugins/proxy.rbs +13 -13
  89. data/sig/plugins/push_promise.rbs +0 -2
  90. data/sig/plugins/retries.rbs +4 -8
  91. data/sig/plugins/stream.rbs +1 -1
  92. data/sig/plugins/upgrade.rbs +2 -3
  93. data/sig/pool.rbs +1 -2
  94. data/sig/registry.rbs +1 -1
  95. data/sig/request.rbs +11 -8
  96. data/sig/resolver/native.rbs +12 -6
  97. data/sig/resolver/resolver_mixin.rbs +4 -5
  98. data/sig/resolver/system.rbs +2 -0
  99. data/sig/resolver.rbs +7 -0
  100. data/sig/response.rbs +24 -12
  101. data/sig/selector.rbs +11 -9
  102. data/sig/session.rbs +22 -23
  103. data/sig/transcoder/body.rbs +6 -1
  104. data/sig/transcoder/chunker.rbs +8 -2
  105. data/sig/transcoder/form.rbs +3 -1
  106. data/sig/transcoder/json.rbs +2 -0
  107. data/sig/transcoder.rbs +13 -5
  108. data/sig/utils.rbs +2 -0
  109. metadata +12 -2
@@ -24,15 +24,19 @@ module HTTPX
24
24
  def call(connection, request, response)
25
25
  connection.upgrade_to_h2c(request, response)
26
26
  end
27
+
28
+ def extra_options(options)
29
+ options.merge(max_concurrent_requests: 1)
30
+ end
27
31
  end
28
32
 
29
33
  module InstanceMethods
30
- def send_requests(*requests, options)
34
+ def send_requests(*requests)
31
35
  upgrade_request, *remainder = requests
32
36
 
33
37
  return super unless VALID_H2C_VERBS.include?(upgrade_request.verb) && upgrade_request.scheme == "http"
34
38
 
35
- connection = pool.find_connection(upgrade_request.uri, @options.merge(options))
39
+ connection = pool.find_connection(upgrade_request.uri, upgrade_request.options)
36
40
 
37
41
  return super if connection && connection.upgrade_protocol == :h2c
38
42
 
@@ -42,7 +46,7 @@ module HTTPX
42
46
  upgrade_request.headers["upgrade"] = "h2c"
43
47
  upgrade_request.headers["http2-settings"] = HTTP2Next::Client.settings_header(upgrade_request.options.http2_settings)
44
48
 
45
- super(upgrade_request, *remainder, options.merge(max_concurrent_requests: 1))
49
+ super(upgrade_request, *remainder)
46
50
  end
47
51
  end
48
52
 
@@ -0,0 +1,187 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "tempfile"
4
+ require "delegate"
5
+
6
+ module HTTPX::Plugins
7
+ module Multipart
8
+ using HTTPX::RegexpExtensions unless Regexp.method_defined?(:match?)
9
+
10
+ CRLF = "\r\n"
11
+
12
+ class FilePart < SimpleDelegator
13
+ attr_reader :original_filename, :content_type
14
+
15
+ def initialize(filename, content_type)
16
+ @original_filename = filename
17
+ @content_type = content_type
18
+ @file = Tempfile.new("httpx", encoding: Encoding::BINARY, mode: File::RDWR)
19
+ super(@file)
20
+ end
21
+ end
22
+
23
+ TOKEN = %r{[^\s()<>,;:\\"/\[\]?=]+}.freeze
24
+ VALUE = /"(?:\\"|[^"])*"|#{TOKEN}/.freeze
25
+ CONDISP = /Content-Disposition:\s*#{TOKEN}\s*/i.freeze
26
+ BROKEN_QUOTED = /^#{CONDISP}.*;\s*filename="(.*?)"(?:\s*$|\s*;\s*#{TOKEN}=)/i.freeze
27
+ BROKEN_UNQUOTED = /^#{CONDISP}.*;\s*filename=(#{TOKEN})/i.freeze
28
+ MULTIPART_CONTENT_TYPE = /Content-Type: (.*)#{CRLF}/ni.freeze
29
+ MULTIPART_CONTENT_DISPOSITION = /Content-Disposition:.*;\s*name=(#{VALUE})/ni.freeze
30
+ MULTIPART_CONTENT_ID = /Content-ID:\s*([^#{CRLF}]*)/ni.freeze
31
+ # Updated definitions from RFC 2231
32
+ ATTRIBUTE_CHAR = %r{[^ \t\v\n\r)(><@,;:\\"/\[\]?='*%]}.freeze
33
+ ATTRIBUTE = /#{ATTRIBUTE_CHAR}+/.freeze
34
+ SECTION = /\*[0-9]+/.freeze
35
+ REGULAR_PARAMETER_NAME = /#{ATTRIBUTE}#{SECTION}?/.freeze
36
+ REGULAR_PARAMETER = /(#{REGULAR_PARAMETER_NAME})=(#{VALUE})/.freeze
37
+ EXTENDED_OTHER_NAME = /#{ATTRIBUTE}\*[1-9][0-9]*\*/.freeze
38
+ EXTENDED_OTHER_VALUE = /%[0-9a-fA-F]{2}|#{ATTRIBUTE_CHAR}/.freeze
39
+ EXTENDED_OTHER_PARAMETER = /(#{EXTENDED_OTHER_NAME})=(#{EXTENDED_OTHER_VALUE}*)/.freeze
40
+ EXTENDED_INITIAL_NAME = /#{ATTRIBUTE}(?:\*0)?\*/.freeze
41
+ EXTENDED_INITIAL_VALUE = /[a-zA-Z0-9\-]*'[a-zA-Z0-9\-]*'#{EXTENDED_OTHER_VALUE}*/.freeze
42
+ EXTENDED_INITIAL_PARAMETER = /(#{EXTENDED_INITIAL_NAME})=(#{EXTENDED_INITIAL_VALUE})/.freeze
43
+ EXTENDED_PARAMETER = /#{EXTENDED_INITIAL_PARAMETER}|#{EXTENDED_OTHER_PARAMETER}/.freeze
44
+ DISPPARM = /;\s*(?:#{REGULAR_PARAMETER}|#{EXTENDED_PARAMETER})\s*/.freeze
45
+ RFC2183 = /^#{CONDISP}(#{DISPPARM})+$/i.freeze
46
+
47
+ class Decoder
48
+ BOUNDARY_RE = /;\s*boundary=([^;]+)/i.freeze
49
+ WINDOW_SIZE = 2 << 14
50
+
51
+ def initialize(response)
52
+ @boundary = begin
53
+ m = response.headers["content-type"].to_s[BOUNDARY_RE, 1]
54
+ raise Error, "no boundary declared in content-type header" unless m
55
+
56
+ m.strip
57
+ end
58
+ @buffer = "".b
59
+ @parts = {}
60
+ @intermediate_boundary = "--#{@boundary}"
61
+ @state = :idle
62
+ end
63
+
64
+ def call(response, _)
65
+ response.body.each do |chunk|
66
+ @buffer << chunk
67
+
68
+ parse
69
+ end
70
+
71
+ raise Error, "invalid or unsupported multipart format" unless @buffer.empty?
72
+
73
+ @parts
74
+ end
75
+
76
+ private
77
+
78
+ def parse
79
+ case @state
80
+ when :idle
81
+ raise Error, "payload does not start with boundary" unless @buffer.start_with?("#{@intermediate_boundary}#{CRLF}")
82
+
83
+ @buffer = @buffer.byteslice(@intermediate_boundary.bytesize + 2..-1)
84
+
85
+ @state = :part_header
86
+ when :part_header
87
+ idx = @buffer.index("#{CRLF}#{CRLF}")
88
+
89
+ # raise Error, "couldn't parse part headers" unless idx
90
+ return unless idx
91
+
92
+ head = @buffer.byteslice(0..idx + 4 - 1)
93
+
94
+ @buffer = @buffer.byteslice(head.bytesize..-1)
95
+
96
+ content_type = head[MULTIPART_CONTENT_TYPE, 1]
97
+ if (name = head[MULTIPART_CONTENT_DISPOSITION, 1])
98
+ name = /\A"(.*)"\Z/ =~ name ? Regexp.last_match(1) : name.dup
99
+ name.gsub!(/\\(.)/, "\\1")
100
+ name
101
+ else
102
+ name = head[MULTIPART_CONTENT_ID, 1]
103
+ end
104
+
105
+ filename = get_filename(head)
106
+
107
+ name = filename || +"#{content_type || "text/plain"}[]" if name.nil? || name.empty?
108
+
109
+ @current = name
110
+
111
+ @parts[name] = if filename
112
+ FilePart.new(filename, content_type)
113
+ else
114
+ "".b
115
+ end
116
+
117
+ @state = :part_body
118
+ when :part_body
119
+ part = @parts[@current]
120
+
121
+ body_separator = if part.is_a?(FilePart)
122
+ "#{CRLF}#{CRLF}"
123
+ else
124
+ CRLF
125
+ end
126
+ idx = @buffer.index(body_separator)
127
+
128
+ if idx
129
+ payload = @buffer.byteslice(0..idx - 1)
130
+ @buffer = @buffer.byteslice(idx + body_separator.bytesize..-1)
131
+ part << payload
132
+ part.rewind if part.respond_to?(:rewind)
133
+ @state = :parse_boundary
134
+ else
135
+ part << @buffer
136
+ @buffer.clear
137
+ end
138
+ when :parse_boundary
139
+ raise Error, "payload does not start with boundary" unless @buffer.start_with?(@intermediate_boundary)
140
+
141
+ @buffer = @buffer.byteslice(@intermediate_boundary.bytesize..-1)
142
+
143
+ if @buffer == "--"
144
+ @buffer.clear
145
+ @state = :done
146
+ return
147
+ elsif @buffer.start_with?(CRLF)
148
+ @buffer = @buffer.byteslice(2..-1)
149
+ @state = :part_header
150
+ else
151
+ return
152
+ end
153
+ when :done
154
+ raise Error, "parsing should have been over by now"
155
+ end until @buffer.empty?
156
+ end
157
+
158
+ def get_filename(head)
159
+ filename = nil
160
+ case head
161
+ when RFC2183
162
+ params = Hash[*head.scan(DISPPARM).flat_map(&:compact)]
163
+
164
+ if (filename = params["filename"])
165
+ filename = Regexp.last_match(1) if filename =~ /^"(.*)"$/
166
+ elsif (filename = params["filename*"])
167
+ encoding, _, filename = filename.split("'", 3)
168
+ end
169
+ when BROKEN_QUOTED, BROKEN_UNQUOTED
170
+ filename = Regexp.last_match(1)
171
+ end
172
+
173
+ return unless filename
174
+
175
+ filename = URI::DEFAULT_PARSER.unescape(filename) if filename.scan(/%.?.?/).all? { |s| /%[0-9a-fA-F]{2}/.match?(s) }
176
+
177
+ filename.scrub!
178
+
179
+ filename = filename.gsub(/\\(.)/, '\1') unless /\\[^\\"]/.match?(filename)
180
+
181
+ filename.force_encoding ::Encoding.find(encoding) if encoding
182
+
183
+ filename
184
+ end
185
+ end
186
+ end
187
+ end
@@ -17,7 +17,7 @@ module HTTPX
17
17
 
18
18
  elsif defined?(MimeMagic)
19
19
 
20
- def call(file, *)
20
+ def call(file, _)
21
21
  mime = MimeMagic.by_magic(file)
22
22
  mime.type if mime
23
23
  end
@@ -25,7 +25,7 @@ module HTTPX
25
25
  elsif system("which file", out: File::NULL)
26
26
  require "open3"
27
27
 
28
- def call(file, *)
28
+ def call(file, _)
29
29
  return if file.eof? # file command returns "application/x-empty" for empty files
30
30
 
31
31
  Open3.popen3(*%w[file --mime-type --brief -]) do |stdin, stdout, stderr, thread|
@@ -56,7 +56,7 @@ module HTTPX
56
56
 
57
57
  else
58
58
 
59
- def call(*); end
59
+ def call(_, _); end
60
60
 
61
61
  end
62
62
  end
@@ -8,7 +8,7 @@ module HTTPX
8
8
  def call(value)
9
9
  # take out specialized objects of the way
10
10
  if value.respond_to?(:filename) && value.respond_to?(:content_type) && value.respond_to?(:read)
11
- return [value, value.content_type, value.filename]
11
+ return value, value.content_type, value.filename
12
12
  end
13
13
 
14
14
  content_type = filename = nil
@@ -19,7 +19,7 @@ module HTTPX
19
19
  value = value[:body]
20
20
  end
21
21
 
22
- value = value.open(:binmode => true) if Object.const_defined?(:Pathname) && value.is_a?(Pathname)
22
+ value = value.open(File::RDONLY) if Object.const_defined?(:Pathname) && value.is_a?(Pathname)
23
23
 
24
24
  if value.is_a?(File)
25
25
  filename ||= File.basename(value.path)
@@ -36,6 +36,7 @@ module HTTPX
36
36
  end
37
37
  # :nocov:
38
38
  require "httpx/plugins/multipart/encoder"
39
+ require "httpx/plugins/multipart/decoder"
39
40
  require "httpx/plugins/multipart/part"
40
41
  require "httpx/plugins/multipart/mime_type_detector"
41
42
  end
@@ -56,6 +57,19 @@ module HTTPX
56
57
  end
57
58
  end
58
59
 
60
+ def decode(response)
61
+ content_type = response.content_type.mime_type
62
+
63
+ case content_type
64
+ when "application/x-www-form-urlencoded"
65
+ Transcoder::Form.decode(response)
66
+ when "multipart/form-data"
67
+ Decoder.new(response)
68
+ else
69
+ raise Error, "invalid form mime type (#{content_type})"
70
+ end
71
+ end
72
+
59
73
  def multipart?(data)
60
74
  data.any? do |_, v|
61
75
  MULTIPART_VALUE_COND.call(v) ||
@@ -15,13 +15,15 @@ module HTTPX
15
15
  end
16
16
 
17
17
  def extra_options(options)
18
- Class.new(options.class) do
19
- def_option(:ntlm, <<-OUT)
20
- raise Error, ":ntlm must be a #{NTLMParams}" unless value.is_a?(#{NTLMParams})
18
+ options.merge(max_concurrent_requests: 1)
19
+ end
20
+ end
21
+
22
+ module OptionsMethods
23
+ def option_ntlm(value)
24
+ raise TypeError, ":ntlm must be a #{NTLMParams}" unless value.is_a?(NTLMParams)
21
25
 
22
- value
23
- OUT
24
- end.new(options).merge(max_concurrent_requests: 1)
26
+ value
25
27
  end
26
28
  end
27
29
 
@@ -32,13 +34,13 @@ module HTTPX
32
34
 
33
35
  alias_method :ntlm_auth, :ntlm_authentication
34
36
 
35
- def send_requests(*requests, options)
37
+ def send_requests(*requests)
36
38
  requests.flat_map do |request|
37
39
  ntlm = request.options.ntlm
38
40
 
39
41
  if ntlm
40
42
  request.headers["authorization"] = "NTLM #{NTLM.negotiate(domain: ntlm.domain).to_base64}"
41
- probe_response = wrap { super(request, options).first }
43
+ probe_response = wrap { super(request).first }
42
44
 
43
45
  if !probe_response.is_a?(ErrorResponse) && probe_response.status == 401 &&
44
46
  probe_response.headers.key?("www-authenticate") &&
@@ -50,12 +52,12 @@ module HTTPX
50
52
  request.transition(:idle)
51
53
 
52
54
  request.headers["authorization"] = "NTLM #{ntlm_challenge}"
53
- super(request, options)
55
+ super(request)
54
56
  else
55
57
  probe_response
56
58
  end
57
59
  else
58
- super(request, options)
60
+ super(request)
59
61
  end
60
62
  end
61
63
  end
@@ -4,7 +4,8 @@ require "resolv"
4
4
  require "ipaddr"
5
5
 
6
6
  module HTTPX
7
- Socks4Error = Class.new(Error)
7
+ class Socks4Error < Error; end
8
+
8
9
  module Plugins
9
10
  module Proxy
10
11
  module Socks4
@@ -1,7 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module HTTPX
4
- Socks5Error = Class.new(Error)
4
+ class Socks5Error < Error; end
5
+
5
6
  module Plugins
6
7
  module Proxy
7
8
  module Socks5
@@ -6,23 +6,27 @@ module HTTPX
6
6
  module Plugins
7
7
  module Proxy
8
8
  module SSH
9
- def self.load_dependencies(*)
10
- require "net/ssh/gateway"
9
+ class << self
10
+ def load_dependencies(*)
11
+ require "net/ssh/gateway"
12
+ end
11
13
  end
12
14
 
13
- def self.extra_options(options)
14
- Class.new(options.class) do
15
- def_option(:proxy, <<-OUT)
16
- Hash[value]
17
- OUT
18
- end.new(options)
15
+ module OptionsMethods
16
+ def option_proxy(value)
17
+ Hash[value]
18
+ end
19
19
  end
20
20
 
21
21
  module InstanceMethods
22
- private
22
+ def request(*args, **options)
23
+ raise ArgumentError, "must perform at least one request" if args.empty?
24
+
25
+ requests = args.first.is_a?(Request) ? args : build_requests(*args, options)
23
26
 
24
- def send_requests(*requests, options)
25
- request_options = @options.merge(options)
27
+ request = requests.first or return super
28
+
29
+ request_options = request.options
26
30
 
27
31
  return super unless request_options.proxy
28
32
 
@@ -37,18 +41,21 @@ module HTTPX
37
41
  if request_options.debug
38
42
  ssh_options[:verbose] = request_options.debug_level == 2 ? :debug : :info
39
43
  end
44
+
40
45
  request_uri = URI(requests.first.uri)
41
46
  @_gateway = Net::SSH::Gateway.new(ssh_uri.host, ssh_username, ssh_options)
42
47
  begin
43
48
  @_gateway.open(request_uri.host, request_uri.port) do |local_port|
44
49
  io = build_gateway_socket(local_port, request_uri, request_options)
45
- super(*requests, options.merge(io: io))
50
+ super(*args, **options.merge(io: io))
46
51
  end
47
52
  ensure
48
53
  @_gateway.shutdown!
49
54
  end
50
55
  end
51
56
 
57
+ private
58
+
52
59
  def build_gateway_socket(port, request_uri, options)
53
60
  case request_uri.scheme
54
61
  when "https"
@@ -65,7 +72,7 @@ module HTTPX
65
72
  when "http"
66
73
  TCPSocket.open("localhost", port)
67
74
  else
68
- raise Error, "unexpected scheme: #{request_uri.scheme}"
75
+ raise TypeError, "unexpected scheme: #{request_uri.scheme}"
69
76
  end
70
77
  end
71
78
  end
@@ -5,7 +5,8 @@ require "ipaddr"
5
5
  require "forwardable"
6
6
 
7
7
  module HTTPX
8
- HTTPProxyError = Class.new(Error)
8
+ class HTTPProxyError < Error; end
9
+
9
10
  module Plugins
10
11
  #
11
12
  # This plugin adds support for proxies. It ships with support for:
@@ -64,13 +65,11 @@ module HTTPX
64
65
  klass.plugin(:"proxy/socks4")
65
66
  klass.plugin(:"proxy/socks5")
66
67
  end
68
+ end
67
69
 
68
- def extra_options(options)
69
- Class.new(options.class) do
70
- def_option(:proxy, <<-OUT)
71
- value.is_a?(#{Parameters}) ? value : Hash[value]
72
- OUT
73
- end.new(options)
70
+ module OptionsMethods
71
+ def option_proxy(value)
72
+ value.is_a?(Parameters) ? value : Hash[value]
74
73
  end
75
74
  end
76
75
 
@@ -138,10 +137,11 @@ module HTTPX
138
137
  def __proxy_error?(response)
139
138
  error = response.error
140
139
  case error
141
- when ResolveError
140
+ when NativeResolveError
142
141
  # failed resolving proxy domain
143
- proxy_uri = error.connection.options.proxy.uri
144
- proxy_uri.to_s == @_proxy_uris.first
142
+ error.connection.origin.to_s == @_proxy_uris.first
143
+ when ResolveError
144
+ error.message.end_with?(@_proxy_uris.first)
145
145
  when *PROXY_ERRORS
146
146
  # timeout errors connecting to proxy
147
147
  true