httpx 0.6.7 → 0.7.0

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: '086c7a29b7788e44b3b1e84dff63b55443e0521e7939ba680ba9334690bb3d10'
4
- data.tar.gz: d8a69f563dfd7fe892e1227ad8aba6ae258b16a10b22545aa52e5127e6822ee0
3
+ metadata.gz: e0f44b6fc0f644a3f70b58a7051e963c5f74f9a5cb8ce6ccde0f6d2aa8909b14
4
+ data.tar.gz: d341c978e49312b3abe26c24845b672be5055fd4a4a2cab9e2aa9d1acf79aa63
5
5
  SHA512:
6
- metadata.gz: 10ef2a61f98d0418eacef74e99f3d7cc3873bbd0f52ed1e312ea705df39eacb9cc943ceac30bc26b880f2faa05e9a5840706a311306616ae0cd417904e492b98
7
- data.tar.gz: 729b0ab97ae5e5a4cb6693593218c9e2a6f10505d190dd3c87461fb77f685e833180e2583bca19fdaf8f7267952f21dc752f4b4a9abf22a8a7f67fe1c85ce3c0
6
+ metadata.gz: c6a9e8caa8f2424c8628153535deda5f1d8623253801ae329884ee51f10c5f0e5237d4c6e2548faa40d14c0e0e3dd982c050e6f67521a8b2a56e39a1bbab40aa
7
+ data.tar.gz: db1b34ff9023343b4f9580b1a72c54765cf7d446f59f96bea22ec9230825ce42cd42f0ac26ec168cd3b721e75e6cf5c2964a05afea1895a778d8f27932f61d65
@@ -153,7 +153,7 @@ module Faraday
153
153
  proxy_options = { uri: env.request.proxy }
154
154
 
155
155
  session = @session.with(options_from_env(env))
156
- session = session.plugin(:proxy).with_proxy(proxy_options) if env.request.proxy
156
+ session = session.plugin(:proxy).with(proxy: proxy_options) if env.request.proxy
157
157
 
158
158
  responses = session.request(requests)
159
159
  Array(responses).each_with_index do |response, index|
@@ -192,7 +192,7 @@ module Faraday
192
192
  meth, uri, request_options = build_request(env)
193
193
 
194
194
  session = @session.with(options_from_env(env))
195
- session = session.plugin(:proxy).with_proxy(proxy_options) if env.request.proxy
195
+ session = session.plugin(:proxy).with(proxy: proxy_options) if env.request.proxy
196
196
  response = session.__send__(meth, uri, **request_options)
197
197
  response.raise_for_status unless response.is_a?(::HTTPX::Response)
198
198
  save_response(env, response.status, response.body.to_s, response.headers, response.reason) do |response_headers|
@@ -12,16 +12,20 @@ module HTTPX
12
12
  branch(default_options).request(verb, uri, **options)
13
13
  end
14
14
 
15
+ # :nocov:
15
16
  def timeout(**args)
16
- branch(default_options.with_timeout(args))
17
+ warn ":#{__method__} is deprecated, use :with_timeout instead"
18
+ branch(default_options.with(timeout: args))
17
19
  end
18
20
 
19
21
  def headers(headers)
20
- branch(default_options.with_headers(headers))
22
+ warn ":#{__method__} is deprecated, use :with_headers instead"
23
+ branch(default_options.with(headers: headers))
21
24
  end
25
+ # :nocov:
22
26
 
23
27
  def accept(type)
24
- headers("accept" => String(type))
28
+ with(headers: { "accept" => String(type) })
25
29
  end
26
30
 
27
31
  def wrap(&blk)
@@ -59,5 +63,18 @@ module HTTPX
59
63
 
60
64
  Session.new(options, &blk)
61
65
  end
66
+
67
+ def method_missing(meth, *args, **options)
68
+ if meth =~ /\Awith_(.+)/
69
+ option = Regexp.last_match(1).to_sym
70
+ with(option => (args.first || options))
71
+ else
72
+ super
73
+ end
74
+ end
75
+
76
+ def respond_to_missing?(meth, *args)
77
+ default_options.respond_to?(meth, *args) || super
78
+ end
62
79
  end
63
80
  end
@@ -46,6 +46,8 @@ module HTTPX
46
46
 
47
47
  attr_reader :origin, :state, :pending, :options
48
48
 
49
+ attr_writer :timers
50
+
49
51
  def initialize(type, uri, options)
50
52
  @type = type
51
53
  @origins = [uri.origin]
@@ -80,6 +82,8 @@ module HTTPX
80
82
  def match?(uri, options)
81
83
  return false if @state == :closing || @state == :closed
82
84
 
85
+ return false if exhausted?
86
+
83
87
  (
84
88
  (
85
89
  @origins.include?(uri.origin) &&
@@ -95,6 +99,8 @@ module HTTPX
95
99
  def mergeable?(connection)
96
100
  return false if @state == :closing || @state == :closed || !@io
97
101
 
102
+ return false if exhausted?
103
+
98
104
  !(@io.addresses & connection.addresses).empty? && @options == connection.options
99
105
  end
100
106
 
@@ -110,10 +116,13 @@ module HTTPX
110
116
  end
111
117
  end
112
118
 
119
+ def create_idle
120
+ self.class.new(@type, @origin, @options)
121
+ end
122
+
113
123
  def merge(connection)
114
124
  @origins += connection.instance_variable_get(:@origins)
115
- pending = connection.instance_variable_get(:@pending)
116
- pending.each do |req|
125
+ connection.purge_pending do |req|
117
126
  send(req)
118
127
  end
119
128
  end
@@ -130,7 +139,10 @@ module HTTPX
130
139
  end
131
140
 
132
141
  def purge_pending
133
- [*@parser.pending, *@pending].each do |pending|
142
+ pendings = []
143
+ pendings << @parser.pending if @parser
144
+ pendings << @pending
145
+ pendings.each do |pending|
134
146
  pending.reject! do |request|
135
147
  yield request
136
148
  end
@@ -213,6 +225,10 @@ module HTTPX
213
225
 
214
226
  private
215
227
 
228
+ def exhausted?
229
+ @parser && parser.exhausted?
230
+ end
231
+
216
232
  def consume
217
233
  catch(:called) do
218
234
  dread
@@ -285,6 +301,9 @@ module HTTPX
285
301
  parser.on(:promise) do |request, stream|
286
302
  request.emit(:promise, parser, stream)
287
303
  end
304
+ parser.on(:exhausted) do
305
+ emit(:exhausted)
306
+ end
288
307
  parser.on(:origin) do |origin|
289
308
  @origins << origin
290
309
  end
@@ -319,6 +338,8 @@ module HTTPX
319
338
  when :open
320
339
  return if @state == :closed
321
340
 
341
+ total_timeout
342
+
322
343
  @io.connect
323
344
  return unless @io.connected?
324
345
 
@@ -330,6 +351,11 @@ module HTTPX
330
351
  return unless @state == :closing
331
352
  return unless @write_buffer.empty?
332
353
 
354
+ if @total_timeout
355
+ @total_timeout.cancel
356
+ remove_instance_variable(:@total_timeout)
357
+ end
358
+
333
359
  @io.close
334
360
  @read_buffer.clear
335
361
  remove_instance_variable(:@timeout) if defined?(@timeout)
@@ -373,5 +399,18 @@ module HTTPX
373
399
  request.emit(:response, ErrorResponse.new(request, error, @options))
374
400
  end
375
401
  end
402
+
403
+ def total_timeout
404
+ total = @options.timeout.total_timeout
405
+
406
+ return unless total
407
+
408
+ @total_timeout ||= @timers.after(total) do
409
+ ex = TotalTimeoutError.new(total, "Timed out after #{total} seconds")
410
+ ex.set_backtrace(caller)
411
+ @parser.close if @parser
412
+ on_error(ex)
413
+ end
414
+ end
376
415
  end
377
416
  end
@@ -7,14 +7,15 @@ module HTTPX
7
7
  include Callbacks
8
8
  include Loggable
9
9
 
10
+ MAX_REQUESTS = 100
10
11
  CRLF = "\r\n"
11
12
 
12
13
  attr_reader :pending
13
14
 
14
15
  def initialize(buffer, options)
15
16
  @options = Options.new(options)
16
- @max_concurrent_requests = @options.max_concurrent_requests
17
- @max_requests = Float::INFINITY
17
+ @max_concurrent_requests = @options.max_concurrent_requests || MAX_REQUESTS
18
+ @max_requests = @options.max_requests || MAX_REQUESTS
18
19
  @parser = Parser::HTTP1.new(self)
19
20
  @buffer = buffer
20
21
  @version = [1, 1]
@@ -23,6 +24,7 @@ module HTTPX
23
24
  end
24
25
 
25
26
  def reset
27
+ @max_requests = @options.max_requests || MAX_REQUESTS
26
28
  @parser.reset!
27
29
  end
28
30
 
@@ -31,6 +33,10 @@ module HTTPX
31
33
  emit(:close)
32
34
  end
33
35
 
36
+ def exhausted?
37
+ !@max_requests.positive?
38
+ end
39
+
34
40
  def empty?
35
41
  # this means that for every request there's an available
36
42
  # partial response, so there are no in-flight requests waiting.
@@ -42,15 +48,16 @@ module HTTPX
42
48
  end
43
49
 
44
50
  def send(request)
45
- if @max_requests.positive? &&
46
- @requests.size >= @max_concurrent_requests
51
+ unless @max_requests.positive?
47
52
  @pending << request
48
53
  return
49
54
  end
55
+
50
56
  unless @requests.include?(request)
51
57
  @requests << request
52
58
  @pipelining = true if @requests.size > 1
53
59
  end
60
+
54
61
  handle(request)
55
62
  end
56
63
 
@@ -163,13 +170,11 @@ module HTTPX
163
170
  emit(:timeout, keep_alive_timeout)
164
171
  end
165
172
  when /close/i
166
- @max_requests = Float::INFINITY
167
173
  disable
168
174
  when nil
169
175
  # In HTTP/1.1, it's keep alive by default
170
176
  return if response.version == "1.1"
171
177
 
172
- @max_requests = Float::INFINITY
173
178
  disable
174
179
  end
175
180
  end
@@ -8,6 +8,8 @@ module HTTPX
8
8
  include Callbacks
9
9
  include Loggable
10
10
 
11
+ MAX_CONCURRENT_REQUESTS = HTTP2Next::DEFAULT_MAX_CONCURRENT_STREAMS
12
+
11
13
  Error = Class.new(Error) do
12
14
  def initialize(id, code)
13
15
  super("stream #{id} closed with error: #{code}")
@@ -18,7 +20,8 @@ module HTTPX
18
20
 
19
21
  def initialize(buffer, options)
20
22
  @options = Options.new(options)
21
- @max_concurrent_requests = @options.max_concurrent_requests
23
+ @max_concurrent_requests = @options.max_concurrent_requests || MAX_CONCURRENT_REQUESTS
24
+ @max_requests = @options.max_requests || 0
22
25
  @pending = []
23
26
  @streams = {}
24
27
  @drains = {}
@@ -28,20 +31,25 @@ module HTTPX
28
31
  end
29
32
 
30
33
  def close
31
- @connection.goaway
34
+ @connection.goaway unless @connection.state == :closed
32
35
  end
33
36
 
34
37
  def empty?
35
38
  @connection.state == :closed || @streams.empty?
36
39
  end
37
40
 
41
+ def exhausted?
42
+ @connection.active_stream_count >= @max_requests
43
+ end
44
+
38
45
  def <<(data)
39
46
  @connection << data
40
47
  end
41
48
 
42
- def send(request, **)
49
+ def send(request)
43
50
  if !@handshake_completed ||
44
- @streams.size >= @max_concurrent_requests
51
+ @streams.size >= @max_concurrent_requests ||
52
+ @streams.size >= @max_requests
45
53
  @pending << request
46
54
  return
47
55
  end
@@ -49,9 +57,13 @@ module HTTPX
49
57
  stream = @connection.new_stream
50
58
  handle_stream(stream, request)
51
59
  @streams[request] = stream
60
+ @max_requests -= 1
52
61
  end
53
62
  handle(request, stream)
54
63
  true
64
+ rescue HTTP2Next::Error::StreamLimitExceeded
65
+ @pending.unshift(request)
66
+ emit(:exhausted)
55
67
  end
56
68
 
57
69
  def consume
@@ -95,6 +107,7 @@ module HTTPX
95
107
 
96
108
  def init_connection
97
109
  @connection = HTTP2Next::Client.new(@options.http2_settings)
110
+ @connection.max_streams = @max_requests if @connection.respond_to?(:max_streams=) && @max_requests.positive?
98
111
  @connection.on(:frame, &method(:on_frame))
99
112
  @connection.on(:frame_sent, &method(:on_frame_sent))
100
113
  @connection.on(:frame_received, &method(:on_frame_received))
@@ -195,6 +208,11 @@ module HTTPX
195
208
 
196
209
  @streams.delete(request)
197
210
  send(@pending.shift) unless @pending.empty?
211
+ return unless @streams.empty? && exhausted?
212
+
213
+ close
214
+ emit(:close)
215
+ emit(:exhausted) unless @pending.empty?
198
216
  end
199
217
 
200
218
  def on_frame(bytes)
@@ -203,8 +221,10 @@ module HTTPX
203
221
 
204
222
  def on_settings(*)
205
223
  @handshake_completed = true
206
- @max_concurrent_requests = [@max_concurrent_requests,
207
- @connection.remote_settings[:settings_max_concurrent_streams]].min
224
+
225
+ @max_requests = [@max_requests, @connection.remote_settings[:settings_max_concurrent_streams]].max
226
+
227
+ @max_concurrent_requests = [@max_concurrent_requests, @max_requests].min
208
228
  send_pending
209
229
  end
210
230
 
@@ -2,7 +2,6 @@
2
2
 
3
3
  module HTTPX
4
4
  class Options
5
- MAX_CONCURRENT_REQUESTS = 100
6
5
  WINDOW_SIZE = 1 << 14 # 16K
7
6
  MAX_BODY_THRESHOLD_SIZE = (1 << 10) * 112 # 112K
8
7
 
@@ -28,7 +27,14 @@ module HTTPX
28
27
  defined_options << name.to_sym
29
28
  interpreter ||= ->(v) { v }
30
29
 
31
- attr_accessor name
30
+ attr_reader name
31
+
32
+ define_method(:"#{name}=") do |value|
33
+ return if value.nil?
34
+
35
+ instance_variable_set(:"@#{name}", instance_exec(value, &interpreter))
36
+ end
37
+
32
38
  protected :"#{name}="
33
39
 
34
40
  define_method(:"with_#{name}") do |value|
@@ -48,7 +54,6 @@ module HTTPX
48
54
  :fallback_protocol => "http/1.1",
49
55
  :timeout => Timeout.new,
50
56
  :headers => {},
51
- :max_concurrent_requests => MAX_CONCURRENT_REQUESTS,
52
57
  :window_size => WINDOW_SIZE,
53
58
  :body_threshold_size => MAX_BODY_THRESHOLD_SIZE,
54
59
  :request_class => Class.new(Request),
@@ -66,37 +71,48 @@ module HTTPX
66
71
 
67
72
  defaults.merge!(options)
68
73
  defaults[:headers] = Headers.new(defaults[:headers])
69
- defaults.each { |(k, v)| self[k] = v }
74
+ defaults.each do |(k, v)|
75
+ __send__(:"#{k}=", v)
76
+ end
70
77
  end
71
78
 
72
79
  def_option(:headers) do |headers|
73
- self.headers.merge(headers)
80
+ if self.headers
81
+ self.headers.merge(headers)
82
+ else
83
+ headers
84
+ end
74
85
  end
75
86
 
76
87
  def_option(:timeout) do |opts|
77
- self.timeout = Timeout.new(opts)
88
+ Timeout.new(opts)
78
89
  end
79
90
 
80
91
  def_option(:max_concurrent_requests) do |num|
81
- max = Integer(num)
82
- raise Error, ":max_concurrent_requests must be positive" unless max.positive?
92
+ raise Error, ":max_concurrent_requests must be positive" unless num.positive?
83
93
 
84
- self.max_concurrent_requests = max
94
+ num
95
+ end
96
+
97
+ def_option(:max_requests) do |num|
98
+ raise Error, ":max_requests must be positive" unless num.positive?
99
+
100
+ num
85
101
  end
86
102
 
87
103
  def_option(:window_size) do |num|
88
- self.window_size = Integer(num)
104
+ Integer(num)
89
105
  end
90
106
 
91
107
  def_option(:body_threshold_size) do |num|
92
- self.body_threshold_size = Integer(num)
108
+ Integer(num)
93
109
  end
94
110
 
95
111
  def_option(:transport) do |tr|
96
112
  transport = tr.to_s
97
113
  raise Error, "#{transport} is an unsupported transport type" unless IO.registry.key?(transport)
98
114
 
99
- self.transport = transport
115
+ transport
100
116
  end
101
117
 
102
118
  %w[
@@ -172,11 +188,5 @@ module HTTPX
172
188
  response_body_class.freeze
173
189
  connection_class.freeze
174
190
  end
175
-
176
- protected
177
-
178
- def []=(option, val)
179
- send(:"#{option}=", val)
180
- end
181
191
  end
182
192
  end
@@ -11,7 +11,7 @@ module HTTPX
11
11
  module Authentication
12
12
  module InstanceMethods
13
13
  def authentication(token)
14
- headers("authorization" => token)
14
+ with(headers: { "authorization" => token })
15
15
  end
16
16
  end
17
17
  end
@@ -17,14 +17,22 @@ module HTTPX
17
17
  def self.extra_options(options)
18
18
  Class.new(options.class) do
19
19
  def_option(:cookies) do |cookies|
20
- return cookies if cookies.is_a?(Store)
21
-
22
- Store.new(cookies)
20
+ if cookies.is_a?(Store)
21
+ cookies
22
+ else
23
+ Store.new(cookies)
24
+ end
23
25
  end
24
26
  end.new(options)
25
27
  end
26
28
 
27
29
  class Store
30
+ def self.new(cookies = nil)
31
+ return cookies if cookies.is_a?(self)
32
+
33
+ super
34
+ end
35
+
28
36
  def initialize(cookies = nil)
29
37
  @store = Hash.new { |hash, origin| hash[origin] = HTTP::CookieJar.new }
30
38
  return unless cookies
@@ -74,10 +82,6 @@ module HTTPX
74
82
  super({ cookies: Store.new }.merge(options), &blk)
75
83
  end
76
84
 
77
- def with_cookies(cookies)
78
- branch(default_options.with_cookies(cookies))
79
- end
80
-
81
85
  def wrap
82
86
  return super unless block_given?
83
87
 
@@ -86,7 +90,7 @@ module HTTPX
86
90
  begin
87
91
  yield session
88
92
  ensure
89
- @options = @options.with_cookies(old_cookies_store)
93
+ @options = @options.with(cookies: old_cookies_store)
90
94
  end
91
95
  end
92
96
  end
@@ -0,0 +1,66 @@
1
+ # frozen_string_literal: true
2
+
3
+ module HTTPX
4
+ module Plugins
5
+ #
6
+ # This plugin makes all HTTP/1.1 requests with a body send the "Expect: 100-continue".
7
+ #
8
+ # https://gitlab.com/honeyryderchuck/httpx/wikis/Expect#expect
9
+ #
10
+ module Expect
11
+ EXPECT_TIMEOUT = 2
12
+
13
+ def self.extra_options(options)
14
+ Class.new(options.class) do
15
+ def_option(:expect_timeout) do |seconds|
16
+ seconds = Integer(seconds)
17
+ raise Error, ":expect_timeout must be positive" unless seconds.positive?
18
+
19
+ seconds
20
+ end
21
+ end.new(options).merge(expect_timeout: EXPECT_TIMEOUT)
22
+ end
23
+
24
+ module RequestBodyMethods
25
+ def initialize(*)
26
+ super
27
+ return if @body.nil?
28
+
29
+ @headers["expect"] = "100-continue"
30
+ end
31
+ end
32
+
33
+ module ConnectionMethods
34
+ def send(request)
35
+ request.once(:expects) do
36
+ @timers.after(@options.expect_timeout) do
37
+ if request.state == :expects && !request.expects?
38
+ request.headers.delete("expect")
39
+ handle(request)
40
+ end
41
+ end
42
+ end
43
+ super
44
+ end
45
+ end
46
+
47
+ module InstanceMethods
48
+ def fetch_response(request, connections, options)
49
+ response = @responses.delete(request)
50
+ return unless response
51
+
52
+ if response.status == 417 && request.headers.key?("expect")
53
+ request.headers.delete("expect")
54
+ request.transition(:idle)
55
+ connection = find_connection(request, connections, options)
56
+ connection.send(request)
57
+ return
58
+ end
59
+
60
+ response
61
+ end
62
+ end
63
+ end
64
+ register_plugin :expect, Expect
65
+ end
66
+ end
@@ -21,7 +21,7 @@ module HTTPX
21
21
  Class.new(options.class) do
22
22
  def_option(:max_redirects) do |num|
23
23
  num = Integer(num)
24
- raise Error, ":max_redirects must be positive" unless num.positive?
24
+ raise Error, ":max_redirects must be positive" if num.negative?
25
25
 
26
26
  num
27
27
  end
@@ -61,7 +61,6 @@ module HTTPX
61
61
 
62
62
  connection = find_connection(retry_request, connections, options)
63
63
  connection.send(retry_request)
64
- set_request_timeout(connection, retry_request, options)
65
64
  nil
66
65
  end
67
66
 
@@ -42,7 +42,6 @@ module HTTPX
42
42
  connection = find_connection(request, connections, options)
43
43
  connections << connection unless connections.include?(connection)
44
44
  connection.upgrade(request, response)
45
- set_request_timeout(connection, request, options)
46
45
  end
47
46
  response
48
47
  end
@@ -68,17 +68,17 @@ module HTTPX
68
68
  def extra_options(options)
69
69
  Class.new(options.class) do
70
70
  def_option(:proxy) do |pr|
71
- Hash[pr]
71
+ if pr.is_a?(Parameters)
72
+ pr
73
+ else
74
+ Hash[pr]
75
+ end
72
76
  end
73
77
  end.new(options)
74
78
  end
75
79
  end
76
80
 
77
81
  module InstanceMethods
78
- def with_proxy(*args)
79
- branch(default_options.with_proxy(*args))
80
- end
81
-
82
82
  private
83
83
 
84
84
  def proxy_uris(uri, options)
@@ -104,7 +104,7 @@ module HTTPX
104
104
  connection = pool.find_connection(uri, proxy_options) || build_connection(uri, proxy_options)
105
105
  unless connections.nil? || connections.include?(connection)
106
106
  connections << connection
107
- set_connection_callbacks(connection, options)
107
+ set_connection_callbacks(connection, connections, options)
108
108
  end
109
109
  connection
110
110
  end
@@ -129,13 +129,12 @@ module HTTPX
129
129
  connection = find_connection(request, connections, options)
130
130
  connections << connection unless connections.include?(connection)
131
131
  connection.send(request)
132
- set_request_timeout(connection, request, options)
133
132
  return
134
133
  end
135
134
  response
136
135
  end
137
136
 
138
- def build_altsvc_connection(_, _, _, _, options)
137
+ def build_altsvc_connection(_, _, _, _, _, options)
139
138
  return if options.proxy
140
139
 
141
140
  super
@@ -91,15 +91,13 @@ module HTTPX
91
91
  def headline_uri(request)
92
92
  return super unless request.verb == :connect
93
93
 
94
- uri = request.uri
95
- tunnel = "#{uri.hostname}:#{uri.port}"
94
+ tunnel = request.path
96
95
  log { "establishing HTTP proxy tunnel to #{tunnel}" }
97
96
  tunnel
98
97
  end
99
98
 
100
99
  def empty?
101
- @requests.reject { |r| r.verb == :connect }.empty? ||
102
- @requests.all? { |request| !request.response.nil? }
100
+ @requests.reject { |r| r.verb == :connect }.empty? || @requests.all? { |request| !request.response.nil? }
103
101
  end
104
102
  end
105
103
 
@@ -19,10 +19,6 @@ module HTTPX
19
19
  end
20
20
 
21
21
  module InstanceMethods
22
- def with_proxy(*args)
23
- branch(default_options.with_proxy(*args))
24
- end
25
-
26
22
  private
27
23
 
28
24
  def send_requests(*requests, options)
@@ -28,10 +28,10 @@ module HTTPX
28
28
  # number of seconds after which one can retry the request
29
29
  def_option(:retry_after) do |num|
30
30
  # return early if callable
31
- return num if num.respond_to?(:call)
32
-
33
- num = Integer(num)
34
- raise Error, ":retry_after must be positive" unless num.positive?
31
+ unless num.respond_to?(:call)
32
+ num = Integer(num)
33
+ raise Error, ":retry_after must be positive" unless num.positive?
34
+ end
35
35
 
36
36
  num
37
37
  end
@@ -46,7 +46,7 @@ module HTTPX
46
46
  def_option(:retry_change_requests)
47
47
 
48
48
  def_option(:retry_on) do |callback|
49
- raise ":retry_on must be called with the response" unless callback.respond_to?(:call) && callback.method(:call).arity == 1
49
+ raise ":retry_on must be called with the response" unless callback.respond_to?(:call)
50
50
 
51
51
  callback
52
52
  end
@@ -73,8 +73,22 @@ module HTTPX
73
73
  request.retries -= 1
74
74
  log { "failed to get response, #{request.retries} tries to go..." }
75
75
  request.transition(:idle)
76
- connection = find_connection(request, connections, options)
77
- __retry_request(connection, request, options)
76
+
77
+ retry_after = options.retry_after
78
+ if retry_after
79
+ retry_after = retry_after.call(request) if retry_after.respond_to?(:call)
80
+
81
+ log { "retrying after #{retry_after} secs..." }
82
+ pool.after(retry_after) do
83
+ log { "retrying!!" }
84
+ connection = find_connection(request, connections, options)
85
+ connection.send(request)
86
+ end
87
+ else
88
+ connection = find_connection(request, connections, options)
89
+ connection.send(request)
90
+ end
91
+
78
92
  return
79
93
  end
80
94
  response
@@ -87,23 +101,6 @@ module HTTPX
87
101
  def __retryable_error?(ex)
88
102
  RETRYABLE_ERRORS.any? { |klass| ex.is_a?(klass) }
89
103
  end
90
-
91
- def __retry_request(connection, request, options)
92
- retry_after = options.retry_after
93
- unless retry_after
94
- connection.send(request)
95
- set_request_timeout(connection, request, options)
96
- return
97
- end
98
-
99
- retry_after = retry_after.call(request) if retry_after.respond_to?(:call)
100
- log { "retrying after #{retry_after} secs..." }
101
- pool.after(retry_after) do
102
- log { "retrying!!" }
103
- connection.send(request)
104
- set_request_timeout(connection, request, options)
105
- end
106
- end
107
104
  end
108
105
 
109
106
  module RequestMethods
@@ -27,7 +27,13 @@ module HTTPX
27
27
 
28
28
  def next_tick
29
29
  catch(:jump_tick) do
30
- @selector.select(next_timeout || @timers.wait_interval) do |monitor|
30
+ timeout = [next_timeout, @timers.wait_interval].compact.min
31
+ if timeout.negative?
32
+ @timers.fire
33
+ throw(:jump_tick)
34
+ end
35
+
36
+ @selector.select(timeout) do |monitor|
31
37
  monitor.io.call
32
38
  monitor.interests = monitor.io.interests
33
39
  end
@@ -51,6 +57,7 @@ module HTTPX
51
57
 
52
58
  def init_connection(connection, _options)
53
59
  resolve_connection(connection)
60
+ connection.timers = @timers
54
61
  connection.on(:open) do
55
62
  @connected_connections += 1
56
63
  end
@@ -37,8 +37,6 @@ module HTTPX
37
37
 
38
38
  attr_reader :options, :response
39
39
 
40
- attr_accessor :timer
41
-
42
40
  def_delegator :@body, :<<
43
41
 
44
42
  def_delegator :@body, :empty?
@@ -83,7 +81,6 @@ module HTTPX
83
81
  def response=(response)
84
82
  return unless response
85
83
 
86
- @timer.cancel if @timer
87
84
  @response = response
88
85
  end
89
86
 
@@ -238,15 +235,13 @@ module HTTPX
238
235
  when 100
239
236
  # deallocate
240
237
  @response = nil
241
- when 417
242
- @response = @response
243
- return
244
238
  end
245
239
  end
246
240
  when :done
247
241
  return if @state == :expect
248
242
  end
249
243
  @state = nextstate
244
+ emit(@state)
250
245
  nil
251
246
  end
252
247
 
@@ -103,6 +103,8 @@ module HTTPX
103
103
  def read(*args)
104
104
  return unless @buffer
105
105
 
106
+ rewind
107
+
106
108
  @buffer.read(*args)
107
109
  end
108
110
 
@@ -60,22 +60,33 @@ module HTTPX
60
60
  connection = pool.find_connection(uri, options) || build_connection(uri, options)
61
61
  unless connections.nil? || connections.include?(connection)
62
62
  connections << connection
63
- set_connection_callbacks(connection, options)
63
+ set_connection_callbacks(connection, connections, options)
64
64
  end
65
65
  connection
66
66
  end
67
67
 
68
- def set_connection_callbacks(connection, options)
68
+ def set_connection_callbacks(connection, connections, options)
69
69
  connection.on(:uncoalesce) do |uncoalesced_uri|
70
70
  other_connection = build_connection(uncoalesced_uri, options)
71
+ connections << other_connection
71
72
  connection.unmerge(other_connection)
72
73
  end
73
74
  connection.on(:altsvc) do |alt_origin, origin, alt_params|
74
- build_altsvc_connection(connection, alt_origin, origin, alt_params, options)
75
+ other_connection = build_altsvc_connection(connection, connections, alt_origin, origin, alt_params, options)
76
+ connections << other_connection if other_connection
77
+ end
78
+ connection.on(:exhausted) do
79
+ other_connection = connection.create_idle
80
+ other_connection.merge(connection)
81
+ catch(:coalesced) do
82
+ pool.init_connection(other_connection, options)
83
+ end
84
+ set_connection_callbacks(other_connection, connections, options)
85
+ connections << other_connection
75
86
  end
76
87
  end
77
88
 
78
- def build_altsvc_connection(existing_connection, alt_origin, origin, alt_params, options)
89
+ def build_altsvc_connection(existing_connection, connections, alt_origin, origin, alt_params, options)
79
90
  altsvc = AltSvc.cached_altsvc_set(origin, alt_params.merge("origin" => alt_origin))
80
91
 
81
92
  # altsvc already exists, somehow it wasn't advertised, probably noop
@@ -85,7 +96,7 @@ module HTTPX
85
96
  # advertised altsvc is the same origin being used, ignore
86
97
  return if connection == existing_connection
87
98
 
88
- set_connection_callbacks(connection, options)
99
+ set_connection_callbacks(connection, connections, options)
89
100
 
90
101
  log(level: 1) { "#{origin} alt-svc: #{alt_origin}" }
91
102
 
@@ -99,8 +110,10 @@ module HTTPX
99
110
  end
100
111
 
101
112
  connection.merge(existing_connection)
113
+ connection
102
114
  rescue UnsupportedSchemeError
103
115
  altsvc["noop"] = true
116
+ nil
104
117
  end
105
118
 
106
119
  def build_requests(*args, options)
@@ -155,7 +168,6 @@ module HTTPX
155
168
  error = catch(:resolve_error) do
156
169
  connection = find_connection(request, connections, request_options)
157
170
  connection.send(request)
158
- set_request_timeout(connection, request, request_options)
159
171
  end
160
172
  next unless error.is_a?(ResolveError)
161
173
 
@@ -191,21 +203,6 @@ module HTTPX
191
203
  request
192
204
  end
193
205
 
194
- def set_request_timeout(connection, request, options)
195
- total = options.timeout.total_timeout
196
- return unless total
197
-
198
- timer = pool.after(total) do
199
- unless @responses[request]
200
- error = TotalTimeoutError.new(total, "Timed out after #{total} seconds")
201
- response = ErrorResponse.new(request, error, options)
202
- request.emit(:response, response)
203
- connection.reset
204
- end
205
- end
206
- request.timer = timer
207
- end
208
-
209
206
  @default_options = Options.new
210
207
  @default_options.freeze
211
208
  @plugins = []
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module HTTPX
4
- VERSION = "0.6.7"
4
+ VERSION = "0.7.0"
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: httpx
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.7
4
+ version: 0.7.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tiago Cardoso
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-02-29 00:00:00.000000000 Z
11
+ date: 2020-03-15 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: http-2-next
@@ -109,6 +109,7 @@ files:
109
109
  - lib/httpx/plugins/compression/gzip.rb
110
110
  - lib/httpx/plugins/cookies.rb
111
111
  - lib/httpx/plugins/digest_authentication.rb
112
+ - lib/httpx/plugins/expect.rb
112
113
  - lib/httpx/plugins/follow_redirects.rb
113
114
  - lib/httpx/plugins/h2c.rb
114
115
  - lib/httpx/plugins/multipart.rb