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