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

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
data/lib/puma/binder.rb CHANGED
@@ -3,24 +3,15 @@
3
3
  require 'uri'
4
4
  require 'socket'
5
5
 
6
- require 'puma/const'
7
- require 'puma/util'
8
- require 'puma/configuration'
6
+ require_relative 'const'
7
+ require_relative 'util'
8
+ require_relative 'configuration'
9
9
 
10
10
  module Puma
11
11
 
12
12
  if HAS_SSL
13
- require 'puma/minissl'
14
- require 'puma/minissl/context_builder'
15
-
16
- # Odd bug in 'pure Ruby' nio4r version 2.5.2, which installs with Ruby 2.3.
17
- # NIO doesn't create any OpenSSL objects, but it rescues an OpenSSL error.
18
- # The bug was that it did not require openssl.
19
- # @todo remove when Ruby 2.3 support is dropped
20
- #
21
- if windows? && RbConfig::CONFIG['ruby_version'] == '2.3.0'
22
- require 'openssl'
23
- end
13
+ require_relative 'minissl'
14
+ require_relative 'minissl/context_builder'
24
15
  end
25
16
 
26
17
  class Binder
@@ -28,8 +19,9 @@ module Puma
28
19
 
29
20
  RACK_VERSION = [1,6].freeze
30
21
 
31
- def initialize(events, conf = Configuration.new)
32
- @events = events
22
+ def initialize(log_writer, conf = Configuration.new)
23
+ @log_writer = log_writer
24
+ @conf = conf
33
25
  @listeners = []
34
26
  @inherited_fds = {}
35
27
  @activated_sockets = {}
@@ -37,10 +29,11 @@ module Puma
37
29
 
38
30
  @proto_env = {
39
31
  "rack.version".freeze => RACK_VERSION,
40
- "rack.errors".freeze => events.stderr,
32
+ "rack.errors".freeze => log_writer.stderr,
41
33
  "rack.multithread".freeze => conf.options[:max_threads] > 1,
42
34
  "rack.multiprocess".freeze => conf.options[:workers] >= 1,
43
35
  "rack.run_once".freeze => false,
36
+ RACK_URL_SCHEME => conf.options[:rack_url_scheme],
44
37
  "SCRIPT_NAME".freeze => ENV['SCRIPT_NAME'] || "",
45
38
 
46
39
  # I'd like to set a default CONTENT_TYPE here but some things
@@ -49,13 +42,13 @@ module Puma
49
42
  # infer properly.
50
43
 
51
44
  "QUERY_STRING".freeze => "",
52
- SERVER_PROTOCOL => HTTP_11,
53
45
  SERVER_SOFTWARE => PUMA_SERVER_STRING,
54
46
  GATEWAY_INTERFACE => CGI_VER
55
47
  }
56
48
 
57
49
  @envs = {}
58
50
  @ios = []
51
+ localhost_authority
59
52
  end
60
53
 
61
54
  attr_reader :ios
@@ -77,7 +70,7 @@ module Puma
77
70
  # @!attribute [r] connected_ports
78
71
  # @version 5.0.0
79
72
  def connected_ports
80
- ios.map { |io| io.addr[1] }.uniq
73
+ t = ios.map { |io| io.addr[1] }; t.uniq!; t
81
74
  end
82
75
 
83
76
  # @version 5.0.0
@@ -95,6 +88,7 @@ module Puma
95
88
  # @version 5.0.0
96
89
  #
97
90
  def create_activated_fds(env_hash)
91
+ @log_writer.debug "ENV['LISTEN_FDS'] #{ENV['LISTEN_FDS'].inspect} env_hash['LISTEN_PID'] #{env_hash['LISTEN_PID'].inspect}"
98
92
  return [] unless env_hash['LISTEN_FDS'] && env_hash['LISTEN_PID'].to_i == $$
99
93
  env_hash['LISTEN_FDS'].to_i.times do |index|
100
94
  sock = TCPServer.for_fd(socket_activation_fd(index))
@@ -102,11 +96,11 @@ module Puma
102
96
  [:unix, Socket.unpack_sockaddr_un(sock.getsockname)]
103
97
  rescue ArgumentError # Try to parse as a port/ip
104
98
  port, addr = Socket.unpack_sockaddr_in(sock.getsockname)
105
- addr = "[#{addr}]" if addr =~ /\:/
99
+ addr = "[#{addr}]" if addr&.include? ':'
106
100
  [:tcp, addr, port]
107
101
  end
108
102
  @activated_sockets[key] = sock
109
- @events.debug "Registered #{key.join ':'} for activation from LISTEN_FDS"
103
+ @log_writer.debug "Registered #{key.join ':'} for activation from LISTEN_FDS"
110
104
  end
111
105
  ["LISTEN_FDS", "LISTEN_PID"] # Signal to remove these keys from ENV
112
106
  end
@@ -148,29 +142,30 @@ module Puma
148
142
  end
149
143
  end
150
144
 
151
- def parse(binds, logger, log_msg = 'Listening')
145
+ def parse(binds, log_writer = nil, log_msg = 'Listening')
146
+ log_writer ||= @log_writer
152
147
  binds.each do |str|
153
148
  uri = URI.parse str
154
149
  case uri.scheme
155
150
  when "tcp"
156
151
  if fd = @inherited_fds.delete(str)
157
152
  io = inherit_tcp_listener uri.host, uri.port, fd
158
- logger.log "* Inherited #{str}"
153
+ log_writer.log "* Inherited #{str}"
159
154
  elsif sock = @activated_sockets.delete([ :tcp, uri.host, uri.port ])
160
155
  io = inherit_tcp_listener uri.host, uri.port, sock
161
- logger.log "* Activated #{str}"
156
+ log_writer.log "* Activated #{str}"
162
157
  else
163
158
  ios_len = @ios.length
164
159
  params = Util.parse_query uri.query
165
160
 
166
161
  opt = params.key?('low_latency') && params['low_latency'] != 'false'
167
- bak = params.fetch('backlog', 1024).to_i
162
+ backlog = params.fetch('backlog', 1024).to_i
168
163
 
169
- io = add_tcp_listener uri.host, uri.port, opt, bak
164
+ io = add_tcp_listener uri.host, uri.port, opt, backlog
170
165
 
171
166
  @ios[ios_len..-1].each do |i|
172
167
  addr = loc_addr_str i
173
- logger.log "* #{log_msg} on http://#{addr}"
168
+ log_writer.log "* #{log_msg} on http://#{addr}"
174
169
  end
175
170
  end
176
171
 
@@ -185,13 +180,14 @@ module Puma
185
180
  end
186
181
 
187
182
  if fd = @inherited_fds.delete(str)
188
- @unix_paths << path unless abstract
183
+ @unix_paths << path unless abstract || File.exist?(path)
189
184
  io = inherit_unix_listener path, fd
190
- logger.log "* Inherited #{str}"
191
- elsif sock = @activated_sockets.delete([ :unix, path ])
185
+ log_writer.log "* Inherited #{str}"
186
+ elsif sock = @activated_sockets.delete([ :unix, path ]) ||
187
+ @activated_sockets.delete([ :unix, File.realdirpath(path) ])
192
188
  @unix_paths << path unless abstract || File.exist?(path)
193
189
  io = inherit_unix_listener path, sock
194
- logger.log "* Activated #{str}"
190
+ log_writer.log "* Activated #{str}"
195
191
  else
196
192
  umask = nil
197
193
  mode = nil
@@ -215,43 +211,64 @@ module Puma
215
211
 
216
212
  @unix_paths << path unless abstract || File.exist?(path)
217
213
  io = add_unix_listener path, umask, mode, backlog
218
- logger.log "* #{log_msg} on #{str}"
214
+ log_writer.log "* #{log_msg} on #{str}"
219
215
  end
220
216
 
221
217
  @listeners << [str, io]
222
218
  when "ssl"
219
+ cert_key = %w[cert key]
223
220
 
224
221
  raise "Puma compiled without SSL support" unless HAS_SSL
225
222
 
226
223
  params = Util.parse_query uri.query
227
- ctx = MiniSSL::ContextBuilder.new(params, @events).context
224
+
225
+ # If key and certs are not defined and localhost gem is required.
226
+ # localhost gem will be used for self signed
227
+ # Load localhost authority if not loaded.
228
+ # Ruby 3 `values_at` accepts an array, earlier do not
229
+ if params.values_at(*cert_key).all? { |v| v.to_s.empty? }
230
+ ctx = localhost_authority && localhost_authority_context
231
+ end
232
+
233
+ ctx ||=
234
+ begin
235
+ # Extract cert_pem and key_pem from options[:store] if present
236
+ cert_key.each do |v|
237
+ if params[v]&.start_with?('store:')
238
+ index = Integer(params.delete(v).split('store:').last)
239
+ params["#{v}_pem"] = @conf.options[:store][index]
240
+ end
241
+ end
242
+ MiniSSL::ContextBuilder.new(params, @log_writer).context
243
+ end
228
244
 
229
245
  if fd = @inherited_fds.delete(str)
230
- logger.log "* Inherited #{str}"
246
+ log_writer.log "* Inherited #{str}"
231
247
  io = inherit_ssl_listener fd, ctx
232
248
  elsif sock = @activated_sockets.delete([ :tcp, uri.host, uri.port ])
233
249
  io = inherit_ssl_listener sock, ctx
234
- logger.log "* Activated #{str}"
250
+ log_writer.log "* Activated #{str}"
235
251
  else
236
252
  ios_len = @ios.length
237
- io = add_ssl_listener uri.host, uri.port, ctx
253
+ backlog = params.fetch('backlog', 1024).to_i
254
+ io = add_ssl_listener uri.host, uri.port, ctx, optimize_for_latency = true, backlog
238
255
 
239
256
  @ios[ios_len..-1].each do |i|
240
257
  addr = loc_addr_str i
241
- logger.log "* #{log_msg} on ssl://#{addr}?#{uri.query}"
258
+ log_writer.log "* #{log_msg} on ssl://#{addr}?#{uri.query}"
242
259
  end
243
260
  end
244
261
 
245
262
  @listeners << [str, io] if io
246
263
  else
247
- logger.error "Invalid URI: #{str}"
264
+ log_writer.error "Invalid URI: #{str}"
248
265
  end
249
266
  end
250
267
 
251
268
  # If we inherited fds but didn't use them (because of a
252
269
  # configuration change), then be sure to close them.
253
270
  @inherited_fds.each do |str, fd|
254
- logger.log "* Closing unused inherited connection: #{str}"
271
+ log_writer.log "* Closing unused inherited connection: #{str}"
255
272
 
256
273
  begin
257
274
  IO.for_fd(fd).close
@@ -271,7 +288,7 @@ module Puma
271
288
  fds = @ios.map(&:to_i)
272
289
  @activated_sockets.each do |key, sock|
273
290
  next if fds.include? sock.to_i
274
- logger.log "* Closing unused activated socket: #{key.first}://#{key[1..-1].join ':'}"
291
+ log_writer.log "* Closing unused activated socket: #{key.first}://#{key[1..-1].join ':'}"
275
292
  begin
276
293
  sock.close
277
294
  rescue SystemCallError
@@ -282,6 +299,22 @@ module Puma
282
299
  end
283
300
  end
284
301
 
302
+ def localhost_authority
303
+ @localhost_authority ||= Localhost::Authority.fetch if defined?(Localhost::Authority) && !Puma::IS_JRUBY
304
+ end
305
+
306
+ def localhost_authority_context
307
+ return unless localhost_authority
308
+
309
+ key_path, crt_path = if [:key_path, :certificate_path].all? { |m| localhost_authority.respond_to?(m) }
310
+ [localhost_authority.key_path, localhost_authority.certificate_path]
311
+ else
312
+ local_certificates_path = File.expand_path("~/.localhost")
313
+ [File.join(local_certificates_path, "localhost.key"), File.join(local_certificates_path, "localhost.crt")]
314
+ end
315
+ MiniSSL::ContextBuilder.new({ "key" => key_path, "cert" => crt_path }, @log_writer).context
316
+ end
317
+
285
318
  # Tell the server to listen on host +host+, port +port+.
286
319
  # If +optimize_for_latency+ is true (the default) then clients connecting
287
320
  # will be optimized for latency over throughput.
@@ -299,6 +332,7 @@ module Puma
299
332
 
300
333
  host = host[1..-2] if host and host[0..0] == '['
301
334
  tcp_server = TCPServer.new(host, port)
335
+
302
336
  if optimize_for_latency
303
337
  tcp_server.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
304
338
  end
@@ -320,6 +354,8 @@ module Puma
320
354
  optimize_for_latency=true, backlog=1024)
321
355
 
322
356
  raise "Puma compiled without SSL support" unless HAS_SSL
357
+ # Puma will try to use local authority context if context is supplied nil
358
+ ctx ||= localhost_authority_context
323
359
 
324
360
  if host == "localhost"
325
361
  loopback_addresses.each do |addr|
@@ -347,6 +383,8 @@ module Puma
347
383
 
348
384
  def inherit_ssl_listener(fd, ctx)
349
385
  raise "Puma compiled without SSL support" unless HAS_SSL
386
+ # Puma will try to use local authority context if context is supplied nil
387
+ ctx ||= localhost_authority_context
350
388
 
351
389
  s = fd.kind_of?(::TCPServer) ? fd : ::TCPServer.for_fd(fd)
352
390
 
@@ -437,9 +475,10 @@ module Puma
437
475
 
438
476
  # @!attribute [r] loopback_addresses
439
477
  def loopback_addresses
440
- Socket.ip_address_list.select do |addrinfo|
478
+ t = Socket.ip_address_list.select do |addrinfo|
441
479
  addrinfo.ipv6_loopback? || addrinfo.ipv4_loopback?
442
- end.map { |addrinfo| addrinfo.ip_address }.uniq
480
+ end
481
+ t.map! { |addrinfo| addrinfo.ip_address }; t.uniq!; t
443
482
  end
444
483
 
445
484
  def loc_addr_str(io)
data/lib/puma/cli.rb CHANGED
@@ -3,36 +3,31 @@
3
3
  require 'optparse'
4
4
  require 'uri'
5
5
 
6
- require 'puma'
7
- require 'puma/configuration'
8
- require 'puma/launcher'
9
- require 'puma/const'
10
- require 'puma/events'
6
+ require_relative '../puma'
7
+ require_relative 'configuration'
8
+ require_relative 'launcher'
9
+ require_relative 'const'
10
+ require_relative 'log_writer'
11
11
 
12
12
  module Puma
13
13
  class << self
14
- # The CLI exports its Puma::Configuration object here to allow
15
- # apps to pick it up. An app needs to use it conditionally though
16
- # since it is not set if the app is launched via another
17
- # mechanism than the CLI class.
14
+ # The CLI exports a Puma::Configuration instance here to allow
15
+ # apps to pick it up. An app must load this object conditionally
16
+ # because it is not set if the app is launched via any mechanism
17
+ # other than the CLI class.
18
18
  attr_accessor :cli_config
19
19
  end
20
20
 
21
21
  # Handles invoke a Puma::Server in a command line style.
22
22
  #
23
23
  class CLI
24
- KEYS_NOT_TO_PERSIST_IN_STATE = Launcher::KEYS_NOT_TO_PERSIST_IN_STATE
25
-
26
24
  # Create a new CLI object using +argv+ as the command line
27
25
  # arguments.
28
26
  #
29
- # +stdout+ and +stderr+ can be set to IO-like objects which
30
- # this object will report status on.
31
- #
32
- def initialize(argv, events=Events.stdio)
27
+ def initialize(argv, log_writer = LogWriter.stdio, events = Events.new)
33
28
  @debug = false
34
29
  @argv = argv.dup
35
-
30
+ @log_writer = log_writer
36
31
  @events = events
37
32
 
38
33
  @conf = nil
@@ -68,7 +63,7 @@ module Puma
68
63
  end
69
64
  end
70
65
 
71
- @launcher = Puma::Launcher.new(@conf, :events => @events, :argv => argv)
66
+ @launcher = Puma::Launcher.new(@conf, :log_writer => @log_writer, :events => @events, :argv => argv)
72
67
  end
73
68
 
74
69
  attr_reader :launcher
@@ -82,7 +77,7 @@ module Puma
82
77
 
83
78
  private
84
79
  def unsupported(str)
85
- @events.error(str)
80
+ @log_writer.error(str)
86
81
  raise UnsupportedOption
87
82
  end
88
83
 
@@ -112,6 +107,11 @@ module Puma
112
107
  file_config.load arg
113
108
  end
114
109
 
110
+ # Identical to supplying --config "-", but more semantic
111
+ o.on "--no-config", "Prevent Puma from searching for a config file" do |arg|
112
+ file_config.load "-"
113
+ end
114
+
115
115
  o.on "--control-url URL", "The bind url to use for the control server. Use 'auto' to use temp unix server" do |arg|
116
116
  configure_control_url(arg)
117
117
  end
@@ -146,7 +146,7 @@ module Puma
146
146
 
147
147
  o.on "-p", "--port PORT", "Define the TCP port to bind to",
148
148
  "Use -b for more advanced options" do |arg|
149
- user_config.bind "tcp://#{Configuration::DefaultTCPHost}:#{arg}"
149
+ user_config.bind "tcp://#{Configuration::DEFAULTS[:tcp_host]}:#{arg}"
150
150
  end
151
151
 
152
152
  o.on "--pidfile PATH", "Use PATH as a pidfile" do |arg|
@@ -179,6 +179,10 @@ module Puma
179
179
  user_config.restart_command cmd
180
180
  end
181
181
 
182
+ o.on "-s", "--silent", "Do not log prompt messages other than errors" do
183
+ @log_writer = LogWriter.new(NullIO.new, $stderr)
184
+ end
185
+
182
186
  o.on "-S", "--state PATH", "Where to store the state details" do |arg|
183
187
  user_config.state_path arg
184
188
  end