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.
Files changed (250) 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 +5 -5
  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_19_8.md +1 -1
  15. data/doc/release_notes/0_20_0.md +2 -2
  16. data/doc/release_notes/0_20_1.md +5 -0
  17. data/doc/release_notes/0_20_2.md +7 -0
  18. data/doc/release_notes/0_20_3.md +6 -0
  19. data/doc/release_notes/0_20_4.md +17 -0
  20. data/doc/release_notes/0_20_5.md +3 -0
  21. data/doc/release_notes/0_21_0.md +96 -0
  22. data/doc/release_notes/0_21_1.md +12 -0
  23. data/doc/release_notes/0_22_0.md +13 -0
  24. data/doc/release_notes/0_22_1.md +11 -0
  25. data/doc/release_notes/0_22_2.md +5 -0
  26. data/doc/release_notes/0_22_3.md +55 -0
  27. data/doc/release_notes/0_22_4.md +6 -0
  28. data/doc/release_notes/0_22_5.md +6 -0
  29. data/doc/release_notes/0_23_0.md +42 -0
  30. data/doc/release_notes/0_23_1.md +5 -0
  31. data/doc/release_notes/0_23_2.md +5 -0
  32. data/doc/release_notes/0_23_3.md +6 -0
  33. data/doc/release_notes/0_23_4.md +5 -0
  34. data/doc/release_notes/0_24_0.md +48 -0
  35. data/doc/release_notes/0_24_1.md +12 -0
  36. data/doc/release_notes/0_24_2.md +12 -0
  37. data/doc/release_notes/0_24_3.md +12 -0
  38. data/doc/release_notes/0_24_4.md +18 -0
  39. data/doc/release_notes/0_24_5.md +6 -0
  40. data/doc/release_notes/0_24_6.md +5 -0
  41. data/doc/release_notes/0_24_7.md +10 -0
  42. data/doc/release_notes/1_0_0.md +60 -0
  43. data/doc/release_notes/1_0_1.md +5 -0
  44. data/doc/release_notes/1_0_2.md +7 -0
  45. data/doc/release_notes/1_1_0.md +32 -0
  46. data/doc/release_notes/1_1_1.md +17 -0
  47. data/doc/release_notes/1_1_2.md +12 -0
  48. data/doc/release_notes/1_1_3.md +18 -0
  49. data/doc/release_notes/1_1_4.md +6 -0
  50. data/doc/release_notes/1_1_5.md +12 -0
  51. data/doc/release_notes/1_2_0.md +49 -0
  52. data/doc/release_notes/1_2_1.md +6 -0
  53. data/doc/release_notes/1_2_2.md +10 -0
  54. data/doc/release_notes/1_2_3.md +16 -0
  55. data/doc/release_notes/1_2_4.md +8 -0
  56. data/doc/release_notes/1_2_5.md +7 -0
  57. data/doc/release_notes/1_2_6.md +13 -0
  58. data/doc/release_notes/1_3_0.md +18 -0
  59. data/doc/release_notes/1_3_1.md +17 -0
  60. data/lib/httpx/adapters/datadog.rb +215 -122
  61. data/lib/httpx/adapters/faraday.rb +145 -107
  62. data/lib/httpx/adapters/sentry.rb +26 -7
  63. data/lib/httpx/adapters/webmock.rb +34 -18
  64. data/lib/httpx/altsvc.rb +63 -26
  65. data/lib/httpx/base64.rb +27 -0
  66. data/lib/httpx/buffer.rb +12 -0
  67. data/lib/httpx/callbacks.rb +5 -3
  68. data/lib/httpx/chainable.rb +54 -39
  69. data/lib/httpx/connection/http1.rb +75 -44
  70. data/lib/httpx/connection/http2.rb +31 -38
  71. data/lib/httpx/connection.rb +287 -117
  72. data/lib/httpx/domain_name.rb +10 -13
  73. data/lib/httpx/errors.rb +52 -2
  74. data/lib/httpx/extensions.rb +24 -131
  75. data/lib/httpx/io/ssl.rb +83 -77
  76. data/lib/httpx/io/tcp.rb +48 -71
  77. data/lib/httpx/io/udp.rb +18 -52
  78. data/lib/httpx/io/unix.rb +10 -15
  79. data/lib/httpx/io.rb +3 -9
  80. data/lib/httpx/loggable.rb +4 -19
  81. data/lib/httpx/options.rb +176 -118
  82. data/lib/httpx/parser/http1.rb +4 -0
  83. data/lib/httpx/plugins/{authentication → auth}/basic.rb +1 -5
  84. data/lib/httpx/plugins/{authentication → auth}/digest.rb +14 -14
  85. data/lib/httpx/plugins/{authentication → auth}/ntlm.rb +1 -3
  86. data/lib/httpx/plugins/{authentication → auth}/socks5.rb +0 -2
  87. data/lib/httpx/plugins/auth.rb +25 -0
  88. data/lib/httpx/plugins/aws_sdk_authentication.rb +4 -3
  89. data/lib/httpx/plugins/aws_sigv4.rb +12 -9
  90. data/lib/httpx/plugins/basic_auth.rb +29 -0
  91. data/lib/httpx/plugins/brotli.rb +50 -0
  92. data/lib/httpx/plugins/callbacks.rb +91 -0
  93. data/lib/httpx/plugins/circuit_breaker/circuit.rb +100 -0
  94. data/lib/httpx/plugins/circuit_breaker/circuit_store.rb +53 -0
  95. data/lib/httpx/plugins/circuit_breaker.rb +148 -0
  96. data/lib/httpx/plugins/cookies/set_cookie_parser.rb +0 -2
  97. data/lib/httpx/plugins/cookies.rb +30 -17
  98. data/lib/httpx/plugins/{digest_authentication.rb → digest_auth.rb} +14 -12
  99. data/lib/httpx/plugins/expect.rb +21 -14
  100. data/lib/httpx/plugins/follow_redirects.rb +140 -41
  101. data/lib/httpx/plugins/grpc/call.rb +2 -3
  102. data/lib/httpx/plugins/grpc/grpc_encoding.rb +88 -0
  103. data/lib/httpx/plugins/grpc/message.rb +7 -37
  104. data/lib/httpx/plugins/grpc.rb +36 -29
  105. data/lib/httpx/plugins/h2c.rb +26 -19
  106. data/lib/httpx/plugins/internal_telemetry.rb +16 -0
  107. data/lib/httpx/plugins/{ntlm_authentication.rb → ntlm_auth.rb} +7 -5
  108. data/lib/httpx/plugins/oauth.rb +175 -0
  109. data/lib/httpx/plugins/persistent.rb +1 -1
  110. data/lib/httpx/plugins/proxy/http.rb +23 -13
  111. data/lib/httpx/plugins/proxy/socks4.rb +9 -7
  112. data/lib/httpx/plugins/proxy/socks5.rb +11 -9
  113. data/lib/httpx/plugins/proxy.rb +80 -61
  114. data/lib/httpx/plugins/push_promise.rb +1 -1
  115. data/lib/httpx/plugins/rate_limiter.rb +5 -1
  116. data/lib/httpx/plugins/response_cache/file_store.rb +40 -0
  117. data/lib/httpx/plugins/response_cache/store.rb +62 -25
  118. data/lib/httpx/plugins/response_cache.rb +105 -12
  119. data/lib/httpx/plugins/retries.rb +87 -17
  120. data/lib/httpx/plugins/ssrf_filter.rb +145 -0
  121. data/lib/httpx/plugins/stream.rb +27 -23
  122. data/lib/httpx/plugins/upgrade/h2.rb +4 -4
  123. data/lib/httpx/plugins/upgrade.rb +8 -10
  124. data/lib/httpx/plugins/webdav.rb +80 -0
  125. data/lib/httpx/pool/synch_pool.rb +93 -0
  126. data/lib/httpx/pool.rb +102 -27
  127. data/lib/httpx/punycode.rb +9 -291
  128. data/lib/httpx/request/body.rb +154 -0
  129. data/lib/httpx/request.rb +130 -146
  130. data/lib/httpx/resolver/https.rb +62 -27
  131. data/lib/httpx/resolver/multi.rb +9 -13
  132. data/lib/httpx/resolver/native.rb +192 -76
  133. data/lib/httpx/resolver/resolver.rb +34 -9
  134. data/lib/httpx/resolver/system.rb +16 -11
  135. data/lib/httpx/resolver.rb +38 -16
  136. data/lib/httpx/response/body.rb +242 -0
  137. data/lib/httpx/response/buffer.rb +96 -0
  138. data/lib/httpx/response.rb +159 -217
  139. data/lib/httpx/selector.rb +9 -4
  140. data/lib/httpx/session.rb +137 -89
  141. data/lib/httpx/session_extensions.rb +4 -1
  142. data/lib/httpx/timers.rb +34 -8
  143. data/lib/httpx/transcoder/body.rb +0 -2
  144. data/lib/httpx/transcoder/chunker.rb +0 -1
  145. data/lib/httpx/transcoder/deflate.rb +37 -0
  146. data/lib/httpx/transcoder/form.rb +52 -33
  147. data/lib/httpx/transcoder/gzip.rb +74 -0
  148. data/lib/httpx/transcoder/json.rb +21 -8
  149. data/lib/httpx/transcoder/multipart/decoder.rb +139 -0
  150. data/lib/httpx/{plugins → transcoder}/multipart/encoder.rb +4 -4
  151. data/lib/httpx/{plugins → transcoder}/multipart/mime_type_detector.rb +1 -1
  152. data/lib/httpx/{plugins → transcoder}/multipart/part.rb +3 -2
  153. data/lib/httpx/transcoder/multipart.rb +17 -0
  154. data/lib/httpx/transcoder/utils/body_reader.rb +46 -0
  155. data/lib/httpx/transcoder/utils/deflater.rb +72 -0
  156. data/lib/httpx/transcoder/utils/inflater.rb +19 -0
  157. data/lib/httpx/transcoder/xml.rb +52 -0
  158. data/lib/httpx/transcoder.rb +5 -6
  159. data/lib/httpx/utils.rb +36 -16
  160. data/lib/httpx/version.rb +1 -1
  161. data/lib/httpx.rb +12 -14
  162. data/sig/altsvc.rbs +33 -0
  163. data/sig/buffer.rbs +2 -1
  164. data/sig/callbacks.rbs +3 -3
  165. data/sig/chainable.rbs +11 -9
  166. data/sig/connection/http1.rbs +8 -7
  167. data/sig/connection/http2.rbs +19 -19
  168. data/sig/connection.rbs +64 -24
  169. data/sig/errors.rbs +22 -3
  170. data/sig/httpx.rbs +5 -4
  171. data/sig/io/ssl.rbs +27 -0
  172. data/sig/io/tcp.rbs +60 -0
  173. data/sig/io/udp.rbs +20 -0
  174. data/sig/io/unix.rbs +27 -0
  175. data/sig/io.rbs +6 -0
  176. data/sig/options.rbs +32 -22
  177. data/sig/parser/http1.rbs +1 -1
  178. data/sig/plugins/{authentication → auth}/basic.rbs +0 -2
  179. data/sig/plugins/{authentication → auth}/digest.rbs +2 -1
  180. data/sig/plugins/auth.rbs +13 -0
  181. data/sig/plugins/{basic_authentication.rbs → basic_auth.rbs} +2 -2
  182. data/sig/plugins/brotli.rbs +22 -0
  183. data/sig/plugins/callbacks.rbs +38 -0
  184. data/sig/plugins/circuit_breaker.rbs +71 -0
  185. data/sig/plugins/compression.rbs +7 -5
  186. data/sig/plugins/cookies/jar.rbs +2 -2
  187. data/sig/plugins/cookies.rbs +2 -0
  188. data/sig/plugins/{digest_authentication.rbs → digest_auth.rbs} +2 -2
  189. data/sig/plugins/follow_redirects.rbs +18 -4
  190. data/sig/plugins/grpc/call.rbs +19 -0
  191. data/sig/plugins/grpc/grpc_encoding.rbs +37 -0
  192. data/sig/plugins/grpc/message.rbs +17 -0
  193. data/sig/plugins/grpc.rbs +7 -32
  194. data/sig/plugins/h2c.rbs +1 -1
  195. data/sig/plugins/{ntlm_authentication.rbs → ntlm_auth.rbs} +2 -2
  196. data/sig/plugins/oauth.rbs +54 -0
  197. data/sig/plugins/proxy/http.rbs +3 -0
  198. data/sig/plugins/proxy/socks4.rbs +9 -6
  199. data/sig/plugins/proxy/socks5.rbs +10 -6
  200. data/sig/plugins/proxy/ssh.rbs +1 -1
  201. data/sig/plugins/proxy.rbs +13 -5
  202. data/sig/plugins/push_promise.rbs +3 -3
  203. data/sig/plugins/rate_limiter.rbs +1 -1
  204. data/sig/plugins/response_cache.rbs +36 -7
  205. data/sig/plugins/retries.rbs +30 -8
  206. data/sig/plugins/stream.rbs +24 -17
  207. data/sig/plugins/upgrade.rbs +5 -3
  208. data/sig/pool.rbs +10 -7
  209. data/sig/request/body.rbs +38 -0
  210. data/sig/request.rbs +15 -24
  211. data/sig/resolver/https.rbs +8 -3
  212. data/sig/resolver/native.rbs +17 -4
  213. data/sig/resolver/resolver.rbs +8 -6
  214. data/sig/resolver/system.rbs +2 -0
  215. data/sig/resolver.rbs +9 -5
  216. data/sig/response/body.rbs +53 -0
  217. data/sig/response/buffer.rbs +24 -0
  218. data/sig/response.rbs +24 -39
  219. data/sig/selector.rbs +1 -1
  220. data/sig/session.rbs +29 -18
  221. data/sig/timers.rbs +18 -8
  222. data/sig/transcoder/body.rbs +4 -3
  223. data/sig/transcoder/deflate.rbs +11 -0
  224. data/sig/transcoder/form.rbs +5 -3
  225. data/sig/transcoder/gzip.rbs +24 -0
  226. data/sig/transcoder/json.rbs +8 -3
  227. data/sig/{plugins → transcoder}/multipart.rbs +15 -19
  228. data/sig/transcoder/utils/body_reader.rbs +15 -0
  229. data/sig/transcoder/utils/deflater.rbs +29 -0
  230. data/sig/transcoder/utils/inflater.rbs +12 -0
  231. data/sig/transcoder/xml.rbs +22 -0
  232. data/sig/transcoder.rbs +24 -9
  233. data/sig/utils.rbs +8 -2
  234. metadata +163 -41
  235. data/lib/httpx/plugins/authentication.rb +0 -20
  236. data/lib/httpx/plugins/basic_authentication.rb +0 -30
  237. data/lib/httpx/plugins/compression/brotli.rb +0 -54
  238. data/lib/httpx/plugins/compression/deflate.rb +0 -49
  239. data/lib/httpx/plugins/compression/gzip.rb +0 -88
  240. data/lib/httpx/plugins/compression.rb +0 -164
  241. data/lib/httpx/plugins/multipart/decoder.rb +0 -187
  242. data/lib/httpx/plugins/multipart.rb +0 -84
  243. data/lib/httpx/registry.rb +0 -85
  244. data/sig/plugins/authentication.rbs +0 -11
  245. data/sig/plugins/compression/brotli.rbs +0 -21
  246. data/sig/plugins/compression/deflate.rbs +0 -17
  247. data/sig/plugins/compression/gzip.rbs +0 -29
  248. data/sig/registry.rbs +0 -12
  249. /data/sig/plugins/{authentication → auth}/ntlm.rbs +0 -0
  250. /data/sig/plugins/{authentication → auth}/socks5.rbs +0 -0
@@ -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
- attr_reader :status, :headers, :body, :version
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
- def bodyless?
45
- @request.verb == :head ||
46
- no_data?
47
- end
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 == :connect && @status == 200)
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
- def json(options = nil)
80
- decode("json", options)
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("form")
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
- def decode(format, options = nil)
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 \"#{format}\"" unless decoder
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
- if dest.respond_to?(:path) && @buffer.respond_to?(:path)
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
- return unless %i[memory buffer].include?(@state)
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
- attr_reader :request, :error
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
- def initialize(request, error, options)
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 = Options.new(options)
254
+ @options = request.options
320
255
  log_exception(@error)
321
256
  end
322
257
 
323
- def status
324
- warn ":#{__method__} is deprecated, use :error.message instead"
325
- @error.message
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
- if Exception.method_defined?(:full_message)
329
- def to_s
330
- @error.full_message(highlight: false)
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
- require "httpx/pmatch_extensions" if RUBY_VERSION >= "3.0.0"
285
+ require_relative "response/body"
286
+ require_relative "response/buffer"
287
+ 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
@@ -74,7 +72,10 @@ class HTTPX::Selector
74
72
 
75
73
  readers, writers = IO.select(r, w, nil, interval)
76
74
 
77
- raise HTTPX::TimeoutError.new(interval, "timed out while waiting on select") if readers.nil? && writers.nil? && interval
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
- raise HTTPX::TimeoutError.new(interval, "timed out while waiting on select") unless result || interval.nil?
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