excon 0.59.0 → 0.60.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of excon might be problematic. Click here for more details.

checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 065cf41c3e9a068ab732dd8a27ba83bf5764c314
4
- data.tar.gz: 7349548aa287fbbfdcecce8143c242698121d7e3
3
+ metadata.gz: d53fc3b2405c940418dfb4d8a4f268b11451a1e6
4
+ data.tar.gz: 7275cf523885f25c3bf8eabbe8baca5d05cffd89
5
5
  SHA512:
6
- metadata.gz: 96dd3aba549eb66d4998ecd7cc8cb46240c3c0a84ae97bbf1a11373081747692ad3c693e2e0171843db78f52fe69bd74e92a0b571a16cf8c4401f98977394b03
7
- data.tar.gz: 321c041190ded594a6e399dcb3ec273987c6551e1591b653920a763fa989b7323b3bb0158b50d2e29768f2285417d9425e03c9f988526cef998ae44c41996afe
6
+ metadata.gz: 6eb4a446aeb95dcb4674837fea2651a06547c584b943e8e29d7e0b3093b728f6226084fa8d8d6a3bfe1a3c664ace754a075cdac863a0f7aab8100ca0092c0a78
7
+ data.tar.gz: 6867048ad67822bf11426e2cd9fb90060c11f8849e8d685a7df660a2064d6382be0cdc14916f1cd1820c923a8cde2bb41d6a09363025bdeacc5c367fdb5c8d35
@@ -0,0 +1,5 @@
1
+ README.rdoc
2
+ lib/**/*.rb
3
+ bin/*
4
+ features/**/*.feature
5
+ LICENSE
@@ -0,0 +1,13 @@
1
+ *.rbc
2
+ *.sw?
3
+ .bundle
4
+ .DS_Store
5
+ .yardoc
6
+ .rvmrc
7
+ .ruby-version
8
+ .ruby-gemset
9
+ coverage
10
+ doc
11
+ rdoc
12
+ pkg
13
+ Gemfile.lock
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --color
2
+ --require spec_helper
3
+ --format d
@@ -0,0 +1,28 @@
1
+ before_install:
2
+ - gem install bundler
3
+ language: ruby
4
+ matrix:
5
+ allow_failures:
6
+ - rvm: 1.8.7
7
+ - rvm: 1.9.2
8
+ - rvm: jruby
9
+ - rvm: rbx-3.2
10
+ - rvm: ree
11
+ fast_finish: true
12
+ rvm:
13
+ - 1.8.7
14
+ - 1.9.2
15
+ - 1.9.3
16
+ - 2.0
17
+ - 2.1
18
+ - 2.2
19
+ - 2.3.3
20
+ - 2.4.1
21
+ - jruby
22
+ - rbx-3.2
23
+ - ree
24
+ script:
25
+ - "bundle exec shindont"
26
+ - "bundle exec rake spec[progress]"
27
+
28
+ sudo: false
data/README.md CHANGED
@@ -215,6 +215,13 @@ connection.requests([{:method => :get}, {:method => :get}])
215
215
  By default, each call to `requests` will use a separate persistent socket connection. To make multiple `requests` calls
216
216
  using a single persistent connection, set `:persistent => true` when establishing the connection.
217
217
 
218
+ For large numbers of simultaneous requests please consider using the `batch_requests` method. This will automatically slice up the requests into batches based on the file descriptor limit of your operating system. The results are the same as the `requests` method, but using this method can help prevent timeout errors.
219
+
220
+ ```ruby
221
+ large_array_of_requests = [{:method => :get, :path => 'some_path'}, { ... }] # Hundreds of items
222
+ connection.batch_requests(large_array_of_requests)
223
+ ```
224
+
218
225
  ## Streaming Responses
219
226
 
220
227
  You can stream responses by passing a block that will receive each chunk.
data/Rakefile CHANGED
@@ -1,148 +1,18 @@
1
- require 'rubygems'
2
- require 'rake'
3
- require 'date'
4
- include Rake::DSL
5
- #############################################################################
6
- #
7
- # Helper functions
8
- #
9
- #############################################################################
10
-
11
- def name
12
- @name ||= Dir['*.gemspec'].first.split('.').first
13
- end
14
-
15
- def version
16
- line = File.read("lib/#{name}/constants.rb")[/^\s*VERSION\s*=\s*.*/]
17
- line.match(/.*VERSION\s*=\s*['"](.*)['"]/)[1]
18
- end
19
-
20
- def date
21
- Date.today.to_s
22
- end
23
-
24
- def rubyforge_project
25
- name
26
- end
27
-
28
- def gemspec_file
29
- "#{name}.gemspec"
30
- end
31
-
32
- def gem_file
33
- "#{name}-#{version}.gem"
34
- end
35
-
36
- def replace_header(head, header_name)
37
- head.sub!(/(\.#{header_name}\s*= ').*'/) { "#{$1}#{send(header_name)}'"}
38
- end
39
-
40
- #############################################################################
41
- #
42
- # Standard tasks
43
- #
44
- #############################################################################
45
-
46
1
  require 'shindo/rake'
47
- require "rspec/core/rake_task"
48
-
49
- RSpec::Core::RakeTask.new(:spec, :format) do |t, args|
50
- format = args[:format] || 'doc'
51
- t.rspec_opts = ["-c", "-f #{format}", "-r ./spec/spec_helper.rb"]
52
- t.pattern = 'spec/**/*_spec.rb'
53
- end
54
-
55
-
56
- Shindo::Rake.new
57
-
58
- task :default => [ :tests, :test]
59
- task :test => :spec
60
-
2
+ require 'rspec/core/rake_task'
3
+ require 'bundler/gem_tasks'
61
4
  require 'rdoc/task'
62
- Rake::RDocTask.new do |rdoc|
63
- rdoc.rdoc_dir = 'rdoc'
64
- rdoc.title = "#{name} #{version}"
65
- rdoc.rdoc_files.include('README*')
66
- rdoc.rdoc_files.include('lib/**/*.rb')
67
- end
68
5
 
69
- desc "Open an irb session preloaded with this library"
70
- task :console do
71
- sh "irb -rubygems -r ./lib/#{name}.rb"
72
- end
73
-
74
- #############################################################################
75
- #
76
- # Custom tasks (add your own tasks here)
77
- #
78
- #############################################################################
79
-
80
-
81
-
82
- #############################################################################
83
- #
84
- # Packaging tasks
85
- #
86
- #############################################################################
87
-
88
- task :release => [:update_certs, :build] do
89
- unless `git branch` =~ /^\* master$/
90
- puts "You must be on the master branch to release!"
91
- exit!
92
- end
93
- sh "gem install pkg/#{name}-#{version}.gem"
94
- sh "git commit --allow-empty -a -m 'Release #{version}'"
95
- sh "git tag v#{version}"
96
- sh "git push origin master"
97
- sh "git push origin v#{version}"
98
- sh "gem push pkg/#{name}-#{version}.gem"
99
- end
100
-
101
- task :build => :gemspec do
102
- sh "mkdir -p pkg"
103
- sh "gem build #{gemspec_file}"
104
- sh "mv #{gem_file} pkg"
105
- end
106
-
107
- task :gemspec => :validate do
108
- # read spec file and split out manifest section
109
- spec = File.read(gemspec_file)
110
- head, manifest, tail = spec.split(" # = MANIFEST =\n")
111
-
112
- # replace name version and date
113
- replace_header(head, :name)
114
- replace_header(head, :version)
115
- replace_header(head, :date)
116
- #comment this out if your rubyforge_project has a different name
117
- replace_header(head, :rubyforge_project)
118
-
119
- # determine file list from git ls-files
120
- files = `git ls-files`.
121
- split("\n").
122
- sort.
123
- reject { |file| file =~ /^\./ }.
124
- reject { |file| file =~ /^(rdoc|pkg)/ }.
125
- map { |file| " #{file}" }.
126
- join("\n")
6
+ Shindo::Rake.new
127
7
 
128
- # piece file back together and write
129
- manifest = " s.files = %w[\n#{files}\n ]\n"
130
- spec = [head, manifest, tail].join(" # = MANIFEST =\n")
131
- File.open(gemspec_file, 'w') { |io| io.write(spec) }
132
- puts "Updated #{gemspec_file}"
8
+ RSpec::Core::RakeTask.new(:spec, :format) do |t, args|
9
+ format = args[:format] || 'doc'
10
+ t.rspec_opts = ["-c", "-f #{format}", "-r ./spec/spec_helper.rb"]
11
+ t.pattern = 'spec/**/*_spec.rb'
133
12
  end
134
13
 
135
- task :validate do
136
- libfiles = Dir['lib/*'] - ["lib/#{name}.rb", "lib/#{name}"]
137
- unless libfiles.empty?
138
- puts "Directory `lib` should only contain a `#{name}.rb` file and `#{name}` dir."
139
- exit!
140
- end
141
- unless Dir['VERSION*'].empty?
142
- puts "A `VERSION` file at root level violates Gem best practices."
143
- exit!
144
- end
145
- end
14
+ task :default => [:tests, :test]
15
+ task :test => :spec
146
16
 
147
17
  desc "update bundled certs"
148
18
  task :update_certs do
@@ -158,6 +28,10 @@ task :update_certs do
158
28
  sh "openssl req -subj '/CN=127.0.0.1/O=excon' -new -newkey rsa:2048 -sha256 -days 365 -nodes -x509 -keyout tests/data/127.0.0.1.cert.key -out tests/data/127.0.0.1.cert.crt"
159
29
  end
160
30
 
31
+ desc "Open an irb session preloaded with this library"
32
+ task :console do
33
+ sh "irb -rubygems -r ./lib/#{name}.rb"
34
+ end
161
35
 
162
36
  desc "check ssl settings"
163
37
  task :hows_my_ssl do
@@ -1,3 +1,12 @@
1
+ 0.60.0 2017-12-15
2
+ =================
3
+
4
+ add requests_in_batches
5
+ cleanup rakefile, gemfile, etc
6
+ add logger/logger= and logging instrumentor
7
+ rewind response_block in idempotent instrumentor
8
+
9
+
1
10
  0.59.0 2017-09-05
2
11
  =================
3
12
 
@@ -1,59 +1,19 @@
1
- ## This is the rakegem gemspec template. Make sure you read and understand
2
- ## all of the comments. Some sections require modification, and others can
3
- ## be deleted if you don't need them. Once you understand the contents of
4
- ## this file, feel free to delete any comments that begin with two hash marks.
5
- ## You can find comprehensive Gem::Specification documentation, at
6
- ## http://docs.rubygems.org/read/chapter/20
7
- Gem::Specification.new do |s|
8
- s.specification_version = 2 if s.respond_to? :specification_version=
9
- s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
10
- s.rubygems_version = '1.3.5'
11
-
12
- ## Leave these as is they will be modified for you by the rake gemspec task.
13
- ## If your rubyforge_project name is different, then edit it and comment out
14
- ## the sub! line in the Rakefile
15
- s.name = 'excon'
16
- s.version = '0.59.0'
17
- s.date = '2017-09-05'
18
- s.rubyforge_project = 'excon'
19
-
20
- ## Make sure your summary is short. The description may be as long
21
- ## as you like.
22
- s.summary = "speed, persistence, http(s)"
23
- s.description = "EXtended http(s) CONnections"
24
-
25
- ## List the primary authors. If there are a bunch of authors, it's probably
26
- ## better to set the email to an email list or something. If you don't have
27
- ## a custom homepage, consider using your GitHub URL or the like.
28
- s.authors = ["dpiddy (Dan Peterson)", "geemus (Wesley Beary)", "nextmat (Matt Sanders)"]
29
- s.email = 'geemus@gmail.com'
30
- s.homepage = 'https://github.com/excon/excon'
31
- s.license = 'MIT'
32
-
33
- ## This gets added to the $LOAD_PATH so that 'lib/NAME.rb' can be required as
34
- ## require 'NAME.rb' or'/lib/NAME/file.rb' can be as require 'NAME/file.rb'
35
- s.require_paths = %w[lib]
1
+ require File.join(File.dirname(__FILE__), 'lib', 'excon', 'version')
36
2
 
37
- ## This sections is only necessary if you have C extensions.
38
- # s.require_paths << 'ext'
39
- # s.extensions = %w[ext/extconf.rb]
40
-
41
- ## If your gem includes any executables, list them here.
42
- # s.executables = ["name"]
43
- # s.default_executable = 'name'
44
-
45
- ## Specify any RDoc options here. You'll want to add your README and
46
- ## LICENSE files to the extra_rdoc_files list.
47
- s.rdoc_options = ["--charset=UTF-8"]
48
- s.extra_rdoc_files = %w[README.md]
49
-
50
- ## List your runtime dependencies here. Runtime dependencies are those
51
- ## that are needed for an end user to actually USE your code.
52
- # s.add_dependency('DEPNAME', [">= 1.1.0", "< 2.0.0"])
3
+ Gem::Specification.new do |s|
4
+ s.name = 'excon'
5
+ s.version = Excon::VERSION
6
+ s.summary = "speed, persistence, http(s)"
7
+ s.description = "EXtended http(s) CONnections"
8
+ s.authors = ["dpiddy (Dan Peterson)", "geemus (Wesley Beary)", "nextmat (Matt Sanders)"]
9
+ s.email = 'geemus@gmail.com'
10
+ s.homepage = 'https://github.com/excon/excon'
11
+ s.license = 'MIT'
12
+ s.rdoc_options = ["--charset=UTF-8"]
13
+ s.extra_rdoc_files = %w[README.md CONTRIBUTORS.md CONTRIBUTING.md]
14
+ s.files = `git ls-files -z`.split("\x0")
15
+ s.test_files = s.files.select { |path| path =~ /^[spec|tests]\/.*_[spec|tests]\.rb/ }
53
16
 
54
- ## List your development dependencies here. Development dependencies are
55
- ## those that are only needed during development
56
- # s.add_development_dependency('DEVDEPNAME', [">= 1.1.0", "< 2.0.0"])
57
17
  s.add_development_dependency('rspec', '>= 3.5.0')
58
18
  s.add_development_dependency('activesupport')
59
19
  s.add_development_dependency('delorean')
@@ -65,138 +25,5 @@ Gem::Specification.new do |s|
65
25
  s.add_development_dependency('sinatra')
66
26
  s.add_development_dependency('sinatra-contrib')
67
27
  s.add_development_dependency('json', '>= 1.8.5')
68
- if RUBY_VERSION.to_f >= 1.9
69
- s.add_development_dependency 'puma'
70
- end
71
- ## Leave this section as-is. It will be automatically generated from the
72
- ## contents of your Git repository via the gemspec task. DO NOT REMOVE
73
- ## THE MANIFEST COMMENTS, they are used as delimiters by the task.
74
- # = MANIFEST =
75
- s.files = %w[
76
- CONTRIBUTING.md
77
- CONTRIBUTORS.md
78
- Gemfile
79
- Gemfile.lock
80
- LICENSE.md
81
- README.md
82
- Rakefile
83
- benchmarks/class_vs_lambda.rb
84
- benchmarks/concat_vs_insert.rb
85
- benchmarks/concat_vs_interpolate.rb
86
- benchmarks/cr_lf.rb
87
- benchmarks/downcase-eq-eq_vs_casecmp.rb
88
- benchmarks/excon.rb
89
- benchmarks/excon_vs.rb
90
- benchmarks/for_vs_array_each.rb
91
- benchmarks/for_vs_hash_each.rb
92
- benchmarks/has_key-vs-lookup.rb
93
- benchmarks/headers_case_sensitivity.rb
94
- benchmarks/headers_split_vs_match.rb
95
- benchmarks/implicit_block-vs-explicit_block.rb
96
- benchmarks/merging.rb
97
- benchmarks/single_vs_double_quotes.rb
98
- benchmarks/string_ranged_index.rb
99
- benchmarks/strip_newline.rb
100
- benchmarks/vs_stdlib.rb
101
- changelog.txt
102
- data/cacert.pem
103
- excon.gemspec
104
- lib/excon.rb
105
- lib/excon/connection.rb
106
- lib/excon/constants.rb
107
- lib/excon/error.rb
108
- lib/excon/extensions/uri.rb
109
- lib/excon/headers.rb
110
- lib/excon/middlewares/base.rb
111
- lib/excon/middlewares/capture_cookies.rb
112
- lib/excon/middlewares/decompress.rb
113
- lib/excon/middlewares/escape_path.rb
114
- lib/excon/middlewares/expects.rb
115
- lib/excon/middlewares/idempotent.rb
116
- lib/excon/middlewares/instrumentor.rb
117
- lib/excon/middlewares/mock.rb
118
- lib/excon/middlewares/redirect_follower.rb
119
- lib/excon/middlewares/response_parser.rb
120
- lib/excon/pretty_printer.rb
121
- lib/excon/response.rb
122
- lib/excon/socket.rb
123
- lib/excon/ssl_socket.rb
124
- lib/excon/standard_instrumentor.rb
125
- lib/excon/test/plugin/server/exec.rb
126
- lib/excon/test/plugin/server/puma.rb
127
- lib/excon/test/plugin/server/unicorn.rb
128
- lib/excon/test/plugin/server/webrick.rb
129
- lib/excon/test/server.rb
130
- lib/excon/unix_socket.rb
131
- lib/excon/utils.rb
132
- spec/excon/error_spec.rb
133
- spec/excon/test/server_spec.rb
134
- spec/excon_spec.rb
135
- spec/helpers/file_path_helpers.rb
136
- spec/requests/basic_spec.rb
137
- spec/requests/eof_requests_spec.rb
138
- spec/requests/unix_socket_spec.rb
139
- spec/spec_helper.rb
140
- spec/support/shared_contexts/test_server_context.rb
141
- spec/support/shared_examples/shared_example_for_clients.rb
142
- spec/support/shared_examples/shared_example_for_streaming_clients.rb
143
- spec/support/shared_examples/shared_example_for_test_servers.rb
144
- tests/authorization_header_tests.rb
145
- tests/bad_tests.rb
146
- tests/basic_tests.rb
147
- tests/complete_responses.rb
148
- tests/data/127.0.0.1.cert.crt
149
- tests/data/127.0.0.1.cert.key
150
- tests/data/excon.cert.crt
151
- tests/data/excon.cert.key
152
- tests/data/xs
153
- tests/error_tests.rb
154
- tests/header_tests.rb
155
- tests/middlewares/canned_response_tests.rb
156
- tests/middlewares/capture_cookies_tests.rb
157
- tests/middlewares/decompress_tests.rb
158
- tests/middlewares/escape_path_tests.rb
159
- tests/middlewares/idempotent_tests.rb
160
- tests/middlewares/instrumentation_tests.rb
161
- tests/middlewares/mock_tests.rb
162
- tests/middlewares/redirect_follower_tests.rb
163
- tests/pipeline_tests.rb
164
- tests/proxy_tests.rb
165
- tests/query_string_tests.rb
166
- tests/rackups/basic.rb
167
- tests/rackups/basic.ru
168
- tests/rackups/basic_auth.ru
169
- tests/rackups/deflater.ru
170
- tests/rackups/proxy.ru
171
- tests/rackups/query_string.ru
172
- tests/rackups/redirecting.ru
173
- tests/rackups/redirecting_with_cookie.ru
174
- tests/rackups/request_headers.ru
175
- tests/rackups/request_methods.ru
176
- tests/rackups/response_header.ru
177
- tests/rackups/ssl.ru
178
- tests/rackups/ssl_mismatched_cn.ru
179
- tests/rackups/ssl_verify_peer.ru
180
- tests/rackups/streaming.ru
181
- tests/rackups/thread_safety.ru
182
- tests/rackups/timeout.ru
183
- tests/rackups/webrick_patch.rb
184
- tests/request_headers_tests.rb
185
- tests/request_method_tests.rb
186
- tests/request_tests.rb
187
- tests/response_tests.rb
188
- tests/servers/bad.rb
189
- tests/servers/eof.rb
190
- tests/servers/error.rb
191
- tests/servers/good.rb
192
- tests/test_helper.rb
193
- tests/thread_safety_tests.rb
194
- tests/timeout_tests.rb
195
- tests/utils_tests.rb
196
- ]
197
- # = MANIFEST =
198
-
199
- ## Test files will be grabbed from the file list. Make sure the path glob
200
- ## matches what you actually use.
201
- s.test_files = s.files.select { |path| path =~ /^[spec|tests]\/.*_[spec|tests]\.rb/ }
28
+ s.add_development_dependency('puma')
202
29
  end
@@ -12,6 +12,8 @@ require 'uri'
12
12
  require 'zlib'
13
13
  require 'stringio'
14
14
 
15
+ require 'excon/version'
16
+
15
17
  require 'excon/extensions/uri'
16
18
 
17
19
  require 'excon/middlewares/base'
@@ -35,7 +37,8 @@ require 'excon/middlewares/capture_cookies'
35
37
  require 'excon/pretty_printer'
36
38
  require 'excon/socket'
37
39
  require 'excon/ssl_socket'
38
- require 'excon/standard_instrumentor'
40
+ require 'excon/instrumentors/standard_instrumentor'
41
+ require 'excon/instrumentors/logging_instrumentor'
39
42
  require 'excon/unix_socket'
40
43
 
41
44
  # Define defaults first so they will be available to other files
@@ -32,6 +32,16 @@ module Excon
32
32
  @data[:proxy] = new_proxy
33
33
  end
34
34
 
35
+ def logger
36
+ if @data[:instrumentor] && @data[:instrumentor].respond_to?(:logger)
37
+ @data[:instrumentor].logger
38
+ end
39
+ end
40
+ def logger=(logger)
41
+ Excon::LoggingInstrumentor.logger = logger
42
+ @data[:instrumentor] = Excon::LoggingInstrumentor
43
+ end
44
+
35
45
  # Initializes a new Connection instance
36
46
  # @param [Hash<Symbol, >] params One or more optional params
37
47
  # @option params [String] :body Default text to be sent over a socket. Only used if :body absent in Connection#request params
@@ -193,7 +203,7 @@ module Excon
193
203
 
194
204
  def response_call(datum)
195
205
  # ensure response_block is yielded to and body is empty from middlewares
196
- if datum.has_key?(:response_block) && !datum[:response][:body].empty?
206
+ if datum.has_key?(:response_block) && !(datum[:response][:body].nil? || datum[:response][:body].empty?)
197
207
  response_body = datum[:response][:body].dup
198
208
  datum[:response][:body] = ''
199
209
  content_length = remaining = response_body.bytesize
@@ -301,6 +311,21 @@ module Excon
301
311
  responses
302
312
  end
303
313
 
314
+ # Sends the supplied requests to the destination host using pipelining in
315
+ # batches of @limit [Numeric] requests. This is your soft file descriptor
316
+ # limit by default, typically 256.
317
+ # @pipeline_params [Array<Hash>] pipeline_params An array of one or more optional params, override defaults set in Connection.new, see #request for details
318
+ def batch_requests(pipeline_params, limit = nil)
319
+ limit ||= Process.respond_to?(:getrlimit) ? Process.getrlimit(:NOFILE).first : 256
320
+ responses = []
321
+
322
+ pipeline_params.each_slice(limit) do |params|
323
+ responses.concat(requests(params))
324
+ end
325
+
326
+ responses
327
+ end
328
+
304
329
  def reset
305
330
  if old_socket = sockets.delete(@socket_key)
306
331
  old_socket.close rescue nil
@@ -1,8 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
  module Excon
3
3
 
4
- VERSION = '0.59.0'
5
-
6
4
  CR_NL = "\r\n"
7
5
 
8
6
  DEFAULT_CA_FILE = File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "data", "cacert.pem"))
@@ -0,0 +1,59 @@
1
+ require 'logger'
2
+
3
+ module Excon
4
+ class LoggingInstrumentor
5
+ # Returns the Logger object for the LoggingInstrumentor. If one doesn't
6
+ # already exist, then one will be created using $stderr as the output
7
+ # stream.
8
+ #
9
+ def self.logger
10
+ @logger ||= Logger.new($stderr)
11
+ end
12
+
13
+ # Sets the logger object for the LoggingInstrumentor.
14
+ #
15
+ def self.logger=(logger)
16
+ @logger = logger
17
+ end
18
+
19
+ def self.instrument(name, params = {}, &block)
20
+ params = params.dup
21
+
22
+ # reduce duplication/noise of output
23
+ params.delete(:connection)
24
+ params.delete(:stack)
25
+
26
+ if params.has_key?(:headers) && params[:headers].has_key?('Authorization')
27
+ params[:headers] = params[:headers].dup
28
+ params[:headers]['Authorization'] = "REDACTED"
29
+ end
30
+
31
+ if params.has_key?(:password)
32
+ params[:password] = "REDACTED"
33
+ end
34
+
35
+ if name.include?('request')
36
+ info = "request: " + params[:scheme] + "://" + File.join(params[:host], params[:path])
37
+
38
+ if params[:query]
39
+ info << "?"
40
+
41
+ if params[:query].is_a?(Hash)
42
+ info << params.to_a.map{ |key,value| "#{key}=#{value}" }.join('&')
43
+ else
44
+ info << params[:query]
45
+ end
46
+ end
47
+ else
48
+ response_type = name.split('.').last
49
+ if params[:body]
50
+ info = "#{response_type}: " + params[:body]
51
+ end
52
+ end
53
+
54
+ self.logger.log(logger.level, info) if info
55
+
56
+ yield if block_given?
57
+ end
58
+ end
59
+ end
@@ -12,6 +12,9 @@ module Excon
12
12
  datum[:idempotent] = false
13
13
  end
14
14
  end
15
+ if datum.has_key?(:response_block) && datum[:response_block].respond_to?(:rewind)
16
+ datum[:response_block].rewind
17
+ end
15
18
  if datum.has_key?(:pipeline)
16
19
  Excon.display_warning("Excon requests can not be :idempotent when pipelining.")
17
20
  datum[:idempotent] = false
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+ module Excon
3
+ VERSION = '0.60.0'
4
+ end
@@ -0,0 +1,133 @@
1
+ require 'shindo'
2
+
3
+ Shindo.tests('Batch Requests') do
4
+ with_server('good') do
5
+ tests('with batch request size 2') do
6
+ returns(%w{ 1 2 1 2 }, 'batch request size 2') do
7
+ connection = Excon.new('http://127.0.0.1:9292')
8
+
9
+ ret = []
10
+ ret << connection.batch_requests([
11
+ {:method => :get, :path => '/echo/request_count'},
12
+ {:method => :get, :path => '/echo/request_count'},
13
+ {:method => :get, :path => '/echo/request_count'},
14
+ {:method => :get, :path => '/echo/request_count'}
15
+ ], 2).map(&:body)
16
+
17
+ ret.flatten
18
+ end
19
+ end
20
+
21
+ tests('peristent with batch request size 2') do
22
+ returns(%w{ 1 2 3 4 }, 'persistent batch request size 2') do
23
+ connection = Excon.new('http://127.0.0.1:9292', :persistent => true)
24
+
25
+ ret = []
26
+ ret << connection.batch_requests([
27
+ {:method => :get, :path => '/echo/request_count'},
28
+ {:method => :get, :path => '/echo/request_count'},
29
+ {:method => :get, :path => '/echo/request_count'},
30
+ {:method => :get, :path => '/echo/request_count'}
31
+ ], 2).map(&:body)
32
+
33
+ ret.flatten
34
+ end
35
+ end
36
+
37
+ tests('with batch request size 3') do
38
+ returns(%w{ 1 2 3 1 }, 'batch request size 3') do
39
+ connection = Excon.new('http://127.0.0.1:9292')
40
+
41
+ ret = []
42
+ ret << connection.batch_requests([
43
+ {:method => :get, :path => '/echo/request_count'},
44
+ {:method => :get, :path => '/echo/request_count'},
45
+ {:method => :get, :path => '/echo/request_count'},
46
+ {:method => :get, :path => '/echo/request_count'}
47
+ ], 3).map(&:body)
48
+
49
+ ret.flatten
50
+ end
51
+ end
52
+
53
+ tests('persistent with batch request size 3') do
54
+ returns(%w{ 1 2 3 4 }, 'persistent batch request size 3') do
55
+ connection = Excon.new('http://127.0.0.1:9292', :persistent => true)
56
+
57
+ ret = []
58
+ ret << connection.batch_requests([
59
+ {:method => :get, :path => '/echo/request_count'},
60
+ {:method => :get, :path => '/echo/request_count'},
61
+ {:method => :get, :path => '/echo/request_count'},
62
+ {:method => :get, :path => '/echo/request_count'}
63
+ ], 3).map(&:body)
64
+
65
+ ret.flatten
66
+ end
67
+ end
68
+
69
+ tests('with batch request size 4') do
70
+ returns(%w{ 1 2 3 4 }, 'batch request size 4') do
71
+ connection = Excon.new('http://127.0.0.1:9292')
72
+
73
+ ret = []
74
+ ret << connection.batch_requests([
75
+ {:method => :get, :path => '/echo/request_count'},
76
+ {:method => :get, :path => '/echo/request_count'},
77
+ {:method => :get, :path => '/echo/request_count'},
78
+ {:method => :get, :path => '/echo/request_count'}
79
+ ], 4).map(&:body)
80
+
81
+ ret.flatten
82
+ end
83
+ end
84
+
85
+ tests('persistent with batch request size 4') do
86
+ returns(%w{ 1 2 3 4 }, 'persistent batch request size 4') do
87
+ connection = Excon.new('http://127.0.0.1:9292', :persistent => true)
88
+
89
+ ret = []
90
+ ret << connection.batch_requests([
91
+ {:method => :get, :path => '/echo/request_count'},
92
+ {:method => :get, :path => '/echo/request_count'},
93
+ {:method => :get, :path => '/echo/request_count'},
94
+ {:method => :get, :path => '/echo/request_count'}
95
+ ], 4).map(&:body)
96
+
97
+ ret.flatten
98
+ end
99
+ end
100
+
101
+ tests('with batch request size 8') do
102
+ returns(%w{ 1 2 3 4 }, 'batch request size 8') do
103
+ connection = Excon.new('http://127.0.0.1:9292')
104
+
105
+ ret = []
106
+ ret << connection.batch_requests([
107
+ {:method => :get, :path => '/echo/request_count'},
108
+ {:method => :get, :path => '/echo/request_count'},
109
+ {:method => :get, :path => '/echo/request_count'},
110
+ {:method => :get, :path => '/echo/request_count'}
111
+ ], 8).map(&:body)
112
+
113
+ ret.flatten
114
+ end
115
+ end
116
+
117
+ tests('persistent with batch request size 8') do
118
+ returns(%w{ 1 2 3 4 }, 'persistent batch request size 8') do
119
+ connection = Excon.new('http://127.0.0.1:9292', :persistent => true)
120
+
121
+ ret = []
122
+ ret << connection.batch_requests([
123
+ {:method => :get, :path => '/echo/request_count'},
124
+ {:method => :get, :path => '/echo/request_count'},
125
+ {:method => :get, :path => '/echo/request_count'},
126
+ {:method => :get, :path => '/echo/request_count'}
127
+ ], 8).map(&:body)
128
+
129
+ ret.flatten
130
+ end
131
+ end
132
+ end
133
+ end
@@ -161,4 +161,46 @@ Shindo.tests('Excon request idempotencey') do
161
161
  response.status
162
162
  end
163
163
 
164
+ class Block
165
+ attr_reader :rewound
166
+ def initialize
167
+ @rewound = false
168
+ end
169
+ def call(_)
170
+ end
171
+ def rewind
172
+ @rewound = true
173
+ end
174
+ end
175
+
176
+ tests("request_block rewound").returns(true) do
177
+ run_count = 0
178
+ Excon.stub({:method => :get}) { |params|
179
+ run_count += 1
180
+ if run_count <= 1 # First call fails.
181
+ raise Excon::Errors::SocketError.new(Exception.new "Mock Error")
182
+ else
183
+ {:body => params[:body], :headers => params[:headers], :status => 200}
184
+ end
185
+ }
186
+ request_block = Block.new
187
+ @connection.request(:method => :get, :idempotent => true, :path => '/some-path', :request_block => request_block, :retry_limit => 2, :retry_interval => 0.1)
188
+ request_block.rewound
189
+ end
190
+
191
+ tests("response_block rewound").returns(true) do
192
+ run_count = 0
193
+ Excon.stub({:method => :get}) { |params|
194
+ run_count += 1
195
+ if run_count <= 1 # First call fails.
196
+ raise Excon::Errors::SocketError.new(Exception.new "Mock Error")
197
+ else
198
+ {:body => params[:body], :headers => params[:headers], :status => 200}
199
+ end
200
+ }
201
+ response_block = Block.new
202
+ @connection.request(:method => :get, :idempotent => true, :path => '/some-path', :response_block => response_block, :retry_limit => 2, :retry_interval => 0.1)
203
+ response_block.rewound
204
+ end
205
+
164
206
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: excon
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.59.0
4
+ version: 0.60.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - dpiddy (Dan Peterson)
@@ -10,7 +10,7 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2017-09-05 00:00:00.000000000 Z
13
+ date: 2017-12-15 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: rspec
@@ -186,11 +186,16 @@ executables: []
186
186
  extensions: []
187
187
  extra_rdoc_files:
188
188
  - README.md
189
+ - CONTRIBUTORS.md
190
+ - CONTRIBUTING.md
189
191
  files:
192
+ - ".document"
193
+ - ".gitignore"
194
+ - ".rspec"
195
+ - ".travis.yml"
190
196
  - CONTRIBUTING.md
191
197
  - CONTRIBUTORS.md
192
198
  - Gemfile
193
- - Gemfile.lock
194
199
  - LICENSE.md
195
200
  - README.md
196
201
  - Rakefile
@@ -221,6 +226,8 @@ files:
221
226
  - lib/excon/error.rb
222
227
  - lib/excon/extensions/uri.rb
223
228
  - lib/excon/headers.rb
229
+ - lib/excon/instrumentors/logging_instrumentor.rb
230
+ - lib/excon/instrumentors/standard_instrumentor.rb
224
231
  - lib/excon/middlewares/base.rb
225
232
  - lib/excon/middlewares/capture_cookies.rb
226
233
  - lib/excon/middlewares/decompress.rb
@@ -235,7 +242,6 @@ files:
235
242
  - lib/excon/response.rb
236
243
  - lib/excon/socket.rb
237
244
  - lib/excon/ssl_socket.rb
238
- - lib/excon/standard_instrumentor.rb
239
245
  - lib/excon/test/plugin/server/exec.rb
240
246
  - lib/excon/test/plugin/server/puma.rb
241
247
  - lib/excon/test/plugin/server/unicorn.rb
@@ -243,6 +249,7 @@ files:
243
249
  - lib/excon/test/server.rb
244
250
  - lib/excon/unix_socket.rb
245
251
  - lib/excon/utils.rb
252
+ - lib/excon/version.rb
246
253
  - spec/excon/error_spec.rb
247
254
  - spec/excon/test/server_spec.rb
248
255
  - spec/excon_spec.rb
@@ -258,6 +265,7 @@ files:
258
265
  - tests/authorization_header_tests.rb
259
266
  - tests/bad_tests.rb
260
267
  - tests/basic_tests.rb
268
+ - tests/batch_requests.rb
261
269
  - tests/complete_responses.rb
262
270
  - tests/data/127.0.0.1.cert.crt
263
271
  - tests/data/127.0.0.1.cert.key
@@ -327,9 +335,9 @@ required_rubygems_version: !ruby/object:Gem::Requirement
327
335
  - !ruby/object:Gem::Version
328
336
  version: '0'
329
337
  requirements: []
330
- rubyforge_project: excon
338
+ rubyforge_project:
331
339
  rubygems_version: 2.6.13
332
340
  signing_key:
333
- specification_version: 2
341
+ specification_version: 4
334
342
  summary: speed, persistence, http(s)
335
343
  test_files: []
@@ -1,301 +0,0 @@
1
- PATH
2
- remote: .
3
- specs:
4
- excon (0.59.0)
5
-
6
- GEM
7
- remote: https://rubygems.org/
8
- specs:
9
- activesupport (3.2.6)
10
- i18n (~> 0.6)
11
- multi_json (~> 1.0)
12
- backports (3.6.4)
13
- chronic (0.6.7)
14
- delorean (2.0.0)
15
- chronic
16
- diff-lcs (1.2.5)
17
- eventmachine (1.0.4)
18
- eventmachine (1.0.4-java)
19
- ffi2-generators (0.1.1)
20
- formatador (0.2.3)
21
- i18n (0.6.0)
22
- jruby-openssl (0.9.17-java)
23
- json (1.8.6)
24
- json (1.8.6-java)
25
- kgio (2.9.2)
26
- minitest (4.7.5)
27
- multi_json (1.3.6)
28
- open4 (1.3.0)
29
- puma (3.6.0)
30
- puma (3.6.0-java)
31
- rack (1.6.0)
32
- rack-protection (1.2.0)
33
- rack
34
- rack-test (0.6.3)
35
- rack (>= 1.0)
36
- raindrops (0.13.0)
37
- rake (0.9.2.2)
38
- rdoc (3.12)
39
- json (~> 1.4)
40
- rspec (3.5.0)
41
- rspec-core (~> 3.5.0)
42
- rspec-expectations (~> 3.5.0)
43
- rspec-mocks (~> 3.5.0)
44
- rspec-core (3.5.0)
45
- rspec-support (~> 3.5.0)
46
- rspec-expectations (3.5.0)
47
- diff-lcs (>= 1.2.0, < 2.0)
48
- rspec-support (~> 3.5.0)
49
- rspec-mocks (3.5.0)
50
- diff-lcs (>= 1.2.0, < 2.0)
51
- rspec-support (~> 3.5.0)
52
- rspec-support (3.5.0)
53
- rubysl (2.0.14)
54
- rubysl-abbrev (~> 2.0)
55
- rubysl-base64 (~> 2.0)
56
- rubysl-benchmark (~> 2.0)
57
- rubysl-bigdecimal (~> 2.0)
58
- rubysl-cgi (~> 2.0)
59
- rubysl-cgi-session (~> 2.0)
60
- rubysl-cmath (~> 2.0)
61
- rubysl-complex (~> 2.0)
62
- rubysl-continuation (~> 2.0)
63
- rubysl-coverage (~> 2.0)
64
- rubysl-csv (~> 2.0)
65
- rubysl-curses (~> 2.0)
66
- rubysl-date (~> 2.0)
67
- rubysl-delegate (~> 2.0)
68
- rubysl-digest (~> 2.0)
69
- rubysl-drb (~> 2.0)
70
- rubysl-e2mmap (~> 2.0)
71
- rubysl-english (~> 2.0)
72
- rubysl-enumerator (~> 2.0)
73
- rubysl-erb (~> 2.0)
74
- rubysl-etc (~> 2.0)
75
- rubysl-expect (~> 2.0)
76
- rubysl-fcntl (~> 2.0)
77
- rubysl-fiber (~> 2.0)
78
- rubysl-fileutils (~> 2.0)
79
- rubysl-find (~> 2.0)
80
- rubysl-forwardable (~> 2.0)
81
- rubysl-getoptlong (~> 2.0)
82
- rubysl-gserver (~> 2.0)
83
- rubysl-io-console (~> 2.0)
84
- rubysl-io-nonblock (~> 2.0)
85
- rubysl-io-wait (~> 2.0)
86
- rubysl-ipaddr (~> 2.0)
87
- rubysl-irb (~> 2.0)
88
- rubysl-logger (~> 2.0)
89
- rubysl-mathn (~> 2.0)
90
- rubysl-matrix (~> 2.0)
91
- rubysl-mkmf (~> 2.0)
92
- rubysl-monitor (~> 2.0)
93
- rubysl-mutex_m (~> 2.0)
94
- rubysl-net-ftp (~> 2.0)
95
- rubysl-net-http (~> 2.0)
96
- rubysl-net-imap (~> 2.0)
97
- rubysl-net-pop (~> 2.0)
98
- rubysl-net-protocol (~> 2.0)
99
- rubysl-net-smtp (~> 2.0)
100
- rubysl-net-telnet (~> 2.0)
101
- rubysl-nkf (~> 2.0)
102
- rubysl-observer (~> 2.0)
103
- rubysl-open-uri (~> 2.0)
104
- rubysl-open3 (~> 2.0)
105
- rubysl-openssl (~> 2.0)
106
- rubysl-optparse (~> 2.0)
107
- rubysl-ostruct (~> 2.0)
108
- rubysl-pathname (~> 2.0)
109
- rubysl-prettyprint (~> 2.0)
110
- rubysl-prime (~> 2.0)
111
- rubysl-profile (~> 2.0)
112
- rubysl-profiler (~> 2.0)
113
- rubysl-pstore (~> 2.0)
114
- rubysl-pty (~> 2.0)
115
- rubysl-rational (~> 2.0)
116
- rubysl-readline (~> 2.0)
117
- rubysl-resolv (~> 2.0)
118
- rubysl-rexml (~> 2.0)
119
- rubysl-rinda (~> 2.0)
120
- rubysl-rss (~> 2.0)
121
- rubysl-scanf (~> 2.0)
122
- rubysl-securerandom (~> 2.0)
123
- rubysl-set (~> 2.0)
124
- rubysl-shellwords (~> 2.0)
125
- rubysl-singleton (~> 2.0)
126
- rubysl-socket (~> 2.0)
127
- rubysl-stringio (~> 2.0)
128
- rubysl-strscan (~> 2.0)
129
- rubysl-sync (~> 2.0)
130
- rubysl-syslog (~> 2.0)
131
- rubysl-tempfile (~> 2.0)
132
- rubysl-test-unit (~> 2.0)
133
- rubysl-thread (~> 2.0)
134
- rubysl-thwait (~> 2.0)
135
- rubysl-time (~> 2.0)
136
- rubysl-timeout (~> 2.0)
137
- rubysl-tmpdir (~> 2.0)
138
- rubysl-tsort (~> 2.0)
139
- rubysl-un (~> 2.0)
140
- rubysl-uri (~> 2.0)
141
- rubysl-weakref (~> 2.0)
142
- rubysl-webrick (~> 2.0)
143
- rubysl-xmlrpc (~> 2.0)
144
- rubysl-yaml (~> 2.0)
145
- rubysl-zlib (~> 2.0)
146
- rubysl-abbrev (2.0.4)
147
- rubysl-base64 (2.0.0)
148
- rubysl-benchmark (2.0.1)
149
- rubysl-bigdecimal (2.0.2)
150
- rubysl-cgi (2.0.1)
151
- rubysl-cgi-session (2.0.1)
152
- rubysl-cmath (2.0.0)
153
- rubysl-complex (2.0.0)
154
- rubysl-continuation (2.0.0)
155
- rubysl-coverage (2.0.3)
156
- rubysl-csv (2.0.2)
157
- rubysl-english (~> 2.0)
158
- rubysl-curses (2.0.0)
159
- rubysl-date (2.0.6)
160
- rubysl-delegate (2.0.1)
161
- rubysl-digest (2.0.3)
162
- rubysl-drb (2.0.1)
163
- rubysl-e2mmap (2.0.0)
164
- rubysl-english (2.0.0)
165
- rubysl-enumerator (2.0.0)
166
- rubysl-erb (2.0.1)
167
- rubysl-etc (2.0.3)
168
- ffi2-generators (~> 0.1)
169
- rubysl-expect (2.0.0)
170
- rubysl-fcntl (2.0.4)
171
- ffi2-generators (~> 0.1)
172
- rubysl-fiber (2.0.0)
173
- rubysl-fileutils (2.0.3)
174
- rubysl-find (2.0.1)
175
- rubysl-forwardable (2.0.1)
176
- rubysl-getoptlong (2.0.0)
177
- rubysl-gserver (2.0.0)
178
- rubysl-socket (~> 2.0)
179
- rubysl-thread (~> 2.0)
180
- rubysl-io-console (2.0.0)
181
- rubysl-io-nonblock (2.0.0)
182
- rubysl-io-wait (2.0.0)
183
- rubysl-ipaddr (2.0.0)
184
- rubysl-irb (2.0.4)
185
- rubysl-e2mmap (~> 2.0)
186
- rubysl-mathn (~> 2.0)
187
- rubysl-readline (~> 2.0)
188
- rubysl-thread (~> 2.0)
189
- rubysl-logger (2.0.0)
190
- rubysl-mathn (2.0.0)
191
- rubysl-matrix (2.1.0)
192
- rubysl-e2mmap (~> 2.0)
193
- rubysl-mkmf (2.0.1)
194
- rubysl-fileutils (~> 2.0)
195
- rubysl-shellwords (~> 2.0)
196
- rubysl-monitor (2.0.0)
197
- rubysl-mutex_m (2.0.0)
198
- rubysl-net-ftp (2.0.1)
199
- rubysl-net-http (2.0.4)
200
- rubysl-cgi (~> 2.0)
201
- rubysl-erb (~> 2.0)
202
- rubysl-singleton (~> 2.0)
203
- rubysl-net-imap (2.0.1)
204
- rubysl-net-pop (2.0.1)
205
- rubysl-net-protocol (2.0.1)
206
- rubysl-net-smtp (2.0.1)
207
- rubysl-net-telnet (2.0.0)
208
- rubysl-nkf (2.0.1)
209
- rubysl-observer (2.0.0)
210
- rubysl-open-uri (2.0.0)
211
- rubysl-open3 (2.0.0)
212
- rubysl-openssl (2.0.4)
213
- rubysl-optparse (2.0.1)
214
- rubysl-shellwords (~> 2.0)
215
- rubysl-ostruct (2.0.4)
216
- rubysl-pathname (2.0.0)
217
- rubysl-prettyprint (2.0.2)
218
- rubysl-prime (2.0.0)
219
- rubysl-profile (2.0.0)
220
- rubysl-profiler (2.0.1)
221
- rubysl-pstore (2.0.0)
222
- rubysl-pty (2.0.2)
223
- rubysl-rational (2.0.1)
224
- rubysl-readline (2.0.2)
225
- rubysl-resolv (2.0.0)
226
- rubysl-rexml (2.0.2)
227
- rubysl-rinda (2.0.0)
228
- rubysl-rss (2.0.0)
229
- rubysl-scanf (2.0.0)
230
- rubysl-securerandom (2.0.0)
231
- rubysl-set (2.0.1)
232
- rubysl-shellwords (2.0.0)
233
- rubysl-singleton (2.0.0)
234
- rubysl-socket (2.0.1)
235
- rubysl-stringio (2.0.0)
236
- rubysl-strscan (2.0.0)
237
- rubysl-sync (2.0.0)
238
- rubysl-syslog (2.0.1)
239
- ffi2-generators (~> 0.1)
240
- rubysl-tempfile (2.0.1)
241
- rubysl-test-unit (2.0.1)
242
- minitest (~> 4.7)
243
- rubysl-thread (2.0.2)
244
- rubysl-thwait (2.0.0)
245
- rubysl-time (2.0.3)
246
- rubysl-timeout (2.0.0)
247
- rubysl-tmpdir (2.0.0)
248
- rubysl-tsort (2.0.1)
249
- rubysl-un (2.0.0)
250
- rubysl-fileutils (~> 2.0)
251
- rubysl-optparse (~> 2.0)
252
- rubysl-uri (2.0.0)
253
- rubysl-weakref (2.0.0)
254
- rubysl-webrick (2.0.0)
255
- rubysl-xmlrpc (2.0.0)
256
- rubysl-yaml (2.0.3)
257
- rubysl-zlib (2.0.1)
258
- shindo (0.3.4)
259
- formatador (>= 0.1.1)
260
- sinatra (1.3.2)
261
- rack (~> 1.3, >= 1.3.6)
262
- rack-protection (~> 1.2)
263
- tilt (~> 1.3, >= 1.3.3)
264
- sinatra-contrib (1.3.2)
265
- backports (>= 2.0)
266
- eventmachine
267
- rack-protection
268
- rack-test
269
- sinatra (~> 1.3.0)
270
- tilt (~> 1.3)
271
- tilt (1.3.3)
272
- unicorn (4.8.3)
273
- kgio (~> 2.6)
274
- rack
275
- raindrops (~> 0.7)
276
-
277
- PLATFORMS
278
- java
279
- ruby
280
-
281
- DEPENDENCIES
282
- activesupport
283
- delorean
284
- eventmachine (>= 1.0.4)
285
- excon!
286
- jruby-openssl (~> 0.9)
287
- json (>= 1.8.5)
288
- open4
289
- puma
290
- rack (~> 1.6)
291
- rake
292
- rdoc
293
- rspec (>= 3.5.0)
294
- rubysl (~> 2.0)
295
- shindo
296
- sinatra
297
- sinatra-contrib
298
- unicorn
299
-
300
- BUNDLED WITH
301
- 1.15.4