excon 0.85.0 → 0.99.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 +4 -4
- data/CONTRIBUTING.md +0 -1
- data/CONTRIBUTORS.md +182 -140
- data/README.md +84 -53
- data/data/cacert.pem +557 -359
- data/excon.gemspec +2 -2
- data/lib/excon/connection.rb +21 -1
- data/lib/excon/constants.rb +2 -0
- data/lib/excon/instrumentors/logging_instrumentor.rb +1 -1
- data/lib/excon/middlewares/decompress.rb +9 -2
- data/lib/excon/response.rb +13 -0
- data/lib/excon/socket.rb +41 -20
- data/lib/excon/ssl_socket.rb +21 -3
- data/lib/excon/test/plugin/server/exec.rb +4 -1
- data/lib/excon/test/plugin/server/puma.rb +4 -1
- data/lib/excon/test/plugin/server/unicorn.rb +5 -0
- data/lib/excon/test/plugin/server/webrick.rb +4 -1
- data/lib/excon/utils.rb +2 -1
- data/lib/excon/version.rb +1 -1
- data/lib/excon.rb +14 -4
- metadata +6 -20
data/excon.gemspec
CHANGED
@@ -1,4 +1,5 @@
|
|
1
|
-
|
1
|
+
$LOAD_PATH.unshift File.join(File.dirname(__FILE__), 'lib')
|
2
|
+
require 'excon/version'
|
2
3
|
|
3
4
|
Gem::Specification.new do |s|
|
4
5
|
s.name = 'excon'
|
@@ -25,7 +26,6 @@ Gem::Specification.new do |s|
|
|
25
26
|
s.add_development_dependency('eventmachine', '>= 1.0.4')
|
26
27
|
s.add_development_dependency('open4')
|
27
28
|
s.add_development_dependency('rake')
|
28
|
-
s.add_development_dependency('rdoc')
|
29
29
|
s.add_development_dependency('shindo')
|
30
30
|
s.add_development_dependency('sinatra')
|
31
31
|
s.add_development_dependency('sinatra-contrib')
|
data/lib/excon/connection.rb
CHANGED
@@ -62,6 +62,7 @@ module Excon
|
|
62
62
|
# @option params [Class] :instrumentor Responds to #instrument as in ActiveSupport::Notifications
|
63
63
|
# @option params [String] :instrumentor_name Name prefix for #instrument events. Defaults to 'excon'
|
64
64
|
def initialize(params = {})
|
65
|
+
@pid = Process.pid
|
65
66
|
@data = Excon.defaults.dup
|
66
67
|
# merge does not deep-dup, so make sure headers is not the original
|
67
68
|
@data[:headers] = @data[:headers].dup
|
@@ -89,7 +90,9 @@ module Excon
|
|
89
90
|
end
|
90
91
|
|
91
92
|
if @data[:scheme] == UNIX
|
92
|
-
|
93
|
+
# 'uri' >= v0.12.0 returns an empty string instead of nil for no host.
|
94
|
+
# So treat the parameter as present if and only if it is both non-nill and non-empty.
|
95
|
+
if @data[:host] && !@data[:host].empty?
|
93
96
|
raise ArgumentError, "The `:host` parameter should not be set for `unix://` connections.\n" +
|
94
97
|
"When supplying a `unix://` URI, it should start with `unix:/` or `unix:///`."
|
95
98
|
elsif !@data[:socket]
|
@@ -190,6 +193,11 @@ module Excon
|
|
190
193
|
case error
|
191
194
|
when Excon::Errors::InvalidHeaderKey, Excon::Errors::InvalidHeaderValue, Excon::Errors::StubNotFound, Excon::Errors::Timeout
|
192
195
|
raise(error)
|
196
|
+
when Errno::EPIPE
|
197
|
+
# Read whatever remains in the pipe to aid in debugging
|
198
|
+
response = socket.read
|
199
|
+
error = Excon::Error.new(response + error.message)
|
200
|
+
raise_socket_error(error)
|
193
201
|
else
|
194
202
|
raise_socket_error(error)
|
195
203
|
end
|
@@ -248,6 +256,11 @@ module Excon
|
|
248
256
|
datum[:headers] = { 'Host' => host }.merge(datum[:headers])
|
249
257
|
end
|
250
258
|
|
259
|
+
# default to GET if no method specified
|
260
|
+
unless datum[:method]
|
261
|
+
datum[:method] = :get
|
262
|
+
end
|
263
|
+
|
251
264
|
# if path is empty or doesn't start with '/', insert one
|
252
265
|
unless datum[:path][0, 1] == '/'
|
253
266
|
datum[:path] = datum[:path].dup.insert(0, '/')
|
@@ -468,6 +481,11 @@ module Excon
|
|
468
481
|
@_excon_sockets ||= {}
|
469
482
|
@_excon_sockets.compare_by_identity
|
470
483
|
|
484
|
+
if @pid != Process.pid
|
485
|
+
@_excon_sockets.clear # GC will take care of closing sockets
|
486
|
+
@pid = Process.pid
|
487
|
+
end
|
488
|
+
|
471
489
|
if @data[:thread_safe_sockets]
|
472
490
|
# In a multi-threaded world, if the same connection is used by multiple
|
473
491
|
# threads at the same time to connect to the same destination, they may
|
@@ -567,6 +585,8 @@ module Excon
|
|
567
585
|
raise ArgumentError, "The `:ssl_proxy_headers` parameter should only be used with HTTPS requests."
|
568
586
|
end
|
569
587
|
if @data[:proxy][:scheme] == UNIX
|
588
|
+
# URI.parse might return empty string for security reasons.
|
589
|
+
@data[:proxy][:host] = nil if @data[:proxy][:host] == ""
|
570
590
|
if @data[:proxy][:host]
|
571
591
|
raise ArgumentError, "The `:host` parameter should not be set for `unix://` proxies.\n" +
|
572
592
|
"When supplying a `unix://` URI, it should start with `unix:/` or `unix:///`."
|
data/lib/excon/constants.rb
CHANGED
@@ -2,6 +2,10 @@
|
|
2
2
|
module Excon
|
3
3
|
module Middleware
|
4
4
|
class Decompress < Excon::Middleware::Base
|
5
|
+
|
6
|
+
INFLATE_ZLIB_OR_GZIP = 47 # Zlib::MAX_WBITS + 32
|
7
|
+
INFLATE_RAW = -15 # Zlib::MAX_WBITS * -1
|
8
|
+
|
5
9
|
def request_call(datum)
|
6
10
|
unless datum.has_key?(:response_block)
|
7
11
|
key = datum[:headers].keys.detect {|k| k.to_s.casecmp('Accept-Encoding') == 0 } || 'Accept-Encoding'
|
@@ -19,8 +23,11 @@ module Excon
|
|
19
23
|
encodings = Utils.split_header_value(datum[:response][:headers][key])
|
20
24
|
if (encoding = encodings.last)
|
21
25
|
if encoding.casecmp('deflate') == 0
|
22
|
-
|
23
|
-
|
26
|
+
datum[:response][:body] = begin
|
27
|
+
Zlib::Inflate.new(INFLATE_ZLIB_OR_GZIP).inflate(body)
|
28
|
+
rescue Zlib::DataError # fallback to raw on error
|
29
|
+
Zlib::Inflate.new(INFLATE_RAW).inflate(body)
|
30
|
+
end
|
24
31
|
encodings.pop
|
25
32
|
elsif encoding.casecmp('gzip') == 0 || encoding.casecmp('x-gzip') == 0
|
26
33
|
datum[:response][:body] = Zlib::GzipReader.new(StringIO.new(body)).read
|
data/lib/excon/response.rb
CHANGED
@@ -20,15 +20,24 @@ module Excon
|
|
20
20
|
def host
|
21
21
|
@data[:host]
|
22
22
|
end
|
23
|
+
def scheme
|
24
|
+
@data[:scheme]
|
25
|
+
end
|
23
26
|
def local_address
|
24
27
|
@data[:local_address]
|
25
28
|
end
|
26
29
|
def local_port
|
27
30
|
@data[:local_port]
|
28
31
|
end
|
32
|
+
def http_method # can't be named "method"
|
33
|
+
@data[:method]
|
34
|
+
end
|
29
35
|
def path
|
30
36
|
@data[:path]
|
31
37
|
end
|
38
|
+
def query
|
39
|
+
@data[:query]
|
40
|
+
end
|
32
41
|
def port
|
33
42
|
@data[:port]
|
34
43
|
end
|
@@ -72,9 +81,13 @@ module Excon
|
|
72
81
|
:body => String.new,
|
73
82
|
:cookies => [],
|
74
83
|
:host => datum[:host],
|
84
|
+
:scheme => datum[:scheme],
|
85
|
+
:method => datum[:method],
|
75
86
|
:headers => Excon::Headers.new,
|
76
87
|
:path => datum[:path],
|
88
|
+
:query => datum[:query],
|
77
89
|
:port => datum[:port],
|
90
|
+
:omit_default_port => datum[:omit_default_port],
|
78
91
|
:status => status,
|
79
92
|
:status_line => line,
|
80
93
|
:reason_phrase => reason_phrase
|
data/lib/excon/socket.rb
CHANGED
@@ -40,12 +40,14 @@ module Excon
|
|
40
40
|
|
41
41
|
def_delegators(:@socket, :close)
|
42
42
|
|
43
|
+
|
43
44
|
def initialize(data = {})
|
44
45
|
@data = data
|
45
46
|
@nonblock = data[:nonblock]
|
46
47
|
@port ||= @data[:port] || 80
|
47
48
|
@read_buffer = String.new
|
48
49
|
@eof = false
|
50
|
+
@backend_eof = false
|
49
51
|
connect
|
50
52
|
end
|
51
53
|
|
@@ -60,19 +62,31 @@ module Excon
|
|
60
62
|
end
|
61
63
|
|
62
64
|
def readline
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
65
|
+
if @nonblock && RUBY_VERSION.to_f > 1.8_7
|
66
|
+
result = String.new
|
67
|
+
block = @read_buffer
|
68
|
+
@read_buffer = String.new
|
69
|
+
|
70
|
+
loop do
|
71
|
+
idx = block.index("\n")
|
72
|
+
if idx.nil?
|
73
|
+
result << block
|
74
|
+
else
|
75
|
+
result << block.slice!(0, idx+1)
|
76
|
+
add_to_read_buffer(block)
|
77
|
+
break
|
78
|
+
end
|
79
|
+
block = read_nonblock(@data[:chunk_size]) || raise(EOFError)
|
80
|
+
end
|
81
|
+
result
|
82
|
+
else # nonblock/legacy
|
83
|
+
begin
|
84
|
+
Timeout.timeout(@data[:read_timeout]) do
|
85
|
+
@socket.readline
|
86
|
+
end
|
87
|
+
rescue Timeout::Error
|
88
|
+
raise Excon::Errors::Timeout.new('read timeout reached')
|
73
89
|
end
|
74
|
-
rescue Timeout::Error
|
75
|
-
raise Excon::Errors::Timeout.new('read timeout reached')
|
76
90
|
end
|
77
91
|
end
|
78
92
|
|
@@ -173,20 +187,27 @@ module Excon
|
|
173
187
|
end
|
174
188
|
end
|
175
189
|
|
190
|
+
def add_to_read_buffer(str)
|
191
|
+
@read_buffer << str
|
192
|
+
@eof = false
|
193
|
+
end
|
194
|
+
|
176
195
|
def read_nonblock(max_length)
|
177
196
|
begin
|
178
197
|
if max_length
|
179
|
-
until @read_buffer.length >= max_length
|
198
|
+
until @backend_eof || @read_buffer.length >= max_length
|
180
199
|
@read_buffer << @socket.read_nonblock(max_length - @read_buffer.length)
|
181
200
|
end
|
182
201
|
else
|
183
|
-
|
202
|
+
while !@backend_eof
|
184
203
|
@read_buffer << @socket.read_nonblock(@data[:chunk_size])
|
185
204
|
end
|
186
205
|
end
|
187
206
|
rescue OpenSSL::SSL::SSLError => error
|
188
207
|
if error.message == 'read would block'
|
189
|
-
|
208
|
+
if @read_buffer.empty?
|
209
|
+
select_with_timeout(@socket, :read) && retry
|
210
|
+
end
|
190
211
|
else
|
191
212
|
raise(error)
|
192
213
|
end
|
@@ -196,10 +217,10 @@ module Excon
|
|
196
217
|
select_with_timeout(@socket, :read) && retry
|
197
218
|
end
|
198
219
|
rescue EOFError
|
199
|
-
@
|
220
|
+
@backend_eof = true
|
200
221
|
end
|
201
222
|
|
202
|
-
if max_length
|
223
|
+
ret = if max_length
|
203
224
|
if @read_buffer.empty?
|
204
225
|
nil # EOF met at beginning
|
205
226
|
else
|
@@ -209,6 +230,8 @@ module Excon
|
|
209
230
|
# read until EOFError, so return everything
|
210
231
|
@read_buffer.slice!(0, @read_buffer.length)
|
211
232
|
end
|
233
|
+
@eof = @backend_eof && @read_buffer.empty?
|
234
|
+
ret
|
212
235
|
end
|
213
236
|
|
214
237
|
def read_block(max_length)
|
@@ -220,9 +243,7 @@ module Excon
|
|
220
243
|
raise(error)
|
221
244
|
end
|
222
245
|
rescue *READ_RETRY_EXCEPTION_CLASSES
|
223
|
-
|
224
|
-
select_with_timeout(@socket, :read) && retry
|
225
|
-
end
|
246
|
+
select_with_timeout(@socket, :read) && retry
|
226
247
|
rescue EOFError
|
227
248
|
@eof = true
|
228
249
|
end
|
data/lib/excon/ssl_socket.rb
CHANGED
@@ -54,11 +54,13 @@ module Excon
|
|
54
54
|
ssl_context.cert_store = cert_store
|
55
55
|
end
|
56
56
|
|
57
|
-
|
58
|
-
unless ca_file || ca_path || cert_store
|
57
|
+
if cert_store.nil?
|
59
58
|
ssl_context.cert_store = OpenSSL::X509::Store.new
|
60
59
|
ssl_context.cert_store.set_default_paths
|
60
|
+
end
|
61
61
|
|
62
|
+
# no defaults, fallback to bundled
|
63
|
+
unless ca_file || ca_path || cert_store
|
62
64
|
# workaround issue #257 (JRUBY-6970)
|
63
65
|
ca_file = DEFAULT_CA_FILE
|
64
66
|
ca_file = ca_file.gsub(/^jar:/, '') if ca_file =~ /^jar:file:\//
|
@@ -88,6 +90,14 @@ module Excon
|
|
88
90
|
else
|
89
91
|
ssl_context.key = OpenSSL::PKey::RSA.new(client_key_data, client_key_pass)
|
90
92
|
end
|
93
|
+
if client_chain_data && OpenSSL::X509::Certificate.respond_to?(:load)
|
94
|
+
ssl_context.extra_chain_cert = OpenSSL::X509::Certificate.load(client_chain_data)
|
95
|
+
elsif client_chain_data
|
96
|
+
certs = client_chain_data.scan(/-----BEGIN CERTIFICATE-----(?:.|\n)+?-----END CERTIFICATE-----/)
|
97
|
+
ssl_context.extra_chain_cert = certs.map do |cert|
|
98
|
+
OpenSSL::X509::Certificate.new(cert)
|
99
|
+
end
|
100
|
+
end
|
91
101
|
elsif @data.key?(:certificate) && @data.key?(:private_key)
|
92
102
|
ssl_context.cert = OpenSSL::X509::Certificate.new(@data[:certificate])
|
93
103
|
if OpenSSL::PKey.respond_to? :read
|
@@ -131,7 +141,7 @@ module Excon
|
|
131
141
|
|
132
142
|
# Server Name Indication (SNI) RFC 3546
|
133
143
|
if @socket.respond_to?(:hostname=)
|
134
|
-
@socket.hostname = @data[:host]
|
144
|
+
@socket.hostname = @data[:ssl_verify_peer_host] || @data[:host]
|
135
145
|
end
|
136
146
|
|
137
147
|
begin
|
@@ -169,6 +179,14 @@ module Excon
|
|
169
179
|
end
|
170
180
|
end
|
171
181
|
|
182
|
+
def client_chain_data
|
183
|
+
@client_chain_data ||= if (ccd = @data[:client_chain_data])
|
184
|
+
ccd
|
185
|
+
elsif (path = @data[:client_chain])
|
186
|
+
File.read path
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
172
190
|
def connect
|
173
191
|
# backwards compatability for things lacking nonblock
|
174
192
|
@nonblock = HAVE_NONBLOCK && @nonblock
|
@@ -4,10 +4,13 @@ module Excon
|
|
4
4
|
module Server
|
5
5
|
module Exec
|
6
6
|
def start(app_str = app)
|
7
|
-
line = ''
|
8
7
|
open_process(app_str)
|
8
|
+
process_stderr = ""
|
9
|
+
line = ''
|
9
10
|
until line =~ /\Aready\Z/
|
10
11
|
line = error.gets
|
12
|
+
raise process_stderr if line.nil?
|
13
|
+
process_stderr << line
|
11
14
|
fatal_time = elapsed_time > timeout
|
12
15
|
if fatal_time
|
13
16
|
msg = "executable #{app_str} has taken too long to start"
|
@@ -4,10 +4,13 @@ module Excon
|
|
4
4
|
module Server
|
5
5
|
module Puma
|
6
6
|
def start(app_str = app, bind_uri = bind)
|
7
|
-
open_process('puma', '-b', bind_uri.to_s, app_str)
|
7
|
+
open_process(RbConfig.ruby, '-S', 'puma', '-b', bind_uri.to_s, app_str)
|
8
|
+
process_stderr = ""
|
8
9
|
line = ''
|
9
10
|
until line =~ /Use Ctrl-C to stop/
|
10
11
|
line = read.gets
|
12
|
+
raise process_stderr if line.nil?
|
13
|
+
process_stderr << line
|
11
14
|
fatal_time = elapsed_time > timeout
|
12
15
|
raise 'puma server has taken too long to start' if fatal_time
|
13
16
|
end
|
@@ -13,6 +13,8 @@ module Excon
|
|
13
13
|
bind_str = "#{host}:#{bind_uri.port}"
|
14
14
|
end
|
15
15
|
args = [
|
16
|
+
RbConfig.ruby,
|
17
|
+
'-S',
|
16
18
|
'unicorn',
|
17
19
|
'--no-default-middleware',
|
18
20
|
'-l',
|
@@ -20,9 +22,12 @@ module Excon
|
|
20
22
|
app_str
|
21
23
|
]
|
22
24
|
open_process(*args)
|
25
|
+
process_stderr = ''
|
23
26
|
line = ''
|
24
27
|
until line =~ /worker\=0 ready/
|
25
28
|
line = error.gets
|
29
|
+
raise process_stderr if line.nil?
|
30
|
+
process_stderr << line
|
26
31
|
fatal_time = elapsed_time > timeout
|
27
32
|
raise 'unicorn server has taken too long to start' if fatal_time
|
28
33
|
end
|
@@ -7,10 +7,13 @@ module Excon
|
|
7
7
|
bind_uri = URI.parse(bind_uri) unless bind_uri.is_a? URI::Generic
|
8
8
|
host = bind_uri.host.gsub(/[\[\]]/, '')
|
9
9
|
port = bind_uri.port.to_s
|
10
|
-
open_process('rackup', '-s', 'webrick', '--host', host, '--port', port, app_str)
|
10
|
+
open_process(RbConfig.ruby, '-S', 'rackup', '-s', 'webrick', '--host', host, '--port', port, app_str)
|
11
|
+
process_stderr = ""
|
11
12
|
line = ''
|
12
13
|
until line =~ /HTTPServer#start/
|
13
14
|
line = error.gets
|
15
|
+
raise process_stderr if line.nil?
|
16
|
+
process_stderr << line
|
14
17
|
fatal_time = elapsed_time > timeout
|
15
18
|
raise 'webrick server has taken too long to start' if fatal_time
|
16
19
|
end
|
data/lib/excon/utils.rb
CHANGED
@@ -131,7 +131,8 @@ module Excon
|
|
131
131
|
end
|
132
132
|
[values].flatten.each do |value|
|
133
133
|
if value.to_s.match(/[\r\n]/)
|
134
|
-
|
134
|
+
# Don't include the potentially sensitive header value (i.e. authorization token) in the message
|
135
|
+
raise Excon::Errors::InvalidHeaderValue.new(key.to_s + ' header value contains forbidden "\r" or "\n"')
|
135
136
|
end
|
136
137
|
headers_str << key.to_s << ': ' << value.to_s << CR_NL
|
137
138
|
end
|
data/lib/excon/version.rb
CHANGED
data/lib/excon.rb
CHANGED
@@ -198,8 +198,13 @@ module Excon
|
|
198
198
|
headers_match = !stub.has_key?(:headers) || stub[:headers].keys.all? do |key|
|
199
199
|
case value = stub[:headers][key]
|
200
200
|
when Regexp
|
201
|
-
|
202
|
-
|
201
|
+
case request_params[:headers][key]
|
202
|
+
when String
|
203
|
+
if (match = value.match(request_params[:headers][key]))
|
204
|
+
captures[:headers][key] = match.captures
|
205
|
+
end
|
206
|
+
when Regexp # for unstub on regex params
|
207
|
+
match = (value == request_params[:headers][key])
|
203
208
|
end
|
204
209
|
match
|
205
210
|
else
|
@@ -209,8 +214,13 @@ module Excon
|
|
209
214
|
non_headers_match = (stub.keys - [:headers]).all? do |key|
|
210
215
|
case value = stub[key]
|
211
216
|
when Regexp
|
212
|
-
|
213
|
-
|
217
|
+
case request_params[key]
|
218
|
+
when String
|
219
|
+
if (match = value.match(request_params[key]))
|
220
|
+
captures[key] = match.captures
|
221
|
+
end
|
222
|
+
when Regexp # for unstub on regex params
|
223
|
+
match = (value == request_params[key])
|
214
224
|
end
|
215
225
|
match
|
216
226
|
else
|
metadata
CHANGED
@@ -1,16 +1,16 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: excon
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.99.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- dpiddy (Dan Peterson)
|
8
8
|
- geemus (Wesley Beary)
|
9
9
|
- nextmat (Matt Sanders)
|
10
|
-
autorequire:
|
10
|
+
autorequire:
|
11
11
|
bindir: bin
|
12
12
|
cert_chain: []
|
13
|
-
date:
|
13
|
+
date: 2023-02-03 00:00:00.000000000 Z
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
16
16
|
name: rspec
|
@@ -96,20 +96,6 @@ dependencies:
|
|
96
96
|
- - ">="
|
97
97
|
- !ruby/object:Gem::Version
|
98
98
|
version: '0'
|
99
|
-
- !ruby/object:Gem::Dependency
|
100
|
-
name: rdoc
|
101
|
-
requirement: !ruby/object:Gem::Requirement
|
102
|
-
requirements:
|
103
|
-
- - ">="
|
104
|
-
- !ruby/object:Gem::Version
|
105
|
-
version: '0'
|
106
|
-
type: :development
|
107
|
-
prerelease: false
|
108
|
-
version_requirements: !ruby/object:Gem::Requirement
|
109
|
-
requirements:
|
110
|
-
- - ">="
|
111
|
-
- !ruby/object:Gem::Version
|
112
|
-
version: '0'
|
113
99
|
- !ruby/object:Gem::Dependency
|
114
100
|
name: shindo
|
115
101
|
requirement: !ruby/object:Gem::Requirement
|
@@ -249,7 +235,7 @@ metadata:
|
|
249
235
|
documentation_uri: https://github.com/excon/excon/blob/master/README.md
|
250
236
|
source_code_uri: https://github.com/excon/excon
|
251
237
|
wiki_uri: https://github.com/excon/excon/wiki
|
252
|
-
post_install_message:
|
238
|
+
post_install_message:
|
253
239
|
rdoc_options:
|
254
240
|
- "--charset=UTF-8"
|
255
241
|
require_paths:
|
@@ -265,8 +251,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
265
251
|
- !ruby/object:Gem::Version
|
266
252
|
version: '0'
|
267
253
|
requirements: []
|
268
|
-
rubygems_version: 3.
|
269
|
-
signing_key:
|
254
|
+
rubygems_version: 3.4.1
|
255
|
+
signing_key:
|
270
256
|
specification_version: 4
|
271
257
|
summary: speed, persistence, http(s)
|
272
258
|
test_files: []
|