httpx 0.20.0 → 1.3.1

Sign up to get free protection for your applications and to get access to all the features.
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"