httpx 0.20.0 → 1.3.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 +5 -5
- 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_19_8.md +1 -1
- data/doc/release_notes/0_20_0.md +2 -2
- data/doc/release_notes/0_20_1.md +5 -0
- data/doc/release_notes/0_20_2.md +7 -0
- data/doc/release_notes/0_20_3.md +6 -0
- data/doc/release_notes/0_20_4.md +17 -0
- data/doc/release_notes/0_20_5.md +3 -0
- data/doc/release_notes/0_21_0.md +96 -0
- 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/doc/release_notes/1_2_2.md +10 -0
- data/doc/release_notes/1_2_3.md +16 -0
- data/doc/release_notes/1_2_4.md +8 -0
- data/doc/release_notes/1_2_5.md +7 -0
- data/doc/release_notes/1_2_6.md +13 -0
- data/doc/release_notes/1_3_0.md +18 -0
- data/doc/release_notes/1_3_1.md +17 -0
- data/lib/httpx/adapters/datadog.rb +215 -122
- data/lib/httpx/adapters/faraday.rb +145 -107
- data/lib/httpx/adapters/sentry.rb +26 -7
- data/lib/httpx/adapters/webmock.rb +34 -18
- data/lib/httpx/altsvc.rb +63 -26
- 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 +75 -44
- data/lib/httpx/connection/http2.rb +31 -38
- data/lib/httpx/connection.rb +287 -117
- data/lib/httpx/domain_name.rb +10 -13
- data/lib/httpx/errors.rb +52 -2
- data/lib/httpx/extensions.rb +24 -131
- data/lib/httpx/io/ssl.rb +83 -77
- data/lib/httpx/io/tcp.rb +48 -71
- data/lib/httpx/io/udp.rb +18 -52
- data/lib/httpx/io/unix.rb +10 -15
- data/lib/httpx/io.rb +3 -9
- data/lib/httpx/loggable.rb +4 -19
- data/lib/httpx/options.rb +176 -118
- data/lib/httpx/parser/http1.rb +4 -0
- data/lib/httpx/plugins/{authentication → auth}/basic.rb +1 -5
- data/lib/httpx/plugins/{authentication → auth}/digest.rb +14 -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 +4 -3
- data/lib/httpx/plugins/aws_sigv4.rb +12 -9
- 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 +100 -0
- data/lib/httpx/plugins/circuit_breaker/circuit_store.rb +53 -0
- data/lib/httpx/plugins/circuit_breaker.rb +148 -0
- data/lib/httpx/plugins/cookies/set_cookie_parser.rb +0 -2
- data/lib/httpx/plugins/cookies.rb +30 -17
- data/lib/httpx/plugins/{digest_authentication.rb → digest_auth.rb} +14 -12
- data/lib/httpx/plugins/expect.rb +21 -14
- data/lib/httpx/plugins/follow_redirects.rb +140 -41
- 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 +36 -29
- data/lib/httpx/plugins/h2c.rb +26 -19
- 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 +175 -0
- data/lib/httpx/plugins/persistent.rb +1 -1
- data/lib/httpx/plugins/proxy/http.rb +23 -13
- data/lib/httpx/plugins/proxy/socks4.rb +9 -7
- data/lib/httpx/plugins/proxy/socks5.rb +11 -9
- data/lib/httpx/plugins/proxy.rb +80 -61
- data/lib/httpx/plugins/push_promise.rb +1 -1
- data/lib/httpx/plugins/rate_limiter.rb +5 -1
- data/lib/httpx/plugins/response_cache/file_store.rb +40 -0
- data/lib/httpx/plugins/response_cache/store.rb +62 -25
- data/lib/httpx/plugins/response_cache.rb +105 -12
- data/lib/httpx/plugins/retries.rb +87 -17
- data/lib/httpx/plugins/ssrf_filter.rb +145 -0
- data/lib/httpx/plugins/stream.rb +27 -23
- data/lib/httpx/plugins/upgrade/h2.rb +4 -4
- data/lib/httpx/plugins/upgrade.rb +8 -10
- data/lib/httpx/plugins/webdav.rb +80 -0
- data/lib/httpx/pool/synch_pool.rb +93 -0
- data/lib/httpx/pool.rb +102 -27
- data/lib/httpx/punycode.rb +9 -291
- data/lib/httpx/request/body.rb +154 -0
- data/lib/httpx/request.rb +130 -146
- data/lib/httpx/resolver/https.rb +62 -27
- data/lib/httpx/resolver/multi.rb +9 -13
- data/lib/httpx/resolver/native.rb +192 -76
- data/lib/httpx/resolver/resolver.rb +34 -9
- data/lib/httpx/resolver/system.rb +16 -11
- 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 +159 -217
- data/lib/httpx/selector.rb +9 -4
- data/lib/httpx/session.rb +137 -89
- data/lib/httpx/session_extensions.rb +4 -1
- data/lib/httpx/timers.rb +34 -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 +21 -8
- data/lib/httpx/transcoder/multipart/decoder.rb +139 -0
- data/lib/httpx/{plugins → transcoder}/multipart/encoder.rb +4 -4
- 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 +52 -0
- data/lib/httpx/transcoder.rb +5 -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 +2 -1
- data/sig/callbacks.rbs +3 -3
- data/sig/chainable.rbs +11 -9
- data/sig/connection/http1.rbs +8 -7
- data/sig/connection/http2.rbs +19 -19
- data/sig/connection.rbs +64 -24
- data/sig/errors.rbs +22 -3
- data/sig/httpx.rbs +5 -4
- data/sig/io/ssl.rbs +27 -0
- data/sig/io/tcp.rbs +60 -0
- data/sig/io/udp.rbs +20 -0
- data/sig/io/unix.rbs +27 -0
- data/sig/io.rbs +6 -0
- data/sig/options.rbs +32 -22
- data/sig/parser/http1.rbs +1 -1
- 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 +71 -0
- data/sig/plugins/compression.rbs +7 -5
- 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 +18 -4
- 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 +7 -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/http.rbs +3 -0
- data/sig/plugins/proxy/socks4.rbs +9 -6
- data/sig/plugins/proxy/socks5.rbs +10 -6
- data/sig/plugins/proxy/ssh.rbs +1 -1
- data/sig/plugins/proxy.rbs +13 -5
- data/sig/plugins/push_promise.rbs +3 -3
- data/sig/plugins/rate_limiter.rbs +1 -1
- data/sig/plugins/response_cache.rbs +36 -7
- data/sig/plugins/retries.rbs +30 -8
- data/sig/plugins/stream.rbs +24 -17
- data/sig/plugins/upgrade.rbs +5 -3
- data/sig/pool.rbs +10 -7
- data/sig/request/body.rbs +38 -0
- data/sig/request.rbs +15 -24
- data/sig/resolver/https.rbs +8 -3
- data/sig/resolver/native.rbs +17 -4
- data/sig/resolver/resolver.rbs +8 -6
- 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 +24 -39
- data/sig/selector.rbs +1 -1
- data/sig/session.rbs +29 -18
- data/sig/timers.rbs +18 -8
- 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 +8 -3
- data/sig/{plugins → transcoder}/multipart.rbs +15 -19
- 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 +22 -0
- data/sig/transcoder.rbs +24 -9
- data/sig/utils.rbs +8 -2
- metadata +163 -41
- 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 -12
- /data/sig/plugins/{authentication → auth}/ntlm.rbs +0 -0
- /data/sig/plugins/{authentication → auth}/socks5.rbs +0 -0
data/lib/httpx/response.rb
CHANGED
@@ -7,23 +7,62 @@ require "fileutils"
|
|
7
7
|
require "forwardable"
|
8
8
|
|
9
9
|
module HTTPX
|
10
|
+
# Defines a HTTP response is handled internally, with a few properties exposed as attributes.
|
11
|
+
#
|
12
|
+
# It delegates the following methods to the corresponding HTTPX::Request:
|
13
|
+
#
|
14
|
+
# * HTTPX::Request#uri
|
15
|
+
# * HTTPX::Request#peer_address
|
16
|
+
#
|
17
|
+
# It implements (indirectly, via the +body+) the IO write protocol to internally buffer payloads.
|
18
|
+
#
|
19
|
+
# It implements the IO reader protocol in order for users to buffer/stream it, acts as an enumerable
|
20
|
+
# (of payload chunks).
|
21
|
+
#
|
10
22
|
class Response
|
11
23
|
extend Forwardable
|
24
|
+
include Callbacks
|
12
25
|
|
13
|
-
|
26
|
+
# the HTTP response status code
|
27
|
+
attr_reader :status
|
14
28
|
|
29
|
+
# an HTTPX::Headers object containing the response HTTP headers.
|
30
|
+
attr_reader :headers
|
31
|
+
|
32
|
+
# a HTTPX::Response::Body object wrapping the response body. The following methods are delegated to it:
|
33
|
+
#
|
34
|
+
# * HTTPX::Response::Body#to_s
|
35
|
+
# * HTTPX::Response::Body#to_str
|
36
|
+
# * HTTPX::Response::Body#read
|
37
|
+
# * HTTPX::Response::Body#copy_to
|
38
|
+
# * HTTPX::Response::Body#close
|
39
|
+
attr_reader :body
|
40
|
+
|
41
|
+
# The HTTP protocol version used to fetch the response.
|
42
|
+
attr_reader :version
|
43
|
+
|
44
|
+
# returns the response body buffered in a string.
|
15
45
|
def_delegator :@body, :to_s
|
16
46
|
|
17
47
|
def_delegator :@body, :to_str
|
18
48
|
|
49
|
+
# implements the IO reader +#read+ interface.
|
19
50
|
def_delegator :@body, :read
|
20
51
|
|
52
|
+
# copies the response body to a different location.
|
21
53
|
def_delegator :@body, :copy_to
|
22
54
|
|
55
|
+
# closes the body.
|
23
56
|
def_delegator :@body, :close
|
24
57
|
|
58
|
+
# the corresponding request uri.
|
25
59
|
def_delegator :@request, :uri
|
26
60
|
|
61
|
+
# the IP address of the peer server.
|
62
|
+
def_delegator :@request, :peer_address
|
63
|
+
|
64
|
+
# inits the instance with the corresponding +request+ to this response, an the
|
65
|
+
# response HTTP +status+, +version+ and HTTPX::Headers instance of +headers+.
|
27
66
|
def initialize(request, status, version, headers)
|
28
67
|
@request = request
|
29
68
|
@options = request.options
|
@@ -31,32 +70,60 @@ module HTTPX
|
|
31
70
|
@status = Integer(status)
|
32
71
|
@headers = @options.headers_class.new(headers)
|
33
72
|
@body = @options.response_body_class.new(self, @options)
|
73
|
+
@finished = complete?
|
74
|
+
@content_type = nil
|
34
75
|
end
|
35
76
|
|
77
|
+
# merges headers defined in +h+ into the response headers.
|
36
78
|
def merge_headers(h)
|
37
79
|
@headers = @headers.merge(h)
|
38
80
|
end
|
39
81
|
|
82
|
+
# writes +data+ chunk into the response body.
|
40
83
|
def <<(data)
|
41
84
|
@body.write(data)
|
42
85
|
end
|
43
86
|
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
87
|
+
# returns the HTTPX::ContentType for the response, as per what's declared in the content-type header.
|
88
|
+
#
|
89
|
+
# response.content_type #=> #<HTTPX::ContentType:xxx @header_value="text/plain">
|
90
|
+
# response.content_type.mime_type #=> "text/plain"
|
49
91
|
def content_type
|
50
92
|
@content_type ||= ContentType.new(@headers["content-type"])
|
51
93
|
end
|
52
94
|
|
95
|
+
# returns whether the response has been fully fetched.
|
96
|
+
def finished?
|
97
|
+
@finished
|
98
|
+
end
|
99
|
+
|
100
|
+
# marks the response as finished, freezes the headers.
|
101
|
+
def finish!
|
102
|
+
@finished = true
|
103
|
+
@headers.freeze
|
104
|
+
end
|
105
|
+
|
106
|
+
# returns whether the response contains body payload.
|
107
|
+
def bodyless?
|
108
|
+
@request.verb == "HEAD" ||
|
109
|
+
@status < 200 || # informational response
|
110
|
+
@status == 204 ||
|
111
|
+
@status == 205 ||
|
112
|
+
@status == 304 || begin
|
113
|
+
content_length = @headers["content-length"]
|
114
|
+
return false if content_length.nil?
|
115
|
+
|
116
|
+
content_length == "0"
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
53
120
|
def complete?
|
54
|
-
bodyless? || (@request.verb ==
|
121
|
+
bodyless? || (@request.verb == "CONNECT" && @status == 200)
|
55
122
|
end
|
56
123
|
|
57
124
|
# :nocov:
|
58
125
|
def inspect
|
59
|
-
"#<Response:#{object_id} "\
|
126
|
+
"#<Response:#{object_id} " \
|
60
127
|
"HTTP/#{version} " \
|
61
128
|
"@status=#{@status} " \
|
62
129
|
"@headers=#{@headers} " \
|
@@ -64,227 +131,67 @@ module HTTPX
|
|
64
131
|
end
|
65
132
|
# :nocov:
|
66
133
|
|
134
|
+
# returns an instance of HTTPX::HTTPError if the response has a 4xx or 5xx
|
135
|
+
# status code, or nothing.
|
136
|
+
#
|
137
|
+
# ok_response.error #=> nil
|
138
|
+
# not_found_response.error #=> HTTPX::HTTPError instance, status 404
|
67
139
|
def error
|
68
140
|
return if @status < 400
|
69
141
|
|
70
142
|
HTTPError.new(self)
|
71
143
|
end
|
72
144
|
|
145
|
+
# it raises the exception returned by +error+, or itself otherwise.
|
146
|
+
#
|
147
|
+
# ok_response.raise_for_status #=> ok_response
|
148
|
+
# not_found_response.raise_for_status #=> raises HTTPX::HTTPError exception
|
73
149
|
def raise_for_status
|
74
150
|
return self unless (err = error)
|
75
151
|
|
76
152
|
raise err
|
77
153
|
end
|
78
154
|
|
79
|
-
|
80
|
-
|
155
|
+
# decodes the response payload into a ruby object **if** the payload is valid json.
|
156
|
+
#
|
157
|
+
# response.json #≈> { "foo" => "bar" } for "{\"foo\":\"bar\"}" payload
|
158
|
+
# response.json(symbolize_names: true) #≈> { foo: "bar" } for "{\"foo\":\"bar\"}" payload
|
159
|
+
def json(*args)
|
160
|
+
decode(Transcoder::JSON, *args)
|
81
161
|
end
|
82
162
|
|
163
|
+
# decodes the response payload into a ruby object **if** the payload is valid
|
164
|
+
# "application/x-www-urlencoded" or "multipart/form-data".
|
83
165
|
def form
|
84
|
-
decode(
|
166
|
+
decode(Transcoder::Form)
|
167
|
+
end
|
168
|
+
|
169
|
+
# decodes the response payload into a Nokogiri::XML::Node object **if** the payload is valid
|
170
|
+
# "application/xml" (requires the "nokogiri" gem).
|
171
|
+
def xml
|
172
|
+
decode(Transcoder::Xml)
|
85
173
|
end
|
86
174
|
|
87
175
|
private
|
88
176
|
|
89
|
-
|
177
|
+
# decodes the response payload using the given +transcoder+, which implements the decoding logic.
|
178
|
+
#
|
179
|
+
# +transcoder+ must implement the internal transcoder API, i.e. respond to <tt>decode(HTTPX::Response response)</tt>,
|
180
|
+
# which returns a decoder which responds to <tt>call(HTTPX::Response response, **kwargs)</tt>
|
181
|
+
def decode(transcoder, *args)
|
90
182
|
# TODO: check if content-type is a valid format, i.e. "application/json" for json parsing
|
91
|
-
transcoder = Transcoder.registry(format)
|
92
|
-
|
93
|
-
raise Error, "no decoder available for \"#{format}\"" unless transcoder.respond_to?(:decode)
|
94
183
|
|
95
184
|
decoder = transcoder.decode(self)
|
96
185
|
|
97
|
-
raise Error, "no decoder available for \"#{
|
98
|
-
|
99
|
-
decoder.call(self, options)
|
100
|
-
rescue Registry::Error
|
101
|
-
raise Error, "no decoder available for \"#{format}\""
|
102
|
-
end
|
103
|
-
|
104
|
-
def no_data?
|
105
|
-
@status < 200 ||
|
106
|
-
@status == 204 ||
|
107
|
-
@status == 205 ||
|
108
|
-
@status == 304 || begin
|
109
|
-
content_length = @headers["content-length"]
|
110
|
-
return false if content_length.nil?
|
111
|
-
|
112
|
-
content_length == "0"
|
113
|
-
end
|
114
|
-
end
|
115
|
-
|
116
|
-
class Body
|
117
|
-
def initialize(response, options)
|
118
|
-
@response = response
|
119
|
-
@headers = response.headers
|
120
|
-
@options = options
|
121
|
-
@threshold_size = options.body_threshold_size
|
122
|
-
@window_size = options.window_size
|
123
|
-
@encoding = response.content_type.charset || Encoding::BINARY
|
124
|
-
@length = 0
|
125
|
-
@buffer = nil
|
126
|
-
@state = :idle
|
127
|
-
end
|
128
|
-
|
129
|
-
def closed?
|
130
|
-
@state == :closed
|
131
|
-
end
|
132
|
-
|
133
|
-
def write(chunk)
|
134
|
-
return if @state == :closed
|
135
|
-
|
136
|
-
@length += chunk.bytesize
|
137
|
-
transition
|
138
|
-
@buffer.write(chunk)
|
139
|
-
end
|
140
|
-
|
141
|
-
def read(*args)
|
142
|
-
return unless @buffer
|
143
|
-
|
144
|
-
rewind
|
145
|
-
|
146
|
-
@buffer.read(*args)
|
147
|
-
end
|
148
|
-
|
149
|
-
def bytesize
|
150
|
-
@length
|
151
|
-
end
|
152
|
-
|
153
|
-
def each
|
154
|
-
return enum_for(__method__) unless block_given?
|
155
|
-
|
156
|
-
begin
|
157
|
-
if @buffer
|
158
|
-
rewind
|
159
|
-
while (chunk = @buffer.read(@window_size))
|
160
|
-
yield(chunk.force_encoding(@encoding))
|
161
|
-
end
|
162
|
-
end
|
163
|
-
ensure
|
164
|
-
close
|
165
|
-
end
|
166
|
-
end
|
167
|
-
|
168
|
-
def to_s
|
169
|
-
case @buffer
|
170
|
-
when StringIO
|
171
|
-
begin
|
172
|
-
@buffer.string.force_encoding(@encoding)
|
173
|
-
rescue ArgumentError
|
174
|
-
@buffer.string
|
175
|
-
end
|
176
|
-
when Tempfile
|
177
|
-
rewind
|
178
|
-
content = _with_same_buffer_pos { @buffer.read }
|
179
|
-
begin
|
180
|
-
content.force_encoding(@encoding)
|
181
|
-
rescue ArgumentError # ex: unknown encoding name - utf
|
182
|
-
content
|
183
|
-
end
|
184
|
-
when nil
|
185
|
-
"".b
|
186
|
-
else
|
187
|
-
@buffer
|
188
|
-
end
|
189
|
-
end
|
190
|
-
alias_method :to_str, :to_s
|
191
|
-
|
192
|
-
def empty?
|
193
|
-
@length.zero?
|
194
|
-
end
|
195
|
-
|
196
|
-
def copy_to(dest)
|
197
|
-
return unless @buffer
|
198
|
-
|
199
|
-
rewind
|
186
|
+
raise Error, "no decoder available for \"#{transcoder}\"" unless decoder
|
200
187
|
|
201
|
-
|
202
|
-
FileUtils.mv(@buffer.path, dest.path)
|
203
|
-
else
|
204
|
-
::IO.copy_stream(@buffer, dest)
|
205
|
-
end
|
206
|
-
end
|
207
|
-
|
208
|
-
# closes/cleans the buffer, resets everything
|
209
|
-
def close
|
210
|
-
if @buffer
|
211
|
-
@buffer.close
|
212
|
-
@buffer.unlink if @buffer.respond_to?(:unlink)
|
213
|
-
@buffer = nil
|
214
|
-
end
|
215
|
-
@length = 0
|
216
|
-
@state = :closed
|
217
|
-
end
|
218
|
-
|
219
|
-
def ==(other)
|
220
|
-
object_id == other.object_id || begin
|
221
|
-
if other.respond_to?(:read)
|
222
|
-
_with_same_buffer_pos { FileUtils.compare_stream(@buffer, other) }
|
223
|
-
else
|
224
|
-
to_s == other.to_s
|
225
|
-
end
|
226
|
-
end
|
227
|
-
end
|
228
|
-
|
229
|
-
# :nocov:
|
230
|
-
def inspect
|
231
|
-
"#<HTTPX::Response::Body:#{object_id} " \
|
232
|
-
"@state=#{@state} " \
|
233
|
-
"@length=#{@length}>"
|
234
|
-
end
|
235
|
-
# :nocov:
|
236
|
-
|
237
|
-
private
|
238
|
-
|
239
|
-
def rewind
|
240
|
-
return unless @buffer
|
241
|
-
|
242
|
-
@buffer.rewind
|
243
|
-
end
|
244
|
-
|
245
|
-
def transition
|
246
|
-
case @state
|
247
|
-
when :idle
|
248
|
-
if @length > @threshold_size
|
249
|
-
@state = :buffer
|
250
|
-
@buffer = Tempfile.new("httpx", encoding: Encoding::BINARY, mode: File::RDWR)
|
251
|
-
else
|
252
|
-
@state = :memory
|
253
|
-
@buffer = StringIO.new("".b)
|
254
|
-
end
|
255
|
-
when :memory
|
256
|
-
# @type ivar @buffer: StringIO | Tempfile
|
257
|
-
if @length > @threshold_size
|
258
|
-
aux = @buffer
|
259
|
-
@buffer = Tempfile.new("httpx", encoding: Encoding::BINARY, mode: File::RDWR)
|
260
|
-
aux.rewind
|
261
|
-
::IO.copy_stream(aux, @buffer)
|
262
|
-
# (this looks like a bug from Ruby < 2.3
|
263
|
-
@buffer.pos = aux.pos ##################
|
264
|
-
########################################
|
265
|
-
aux.close
|
266
|
-
@state = :buffer
|
267
|
-
end
|
268
|
-
end
|
188
|
+
@body.rewind
|
269
189
|
|
270
|
-
|
271
|
-
end
|
272
|
-
|
273
|
-
def _with_same_buffer_pos
|
274
|
-
return yield unless @buffer && @buffer.respond_to?(:pos)
|
275
|
-
|
276
|
-
# @type ivar @buffer: StringIO | Tempfile
|
277
|
-
current_pos = @buffer.pos
|
278
|
-
@buffer.rewind
|
279
|
-
begin
|
280
|
-
yield
|
281
|
-
ensure
|
282
|
-
@buffer.pos = current_pos
|
283
|
-
end
|
284
|
-
end
|
190
|
+
decoder.call(self, *args)
|
285
191
|
end
|
286
192
|
end
|
287
193
|
|
194
|
+
# Helper class which decodes the HTTP "content-type" header.
|
288
195
|
class ContentType
|
289
196
|
MIME_TYPE_RE = %r{^([^/]+/[^;]+)(?:$|;)}.freeze
|
290
197
|
CHARSET_RE = /;\s*charset=([^;]+)/i.freeze
|
@@ -293,6 +200,9 @@ module HTTPX
|
|
293
200
|
@header_value = header_value
|
294
201
|
end
|
295
202
|
|
203
|
+
# returns the mime type declared in the header.
|
204
|
+
#
|
205
|
+
# ContentType.new("application/json; charset=utf-8").mime_type #=> "application/json"
|
296
206
|
def mime_type
|
297
207
|
return @mime_type if defined?(@mime_type)
|
298
208
|
|
@@ -300,6 +210,10 @@ module HTTPX
|
|
300
210
|
m && @mime_type = m.strip.downcase
|
301
211
|
end
|
302
212
|
|
213
|
+
# returns the charset declared in the header.
|
214
|
+
#
|
215
|
+
# ContentType.new("application/json; charset=utf-8").charset #=> "utf-8"
|
216
|
+
# ContentType.new("text/plain").charset #=> nil
|
303
217
|
def charset
|
304
218
|
return @charset if defined?(@charset)
|
305
219
|
|
@@ -308,38 +222,66 @@ module HTTPX
|
|
308
222
|
end
|
309
223
|
end
|
310
224
|
|
225
|
+
# Wraps an error which has happened while processing an HTTP Request. It has partial
|
226
|
+
# public API parity with HTTPX::Response, so users should rely on it to infer whether
|
227
|
+
# the returned response is one or the other.
|
228
|
+
#
|
229
|
+
# response = HTTPX.get("https://some-domain/path") #=> response is HTTPX::Response or HTTPX::ErrorResponse
|
230
|
+
# response.raise_for_status #=> raises if it wraps an error
|
311
231
|
class ErrorResponse
|
312
232
|
include Loggable
|
233
|
+
extend Forwardable
|
313
234
|
|
314
|
-
|
235
|
+
# the corresponding HTTPX::Request instance.
|
236
|
+
attr_reader :request
|
237
|
+
|
238
|
+
# the HTTPX::Response instance, when there is one (i.e. error happens fetching the response).
|
239
|
+
attr_reader :response
|
240
|
+
|
241
|
+
# the wrapped exception.
|
242
|
+
attr_reader :error
|
243
|
+
|
244
|
+
# the request uri
|
245
|
+
def_delegator :@request, :uri
|
315
246
|
|
316
|
-
|
247
|
+
# the IP address of the peer server.
|
248
|
+
def_delegator :@request, :peer_address
|
249
|
+
|
250
|
+
def initialize(request, error)
|
317
251
|
@request = request
|
252
|
+
@response = request.response if request.response.is_a?(Response)
|
318
253
|
@error = error
|
319
|
-
@options =
|
254
|
+
@options = request.options
|
320
255
|
log_exception(@error)
|
321
256
|
end
|
322
257
|
|
323
|
-
|
324
|
-
|
325
|
-
@error.
|
258
|
+
# returns the exception full message.
|
259
|
+
def to_s
|
260
|
+
@error.full_message(highlight: false)
|
261
|
+
end
|
262
|
+
|
263
|
+
# closes the error resources.
|
264
|
+
def close
|
265
|
+
@response.close if @response && @response.respond_to?(:close)
|
326
266
|
end
|
327
267
|
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
end
|
332
|
-
else
|
333
|
-
def to_s
|
334
|
-
"#{@error.message} (#{@error.class})\n" \
|
335
|
-
"#{@error.backtrace.join("\n") if @error.backtrace}"
|
336
|
-
end
|
268
|
+
# always true for error responses.
|
269
|
+
def finished?
|
270
|
+
true
|
337
271
|
end
|
338
272
|
|
273
|
+
# raises the wrapped exception.
|
339
274
|
def raise_for_status
|
340
275
|
raise @error
|
341
276
|
end
|
277
|
+
|
278
|
+
# buffers lost chunks to error response
|
279
|
+
def <<(data)
|
280
|
+
@response << data
|
281
|
+
end
|
342
282
|
end
|
343
283
|
end
|
344
284
|
|
345
|
-
|
285
|
+
require_relative "response/body"
|
286
|
+
require_relative "response/buffer"
|
287
|
+
require_relative "pmatch_extensions" if RUBY_VERSION >= "2.7.0"
|
data/lib/httpx/selector.rb
CHANGED
@@ -9,8 +9,6 @@ class HTTPX::Selector
|
|
9
9
|
private_constant :READABLE
|
10
10
|
private_constant :WRITABLE
|
11
11
|
|
12
|
-
using HTTPX::IOExtensions
|
13
|
-
|
14
12
|
def initialize
|
15
13
|
@selectables = []
|
16
14
|
end
|
@@ -74,7 +72,10 @@ class HTTPX::Selector
|
|
74
72
|
|
75
73
|
readers, writers = IO.select(r, w, nil, interval)
|
76
74
|
|
77
|
-
|
75
|
+
if readers.nil? && writers.nil? && interval
|
76
|
+
[*r, *w].each { |io| io.handle_socket_timeout(interval) }
|
77
|
+
return
|
78
|
+
end
|
78
79
|
rescue IOError, SystemCallError
|
79
80
|
@selectables.reject!(&:closed?)
|
80
81
|
retry
|
@@ -108,7 +109,11 @@ class HTTPX::Selector
|
|
108
109
|
when nil then return
|
109
110
|
end
|
110
111
|
|
111
|
-
|
112
|
+
unless result || interval.nil?
|
113
|
+
io.handle_socket_timeout(interval)
|
114
|
+
return
|
115
|
+
end
|
116
|
+
# raise HTTPX::TimeoutError.new(interval, "timed out while waiting on select")
|
112
117
|
|
113
118
|
yield io
|
114
119
|
rescue IOError, SystemCallError
|