httpx 0.17.0 → 0.18.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (64) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +4 -3
  3. data/doc/release_notes/0_18_0.md +69 -0
  4. data/doc/release_notes/0_18_1.md +12 -0
  5. data/doc/release_notes/0_18_2.md +10 -0
  6. data/doc/release_notes/0_18_3.md +7 -0
  7. data/lib/httpx/adapters/datadog.rb +1 -1
  8. data/lib/httpx/adapters/faraday.rb +5 -3
  9. data/lib/httpx/adapters/webmock.rb +7 -1
  10. data/lib/httpx/altsvc.rb +2 -2
  11. data/lib/httpx/chainable.rb +3 -3
  12. data/lib/httpx/connection/http1.rb +8 -5
  13. data/lib/httpx/connection/http2.rb +22 -7
  14. data/lib/httpx/connection.rb +70 -71
  15. data/lib/httpx/domain_name.rb +1 -1
  16. data/lib/httpx/extensions.rb +50 -4
  17. data/lib/httpx/io/ssl.rb +5 -1
  18. data/lib/httpx/io/tls.rb +7 -7
  19. data/lib/httpx/loggable.rb +5 -5
  20. data/lib/httpx/options.rb +7 -7
  21. data/lib/httpx/plugins/aws_sdk_authentication.rb +42 -18
  22. data/lib/httpx/plugins/aws_sigv4.rb +9 -11
  23. data/lib/httpx/plugins/compression.rb +5 -3
  24. data/lib/httpx/plugins/cookies/jar.rb +1 -1
  25. data/lib/httpx/plugins/expect.rb +7 -3
  26. data/lib/httpx/plugins/grpc/message.rb +2 -2
  27. data/lib/httpx/plugins/grpc.rb +3 -3
  28. data/lib/httpx/plugins/internal_telemetry.rb +8 -8
  29. data/lib/httpx/plugins/multipart.rb +2 -2
  30. data/lib/httpx/plugins/response_cache/store.rb +55 -0
  31. data/lib/httpx/plugins/response_cache.rb +88 -0
  32. data/lib/httpx/plugins/retries.rb +36 -14
  33. data/lib/httpx/plugins/stream.rb +1 -1
  34. data/lib/httpx/pool.rb +39 -13
  35. data/lib/httpx/request.rb +7 -7
  36. data/lib/httpx/resolver/https.rb +5 -7
  37. data/lib/httpx/resolver/native.rb +4 -2
  38. data/lib/httpx/resolver/system.rb +2 -0
  39. data/lib/httpx/resolver.rb +2 -2
  40. data/lib/httpx/response.rb +23 -14
  41. data/lib/httpx/selector.rb +12 -17
  42. data/lib/httpx/session.rb +7 -2
  43. data/lib/httpx/session2.rb +1 -1
  44. data/lib/httpx/timers.rb +84 -0
  45. data/lib/httpx/transcoder/body.rb +2 -1
  46. data/lib/httpx/transcoder/form.rb +1 -1
  47. data/lib/httpx/transcoder/json.rb +1 -1
  48. data/lib/httpx/utils.rb +8 -0
  49. data/lib/httpx/version.rb +1 -1
  50. data/lib/httpx.rb +1 -0
  51. data/sig/chainable.rbs +1 -0
  52. data/sig/connection/http1.rbs +5 -0
  53. data/sig/connection/http2.rbs +3 -0
  54. data/sig/connection.rbs +12 -6
  55. data/sig/plugins/aws_sdk_authentication.rbs +22 -4
  56. data/sig/plugins/response_cache.rbs +35 -0
  57. data/sig/plugins/retries.rbs +3 -0
  58. data/sig/pool.rbs +6 -0
  59. data/sig/resolver/native.rbs +3 -4
  60. data/sig/resolver/system.rbs +2 -0
  61. data/sig/response.rbs +3 -2
  62. data/sig/timers.rbs +32 -0
  63. data/sig/utils.rbs +4 -0
  64. metadata +17 -17
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f6b2befec2e4b0093acd45f7ef9ef448ad41516510710616aded46934f1e3981
4
- data.tar.gz: a9858adfacbdc27e1097b98b958f7dd1614f1207c74ec4290a54268df6270f69
3
+ metadata.gz: 81b7e14071e0e1978c864c63b0c8146044f255a9b2f7d9d3e917b564fb301ffb
4
+ data.tar.gz: 7492d34e586d86e373f62f151ceb6962719fcec156597ff34dae4c10b8347072
5
5
  SHA512:
6
- metadata.gz: 86276d59efaf3a15efe0a27fbd59bff2d005bb3bab83ee9917599854bf46eba1bfbb016b9df55172c41799d1fe82195e4bb7d82c008c1996814ec2e393be71b3
7
- data.tar.gz: bd113e65cf5700f231992bb485f6c59511f114372d53078ea53c019a654e449e5012b2a1a19f889d79cee1eecd24345d3e03f3f9fa50aa6c69060138327e1d09
6
+ metadata.gz: 5c5930cd68b2ba0f42ea60e6dc5a76ff24b2643f16c300b778e8af62f0f04d8c01baadbf0163e2b925275bbfdf44d8dd73bbf78e06dd2c86b2e54d0eb99cbbdc
7
+ data.tar.gz: 20a70c6ffc8895c7b54f84f9832bc8831942048ba37dc83907e0b4fef83484d1f6797a0792b075539cb985a8df65798085f3089e6746d1246e9d27ef0cb63d96
data/README.md CHANGED
@@ -45,7 +45,7 @@ response = HTTPX.get("https://nghttp2.org")
45
45
  puts response.status #=> 200
46
46
  body = response.body
47
47
  puts body #=> #<HTTPX::Response ...
48
- ```
48
+ ```
49
49
 
50
50
  You can also send as many requests as you want simultaneously:
51
51
 
@@ -79,7 +79,7 @@ In Ruby, HTTP client implementations are a known cheap commodity. Why this one?
79
79
 
80
80
  ### Concurrency
81
81
 
82
- This library supports HTTP/2 seamlessly (which means, if the request is secure, and the server support ALPN negotiation AND HTTP/2, the request will be made through HTTP/2). If you pass multiple URIs, and they can utilize the same connection, they will run concurrently in it.
82
+ This library supports HTTP/2 seamlessly (which means, if the request is secure, and the server support ALPN negotiation AND HTTP/2, the request will be made through HTTP/2). If you pass multiple URIs, and they can utilize the same connection, they will run concurrently in it.
83
83
 
84
84
  However if the server supports HTTP/1.1, it will try to use HTTP pipelining, falling back to 1 request at a time if the server doesn't support it (if the server support Keep-Alive connections, it will reuse the same connection).
85
85
 
@@ -137,7 +137,8 @@ In order to use HTTP/2 under JRuby, [check this link](https://gitlab.com/honeyry
137
137
 
138
138
  ### Known bugs
139
139
 
140
- Doesn't work with ruby 2.4.0 for Windows (see [#36](https://gitlab.com/honeyryderchuck/httpx/issues/36)).
140
+ * Doesn't work with ruby 2.4.0 for Windows (see [#36](https://gitlab.com/honeyryderchuck/httpx/issues/36)).
141
+ * Using `total_timeout` along with the `:persistent` plugin [does not work as you might expect](https://gitlab.com/honeyryderchuck/httpx/-/wikis/Timeouts#total_timeout).
141
142
 
142
143
  ## Contributing
143
144
 
@@ -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.
@@ -0,0 +1,12 @@
1
+ # 0.18.1
2
+
3
+ ## Bugfixes
4
+
5
+ * HTTP/1.1 pipelining logs were logging the previously-buffered requests all together for each triggered request, which created some confusion for users when reporting errors. This has been fixed.
6
+ * HTTP/2 coalescing is now skipped when performing TLS connections with VERIFY_NONE.
7
+ * HTTP/2 peer GOAWAY frames will now result in a (retryable) connection error, instead of being ignored and leaving a "ghost" connection behind.
8
+ * fixed total timeout call which was not raising the exception.
9
+
10
+ ## Chore
11
+
12
+ This gem now requires MFA-based gem releases.
@@ -0,0 +1,10 @@
1
+ # 0.18.2
2
+
3
+ ## Bugfixes
4
+
5
+ * A bug was reported and fixed, whereby a persistent connection with a `:total_timeout` set was triggering the timeout and leaving the process looping indefinitely.
6
+
7
+
8
+ ## Chore
9
+
10
+ The quirk of using the `:persistent` plugin with `:total_timeout` has been documented: https://gitlab.com/honeyryderchuck/httpx/-/wikis/Timeouts#total_timeout.
@@ -0,0 +1,7 @@
1
+ # 0.18.3
2
+
3
+ ## Bugfixes
4
+
5
+ * request bodies eager-loaded from enumerables yield duped partial chunks.
6
+
7
+ An error was observed while looking at webmock integration, where requests formed via the multipart plugin where returning an empty string as body. The issue was caused by an optimization on multipart encoder, which reuses the same buffer when reading chunks. Unfortunately, these cannot be yielded the same way via IO.copy_stream, as the same (cleared) buffer will be used to generate the eager-loaded body chunks.
@@ -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)
@@ -22,6 +22,8 @@ module Faraday
22
22
  # :nocov:
23
23
 
24
24
  module RequestMixin
25
+ using ::HTTPX::HashExtensions
26
+
25
27
  private
26
28
 
27
29
  def build_request(env)
@@ -38,7 +40,7 @@ module Faraday
38
40
  timeout_options = {
39
41
  connect_timeout: env.request.open_timeout,
40
42
  operation_timeout: env.request.timeout,
41
- }.reject { |_, v| v.nil? }
43
+ }.compact
42
44
 
43
45
  options = {
44
46
  ssl: {},
@@ -101,7 +103,7 @@ module Faraday
101
103
  end
102
104
 
103
105
  def on_response(&blk)
104
- if block_given?
106
+ if blk
105
107
  @on_response = lambda do |response|
106
108
  blk.call(response)
107
109
  end
@@ -112,7 +114,7 @@ module Faraday
112
114
  end
113
115
 
114
116
  def on_complete(&blk)
115
- if block_given?
117
+ if blk
116
118
  @on_complete = blk
117
119
  self
118
120
  else
@@ -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
 
@@ -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
 
@@ -36,6 +36,8 @@ module HTTPX
36
36
 
37
37
  request = @requests.first
38
38
 
39
+ return unless request
40
+
39
41
  return :w if request.interests == :w || !@buffer.empty?
40
42
 
41
43
  :r
@@ -283,10 +285,10 @@ module HTTPX
283
285
  # on the last request of the possible batch (either allowed max requests,
284
286
  # or if smaller, the size of the batch itself)
285
287
  requests_limit = [@max_requests, @requests.size].min
286
- if request != @requests[requests_limit - 1]
287
- "keep-alive"
288
- else
288
+ if request == @requests[requests_limit - 1]
289
289
  "close"
290
+ else
291
+ "keep-alive"
290
292
  end
291
293
  end
292
294
 
@@ -313,8 +315,9 @@ module HTTPX
313
315
  end
314
316
 
315
317
  def join_headers(request)
316
- @buffer << "#{request.verb.to_s.upcase} #{headline_uri(request)} HTTP/#{@version.join(".")}" << CRLF
317
- log(color: :yellow) { "<- HEADLINE: #{@buffer.to_s.chomp.inspect}" }
318
+ headline = "#{request.verb.to_s.upcase} #{headline_uri(request)} HTTP/#{@version.join(".")}"
319
+ @buffer << headline << CRLF
320
+ log(color: :yellow) { "<- HEADLINE: #{headline.chomp.inspect}" }
318
321
  extra_headers = set_protocol_headers(request)
319
322
  join_headers2(request.headers.each(extra_headers))
320
323
  log { "<- " }
@@ -16,6 +16,12 @@ module HTTPX
16
16
  end
17
17
  end
18
18
 
19
+ class GoawayError < Error
20
+ def initialize
21
+ super(0, :no_error)
22
+ end
23
+ end
24
+
19
25
  attr_reader :streams, :pending
20
26
 
21
27
  def initialize(buffer, options)
@@ -291,16 +297,18 @@ module HTTPX
291
297
  end
292
298
 
293
299
  def on_stream_refuse(stream, request, error)
294
- stream.close
295
300
  on_stream_close(stream, request, error)
301
+ stream.close
296
302
  end
297
303
 
298
304
  def on_stream_close(stream, request, error)
305
+ return if error == :stream_closed && !@streams.key?(request)
306
+
299
307
  log(level: 2) { "#{stream.id}: closing stream" }
300
308
  @drains.delete(request)
301
309
  @streams.delete(request)
302
310
 
303
- if error && error != :no_error
311
+ if error
304
312
  ex = Error.new(stream.id, error)
305
313
  ex.set_backtrace(caller)
306
314
  response = ErrorResponse.new(request, ex, request.options)
@@ -342,9 +350,16 @@ module HTTPX
342
350
 
343
351
  def on_close(_last_frame, error, _payload)
344
352
  is_connection_closed = @connection.state == :closed
345
- if error && error != :no_error
353
+ if error
346
354
  @buffer.clear if is_connection_closed
347
- ex = Error.new(0, error)
355
+ if error == :no_error
356
+ ex = GoawayError.new
357
+ @pending.unshift(*@streams.keys)
358
+ @drains.clear
359
+ @streams.clear
360
+ else
361
+ ex = Error.new(0, error)
362
+ end
348
363
  ex.set_backtrace(caller)
349
364
  handle_error(ex)
350
365
  end
@@ -388,10 +403,10 @@ module HTTPX
388
403
  end
389
404
 
390
405
  def on_pong(ping)
391
- if !@pings.delete(ping.to_s)
392
- close(:protocol_error, "ping payload did not match")
393
- else
406
+ if @pings.delete(ping.to_s)
394
407
  emit(:pong)
408
+ else
409
+ close(:protocol_error, "ping payload did not match")
395
410
  end
396
411
  end
397
412
  end
@@ -70,7 +70,7 @@ module HTTPX
70
70
 
71
71
  @inflight = 0
72
72
  @keep_alive_timeout = @options.timeout[:keep_alive_timeout]
73
- @keep_alive_timer = nil
73
+ @total_timeout = @options.timeout[:total_timeout]
74
74
 
75
75
  self.addresses = @options.addresses if @options.addresses
76
76
  end
@@ -117,7 +117,8 @@ module HTTPX
117
117
  def coalescable?(connection)
118
118
  if @io.protocol == "h2" &&
119
119
  @origin.scheme == "https" &&
120
- connection.origin.scheme == "https"
120
+ connection.origin.scheme == "https" &&
121
+ @io.can_verify_peer?
121
122
  @io.verify_hostname(connection.origin.host)
122
123
  else
123
124
  @origin == connection.origin
@@ -200,11 +201,9 @@ module HTTPX
200
201
  end
201
202
 
202
203
  def close
203
- @parser.close if @parser
204
- return unless @keep_alive_timer
204
+ transition(:active) if @state == :inactive
205
205
 
206
- @keep_alive_timer.cancel
207
- remove_instance_variable(:@keep_alive_timer)
206
+ @parser.close if @parser
208
207
  end
209
208
 
210
209
  def reset
@@ -216,26 +215,40 @@ module HTTPX
216
215
  def send(request)
217
216
  if @parser && !@write_buffer.full?
218
217
  request.headers["alt-used"] = @origin.authority if match_altsvcs?(request.uri)
219
- if @keep_alive_timer
218
+
219
+ if @response_received_at && @keep_alive_timeout &&
220
+ Utils.elapsed_time(@response_received_at) > @keep_alive_timeout
220
221
  # when pushing a request into an existing connection, we have to check whether there
221
222
  # is the possibility that the connection might have extended the keep alive timeout.
222
223
  # for such cases, we want to ping for availability before deciding to shovel requests.
223
- if @keep_alive_timer.fires_in.negative?
224
- @pending << request
225
- parser.ping
226
- return
227
- end
228
-
229
- @keep_alive_timer.pause
224
+ @pending << request
225
+ parser.ping
226
+ transition(:active) if @state == :inactive
227
+ return
230
228
  end
231
- @inflight += 1
232
- parser.send(request)
229
+
230
+ send_request_to_parser(request)
233
231
  else
234
232
  @pending << request
235
233
  end
236
234
  end
237
235
 
238
236
  def timeout
237
+ if @total_timeout
238
+ return @total_timeout unless @connected_at
239
+
240
+ elapsed_time = @total_timeout - Utils.elapsed_time(@connected_at)
241
+
242
+ if elapsed_time.negative?
243
+ ex = TotalTimeoutError.new(@total_timeout, "Timed out after #{@total_timeout} seconds")
244
+ ex.set_backtrace(caller)
245
+ on_error(ex)
246
+ return
247
+ end
248
+
249
+ return elapsed_time
250
+ end
251
+
239
252
  return @timeout if defined?(@timeout)
240
253
 
241
254
  return @options.timeout[:connect_timeout] if @state == :idle
@@ -243,6 +256,14 @@ module HTTPX
243
256
  @options.timeout[:operation_timeout]
244
257
  end
245
258
 
259
+ def deactivate
260
+ transition(:inactive)
261
+ end
262
+
263
+ def open?
264
+ @state == :open || @state == :inactive
265
+ end
266
+
246
267
  private
247
268
 
248
269
  def connect
@@ -379,9 +400,7 @@ module HTTPX
379
400
 
380
401
  def send_pending
381
402
  while !@write_buffer.full? && (request = @pending.shift)
382
- @inflight += 1
383
- @keep_alive_timer.pause if @keep_alive_timer
384
- parser.send(request)
403
+ send_request_to_parser(request)
385
404
  end
386
405
  end
387
406
 
@@ -389,6 +408,15 @@ module HTTPX
389
408
  @parser ||= build_parser
390
409
  end
391
410
 
411
+ def send_request_to_parser(request)
412
+ @inflight += 1
413
+ parser.send(request)
414
+
415
+ return unless @state == :inactive
416
+
417
+ transition(:active)
418
+ end
419
+
392
420
  def build_parser(protocol = @io.protocol)
393
421
  parser = registry(protocol).new(@write_buffer, @options)
394
422
  set_parser_callbacks(parser)
@@ -400,7 +428,8 @@ module HTTPX
400
428
  AltSvc.emit(request, response) do |alt_origin, origin, alt_params|
401
429
  emit(:altsvc, alt_origin, origin, alt_params)
402
430
  end
403
- handle_response
431
+ @response_received_at = Utils.now
432
+ @inflight -= 1
404
433
  request.emit(:response, response)
405
434
  end
406
435
  parser.on(:altsvc) do |alt_origin, origin, alt_params|
@@ -420,7 +449,7 @@ module HTTPX
420
449
  end
421
450
  parser.on(:close) do |force|
422
451
  transition(:closing)
423
- if force
452
+ if force || @state == :idle
424
453
  transition(:closed)
425
454
  emit(:close)
426
455
  end
@@ -435,6 +464,7 @@ module HTTPX
435
464
  transition(:closing)
436
465
  transition(:closed)
437
466
  emit(:reset)
467
+
438
468
  @parser.reset if @parser
439
469
  transition(:idle)
440
470
  transition(:open)
@@ -466,15 +496,17 @@ module HTTPX
466
496
  when :open
467
497
  return if @state == :closed
468
498
 
469
- total_timeout
470
-
471
499
  @io.connect
472
500
  return unless @io.connected?
473
501
 
502
+ @connected_at = Utils.now
503
+
474
504
  send_pending
475
505
 
476
506
  @timeout = @current_timeout = parser.timeout
477
507
  emit(:open)
508
+ when :inactive
509
+ return unless @state == :open
478
510
  when :closing
479
511
  return unless @state == :open
480
512
 
@@ -482,15 +514,15 @@ module HTTPX
482
514
  return unless @state == :closing
483
515
  return unless @write_buffer.empty?
484
516
 
485
- if @total_timeout
486
- @total_timeout.cancel
487
- remove_instance_variable(:@total_timeout)
488
- end
489
-
490
517
  purge_after_closed
491
518
  when :already_open
492
519
  nextstate = :open
493
520
  send_pending
521
+ when :active
522
+ return unless @state == :inactive
523
+
524
+ nextstate = :open
525
+ emit(:activate)
494
526
  end
495
527
  @state = nextstate
496
528
  rescue Errno::ECONNREFUSED,
@@ -506,44 +538,24 @@ module HTTPX
506
538
  def purge_after_closed
507
539
  @io.close if @io
508
540
  @read_buffer.clear
509
- if @keep_alive_timer
510
- @keep_alive_timer.cancel
511
- remove_instance_variable(:@keep_alive_timer)
512
- end
513
-
514
541
  remove_instance_variable(:@timeout) if defined?(@timeout)
515
542
  end
516
543
 
517
- def handle_response
518
- @inflight -= 1
519
- return unless @inflight.zero?
520
-
521
- if @keep_alive_timer
522
- @keep_alive_timer.resume
523
- @keep_alive_timer.reset
524
- else
525
- @keep_alive_timer = @timers.after(@keep_alive_timeout) do
526
- unless @inflight.zero?
527
- log { "(#{@origin}): keep alive timeout expired" }
528
- parser.ping
529
- end
530
- end
531
- end
532
- end
533
-
534
544
  def on_error(error)
535
545
  if error.instance_of?(TimeoutError)
536
- if @timeout
537
- @timeout -= error.timeout
538
- return unless @timeout <= 0
539
- end
540
546
 
541
- if @total_timeout && @total_timeout.fires_in.negative?
542
- ex = TotalTimeoutError.new(@total_timeout.interval, "Timed out after #{@total_timeout.interval} seconds")
547
+ if @total_timeout && @connected_at &&
548
+ Utils.elapsed_time(@connected_at) > @total_timeout
549
+ ex = TotalTimeoutError.new(@total_timeout, "Timed out after #{@total_timeout} seconds")
543
550
  ex.set_backtrace(error.backtrace)
544
551
  error = ex
545
- elsif connecting?
546
- error = error.to_connection_error
552
+ else
553
+ if @timeout
554
+ @timeout -= error.timeout
555
+ return unless @timeout <= 0
556
+ end
557
+
558
+ error = error.to_connection_error if connecting?
547
559
  end
548
560
  end
549
561
  handle_error(error)
@@ -558,18 +570,5 @@ module HTTPX
558
570
  request.emit(:response, response)
559
571
  end
560
572
  end
561
-
562
- def total_timeout
563
- total = @options.timeout[:total_timeout]
564
-
565
- return unless total
566
-
567
- @total_timeout ||= @timers.after(total) do
568
- ex = TotalTimeoutError.new(total, "Timed out after #{total} seconds")
569
- ex.set_backtrace(caller)
570
- on_error(ex)
571
- @parser.close if @parser
572
- end
573
- end
574
573
  end
575
574
  end
@@ -123,7 +123,7 @@ module HTTPX
123
123
 
124
124
  # RFC 6265 #4.1.1
125
125
  # Domain-value must be a subdomain.
126
- @domain && self <= domain && domain <= @domain ? true : false
126
+ @domain && self <= domain && domain <= @domain
127
127
  end
128
128
 
129
129
  # def ==(other)