httpx 0.4.1 → 0.5.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: cdf61781fa437f8bf02932b38f029d56f25443690eee087fb09974c5d48595a3
4
- data.tar.gz: 75ab23803f01f6c52bcc6f754a93c799efce99b5892c9a4f0815c6254a3e022c
3
+ metadata.gz: bbd100aaeeeb3425820db41745f3dbc9ff8f4932308a4396139025818054d8a9
4
+ data.tar.gz: a946a80e2e0060106799228bca3ff6b6c4a7317297758a90e65bd3599eb5cac0
5
5
  SHA512:
6
- metadata.gz: 57ae0b1d04da8d0e19951a704a40d9c136785281fb5ec2677aaac6b54e98baab0542ad4c8d40553270af8b120d6cf856678bd62554fe1cab7192bfdc26ee08f2
7
- data.tar.gz: 1e0f59ecd050838021578981d3ea1f3fa467671ef6c7efc740770cb33462ec81afa7e6a55bbfa2814cd19dacff0bf0e887c6b24ecea405c5440daf369074f766
6
+ metadata.gz: 95d038b06bad0457f34cb39a373691b64d4c8c2b323c84005166eb22e3240133c7e4f0975e5522c5846cf6dc1959bdec798534026e550e68bc930b0a6d68aad2
7
+ data.tar.gz: 5d1980dfab340b60851140f6d4976fd374ee74650f6b6e731ca4d6c16288dbcb2cf5acc956db09610e62eda33186afac0cc0b853218852074306644fd2acde7c
@@ -6,6 +6,18 @@ require "faraday"
6
6
  module Faraday
7
7
  class Adapter
8
8
  class HTTPX < Faraday::Adapter
9
+ SSL_ERROR = if defined?(Faraday::SSLError)
10
+ Faraday::SSLError
11
+ else
12
+ Faraday::Error::SSLError
13
+ end
14
+
15
+ CONNECTION_FAILED_ERROR = if defined?(Faraday::ConnectionFailed)
16
+ Faraday::ConnectionFailed
17
+ else
18
+ Faraday::Error::ConnectionFailed
19
+ end
20
+
9
21
  module RequestMixin
10
22
  private
11
23
 
@@ -140,7 +152,7 @@ module Faraday
140
152
  session = session.plugin(:proxy).with_proxy(proxy_options) if env.request.proxy
141
153
 
142
154
  responses = session.request(requests)
143
- responses.each_with_index do |response, index|
155
+ Array(responses).each_with_index do |response, index|
144
156
  handler = @handlers[index]
145
157
  handler.on_response.call(response)
146
158
  handler.on_complete.call(handler.env)
@@ -162,10 +174,11 @@ module Faraday
162
174
  end
163
175
 
164
176
  def call(env)
177
+ super
165
178
  if parallel?(env)
166
179
  handler = env[:parallel_manager].enqueue(env)
167
180
  handler.on_response do |response|
168
- save_response(env, response.status, response.body, response.headers, response.reason) do |response_headers|
181
+ save_response(env, response.status, response.body.to_s, response.headers, response.reason) do |response_headers|
169
182
  response_headers.merge!(response.headers)
170
183
  end
171
184
  end
@@ -178,12 +191,12 @@ module Faraday
178
191
  session = session.plugin(:proxy).with_proxy(proxy_options) if env.request.proxy
179
192
  response = session.__send__(*request_options)
180
193
  response.raise_for_status unless response.is_a?(::HTTPX::Response)
181
- save_response(env, response.status, response.body, response.headers, response.reason) do |response_headers|
194
+ save_response(env, response.status, response.body.to_s, response.headers, response.reason) do |response_headers|
182
195
  response_headers.merge!(response.headers)
183
196
  end
184
197
  @app.call(env)
185
198
  rescue OpenSSL::SSL::SSLError => err
186
- raise Error::SSLError, err
199
+ raise SSL_ERROR, err
187
200
  rescue Errno::ECONNABORTED,
188
201
  Errno::ECONNREFUSED,
189
202
  Errno::ECONNRESET,
@@ -191,7 +204,7 @@ module Faraday
191
204
  Errno::EINVAL,
192
205
  Errno::ENETUNREACH,
193
206
  Errno::EPIPE => err
194
- raise Error::ConnectionFailed, err
207
+ raise CONNECTION_FAILED_ERROR, err
195
208
  end
196
209
 
197
210
  private
data/lib/httpx/altsvc.rb CHANGED
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "strscan"
4
+
3
5
  module HTTPX
4
6
  module AltSvc
5
7
  @altsvc_mutex = Mutex.new
@@ -49,9 +51,18 @@ module HTTPX
49
51
  def parse(altsvc)
50
52
  return enum_for(__method__, altsvc) unless block_given?
51
53
 
52
- alt_origins, *alt_params = altsvc.split(/ *; */)
53
- alt_params = Hash[alt_params.map { |field| field.split("=") }]
54
- alt_origins.split(/ *, */).each do |alt_origin|
54
+ scanner = StringScanner.new(altsvc)
55
+ until scanner.eos?
56
+ alt_origin = scanner.scan(/[^=]+=("[^"]+"|[^;,]+)/)
57
+
58
+ alt_params = []
59
+ loop do
60
+ alt_param = scanner.scan(/[^=]+=("[^"]+"|[^;,]+)/)
61
+ alt_params << alt_param.strip if alt_param
62
+ scanner.skip(/;/)
63
+ break if scanner.eos? || scanner.scan(/ *, */)
64
+ end
65
+ alt_params = Hash[alt_params.map { |field| field.split("=") }]
55
66
  yield(parse_altsvc_origin(alt_origin), alt_params)
56
67
  end
57
68
  end
@@ -46,8 +46,6 @@ module HTTPX
46
46
 
47
47
  attr_reader :origin, :state, :pending, :options
48
48
 
49
- attr_reader :timeout
50
-
51
49
  def initialize(type, uri, options)
52
50
  @type = type
53
51
  @origins = [uri.origin]
@@ -148,13 +146,7 @@ module HTTPX
148
146
  def interests
149
147
  return :w if @state == :idle
150
148
 
151
- readable = !@read_buffer.full?
152
- writable = !@write_buffer.empty?
153
- if readable
154
- writable ? :rw : :r
155
- else
156
- writable ? :w : :r
157
- end
149
+ :rw
158
150
  end
159
151
 
160
152
  def to_io
@@ -177,9 +169,7 @@ module HTTPX
177
169
  end
178
170
 
179
171
  def send(request)
180
- if @error
181
- emit(:response, request, ErrorResponse.new(request, @error, @options))
182
- elsif @parser && !@write_buffer.full?
172
+ if @parser && !@write_buffer.full?
183
173
  request.headers["alt-used"] = @origin.authority if match_altsvcs?(request.uri)
184
174
  parser.send(request)
185
175
  else
@@ -188,7 +178,6 @@ module HTTPX
188
178
  end
189
179
 
190
180
  def call
191
- @timeout = @timeout_threshold
192
181
  case @state
193
182
  when :closed
194
183
  return
@@ -202,6 +191,14 @@ module HTTPX
202
191
  nil
203
192
  end
204
193
 
194
+ def timeout
195
+ return @timeout if defined?(@timeout)
196
+
197
+ return @options.timeout.connect_timeout if @state == :idle
198
+
199
+ @options.timeout.operation_timeout
200
+ end
201
+
205
202
  private
206
203
 
207
204
  def consume
@@ -289,8 +286,8 @@ module HTTPX
289
286
  transition(:open)
290
287
  end
291
288
  end
292
- parser.on(:timeout) do |timeout|
293
- @timeout = timeout
289
+ parser.on(:timeout) do |tout|
290
+ @timeout = tout
294
291
  end
295
292
  parser.on(:error) do |request, ex|
296
293
  case ex
@@ -305,10 +302,6 @@ module HTTPX
305
302
 
306
303
  def transition(nextstate)
307
304
  case nextstate
308
- when :idle
309
- @error = nil
310
- @timeout_threshold = @options.timeout.connect_timeout
311
- @timeout = @timeout_threshold
312
305
  when :open
313
306
  return if @state == :closed
314
307
 
@@ -316,8 +309,6 @@ module HTTPX
316
309
  return unless @io.connected?
317
310
 
318
311
  send_pending
319
- @timeout_threshold = @options.timeout.operation_timeout
320
- @timeout = @timeout_threshold
321
312
  emit(:open)
322
313
  when :closing
323
314
  return unless @state == :open
@@ -327,11 +318,10 @@ module HTTPX
327
318
 
328
319
  @io.close
329
320
  @read_buffer.clear
321
+ remove_instance_variable(:@timeout) if defined?(@timeout)
330
322
  when :already_open
331
323
  nextstate = :open
332
324
  send_pending
333
- @timeout_threshold = @options.timeout.operation_timeout
334
- @timeout = @timeout_threshold
335
325
  end
336
326
  @state = nextstate
337
327
  rescue Errno::EHOSTUNREACH
@@ -354,18 +344,17 @@ module HTTPX
354
344
  reset
355
345
  end
356
346
 
357
- def handle_error(e)
358
- if e.instance_of?(TimeoutError) && @timeout
359
- @timeout -= e.timeout
347
+ def handle_error(error)
348
+ if error.instance_of?(TimeoutError) && @timeout
349
+ @timeout -= error.timeout
360
350
  return unless @timeout <= 0
361
351
 
362
- e = e.to_connection_error if connecting?
352
+ error = error.to_connection_error if connecting?
363
353
  end
364
354
 
365
- parser.handle_error(e) if @parser && parser.respond_to?(:handle_error)
366
- @error = e
355
+ parser.handle_error(error) if @parser && parser.respond_to?(:handle_error)
367
356
  @pending.each do |request, _|
368
- request.emit(:response, ErrorResponse.new(request, @error, @options))
357
+ request.emit(:response, ErrorResponse.new(request, error, @options))
369
358
  end
370
359
  end
371
360
  end
@@ -61,6 +61,7 @@ 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)
64
65
  nil
65
66
  end
66
67
 
@@ -44,6 +44,7 @@ module HTTPX
44
44
  connection = find_connection(request, connections, options)
45
45
  connections << connection unless connections.include?(connection)
46
46
  connection.upgrade(request, response)
47
+ set_request_timeout(connection, request, options)
47
48
  end
48
49
  response
49
50
  end
@@ -119,6 +119,7 @@ module HTTPX
119
119
  connection = find_connection(request, connections, options)
120
120
  connections << connection unless connections.include?(connection)
121
121
  connection.send(request)
122
+ set_request_timeout(connection, request, options)
122
123
  return
123
124
  end
124
125
  response
@@ -54,6 +54,7 @@ module HTTPX
54
54
  request.transition(:idle)
55
55
  connection = find_connection(request, connections, options)
56
56
  connection.send(request)
57
+ set_request_timeout(connection, request, options)
57
58
  return
58
59
  end
59
60
  response
data/lib/httpx/pool.rb CHANGED
@@ -1,14 +1,21 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "forwardable"
4
+ require "timers"
3
5
  require "httpx/selector"
4
6
  require "httpx/connection"
5
7
  require "httpx/resolver"
6
8
 
7
9
  module HTTPX
8
10
  class Pool
11
+ extend Forwardable
12
+
13
+ def_delegator :@timers, :after
14
+
9
15
  def initialize
10
16
  @resolvers = {}
11
17
  @_resolver_monitors = {}
18
+ @timers = Timers::Group.new
12
19
  @selector = Selector.new
13
20
  @connections = []
14
21
  @connected_connections = 0
@@ -18,14 +25,13 @@ module HTTPX
18
25
  @connections.empty?
19
26
  end
20
27
 
21
- def next_tick(timeout = nil)
28
+ def next_tick
22
29
  catch(:jump_tick) do
23
- tout = timeout.total_timeout if timeout
24
-
25
- @selector.select(next_timeout || tout) do |monitor|
30
+ @selector.select(next_timeout || @timers.wait_interval) do |monitor|
26
31
  monitor.io.call
27
32
  monitor.interests = monitor.io.interests
28
33
  end
34
+ @timers.fire
29
35
  end
30
36
  rescue StandardError => ex
31
37
  @connections.each do |connection|
@@ -34,6 +40,7 @@ module HTTPX
34
40
  end
35
41
 
36
42
  def close(connections = @connections)
43
+ @timers.cancel
37
44
  connections = connections.reject(&:inflight?)
38
45
  connections.each(&:close)
39
46
  next_tick until connections.none? { |c| @connections.include?(c) }
data/lib/httpx/request.rb CHANGED
@@ -35,9 +35,9 @@ module HTTPX
35
35
 
36
36
  attr_reader :verb, :uri, :headers, :body, :state
37
37
 
38
- attr_reader :options
38
+ attr_reader :options, :response
39
39
 
40
- attr_accessor :response
40
+ attr_accessor :timer
41
41
 
42
42
  def_delegator :@body, :<<
43
43
 
@@ -68,6 +68,13 @@ module HTTPX
68
68
  @uri.scheme
69
69
  end
70
70
 
71
+ def response=(response)
72
+ return unless response
73
+
74
+ @timer.cancel if @timer
75
+ @response = response
76
+ end
77
+
71
78
  def path
72
79
  path = uri.path.dup
73
80
  path << "/" if path.empty?
@@ -31,6 +31,18 @@ module HTTPX
31
31
  }.freeze
32
32
  end
33
33
 
34
+ # nameservers for ipv6 are misconfigured in certain systems;
35
+ # this can use an unexpected endless loop
36
+ # https://gitlab.com/honeyryderchuck/httpx/issues/56
37
+ DEFAULTS[:nameserver].select! do |nameserver|
38
+ begin
39
+ IPAddr.new(nameserver)
40
+ true
41
+ rescue IPAddr::InvalidAddressError
42
+ false
43
+ end
44
+ end if DEFAULTS[:nameserver]
45
+
34
46
  DNS_PORT = 53
35
47
 
36
48
  def_delegator :@connections, :empty?
@@ -87,7 +99,7 @@ module HTTPX
87
99
  else
88
100
  if e.respond_to?(:connection) &&
89
101
  e.respond_to?(:host)
90
- emit_resolve_error(connection, host, e)
102
+ emit_resolve_error(e.connection, e.host, e)
91
103
  else
92
104
  @queries.each do |host, connection|
93
105
  emit_resolve_error(connection, host, e)
@@ -111,7 +111,7 @@ module HTTPX
111
111
  unless @state == :idle
112
112
  rewind
113
113
  while (chunk = @buffer.read(@window_size))
114
- yield(chunk)
114
+ yield(chunk.force_encoding(@encoding))
115
115
  end
116
116
  end
117
117
  ensure
@@ -129,7 +129,7 @@ module HTTPX
129
129
  return content
130
130
  end
131
131
  end
132
- ""
132
+ "".b
133
133
  ensure
134
134
  close
135
135
  end
@@ -178,7 +178,7 @@ module HTTPX
178
178
  when :idle
179
179
  if @length > @threshold_size
180
180
  @state = :buffer
181
- @buffer = Tempfile.new("httpx", encoding: @encoding, mode: File::RDWR)
181
+ @buffer = Tempfile.new("httpx", encoding: Encoding::BINARY, mode: File::RDWR)
182
182
  else
183
183
  @state = :memory
184
184
  @buffer = StringIO.new("".b, File::RDWR)
@@ -186,7 +186,7 @@ module HTTPX
186
186
  when :memory
187
187
  if @length > @threshold_size
188
188
  aux = @buffer
189
- @buffer = Tempfile.new("httpx", encoding: @encoding, mode: File::RDWR)
189
+ @buffer = Tempfile.new("httpx", encoding: Encoding::BINARY, mode: File::RDWR)
190
190
  aux.rewind
191
191
  ::IO.copy_stream(aux, @buffer)
192
192
  # (this looks like a bug from Ruby < 2.3
@@ -48,44 +48,25 @@ class HTTPX::Selector
48
48
  end
49
49
 
50
50
  def initialize
51
- @readers = {}
52
- @writers = {}
51
+ @selectables = {}
53
52
  @__r__, @__w__ = IO.pipe
54
53
  @closed = false
55
54
  end
56
55
 
57
56
  # deregisters +io+ from selectables.
58
57
  def deregister(io)
59
- rmonitor = @readers.delete(io)
60
- wmonitor = @writers.delete(io)
61
- monitor = rmonitor || wmonitor
58
+ monitor = @selectables.delete(io)
62
59
  monitor.close(false) if monitor
63
60
  end
64
61
 
65
62
  # register +io+ for +interests+ events.
66
63
  def register(io, interests)
67
- readable = READABLE.include?(interests)
68
- writable = WRITABLE.include?(interests)
69
- if readable
70
- monitor = @readers[io]
71
- if monitor
72
- monitor.interests = interests
73
- else
74
- monitor = Monitor.new(io, interests, self)
75
- end
76
- @readers[io] = monitor
77
- @writers.delete(io) unless writable
78
- end
79
- if writable
80
- monitor = @writers[io]
81
- if monitor
82
- monitor.interests = interests
83
- else
84
- # reuse object
85
- monitor = readable ? @readers[io] : Monitor.new(io, interests, self)
86
- end
87
- @writers[io] = monitor
88
- @readers.delete(io) unless readable
64
+ monitor = @selectables[io]
65
+ if monitor
66
+ monitor.interests = interests
67
+ else
68
+ monitor = Monitor.new(io, interests, self)
69
+ @selectables[io] = monitor
89
70
  end
90
71
  monitor
91
72
  end
@@ -95,16 +76,20 @@ class HTTPX::Selector
95
76
  #
96
77
  def select(interval)
97
78
  begin
98
- r = @readers.keys
99
- w = @writers.keys
100
- r.unshift(@__r__)
79
+ r = [@__r__]
80
+ w = []
81
+
82
+ @selectables.each do |io, monitor|
83
+ r << io if monitor.interests == :r || monitor.interests == :rw
84
+ w << io if monitor.interests == :w || monitor.interests == :rw
85
+ monitor.readiness = nil
86
+ end
101
87
 
102
88
  readers, writers = IO.select(r, w, nil, interval)
103
89
 
104
90
  raise HTTPX::TimeoutError.new(interval, "timed out while waiting on select") if readers.nil? && writers.nil?
105
91
  rescue IOError, SystemCallError
106
- @readers.reject! { |io, _| io.closed? }
107
- @writers.reject! { |io, _| io.closed? }
92
+ @selectables.reject! { |io, _| io.closed? }
108
93
  retry
109
94
  end
110
95
 
@@ -113,7 +98,7 @@ class HTTPX::Selector
113
98
  # clean up wakeups
114
99
  @__r__.read(@__r__.stat.size)
115
100
  else
116
- monitor = io.closed? ? @readers.delete(io) : @readers[io]
101
+ monitor = io.closed? ? @selectables.delete(io) : @selectables[io]
117
102
  next unless monitor
118
103
 
119
104
  monitor.readiness = writers.delete(io) ? :rw : :r
@@ -122,7 +107,7 @@ class HTTPX::Selector
122
107
  end if readers
123
108
 
124
109
  writers.each do |io|
125
- monitor = io.closed? ? @writers.delete(io) : @writers[io]
110
+ monitor = io.closed? ? @selectables.delete(io) : @selectables[io]
126
111
  next unless monitor
127
112
 
128
113
  # don't double run this, the last iteration might have run this task already
data/lib/httpx/session.rb CHANGED
@@ -154,11 +154,11 @@ module HTTPX
154
154
  def send_requests(*requests, options)
155
155
  connections = []
156
156
  request_options = @options.merge(options)
157
- timeout = request_options.timeout
158
157
 
159
158
  requests.each do |request|
160
159
  connection = find_connection(request, connections, request_options)
161
160
  connection.send(request)
161
+ set_request_timeout(connection, request, request_options)
162
162
  end
163
163
 
164
164
  responses = []
@@ -168,7 +168,7 @@ module HTTPX
168
168
  loop do
169
169
  begin
170
170
  request = requests.first
171
- pool.next_tick(timeout) until (response = fetch_response(request, connections, request_options))
171
+ pool.next_tick until (response = fetch_response(request, connections, request_options))
172
172
 
173
173
  responses << response
174
174
  requests.shift
@@ -190,6 +190,21 @@ module HTTPX
190
190
  request
191
191
  end
192
192
 
193
+ def set_request_timeout(connection, request, options)
194
+ total = options.timeout.total_timeout
195
+ return unless total
196
+
197
+ timer = pool.after(total) do
198
+ unless @responses[request]
199
+ error = TotalTimeoutError.new(total, "Timed out after #{total} seconds")
200
+ response = ErrorResponse.new(request, error, options)
201
+ request.emit(:response, response)
202
+ connection.reset
203
+ end
204
+ end
205
+ request.timer = timer
206
+ end
207
+
193
208
  @default_options = Options.new
194
209
  @default_options.freeze
195
210
  @plugins = []
data/lib/httpx/timeout.rb CHANGED
@@ -13,7 +13,7 @@ module HTTPX
13
13
  super
14
14
  end
15
15
 
16
- attr_reader :connect_timeout, :operation_timeout
16
+ attr_reader :connect_timeout, :operation_timeout, :total_timeout
17
17
 
18
18
  def initialize(connect_timeout: CONNECT_TIMEOUT,
19
19
  operation_timeout: OPERATION_TIMEOUT,
@@ -22,17 +22,11 @@ module HTTPX
22
22
  @connect_timeout = connect_timeout
23
23
  @operation_timeout = operation_timeout
24
24
  @total_timeout = total_timeout
25
- if loop_timeout
26
- warn ":loop_timeout is deprecated, use :operation_timeout instead"
27
- @operation_timeout = loop_timeout
28
- end
29
- reset_counter
30
- end
31
25
 
32
- def total_timeout
33
- @total_timeout
34
- ensure
35
- log_time
26
+ return unless loop_timeout
27
+
28
+ warn ":loop_timeout is deprecated, use :operation_timeout instead"
29
+ @operation_timeout = loop_timeout
36
30
  end
37
31
 
38
32
  def ==(other)
@@ -61,29 +55,5 @@ module HTTPX
61
55
  raise ArgumentError, "can't merge with #{other.class}"
62
56
  end
63
57
  end
64
-
65
- def no_time_left?
66
- @time_left <= 0
67
- end
68
-
69
- private
70
-
71
- def reset_counter
72
- @time_left = @total_timeout
73
- end
74
-
75
- def reset_timer
76
- @started = Process.clock_gettime(Process::CLOCK_MONOTONIC)
77
- end
78
-
79
- def log_time
80
- return unless @time_left
81
- return reset_timer unless @started
82
-
83
- @time_left -= (Process.clock_gettime(Process::CLOCK_MONOTONIC) - @started)
84
- raise TotalTimeoutError.new(@total_timeout, "Timed out after #{@total_timeout} seconds") if no_time_left?
85
-
86
- reset_timer
87
- end
88
58
  end
89
59
  end
data/lib/httpx/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module HTTPX
4
- VERSION = "0.4.1"
4
+ VERSION = "0.5.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.4.1
4
+ version: 0.5.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: 2019-06-27 00:00:00.000000000 Z
11
+ date: 2019-09-29 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: http-2
@@ -24,6 +24,20 @@ dependencies:
24
24
  - - ">="
25
25
  - !ruby/object:Gem::Version
26
26
  version: 0.9.0
27
+ - !ruby/object:Gem::Dependency
28
+ name: timers
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
27
41
  - !ruby/object:Gem::Dependency
28
42
  name: http-form_data
29
43
  requirement: !ruby/object:Gem::Requirement