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
@@ -0,0 +1,154 @@
1
+ # frozen_string_literal: true
2
+
3
+ module HTTPX
4
+ # Implementation of the HTTP Request body as a delegator which iterates (responds to +each+) payload chunks.
5
+ class Request::Body < SimpleDelegator
6
+ class << self
7
+ def new(_, options, body: nil, **params)
8
+ if body.is_a?(self)
9
+ # request derives its options from body
10
+ body.options = options.merge(params)
11
+ return body
12
+ end
13
+
14
+ super
15
+ end
16
+ end
17
+
18
+ attr_accessor :options
19
+
20
+ # inits the instance with the request +headers+, +options+ and +params+, which contain the payload definition.
21
+ # it wraps the given body with the appropriate encoder on initialization.
22
+ #
23
+ # ..., json: { foo: "bar" }) #=> json encoder
24
+ # ..., form: { foo: "bar" }) #=> form urlencoded encoder
25
+ # ..., form: { foo: Pathname.open("path/to/file") }) #=> multipart urlencoded encoder
26
+ # ..., form: { foo: File.open("path/to/file") }) #=> multipart urlencoded encoder
27
+ # ..., form: { body: "bla") }) #=> raw data encoder
28
+ def initialize(headers, options, body: nil, form: nil, json: nil, xml: nil, **params)
29
+ @headers = headers
30
+ @options = options.merge(params)
31
+
32
+ @body = if body
33
+ Transcoder::Body.encode(body)
34
+ elsif form
35
+ Transcoder::Form.encode(form)
36
+ elsif json
37
+ Transcoder::JSON.encode(json)
38
+ elsif xml
39
+ Transcoder::Xml.encode(xml)
40
+ end
41
+
42
+ if @body
43
+ if @options.compress_request_body && @headers.key?("content-encoding")
44
+
45
+ @headers.get("content-encoding").each do |encoding|
46
+ @body = self.class.initialize_deflater_body(@body, encoding)
47
+ end
48
+ end
49
+
50
+ @headers["content-type"] ||= @body.content_type
51
+ @headers["content-length"] = @body.bytesize unless unbounded_body?
52
+ end
53
+
54
+ super(@body)
55
+ end
56
+
57
+ # consumes and yields the request payload in chunks.
58
+ def each(&block)
59
+ return enum_for(__method__) unless block
60
+ return if @body.nil?
61
+
62
+ body = stream(@body)
63
+ if body.respond_to?(:read)
64
+ ::IO.copy_stream(body, ProcIO.new(block))
65
+ elsif body.respond_to?(:each)
66
+ body.each(&block)
67
+ else
68
+ block[body.to_s]
69
+ end
70
+ end
71
+
72
+ # if the +@body+ is rewindable, it rewinnds it.
73
+ def rewind
74
+ return if empty?
75
+
76
+ @body.rewind if @body.respond_to?(:rewind)
77
+ end
78
+
79
+ # return +true+ if the +body+ has been fully drained (or does nnot exist).
80
+ def empty?
81
+ return true if @body.nil?
82
+ return false if chunked?
83
+
84
+ @body.bytesize.zero?
85
+ end
86
+
87
+ # returns the +@body+ payload size in bytes.
88
+ def bytesize
89
+ return 0 if @body.nil?
90
+
91
+ @body.bytesize
92
+ end
93
+
94
+ # sets the body to yield using chunked trannsfer encoding format.
95
+ def stream(body)
96
+ return body unless chunked?
97
+
98
+ Transcoder::Chunker.encode(body.enum_for(:each))
99
+ end
100
+
101
+ # returns whether the body yields infinitely.
102
+ def unbounded_body?
103
+ return @unbounded_body if defined?(@unbounded_body)
104
+
105
+ @unbounded_body = !@body.nil? && (chunked? || @body.bytesize == Float::INFINITY)
106
+ end
107
+
108
+ # returns whether the chunked transfer encoding header is set.
109
+ def chunked?
110
+ @headers["transfer-encoding"] == "chunked"
111
+ end
112
+
113
+ # sets the chunked transfer encoding header.
114
+ def chunk!
115
+ @headers.add("transfer-encoding", "chunked")
116
+ end
117
+
118
+ # :nocov:
119
+ def inspect
120
+ "#<HTTPX::Request::Body:#{object_id} " \
121
+ "#{unbounded_body? ? "stream" : "@bytesize=#{bytesize}"}>"
122
+ end
123
+ # :nocov:
124
+
125
+ class << self
126
+ # returns the +body+ wrapped with the correct deflater accordinng to the given +encodisng+.
127
+ def initialize_deflater_body(body, encoding)
128
+ case encoding
129
+ when "gzip"
130
+ Transcoder::GZIP.encode(body)
131
+ when "deflate"
132
+ Transcoder::Deflate.encode(body)
133
+ when "identity"
134
+ body
135
+ else
136
+ body
137
+ end
138
+ end
139
+ end
140
+ end
141
+
142
+ # Wrapper yielder which can be used with functions which expect an IO writer.
143
+ class ProcIO
144
+ def initialize(block)
145
+ @block = block
146
+ end
147
+
148
+ # Implementation the IO write protocol, which yield the given chunk to +@block+.
149
+ def write(data)
150
+ @block.call(data.dup)
151
+ data.bytesize
152
+ end
153
+ end
154
+ end
data/lib/httpx/request.rb CHANGED
@@ -4,47 +4,85 @@ require "delegate"
4
4
  require "forwardable"
5
5
 
6
6
  module HTTPX
7
+ # Defines how an HTTP request is handled internally, both in terms of making attributes accessible,
8
+ # as well as maintaining the state machine which manages streaming the request onto the wire.
7
9
  class Request
8
10
  extend Forwardable
9
11
  include Callbacks
10
12
  using URIExtensions
11
13
 
12
- METHODS = [
13
- # RFC 2616: Hypertext Transfer Protocol -- HTTP/1.1
14
- :options, :get, :head, :post, :put, :delete, :trace, :connect,
15
-
16
- # RFC 2518: HTTP Extensions for Distributed Authoring -- WEBDAV
17
- :propfind, :proppatch, :mkcol, :copy, :move, :lock, :unlock,
14
+ # default value used for "user-agent" header, when not overridden.
15
+ USER_AGENT = "httpx.rb/#{VERSION}"
18
16
 
19
- # RFC 3648: WebDAV Ordered Collections Protocol
20
- :orderpatch,
17
+ # the upcased string HTTP verb for this request.
18
+ attr_reader :verb
21
19
 
22
- # RFC 3744: WebDAV Access Control Protocol
23
- :acl,
20
+ # the absolute URI object for this request.
21
+ attr_reader :uri
24
22
 
25
- # RFC 6352: vCard Extensions to WebDAV -- CardDAV
26
- :report,
23
+ # an HTTPX::Headers object containing the request HTTP headers.
24
+ attr_reader :headers
27
25
 
28
- # RFC 5789: PATCH Method for HTTP
29
- :patch,
26
+ # an HTTPX::Request::Body object containing the request body payload (or +nil+, whenn there is none).
27
+ attr_reader :body
30
28
 
31
- # draft-reschke-webdav-search: WebDAV Search
32
- :search
33
- ].freeze
29
+ # a symbol describing which frame is currently being flushed.
30
+ attr_reader :state
34
31
 
35
- USER_AGENT = "httpx.rb/#{VERSION}"
32
+ # an HTTPX::Options object containing request options.
33
+ attr_reader :options
36
34
 
37
- attr_reader :verb, :uri, :headers, :body, :state, :options, :response
35
+ # the corresponding HTTPX::Response object, when there is one.
36
+ attr_reader :response
38
37
 
39
- # Exception raised during enumerable body writes
38
+ # Exception raised during enumerable body writes.
40
39
  attr_reader :drain_error
41
40
 
41
+ # The IP address from the peer server.
42
+ attr_accessor :peer_address
43
+
44
+ attr_writer :persistent
45
+
46
+ # will be +true+ when request body has been completely flushed.
42
47
  def_delegator :@body, :empty?
43
48
 
44
- def initialize(verb, uri, options = {})
45
- @verb = verb.to_s.downcase.to_sym
46
- @options = Options.new(options)
49
+ # initializes the instance with the given +verb+ (an upppercase String, ex. 'GEt'),
50
+ # an absolute or relative +uri+ (either as String or URI::HTTP object), the
51
+ # request +options+ (instance of HTTPX::Options) and an optional Hash of +params+.
52
+ #
53
+ # Besides any of the options documented in HTTPX::Options (which would override or merge with what
54
+ # +options+ sets), it accepts also the following:
55
+ #
56
+ # :params :: hash or array of key-values which will be encoded and set in the query string of request uris.
57
+ # :body :: to be encoded in the request body payload. can be a String, an IO object (i.e. a File), or an Enumerable.
58
+ # :form :: hash of array of key-values which will be form-urlencoded- or multipart-encoded in requests body payload.
59
+ # :json :: hash of array of key-values which will be JSON-encoded in requests body payload.
60
+ # :xml :: Nokogiri XML nodes which will be encoded in requests body payload.
61
+ #
62
+ # :body, :form, :json and :xml are all mutually exclusive, i.e. only one of them gets picked up.
63
+ def initialize(verb, uri, options, params = EMPTY_HASH)
64
+ @verb = verb.to_s.upcase
47
65
  @uri = Utils.to_uri(uri)
66
+
67
+ @headers = options.headers.dup
68
+ merge_headers(params.delete(:headers)) if params.key?(:headers)
69
+
70
+ @headers["user-agent"] ||= USER_AGENT
71
+ @headers["accept"] ||= "*/*"
72
+
73
+ # forego compression in the Range request case
74
+ if @headers.key?("range")
75
+ @headers.delete("accept-encoding")
76
+ else
77
+ @headers["accept-encoding"] ||= options.supported_compression_formats
78
+ end
79
+
80
+ @query_params = params.delete(:params) if params.key?(:params)
81
+
82
+ @body = options.request_body_class.new(@headers, options, **params)
83
+
84
+ @options = @body.options
85
+
48
86
  if @uri.relative?
49
87
  origin = @options.origin
50
88
  raise(Error, "invalid URI: #{@uri}") unless origin
@@ -54,92 +92,131 @@ module HTTPX
54
92
  @uri = origin.merge("#{base_path}#{@uri}")
55
93
  end
56
94
 
57
- raise(Error, "unknown method: #{verb}") unless METHODS.include?(@verb)
95
+ @state = :idle
96
+ @response = nil
97
+ @peer_address = nil
98
+ @persistent = @options.persistent
99
+ end
58
100
 
59
- @headers = @options.headers_class.new(@options.headers)
60
- @headers["user-agent"] ||= USER_AGENT
61
- @headers["accept"] ||= "*/*"
101
+ # the read timeout defined for this requet.
102
+ def read_timeout
103
+ @options.timeout[:read_timeout]
104
+ end
62
105
 
63
- @body = @options.request_body_class.new(@headers, @options)
64
- @state = :idle
106
+ # the write timeout defined for this requet.
107
+ def write_timeout
108
+ @options.timeout[:write_timeout]
65
109
  end
66
110
 
111
+ # the request timeout defined for this requet.
112
+ def request_timeout
113
+ @options.timeout[:request_timeout]
114
+ end
115
+
116
+ def persistent?
117
+ @persistent
118
+ end
119
+
120
+ # if the request contains trailer headers
67
121
  def trailers?
68
122
  defined?(@trailers)
69
123
  end
70
124
 
125
+ # returns an instance of HTTPX::Headers containing the trailer headers
71
126
  def trailers
72
127
  @trailers ||= @options.headers_class.new
73
128
  end
74
129
 
130
+ # returns +:r+ or +:w+, depending on whether the request is waiting for a response or flushing.
75
131
  def interests
76
132
  return :r if @state == :done || @state == :expect
77
133
 
78
134
  :w
79
135
  end
80
136
 
81
- if RUBY_VERSION < "2.2"
82
- URIParser = URI::DEFAULT_PARSER
83
-
84
- def initialize_with_escape(verb, uri, options = {})
85
- initialize_without_escape(verb, URIParser.escape(uri.to_s), options)
86
- end
87
- alias_method :initialize_without_escape, :initialize
88
- alias_method :initialize, :initialize_with_escape
89
- end
90
-
137
+ # merges +h+ into the instance of HTTPX::Headers of the request.
91
138
  def merge_headers(h)
92
139
  @headers = @headers.merge(h)
93
140
  end
94
141
 
142
+ # the URI scheme of the request +uri+.
95
143
  def scheme
96
144
  @uri.scheme
97
145
  end
98
146
 
147
+ # sets the +response+ on this request.
99
148
  def response=(response)
100
149
  return unless response
101
150
 
102
- if response.is_a?(Response) && response.status == 100
103
- @informational_status = response.status
104
- return
151
+ if response.is_a?(Response) && response.status < 200
152
+ # deal with informational responses
153
+
154
+ if response.status == 100 && @headers.key?("expect")
155
+ @informational_status = response.status
156
+ return
157
+ end
158
+
159
+ # 103 Early Hints advertises resources in document to browsers.
160
+ # not very relevant for an HTTP client, discard.
161
+ return if response.status >= 103
105
162
  end
163
+
106
164
  @response = response
165
+
166
+ emit(:response_started, response)
107
167
  end
108
168
 
169
+ # returnns the URI path of the request +uri+.
109
170
  def path
110
171
  path = uri.path.dup
172
+ path = +"" if path.nil?
111
173
  path << "/" if path.empty?
112
174
  path << "?#{query}" unless query.empty?
113
175
  path
114
176
  end
115
177
 
116
- # https://bugs.ruby-lang.org/issues/15278
178
+ # returs the URI authority of the request.
179
+ #
180
+ # session.build_request("GET", "https://google.com/query").authority #=> "google.com"
181
+ # session.build_request("GET", "http://internal:3182/a").authority #=> "internal:3182"
117
182
  def authority
118
183
  @uri.authority
119
184
  end
120
185
 
121
- # https://bugs.ruby-lang.org/issues/15278
186
+ # returs the URI origin of the request.
187
+ #
188
+ # session.build_request("GET", "https://google.com/query").authority #=> "https://google.com"
189
+ # session.build_request("GET", "http://internal:3182/a").authority #=> "http://internal:3182"
122
190
  def origin
123
191
  @uri.origin
124
192
  end
125
193
 
194
+ # returs the URI query string of the request (when available).
195
+ #
196
+ # session.build_request("GET", "https://search.com").query #=> ""
197
+ # session.build_request("GET", "https://search.com?q=a").query #=> "q=a"
198
+ # session.build_request("GET", "https://search.com", params: { q: "a"}).query #=> "q=a"
199
+ # session.build_request("GET", "https://search.com?q=a", params: { foo: "bar"}).query #=> "q=a&foo&bar"
126
200
  def query
127
201
  return @query if defined?(@query)
128
202
 
129
203
  query = []
130
- if (q = @options.params)
131
- query << Transcoder.registry("form").encode(q)
204
+ if (q = @query_params)
205
+ query << Transcoder::Form.encode(q)
132
206
  end
133
207
  query << @uri.query if @uri.query
134
208
  @query = query.join("&")
135
209
  end
136
210
 
211
+ # consumes and returns the next available chunk of request body that can be sent
137
212
  def drain_body
138
213
  return nil if @body.nil?
139
214
 
140
215
  @drainer ||= @body.each
141
- chunk = @drainer.next
142
- chunk.dup
216
+ chunk = @drainer.next.dup
217
+
218
+ emit(:body_chunk, chunk)
219
+ chunk
143
220
  rescue StopIteration
144
221
  nil
145
222
  rescue StandardError => e
@@ -150,99 +227,14 @@ module HTTPX
150
227
  # :nocov:
151
228
  def inspect
152
229
  "#<HTTPX::Request:#{object_id} " \
153
- "#{@verb.to_s.upcase} " \
230
+ "#{@verb} " \
154
231
  "#{uri} " \
155
232
  "@headers=#{@headers} " \
156
233
  "@body=#{@body}>"
157
234
  end
158
235
  # :nocov:
159
236
 
160
- class Body < SimpleDelegator
161
- class << self
162
- def new(_, options)
163
- return options.body if options.body.is_a?(self)
164
-
165
- super
166
- end
167
- end
168
-
169
- def initialize(headers, options)
170
- @headers = headers
171
- @body = if options.body
172
- Transcoder.registry("body").encode(options.body)
173
- elsif options.form
174
- Transcoder.registry("form").encode(options.form)
175
- elsif options.json
176
- Transcoder.registry("json").encode(options.json)
177
- end
178
- return if @body.nil?
179
-
180
- @headers["content-type"] ||= @body.content_type
181
- @headers["content-length"] = @body.bytesize unless unbounded_body?
182
- super(@body)
183
- end
184
-
185
- def each(&block)
186
- return enum_for(__method__) unless block
187
- return if @body.nil?
188
-
189
- body = stream(@body)
190
- if body.respond_to?(:read)
191
- ::IO.copy_stream(body, ProcIO.new(block))
192
- elsif body.respond_to?(:each)
193
- body.each(&block)
194
- else
195
- block[body.to_s]
196
- end
197
- end
198
-
199
- def rewind
200
- return if empty?
201
-
202
- @body.rewind if @body.respond_to?(:rewind)
203
- end
204
-
205
- def empty?
206
- return true if @body.nil?
207
- return false if chunked?
208
-
209
- @body.bytesize.zero?
210
- end
211
-
212
- def bytesize
213
- return 0 if @body.nil?
214
-
215
- @body.bytesize
216
- end
217
-
218
- def stream(body)
219
- encoded = body
220
- encoded = Transcoder.registry("chunker").encode(body.enum_for(:each)) if chunked?
221
- encoded
222
- end
223
-
224
- def unbounded_body?
225
- return @unbounded_body if defined?(@unbounded_body)
226
-
227
- @unbounded_body = !@body.nil? && (chunked? || @body.bytesize == Float::INFINITY)
228
- end
229
-
230
- def chunked?
231
- @headers["transfer-encoding"] == "chunked"
232
- end
233
-
234
- def chunk!
235
- @headers.add("transfer-encoding", "chunked")
236
- end
237
-
238
- # :nocov:
239
- def inspect
240
- "#<HTTPX::Request::Body:#{object_id} " \
241
- "#{unbounded_body? ? "stream" : "@bytesize=#{bytesize}"}>"
242
- end
243
- # :nocov:
244
- end
245
-
237
+ # moves on to the +nextstate+ of the request state machine (when all preconditions are met)
246
238
  def transition(nextstate)
247
239
  case nextstate
248
240
  when :idle
@@ -277,19 +269,11 @@ module HTTPX
277
269
  nil
278
270
  end
279
271
 
272
+ # whether the request supports the 100-continue handshake and already processed the 100 response.
280
273
  def expects?
281
274
  @headers["expect"] == "100-continue" && @informational_status == 100 && !@response
282
275
  end
283
-
284
- class ProcIO
285
- def initialize(block)
286
- @block = block
287
- end
288
-
289
- def write(data)
290
- @block.call(data.dup)
291
- data.bytesize
292
- end
293
- end
294
276
  end
295
277
  end
278
+
279
+ require_relative "request/body"