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.
- checksums.yaml +4 -4
- data/doc/release_notes/0_15_4.md +5 -0
- data/doc/release_notes/0_16_0.md +93 -0
- data/doc/release_notes/0_16_1.md +5 -0
- data/doc/release_notes/0_17_0.md +49 -0
- data/lib/httpx/adapters/faraday.rb +3 -11
- data/lib/httpx/adapters/webmock.rb +2 -2
- data/lib/httpx/buffer.rb +1 -1
- data/lib/httpx/callbacks.rb +1 -1
- data/lib/httpx/chainable.rb +15 -8
- data/lib/httpx/connection/http1.rb +18 -10
- data/lib/httpx/connection/http2.rb +14 -21
- data/lib/httpx/connection.rb +6 -7
- data/lib/httpx/errors.rb +11 -11
- data/lib/httpx/headers.rb +1 -1
- data/lib/httpx/io/ssl.rb +2 -2
- data/lib/httpx/io/tls.rb +1 -1
- data/lib/httpx/options.rb +108 -81
- data/lib/httpx/parser/http1.rb +11 -7
- data/lib/httpx/plugins/aws_sigv4.rb +10 -9
- data/lib/httpx/plugins/compression.rb +12 -11
- data/lib/httpx/plugins/cookies/cookie.rb +4 -2
- data/lib/httpx/plugins/cookies/jar.rb +20 -1
- data/lib/httpx/plugins/cookies.rb +20 -7
- data/lib/httpx/plugins/digest_authentication.rb +19 -15
- data/lib/httpx/plugins/expect.rb +19 -15
- data/lib/httpx/plugins/follow_redirects.rb +9 -9
- data/lib/httpx/plugins/grpc/call.rb +4 -1
- data/lib/httpx/plugins/grpc.rb +73 -47
- data/lib/httpx/plugins/h2c.rb +7 -3
- data/lib/httpx/plugins/multipart/decoder.rb +187 -0
- data/lib/httpx/plugins/multipart/mime_type_detector.rb +3 -3
- data/lib/httpx/plugins/multipart/part.rb +2 -2
- data/lib/httpx/plugins/multipart.rb +14 -0
- data/lib/httpx/plugins/ntlm_authentication.rb +12 -10
- data/lib/httpx/plugins/proxy/socks4.rb +2 -1
- data/lib/httpx/plugins/proxy/socks5.rb +2 -1
- data/lib/httpx/plugins/proxy/ssh.rb +20 -13
- data/lib/httpx/plugins/proxy.rb +10 -10
- data/lib/httpx/plugins/retries.rb +25 -21
- data/lib/httpx/plugins/stream.rb +2 -3
- data/lib/httpx/plugins/upgrade.rb +7 -6
- data/lib/httpx/registry.rb +2 -2
- data/lib/httpx/request.rb +10 -19
- data/lib/httpx/resolver/https.rb +0 -2
- data/lib/httpx/resolver/native.rb +15 -3
- data/lib/httpx/resolver/resolver_mixin.rb +2 -1
- data/lib/httpx/response.rb +72 -38
- data/lib/httpx/selector.rb +6 -7
- data/lib/httpx/session.rb +34 -21
- data/lib/httpx/session2.rb +23 -0
- data/lib/httpx/transcoder/body.rb +1 -1
- data/lib/httpx/transcoder/chunker.rb +2 -1
- data/lib/httpx/transcoder/form.rb +20 -0
- data/lib/httpx/transcoder/json.rb +12 -0
- data/lib/httpx/transcoder.rb +62 -1
- data/lib/httpx/utils.rb +2 -2
- data/lib/httpx/version.rb +1 -1
- data/lib/httpx.rb +6 -3
- data/sig/buffer.rbs +3 -1
- data/sig/chainable.rbs +30 -29
- data/sig/connection/http1.rbs +11 -5
- data/sig/connection/http2.rbs +16 -5
- data/sig/connection.rbs +23 -11
- data/sig/errors.rbs +35 -1
- data/sig/headers.rbs +20 -19
- data/sig/httpx.rbs +4 -1
- data/sig/loggable.rbs +3 -1
- data/sig/options.rbs +45 -34
- data/sig/parser/http1.rbs +3 -3
- data/sig/plugins/authentication.rbs +1 -1
- data/sig/plugins/aws_sdk_authentication.rbs +5 -1
- data/sig/plugins/aws_sigv4.rbs +13 -5
- data/sig/plugins/basic_authentication.rbs +1 -1
- data/sig/plugins/compression.rbs +4 -6
- data/sig/plugins/cookies/cookie.rbs +5 -7
- data/sig/plugins/cookies/jar.rbs +9 -10
- data/sig/plugins/cookies.rbs +4 -5
- data/sig/plugins/digest_authentication.rbs +2 -3
- data/sig/plugins/expect.rbs +2 -4
- data/sig/plugins/follow_redirects.rbs +3 -5
- data/sig/plugins/grpc.rbs +4 -7
- data/sig/plugins/h2c.rbs +0 -2
- data/sig/plugins/multipart.rbs +64 -10
- data/sig/plugins/ntlm_authentication.rbs +2 -3
- data/sig/plugins/persistent.rbs +3 -8
- data/sig/plugins/proxy/ssh.rbs +4 -4
- data/sig/plugins/proxy.rbs +13 -13
- data/sig/plugins/push_promise.rbs +0 -2
- data/sig/plugins/retries.rbs +4 -8
- data/sig/plugins/stream.rbs +1 -1
- data/sig/plugins/upgrade.rbs +2 -3
- data/sig/pool.rbs +1 -2
- data/sig/registry.rbs +1 -1
- data/sig/request.rbs +11 -8
- data/sig/resolver/native.rbs +12 -6
- data/sig/resolver/resolver_mixin.rbs +4 -5
- data/sig/resolver/system.rbs +2 -0
- data/sig/resolver.rbs +7 -0
- data/sig/response.rbs +24 -12
- data/sig/selector.rbs +11 -9
- data/sig/session.rbs +22 -23
- data/sig/transcoder/body.rbs +6 -1
- data/sig/transcoder/chunker.rbs +8 -2
- data/sig/transcoder/form.rbs +3 -1
- data/sig/transcoder/json.rbs +2 -0
- data/sig/transcoder.rbs +13 -5
- data/sig/utils.rbs +2 -0
- metadata +12 -2
data/lib/httpx/plugins/h2c.rb
CHANGED
|
@@ -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
|
|
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,
|
|
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
|
|
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(
|
|
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
|
|
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(
|
|
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
|
-
|
|
19
|
-
|
|
20
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
55
|
+
super(request)
|
|
54
56
|
else
|
|
55
57
|
probe_response
|
|
56
58
|
end
|
|
57
59
|
else
|
|
58
|
-
super(request
|
|
60
|
+
super(request)
|
|
59
61
|
end
|
|
60
62
|
end
|
|
61
63
|
end
|
|
@@ -6,23 +6,27 @@ module HTTPX
|
|
|
6
6
|
module Plugins
|
|
7
7
|
module Proxy
|
|
8
8
|
module SSH
|
|
9
|
-
|
|
10
|
-
|
|
9
|
+
class << self
|
|
10
|
+
def load_dependencies(*)
|
|
11
|
+
require "net/ssh/gateway"
|
|
12
|
+
end
|
|
11
13
|
end
|
|
12
14
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
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
|
-
|
|
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
|
-
|
|
25
|
-
|
|
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(*
|
|
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
|
|
75
|
+
raise TypeError, "unexpected scheme: #{request_uri.scheme}"
|
|
69
76
|
end
|
|
70
77
|
end
|
|
71
78
|
end
|
data/lib/httpx/plugins/proxy.rb
CHANGED
|
@@ -5,7 +5,8 @@ require "ipaddr"
|
|
|
5
5
|
require "forwardable"
|
|
6
6
|
|
|
7
7
|
module HTTPX
|
|
8
|
-
HTTPProxyError
|
|
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
|
-
|
|
69
|
-
|
|
70
|
-
|
|
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
|
|
140
|
+
when NativeResolveError
|
|
142
141
|
# failed resolving proxy domain
|
|
143
|
-
|
|
144
|
-
|
|
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
|