httpx 0.4.1 → 0.5.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: 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