httpx 0.6.7 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
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