httpx 0.21.0 → 1.2.1
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/LICENSE.txt +0 -48
- data/README.md +54 -45
- data/doc/release_notes/0_10_0.md +2 -2
- data/doc/release_notes/0_11_0.md +3 -5
- data/doc/release_notes/0_12_0.md +5 -5
- data/doc/release_notes/0_13_0.md +4 -4
- data/doc/release_notes/0_14_0.md +2 -2
- data/doc/release_notes/0_16_0.md +3 -3
- data/doc/release_notes/0_17_0.md +1 -1
- data/doc/release_notes/0_18_0.md +4 -4
- data/doc/release_notes/0_18_2.md +1 -1
- data/doc/release_notes/0_19_0.md +1 -1
- data/doc/release_notes/0_20_0.md +1 -1
- data/doc/release_notes/0_21_0.md +7 -5
- data/doc/release_notes/0_21_1.md +12 -0
- data/doc/release_notes/0_22_0.md +13 -0
- data/doc/release_notes/0_22_1.md +11 -0
- data/doc/release_notes/0_22_2.md +5 -0
- data/doc/release_notes/0_22_3.md +55 -0
- data/doc/release_notes/0_22_4.md +6 -0
- data/doc/release_notes/0_22_5.md +6 -0
- data/doc/release_notes/0_23_0.md +42 -0
- data/doc/release_notes/0_23_1.md +5 -0
- data/doc/release_notes/0_23_2.md +5 -0
- data/doc/release_notes/0_23_3.md +6 -0
- data/doc/release_notes/0_23_4.md +5 -0
- data/doc/release_notes/0_24_0.md +48 -0
- data/doc/release_notes/0_24_1.md +12 -0
- data/doc/release_notes/0_24_2.md +12 -0
- data/doc/release_notes/0_24_3.md +12 -0
- data/doc/release_notes/0_24_4.md +18 -0
- data/doc/release_notes/0_24_5.md +6 -0
- data/doc/release_notes/0_24_6.md +5 -0
- data/doc/release_notes/0_24_7.md +10 -0
- data/doc/release_notes/1_0_0.md +60 -0
- data/doc/release_notes/1_0_1.md +5 -0
- data/doc/release_notes/1_0_2.md +7 -0
- data/doc/release_notes/1_1_0.md +32 -0
- data/doc/release_notes/1_1_1.md +17 -0
- data/doc/release_notes/1_1_2.md +12 -0
- data/doc/release_notes/1_1_3.md +18 -0
- data/doc/release_notes/1_1_4.md +6 -0
- data/doc/release_notes/1_1_5.md +12 -0
- data/doc/release_notes/1_2_0.md +49 -0
- data/doc/release_notes/1_2_1.md +6 -0
- data/lib/httpx/adapters/datadog.rb +100 -106
- data/lib/httpx/adapters/faraday.rb +143 -107
- data/lib/httpx/adapters/sentry.rb +26 -7
- data/lib/httpx/adapters/webmock.rb +33 -17
- data/lib/httpx/altsvc.rb +61 -24
- data/lib/httpx/base64.rb +27 -0
- data/lib/httpx/buffer.rb +12 -0
- data/lib/httpx/callbacks.rb +5 -3
- data/lib/httpx/chainable.rb +54 -39
- data/lib/httpx/connection/http1.rb +62 -37
- data/lib/httpx/connection/http2.rb +16 -27
- data/lib/httpx/connection.rb +213 -120
- data/lib/httpx/domain_name.rb +10 -13
- data/lib/httpx/errors.rb +34 -2
- data/lib/httpx/extensions.rb +4 -134
- data/lib/httpx/io/ssl.rb +77 -71
- data/lib/httpx/io/tcp.rb +46 -70
- data/lib/httpx/io/udp.rb +18 -52
- data/lib/httpx/io/unix.rb +6 -13
- data/lib/httpx/io.rb +3 -9
- data/lib/httpx/loggable.rb +4 -19
- data/lib/httpx/options.rb +168 -110
- data/lib/httpx/plugins/{authentication → auth}/basic.rb +1 -5
- data/lib/httpx/plugins/{authentication → auth}/digest.rb +13 -14
- data/lib/httpx/plugins/{authentication → auth}/ntlm.rb +1 -3
- data/lib/httpx/plugins/{authentication → auth}/socks5.rb +0 -2
- data/lib/httpx/plugins/auth.rb +25 -0
- data/lib/httpx/plugins/aws_sdk_authentication.rb +1 -3
- data/lib/httpx/plugins/aws_sigv4.rb +5 -6
- data/lib/httpx/plugins/basic_auth.rb +29 -0
- data/lib/httpx/plugins/brotli.rb +50 -0
- data/lib/httpx/plugins/callbacks.rb +91 -0
- data/lib/httpx/plugins/circuit_breaker/circuit.rb +40 -16
- data/lib/httpx/plugins/circuit_breaker/circuit_store.rb +14 -5
- data/lib/httpx/plugins/circuit_breaker.rb +30 -7
- data/lib/httpx/plugins/cookies/set_cookie_parser.rb +0 -2
- data/lib/httpx/plugins/cookies.rb +20 -10
- data/lib/httpx/plugins/{digest_authentication.rb → digest_auth.rb} +11 -12
- data/lib/httpx/plugins/expect.rb +15 -13
- data/lib/httpx/plugins/follow_redirects.rb +71 -29
- data/lib/httpx/plugins/grpc/call.rb +2 -3
- data/lib/httpx/plugins/grpc/grpc_encoding.rb +88 -0
- data/lib/httpx/plugins/grpc/message.rb +7 -37
- data/lib/httpx/plugins/grpc.rb +35 -29
- data/lib/httpx/plugins/h2c.rb +25 -18
- data/lib/httpx/plugins/internal_telemetry.rb +16 -0
- data/lib/httpx/plugins/{ntlm_authentication.rb → ntlm_auth.rb} +7 -5
- data/lib/httpx/plugins/oauth.rb +170 -0
- data/lib/httpx/plugins/persistent.rb +1 -1
- data/lib/httpx/plugins/proxy/http.rb +15 -10
- data/lib/httpx/plugins/proxy/socks4.rb +8 -6
- data/lib/httpx/plugins/proxy/socks5.rb +10 -8
- data/lib/httpx/plugins/proxy.rb +69 -67
- data/lib/httpx/plugins/push_promise.rb +1 -1
- data/lib/httpx/plugins/rate_limiter.rb +3 -1
- data/lib/httpx/plugins/response_cache/file_store.rb +40 -0
- data/lib/httpx/plugins/response_cache/store.rb +34 -17
- data/lib/httpx/plugins/response_cache.rb +6 -6
- data/lib/httpx/plugins/retries.rb +61 -12
- data/lib/httpx/plugins/ssrf_filter.rb +142 -0
- data/lib/httpx/plugins/stream.rb +27 -32
- data/lib/httpx/plugins/upgrade/h2.rb +4 -4
- data/lib/httpx/plugins/upgrade.rb +8 -10
- data/lib/httpx/plugins/webdav.rb +10 -8
- data/lib/httpx/pool.rb +85 -23
- data/lib/httpx/punycode.rb +9 -291
- data/lib/httpx/request/body.rb +158 -0
- data/lib/httpx/request.rb +86 -121
- data/lib/httpx/resolver/https.rb +54 -17
- data/lib/httpx/resolver/multi.rb +8 -12
- data/lib/httpx/resolver/native.rb +163 -70
- data/lib/httpx/resolver/resolver.rb +28 -13
- data/lib/httpx/resolver/system.rb +15 -10
- data/lib/httpx/resolver.rb +38 -16
- data/lib/httpx/response/body.rb +242 -0
- data/lib/httpx/response/buffer.rb +96 -0
- data/lib/httpx/response.rb +113 -211
- data/lib/httpx/selector.rb +2 -4
- data/lib/httpx/session.rb +91 -64
- data/lib/httpx/session_extensions.rb +4 -1
- data/lib/httpx/timers.rb +28 -8
- data/lib/httpx/transcoder/body.rb +0 -2
- data/lib/httpx/transcoder/chunker.rb +0 -1
- data/lib/httpx/transcoder/deflate.rb +37 -0
- data/lib/httpx/transcoder/form.rb +52 -33
- data/lib/httpx/transcoder/gzip.rb +74 -0
- data/lib/httpx/transcoder/json.rb +2 -5
- data/lib/httpx/transcoder/multipart/decoder.rb +139 -0
- data/lib/httpx/{plugins → transcoder}/multipart/encoder.rb +3 -3
- data/lib/httpx/{plugins → transcoder}/multipart/mime_type_detector.rb +1 -1
- data/lib/httpx/{plugins → transcoder}/multipart/part.rb +3 -2
- data/lib/httpx/transcoder/multipart.rb +17 -0
- data/lib/httpx/transcoder/utils/body_reader.rb +46 -0
- data/lib/httpx/transcoder/utils/deflater.rb +72 -0
- data/lib/httpx/transcoder/utils/inflater.rb +19 -0
- data/lib/httpx/transcoder/xml.rb +0 -5
- data/lib/httpx/transcoder.rb +4 -6
- data/lib/httpx/utils.rb +36 -16
- data/lib/httpx/version.rb +1 -1
- data/lib/httpx.rb +12 -14
- data/sig/altsvc.rbs +33 -0
- data/sig/buffer.rbs +1 -0
- data/sig/callbacks.rbs +3 -3
- data/sig/chainable.rbs +10 -9
- data/sig/connection/http1.rbs +5 -4
- data/sig/connection/http2.rbs +1 -1
- data/sig/connection.rbs +46 -24
- data/sig/errors.rbs +9 -3
- data/sig/httpx.rbs +5 -4
- data/sig/io/ssl.rbs +26 -0
- data/sig/io/tcp.rbs +60 -0
- data/sig/io/udp.rbs +20 -0
- data/sig/io/unix.rbs +10 -0
- data/sig/options.rbs +28 -12
- data/sig/plugins/{authentication → auth}/basic.rbs +0 -2
- data/sig/plugins/{authentication → auth}/digest.rbs +2 -1
- data/sig/plugins/auth.rbs +13 -0
- data/sig/plugins/{basic_authentication.rbs → basic_auth.rbs} +2 -2
- data/sig/plugins/brotli.rbs +22 -0
- data/sig/plugins/callbacks.rbs +38 -0
- data/sig/plugins/circuit_breaker.rbs +13 -3
- data/sig/plugins/compression.rbs +6 -4
- data/sig/plugins/cookies/jar.rbs +2 -2
- data/sig/plugins/cookies.rbs +2 -0
- data/sig/plugins/{digest_authentication.rbs → digest_auth.rbs} +2 -2
- data/sig/plugins/follow_redirects.rbs +11 -2
- data/sig/plugins/grpc/call.rbs +19 -0
- data/sig/plugins/grpc/grpc_encoding.rbs +37 -0
- data/sig/plugins/grpc/message.rbs +17 -0
- data/sig/plugins/grpc.rbs +2 -32
- data/sig/plugins/h2c.rbs +1 -1
- data/sig/plugins/{ntlm_authentication.rbs → ntlm_auth.rbs} +2 -2
- data/sig/plugins/oauth.rbs +54 -0
- data/sig/plugins/proxy/socks4.rbs +4 -4
- data/sig/plugins/proxy/socks5.rbs +2 -2
- data/sig/plugins/proxy/ssh.rbs +1 -1
- data/sig/plugins/proxy.rbs +10 -4
- data/sig/plugins/response_cache.rbs +12 -3
- data/sig/plugins/retries.rbs +28 -8
- data/sig/plugins/stream.rbs +24 -17
- data/sig/plugins/upgrade.rbs +5 -3
- data/sig/pool.rbs +5 -4
- data/sig/request/body.rbs +40 -0
- data/sig/request.rbs +12 -28
- data/sig/resolver/https.rbs +7 -2
- data/sig/resolver/native.rbs +10 -4
- data/sig/resolver/resolver.rbs +6 -4
- data/sig/resolver/system.rbs +2 -0
- data/sig/resolver.rbs +9 -5
- data/sig/response/body.rbs +53 -0
- data/sig/response/buffer.rbs +24 -0
- data/sig/response.rbs +17 -38
- data/sig/session.rbs +24 -18
- data/sig/timers.rbs +17 -7
- data/sig/transcoder/body.rbs +4 -3
- data/sig/transcoder/deflate.rbs +11 -0
- data/sig/transcoder/form.rbs +5 -3
- data/sig/transcoder/gzip.rbs +24 -0
- data/sig/transcoder/json.rbs +4 -2
- data/sig/{plugins → transcoder}/multipart.rbs +3 -12
- data/sig/transcoder/utils/body_reader.rbs +15 -0
- data/sig/transcoder/utils/deflater.rbs +29 -0
- data/sig/transcoder/utils/inflater.rbs +12 -0
- data/sig/transcoder/xml.rbs +1 -1
- data/sig/transcoder.rbs +22 -7
- data/sig/utils.rbs +2 -0
- metadata +127 -40
- data/lib/httpx/plugins/authentication.rb +0 -20
- data/lib/httpx/plugins/basic_authentication.rb +0 -30
- data/lib/httpx/plugins/compression/brotli.rb +0 -54
- data/lib/httpx/plugins/compression/deflate.rb +0 -49
- data/lib/httpx/plugins/compression/gzip.rb +0 -88
- data/lib/httpx/plugins/compression.rb +0 -164
- data/lib/httpx/plugins/multipart/decoder.rb +0 -187
- data/lib/httpx/plugins/multipart.rb +0 -84
- data/lib/httpx/registry.rb +0 -85
- data/sig/plugins/authentication.rbs +0 -11
- data/sig/plugins/compression/brotli.rbs +0 -21
- data/sig/plugins/compression/deflate.rbs +0 -17
- data/sig/plugins/compression/gzip.rbs +0 -29
- data/sig/registry.rbs +0 -13
- /data/sig/plugins/{authentication → auth}/ntlm.rbs +0 -0
- /data/sig/plugins/{authentication → auth}/socks5.rbs +0 -0
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "tempfile"
|
|
4
|
+
require "delegate"
|
|
5
|
+
|
|
6
|
+
module HTTPX
|
|
7
|
+
module Transcoder
|
|
8
|
+
module Multipart
|
|
9
|
+
class FilePart < SimpleDelegator
|
|
10
|
+
attr_reader :original_filename, :content_type
|
|
11
|
+
|
|
12
|
+
def initialize(filename, content_type)
|
|
13
|
+
@original_filename = filename
|
|
14
|
+
@content_type = content_type
|
|
15
|
+
@file = Tempfile.new("httpx", encoding: Encoding::BINARY, mode: File::RDWR)
|
|
16
|
+
super(@file)
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
class Decoder
|
|
21
|
+
include HTTPX::Utils
|
|
22
|
+
|
|
23
|
+
CRLF = "\r\n"
|
|
24
|
+
BOUNDARY_RE = /;\s*boundary=([^;]+)/i.freeze
|
|
25
|
+
MULTIPART_CONTENT_TYPE = /Content-Type: (.*)#{CRLF}/ni.freeze
|
|
26
|
+
MULTIPART_CONTENT_DISPOSITION = /Content-Disposition:.*;\s*name=(#{VALUE})/ni.freeze
|
|
27
|
+
MULTIPART_CONTENT_ID = /Content-ID:\s*([^#{CRLF}]*)/ni.freeze
|
|
28
|
+
WINDOW_SIZE = 2 << 14
|
|
29
|
+
|
|
30
|
+
def initialize(response)
|
|
31
|
+
@boundary = begin
|
|
32
|
+
m = response.headers["content-type"].to_s[BOUNDARY_RE, 1]
|
|
33
|
+
raise Error, "no boundary declared in content-type header" unless m
|
|
34
|
+
|
|
35
|
+
m.strip
|
|
36
|
+
end
|
|
37
|
+
@buffer = "".b
|
|
38
|
+
@parts = {}
|
|
39
|
+
@intermediate_boundary = "--#{@boundary}"
|
|
40
|
+
@state = :idle
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def call(response, *)
|
|
44
|
+
response.body.each do |chunk|
|
|
45
|
+
@buffer << chunk
|
|
46
|
+
|
|
47
|
+
parse
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
raise Error, "invalid or unsupported multipart format" unless @buffer.empty?
|
|
51
|
+
|
|
52
|
+
@parts
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
private
|
|
56
|
+
|
|
57
|
+
def parse
|
|
58
|
+
case @state
|
|
59
|
+
when :idle
|
|
60
|
+
raise Error, "payload does not start with boundary" unless @buffer.start_with?("#{@intermediate_boundary}#{CRLF}")
|
|
61
|
+
|
|
62
|
+
@buffer = @buffer.byteslice(@intermediate_boundary.bytesize + 2..-1)
|
|
63
|
+
|
|
64
|
+
@state = :part_header
|
|
65
|
+
when :part_header
|
|
66
|
+
idx = @buffer.index("#{CRLF}#{CRLF}")
|
|
67
|
+
|
|
68
|
+
# raise Error, "couldn't parse part headers" unless idx
|
|
69
|
+
return unless idx
|
|
70
|
+
|
|
71
|
+
head = @buffer.byteslice(0..idx + 4 - 1)
|
|
72
|
+
|
|
73
|
+
@buffer = @buffer.byteslice(head.bytesize..-1)
|
|
74
|
+
|
|
75
|
+
content_type = head[MULTIPART_CONTENT_TYPE, 1]
|
|
76
|
+
if (name = head[MULTIPART_CONTENT_DISPOSITION, 1])
|
|
77
|
+
name = /\A"(.*)"\Z/ =~ name ? Regexp.last_match(1) : name.dup
|
|
78
|
+
name.gsub!(/\\(.)/, "\\1")
|
|
79
|
+
name
|
|
80
|
+
else
|
|
81
|
+
name = head[MULTIPART_CONTENT_ID, 1]
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
filename = HTTPX::Utils.get_filename(head)
|
|
85
|
+
|
|
86
|
+
name = filename || +"#{content_type || "text/plain"}[]" if name.nil? || name.empty?
|
|
87
|
+
|
|
88
|
+
@current = name
|
|
89
|
+
|
|
90
|
+
@parts[name] = if filename
|
|
91
|
+
FilePart.new(filename, content_type)
|
|
92
|
+
else
|
|
93
|
+
"".b
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
@state = :part_body
|
|
97
|
+
when :part_body
|
|
98
|
+
part = @parts[@current]
|
|
99
|
+
|
|
100
|
+
body_separator = if part.is_a?(FilePart)
|
|
101
|
+
"#{CRLF}#{CRLF}"
|
|
102
|
+
else
|
|
103
|
+
CRLF
|
|
104
|
+
end
|
|
105
|
+
idx = @buffer.index(body_separator)
|
|
106
|
+
|
|
107
|
+
if idx
|
|
108
|
+
payload = @buffer.byteslice(0..idx - 1)
|
|
109
|
+
@buffer = @buffer.byteslice(idx + body_separator.bytesize..-1)
|
|
110
|
+
part << payload
|
|
111
|
+
part.rewind if part.respond_to?(:rewind)
|
|
112
|
+
@state = :parse_boundary
|
|
113
|
+
else
|
|
114
|
+
part << @buffer
|
|
115
|
+
@buffer.clear
|
|
116
|
+
end
|
|
117
|
+
when :parse_boundary
|
|
118
|
+
raise Error, "payload does not start with boundary" unless @buffer.start_with?(@intermediate_boundary)
|
|
119
|
+
|
|
120
|
+
@buffer = @buffer.byteslice(@intermediate_boundary.bytesize..-1)
|
|
121
|
+
|
|
122
|
+
if @buffer == "--"
|
|
123
|
+
@buffer.clear
|
|
124
|
+
@state = :done
|
|
125
|
+
return
|
|
126
|
+
elsif @buffer.start_with?(CRLF)
|
|
127
|
+
@buffer = @buffer.byteslice(2..-1)
|
|
128
|
+
@state = :part_header
|
|
129
|
+
else
|
|
130
|
+
return
|
|
131
|
+
end
|
|
132
|
+
when :done
|
|
133
|
+
raise Error, "parsing should have been over by now"
|
|
134
|
+
end until @buffer.empty?
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
end
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
module HTTPX
|
|
4
|
-
module Multipart
|
|
3
|
+
module HTTPX
|
|
4
|
+
module Transcoder::Multipart
|
|
5
5
|
class Encoder
|
|
6
6
|
attr_reader :bytesize
|
|
7
7
|
|
|
@@ -43,7 +43,7 @@ module HTTPX::Plugins
|
|
|
43
43
|
def to_parts(form)
|
|
44
44
|
@bytesize = 0
|
|
45
45
|
params = form.each_with_object([]) do |(key, val), aux|
|
|
46
|
-
|
|
46
|
+
Transcoder.normalize_keys(key, val, MULTIPART_VALUE_COND) do |k, v|
|
|
47
47
|
next if v.nil?
|
|
48
48
|
|
|
49
49
|
value, content_type, filename = Part.call(v)
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module HTTPX
|
|
4
|
-
module
|
|
4
|
+
module Transcoder::Multipart
|
|
5
5
|
module Part
|
|
6
6
|
module_function
|
|
7
7
|
|
|
@@ -21,7 +21,8 @@ module HTTPX
|
|
|
21
21
|
|
|
22
22
|
value = value.open(File::RDONLY) if Object.const_defined?(:Pathname) && value.is_a?(Pathname)
|
|
23
23
|
|
|
24
|
-
if value.
|
|
24
|
+
if value.respond_to?(:path) && value.respond_to?(:read)
|
|
25
|
+
# either a File, a Tempfile, or something else which has to quack like a file
|
|
25
26
|
filename ||= File.basename(value.path)
|
|
26
27
|
content_type ||= MimeTypeDetector.call(value, filename) || "application/octet-stream"
|
|
27
28
|
[value, content_type, filename]
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "multipart/encoder"
|
|
4
|
+
require_relative "multipart/decoder"
|
|
5
|
+
require_relative "multipart/part"
|
|
6
|
+
require_relative "multipart/mime_type_detector"
|
|
7
|
+
|
|
8
|
+
module HTTPX::Transcoder
|
|
9
|
+
module Multipart
|
|
10
|
+
MULTIPART_VALUE_COND = lambda do |value|
|
|
11
|
+
value.respond_to?(:read) ||
|
|
12
|
+
(value.respond_to?(:to_hash) &&
|
|
13
|
+
value.key?(:body) &&
|
|
14
|
+
(value.key?(:filename) || value.key?(:content_type)))
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "stringio"
|
|
4
|
+
|
|
5
|
+
module HTTPX
|
|
6
|
+
module Transcoder
|
|
7
|
+
class BodyReader
|
|
8
|
+
def initialize(body)
|
|
9
|
+
@body = if body.respond_to?(:read)
|
|
10
|
+
body.rewind if body.respond_to?(:rewind)
|
|
11
|
+
body
|
|
12
|
+
elsif body.respond_to?(:each)
|
|
13
|
+
body.enum_for(:each)
|
|
14
|
+
else
|
|
15
|
+
StringIO.new(body.to_s)
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def bytesize
|
|
20
|
+
return @body.bytesize if @body.respond_to?(:bytesize)
|
|
21
|
+
|
|
22
|
+
Float::INFINITY
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def read(length = nil, outbuf = nil)
|
|
26
|
+
return @body.read(length, outbuf) if @body.respond_to?(:read)
|
|
27
|
+
|
|
28
|
+
begin
|
|
29
|
+
chunk = @body.next
|
|
30
|
+
if outbuf
|
|
31
|
+
outbuf.clear.force_encoding(Encoding::BINARY)
|
|
32
|
+
outbuf << chunk
|
|
33
|
+
else
|
|
34
|
+
outbuf = chunk
|
|
35
|
+
end
|
|
36
|
+
outbuf unless length && outbuf.empty?
|
|
37
|
+
rescue StopIteration
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def close
|
|
42
|
+
@body.close if @body.respond_to?(:close)
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "forwardable"
|
|
4
|
+
require_relative "body_reader"
|
|
5
|
+
|
|
6
|
+
module HTTPX
|
|
7
|
+
module Transcoder
|
|
8
|
+
class Deflater
|
|
9
|
+
extend Forwardable
|
|
10
|
+
|
|
11
|
+
attr_reader :content_type
|
|
12
|
+
|
|
13
|
+
def initialize(body)
|
|
14
|
+
@content_type = body.content_type
|
|
15
|
+
@body = BodyReader.new(body)
|
|
16
|
+
@closed = false
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def bytesize
|
|
20
|
+
buffer_deflate!
|
|
21
|
+
|
|
22
|
+
@buffer.size
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def read(length = nil, outbuf = nil)
|
|
26
|
+
return @buffer.read(length, outbuf) if @buffer
|
|
27
|
+
|
|
28
|
+
return if @closed
|
|
29
|
+
|
|
30
|
+
chunk = @body.read(length)
|
|
31
|
+
|
|
32
|
+
compressed_chunk = deflate(chunk)
|
|
33
|
+
|
|
34
|
+
return unless compressed_chunk
|
|
35
|
+
|
|
36
|
+
if outbuf
|
|
37
|
+
outbuf.clear.force_encoding(Encoding::BINARY)
|
|
38
|
+
outbuf << compressed_chunk
|
|
39
|
+
else
|
|
40
|
+
compressed_chunk
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def close
|
|
45
|
+
return if @closed
|
|
46
|
+
|
|
47
|
+
@buffer.close if @buffer
|
|
48
|
+
|
|
49
|
+
@body.close
|
|
50
|
+
|
|
51
|
+
@closed = true
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
private
|
|
55
|
+
|
|
56
|
+
# rubocop:disable Naming/MemoizedInstanceVariableName
|
|
57
|
+
def buffer_deflate!
|
|
58
|
+
return @buffer if defined?(@buffer)
|
|
59
|
+
|
|
60
|
+
buffer = Response::Buffer.new(
|
|
61
|
+
threshold_size: Options::MAX_BODY_THRESHOLD_SIZE
|
|
62
|
+
)
|
|
63
|
+
::IO.copy_stream(self, buffer)
|
|
64
|
+
|
|
65
|
+
buffer.rewind
|
|
66
|
+
|
|
67
|
+
@buffer = buffer
|
|
68
|
+
end
|
|
69
|
+
# rubocop:enable Naming/MemoizedInstanceVariableName
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
module HTTPX
|
|
2
|
+
module Transcoder
|
|
3
|
+
class Inflater
|
|
4
|
+
def initialize(bytesize)
|
|
5
|
+
@bytesize = bytesize
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def call(chunk)
|
|
9
|
+
buffer = @inflater.inflate(chunk)
|
|
10
|
+
@bytesize -= chunk.bytesize
|
|
11
|
+
if @bytesize <= 0
|
|
12
|
+
buffer << @inflater.finish
|
|
13
|
+
@inflater.close
|
|
14
|
+
end
|
|
15
|
+
buffer
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
data/lib/httpx/transcoder/xml.rb
CHANGED
|
@@ -6,8 +6,6 @@ require "uri"
|
|
|
6
6
|
|
|
7
7
|
module HTTPX::Transcoder
|
|
8
8
|
module Xml
|
|
9
|
-
using HTTPX::RegexpExtensions
|
|
10
|
-
|
|
11
9
|
module_function
|
|
12
10
|
|
|
13
11
|
MIME_TYPES = %r{\b(application|text)/(.+\+)?xml\b}.freeze
|
|
@@ -38,7 +36,6 @@ module HTTPX::Transcoder
|
|
|
38
36
|
begin
|
|
39
37
|
require "nokogiri"
|
|
40
38
|
|
|
41
|
-
# rubocop:disable Lint/DuplicateMethods
|
|
42
39
|
def decode(response)
|
|
43
40
|
content_type = response.content_type.mime_type
|
|
44
41
|
|
|
@@ -51,7 +48,5 @@ module HTTPX::Transcoder
|
|
|
51
48
|
raise HTTPX::Error, "\"nokogiri\" is required in order to decode XML"
|
|
52
49
|
end
|
|
53
50
|
end
|
|
54
|
-
# rubocop:enable Lint/DuplicateMethods
|
|
55
51
|
end
|
|
56
|
-
register "xml", Xml
|
|
57
52
|
end
|
data/lib/httpx/transcoder.rb
CHANGED
|
@@ -2,14 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
module HTTPX
|
|
4
4
|
module Transcoder
|
|
5
|
-
extend Registry
|
|
6
|
-
|
|
7
|
-
using RegexpExtensions unless Regexp.method_defined?(:match?)
|
|
8
|
-
|
|
9
5
|
module_function
|
|
10
6
|
|
|
11
7
|
def normalize_keys(key, value, cond = nil, &block)
|
|
12
|
-
if
|
|
8
|
+
if cond && cond.call(value)
|
|
13
9
|
block.call(key.to_s, value)
|
|
14
10
|
elsif value.respond_to?(:to_ary)
|
|
15
11
|
if value.empty?
|
|
@@ -73,7 +69,7 @@ module HTTPX
|
|
|
73
69
|
end
|
|
74
70
|
|
|
75
71
|
def params_hash_has_key?(hash, key)
|
|
76
|
-
return false if
|
|
72
|
+
return false if key.include?("[]")
|
|
77
73
|
|
|
78
74
|
key.split(/[\[\]]+/).inject(hash) do |h, part|
|
|
79
75
|
next h if part == ""
|
|
@@ -92,3 +88,5 @@ require "httpx/transcoder/form"
|
|
|
92
88
|
require "httpx/transcoder/json"
|
|
93
89
|
require "httpx/transcoder/xml"
|
|
94
90
|
require "httpx/transcoder/chunker"
|
|
91
|
+
require "httpx/transcoder/deflate"
|
|
92
|
+
require "httpx/transcoder/gzip"
|
data/lib/httpx/utils.rb
CHANGED
|
@@ -4,6 +4,11 @@ module HTTPX
|
|
|
4
4
|
module Utils
|
|
5
5
|
using URIExtensions
|
|
6
6
|
|
|
7
|
+
TOKEN = %r{[^\s()<>,;:\\"/\[\]?=]+}.freeze
|
|
8
|
+
VALUE = /"(?:\\"|[^"])*"|#{TOKEN}/.freeze
|
|
9
|
+
FILENAME_REGEX = /\s*filename=(#{VALUE})/.freeze
|
|
10
|
+
FILENAME_EXTENSION_REGEX = /\s*filename\*=(#{VALUE})/.freeze
|
|
11
|
+
|
|
7
12
|
module_function
|
|
8
13
|
|
|
9
14
|
def now
|
|
@@ -25,31 +30,46 @@ module HTTPX
|
|
|
25
30
|
time - Time.now
|
|
26
31
|
end
|
|
27
32
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
33
|
+
def get_filename(header, _prefix_regex = nil)
|
|
34
|
+
filename = nil
|
|
35
|
+
case header
|
|
36
|
+
when FILENAME_REGEX
|
|
37
|
+
filename = Regexp.last_match(1)
|
|
38
|
+
filename = Regexp.last_match(1) if filename =~ /^"(.*)"$/
|
|
39
|
+
when FILENAME_EXTENSION_REGEX
|
|
40
|
+
filename = Regexp.last_match(1)
|
|
41
|
+
encoding, _, filename = filename.split("'", 3)
|
|
32
42
|
end
|
|
33
43
|
|
|
34
|
-
|
|
44
|
+
return unless filename
|
|
35
45
|
|
|
36
|
-
|
|
46
|
+
filename = URI::DEFAULT_PARSER.unescape(filename) if filename.scan(/%.?.?/).all? { |s| /%[0-9a-fA-F]{2}/.match?(s) }
|
|
37
47
|
|
|
38
|
-
|
|
39
|
-
return URI(uri) unless uri.is_a?(String) && !uri.ascii_only?
|
|
48
|
+
filename.scrub!
|
|
40
49
|
|
|
41
|
-
|
|
50
|
+
filename = filename.gsub(/\\(.)/, '\1') unless /\\[^\\"]/.match?(filename)
|
|
42
51
|
|
|
43
|
-
|
|
52
|
+
filename.force_encoding ::Encoding.find(encoding) if encoding
|
|
44
53
|
|
|
45
|
-
|
|
54
|
+
filename
|
|
55
|
+
end
|
|
46
56
|
|
|
47
|
-
|
|
57
|
+
URIParser = URI::RFC2396_Parser.new
|
|
48
58
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
59
|
+
def to_uri(uri)
|
|
60
|
+
return URI(uri) unless uri.is_a?(String) && !uri.ascii_only?
|
|
61
|
+
|
|
62
|
+
uri = URI(URIParser.escape(uri))
|
|
63
|
+
|
|
64
|
+
non_ascii_hostname = URIParser.unescape(uri.host)
|
|
65
|
+
|
|
66
|
+
non_ascii_hostname.force_encoding(Encoding::UTF_8)
|
|
67
|
+
|
|
68
|
+
idna_hostname = Punycode.encode_hostname(non_ascii_hostname)
|
|
69
|
+
|
|
70
|
+
uri.host = idna_hostname
|
|
71
|
+
uri.non_ascii_hostname = non_ascii_hostname
|
|
72
|
+
uri
|
|
53
73
|
end
|
|
54
74
|
end
|
|
55
75
|
end
|
data/lib/httpx/version.rb
CHANGED
data/lib/httpx.rb
CHANGED
|
@@ -11,7 +11,6 @@ require "httpx/domain_name"
|
|
|
11
11
|
require "httpx/altsvc"
|
|
12
12
|
require "httpx/callbacks"
|
|
13
13
|
require "httpx/loggable"
|
|
14
|
-
require "httpx/registry"
|
|
15
14
|
require "httpx/transcoder"
|
|
16
15
|
require "httpx/timers"
|
|
17
16
|
require "httpx/pool"
|
|
@@ -21,7 +20,6 @@ require "httpx/response"
|
|
|
21
20
|
require "httpx/options"
|
|
22
21
|
require "httpx/chainable"
|
|
23
22
|
|
|
24
|
-
require "mutex_m"
|
|
25
23
|
# Top-Level Namespace
|
|
26
24
|
#
|
|
27
25
|
module HTTPX
|
|
@@ -32,16 +30,17 @@ module HTTPX
|
|
|
32
30
|
#
|
|
33
31
|
module Plugins
|
|
34
32
|
@plugins = {}
|
|
35
|
-
@
|
|
33
|
+
@plugins_mutex = Thread::Mutex.new
|
|
36
34
|
|
|
37
35
|
# Loads a plugin based on a name. If the plugin hasn't been loaded, tries to load
|
|
38
36
|
# it from the load path under "httpx/plugins/" directory.
|
|
39
37
|
#
|
|
40
38
|
def self.load_plugin(name)
|
|
41
39
|
h = @plugins
|
|
42
|
-
|
|
40
|
+
m = @plugins_mutex
|
|
41
|
+
unless (plugin = m.synchronize { h[name] })
|
|
43
42
|
require "httpx/plugins/#{name}"
|
|
44
|
-
raise "Plugin #{name} hasn't been registered" unless (plugin =
|
|
43
|
+
raise "Plugin #{name} hasn't been registered" unless (plugin = m.synchronize { h[name] })
|
|
45
44
|
end
|
|
46
45
|
plugin
|
|
47
46
|
end
|
|
@@ -50,20 +49,19 @@ module HTTPX
|
|
|
50
49
|
#
|
|
51
50
|
def self.register_plugin(name, mod)
|
|
52
51
|
h = @plugins
|
|
53
|
-
|
|
52
|
+
m = @plugins_mutex
|
|
53
|
+
m.synchronize { h[name] = mod }
|
|
54
54
|
end
|
|
55
55
|
end
|
|
56
56
|
|
|
57
|
-
# :nocov:
|
|
58
|
-
def self.const_missing(const_name)
|
|
59
|
-
super unless const_name == :Client
|
|
60
|
-
warn "DEPRECATION WARNING: the class #{self}::Client is deprecated. Use #{self}::Session instead."
|
|
61
|
-
Session
|
|
62
|
-
end
|
|
63
|
-
# :nocov:
|
|
64
|
-
|
|
65
57
|
extend Chainable
|
|
66
58
|
end
|
|
67
59
|
|
|
68
60
|
require "httpx/session"
|
|
69
61
|
require "httpx/session_extensions"
|
|
62
|
+
|
|
63
|
+
# load integrations when possible
|
|
64
|
+
|
|
65
|
+
require "httpx/adapters/datadog" if defined?(DDTrace) || defined?(Datadog)
|
|
66
|
+
require "httpx/adapters/sentry" if defined?(Sentry)
|
|
67
|
+
require "httpx/adapters/webmock" if defined?(WebMock)
|
data/sig/altsvc.rbs
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
module HTTPX
|
|
2
|
+
module AltSvc
|
|
3
|
+
module ConnectionMixin
|
|
4
|
+
|
|
5
|
+
def send: (Request request) -> void
|
|
6
|
+
|
|
7
|
+
def match?: (URI::Generic uri, Options options) -> bool
|
|
8
|
+
|
|
9
|
+
private
|
|
10
|
+
|
|
11
|
+
def match_altsvcs?: (URI::Generic uri) -> bool
|
|
12
|
+
|
|
13
|
+
def match_altsvc_options?: (URI::Generic uri, Options options) -> bool
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
type altsvc_params = Hash[String, untyped]
|
|
17
|
+
|
|
18
|
+
def self?.cached_altsvc: (String origin) -> Array[altsvc_params]
|
|
19
|
+
|
|
20
|
+
def self?.cached_altsvc_set: (String origin, altsvc_params) -> void
|
|
21
|
+
|
|
22
|
+
def self?.lookup: (String origin, Integer | Float ttl) -> Array[altsvc_params]
|
|
23
|
+
|
|
24
|
+
def self?.emit: (Request request, response response) { (http_uri alt_origin, String origin, altsvc_params alt_params) -> void } -> void
|
|
25
|
+
|
|
26
|
+
def self?.parse: (String altsvc) { (http_uri alt_origin, altsvc_params alt_params) -> void } -> void
|
|
27
|
+
| (String altsvc) -> Enumerable[[http_uri, altsvc_params]]
|
|
28
|
+
|
|
29
|
+
def self?.parse_altsvc_scheme: (String alt_proto) -> String?
|
|
30
|
+
|
|
31
|
+
def self.parse_altsvc_origin: (string alt_proto, String alt_origin) -> http_uri?
|
|
32
|
+
end
|
|
33
|
+
end
|
data/sig/buffer.rbs
CHANGED
data/sig/callbacks.rbs
CHANGED
|
@@ -4,9 +4,9 @@ module HTTPX
|
|
|
4
4
|
end
|
|
5
5
|
|
|
6
6
|
module Callbacks
|
|
7
|
-
def on: (Symbol) { (*untyped) -> void } ->
|
|
8
|
-
def once: (Symbol) { (*untyped) -> void } ->
|
|
9
|
-
def only: (Symbol) { (*untyped) -> void } ->
|
|
7
|
+
def on: (Symbol) { (*untyped) -> void } -> self
|
|
8
|
+
def once: (Symbol) { (*untyped) -> void } -> self
|
|
9
|
+
def only: (Symbol) { (*untyped) -> void } -> self
|
|
10
10
|
def emit: (Symbol, *untyped) -> void
|
|
11
11
|
|
|
12
12
|
def callbacks_for?: (Symbol) -> bool
|
data/sig/chainable.rbs
CHANGED
|
@@ -2,9 +2,9 @@ module HTTPX
|
|
|
2
2
|
module Chainable
|
|
3
3
|
def request: (*Request, **untyped) -> Array[response]
|
|
4
4
|
| (Request, **untyped) -> response
|
|
5
|
-
| (verb
|
|
6
|
-
| (Array[[verb
|
|
7
|
-
| (verb
|
|
5
|
+
| (verb, uri | [uri], **untyped) -> response
|
|
6
|
+
| (Array[[verb, uri] | [verb, uri, options]], **untyped) -> Array[response]
|
|
7
|
+
| (verb, _Each[uri | [uri, options]], **untyped) -> Array[response]
|
|
8
8
|
|
|
9
9
|
def accept: (String) -> Session
|
|
10
10
|
def wrap: () { (Session) -> void } -> void
|
|
@@ -12,18 +12,17 @@ module HTTPX
|
|
|
12
12
|
def with: (options) -> Session
|
|
13
13
|
| (options) { (Session) -> void } -> void
|
|
14
14
|
|
|
15
|
-
def plugin: (:
|
|
16
|
-
| (:
|
|
17
|
-
| (:
|
|
18
|
-
| (:
|
|
15
|
+
def plugin: (:auth, ?options) -> Plugins::sessionAuthorization
|
|
16
|
+
| (:basic_auth, ?options) -> Plugins::sessionBasicAuth
|
|
17
|
+
| (:digest_auth, ?options) -> Plugins::sessionDigestAuth
|
|
18
|
+
| (:ntlm_auth, ?options) -> Plugins::sessionNTLMAuth
|
|
19
19
|
| (:aws_sdk_authentication, ?options) -> Plugins::sessionAwsSdkAuthentication
|
|
20
|
-
| (:
|
|
20
|
+
| (:brotli, ?options) -> Session
|
|
21
21
|
| (:cookies, ?options) -> Plugins::sessionCookies
|
|
22
22
|
| (:expect, ?options) -> Session
|
|
23
23
|
| (:follow_redirects, ?options) -> Plugins::sessionFollowRedirects
|
|
24
24
|
| (:upgrade, ?options) -> Session
|
|
25
25
|
| (:h2c, ?options) -> Session
|
|
26
|
-
| (:multipart, ?options) -> Session
|
|
27
26
|
| (:persistent, ?options) -> Plugins::sessionPersistent
|
|
28
27
|
| (:proxy, ?options) -> (Plugins::sessionProxy & Plugins::httpProxy)
|
|
29
28
|
| (:push_promise, ?options) -> Plugins::sessionPushPromise
|
|
@@ -34,6 +33,8 @@ module HTTPX
|
|
|
34
33
|
| (:grpc, ?options) -> Plugins::grpcSession
|
|
35
34
|
| (:response_cache, ?options) -> Plugins::sessionResponseCache
|
|
36
35
|
| (:circuit_breaker, ?options) -> Plugins::sessionCircuitBreaker
|
|
36
|
+
| (:oauth, ?options) -> Plugins::sessionOAuth
|
|
37
|
+
| (:callbacks, ?options) -> Plugins::sessionCallbacks
|
|
37
38
|
| (Symbol | Module, ?options) { (Class) -> void } -> Session
|
|
38
39
|
| (Symbol | Module, ?options) -> Session
|
|
39
40
|
|
data/sig/connection/http1.rbs
CHANGED
|
@@ -10,8 +10,9 @@ module HTTPX
|
|
|
10
10
|
attr_reader pending: Array[Request]
|
|
11
11
|
attr_reader requests: Array[Request]
|
|
12
12
|
|
|
13
|
+
attr_accessor max_concurrent_requests: Integer
|
|
14
|
+
|
|
13
15
|
@options: Options
|
|
14
|
-
@max_concurrent_requests: Integer
|
|
15
16
|
@max_requests: Integer
|
|
16
17
|
@parser: Parser::HTTP1
|
|
17
18
|
@buffer: Buffer
|
|
@@ -53,19 +54,19 @@ module HTTPX
|
|
|
53
54
|
|
|
54
55
|
def ping: () -> void
|
|
55
56
|
|
|
56
|
-
def timeout: () -> Numeric
|
|
57
|
+
def timeout: () -> Numeric?
|
|
57
58
|
|
|
58
59
|
private
|
|
59
60
|
|
|
60
61
|
def initialize: (Buffer, options) -> untyped
|
|
61
62
|
|
|
62
|
-
def manage_connection: (Response) -> void
|
|
63
|
+
def manage_connection: (Request request, Response response) -> void
|
|
63
64
|
|
|
64
65
|
def disable: () -> void
|
|
65
66
|
|
|
66
67
|
def disable_pipelining: () -> void
|
|
67
68
|
|
|
68
|
-
def set_protocol_headers: (Request) -> _Each[[String, String]]
|
|
69
|
+
def set_protocol_headers: (Request request) -> _Each[[String, String]]
|
|
69
70
|
|
|
70
71
|
def handle: (Request request) -> void
|
|
71
72
|
|