httpx 0.15.3 → 0.17.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (109) hide show
  1. checksums.yaml +4 -4
  2. data/doc/release_notes/0_15_4.md +5 -0
  3. data/doc/release_notes/0_16_0.md +93 -0
  4. data/doc/release_notes/0_16_1.md +5 -0
  5. data/doc/release_notes/0_17_0.md +49 -0
  6. data/lib/httpx/adapters/faraday.rb +3 -11
  7. data/lib/httpx/adapters/webmock.rb +2 -2
  8. data/lib/httpx/buffer.rb +1 -1
  9. data/lib/httpx/callbacks.rb +1 -1
  10. data/lib/httpx/chainable.rb +15 -8
  11. data/lib/httpx/connection/http1.rb +18 -10
  12. data/lib/httpx/connection/http2.rb +14 -21
  13. data/lib/httpx/connection.rb +6 -7
  14. data/lib/httpx/errors.rb +11 -11
  15. data/lib/httpx/headers.rb +1 -1
  16. data/lib/httpx/io/ssl.rb +2 -2
  17. data/lib/httpx/io/tls.rb +1 -1
  18. data/lib/httpx/options.rb +108 -81
  19. data/lib/httpx/parser/http1.rb +11 -7
  20. data/lib/httpx/plugins/aws_sigv4.rb +10 -9
  21. data/lib/httpx/plugins/compression.rb +12 -11
  22. data/lib/httpx/plugins/cookies/cookie.rb +4 -2
  23. data/lib/httpx/plugins/cookies/jar.rb +20 -1
  24. data/lib/httpx/plugins/cookies.rb +20 -7
  25. data/lib/httpx/plugins/digest_authentication.rb +19 -15
  26. data/lib/httpx/plugins/expect.rb +19 -15
  27. data/lib/httpx/plugins/follow_redirects.rb +9 -9
  28. data/lib/httpx/plugins/grpc/call.rb +4 -1
  29. data/lib/httpx/plugins/grpc.rb +73 -47
  30. data/lib/httpx/plugins/h2c.rb +7 -3
  31. data/lib/httpx/plugins/multipart/decoder.rb +187 -0
  32. data/lib/httpx/plugins/multipart/mime_type_detector.rb +3 -3
  33. data/lib/httpx/plugins/multipart/part.rb +2 -2
  34. data/lib/httpx/plugins/multipart.rb +14 -0
  35. data/lib/httpx/plugins/ntlm_authentication.rb +12 -10
  36. data/lib/httpx/plugins/proxy/socks4.rb +2 -1
  37. data/lib/httpx/plugins/proxy/socks5.rb +2 -1
  38. data/lib/httpx/plugins/proxy/ssh.rb +20 -13
  39. data/lib/httpx/plugins/proxy.rb +10 -10
  40. data/lib/httpx/plugins/retries.rb +25 -21
  41. data/lib/httpx/plugins/stream.rb +2 -3
  42. data/lib/httpx/plugins/upgrade.rb +7 -6
  43. data/lib/httpx/registry.rb +2 -2
  44. data/lib/httpx/request.rb +10 -19
  45. data/lib/httpx/resolver/https.rb +0 -2
  46. data/lib/httpx/resolver/native.rb +15 -3
  47. data/lib/httpx/resolver/resolver_mixin.rb +2 -1
  48. data/lib/httpx/response.rb +72 -38
  49. data/lib/httpx/selector.rb +6 -7
  50. data/lib/httpx/session.rb +34 -21
  51. data/lib/httpx/session2.rb +23 -0
  52. data/lib/httpx/transcoder/body.rb +1 -1
  53. data/lib/httpx/transcoder/chunker.rb +2 -1
  54. data/lib/httpx/transcoder/form.rb +20 -0
  55. data/lib/httpx/transcoder/json.rb +12 -0
  56. data/lib/httpx/transcoder.rb +62 -1
  57. data/lib/httpx/utils.rb +2 -2
  58. data/lib/httpx/version.rb +1 -1
  59. data/lib/httpx.rb +6 -3
  60. data/sig/buffer.rbs +3 -1
  61. data/sig/chainable.rbs +30 -29
  62. data/sig/connection/http1.rbs +11 -5
  63. data/sig/connection/http2.rbs +16 -5
  64. data/sig/connection.rbs +23 -11
  65. data/sig/errors.rbs +35 -1
  66. data/sig/headers.rbs +20 -19
  67. data/sig/httpx.rbs +4 -1
  68. data/sig/loggable.rbs +3 -1
  69. data/sig/options.rbs +45 -34
  70. data/sig/parser/http1.rbs +3 -3
  71. data/sig/plugins/authentication.rbs +1 -1
  72. data/sig/plugins/aws_sdk_authentication.rbs +5 -1
  73. data/sig/plugins/aws_sigv4.rbs +13 -5
  74. data/sig/plugins/basic_authentication.rbs +1 -1
  75. data/sig/plugins/compression.rbs +4 -6
  76. data/sig/plugins/cookies/cookie.rbs +5 -7
  77. data/sig/plugins/cookies/jar.rbs +9 -10
  78. data/sig/plugins/cookies.rbs +4 -5
  79. data/sig/plugins/digest_authentication.rbs +2 -3
  80. data/sig/plugins/expect.rbs +2 -4
  81. data/sig/plugins/follow_redirects.rbs +3 -5
  82. data/sig/plugins/grpc.rbs +4 -7
  83. data/sig/plugins/h2c.rbs +0 -2
  84. data/sig/plugins/multipart.rbs +64 -10
  85. data/sig/plugins/ntlm_authentication.rbs +2 -3
  86. data/sig/plugins/persistent.rbs +3 -8
  87. data/sig/plugins/proxy/ssh.rbs +4 -4
  88. data/sig/plugins/proxy.rbs +13 -13
  89. data/sig/plugins/push_promise.rbs +0 -2
  90. data/sig/plugins/retries.rbs +4 -8
  91. data/sig/plugins/stream.rbs +1 -1
  92. data/sig/plugins/upgrade.rbs +2 -3
  93. data/sig/pool.rbs +1 -2
  94. data/sig/registry.rbs +1 -1
  95. data/sig/request.rbs +11 -8
  96. data/sig/resolver/native.rbs +12 -6
  97. data/sig/resolver/resolver_mixin.rbs +4 -5
  98. data/sig/resolver/system.rbs +2 -0
  99. data/sig/resolver.rbs +7 -0
  100. data/sig/response.rbs +24 -12
  101. data/sig/selector.rbs +11 -9
  102. data/sig/session.rbs +22 -23
  103. data/sig/transcoder/body.rbs +6 -1
  104. data/sig/transcoder/chunker.rbs +8 -2
  105. data/sig/transcoder/form.rbs +3 -1
  106. data/sig/transcoder/json.rbs +2 -0
  107. data/sig/transcoder.rbs +13 -5
  108. data/sig/utils.rbs +2 -0
  109. metadata +12 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e35dd08edd7da2735fbcab65f0cab8c7c41314946e64b02780b6afd17faa5622
4
- data.tar.gz: 1b34789ecb769043120c5c524657f2e747c91385c32064eed732a25106d2fa81
3
+ metadata.gz: f6b2befec2e4b0093acd45f7ef9ef448ad41516510710616aded46934f1e3981
4
+ data.tar.gz: a9858adfacbdc27e1097b98b958f7dd1614f1207c74ec4290a54268df6270f69
5
5
  SHA512:
6
- metadata.gz: b313d4468410befb74da77551484b116389fa785c643c935756f949d16077773d6a4ffe1b99ed20c6263e94385a2a4a40f82e3ec029db830d21b8c4ea03b847b
7
- data.tar.gz: 69955f7aa53181fc0a5b08dd9a5c909f5f526b45856dbc26991a65719a2916c1372264a89461d1aea91192a020bbffd9abac5d644194b9e4fc6f9c29d7837ef2
6
+ metadata.gz: 86276d59efaf3a15efe0a27fbd59bff2d005bb3bab83ee9917599854bf46eba1bfbb016b9df55172c41799d1fe82195e4bb7d82c008c1996814ec2e393be71b3
7
+ data.tar.gz: bd113e65cf5700f231992bb485f6c59511f114372d53078ea53c019a654e449e5012b2a1a19f889d79cee1eecd24345d3e03f3f9fa50aa6c69060138327e1d09
@@ -0,0 +1,5 @@
1
+ # 0.15.4
2
+
3
+ ## Bugfixes
4
+
5
+ * Fixed `grpc` plugin, where `.marshal` was being called on the encoding protobuf object, instead of the coorrect "marshal method", which is `.encode`.
@@ -0,0 +1,93 @@
1
+ # 0.16.0
2
+
3
+ ## Features
4
+
5
+ ### Response::Body#to_s does not clear the internal buffer
6
+
7
+ It's well documented that the response body should be treated as a file, and that calling `.to_s` on a response should be done only once, and the user should not expect the same call to return the same response body again, while suggesting that the first call should be cached in a variable in case it's needed:
8
+
9
+ ```ruby
10
+ response = HTTPX.get("https://google.com")
11
+ body = response.body.to_s #=> "<html ...."
12
+ response.body.to_s #=> ""
13
+
14
+ # thankfully,it's cached in the body var there.
15
+ ```
16
+
17
+ The justification for this behaviour probably had to do with avoiding keeping huge payloads around, but it got a bit lost in git history. It became a feature, not a bug.
18
+
19
+ However, I got an [issue report](https://gitlab.com/honeyryderchuck/httpx/-/issues/143) that made me change my mind about this behaviour (tl;dr: it broke pattern matching when matching against response bodies more than once).
20
+
21
+ So now, you can call `.to_s` how many times you want!
22
+
23
+ ```ruby
24
+ response = HTTPX.get("https://google.com")
25
+ body = response.body.to_s #=> "<html ...."
26
+ response.body.to_s #=> "<html ....", still here!
27
+ ```
28
+
29
+ Some optimizations were done around how the body is carried forward, and bodies buffered in files will now get properly garbage collected and not leak descriptors behind when users forget to call `.close`.
30
+
31
+ ### grpc plugin improvements
32
+
33
+ ##### build fully-enabled stub from grpc service
34
+
35
+ The `:grpc` plugin can now build fully-loaded stubs from existing GRPC generic services.
36
+
37
+ GRPC stubs could be a bit tedious to write when compared to what the `grpc` gem offers, which is, auto-generation from ruby service stubs from protobuf definitions:
38
+
39
+ ```ruby
40
+ # service generated from the command:
41
+ #
42
+ # > grpc_tools_ruby_protoc -I ../../protos --ruby_out=../lib --grpc_out=../lib ../../protos/route_guide.proto
43
+ #
44
+ require "route_guide_services_pb.rb"
45
+
46
+ # with httpx, before 0.16
47
+ stub = HTTPX.plugin(:grpc).build_stub("localhost:#{server_port}", service: "RouteGuide")
48
+ .rpc(:GetFeature, Point, Feature)
49
+ .rpc(:ListFeatures, # ... and so on, all hand stitched
50
+
51
+ stub.get_feature(# ...
52
+
53
+ # with httpx 0.16
54
+ stub = HTTPX.plugin(:grpc).build_stub("localhost:#{server_port}", service: RouteGuide)
55
+ # that's it!
56
+ stub.get_feature(# ...
57
+ ```
58
+
59
+ #### no google/protobuf direct dependency
60
+
61
+ `"google/protobuf"` is no longer assumed when using the plugin, i.e. you can use other protobuf serializers, such as https://github.com/ruby-protobuf/protobuf , which supports `jruby` (unlike the former).
62
+
63
+ ### OptionsMethods for plugins
64
+
65
+ https://gitlab.com/honeyryderchuck/httpx/-/wikis/Custom-Plugins
66
+
67
+ You can now define an `OptionsMethods` module under your custom plugin to define your own methods. The tl;dr is, that, given the following module below, a new `:bar` option will be available (and the method will be used to set it):
68
+
69
+ ```ruby
70
+ module CustomPlugin
71
+ module OptionsMethods
72
+ def option_bar(x) ; x; end
73
+ end
74
+ end
75
+
76
+ HTTPX.plugin(CustomPlugin).with(bar: 2)
77
+ ```
78
+
79
+ ### cookies plugin: improved jar management
80
+
81
+ The behaviour of the cookies jar from the `:cookies` plugin was a bit unpredictable in certain conditions, for instance if a "Cookie" header would be passed directly via `.with(headers: {"Cookie" => "a=1"})` and there'd be a value for it already (in same cases, it'd be fully ignored). This would even get worse, if the session had a jar, and a specific set of cookies would be passed to a request(i.e.: `session_with_cookies.get("http://url.get", headers: {"Cookies" => "..."}`).
82
+
83
+ The behaviour was fixed, and is now specced under https://gitlab.com/honeyryderchuck/httpx/-/blob/master/test/support/requests/plugins/cookies.rb .
84
+
85
+ ## Bugfixes
86
+
87
+ * Cookies sorting in the `:cookies` plugin jar was fixed for truffleruby;
88
+
89
+ ## Chore
90
+
91
+ * errors when setting options nnow raise `TypeError` instead of `HTTPX::Error`.
92
+ * options are now internally frozen by default, which should protect the internals against accidentally updating them;
93
+ * Fixed optimization around options initialization, to prevent needless allocations;
@@ -0,0 +1,5 @@
1
+ # 0.16.1
2
+
3
+ ## Bugfixes
4
+
5
+ * fixed native resolver hanging when dealing with FQDN end names (#145).
@@ -0,0 +1,49 @@
1
+ # 0.17.0
2
+
3
+ ## Features
4
+
5
+ ### Response mime type decoders (#json, #form)
6
+
7
+ https://gitlab.com/honeyryderchuck/httpx/-/wikis/Response-Handling#response-decoding
8
+
9
+ Two new methods, `#json` and `#form`, were added to `HTTPX::Response`. As the name implies, they'll decode the raw payload into ruby objects you can work with.
10
+
11
+ ```ruby
12
+ # after HTTPX.get("https://api.smth/endpoint-returning-json")
13
+ response.json # same as JSON.dump(response.to_s)
14
+ ```
15
+
16
+ Although not yet documented, integrating custom decoders is also possible (i.e. parsing HTML with `nokogiri` or something similar).
17
+
18
+ ## Improvements
19
+
20
+ ### Connection: reduce interest calculations
21
+
22
+ Due to it being an intensive task, internal interest calculation in connections was reduce to the bare minimum.
23
+
24
+ ### Immutable Options, internal recycling of instances, improves memory usage in the happy path
25
+
26
+ A lot of effort went into avoiding generating options objects internally whenever necessary. This means, when sending several requests with the same set of options (the most common case in `httpx` usage), internally only one object is passed around. For that, the following improvements were done:
27
+
28
+ * `Options#merge` returns the same options the the options being merged are a subset of the current set of options (b126938a6547e09b726dd64298fb488891d938e9).
29
+ * `Session#build_request` bypasses instantiation of options if it receives an `Options` object (which happens internally in the happy path, if users don't call `#build_request` directly) (3d549817cb41d4b904102fdc61afe3ecd9170893).
30
+ * Improving internal `Session` APIs to not pass around options, and instead rely on accessing request options.
31
+ * `Options#to_hash` does not build internal garbage arrays anymore (cc02679b804f63798f5d2136a039be1624e96ab6).
32
+
33
+ ### Reduce regexp operations in the HTTP/1 parser
34
+
35
+ Some code paths in the HTTP/1 parser still using regular expressions were replaced by string operations accomplishing the same.
36
+
37
+ ### HTTP/1 improvements on the complexity of connection accounting calculations
38
+
39
+ Managing open HTTP/1 connections relies on operations calculating whether there are requests waiting for completion. This relied on traversing all requests for that connectionn (O(n)); it now only checks the completion state of the first and last request of that connection, given that all requests in HTTP/1 are sequential (O(1)); this optimization brings a big improvement to persistent and pipelined requests (65261217b1270913e4bb93717e8b8dcfa775565a).
40
+
41
+ ## Bugfixes
42
+
43
+ * fixing HTTP/1 protocol uncompliant exposing multiple values for the "Host" header (e435dd0534314508262184fb03d83124d89d2079).
44
+
45
+ * Custom response finalizer introduced in 0.16.0 has been reverted. It was brought to my attention that `Tempfile` implementation already takes care of the file on GC (and `httpx` was duplicating), and the approach taken in `httpx` was buggy in several ways (not tolerant to forks, never recycled finalizers...) (aa3be21c890f92a41afcc7931f01dd24cc801f7c).
46
+
47
+ ## Chore
48
+
49
+ RBS Typing improvements based on latest stdlib signatures additions, such as `openssl`, `digest`, `socket` and others.
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "delegate"
3
4
  require "httpx"
4
5
  require "faraday"
5
6
 
@@ -91,11 +92,12 @@ module Faraday
91
92
  end
92
93
 
93
94
  class ParallelManager
94
- class ResponseHandler
95
+ class ResponseHandler < SimpleDelegator
95
96
  attr_reader :env
96
97
 
97
98
  def initialize(env)
98
99
  @env = env
100
+ super
99
101
  end
100
102
 
101
103
  def on_response(&blk)
@@ -117,16 +119,6 @@ module Faraday
117
119
  @on_complete
118
120
  end
119
121
  end
120
-
121
- def respond_to_missing?(meth)
122
- @env.respond_to?(meth) || super
123
- end
124
-
125
- def method_missing(meth, *args, &blk)
126
- return super unless @env && @env.respond_to?(meth)
127
-
128
- @env.__send__(meth, *args, &blk)
129
- end
130
122
  end
131
123
 
132
124
  include RequestMixin
@@ -19,7 +19,7 @@ module WebMock
19
19
  module InstanceMethods
20
20
  private
21
21
 
22
- def send_requests(*requests, options)
22
+ def send_requests(*requests)
23
23
  request_signatures = requests.map do |request|
24
24
  request_signature = _build_webmock_request_signature(request)
25
25
  WebMock::RequestRegistry.instance.requested_signatures.put(request_signature)
@@ -47,7 +47,7 @@ module WebMock
47
47
 
48
48
  unless real_requests.empty?
49
49
  reqs = real_requests.keys
50
- reqs.zip(super(*reqs, options)).each do |req, res|
50
+ reqs.zip(super(*reqs)).each do |req, res|
51
51
  idx = real_requests[req]
52
52
 
53
53
  if WebMock::CallbackRegistry.any_callbacks?
data/lib/httpx/buffer.rb CHANGED
@@ -32,7 +32,7 @@ module HTTPX
32
32
  end
33
33
 
34
34
  def shift!(fin)
35
- @buffer = @buffer.byteslice(fin..-1)
35
+ @buffer = @buffer.byteslice(fin..-1) || "".b
36
36
  end
37
37
  end
38
38
  end
@@ -19,7 +19,7 @@ module HTTPX
19
19
  end
20
20
 
21
21
  def emit(type, *args)
22
- callbacks(type).delete_if { |pr| :delete == pr[*args] } # rubocop:disable Style/YodaCondition
22
+ callbacks(type).delete_if { |pr| :delete == pr.call(*args) } # rubocop:disable Style/YodaCondition
23
23
  end
24
24
 
25
25
  protected
@@ -34,21 +34,21 @@ module HTTPX
34
34
  branch(default_options).wrap(&blk)
35
35
  end
36
36
 
37
- def plugin(*args, **opts, &blk)
37
+ def plugin(pl, options = nil, &blk)
38
38
  klass = is_a?(Session) ? self.class : Session
39
39
  klass = Class.new(klass)
40
40
  klass.instance_variable_set(:@default_options, klass.default_options.merge(default_options))
41
- klass.plugin(*args, **opts, &blk).new
41
+ klass.plugin(pl, options, &blk).new
42
42
  end
43
43
 
44
44
  # deprecated
45
45
  # :nocov:
46
- def plugins(*args, **opts)
46
+ def plugins(pls)
47
47
  warn ":#{__method__} is deprecated, use :plugin instead"
48
48
  klass = is_a?(Session) ? self.class : Session
49
49
  klass = Class.new(klass)
50
50
  klass.instance_variable_set(:@default_options, klass.default_options.merge(default_options))
51
- klass.plugins(*args, **opts).new
51
+ klass.plugins(pls).new
52
52
  end
53
53
  # :nocov:
54
54
 
@@ -71,12 +71,19 @@ module HTTPX
71
71
  def method_missing(meth, *args, **options)
72
72
  return super unless meth =~ /\Awith_(.+)/
73
73
 
74
- option = Regexp.last_match(1).to_sym
75
- with(option => (args.first || options))
74
+ option = Regexp.last_match(1)
75
+
76
+ return super unless option
77
+
78
+ with(option.to_sym => (args.first || options))
76
79
  end
77
80
 
78
- def respond_to_missing?(meth, *args)
79
- default_options.respond_to?(meth, *args) || super
81
+ def respond_to_missing?(meth, *)
82
+ return super unless meth =~ /\Awith_(.+)/
83
+
84
+ option = Regexp.last_match(1)
85
+
86
+ default_options.respond_to?(option) || super
80
87
  end
81
88
  end
82
89
  end
@@ -59,7 +59,12 @@ module HTTPX
59
59
  def empty?
60
60
  # this means that for every request there's an available
61
61
  # partial response, so there are no in-flight requests waiting.
62
- @requests.empty? || @requests.all? { |request| !request.response.nil? }
62
+ @requests.empty? || (
63
+ # checking all responses can be time-consuming. Alas, as in HTTP/1, responses
64
+ # do not come out of order, we can get away with checking first and last.
65
+ !@requests.first.response.nil? &&
66
+ (@requests.size == 1 || !@requests.last.response.nil?)
67
+ )
63
68
  end
64
69
 
65
70
  def <<(data)
@@ -260,10 +265,12 @@ module HTTPX
260
265
  def set_protocol_headers(request)
261
266
  if !request.headers.key?("content-length") &&
262
267
  request.body.bytesize == Float::INFINITY
263
- request.chunk!
268
+ request.body.chunk!
264
269
  end
265
270
 
266
- connection = if request.options.persistent
271
+ connection = request.headers["connection"]
272
+
273
+ connection ||= if request.options.persistent
267
274
  # when in a persistent connection, the request can't be at
268
275
  # the edge of a renegotiation
269
276
  if @requests.index(request) + 1 < @max_requests
@@ -283,10 +290,9 @@ module HTTPX
283
290
  end
284
291
  end
285
292
 
286
- {
287
- "host" => (request.headers["host"] || request.authority),
288
- "connection" => connection,
289
- }
293
+ extra_headers = { "connection" => connection }
294
+ extra_headers["host"] = request.authority unless request.headers.key?("host")
295
+ extra_headers
290
296
  end
291
297
 
292
298
  def headline_uri(request)
@@ -316,7 +322,7 @@ module HTTPX
316
322
  end
317
323
 
318
324
  def join_body(request)
319
- return if request.empty?
325
+ return if request.body.empty?
320
326
 
321
327
  while (chunk = request.drain_body)
322
328
  log(color: :green) { "<- DATA: #{chunk.bytesize} bytes..." }
@@ -325,7 +331,9 @@ module HTTPX
325
331
  throw(:buffer_full, request) if @buffer.full?
326
332
  end
327
333
 
328
- raise request.drain_error if request.drain_error
334
+ return unless (error = request.drain_error)
335
+
336
+ raise error
329
337
  end
330
338
 
331
339
  def join_trailers(request)
@@ -352,7 +360,7 @@ module HTTPX
352
360
  }.freeze
353
361
 
354
362
  def capitalized(field)
355
- UPCASED[field] || field.to_s.split("-").map(&:capitalize).join("-")
363
+ UPCASED[field] || field.split("-").map(&:capitalize).join("-")
356
364
  end
357
365
  end
358
366
  Connection.register "http/1.1", Connection::HTTP1
@@ -1,7 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "securerandom"
4
- require "io/wait"
5
4
  require "http/2/next"
6
5
 
7
6
  module HTTPX
@@ -11,7 +10,7 @@ module HTTPX
11
10
 
12
11
  MAX_CONCURRENT_REQUESTS = HTTP2Next::DEFAULT_MAX_CONCURRENT_STREAMS
13
12
 
14
- Error = Class.new(Error) do
13
+ class Error < Error
15
14
  def initialize(id, code)
16
15
  super("stream #{id} closed with error: #{code}")
17
16
  end
@@ -56,7 +55,7 @@ module HTTPX
56
55
 
57
56
  return :w if !@pending.empty? && can_buffer_more_requests?
58
57
 
59
- return :w if @streams.each_key.any? { |r| r.interests == :w }
58
+ return :w unless @drains.empty?
60
59
 
61
60
  if @buffer.empty?
62
61
  return if @streams.empty? && @pings.empty?
@@ -218,7 +217,7 @@ module HTTPX
218
217
  log(level: 1, color: :yellow) do
219
218
  request.headers.merge(extra_headers).each.map { |k, v| "#{stream.id}: -> HEADER: #{k}: #{v}" }.join("\n")
220
219
  end
221
- stream.headers(request.headers.each(extra_headers), end_stream: request.empty?)
220
+ stream.headers(request.headers.each(extra_headers), end_stream: request.body.empty?)
222
221
  end
223
222
 
224
223
  def join_trailers(stream, request)
@@ -234,7 +233,7 @@ module HTTPX
234
233
  end
235
234
 
236
235
  def join_body(stream, request)
237
- return if request.empty?
236
+ return if request.body.empty?
238
237
 
239
238
  chunk = @drains.delete(request) || request.drain_body
240
239
  while chunk
@@ -249,7 +248,9 @@ module HTTPX
249
248
  chunk = next_chunk
250
249
  end
251
250
 
252
- on_stream_refuse(stream, request, request.drain_error) if request.drain_error
251
+ return unless (error = request.drain_error)
252
+
253
+ on_stream_refuse(stream, request, error)
253
254
  end
254
255
 
255
256
  ######
@@ -257,8 +258,10 @@ module HTTPX
257
258
  ######
258
259
 
259
260
  def on_stream_headers(stream, request, h)
260
- if request.response && request.response.version == "2.0"
261
- on_stream_trailers(stream, request, h)
261
+ response = request.response
262
+
263
+ if response.is_a?(Response) && response.version == "2.0"
264
+ on_stream_trailers(stream, response, h)
262
265
  return
263
266
  end
264
267
 
@@ -274,11 +277,11 @@ module HTTPX
274
277
  handle(request, stream) if request.expects?
275
278
  end
276
279
 
277
- def on_stream_trailers(stream, request, h)
280
+ def on_stream_trailers(stream, response, h)
278
281
  log(color: :yellow) do
279
282
  h.map { |k, v| "#{stream.id}: <- HEADER: #{k}: #{v}" }.join("\n")
280
283
  end
281
- request.response.merge_headers(h)
284
+ response.merge_headers(h)
282
285
  end
283
286
 
284
287
  def on_stream_data(stream, request, data)
@@ -304,7 +307,7 @@ module HTTPX
304
307
  emit(:response, request, response)
305
308
  else
306
309
  response = request.response
307
- if response.status == 421
310
+ if response && response.status == 421
308
311
  ex = MisdirectedRequestError.new(response)
309
312
  ex.set_backtrace(caller)
310
313
  emit(:error, request, ex)
@@ -391,16 +394,6 @@ module HTTPX
391
394
  emit(:pong)
392
395
  end
393
396
  end
394
-
395
- def respond_to_missing?(meth, *args)
396
- @connection.respond_to?(meth, *args) || super
397
- end
398
-
399
- def method_missing(meth, *args, &blk)
400
- return super unless @connection.respond_to?(meth)
401
-
402
- @connection.__send__(meth, *args, &blk)
403
- end
404
397
  end
405
398
  Connection.register "h2", Connection::HTTP2
406
399
  end