httpx 1.7.8 → 1.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (99) hide show
  1. checksums.yaml +4 -4
  2. data/doc/release_notes/1_7_1.md +1 -2
  3. data/doc/release_notes/1_8_0.md +100 -0
  4. data/doc/release_notes/1_8_1.md +27 -0
  5. data/lib/httpx/adapters/datadog.rb +12 -1
  6. data/lib/httpx/adapters/faraday.rb +2 -2
  7. data/lib/httpx/adapters/webmock.rb +6 -0
  8. data/lib/httpx/connection/http1.rb +15 -12
  9. data/lib/httpx/connection/http2.rb +69 -9
  10. data/lib/httpx/connection.rb +119 -21
  11. data/lib/httpx/errors.rb +8 -1
  12. data/lib/httpx/headers.rb +2 -2
  13. data/lib/httpx/io/ssl.rb +8 -9
  14. data/lib/httpx/io/tcp.rb +22 -5
  15. data/lib/httpx/io/unix.rb +2 -2
  16. data/lib/httpx/loggable.rb +11 -4
  17. data/lib/httpx/options.rb +16 -4
  18. data/lib/httpx/parser/http1.rb +8 -2
  19. data/lib/httpx/plugins/auth/digest.rb +1 -1
  20. data/lib/httpx/plugins/auth.rb +61 -4
  21. data/lib/httpx/plugins/{response_cache → cache}/file_store.rb +1 -1
  22. data/lib/httpx/plugins/{response_cache → cache}/store.rb +1 -1
  23. data/lib/httpx/plugins/cache.rb +221 -0
  24. data/lib/httpx/plugins/callbacks.rb +12 -0
  25. data/lib/httpx/plugins/digest_auth.rb +4 -0
  26. data/lib/httpx/plugins/fiber_concurrency.rb +75 -4
  27. data/lib/httpx/plugins/ntlm_v2_auth.rb +92 -0
  28. data/lib/httpx/plugins/oauth.rb +66 -14
  29. data/lib/httpx/plugins/persistent.rb +18 -2
  30. data/lib/httpx/plugins/proxy.rb +5 -0
  31. data/lib/httpx/plugins/push_promise.rb +2 -2
  32. data/lib/httpx/plugins/response_cache.rb +26 -105
  33. data/lib/httpx/plugins/retries.rb +7 -5
  34. data/lib/httpx/plugins/server_sent_events.rb +158 -0
  35. data/lib/httpx/plugins/ssrf_filter.rb +17 -2
  36. data/lib/httpx/plugins/stream.rb +9 -5
  37. data/lib/httpx/plugins/stream_bidi.rb +2 -0
  38. data/lib/httpx/plugins/tracing.rb +15 -4
  39. data/lib/httpx/pool.rb +2 -2
  40. data/lib/httpx/request/body.rb +2 -2
  41. data/lib/httpx/request.rb +42 -10
  42. data/lib/httpx/resolver/cache/base.rb +1 -8
  43. data/lib/httpx/resolver/cache/file.rb +56 -0
  44. data/lib/httpx/resolver/https.rb +20 -20
  45. data/lib/httpx/resolver/native.rb +21 -3
  46. data/lib/httpx/resolver/resolver.rb +4 -0
  47. data/lib/httpx/resolver/system.rb +5 -2
  48. data/lib/httpx/response/body.rb +6 -4
  49. data/lib/httpx/response.rb +12 -5
  50. data/lib/httpx/selector.rb +12 -1
  51. data/lib/httpx/session.rb +6 -1
  52. data/lib/httpx/session_extensions.rb +2 -2
  53. data/lib/httpx/timers.rb +3 -0
  54. data/lib/httpx/version.rb +1 -1
  55. data/sig/chainable.rbs +3 -0
  56. data/sig/connection/http1.rbs +1 -1
  57. data/sig/connection/http2.rbs +4 -1
  58. data/sig/connection.rbs +16 -9
  59. data/sig/errors.rbs +9 -3
  60. data/sig/httpx.rbs +2 -0
  61. data/sig/io/tcp.rbs +2 -0
  62. data/sig/loggable.rbs +8 -0
  63. data/sig/options.rbs +25 -12
  64. data/sig/parser/http1.rbs +3 -1
  65. data/sig/plugins/auth/ntlm.rbs +1 -1
  66. data/sig/plugins/auth.rbs +4 -0
  67. data/sig/plugins/{response_cache → cache}/file_store.rbs +2 -2
  68. data/sig/plugins/{response_cache → cache}/store.rbs +2 -2
  69. data/sig/plugins/cache.rbs +69 -0
  70. data/sig/plugins/fiber_concurrency.rbs +4 -0
  71. data/sig/plugins/ntlm_v2_auth.rbs +36 -0
  72. data/sig/plugins/persistent.rbs +3 -0
  73. data/sig/plugins/response_cache.rbs +13 -38
  74. data/sig/plugins/retries.rbs +5 -5
  75. data/sig/plugins/server_sent_events.rbs +45 -0
  76. data/sig/plugins/ssrf_filter.rbs +5 -1
  77. data/sig/plugins/stream.rbs +1 -1
  78. data/sig/plugins/stream_bidi.rbs +0 -2
  79. data/sig/plugins/webdav.rbs +1 -1
  80. data/sig/pool.rbs +2 -2
  81. data/sig/request.rbs +9 -6
  82. data/sig/resolver/cache/file.rbs +13 -0
  83. data/sig/resolver/entry.rbs +1 -1
  84. data/sig/resolver/https.rbs +3 -3
  85. data/sig/resolver/multi.rbs +1 -1
  86. data/sig/resolver/native.rbs +5 -5
  87. data/sig/resolver/resolver.rbs +1 -3
  88. data/sig/resolver/system.rbs +5 -2
  89. data/sig/resolver.rbs +3 -0
  90. data/sig/response.rbs +3 -0
  91. data/sig/selector.rbs +13 -8
  92. data/sig/timers.rbs +7 -5
  93. data/sig/transcoder/body.rbs +1 -1
  94. data/sig/transcoder/gzip.rbs +3 -2
  95. data/sig/transcoder/multipart.rbs +4 -1
  96. data/sig/transcoder/utils/deflater.rbs +2 -0
  97. data/sig/transcoder.rbs +2 -0
  98. data/sig/utils.rbs +1 -1
  99. metadata +19 -7
@@ -46,8 +46,8 @@ module HTTPX
46
46
  end
47
47
  end
48
48
 
49
- def each_line
50
- return enum_for(__method__) unless block_given?
49
+ def each_line(&block)
50
+ return enum_for(__method__) unless block
51
51
 
52
52
  line = "".b
53
53
 
@@ -55,7 +55,11 @@ module HTTPX
55
55
  line << chunk
56
56
 
57
57
  while (idx = line.index("\n"))
58
- yield line.byteslice(0..(idx - 1))
58
+ if idx.zero?
59
+ yield ""
60
+ else
61
+ yield line.byteslice(0..(idx - 1))
62
+ end
59
63
 
60
64
  line = line.byteslice((idx + 1)..-1)
61
65
  end
@@ -71,11 +75,11 @@ module HTTPX
71
75
  @on_chunk.call(chunk)
72
76
  end
73
77
 
74
- # :nocov:
78
+ # simplecov:disable
75
79
  def inspect
76
80
  "#<#{self.class}:#{object_id}>"
77
81
  end
78
- # :nocov:
82
+ # simplecov:enable
79
83
 
80
84
  def to_s
81
85
  if @request.response
@@ -177,6 +177,8 @@ module HTTPX
177
177
  @pipe_read.readpartial(1)
178
178
  end
179
179
 
180
+ def initial_call; end
181
+
180
182
  def interests
181
183
  return if @closed
182
184
 
@@ -111,10 +111,11 @@ module HTTPX::Plugins
111
111
  @init_time = ::Time.now.utc
112
112
  end
113
113
 
114
- def send(request)
115
- # request init time is only the same as the connection init time
116
- # if the connection is going through the connection handshake.
117
- request.init_time ||= @init_time unless open?
114
+ def send_request_to_parser(request)
115
+ if connecting?
116
+ # request span timeframe should include the time it took to connect.
117
+ request.init_time ||= @init_time
118
+ end
118
119
 
119
120
  super
120
121
  end
@@ -126,6 +127,16 @@ module HTTPX::Plugins
126
127
  # the connection is back to :idle, and ready to connect again.
127
128
  @init_time = ::Time.now.utc
128
129
  end
130
+
131
+ private
132
+
133
+ def ping(request)
134
+ # if a connection is probed for liveness, the request timeframe should include
135
+ # it too.
136
+ request.init_time ||= ::Time.now.utc
137
+
138
+ super
139
+ end
129
140
  end
130
141
  end
131
142
  register_plugin :tracing, Tracing
data/lib/httpx/pool.rb CHANGED
@@ -173,7 +173,7 @@ module HTTPX
173
173
  end
174
174
  end
175
175
 
176
- # :nocov:
176
+ # simplecov:disable
177
177
  def inspect
178
178
  "#<#{self.class}:#{object_id} " \
179
179
  "@max_connections=#{@max_connections} " \
@@ -181,7 +181,7 @@ module HTTPX
181
181
  "@pool_timeout=#{@pool_timeout} " \
182
182
  "@connections=#{@connections.size}>"
183
183
  end
184
- # :nocov:
184
+ # simplecov:enable
185
185
 
186
186
  private
187
187
 
@@ -114,12 +114,12 @@ module HTTPX
114
114
  @headers.add("transfer-encoding", "chunked")
115
115
  end
116
116
 
117
- # :nocov:
117
+ # simplecov:disable
118
118
  def inspect
119
119
  "#<#{self.class}:#{object_id} " \
120
120
  "#{unbounded_body? ? "stream" : "@bytesize=#{bytesize}"}>"
121
121
  end
122
- # :nocov:
122
+ # simplecov:enable
123
123
 
124
124
  class << self
125
125
  def initialize_body(params)
data/lib/httpx/request.rb CHANGED
@@ -39,6 +39,10 @@ module HTTPX
39
39
  # Exception raised during enumerable body writes.
40
40
  attr_reader :drain_error
41
41
 
42
+ # when this request is sent via HTTP/2, it'll use this hash of options to set the priority of the
43
+ # respective HTTP/2 frame.
44
+ attr_reader :http2_stream_options
45
+
42
46
  # The IP address from the peer server.
43
47
  attr_accessor :peer_address
44
48
 
@@ -70,6 +74,7 @@ module HTTPX
70
74
  # :form :: hash of array of key-values which will be form-urlencoded- or multipart-encoded in requests body payload.
71
75
  # :json :: hash of array of key-values which will be JSON-encoded in requests body payload.
72
76
  # :xml :: Nokogiri XML nodes which will be encoded in requests body payload.
77
+ # :http2_stream_options :: hash of options to be used to set the HTTP/2 priority by sending an initial PRIORITY frame.
73
78
  #
74
79
  # :body, :form, :json and :xml are all mutually exclusive, i.e. only one of them gets picked up.
75
80
  def initialize(verb, uri, options, params = EMPTY_HASH)
@@ -81,6 +86,8 @@ module HTTPX
81
86
 
82
87
  @query_params = params.delete(:params) if params.key?(:params)
83
88
 
89
+ @http2_stream_options = params.key?(:http2_stream_options) ? params.delete(:http2_stream_options) : EMPTY_HASH
90
+
84
91
  @body = options.request_body_class.new(@headers, options, **params)
85
92
 
86
93
  @options = @body.options
@@ -100,7 +107,7 @@ module HTTPX
100
107
  @connection = @response =
101
108
  @drainer = @peer_address =
102
109
  @informational_status = @on_response_arrived = nil
103
- @ping = false
110
+ @ping = @started = false
104
111
  @persistent = @options.persistent
105
112
  @active_timeouts = []
106
113
  end
@@ -115,6 +122,7 @@ module HTTPX
115
122
 
116
123
  def complete!(response = @response)
117
124
  emit(:complete, response)
125
+ reset_timers(true)
118
126
  end
119
127
 
120
128
  # whether request has been buffered with a ping
@@ -142,6 +150,11 @@ module HTTPX
142
150
  @options.timeout[:request_timeout]
143
151
  end
144
152
 
153
+ # the total request timeout defined for this request.
154
+ def total_request_timeout
155
+ @options.timeout[:total_request_timeout]
156
+ end
157
+
145
158
  def persistent?
146
159
  @persistent
147
160
  end
@@ -167,6 +180,10 @@ module HTTPX
167
180
  @state != :done
168
181
  end
169
182
 
183
+ def started?
184
+ @started
185
+ end
186
+
170
187
  # merges +h+ into the instance of HTTPX::Headers of the request.
171
188
  def merge_headers(h)
172
189
  @headers = @headers.merge(h)
@@ -252,21 +269,19 @@ module HTTPX
252
269
 
253
270
  # consumes and returns the next available chunk of request body that can be sent
254
271
  def drain_body
255
- return nil if @body.nil?
272
+ return if @body.nil?
256
273
 
257
274
  @drainer ||= @body.each
258
- chunk = @drainer.next.dup
259
-
260
- emit(:body_chunk, chunk)
261
- chunk
275
+ @drainer.next.dup
262
276
  rescue StopIteration
263
277
  nil
264
278
  rescue StandardError => e
279
+ # in case an error occurs while emitting body chunks
265
280
  @drain_error = e
266
281
  nil
267
282
  end
268
283
 
269
- # :nocov:
284
+ # simplecov:disable
270
285
  def inspect
271
286
  "#<#{self.class}:#{object_id} " \
272
287
  "#{@verb} " \
@@ -274,7 +289,7 @@ module HTTPX
274
289
  "@headers=#{@headers} " \
275
290
  "@body=#{@body}>"
276
291
  end
277
- # :nocov:
292
+ # simplecov:enable
278
293
 
279
294
  # moves on to the +nextstate+ of the request state machine (when all preconditions are met)
280
295
  def transition(nextstate)
@@ -283,10 +298,14 @@ module HTTPX
283
298
  @body.rewind
284
299
  @ping = false
285
300
  @response = @drainer = nil
286
- @active_timeouts.clear
301
+
302
+ # request may be sent to a different connection and will be
303
+ # reassigned a new set of timers.
304
+ reset_timers(false)
287
305
  when :headers
288
306
  return unless @state == :idle
289
307
 
308
+ @started = true
290
309
  when :body
291
310
  return unless @state == :headers ||
292
311
  @state == :expect
@@ -309,7 +328,7 @@ module HTTPX
309
328
  return if @state == :expect
310
329
 
311
330
  end
312
- log(level: 3) { "#{@state}] -> #{nextstate}" }
331
+ log(level: 3) { "#{@state} -> #{nextstate}" }
313
332
  @state = nextstate
314
333
  emit(@state, self)
315
334
  nil
@@ -346,6 +365,19 @@ module HTTPX
346
365
 
347
366
  @on_response_arrived.call
348
367
  end
368
+
369
+ private
370
+
371
+ def reset_timers(reset_total_request_timers)
372
+ timers = @active_timeouts
373
+
374
+ until (timer = timers.shift).nil?
375
+ next if !reset_total_request_timers && timer.label == :total_request_timeout
376
+
377
+ # cancel active timers.
378
+ timer.cancel
379
+ end
380
+ end
349
381
  end
350
382
  end
351
383
 
@@ -106,14 +106,7 @@ module HTTPX
106
106
  name = entry["name"]
107
107
  next unless name != hostname
108
108
 
109
- lookups[name] ||= []
110
-
111
- case family
112
- when Socket::AF_INET6
113
- lookups[name] << entry
114
- when Socket::AF_INET
115
- lookups[name].unshift(entry)
116
- end
109
+ _set(name, family, [entry], lookups, hostnames)
117
110
  end
118
111
  end
119
112
 
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "pstore"
4
+ require "tmpdir"
5
+
6
+ module HTTPX
7
+ module Resolver::Cache
8
+ # Implementation of a file resolver cache.
9
+ class File < Base
10
+ # default path where the resolver cache is stored. It's versioned, as the file may
11
+ # change format in-between releases, and it'd signal it as corrupted.
12
+ DEFAULT_PATH = ::File.join(Dir.tmpdir, "httpx-ruby-#{VERSION}.cache")
13
+
14
+ def initialize(path = DEFAULT_PATH)
15
+ super()
16
+ @store = PStore.new(path, true)
17
+ end
18
+
19
+ def get(hostname)
20
+ now = Utils.now
21
+ @store.transaction do
22
+ lookups = @store[:lookups] || EMPTY_HASH
23
+ hostnames = @store[:hostnames] || EMPTY
24
+
25
+ _get(hostname, lookups, hostnames, now)
26
+ end
27
+ end
28
+
29
+ def set(hostname, family, entries)
30
+ @store.transaction do
31
+ lookups = @store[:lookups] || {}
32
+ hostnames = @store[:hostnames] || []
33
+
34
+ _set(hostname, family, entries, lookups, hostnames)
35
+
36
+ @store[:lookups] = lookups
37
+ @store[:hostnames] = hostnames
38
+ end
39
+ end
40
+
41
+ def evict(hostname, ip)
42
+ ip = ip.to_s
43
+
44
+ @store.transaction do
45
+ lookups = @store[:lookups] || EMPTY_HASH
46
+ hostnames = @store[:hostnames] || EMPTY
47
+
48
+ _evict(hostname, ip, lookups, hostnames)
49
+
50
+ @store[:lookups] = lookups
51
+ @store[:hostnames] = hostnames
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
@@ -126,27 +126,27 @@ module HTTPX
126
126
  end
127
127
 
128
128
  def on_response(request, response)
129
- response.raise_for_status
130
- rescue StandardError => e
131
- hostname = @requests.delete(request)
132
- connection = reset_hostname(hostname)
133
- emit_resolve_error(connection, connection.peer.host, e)
134
- close_or_resolve
135
- else
136
- # @type var response: HTTPX::Response
137
- if response.status.between?(300, 399) && response.headers.key?("location")
138
- hostname = @requests[request]
139
- connection = @queries[hostname]
140
- location_uri = URI(response.headers["location"])
141
- location_uri = response.uri.merge(location_uri) if location_uri.relative?
142
-
143
- # we assume that the DNS server URI changed permanently and move on
144
- @uri = location_uri
145
- send_request(hostname, connection)
146
- return
147
- end
129
+ if (e = response.error)
130
+ hostname = @requests.delete(request)
131
+ connection = reset_hostname(hostname)
132
+ emit_resolve_error(connection, connection.peer.host, e)
133
+ close_or_resolve
134
+ else
135
+ # @type var response: HTTPX::Response
136
+ if response.status.between?(300, 399) && response.headers.key?("location")
137
+ hostname = @requests[request]
138
+ connection = @queries[hostname]
139
+ location_uri = URI(response.headers["location"])
140
+ location_uri = response.uri.merge(location_uri) if location_uri.relative?
148
141
 
149
- parse(request, response)
142
+ # we assume that the DNS server URI changed permanently and move on
143
+ @uri = location_uri
144
+ send_request(hostname, connection)
145
+ return
146
+ end
147
+
148
+ parse(request, response)
149
+ end
150
150
  ensure
151
151
  @requests.delete(request)
152
152
  end
@@ -75,6 +75,13 @@ module HTTPX
75
75
 
76
76
  def call
77
77
  case @state
78
+ when :idle, :closed
79
+ return if @connections.empty?
80
+
81
+ transition(:idle) if @state == :closed
82
+ transition(:open)
83
+
84
+ consume if @state == :open
78
85
  when :open
79
86
  consume
80
87
  end
@@ -512,12 +519,16 @@ module HTTPX
512
519
  transition(:open)
513
520
  end
514
521
 
522
+ # moves the resolver state machine to the +nextstate+ state (if all conditions are met)-
515
523
  def transition(nextstate)
516
524
  case nextstate
517
525
  when :idle
518
- if @io
519
- @io.close
526
+ if (io = @io)
520
527
  @io = nil
528
+ io.close
529
+
530
+ # @fiber-switch-guard
531
+ return if @io
521
532
  end
522
533
  when :open
523
534
  return unless @state == :idle
@@ -531,7 +542,14 @@ module HTTPX
531
542
  when :closed
532
543
  return if @state == :closed
533
544
 
534
- @io.close if @io
545
+ if (io = @io)
546
+ @io = nil
547
+ io.close
548
+
549
+ # @fiber-switch-guard
550
+ return if @io
551
+ end
552
+
535
553
  @start_timeout = nil
536
554
  @write_buffer.clear
537
555
  @read_buffer.clear
@@ -52,6 +52,10 @@ module HTTPX
52
52
 
53
53
  alias_method :terminate, :close
54
54
 
55
+ def initial_call
56
+ call
57
+ end
58
+
55
59
  def force_close(*args)
56
60
  while (connection = @connections.shift)
57
61
  connection.force_close(*args)
@@ -23,6 +23,9 @@ module HTTPX
23
23
  DONE = 1
24
24
  ERROR = 2
25
25
 
26
+ class AddrinfoTimeoutError < StandardError
27
+ end
28
+
26
29
  class << self
27
30
  def multi?
28
31
  false
@@ -227,7 +230,7 @@ module HTTPX
227
230
  begin
228
231
  addrs = if resolve_timeout
229
232
 
230
- Timeout.timeout(resolve_timeout) do
233
+ Timeout.timeout(resolve_timeout, AddrinfoTimeoutError) do
231
234
  __addrinfo_resolve(hostname, scheme)
232
235
  end
233
236
  else
@@ -246,7 +249,7 @@ module HTTPX
246
249
  end
247
250
  end
248
251
  rescue StandardError => e
249
- if e.is_a?(Timeout::Error)
252
+ if e.is_a?(AddrinfoTimeoutError)
250
253
  timeouts.shift
251
254
  retry unless timeouts.empty?
252
255
  e = ResolveTimeoutError.new(resolve_timeout, e.message)
@@ -20,10 +20,10 @@ module HTTPX
20
20
  @headers = response.headers
21
21
  @options = options
22
22
  @window_size = options.window_size
23
+ @max_response_body_size = options.max_response_body_size
23
24
  @encodings = []
24
25
  @length = 0
25
- @buffer = nil
26
- @reader = nil
26
+ @buffer = @reader = nil
27
27
  @state = :idle
28
28
 
29
29
  # initialize response encoding
@@ -59,6 +59,8 @@ module HTTPX
59
59
 
60
60
  chunk = decode_chunk(chunk)
61
61
 
62
+ raise Error, "maximum response body size exceeded" if @max_response_body_size < @length
63
+
62
64
  transition(:open)
63
65
  @buffer.write(chunk)
64
66
 
@@ -159,13 +161,13 @@ module HTTPX
159
161
  end
160
162
  end
161
163
 
162
- # :nocov:
164
+ # simplecov:disable
163
165
  def inspect
164
166
  "#<#{self.class}:#{object_id} " \
165
167
  "@state=#{@state} " \
166
168
  "@length=#{@length}>"
167
169
  end
168
- # :nocov:
170
+ # simplecov:enable
169
171
 
170
172
  # rewinds the response payload buffer.
171
173
  def rewind
@@ -68,7 +68,7 @@ module HTTPX
68
68
  @headers = @options.headers_class.new(headers)
69
69
  @body = @options.response_body_class.new(self, @options)
70
70
  @finished = complete?
71
- @content_type = nil
71
+ @content_type = @content_length = nil
72
72
  end
73
73
 
74
74
  # dupped initialization
@@ -88,6 +88,7 @@ module HTTPX
88
88
  # merges headers defined in +h+ into the response headers.
89
89
  def merge_headers(h)
90
90
  @headers = @headers.merge(h)
91
+ @content_type = @content_length = nil
91
92
  end
92
93
 
93
94
  # writes +data+ chunk into the response body.
@@ -103,6 +104,13 @@ module HTTPX
103
104
  @content_type ||= ContentType.new(@headers["content-type"])
104
105
  end
105
106
 
107
+ # returns the response content length as advertised in the HTTP Content-Length header value.
108
+ def content_length
109
+ return @content_length if defined?(@content_length)
110
+
111
+ @content_length = @headers["content-length"]&.to_i
112
+ end
113
+
106
114
  # returns whether the response has been fully fetched.
107
115
  def finished?
108
116
  @finished
@@ -133,7 +141,7 @@ module HTTPX
133
141
  bodyless? || (@request.verb == "CONNECT" && @status == 200)
134
142
  end
135
143
 
136
- # :nocov:
144
+ # simplecov:disable
137
145
  def inspect
138
146
  "#<#{self.class}:#{object_id} " \
139
147
  "HTTP/#{version} " \
@@ -141,7 +149,7 @@ module HTTPX
141
149
  "@headers=#{@headers} " \
142
150
  "@body=#{@body.bytesize}>"
143
151
  end
144
- # :nocov:
152
+ # simplecov:enable
145
153
 
146
154
  # returns an instance of HTTPX::HTTPError if the response has a 4xx or 5xx
147
155
  # status code, or nothing.
@@ -259,7 +267,6 @@ module HTTPX
259
267
  # response = HTTPX.get("https://some-domain/path") #=> response is HTTPX::Response or HTTPX::ErrorResponse
260
268
  # response.raise_for_status #=> raises if it wraps an error
261
269
  class ErrorResponse
262
- include Loggable
263
270
  extend Forwardable
264
271
 
265
272
  # the corresponding HTTPX::Request instance.
@@ -282,7 +289,7 @@ module HTTPX
282
289
  @response = request.response if request.response.is_a?(Response)
283
290
  @error = error
284
291
  @options = request.options
285
- log_exception(@error)
292
+ @request.log_exception(@error)
286
293
  finish!
287
294
  end
288
295
 
@@ -41,6 +41,11 @@ module HTTPX
41
41
  @selectables.empty? && @timers.empty?
42
42
  end
43
43
 
44
+ # first time the registered selectables are added, there's probably work to do.
45
+ def initial_call
46
+ @selectables.each(&:initial_call)
47
+ end
48
+
44
49
  def next_tick
45
50
  catch(:jump_tick) do
46
51
  timeout = next_timeout
@@ -134,7 +139,13 @@ module HTTPX
134
139
  # the connections to be reaped (such as the total timeout error) before #select
135
140
  # gets called.
136
141
  if @selectables.empty?
137
- sleep(interval) if interval
142
+ begin
143
+ sleep(interval)
144
+ rescue IOError
145
+ # @fiber-switch-guard
146
+ # in a fiber scheduler scenario, IOs may be closed by the scheduler and raised in a separate fiber
147
+ # on wakeup, which includes a sleep call.
148
+ end if interval
138
149
  return
139
150
  end
140
151
 
data/lib/httpx/session.rb CHANGED
@@ -319,9 +319,14 @@ module HTTPX
319
319
 
320
320
  waiting = false
321
321
 
322
- responses = requests.each_with_index.map do |request, idx|
322
+ requests.each do |request|
323
323
  send_request(request, selector)
324
+ end
324
325
 
326
+ # do work first
327
+ selector.initial_call
328
+
329
+ responses = requests.each_with_index.map do |request, idx|
325
330
  fetch_response(request, selector, request.options).tap do |response|
326
331
  if response.nil?
327
332
  pending += 1
@@ -20,11 +20,11 @@ module HTTPX
20
20
  $VERBOSE = original_verbosity
21
21
  end
22
22
 
23
- # :nocov:
23
+ # simplecov:disable
24
24
  if Session.default_options.debug_level > 2
25
25
  proxy_session = plugin(:internal_telemetry)
26
26
  remove_const(:Session)
27
27
  const_set(:Session, proxy_session.class)
28
28
  end
29
- # :nocov:
29
+ # simplecov:enable
30
30
  end
data/lib/httpx/timers.rb CHANGED
@@ -62,6 +62,9 @@ module HTTPX
62
62
  end
63
63
 
64
64
  class Timer
65
+ # simpler helper which allows classification
66
+ attr_accessor :label
67
+
65
68
  def initialize(interval, callback)
66
69
  @interval = interval
67
70
  @callback = callback
data/lib/httpx/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module HTTPX
4
- VERSION = "1.7.8"
4
+ VERSION = "1.8.1"
5
5
  end
data/sig/chainable.rbs CHANGED
@@ -16,6 +16,7 @@ module HTTPX
16
16
  | (:basic_auth, ?options) -> Plugins::sessionBasicAuth
17
17
  | (:digest_auth, ?options) -> Plugins::sessionDigestAuth
18
18
  | (:ntlm_auth, ?options) -> Plugins::sessionNTLMAuth
19
+ | (:ntlm_v2_auth, ?options) -> Plugins::sessionNTLMV2Auth
19
20
  | (:aws_sdk_authentication, ?options) -> Plugins::sessionAwsSdkAuthentication
20
21
  | (:brotli, ?options) -> Session
21
22
  | (:cookies, ?options) -> Plugins::sessionCookies
@@ -32,8 +33,10 @@ module HTTPX
32
33
  | (:rate_limiter, ?options) -> Plugins::sessionRateLimiter
33
34
  | (:stream, ?options) -> Plugins::sessionStream
34
35
  | (:stream_bidi, ?options) -> Plugins::sessionStreamBidi
36
+ | (:server_sent_events, ?options) -> Plugins::sessionServerSentEvents
35
37
  | (:aws_sigv4, ?options) -> Plugins::awsSigV4Session
36
38
  | (:grpc, ?options) -> Plugins::grpcSession
39
+ | (:cache, ?options) -> Plugins::sessionCache
37
40
  | (:response_cache, ?options) -> Plugins::sessionResponseCache
38
41
  | (:circuit_breaker, ?options) -> Plugins::sessionCircuitBreaker
39
42
  | (:oauth, ?options) -> Plugins::sessionOAuth
@@ -58,7 +58,7 @@ module HTTPX
58
58
 
59
59
  def waiting_for_ping?: () -> bool
60
60
 
61
- def timeout: () -> Numeric?
61
+ def timeout: () -> interval?
62
62
 
63
63
  private
64
64