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
@@ -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