puma 5.3.2 → 6.0.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.

Files changed (83) hide show
  1. checksums.yaml +4 -4
  2. data/History.md +284 -11
  3. data/LICENSE +0 -0
  4. data/README.md +61 -16
  5. data/bin/puma-wild +1 -1
  6. data/docs/architecture.md +49 -16
  7. data/docs/compile_options.md +38 -2
  8. data/docs/deployment.md +53 -67
  9. data/docs/fork_worker.md +1 -3
  10. data/docs/images/puma-connection-flow-no-reactor.png +0 -0
  11. data/docs/images/puma-connection-flow.png +0 -0
  12. data/docs/images/puma-general-arch.png +0 -0
  13. data/docs/jungle/README.md +0 -0
  14. data/docs/jungle/rc.d/README.md +0 -0
  15. data/docs/jungle/rc.d/puma.conf +0 -0
  16. data/docs/kubernetes.md +0 -0
  17. data/docs/nginx.md +0 -0
  18. data/docs/plugins.md +15 -15
  19. data/docs/rails_dev_mode.md +2 -3
  20. data/docs/restart.md +6 -6
  21. data/docs/signals.md +11 -10
  22. data/docs/stats.md +8 -8
  23. data/docs/systemd.md +64 -67
  24. data/docs/testing_benchmarks_local_files.md +150 -0
  25. data/docs/testing_test_rackup_ci_files.md +36 -0
  26. data/ext/puma_http11/PumaHttp11Service.java +0 -0
  27. data/ext/puma_http11/ext_help.h +0 -0
  28. data/ext/puma_http11/extconf.rb +44 -13
  29. data/ext/puma_http11/http11_parser.c +24 -11
  30. data/ext/puma_http11/http11_parser.h +1 -1
  31. data/ext/puma_http11/http11_parser.java.rl +2 -2
  32. data/ext/puma_http11/http11_parser.rl +2 -2
  33. data/ext/puma_http11/http11_parser_common.rl +3 -3
  34. data/ext/puma_http11/mini_ssl.c +122 -23
  35. data/ext/puma_http11/no_ssl/PumaHttp11Service.java +0 -0
  36. data/ext/puma_http11/org/jruby/puma/Http11.java +3 -3
  37. data/ext/puma_http11/org/jruby/puma/Http11Parser.java +50 -48
  38. data/ext/puma_http11/org/jruby/puma/MiniSSL.java +188 -102
  39. data/ext/puma_http11/puma_http11.c +18 -10
  40. data/lib/puma/app/status.rb +9 -6
  41. data/lib/puma/binder.rb +81 -42
  42. data/lib/puma/cli.rb +23 -19
  43. data/lib/puma/client.rb +124 -30
  44. data/lib/puma/cluster/worker.rb +21 -29
  45. data/lib/puma/cluster/worker_handle.rb +8 -1
  46. data/lib/puma/cluster.rb +57 -48
  47. data/lib/puma/commonlogger.rb +0 -0
  48. data/lib/puma/configuration.rb +74 -55
  49. data/lib/puma/const.rb +21 -24
  50. data/lib/puma/control_cli.rb +22 -19
  51. data/lib/puma/detect.rb +10 -2
  52. data/lib/puma/dsl.rb +196 -57
  53. data/lib/puma/error_logger.rb +17 -9
  54. data/lib/puma/events.rb +6 -126
  55. data/lib/puma/io_buffer.rb +29 -4
  56. data/lib/puma/jruby_restart.rb +2 -1
  57. data/lib/puma/{json.rb → json_serialization.rb} +1 -1
  58. data/lib/puma/launcher/bundle_pruner.rb +104 -0
  59. data/lib/puma/launcher.rb +108 -154
  60. data/lib/puma/log_writer.rb +137 -0
  61. data/lib/puma/minissl/context_builder.rb +29 -16
  62. data/lib/puma/minissl.rb +115 -38
  63. data/lib/puma/null_io.rb +5 -0
  64. data/lib/puma/plugin/tmp_restart.rb +1 -1
  65. data/lib/puma/plugin.rb +2 -2
  66. data/lib/puma/rack/builder.rb +5 -5
  67. data/lib/puma/rack/urlmap.rb +0 -0
  68. data/lib/puma/rack_default.rb +1 -1
  69. data/lib/puma/reactor.rb +3 -3
  70. data/lib/puma/request.rb +293 -153
  71. data/lib/puma/runner.rb +63 -28
  72. data/lib/puma/server.rb +83 -88
  73. data/lib/puma/single.rb +10 -10
  74. data/lib/puma/state_file.rb +39 -7
  75. data/lib/puma/systemd.rb +3 -2
  76. data/lib/puma/thread_pool.rb +22 -17
  77. data/lib/puma/util.rb +20 -15
  78. data/lib/puma.rb +12 -9
  79. data/lib/rack/handler/puma.rb +9 -9
  80. data/tools/Dockerfile +1 -1
  81. data/tools/trickletest.rb +0 -0
  82. metadata +13 -9
  83. data/lib/puma/queue_close.rb +0 -26
@@ -1,9 +1,9 @@
1
1
  module Puma
2
2
  module MiniSSL
3
3
  class ContextBuilder
4
- def initialize(params, events)
4
+ def initialize(params, log_writer)
5
5
  @params = params
6
- @events = events
6
+ @log_writer = log_writer
7
7
  end
8
8
 
9
9
  def context
@@ -11,42 +11,55 @@ module Puma
11
11
 
12
12
  if defined?(JRUBY_VERSION)
13
13
  unless params['keystore']
14
- events.error "Please specify the Java keystore via 'keystore='"
14
+ log_writer.error "Please specify the Java keystore via 'keystore='"
15
15
  end
16
16
 
17
17
  ctx.keystore = params['keystore']
18
18
 
19
19
  unless params['keystore-pass']
20
- events.error "Please specify the Java keystore password via 'keystore-pass='"
20
+ log_writer.error "Please specify the Java keystore password via 'keystore-pass='"
21
21
  end
22
22
 
23
23
  ctx.keystore_pass = params['keystore-pass']
24
- ctx.ssl_cipher_list = params['ssl_cipher_list'] if params['ssl_cipher_list']
24
+ ctx.keystore_type = params['keystore-type']
25
+
26
+ if truststore = params['truststore']
27
+ ctx.truststore = truststore.eql?('default') ? :default : truststore
28
+ ctx.truststore_pass = params['truststore-pass']
29
+ ctx.truststore_type = params['truststore-type']
30
+ end
31
+
32
+ ctx.cipher_suites = params['cipher_suites'] || params['ssl_cipher_list']
33
+ ctx.protocols = params['protocols'] if params['protocols']
25
34
  else
26
- unless params['key']
27
- events.error "Please specify the SSL key via 'key='"
35
+ if params['key'].nil? && params['key_pem'].nil?
36
+ log_writer.error "Please specify the SSL key via 'key=' or 'key_pem='"
28
37
  end
29
38
 
30
- ctx.key = params['key']
39
+ ctx.key = params['key'] if params['key']
40
+ ctx.key_pem = params['key_pem'] if params['key_pem']
31
41
 
32
- unless params['cert']
33
- events.error "Please specify the SSL cert via 'cert='"
42
+ if params['cert'].nil? && params['cert_pem'].nil?
43
+ log_writer.error "Please specify the SSL cert via 'cert=' or 'cert_pem='"
34
44
  end
35
45
 
36
- ctx.cert = params['cert']
46
+ ctx.cert = params['cert'] if params['cert']
47
+ ctx.cert_pem = params['cert_pem'] if params['cert_pem']
37
48
 
38
49
  if ['peer', 'force_peer'].include?(params['verify_mode'])
39
50
  unless params['ca']
40
- events.error "Please specify the SSL ca via 'ca='"
51
+ log_writer.error "Please specify the SSL ca via 'ca='"
41
52
  end
42
53
  end
43
54
 
44
55
  ctx.ca = params['ca'] if params['ca']
45
56
  ctx.ssl_cipher_filter = params['ssl_cipher_filter'] if params['ssl_cipher_filter']
57
+
58
+ ctx.reuse = params['reuse'] if params['reuse']
46
59
  end
47
60
 
48
- ctx.no_tlsv1 = true if params['no_tlsv1'] == 'true'
49
- ctx.no_tlsv1_1 = true if params['no_tlsv1_1'] == 'true'
61
+ ctx.no_tlsv1 = params['no_tlsv1'] == 'true'
62
+ ctx.no_tlsv1_1 = params['no_tlsv1_1'] == 'true'
50
63
 
51
64
  if params['verify_mode']
52
65
  ctx.verify_mode = case params['verify_mode']
@@ -57,7 +70,7 @@ module Puma
57
70
  when "none"
58
71
  MiniSSL::VERIFY_NONE
59
72
  else
60
- events.error "Please specify a valid verify_mode="
73
+ log_writer.error "Please specify a valid verify_mode="
61
74
  MiniSSL::VERIFY_NONE
62
75
  end
63
76
  end
@@ -73,7 +86,7 @@ module Puma
73
86
 
74
87
  private
75
88
 
76
- attr_reader :params, :events
89
+ attr_reader :params, :log_writer
77
90
  end
78
91
  end
79
92
  end
data/lib/puma/minissl.rb CHANGED
@@ -1,11 +1,12 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  begin
4
- require 'io/wait'
4
+ require 'io/wait' unless Puma::HAS_NATIVE_IO_WAIT
5
5
  rescue LoadError
6
6
  end
7
7
 
8
8
  # need for Puma::MiniSSL::OPENSSL constants used in `HAS_TLS1_3`
9
+ # use require, see https://github.com/puma/puma/pull/2381
9
10
  require 'puma/puma_http11'
10
11
 
11
12
  module Puma
@@ -13,15 +14,16 @@ module Puma
13
14
  # Define constant at runtime, as it's easy to determine at built time,
14
15
  # but Puma could (it shouldn't) be loaded with an older OpenSSL version
15
16
  # @version 5.0.0
16
- HAS_TLS1_3 = !IS_JRUBY &&
17
- (OPENSSL_VERSION[/ \d+\.\d+\.\d+/].split('.').map(&:to_i) <=> [1,1,1]) != -1 &&
18
- (OPENSSL_LIBRARY_VERSION[/ \d+\.\d+\.\d+/].split('.').map(&:to_i) <=> [1,1,1]) !=-1
17
+ HAS_TLS1_3 = IS_JRUBY ||
18
+ ((OPENSSL_VERSION[/ \d+\.\d+\.\d+/].split('.').map(&:to_i) <=> [1,1,1]) != -1 &&
19
+ (OPENSSL_LIBRARY_VERSION[/ \d+\.\d+\.\d+/].split('.').map(&:to_i) <=> [1,1,1]) !=-1)
19
20
 
20
21
  class Socket
21
22
  def initialize(socket, engine)
22
23
  @socket = socket
23
24
  @engine = engine
24
25
  @peercert = nil
26
+ @reuse = nil
25
27
  end
26
28
 
27
29
  # @!attribute [r] to_io
@@ -50,7 +52,7 @@ module Puma
50
52
  # is made with TLSv1.3 as an available protocol
51
53
  # @version 5.0.0
52
54
  def bad_tlsv1_3?
53
- HAS_TLS1_3 && @engine.ssl_vers_st == ['TLSv1.3', 'SSLERR']
55
+ HAS_TLS1_3 && ssl_version_state == ['TLSv1.3', 'SSLERR']
54
56
  end
55
57
  private :bad_tlsv1_3?
56
58
 
@@ -123,7 +125,7 @@ module Puma
123
125
  while true
124
126
  wrote = @engine.write data
125
127
 
126
- enc_wr = ''.dup
128
+ enc_wr = +''
127
129
  while (enc = @engine.extract)
128
130
  enc_wr << enc
129
131
  end
@@ -161,30 +163,15 @@ module Puma
161
163
  @socket.flush
162
164
  end
163
165
 
164
- def read_and_drop(timeout = 1)
165
- return :timeout unless IO.select([@socket], nil, nil, timeout)
166
- case @socket.read_nonblock(1024, exception: false)
167
- when nil
168
- :eof
169
- when :wait_readable
170
- :eagain
171
- else
172
- :drop
173
- end
174
- end
175
-
176
- def should_drop_bytes?
177
- @engine.init? || !@engine.shutdown
178
- end
179
-
180
166
  def close
181
167
  begin
182
- # Read any drop any partially initialized sockets and any received bytes during shutdown.
183
- # Don't let this socket hold this loop forever.
184
- # If it can't send more packets within 1s, then give up.
185
- return if [:timeout, :eof].include?(read_and_drop(1)) while should_drop_bytes?
168
+ unless @engine.shutdown
169
+ while alert_data = @engine.extract
170
+ @socket.write alert_data
171
+ end
172
+ end
186
173
  rescue IOError, SystemCallError
187
- Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
174
+ Puma::Util.purge_interrupt_queue
188
175
  # nothing
189
176
  ensure
190
177
  @socket.close
@@ -210,10 +197,6 @@ module Puma
210
197
  if IS_JRUBY
211
198
  OPENSSL_NO_SSL3 = false
212
199
  OPENSSL_NO_TLS1 = false
213
-
214
- class SSLError < StandardError
215
- # Define this for jruby even though it isn't used.
216
- end
217
200
  end
218
201
 
219
202
  class Context
@@ -223,21 +206,72 @@ module Puma
223
206
  def initialize
224
207
  @no_tlsv1 = false
225
208
  @no_tlsv1_1 = false
209
+ @key = nil
210
+ @cert = nil
211
+ @key_pem = nil
212
+ @cert_pem = nil
213
+ @reuse = nil
214
+ @reuse_cache_size = nil
215
+ @reuse_timeout = nil
216
+ end
217
+
218
+ def check_file(file, desc)
219
+ raise ArgumentError, "#{desc} file '#{file}' does not exist" unless File.exist? file
220
+ raise ArgumentError, "#{desc} file '#{file}' is not readable" unless File.readable? file
226
221
  end
227
222
 
228
223
  if IS_JRUBY
229
224
  # jruby-specific Context properties: java uses a keystore and password pair rather than a cert/key pair
230
225
  attr_reader :keystore
226
+ attr_reader :keystore_type
231
227
  attr_accessor :keystore_pass
232
- attr_accessor :ssl_cipher_list
228
+ attr_reader :truststore
229
+ attr_reader :truststore_type
230
+ attr_accessor :truststore_pass
231
+ attr_reader :cipher_suites
232
+ attr_reader :protocols
233
233
 
234
234
  def keystore=(keystore)
235
- raise ArgumentError, "No such keystore file '#{keystore}'" unless File.exist? keystore
235
+ check_file keystore, 'Keystore'
236
236
  @keystore = keystore
237
237
  end
238
238
 
239
+ def truststore=(truststore)
240
+ # NOTE: historically truststore was assumed the same as keystore, this is kept for backwards
241
+ # compatibility, to rely on JVM's trust defaults we allow setting `truststore = :default`
242
+ unless truststore.eql?(:default)
243
+ raise ArgumentError, "No such truststore file '#{truststore}'" unless File.exist?(truststore)
244
+ end
245
+ @truststore = truststore
246
+ end
247
+
248
+ def keystore_type=(type)
249
+ raise ArgumentError, "Invalid keystore type: #{type.inspect}" unless ['pkcs12', 'jks', nil].include?(type)
250
+ @keystore_type = type
251
+ end
252
+
253
+ def truststore_type=(type)
254
+ raise ArgumentError, "Invalid truststore type: #{type.inspect}" unless ['pkcs12', 'jks', nil].include?(type)
255
+ @truststore_type = type
256
+ end
257
+
258
+ def cipher_suites=(list)
259
+ list = list.split(',').map(&:strip) if list.is_a?(String)
260
+ @cipher_suites = list
261
+ end
262
+
263
+ # aliases for backwards compatibility
264
+ alias_method :ssl_cipher_list, :cipher_suites
265
+ alias_method :ssl_cipher_list=, :cipher_suites=
266
+
267
+ def protocols=(list)
268
+ list = list.split(',').map(&:strip) if list.is_a?(String)
269
+ @protocols = list
270
+ end
271
+
239
272
  def check
240
273
  raise "Keystore not configured" unless @keystore
274
+ # @truststore defaults to @keystore due backwards compatibility
241
275
  end
242
276
 
243
277
  else
@@ -245,27 +279,70 @@ module Puma
245
279
  attr_reader :key
246
280
  attr_reader :cert
247
281
  attr_reader :ca
282
+ attr_reader :cert_pem
283
+ attr_reader :key_pem
248
284
  attr_accessor :ssl_cipher_filter
249
285
  attr_accessor :verification_flags
250
286
 
287
+ attr_reader :reuse, :reuse_cache_size, :reuse_timeout
288
+
251
289
  def key=(key)
252
- raise ArgumentError, "No such key file '#{key}'" unless File.exist? key
290
+ check_file key, 'Key'
253
291
  @key = key
254
292
  end
255
293
 
256
294
  def cert=(cert)
257
- raise ArgumentError, "No such cert file '#{cert}'" unless File.exist? cert
295
+ check_file cert, 'Cert'
258
296
  @cert = cert
259
297
  end
260
298
 
261
299
  def ca=(ca)
262
- raise ArgumentError, "No such ca file '#{ca}'" unless File.exist? ca
300
+ check_file ca, 'ca'
263
301
  @ca = ca
264
302
  end
265
303
 
304
+ def cert_pem=(cert_pem)
305
+ raise ArgumentError, "'cert_pem' is not a String" unless cert_pem.is_a? String
306
+ @cert_pem = cert_pem
307
+ end
308
+
309
+ def key_pem=(key_pem)
310
+ raise ArgumentError, "'key_pem' is not a String" unless key_pem.is_a? String
311
+ @key_pem = key_pem
312
+ end
313
+
266
314
  def check
267
- raise "Key not configured" unless @key
268
- raise "Cert not configured" unless @cert
315
+ raise "Key not configured" if @key.nil? && @key_pem.nil?
316
+ raise "Cert not configured" if @cert.nil? && @cert_pem.nil?
317
+ end
318
+
319
+ # Controls session reuse. Allowed values are as follows:
320
+ # * 'off' - matches the behavior of Puma 5.6 and earlier. This is included
321
+ # in case reuse 'on' is made the default in future Puma versions.
322
+ # * 'dflt' - sets session reuse on, with OpenSSL default cache size of
323
+ # 20k and default timeout of 300 seconds.
324
+ # * 's,t' - where s and t are integer strings, for size and timeout.
325
+ # * 's' - where s is an integer strings for size.
326
+ # * ',t' - where t is an integer strings for timeout.
327
+ #
328
+ def reuse=(reuse_str)
329
+ case reuse_str
330
+ when 'off'
331
+ @reuse = nil
332
+ when 'dflt'
333
+ @reuse = true
334
+ when /\A\d+\z/
335
+ @reuse = true
336
+ @reuse_cache_size = reuse_str.to_i
337
+ when /\A\d+,\d+\z/
338
+ @reuse = true
339
+ size, time = reuse_str.split ','
340
+ @reuse_cache_size = size.to_i
341
+ @reuse_timeout = time.to_i
342
+ when /\A,\d+\z/
343
+ @reuse = true
344
+ @reuse_timeout = reuse_str.delete(',').to_i
345
+ end
269
346
  end
270
347
  end
271
348
 
data/lib/puma/null_io.rb CHANGED
@@ -52,5 +52,10 @@ module Puma
52
52
  def flush
53
53
  self
54
54
  end
55
+
56
+ # This is used as singleton class, so can't have state.
57
+ def closed?
58
+ false
59
+ end
55
60
  end
56
61
  end
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'puma/plugin'
3
+ require_relative '../plugin'
4
4
 
5
5
  Puma::Plugin.create do
6
6
  def start(launcher)
data/lib/puma/plugin.rb CHANGED
@@ -64,7 +64,7 @@ module Puma
64
64
  def fire_background
65
65
  @background.each_with_index do |b, i|
66
66
  Thread.new do
67
- Puma.set_thread_name "plugin background #{i}"
67
+ Puma.set_thread_name "plgn bg #{i}"
68
68
  b.call
69
69
  end
70
70
  end
@@ -91,7 +91,7 @@ module Puma
91
91
  path = ary.first[CALLER_FILE]
92
92
 
93
93
  m = %r!puma/plugin/([^/]*)\.rb$!.match(path)
94
- return m[1]
94
+ m[1]
95
95
  end
96
96
 
97
97
  def self.create(&blk)
@@ -102,13 +102,13 @@ module Puma::Rack
102
102
  begin
103
103
  info = []
104
104
  server = Rack::Handler.get(options[:server]) || Rack::Handler.default(options)
105
- if server && server.respond_to?(:valid_options)
105
+ if server&.respond_to?(:valid_options)
106
106
  info << ""
107
107
  info << "Server-specific options for #{server.name}:"
108
108
 
109
109
  has_options = false
110
110
  server.valid_options.each do |name, description|
111
- next if name.to_s =~ /^(Host|Port)[^a-zA-Z]/ # ignore handler's host and port options, we do our own.
111
+ next if /^(Host|Port)[^a-zA-Z]/.match? name.to_s # ignore handler's host and port options, we do our own.
112
112
 
113
113
  info << " -O %-21s %s" % [name, description]
114
114
  has_options = true
@@ -165,7 +165,7 @@ module Puma::Rack
165
165
  require config
166
166
  app = Object.const_get(::File.basename(config, '.rb').capitalize)
167
167
  end
168
- return app, options
168
+ [app, options]
169
169
  end
170
170
 
171
171
  def self.new_from_string(builder_script, file="(rackup)")
@@ -276,7 +276,7 @@ module Puma::Rack
276
276
  app = @map ? generate_map(@run, @map) : @run
277
277
  fail "missing run or map statement" unless app
278
278
  app = @use.reverse.inject(app) { |a,e| e[a] }
279
- @warmup.call(app) if @warmup
279
+ @warmup&.call app
280
280
  app
281
281
  end
282
282
 
@@ -287,7 +287,7 @@ module Puma::Rack
287
287
  private
288
288
 
289
289
  def generate_map(default_app, mapping)
290
- require 'puma/rack/urlmap'
290
+ require_relative 'urlmap'
291
291
 
292
292
  mapped = default_app ? {'/' => default_app} : {}
293
293
  mapping.each { |r,b| mapped[r] = self.class.new(default_app, &b).to_app }
File without changes
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'rack/handler/puma'
3
+ require_relative '../rack/handler/puma'
4
4
 
5
5
  module Rack::Handler
6
6
  def self.default(options = {})
data/lib/puma/reactor.rb CHANGED
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'puma/queue_close' unless ::Queue.instance_methods.include? :close
3
+ require_relative 'queue_close' unless ::Queue.instance_methods.include? :close
4
4
 
5
5
  module Puma
6
6
  class UnsupportedBackend < StandardError; end
@@ -61,7 +61,7 @@ module Puma
61
61
  @selector.wakeup
62
62
  rescue IOError # Ignore if selector is already closed
63
63
  end
64
- @thread.join if @thread
64
+ @thread&.join
65
65
  end
66
66
 
67
67
  private
@@ -76,7 +76,7 @@ module Puma
76
76
 
77
77
  # Wakeup all objects that timed out.
78
78
  timed_out = @timeouts.take_while {|t| t.timeout == 0}
79
- timed_out.each(&method(:wakeup!))
79
+ timed_out.each { |c| wakeup! c }
80
80
 
81
81
  unless @input.empty?
82
82
  until @input.empty?