httpx 0.15.4 → 0.18.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (126) hide show
  1. checksums.yaml +4 -4
  2. data/doc/release_notes/0_16_0.md +93 -0
  3. data/doc/release_notes/0_16_1.md +5 -0
  4. data/doc/release_notes/0_17_0.md +49 -0
  5. data/doc/release_notes/0_18_0.md +69 -0
  6. data/lib/httpx/adapters/datadog.rb +1 -1
  7. data/lib/httpx/adapters/faraday.rb +8 -14
  8. data/lib/httpx/adapters/webmock.rb +9 -3
  9. data/lib/httpx/altsvc.rb +2 -2
  10. data/lib/httpx/buffer.rb +1 -1
  11. data/lib/httpx/callbacks.rb +1 -1
  12. data/lib/httpx/chainable.rb +18 -11
  13. data/lib/httpx/connection/http1.rb +21 -13
  14. data/lib/httpx/connection/http2.rb +20 -25
  15. data/lib/httpx/connection.rb +73 -77
  16. data/lib/httpx/domain_name.rb +1 -1
  17. data/lib/httpx/errors.rb +11 -11
  18. data/lib/httpx/extensions.rb +50 -4
  19. data/lib/httpx/headers.rb +1 -1
  20. data/lib/httpx/io/ssl.rb +3 -3
  21. data/lib/httpx/io/tls.rb +8 -8
  22. data/lib/httpx/loggable.rb +5 -5
  23. data/lib/httpx/options.rb +108 -81
  24. data/lib/httpx/parser/http1.rb +11 -7
  25. data/lib/httpx/plugins/aws_sdk_authentication.rb +42 -18
  26. data/lib/httpx/plugins/aws_sigv4.rb +19 -20
  27. data/lib/httpx/plugins/compression.rb +17 -14
  28. data/lib/httpx/plugins/cookies/cookie.rb +4 -2
  29. data/lib/httpx/plugins/cookies/jar.rb +21 -2
  30. data/lib/httpx/plugins/cookies.rb +20 -7
  31. data/lib/httpx/plugins/digest_authentication.rb +19 -15
  32. data/lib/httpx/plugins/expect.rb +26 -18
  33. data/lib/httpx/plugins/follow_redirects.rb +9 -9
  34. data/lib/httpx/plugins/grpc/call.rb +4 -1
  35. data/lib/httpx/plugins/grpc/message.rb +2 -2
  36. data/lib/httpx/plugins/grpc.rb +72 -46
  37. data/lib/httpx/plugins/h2c.rb +7 -3
  38. data/lib/httpx/plugins/internal_telemetry.rb +8 -8
  39. data/lib/httpx/plugins/multipart/decoder.rb +187 -0
  40. data/lib/httpx/plugins/multipart/mime_type_detector.rb +3 -3
  41. data/lib/httpx/plugins/multipart/part.rb +2 -2
  42. data/lib/httpx/plugins/multipart.rb +16 -2
  43. data/lib/httpx/plugins/ntlm_authentication.rb +12 -10
  44. data/lib/httpx/plugins/proxy/socks4.rb +2 -1
  45. data/lib/httpx/plugins/proxy/socks5.rb +2 -1
  46. data/lib/httpx/plugins/proxy/ssh.rb +20 -13
  47. data/lib/httpx/plugins/proxy.rb +10 -10
  48. data/lib/httpx/plugins/response_cache/store.rb +55 -0
  49. data/lib/httpx/plugins/response_cache.rb +88 -0
  50. data/lib/httpx/plugins/retries.rb +46 -23
  51. data/lib/httpx/plugins/stream.rb +3 -4
  52. data/lib/httpx/plugins/upgrade.rb +7 -6
  53. data/lib/httpx/pool.rb +39 -13
  54. data/lib/httpx/registry.rb +2 -2
  55. data/lib/httpx/request.rb +16 -25
  56. data/lib/httpx/resolver/https.rb +4 -8
  57. data/lib/httpx/resolver/native.rb +19 -5
  58. data/lib/httpx/resolver/resolver_mixin.rb +2 -1
  59. data/lib/httpx/resolver/system.rb +2 -0
  60. data/lib/httpx/resolver.rb +2 -2
  61. data/lib/httpx/response.rb +91 -48
  62. data/lib/httpx/selector.rb +11 -24
  63. data/lib/httpx/session.rb +41 -23
  64. data/lib/httpx/session2.rb +23 -0
  65. data/lib/httpx/timers.rb +84 -0
  66. data/lib/httpx/transcoder/body.rb +3 -2
  67. data/lib/httpx/transcoder/chunker.rb +2 -1
  68. data/lib/httpx/transcoder/form.rb +20 -0
  69. data/lib/httpx/transcoder/json.rb +12 -0
  70. data/lib/httpx/transcoder.rb +62 -1
  71. data/lib/httpx/utils.rb +10 -2
  72. data/lib/httpx/version.rb +1 -1
  73. data/lib/httpx.rb +7 -3
  74. data/sig/buffer.rbs +3 -1
  75. data/sig/chainable.rbs +31 -29
  76. data/sig/connection/http1.rbs +11 -5
  77. data/sig/connection/http2.rbs +16 -5
  78. data/sig/connection.rbs +31 -13
  79. data/sig/errors.rbs +35 -1
  80. data/sig/headers.rbs +20 -19
  81. data/sig/httpx.rbs +4 -1
  82. data/sig/loggable.rbs +3 -1
  83. data/sig/options.rbs +45 -34
  84. data/sig/parser/http1.rbs +3 -3
  85. data/sig/plugins/authentication.rbs +1 -1
  86. data/sig/plugins/aws_sdk_authentication.rbs +25 -3
  87. data/sig/plugins/aws_sigv4.rbs +13 -5
  88. data/sig/plugins/basic_authentication.rbs +1 -1
  89. data/sig/plugins/compression.rbs +4 -6
  90. data/sig/plugins/cookies/cookie.rbs +5 -7
  91. data/sig/plugins/cookies/jar.rbs +9 -10
  92. data/sig/plugins/cookies.rbs +4 -5
  93. data/sig/plugins/digest_authentication.rbs +2 -3
  94. data/sig/plugins/expect.rbs +2 -4
  95. data/sig/plugins/follow_redirects.rbs +3 -5
  96. data/sig/plugins/grpc.rbs +4 -7
  97. data/sig/plugins/h2c.rbs +0 -2
  98. data/sig/plugins/multipart.rbs +64 -10
  99. data/sig/plugins/ntlm_authentication.rbs +2 -3
  100. data/sig/plugins/persistent.rbs +3 -8
  101. data/sig/plugins/proxy/ssh.rbs +4 -4
  102. data/sig/plugins/proxy.rbs +13 -13
  103. data/sig/plugins/push_promise.rbs +0 -2
  104. data/sig/plugins/response_cache.rbs +35 -0
  105. data/sig/plugins/retries.rbs +7 -8
  106. data/sig/plugins/stream.rbs +1 -1
  107. data/sig/plugins/upgrade.rbs +2 -3
  108. data/sig/pool.rbs +7 -2
  109. data/sig/registry.rbs +1 -1
  110. data/sig/request.rbs +11 -8
  111. data/sig/resolver/native.rbs +10 -5
  112. data/sig/resolver/resolver_mixin.rbs +4 -5
  113. data/sig/resolver/system.rbs +4 -0
  114. data/sig/resolver.rbs +7 -0
  115. data/sig/response.rbs +26 -13
  116. data/sig/selector.rbs +11 -9
  117. data/sig/session.rbs +22 -23
  118. data/sig/timers.rbs +32 -0
  119. data/sig/transcoder/body.rbs +6 -1
  120. data/sig/transcoder/chunker.rbs +8 -2
  121. data/sig/transcoder/form.rbs +3 -1
  122. data/sig/transcoder/json.rbs +2 -0
  123. data/sig/transcoder.rbs +13 -5
  124. data/sig/utils.rbs +6 -0
  125. metadata +18 -18
  126. data/lib/httpx/request2.rb +0 -14
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b9ddf7801add132a755b4ae9a069d5267251780577ca68398f92ca04788ea5ba
4
- data.tar.gz: 17a8072343c029940262b14810d66744d7761e561c2f0f0c5bb892737690c761
3
+ metadata.gz: 372f89cdf05727a32d23467503299ae77831cb5d23356467dc0c7f3a22bbfd0c
4
+ data.tar.gz: bbb3df9079bf4f5a449f37f34dd6b1d3208a978e8a8eaa7fdd69ea9d1829ac6c
5
5
  SHA512:
6
- metadata.gz: bcf80ebdd6b1b767f0617e5f21c162d2beaacfb988d83061aebc2a4bbf4b7308ed7a693f2147cbc31cdee01f1dd4543087b4d57d241de61bdcf3e68d07525597
7
- data.tar.gz: 176bfb2025dcf3039da7fc803a4abfada95d19a8221dfac746872f8806018b7b6b9b2c659ed6788183fe37bc661d7deb769094a9929b34534602bd7c4689ea01
6
+ metadata.gz: 38211af1e56fbc823ad0780198579ff11d4fa4b5b64677af95e78361f8b274d0dc43b124697358f1a0b17c2d45e4cdd620f97ca983af7fa60ce59515e9b51785
7
+ data.tar.gz: 2c9d48f3ad7a499046ab94ac3ee1831f1623ce44990de0f08320ac6e5ce3767ca938381cd7b96c3799477f201a811aa5c446d62f88bd58c6e1e0afd84c53af75
@@ -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.
@@ -0,0 +1,69 @@
1
+ # 0.18.0
2
+
3
+ ## Features
4
+
5
+ ### Response Cache
6
+
7
+ https://gitlab.com/honeyryderchuck/httpx/-/wikis/Response-Cache
8
+
9
+ The `:response_cache` plugin handles transparent usage of HTTP caching and conditional requests to improve performance and bandwidth usage.
10
+
11
+ ```ruby
12
+ client = HTTPX.plugin(:response_cache)
13
+ r1 = client.get("https://nghttp2.org/httpbin/cache")
14
+ r2 = client.get("https://nghttp2.org/httpbin/cache")
15
+
16
+ r1.status #=> 200
17
+ r2.status #=> 304
18
+ r1.body == r2.body #=> true
19
+ ```
20
+
21
+ ### jitter on "retry-after"
22
+
23
+ On the `:retries` plugin, jitter calculation is now applied to the value in seconds defined by user after which a request should be retried (i.e. if `:retry_after` option is set to `2`, the retry interval may be `1.5422312` seconds, for example). This is important to avoid cases of synchronized "thundering herd", where server rejects requests, but they all get retried at the same time because the retry interval is exactly the same.
24
+
25
+ You can override the jitter calculation function by using the [:retry_jitter](https://gitlab.com/honeyryderchuck/httpx/-/wikis/Retries#retry_jitter) option:
26
+
27
+ ```ruby
28
+ HTTPX.plugin(:retries, retry_after: 2, retry_jitter: ->(interval) { interval + rand }) # interval is 3
29
+ ```
30
+
31
+ You can opt out of this by setting `HTTPX_NO_JITTER=1` environment variable.
32
+
33
+ ### Response#error
34
+
35
+ `HTTPX::Response#error` was added, to match `HTTPX::Response#error`. It returns an exception for 4xx/5xx responses (`HTTPX::HTTPError`), `nil` otherwise. It allows for end users to write such code:
36
+
37
+ ```ruby
38
+ if (response = HTTPX.get(uri)).error
39
+ # success
40
+ else
41
+ #error
42
+ end
43
+ ```
44
+
45
+ ## Improvements
46
+
47
+ * `webmock` adapter: added support for "stub_http_request#to_timeout" (https://gitlab.com/honeyryderchuck/httpx/-/merge_requests/165).
48
+
49
+ ## timers not a dependency
50
+
51
+ The functionality provided by the `timers` gem was replaced by a simpler custom implementation. Although powerful, its complexity was somewhat unnecessary for `httpx`'s simpler event loop, where user-defined timeouts are usually the same for a given batch of requests. The removal of `timers` reduces the number of dependencies to 1, which is `http-2-next` and is maintained by me.
52
+
53
+ ## AWS plugins
54
+
55
+ * `aws_sdk_authentication` plugin: removed implementation relying on `aws-sdk-s3`, replacing it with an `aws-sdk-core` relying implementation, which only uses credentials strategies and region discovery (the whole point of this SDK is to use a minimal subset of AWS SDK).
56
+
57
+
58
+ ## Bugfixes
59
+
60
+ * Fixed Error class declaration on response decoders when mime type is invalid (https://gitlab.com/honeyryderchuck/httpx/-/merge_requests/166).
61
+ * `ErrorResponse#to_s` now removes ANSI escape sequences from error backtraces.
62
+ * Persistent connections were kept around both in the pool and in the selector; the first is necessary, but the second caused busy loop scenarios all over; they are now removed when no requests are being handled.
63
+ * Connections which failed connection handshake were removed from the pool, but not from the selector list, causing busy loop scenarios in a few cases; this has been fixed.
64
+ * Fixed issue where HTTP/2 streams were being closed twice (and signaling it also twice), messing connection accounting in the pool.
65
+ * DoH resolver was always subscribed to the default thread "connection pool", which broke scenarios were session was patched to use its custom pool; it now ensures that it subscribes to the pool it was created in.
66
+ * `:aws_sigv4` plugin: removed require of `aws-sdk-s3`, left there by mistake (the whole point of the plugin is to run without the AWS SDK).
67
+ ## Chore
68
+
69
+ * `HTTPX::ErrorResponse#status` is now deprecated.
@@ -64,7 +64,7 @@ module Datadog
64
64
  def finish(response)
65
65
  return unless @span
66
66
 
67
- if response.respond_to?(:error)
67
+ if response.is_a?(::HTTPX::ErrorResponse)
68
68
  @span.set_error(response.error)
69
69
  else
70
70
  @span.set_tag(Datadog::Ext::HTTP::STATUS_CODE, response.status.to_s)
@@ -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
 
@@ -21,6 +22,8 @@ module Faraday
21
22
  # :nocov:
22
23
 
23
24
  module RequestMixin
25
+ using ::HTTPX::HashExtensions
26
+
24
27
  private
25
28
 
26
29
  def build_request(env)
@@ -37,7 +40,7 @@ module Faraday
37
40
  timeout_options = {
38
41
  connect_timeout: env.request.open_timeout,
39
42
  operation_timeout: env.request.timeout,
40
- }.reject { |_, v| v.nil? }
43
+ }.compact
41
44
 
42
45
  options = {
43
46
  ssl: {},
@@ -91,15 +94,16 @@ module Faraday
91
94
  end
92
95
 
93
96
  class ParallelManager
94
- class ResponseHandler
97
+ class ResponseHandler < SimpleDelegator
95
98
  attr_reader :env
96
99
 
97
100
  def initialize(env)
98
101
  @env = env
102
+ super
99
103
  end
100
104
 
101
105
  def on_response(&blk)
102
- if block_given?
106
+ if blk
103
107
  @on_response = lambda do |response|
104
108
  blk.call(response)
105
109
  end
@@ -110,23 +114,13 @@ module Faraday
110
114
  end
111
115
 
112
116
  def on_complete(&blk)
113
- if block_given?
117
+ if blk
114
118
  @on_complete = blk
115
119
  self
116
120
  else
117
121
  @on_complete
118
122
  end
119
123
  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
124
  end
131
125
 
132
126
  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?
@@ -86,7 +86,9 @@ module WebMock
86
86
  end
87
87
 
88
88
  def _build_from_webmock_response(request, webmock_response)
89
- return ErrorResponse.new(request, webmock_response.exception, request.options) if webmock_response.exception
89
+ return _build_error_response(request, HTTPX::TimeoutError.new(1, "Timed out")) if webmock_response.should_timeout
90
+
91
+ return _build_error_response(request, webmock_response.exception) if webmock_response.exception
90
92
 
91
93
  response = request.options.response_class.new(request,
92
94
  webmock_response.status[0],
@@ -95,6 +97,10 @@ module WebMock
95
97
  response << webmock_response.body.dup
96
98
  response
97
99
  end
100
+
101
+ def _build_error_response(request, exception)
102
+ HTTPX::ErrorResponse.new(request, exception, request.options)
103
+ end
98
104
  end
99
105
  end
100
106
 
data/lib/httpx/altsvc.rb CHANGED
@@ -10,14 +10,14 @@ module HTTPX
10
10
  module_function
11
11
 
12
12
  def cached_altsvc(origin)
13
- now = Process.clock_gettime(Process::CLOCK_MONOTONIC)
13
+ now = Utils.now
14
14
  @altsvc_mutex.synchronize do
15
15
  lookup(origin, now)
16
16
  end
17
17
  end
18
18
 
19
19
  def cached_altsvc_set(origin, entry)
20
- now = Process.clock_gettime(Process::CLOCK_MONOTONIC)
20
+ now = Utils.now
21
21
  @altsvc_mutex.synchronize do
22
22
  return if @altsvcs[origin].any? { |altsvc| altsvc["origin"] == entry["origin"] }
23
23
 
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
@@ -4,9 +4,9 @@ module HTTPX
4
4
  module Chainable
5
5
  %i[head get post put delete trace options connect patch].each do |meth|
6
6
  class_eval(<<-MOD, __FILE__, __LINE__ + 1)
7
- def #{meth}(*uri, **options)
8
- request(:#{meth}, uri, **options)
9
- end
7
+ def #{meth}(*uri, **options) # def get(*uri, **options)
8
+ request(:#{meth}, uri, **options) # request(:get, uri, **options)
9
+ end # end
10
10
  MOD
11
11
  end
12
12
 
@@ -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
@@ -276,17 +283,16 @@ module HTTPX
276
283
  # on the last request of the possible batch (either allowed max requests,
277
284
  # or if smaller, the size of the batch itself)
278
285
  requests_limit = [@max_requests, @requests.size].min
279
- if request != @requests[requests_limit - 1]
280
- "keep-alive"
281
- else
286
+ if request == @requests[requests_limit - 1]
282
287
  "close"
288
+ else
289
+ "keep-alive"
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)
@@ -288,11 +291,13 @@ module HTTPX
288
291
  end
289
292
 
290
293
  def on_stream_refuse(stream, request, error)
291
- stream.close
292
294
  on_stream_close(stream, request, error)
295
+ stream.close
293
296
  end
294
297
 
295
298
  def on_stream_close(stream, request, error)
299
+ return if error == :stream_closed && !@streams.key?(request)
300
+
296
301
  log(level: 2) { "#{stream.id}: closing stream" }
297
302
  @drains.delete(request)
298
303
  @streams.delete(request)
@@ -304,7 +309,7 @@ module HTTPX
304
309
  emit(:response, request, response)
305
310
  else
306
311
  response = request.response
307
- if response.status == 421
312
+ if response && response.status == 421
308
313
  ex = MisdirectedRequestError.new(response)
309
314
  ex.set_backtrace(caller)
310
315
  emit(:error, request, ex)
@@ -385,22 +390,12 @@ module HTTPX
385
390
  end
386
391
 
387
392
  def on_pong(ping)
388
- if !@pings.delete(ping.to_s)
389
- close(:protocol_error, "ping payload did not match")
390
- else
393
+ if @pings.delete(ping.to_s)
391
394
  emit(:pong)
395
+ else
396
+ close(:protocol_error, "ping payload did not match")
392
397
  end
393
398
  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
399
  end
405
400
  Connection.register "h2", Connection::HTTP2
406
401
  end