puma 2.15.3 → 2.16.0

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

Potentially problematic release.


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

checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 5d64247c35d30d830a6138f5afebaab9e33b7569
4
- data.tar.gz: 45423049b4bce256ca87a9aa3272c24d56ee3ccc
3
+ metadata.gz: 761911bec0cc543faf03994736a763dc7d87b6eb
4
+ data.tar.gz: 20cd1109cf893a9940549520e1f2e6c875532f2e
5
5
  SHA512:
6
- metadata.gz: d022e465ffa0a1aa326f90edad187fa1693c88dfcdd033473b3cd625a578820d6418f9737949626e28b74c86513d721d76174883b1745a46c5acf989b3b9fba8
7
- data.tar.gz: 212eab2a81bff8ae41f2291d9ef663ea7285b2ccd4f301aed6cfd9fea50e82661e795fffdbcf17d2ac6b7fa5f4a3c44915f396e204fcda25b6a021352050937f
6
+ metadata.gz: c655e44ef966f2a4d8acd9b3d96b11a13072993edb1049ca65470cc0af3460bcd2e0d3f57f8afcec203cf6d9caad85cc7f1849454374ed65949b4284bd27ea33
7
+ data.tar.gz: 9b0e3acb94d4b0e3ae1a0f1eb1279dba87d600df67c928b455d8ea3ad47f3f90fb0a6854920776bad809dd46d013441e95385b981472d5f81f88d19ecbf4560e
data/History.txt CHANGED
@@ -1,3 +1,43 @@
1
+ === 2.16.0 / 2016-01-27
2
+
3
+ * 7 minor features:
4
+
5
+ * Add 'set_remote_address' config option
6
+ * Allow to run puma in silent mode
7
+ * Expose cli options in DSL
8
+ * Support passing JRuby keystore info in ssl_bind DSL
9
+ * Allow umask for unix:/// style control urls
10
+ * Expose `old_worker_count` in stats url
11
+ * Support TLS client auth (verify_mode) in jruby
12
+
13
+ * 7 bug fixes:
14
+
15
+ * Don't persist before_fork hook in state file
16
+ * Reload bundler before pulling in rack. Fixes #859
17
+ * Remove NEWRELIC_DISPATCHER env variable
18
+ * Cleanup C code
19
+ * Use Timeout.timeout instead of Object.timeout
20
+ * Make phased restarts faster
21
+ * Ignore the case of certain headers, because HTTP
22
+
23
+ * 1 doc changes:
24
+
25
+ * Test against the latest Ruby 2.1, 2.2, 2.3, head and JRuby 9.0.4.0 on Travis
26
+
27
+ * 12 merged PRs
28
+ * Merge pull request #822 from kwugirl/remove_NEWRELIC_DISPATCHER
29
+ * Merge pull request #833 from joemiller/jruby-client-tls-auth
30
+ * Merge pull request #837 from YuriSolovyov/ssl-keystore-jruby
31
+ * Merge pull request #839 from mezuka/master
32
+ * Merge pull request #845 from deepj/timeout-deprecation
33
+ * Merge pull request #846 from sriedel/strip_before_fork
34
+ * Merge pull request #850 from deepj/travis
35
+ * Merge pull request #853 from Jeffrey6052/patch-1
36
+ * Merge pull request #857 from zendesk/faster_phased_restarts
37
+ * Merge pull request #858 from mlarraz/fix_some_warnings
38
+ * Merge pull request #860 from zendesk/expose_old_worker_count
39
+ * Merge pull request #861 from zendesk/allow_control_url_umask
40
+
1
41
  === 2.15.3 / 2015-11-07
2
42
 
3
43
  * 1 bug fix:
@@ -248,7 +248,7 @@ void raise_error(SSL* ssl, int result) {
248
248
  VALUE engine_read(VALUE self) {
249
249
  ms_conn* conn;
250
250
  char buf[512];
251
- int bytes, n, error;
251
+ int bytes, error;
252
252
 
253
253
  Data_Get_Struct(self, ms_conn, conn);
254
254
 
@@ -13,6 +13,7 @@ import org.jruby.runtime.builtin.IRubyObject;
13
13
  import org.jruby.util.ByteList;
14
14
 
15
15
  import javax.net.ssl.KeyManagerFactory;
16
+ import javax.net.ssl.TrustManagerFactory;
16
17
  import javax.net.ssl.SSLContext;
17
18
  import javax.net.ssl.SSLEngine;
18
19
  import javax.net.ssl.SSLEngineResult;
@@ -136,23 +137,36 @@ public class MiniSSL extends RubyObject {
136
137
  public IRubyObject initialize(ThreadContext threadContext, IRubyObject miniSSLContext)
137
138
  throws KeyStoreException, IOException, CertificateException, NoSuchAlgorithmException, UnrecoverableKeyException, KeyManagementException {
138
139
  KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
140
+ KeyStore ts = KeyStore.getInstance(KeyStore.getDefaultType());
139
141
 
140
142
  char[] password = miniSSLContext.callMethod(threadContext, "keystore_pass").convertToString().asJavaString().toCharArray();
141
- ks.load(new FileInputStream(miniSSLContext.callMethod(threadContext, "keystore").convertToString().asJavaString()),
142
- password);
143
+ String keystoreFile = miniSSLContext.callMethod(threadContext, "keystore").convertToString().asJavaString();
144
+ ks.load(new FileInputStream(keystoreFile), password);
145
+ ts.load(new FileInputStream(keystoreFile), password);
143
146
 
144
147
  KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
145
148
  kmf.init(ks, password);
146
149
 
150
+ TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509");
151
+ tmf.init(ts);
152
+
147
153
  SSLContext sslCtx = SSLContext.getInstance("TLS");
148
154
 
149
- sslCtx.init(kmf.getKeyManagers(), null, null);
155
+ sslCtx.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
150
156
  engine = sslCtx.createSSLEngine();
151
157
 
152
158
  String[] protocols = new String[] { "TLSv1", "TLSv1.1", "TLSv1.2" };
153
159
  engine.setEnabledProtocols(protocols);
154
160
  engine.setUseClientMode(false);
155
161
 
162
+ long verify_mode = miniSSLContext.callMethod(threadContext, "verify_mode").convertToInteger().getLongValue();
163
+ if ((verify_mode & 0x1) != 0) { // 'peer'
164
+ engine.setWantClientAuth(true);
165
+ }
166
+ if ((verify_mode & 0x2) != 0) { // 'force_peer'
167
+ engine.setNeedClientAuth(true);
168
+ }
169
+
156
170
  SSLSession session = engine.getSession();
157
171
  inboundNetData = new MiniSSLBuffer(session.getPacketBufferSize());
158
172
  outboundAppData = new MiniSSLBuffer(session.getApplicationBufferSize());
data/lib/puma/cli.rb CHANGED
@@ -25,6 +25,12 @@ module Puma
25
25
  # Handles invoke a Puma::Server in a command line style.
26
26
  #
27
27
  class CLI
28
+ KEYS_NOT_TO_PERSIST_IN_STATE = [
29
+ :logger, :lowlevel_error_handler,
30
+ :before_worker_shutdown, :before_worker_boot, :before_worker_fork,
31
+ :after_worker_boot, :before_fork, :on_restart
32
+ ]
33
+
28
34
  # Create a new CLI object using +argv+ as the command line
29
35
  # arguments.
30
36
  #
@@ -42,8 +48,6 @@ module Puma
42
48
 
43
49
  @config = nil
44
50
 
45
- ENV['NEWRELIC_DISPATCHER'] ||= "Puma"
46
-
47
51
  setup_options
48
52
  generate_restart_data
49
53
 
@@ -108,12 +112,7 @@ module Puma
108
112
  state = { 'pid' => Process.pid }
109
113
  cfg = @config.dup
110
114
 
111
- [
112
- :logger,
113
- :before_worker_shutdown, :before_worker_boot, :before_worker_fork,
114
- :after_worker_boot,
115
- :on_restart, :lowlevel_error_handler
116
- ].each { |k| cfg.options.delete(k) }
115
+ KEYS_NOT_TO_PERSIST_IN_STATE.each { |k| cfg.options.delete(k) }
117
116
  state['config'] = cfg
118
117
 
119
118
  require 'yaml'
@@ -573,6 +572,7 @@ module Puma
573
572
  home = ENV['GEM_HOME']
574
573
  Bundler.with_clean_env do
575
574
  ENV['GEM_HOME'] = home
575
+ ENV['PUMA_BUNDLER_PRUNED'] = '1'
576
576
  wild = File.expand_path(File.join(puma_lib_dir, "../bin/puma-wild"))
577
577
  args = [Gem.ruby, wild, '-I', dirs.join(':'), deps.join(',')] + @original_argv
578
578
  Kernel.exec(*args)
data/lib/puma/client.rb CHANGED
@@ -45,11 +45,18 @@ module Puma
45
45
 
46
46
  @requests_served = 0
47
47
  @hijacked = false
48
+
49
+ @peerip = nil
50
+ @remote_addr_header = nil
48
51
  end
49
52
 
50
53
  attr_reader :env, :to_io, :body, :io, :timeout_at, :ready, :hijacked,
51
54
  :tempfile
52
55
 
56
+ attr_writer :peerip
57
+
58
+ attr_accessor :remote_addr_header
59
+
53
60
  def inspect
54
61
  "#<Puma::Client:0x#{object_id.to_s(16)} @ready=#{@ready.inspect}>"
55
62
  end
@@ -297,5 +304,17 @@ module Puma
297
304
  rescue StandardError
298
305
  end
299
306
  end
307
+
308
+ def peerip
309
+ return @peerip if @peerip
310
+
311
+ if @remote_addr_header
312
+ hdr = (@env[@remote_addr_header] || LOCALHOST_ADDR).split(/[\s,]/).first
313
+ @peerip = hdr
314
+ return hdr
315
+ end
316
+
317
+ @peerip ||= @io.peeraddr.last
318
+ end
300
319
  end
301
320
  end
data/lib/puma/cluster.rb CHANGED
@@ -65,6 +65,14 @@ module Puma
65
65
  @stage = :booted
66
66
  end
67
67
 
68
+ def dead?
69
+ @dead
70
+ end
71
+
72
+ def dead!
73
+ @dead = true
74
+ end
75
+
68
76
  def ping!
69
77
  @last_checkin = Time.now
70
78
  end
@@ -155,6 +163,8 @@ module Puma
155
163
  @workers.delete_if { |w| w.pid == pid }
156
164
  end
157
165
 
166
+ @workers.delete_if(&:dead?)
167
+
158
168
  spawn_workers
159
169
 
160
170
  if all_workers_booted?
@@ -242,6 +252,7 @@ module Puma
242
252
  hooks = @options[:before_worker_shutdown]
243
253
  hooks.each { |h| h.call(index) }
244
254
  ensure
255
+ @worker_write << "t#{Process.pid}\n" rescue nil
245
256
  @worker_write.close
246
257
  end
247
258
 
@@ -284,7 +295,9 @@ module Puma
284
295
  end
285
296
 
286
297
  def stats
287
- %Q!{ "workers": #{@workers.size}, "phase": #{@phase}, "booted_workers": #{@workers.count{|w| w.booted?}} }!
298
+ old_worker_count = @workers.count { |w| w.phase != @phase }
299
+ booted_worker_count = @workers.count { |w| w.booted? }
300
+ %Q!{ "workers": #{@workers.size}, "phase": #{@phase}, "booted_workers": #{booted_worker_count}, "old_workers": #{old_worker_count} }!
288
301
  end
289
302
 
290
303
  def preload?
@@ -412,6 +425,9 @@ module Puma
412
425
  w.boot!
413
426
  log "- Worker #{w.index} (pid: #{pid}) booted, phase: #{w.phase}"
414
427
  force_check = true
428
+ when "t"
429
+ w.dead!
430
+ force_check = true
415
431
  when "p"
416
432
  w.ping!
417
433
  end
@@ -29,6 +29,7 @@ module Puma
29
29
  @conf[:worker_timeout] ||= DefaultWorkerTimeout
30
30
  @conf[:worker_boot_timeout] ||= @conf[:worker_timeout]
31
31
  @conf[:worker_shutdown_timeout] ||= DefaultWorkerShutdownTimeout
32
+ @conf[:remote_address] ||= :socket
32
33
 
33
34
  @options = {}
34
35
  end
@@ -54,6 +55,7 @@ module Puma
54
55
  end
55
56
 
56
57
  def load
58
+ @conf.merge! @cli_options
57
59
  DSL.load(@conf, @cli_options[:config_file])
58
60
 
59
61
  # Load the options in the right priority
@@ -127,6 +129,15 @@ module Puma
127
129
  # Load and use the normal Rack builder if we can, otherwise
128
130
  # fallback to our minimal version.
129
131
  def rack_builder
132
+ # Load bundler now if we can so that we can pickup rack from
133
+ # a Gemfile
134
+ if ENV.key? 'PUMA_BUNDLER_PRUNED'
135
+ begin
136
+ require 'bundler/setup'
137
+ rescue LoadError
138
+ end
139
+ end
140
+
130
141
  begin
131
142
  require 'rack'
132
143
  require 'rack/builder'
data/lib/puma/const.rb CHANGED
@@ -99,8 +99,8 @@ module Puma
99
99
  # too taxing on performance.
100
100
  module Const
101
101
 
102
- PUMA_VERSION = VERSION = "2.15.3".freeze
103
- CODE_NAME = "Autumn Arbor Airbrush".freeze
102
+ PUMA_VERSION = VERSION = "2.16.0".freeze
103
+ CODE_NAME = "Midwinter Nights Trance".freeze
104
104
 
105
105
  FAST_TRACK_KA_TIMEOUT = 0.2
106
106
 
@@ -183,6 +183,7 @@ module Puma
183
183
  PORT_443 = "443".freeze
184
184
  LOCALHOST = "localhost".freeze
185
185
  LOCALHOST_IP = "127.0.0.1".freeze
186
+ LOCALHOST_ADDR = "127.0.0.1:0".freeze
186
187
 
187
188
  SERVER_PROTOCOL = "SERVER_PROTOCOL".freeze
188
189
  HTTP_11 = "HTTP/1.1".freeze
@@ -215,11 +216,11 @@ module Puma
215
216
  HTTP_10_200 = "HTTP/1.0 200 OK\r\n".freeze
216
217
 
217
218
  CLOSE = "close".freeze
218
- KEEP_ALIVE = "Keep-Alive".freeze
219
+ KEEP_ALIVE = "keep-alive".freeze
219
220
 
220
- CONTENT_LENGTH2 = "Content-Length".freeze
221
+ CONTENT_LENGTH2 = "content-length".freeze
221
222
  CONTENT_LENGTH_S = "Content-Length: ".freeze
222
- TRANSFER_ENCODING = "Transfer-Encoding".freeze
223
+ TRANSFER_ENCODING = "transfer-encoding".freeze
223
224
 
224
225
  CONNECTION_CLOSE = "Connection: close\r\n".freeze
225
226
  CONNECTION_KEEP_ALIVE = "Connection: Keep-Alive\r\n".freeze
data/lib/puma/dsl.rb CHANGED
@@ -42,6 +42,7 @@ module Puma
42
42
  @options[:control_auth_token] = auth_token if auth_token
43
43
 
44
44
  @options[:control_auth_token] = :none if opts[:no_token]
45
+ @options[:control_url_umask] = opts[:umask] if opts[:umask]
45
46
  end
46
47
  end
47
48
 
@@ -140,7 +141,12 @@ module Puma
140
141
  end
141
142
 
142
143
  def ssl_bind(host, port, opts)
143
- @options[:binds] << "ssl://#{host}:#{port}?cert=#{opts[:cert]}&key=#{opts[:key]}"
144
+ if defined?(JRUBY_VERSION)
145
+ keystore_additions = "keystore=#{opts[:keystore]}&keystore-pass=#{opts[:keystore_pass]}"
146
+ @options[:binds] << "ssl://#{host}:#{port}?cert=#{opts[:cert]}&key=#{opts[:key]}&#{keystore_additions}"
147
+ else
148
+ @options[:binds] << "ssl://#{host}:#{port}?cert=#{opts[:cert]}&key=#{opts[:key]}"
149
+ end
144
150
  end
145
151
 
146
152
  # Use +path+ as the file to store the server info state. This is
@@ -297,5 +303,46 @@ module Puma
297
303
  def shutdown_debug(val=true)
298
304
  @options[:shutdown_debug] = val
299
305
  end
306
+
307
+ # Control how the remote address of the connection is set. This
308
+ # is configurable because to calculate the true socket peer address
309
+ # a kernel syscall is required which for very fast rack handlers
310
+ # slows down the handling significantly.
311
+ #
312
+ # There are 4 possible values:
313
+ #
314
+ # * :socket (the default) - read the peername from the socket using the
315
+ # syscall. This is the normal behavior.
316
+ # * :localhost - set the remote address to "127.0.0.1"
317
+ # * header: http_header - set the remote address to the value of the
318
+ # provided http header. For instance:
319
+ # `set_remote_address header: "X-Real-IP"`.
320
+ # Only the first word (as separated by spaces or comma)
321
+ # is used, allowing headers such as X-Forwarded-For
322
+ # to be used as well.
323
+ # * Any string - this allows you to hardcode remote address to any value
324
+ # you wish. Because puma never uses this field anyway, it's
325
+ # format is entirely in your hands.
326
+ def set_remote_address(val=:socket)
327
+ case val
328
+ when :socket
329
+ @options[:remote_address] = val
330
+ when :localhost
331
+ @options[:remote_address] = :value
332
+ @options[:remote_address_value] = "127.0.0.1".freeze
333
+ when String
334
+ @options[:remote_address] = :value
335
+ @options[:remote_address_value] = val
336
+ when Hash
337
+ if hdr = val[:header]
338
+ @options[:remote_address] = :header
339
+ @options[:remote_address_header] = "HTTP_" + hdr.upcase.gsub("-", "_")
340
+ else
341
+ raise "Invalid value for set_remote_address - #{val.inspect}"
342
+ end
343
+ else
344
+ raise "Invalid value for set_remote_address - #{val}"
345
+ end
346
+ end
300
347
  end
301
348
  end
data/lib/puma/runner.rb CHANGED
@@ -52,8 +52,9 @@ module Puma
52
52
  when "unix"
53
53
  log "* Starting control server on #{str}"
54
54
  path = "#{uri.host}#{uri.path}"
55
+ mask = @options[:control_url_umask]
55
56
 
56
- control.add_unix_listener path
57
+ control.add_unix_listener path, mask
57
58
  else
58
59
  error "Invalid control URI: #{str}"
59
60
  end
data/lib/puma/server.rb CHANGED
@@ -307,6 +307,16 @@ module Puma
307
307
  pool = @thread_pool
308
308
  queue_requests = @queue_requests
309
309
 
310
+ remote_addr_value = nil
311
+ remote_addr_header = nil
312
+
313
+ case @options[:remote_address]
314
+ when :value
315
+ remote_addr_value = @options[:remote_address_value]
316
+ when :header
317
+ remote_addr_header = @options[:remote_address_header]
318
+ end
319
+
310
320
  while @status == :run
311
321
  begin
312
322
  ios = IO.select sockets
@@ -317,6 +327,12 @@ module Puma
317
327
  begin
318
328
  if io = sock.accept_nonblock
319
329
  client = Client.new io, @binder.env(sock)
330
+ if remote_addr_value
331
+ client.peerip = remote_addr_value
332
+ elsif remote_addr_header
333
+ client.remote_addr_header = remote_addr_header
334
+ end
335
+
320
336
  pool << client
321
337
  pool.wait_until_not_full unless queue_requests
322
338
  end
@@ -481,7 +497,7 @@ module Puma
481
497
 
482
498
  unless env.key?(REMOTE_ADDR)
483
499
  begin
484
- addr = client.peeraddr.last
500
+ addr = client.peerip
485
501
  rescue Errno::ENOTCONN
486
502
  # Client disconnects can result in an inability to get the
487
503
  # peeraddr from the socket; default to localhost.
@@ -513,7 +529,7 @@ module Puma
513
529
  env = req.env
514
530
  client = req.io
515
531
 
516
- normalize_env env, client
532
+ normalize_env env, req
517
533
 
518
534
  env[PUMA_SOCKET] = client
519
535
 
@@ -571,7 +587,7 @@ module Puma
571
587
 
572
588
  http_11 = if env[HTTP_VERSION] == HTTP_11
573
589
  allow_chunked = true
574
- keep_alive = env[HTTP_CONNECTION] != CLOSE
590
+ keep_alive = env.fetch(HTTP_CONNECTION, "").downcase != CLOSE
575
591
  include_keepalive_header = false
576
592
 
577
593
  # An optimization. The most common response is 200, so we can
@@ -589,7 +605,7 @@ module Puma
589
605
  true
590
606
  else
591
607
  allow_chunked = false
592
- keep_alive = env[HTTP_CONNECTION] == KEEP_ALIVE
608
+ keep_alive = env.fetch(HTTP_CONNECTION, "").downcase == KEEP_ALIVE
593
609
  include_keepalive_header = keep_alive
594
610
 
595
611
  # Same optimization as above for HTTP/1.1
@@ -608,7 +624,7 @@ module Puma
608
624
  response_hijack = nil
609
625
 
610
626
  headers.each do |k, vs|
611
- case k
627
+ case k.downcase
612
628
  when CONTENT_LENGTH2
613
629
  content_length = vs
614
630
  next
@@ -644,7 +660,7 @@ module Puma
644
660
  fast_write client, lines.to_s
645
661
  return keep_alive
646
662
  end
647
-
663
+
648
664
  if content_length
649
665
  lines.append CONTENT_LENGTH_S, content_length.to_s, line_ending
650
666
  chunked = false
@@ -8,7 +8,8 @@ module Rack
8
8
  :Host => '0.0.0.0',
9
9
  :Port => 8080,
10
10
  :Threads => '0:16',
11
- :Verbose => false
11
+ :Verbose => false,
12
+ :Silent => false
12
13
  }
13
14
 
14
15
  def self.run(app, options = {})
@@ -22,13 +23,16 @@ module Rack
22
23
  ENV['RACK_ENV'] = options[:environment].to_s
23
24
  end
24
25
 
25
- server = ::Puma::Server.new(app)
26
+ events_hander = options[:Silent] ? ::Puma::Events.strings : ::Puma::Events.stdio
27
+ server = ::Puma::Server.new(app, events_hander)
26
28
  min, max = options[:Threads].split(':', 2)
27
29
 
28
- puts "Puma #{::Puma::Const::PUMA_VERSION} starting..."
29
- puts "* Min threads: #{min}, max threads: #{max}"
30
- puts "* Environment: #{ENV['RACK_ENV']}"
31
- puts "* Listening on tcp://#{options[:Host]}:#{options[:Port]}"
30
+ log = events_hander.stdout
31
+
32
+ log.puts "Puma #{::Puma::Const::PUMA_VERSION} starting..."
33
+ log.puts "* Min threads: #{min}, max threads: #{max}"
34
+ log.puts "* Environment: #{ENV['RACK_ENV']}"
35
+ log.puts "* Listening on tcp://#{options[:Host]}:#{options[:Port]}"
32
36
 
33
37
  server.add_tcp_listener options[:Host], options[:Port]
34
38
  server.min_threads = min
@@ -38,9 +42,9 @@ module Rack
38
42
  begin
39
43
  server.run.join
40
44
  rescue Interrupt
41
- puts "* Gracefully stopping, waiting for requests to finish"
45
+ log.puts "* Gracefully stopping, waiting for requests to finish"
42
46
  server.stop(true)
43
- puts "* Goodbye!"
47
+ log.puts "* Goodbye!"
44
48
  end
45
49
 
46
50
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: puma
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.15.3
4
+ version: 2.16.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Evan Phoenix
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-11-07 00:00:00.000000000 Z
11
+ date: 2016-01-28 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rdoc
@@ -191,7 +191,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
191
191
  version: '0'
192
192
  requirements: []
193
193
  rubyforge_project:
194
- rubygems_version: 2.4.5
194
+ rubygems_version: 2.5.1
195
195
  signing_key:
196
196
  specification_version: 4
197
197
  summary: Puma is a simple, fast, threaded, and highly concurrent HTTP 1.1 server for