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
@@ -4,6 +4,7 @@ module HTTPX
4
4
  module Callbacks
5
5
  def on(type, &action)
6
6
  callbacks(type) << action
7
+ self
7
8
  end
8
9
 
9
10
  def once(type, &block)
@@ -11,6 +12,7 @@ module HTTPX
11
12
  block.call(*args, &callback)
12
13
  :delete
13
14
  end
15
+ self
14
16
  end
15
17
 
16
18
  def only(type, &block)
@@ -22,12 +24,12 @@ module HTTPX
22
24
  callbacks(type).delete_if { |pr| :delete == pr.call(*args) } # rubocop:disable Style/YodaCondition
23
25
  end
24
26
 
25
- protected
26
-
27
27
  def callbacks_for?(type)
28
- @callbacks.key?(type) && !@callbacks[type].empty?
28
+ @callbacks.key?(type) && @callbacks[type].any?
29
29
  end
30
30
 
31
+ protected
32
+
31
33
  def callbacks(type = nil)
32
34
  return @callbacks unless type
33
35
 
@@ -1,89 +1,104 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module HTTPX
4
+ # Session mixin, implements most of the APIs that the users call.
5
+ # delegates to a default session when extended.
4
6
  module Chainable
5
- %i[head get post put delete trace options connect patch].each do |meth|
7
+ %w[head get post put delete trace options connect patch].each do |meth|
6
8
  class_eval(<<-MOD, __FILE__, __LINE__ + 1)
7
- def #{meth}(*uri, **options) # def get(*uri, **options)
8
- request(:#{meth}, uri, **options) # request(:get, uri, **options)
9
- end # end
9
+ def #{meth}(*uri, **options) # def get(*uri, **options)
10
+ request("#{meth.upcase}", uri, **options) # request("GET", uri, **options)
11
+ end # end
10
12
  MOD
11
13
  end
12
14
 
15
+ # delegates to the default session (see HTTPX::Session#request).
13
16
  def request(*args, **options)
14
17
  branch(default_options).request(*args, **options)
15
18
  end
16
19
 
17
- # :nocov:
18
- def timeout(**args)
19
- warn ":#{__method__} is deprecated, use :with_timeout instead"
20
- with(timeout: args)
21
- end
22
-
23
- def headers(headers)
24
- warn ":#{__method__} is deprecated, use :with_headers instead"
25
- with(headers: headers)
26
- end
27
- # :nocov:
28
-
29
20
  def accept(type)
30
21
  with(headers: { "accept" => String(type) })
31
22
  end
32
23
 
24
+ # delegates to the default session (see HTTPX::Session#wrap).
33
25
  def wrap(&blk)
34
26
  branch(default_options).wrap(&blk)
35
27
  end
36
28
 
29
+ # returns a new instance loaded with the +pl+ plugin and +options+.
37
30
  def plugin(pl, options = nil, &blk)
38
- klass = is_a?(Session) ? self.class : Session
31
+ klass = is_a?(S) ? self.class : Session
39
32
  klass = Class.new(klass)
40
33
  klass.instance_variable_set(:@default_options, klass.default_options.merge(default_options))
41
34
  klass.plugin(pl, options, &blk).new
42
35
  end
43
36
 
44
- # deprecated
45
- # :nocov:
46
- def plugins(pls)
47
- warn ":#{__method__} is deprecated, use :plugin instead"
48
- klass = is_a?(Session) ? self.class : Session
49
- klass = Class.new(klass)
50
- klass.instance_variable_set(:@default_options, klass.default_options.merge(default_options))
51
- klass.plugins(pls).new
52
- end
53
- # :nocov:
54
-
37
+ # returns a new instance loaded with +options+.
55
38
  def with(options, &blk)
56
39
  branch(default_options.merge(options), &blk)
57
40
  end
58
41
 
59
42
  private
60
43
 
44
+ # returns default instance of HTTPX::Options.
61
45
  def default_options
62
46
  @options || Session.default_options
63
47
  end
64
48
 
49
+ # returns a default instance of HTTPX::Session.
65
50
  def branch(options, &blk)
66
- return self.class.new(options, &blk) if is_a?(Session)
51
+ return self.class.new(options, &blk) if is_a?(S)
67
52
 
68
53
  Session.new(options, &blk)
69
54
  end
70
55
 
71
- def method_missing(meth, *args, **options)
72
- return super unless meth =~ /\Awith_(.+)/
56
+ def method_missing(meth, *args, **options, &blk)
57
+ case meth
58
+ when /\Awith_(.+)/
73
59
 
74
- option = Regexp.last_match(1)
60
+ option = Regexp.last_match(1)
75
61
 
76
- return super unless option
62
+ return super unless option
77
63
 
78
- with(option.to_sym => (args.first || options))
79
- end
64
+ with(option.to_sym => args.first || options)
65
+ when /\Aon_(.+)/
66
+ callback = Regexp.last_match(1)
80
67
 
81
- def respond_to_missing?(meth, *)
82
- return super unless meth =~ /\Awith_(.+)/
68
+ return super unless %w[
69
+ connection_opened connection_closed
70
+ request_error
71
+ request_started request_body_chunk request_completed
72
+ response_started response_body_chunk response_completed
73
+ ].include?(callback)
83
74
 
84
- option = Regexp.last_match(1)
75
+ warn "DEPRECATION WARNING: calling `.#{meth}` on plain HTTPX sessions is deprecated. " \
76
+ "Use HTTPX.plugin(:callbacks).#{meth} instead."
85
77
 
86
- default_options.respond_to?(option) || super
78
+ plugin(:callbacks).__send__(meth, *args, **options, &blk)
79
+ else
80
+ super
81
+ end
82
+ end
83
+
84
+ def respond_to_missing?(meth, *)
85
+ case meth
86
+ when /\Awith_(.+)/
87
+ option = Regexp.last_match(1)
88
+
89
+ default_options.respond_to?(option) || super
90
+ when /\Aon_(.+)/
91
+ callback = Regexp.last_match(1)
92
+
93
+ %w[
94
+ connection_opened connection_closed
95
+ request_error
96
+ request_started request_body_chunk request_completed
97
+ response_started response_body_chunk response_completed
98
+ ].include?(callback) || super
99
+ else
100
+ super
101
+ end
87
102
  end
88
103
  end
89
104
  end
@@ -7,15 +7,17 @@ module HTTPX
7
7
  include Callbacks
8
8
  include Loggable
9
9
 
10
- MAX_REQUESTS = 100
10
+ MAX_REQUESTS = 200
11
11
  CRLF = "\r\n"
12
12
 
13
13
  attr_reader :pending, :requests
14
14
 
15
+ attr_accessor :max_concurrent_requests
16
+
15
17
  def initialize(buffer, options)
16
- @options = Options.new(options)
18
+ @options = options
17
19
  @max_concurrent_requests = @options.max_concurrent_requests || MAX_REQUESTS
18
- @max_requests = @options.max_requests || MAX_REQUESTS
20
+ @max_requests = @options.max_requests
19
21
  @parser = Parser::HTTP1.new(self)
20
22
  @buffer = buffer
21
23
  @version = [1, 1]
@@ -47,6 +49,7 @@ module HTTPX
47
49
  @max_requests = @options.max_requests || MAX_REQUESTS
48
50
  @parser.reset!
49
51
  @handshake_completed = false
52
+ @pending.concat(@requests) unless @requests.empty?
50
53
  end
51
54
 
52
55
  def close
@@ -106,6 +109,7 @@ module HTTPX
106
109
 
107
110
  def on_headers(h)
108
111
  @request = @requests.first
112
+
109
113
  return if @request.response
110
114
 
111
115
  log(level: 2) { "headers received" }
@@ -118,7 +122,7 @@ module HTTPX
118
122
  log(color: :yellow) { response.headers.each.map { |f, v| "-> HEADER: #{f}: #{v}" }.join("\n") }
119
123
 
120
124
  @request.response = response
121
- on_complete if response.complete?
125
+ on_complete if response.finished?
122
126
  end
123
127
 
124
128
  def on_trailers(h)
@@ -132,32 +136,42 @@ module HTTPX
132
136
  end
133
137
 
134
138
  def on_data(chunk)
135
- return unless @request
139
+ request = @request
140
+
141
+ return unless request
136
142
 
137
143
  log(color: :green) { "-> DATA: #{chunk.bytesize} bytes..." }
138
144
  log(level: 2, color: :green) { "-> #{chunk.inspect}" }
139
- response = @request.response
145
+ response = request.response
140
146
 
141
147
  response << chunk
148
+ rescue StandardError => e
149
+ error_response = ErrorResponse.new(request, e)
150
+ request.response = error_response
151
+ dispatch
142
152
  end
143
153
 
144
154
  def on_complete
145
- return unless @request
155
+ request = @request
156
+
157
+ return unless request
146
158
 
147
159
  log(level: 2) { "parsing complete" }
148
160
  dispatch
149
161
  end
150
162
 
151
163
  def dispatch
152
- if @request.expects?
164
+ request = @request
165
+
166
+ if request.expects?
153
167
  @parser.reset!
154
- return handle(@request)
168
+ return handle(request)
155
169
  end
156
170
 
157
- request = @request
158
171
  @request = nil
159
172
  @requests.shift
160
173
  response = request.response
174
+ response.finish! unless response.is_a?(ErrorResponse)
161
175
  emit(:response, request, response)
162
176
 
163
177
  if @parser.upgrade?
@@ -167,12 +181,23 @@ module HTTPX
167
181
 
168
182
  @parser.reset!
169
183
  @max_requests -= 1
170
- manage_connection(response)
184
+ if response.is_a?(ErrorResponse)
185
+ disable
186
+ else
187
+ manage_connection(request, response)
188
+ end
171
189
 
172
- send(@pending.shift) unless @pending.empty?
190
+ if exhausted?
191
+ @pending.concat(@requests)
192
+ @requests.clear
193
+
194
+ emit(:exhausted)
195
+ else
196
+ send(@pending.shift) unless @pending.empty?
197
+ end
173
198
  end
174
199
 
175
- def handle_error(ex)
200
+ def handle_error(ex, request = nil)
176
201
  if (ex.is_a?(EOFError) || ex.is_a?(TimeoutError)) && @request && @request.response &&
177
202
  !@request.response.headers.key?("content-length") &&
178
203
  !@request.response.headers.key?("transfer-encoding")
@@ -186,23 +211,28 @@ module HTTPX
186
211
  if @pipelining
187
212
  catch(:called) { disable }
188
213
  else
189
- @requests.each do |request|
190
- emit(:error, request, ex)
214
+ @requests.each do |req|
215
+ next if request && request == req
216
+
217
+ emit(:error, req, ex)
191
218
  end
192
- @pending.each do |request|
193
- emit(:error, request, ex)
219
+ @pending.each do |req|
220
+ next if request && request == req
221
+
222
+ emit(:error, req, ex)
194
223
  end
195
224
  end
196
225
  end
197
226
 
198
227
  def ping
228
+ reset
199
229
  emit(:reset)
200
230
  emit(:exhausted)
201
231
  end
202
232
 
203
233
  private
204
234
 
205
- def manage_connection(response)
235
+ def manage_connection(request, response)
206
236
  connection = response.headers["connection"]
207
237
  case connection
208
238
  when /keep-alive/i
@@ -219,7 +249,7 @@ module HTTPX
219
249
  return unless keep_alive
220
250
 
221
251
  parameters = Hash[keep_alive.split(/ *, */).map do |pair|
222
- pair.split(/ *= */)
252
+ pair.split(/ *= */, 2)
223
253
  end]
224
254
  @max_requests = parameters["max"].to_i - 1 if parameters.key?("max")
225
255
 
@@ -232,7 +262,7 @@ module HTTPX
232
262
  disable
233
263
  when nil
234
264
  # In HTTP/1.1, it's keep alive by default
235
- return if response.version == "1.1"
265
+ return if response.version == "1.1" && request.headers["connection"] != "close"
236
266
 
237
267
  disable
238
268
  end
@@ -240,6 +270,7 @@ module HTTPX
240
270
 
241
271
  def disable
242
272
  disable_pipelining
273
+ reset
243
274
  emit(:reset)
244
275
  throw(:called)
245
276
  end
@@ -270,29 +301,31 @@ module HTTPX
270
301
  request.body.chunk!
271
302
  end
272
303
 
273
- connection = request.headers["connection"]
304
+ extra_headers = {}
274
305
 
275
- connection ||= if request.options.persistent
276
- # when in a persistent connection, the request can't be at
277
- # the edge of a renegotiation
278
- if @requests.index(request) + 1 < @max_requests
279
- "keep-alive"
280
- else
281
- "close"
282
- end
283
- else
284
- # when it's not a persistent connection, it sets "Connection: close" always
285
- # on the last request of the possible batch (either allowed max requests,
286
- # or if smaller, the size of the batch itself)
287
- requests_limit = [@max_requests, @requests.size].min
288
- if request == @requests[requests_limit - 1]
289
- "close"
306
+ unless request.headers.key?("connection")
307
+ connection_value = if request.persistent?
308
+ # when in a persistent connection, the request can't be at
309
+ # the edge of a renegotiation
310
+ if @requests.index(request) + 1 < @max_requests
311
+ "keep-alive"
312
+ else
313
+ "close"
314
+ end
290
315
  else
291
- "keep-alive"
316
+ # when it's not a persistent connection, it sets "Connection: close" always
317
+ # on the last request of the possible batch (either allowed max requests,
318
+ # or if smaller, the size of the batch itself)
319
+ requests_limit = [@max_requests, @requests.size].min
320
+ if request == @requests[requests_limit - 1]
321
+ "close"
322
+ else
323
+ "keep-alive"
324
+ end
292
325
  end
293
- end
294
326
 
295
- extra_headers = { "connection" => connection }
327
+ extra_headers["connection"] = connection_value
328
+ end
296
329
  extra_headers["host"] = request.authority unless request.headers.key?("host")
297
330
  extra_headers
298
331
  end
@@ -311,7 +344,7 @@ module HTTPX
311
344
  end
312
345
 
313
346
  def join_headline(request)
314
- "#{request.verb.to_s.upcase} #{request.path} HTTP/#{@version.join(".")}"
347
+ "#{request.verb} #{request.path} HTTP/#{@version.join(".")}"
315
348
  end
316
349
 
317
350
  def join_headers(request)
@@ -348,23 +381,21 @@ module HTTPX
348
381
  end
349
382
 
350
383
  def join_headers2(headers)
351
- buffer = "".b
352
384
  headers.each do |field, value|
353
- buffer << "#{capitalized(field)}: #{value}" << CRLF
385
+ buffer = "#{capitalized(field)}: #{value}#{CRLF}"
354
386
  log(color: :yellow) { "<- HEADER: #{buffer.chomp}" }
355
387
  @buffer << buffer
356
- buffer.clear
357
388
  end
358
389
  end
359
390
 
360
391
  UPCASED = {
361
392
  "www-authenticate" => "WWW-Authenticate",
362
393
  "http2-settings" => "HTTP2-Settings",
394
+ "content-md5" => "Content-MD5",
363
395
  }.freeze
364
396
 
365
397
  def capitalized(field)
366
398
  UPCASED[field] || field.split("-").map(&:capitalize).join("-")
367
399
  end
368
400
  end
369
- Connection.register "http/1.1", Connection::HTTP1
370
401
  end
@@ -1,14 +1,14 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "securerandom"
4
- require "http/2/next"
4
+ require "http/2"
5
5
 
6
6
  module HTTPX
7
7
  class Connection::HTTP2
8
8
  include Callbacks
9
9
  include Loggable
10
10
 
11
- MAX_CONCURRENT_REQUESTS = HTTP2Next::DEFAULT_MAX_CONCURRENT_STREAMS
11
+ MAX_CONCURRENT_REQUESTS = ::HTTP2::DEFAULT_MAX_CONCURRENT_STREAMS
12
12
 
13
13
  class Error < Error
14
14
  def initialize(id, code)
@@ -25,7 +25,7 @@ module HTTPX
25
25
  attr_reader :streams, :pending
26
26
 
27
27
  def initialize(buffer, options)
28
- @options = Options.new(options)
28
+ @options = options
29
29
  @settings = @options.http2_settings
30
30
  @pending = []
31
31
  @streams = {}
@@ -35,7 +35,7 @@ module HTTPX
35
35
  @handshake_completed = false
36
36
  @wait_for_handshake = @settings.key?(:wait_for_handshake) ? @settings.delete(:wait_for_handshake) : true
37
37
  @max_concurrent_requests = @options.max_concurrent_requests || MAX_CONCURRENT_REQUESTS
38
- @max_requests = @options.max_requests || 0
38
+ @max_requests = @options.max_requests
39
39
  init_connection
40
40
  end
41
41
 
@@ -55,7 +55,7 @@ module HTTPX
55
55
  return :w
56
56
  end
57
57
 
58
- unless (@connection.state == :connected && @handshake_completed)
58
+ unless @connection.state == :connected && @handshake_completed
59
59
  return @buffer.empty? ? :r : :rw
60
60
  end
61
61
 
@@ -73,8 +73,11 @@ module HTTPX
73
73
  end
74
74
 
75
75
  def close
76
- @connection.goaway unless @connection.state == :closed
77
- emit(:close)
76
+ unless @connection.state == :closed
77
+ @connection.goaway
78
+ emit(:timeout, @options.timeout[:close_handshake_timeout])
79
+ end
80
+ emit(:close, true)
78
81
  end
79
82
 
80
83
  def empty?
@@ -82,9 +85,7 @@ module HTTPX
82
85
  end
83
86
 
84
87
  def exhausted?
85
- return false if @max_requests.zero? && @connection.active_stream_count.zero?
86
-
87
- @connection.active_stream_count >= @max_requests
88
+ !@max_requests.positive?
88
89
  end
89
90
 
90
91
  def <<(data)
@@ -92,13 +93,9 @@ module HTTPX
92
93
  end
93
94
 
94
95
  def can_buffer_more_requests?
95
- if @handshake_completed
96
+ (@handshake_completed || !@wait_for_handshake) &&
96
97
  @streams.size < @max_concurrent_requests &&
97
- @streams.size < @max_requests
98
- else
99
- !@wait_for_handshake &&
100
- @streams.size < @max_concurrent_requests
101
- end
98
+ @streams.size < @max_requests
102
99
  end
103
100
 
104
101
  def send(request)
@@ -114,9 +111,8 @@ module HTTPX
114
111
  end
115
112
  handle(request, stream)
116
113
  true
117
- rescue HTTP2Next::Error::StreamLimitExceeded
114
+ rescue ::HTTP2::Error::StreamLimitExceeded
118
115
  @pending.unshift(request)
119
- emit(:exhausted)
120
116
  end
121
117
 
122
118
  def consume
@@ -127,7 +123,7 @@ module HTTPX
127
123
  end
128
124
  end
129
125
 
130
- def handle_error(ex)
126
+ def handle_error(ex, request = nil)
131
127
  if ex.instance_of?(TimeoutError) && !@handshake_completed && @connection.state != :closed
132
128
  @connection.goaway(:settings_timeout, "closing due to settings timeout")
133
129
  emit(:close_handshake)
@@ -135,11 +131,15 @@ module HTTPX
135
131
  settings_ex.set_backtrace(ex.backtrace)
136
132
  ex = settings_ex
137
133
  end
138
- @streams.each_key do |request|
139
- emit(:error, request, ex)
134
+ @streams.each_key do |req|
135
+ next if request && request == req
136
+
137
+ emit(:error, req, ex)
140
138
  end
141
- @pending.each do |request|
142
- emit(:error, request, ex)
139
+ @pending.each do |req|
140
+ next if request && request == req
141
+
142
+ emit(:error, req, ex)
143
143
  end
144
144
  end
145
145
 
@@ -154,6 +154,7 @@ module HTTPX
154
154
 
155
155
  def send_pending
156
156
  while (request = @pending.shift)
157
+ # TODO: this request should go back to top of stack
157
158
  break unless send(request)
158
159
  end
159
160
  end
@@ -171,8 +172,7 @@ module HTTPX
171
172
  end
172
173
 
173
174
  def init_connection
174
- @connection = HTTP2Next::Client.new(@settings)
175
- @connection.max_streams = @max_requests if @connection.respond_to?(:max_streams=) && @max_requests.positive?
175
+ @connection = ::HTTP2::Client.new(@settings)
176
176
  @connection.on(:frame, &method(:on_frame))
177
177
  @connection.on(:frame_sent, &method(:on_frame_sent))
178
178
  @connection.on(:frame_received, &method(:on_frame_received))
@@ -208,7 +208,7 @@ module HTTPX
208
208
  def set_protocol_headers(request)
209
209
  {
210
210
  ":scheme" => request.scheme,
211
- ":method" => request.verb.to_s.upcase,
211
+ ":method" => request.verb,
212
212
  ":path" => request.path,
213
213
  ":authority" => request.authority,
214
214
  }
@@ -313,11 +313,12 @@ module HTTPX
313
313
  if error
314
314
  ex = Error.new(stream.id, error)
315
315
  ex.set_backtrace(caller)
316
- response = ErrorResponse.new(request, ex, request.options)
316
+ response = ErrorResponse.new(request, ex)
317
+ request.response = response
317
318
  emit(:response, request, response)
318
319
  else
319
320
  response = request.response
320
- if response && response.status == 421
321
+ if response && response.is_a?(Response) && response.status == 421
321
322
  ex = MisdirectedRequestError.new(response)
322
323
  ex.set_backtrace(caller)
323
324
  emit(:error, request, ex)
@@ -339,14 +340,7 @@ module HTTPX
339
340
  def on_settings(*)
340
341
  @handshake_completed = true
341
342
  emit(:current_timeout)
342
-
343
- if @max_requests.zero?
344
- @max_requests = @connection.remote_settings[:settings_max_concurrent_streams]
345
-
346
- @connection.max_streams = @max_requests if @connection.respond_to?(:max_streams=) && @max_requests.positive?
347
- end
348
-
349
- @max_concurrent_requests = [@max_concurrent_requests, @max_requests].min
343
+ @max_concurrent_requests = [@max_concurrent_requests, @connection.remote_settings[:settings_max_concurrent_streams]].min
350
344
  send_pending
351
345
  end
352
346
 
@@ -365,7 +359,7 @@ module HTTPX
365
359
  ex.set_backtrace(caller)
366
360
  handle_error(ex)
367
361
  end
368
- return unless is_connection_closed && @streams.size.zero?
362
+ return unless is_connection_closed && @streams.empty?
369
363
 
370
364
  emit(:close, is_connection_closed)
371
365
  end
@@ -412,5 +406,4 @@ module HTTPX
412
406
  end
413
407
  end
414
408
  end
415
- Connection.register "h2", Connection::HTTP2
416
409
  end