httpx 0.15.3 → 0.17.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.
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