puma 5.3.2 → 5.6.9

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.
Files changed (49) hide show
  1. checksums.yaml +4 -4
  2. data/History.md +216 -11
  3. data/README.md +47 -6
  4. data/docs/architecture.md +49 -16
  5. data/docs/compile_options.md +4 -2
  6. data/docs/deployment.md +53 -67
  7. data/docs/plugins.md +15 -15
  8. data/docs/rails_dev_mode.md +2 -3
  9. data/docs/restart.md +6 -6
  10. data/docs/signals.md +11 -10
  11. data/docs/stats.md +8 -8
  12. data/docs/systemd.md +64 -67
  13. data/ext/puma_http11/extconf.rb +34 -6
  14. data/ext/puma_http11/http11_parser.c +23 -10
  15. data/ext/puma_http11/http11_parser_common.rl +1 -1
  16. data/ext/puma_http11/mini_ssl.c +90 -12
  17. data/ext/puma_http11/org/jruby/puma/Http11.java +2 -0
  18. data/ext/puma_http11/org/jruby/puma/Http11Parser.java +49 -47
  19. data/ext/puma_http11/org/jruby/puma/MiniSSL.java +38 -55
  20. data/ext/puma_http11/puma_http11.c +1 -1
  21. data/lib/puma/app/status.rb +7 -4
  22. data/lib/puma/binder.rb +51 -6
  23. data/lib/puma/cli.rb +14 -4
  24. data/lib/puma/client.rb +143 -25
  25. data/lib/puma/cluster/worker.rb +8 -18
  26. data/lib/puma/cluster/worker_handle.rb +4 -0
  27. data/lib/puma/cluster.rb +30 -24
  28. data/lib/puma/configuration.rb +4 -1
  29. data/lib/puma/const.rb +17 -8
  30. data/lib/puma/control_cli.rb +19 -13
  31. data/lib/puma/detect.rb +8 -2
  32. data/lib/puma/dsl.rb +111 -13
  33. data/lib/puma/{json.rb → json_serialization.rb} +1 -1
  34. data/lib/puma/launcher.rb +15 -1
  35. data/lib/puma/minissl/context_builder.rb +8 -6
  36. data/lib/puma/minissl.rb +33 -27
  37. data/lib/puma/null_io.rb +5 -0
  38. data/lib/puma/plugin.rb +2 -2
  39. data/lib/puma/rack/builder.rb +1 -1
  40. data/lib/puma/request.rb +35 -13
  41. data/lib/puma/runner.rb +22 -8
  42. data/lib/puma/server.rb +37 -29
  43. data/lib/puma/state_file.rb +42 -7
  44. data/lib/puma/thread_pool.rb +7 -5
  45. data/lib/puma/util.rb +20 -4
  46. data/lib/puma.rb +6 -4
  47. data/lib/rack/version_restriction.rb +15 -0
  48. data/tools/Dockerfile +1 -1
  49. metadata +8 -7
@@ -6,6 +6,7 @@ import org.jruby.RubyModule;
6
6
  import org.jruby.RubyObject;
7
7
  import org.jruby.RubyString;
8
8
  import org.jruby.anno.JRubyMethod;
9
+ import org.jruby.exceptions.RaiseException;
9
10
  import org.jruby.javasupport.JavaEmbedUtils;
10
11
  import org.jruby.runtime.Block;
11
12
  import org.jruby.runtime.ObjectAllocator;
@@ -80,11 +81,11 @@ public class MiniSSL extends RubyObject {
80
81
  /**
81
82
  * Writes bytes to the buffer after ensuring there's room
82
83
  */
83
- public void put(byte[] bytes) {
84
- if (buffer.remaining() < bytes.length) {
85
- resize(buffer.limit() + bytes.length);
84
+ private void put(byte[] bytes, final int offset, final int length) {
85
+ if (buffer.remaining() < length) {
86
+ resize(buffer.limit() + length);
86
87
  }
87
- buffer.put(bytes);
88
+ buffer.put(bytes, offset, length);
88
89
  }
89
90
 
90
91
  /**
@@ -115,7 +116,7 @@ public class MiniSSL extends RubyObject {
115
116
 
116
117
  buffer.get(bss);
117
118
  buffer.clear();
118
- return new ByteList(bss);
119
+ return new ByteList(bss, false);
119
120
  }
120
121
 
121
122
  @Override
@@ -174,8 +175,6 @@ public class MiniSSL extends RubyObject {
174
175
  @JRubyMethod
175
176
  public IRubyObject initialize(ThreadContext threadContext, IRubyObject miniSSLContext)
176
177
  throws KeyStoreException, NoSuchAlgorithmException, KeyManagementException {
177
- KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
178
- KeyStore ts = KeyStore.getInstance(KeyStore.getDefaultType());
179
178
 
180
179
  String keystoreFile = miniSSLContext.callMethod(threadContext, "keystore").convertToString().asJavaString();
181
180
  KeyManagerFactory kmf = keyManagerFactoryMap.get(keystoreFile);
@@ -230,14 +229,9 @@ public class MiniSSL extends RubyObject {
230
229
 
231
230
  @JRubyMethod
232
231
  public IRubyObject inject(IRubyObject arg) {
233
- try {
234
- byte[] bytes = arg.convertToString().getBytes();
235
- inboundNetData.put(bytes);
236
- return this;
237
- } catch (Exception e) {
238
- e.printStackTrace();
239
- throw new RuntimeException(e);
240
- }
232
+ ByteList bytes = arg.convertToString().getByteList();
233
+ inboundNetData.put(bytes.unsafeBytes(), bytes.getBegin(), bytes.getRealSize());
234
+ return this;
241
235
  }
242
236
 
243
237
  private enum SSLOperation {
@@ -285,19 +279,11 @@ public class MiniSSL extends RubyObject {
285
279
  }
286
280
  }
287
281
 
288
- // after each op, run any delegated tasks if needed
289
- if(res.getHandshakeStatus() == HandshakeStatus.NEED_TASK) {
290
- Runnable runnable;
291
- while ((runnable = engine.getDelegatedTask()) != null) {
292
- runnable.run();
293
- }
294
- }
295
-
296
282
  return res;
297
283
  }
298
284
 
299
285
  @JRubyMethod
300
- public IRubyObject read() throws Exception {
286
+ public IRubyObject read() {
301
287
  try {
302
288
  inboundNetData.flip();
303
289
 
@@ -310,11 +296,12 @@ public class MiniSSL extends RubyObject {
310
296
 
311
297
  HandshakeStatus handshakeStatus = engine.getHandshakeStatus();
312
298
  boolean done = false;
313
- SSLEngineResult res = null;
314
299
  while (!done) {
300
+ SSLEngineResult res;
315
301
  switch (handshakeStatus) {
316
302
  case NEED_WRAP:
317
303
  res = doOp(SSLOperation.WRAP, inboundAppData, outboundNetData);
304
+ handshakeStatus = res.getHandshakeStatus();
318
305
  break;
319
306
  case NEED_UNWRAP:
320
307
  res = doOp(SSLOperation.UNWRAP, inboundNetData, inboundAppData);
@@ -322,13 +309,18 @@ public class MiniSSL extends RubyObject {
322
309
  // need more data before we can shake more hands
323
310
  done = true;
324
311
  }
312
+ handshakeStatus = res.getHandshakeStatus();
313
+ break;
314
+ case NEED_TASK:
315
+ Runnable runnable;
316
+ while ((runnable = engine.getDelegatedTask()) != null) {
317
+ runnable.run();
318
+ }
319
+ handshakeStatus = engine.getHandshakeStatus();
325
320
  break;
326
321
  default:
327
322
  done = true;
328
323
  }
329
- if (!done) {
330
- handshakeStatus = res.getHandshakeStatus();
331
- }
332
324
  }
333
325
 
334
326
  if (inboundNetData.hasRemaining()) {
@@ -342,55 +334,46 @@ public class MiniSSL extends RubyObject {
342
334
  return getRuntime().getNil();
343
335
  }
344
336
 
345
- RubyString str = getRuntime().newString("");
346
- str.setValue(appDataByteList);
347
- return str;
348
- } catch (Exception e) {
349
- throw getRuntime().newEOFError(e.getMessage());
337
+ return RubyString.newString(getRuntime(), appDataByteList);
338
+ } catch (SSLException e) {
339
+ RaiseException re = getRuntime().newEOFError(e.getMessage());
340
+ re.initCause(e);
341
+ throw re;
350
342
  }
351
343
  }
352
344
 
353
345
  @JRubyMethod
354
346
  public IRubyObject write(IRubyObject arg) {
355
- try {
356
- byte[] bls = arg.convertToString().getBytes();
357
- outboundAppData = new MiniSSLBuffer(bls);
347
+ byte[] bls = arg.convertToString().getBytes();
348
+ outboundAppData = new MiniSSLBuffer(bls);
358
349
 
359
- return getRuntime().newFixnum(bls.length);
360
- } catch (Exception e) {
361
- e.printStackTrace();
362
- throw new RuntimeException(e);
363
- }
350
+ return getRuntime().newFixnum(bls.length);
364
351
  }
365
352
 
366
353
  @JRubyMethod
367
- public IRubyObject extract() throws SSLException {
354
+ public IRubyObject extract(ThreadContext context) {
368
355
  try {
369
356
  ByteList dataByteList = outboundNetData.asByteList();
370
357
  if (dataByteList != null) {
371
- RubyString str = getRuntime().newString("");
372
- str.setValue(dataByteList);
373
- return str;
358
+ return RubyString.newString(context.runtime, dataByteList);
374
359
  }
375
360
 
376
361
  if (!outboundAppData.hasRemaining()) {
377
- return getRuntime().getNil();
362
+ return context.nil;
378
363
  }
379
364
 
380
365
  outboundNetData.clear();
381
366
  doOp(SSLOperation.WRAP, outboundAppData, outboundNetData);
382
367
  dataByteList = outboundNetData.asByteList();
383
368
  if (dataByteList == null) {
384
- return getRuntime().getNil();
369
+ return context.nil;
385
370
  }
386
371
 
387
- RubyString str = getRuntime().newString("");
388
- str.setValue(dataByteList);
389
-
390
- return str;
391
- } catch (Exception e) {
392
- e.printStackTrace();
393
- throw new RuntimeException(e);
372
+ return RubyString.newString(context.runtime, dataByteList);
373
+ } catch (SSLException e) {
374
+ RaiseException ex = context.runtime.newRuntimeError(e.toString());
375
+ ex.initCause(e);
376
+ throw ex;
394
377
  }
395
378
  }
396
379
 
@@ -398,7 +381,7 @@ public class MiniSSL extends RubyObject {
398
381
  public IRubyObject peercert() throws CertificateEncodingException {
399
382
  try {
400
383
  return JavaEmbedUtils.javaToRuby(getRuntime(), engine.getSession().getPeerCertificates()[0].getEncoded());
401
- } catch (SSLPeerUnverifiedException ex) {
384
+ } catch (SSLPeerUnverifiedException e) {
402
385
  return getRuntime().getNil();
403
386
  }
404
387
  }
@@ -451,7 +451,7 @@ VALUE HttpParser_body(VALUE self) {
451
451
  void Init_mini_ssl(VALUE mod);
452
452
  #endif
453
453
 
454
- void Init_puma_http11()
454
+ void Init_puma_http11(void)
455
455
  {
456
456
 
457
457
  VALUE mPuma = rb_define_module("Puma");
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
- require 'puma/json'
2
+ require 'puma/json_serialization'
3
3
 
4
4
  module Puma
5
5
  module App
@@ -39,6 +39,9 @@ module Puma
39
39
  when 'phased-restart'
40
40
  @launcher.phased_restart ? 200 : 404
41
41
 
42
+ when 'refork'
43
+ @launcher.refork ? 200 : 404
44
+
42
45
  when 'reload-worker-directory'
43
46
  @launcher.send(:reload_worker_directory) ? 200 : 404
44
47
 
@@ -46,17 +49,17 @@ module Puma
46
49
  GC.start ; 200
47
50
 
48
51
  when 'gc-stats'
49
- Puma::JSON.generate GC.stat
52
+ Puma::JSONSerialization.generate GC.stat
50
53
 
51
54
  when 'stats'
52
- Puma::JSON.generate @launcher.stats
55
+ Puma::JSONSerialization.generate @launcher.stats
53
56
 
54
57
  when 'thread-backtraces'
55
58
  backtraces = []
56
59
  @launcher.thread_status do |name, backtrace|
57
60
  backtraces << { name: name, backtrace: backtrace }
58
61
  end
59
- Puma::JSON.generate backtraces
62
+ Puma::JSONSerialization.generate backtraces
60
63
 
61
64
  else
62
65
  return rack_response(404, "Unsupported action", 'text/plain')
data/lib/puma/binder.rb CHANGED
@@ -30,6 +30,7 @@ module Puma
30
30
 
31
31
  def initialize(events, conf = Configuration.new)
32
32
  @events = events
33
+ @conf = conf
33
34
  @listeners = []
34
35
  @inherited_fds = {}
35
36
  @activated_sockets = {}
@@ -41,6 +42,7 @@ module Puma
41
42
  "rack.multithread".freeze => conf.options[:max_threads] > 1,
42
43
  "rack.multiprocess".freeze => conf.options[:workers] >= 1,
43
44
  "rack.run_once".freeze => false,
45
+ RACK_URL_SCHEME => conf.options[:rack_url_scheme],
44
46
  "SCRIPT_NAME".freeze => ENV['SCRIPT_NAME'] || "",
45
47
 
46
48
  # I'd like to set a default CONTENT_TYPE here but some things
@@ -56,6 +58,7 @@ module Puma
56
58
 
57
59
  @envs = {}
58
60
  @ios = []
61
+ localhost_authority
59
62
  end
60
63
 
61
64
  attr_reader :ios
@@ -95,6 +98,7 @@ module Puma
95
98
  # @version 5.0.0
96
99
  #
97
100
  def create_activated_fds(env_hash)
101
+ @events.debug "ENV['LISTEN_FDS'] #{ENV['LISTEN_FDS'].inspect} env_hash['LISTEN_PID'] #{env_hash['LISTEN_PID'].inspect}"
98
102
  return [] unless env_hash['LISTEN_FDS'] && env_hash['LISTEN_PID'].to_i == $$
99
103
  env_hash['LISTEN_FDS'].to_i.times do |index|
100
104
  sock = TCPServer.for_fd(socket_activation_fd(index))
@@ -164,9 +168,9 @@ module Puma
164
168
  params = Util.parse_query uri.query
165
169
 
166
170
  opt = params.key?('low_latency') && params['low_latency'] != 'false'
167
- bak = params.fetch('backlog', 1024).to_i
171
+ backlog = params.fetch('backlog', 1024).to_i
168
172
 
169
- io = add_tcp_listener uri.host, uri.port, opt, bak
173
+ io = add_tcp_listener uri.host, uri.port, opt, backlog
170
174
 
171
175
  @ios[ios_len..-1].each do |i|
172
176
  addr = loc_addr_str i
@@ -185,10 +189,11 @@ module Puma
185
189
  end
186
190
 
187
191
  if fd = @inherited_fds.delete(str)
188
- @unix_paths << path unless abstract
192
+ @unix_paths << path unless abstract || File.exist?(path)
189
193
  io = inherit_unix_listener path, fd
190
194
  logger.log "* Inherited #{str}"
191
- elsif sock = @activated_sockets.delete([ :unix, path ])
195
+ elsif sock = @activated_sockets.delete([ :unix, path ]) ||
196
+ @activated_sockets.delete([ :unix, File.realdirpath(path) ])
192
197
  @unix_paths << path unless abstract || File.exist?(path)
193
198
  io = inherit_unix_listener path, sock
194
199
  logger.log "* Activated #{str}"
@@ -224,7 +229,25 @@ module Puma
224
229
  raise "Puma compiled without SSL support" unless HAS_SSL
225
230
 
226
231
  params = Util.parse_query uri.query
227
- ctx = MiniSSL::ContextBuilder.new(params, @events).context
232
+
233
+ # If key and certs are not defined and localhost gem is required.
234
+ # localhost gem will be used for self signed
235
+ # Load localhost authority if not loaded.
236
+ if params.values_at('cert', 'key').all? { |v| v.to_s.empty? }
237
+ ctx = localhost_authority && localhost_authority_context
238
+ end
239
+
240
+ ctx ||=
241
+ begin
242
+ # Extract cert_pem and key_pem from options[:store] if present
243
+ ['cert', 'key'].each do |v|
244
+ if params[v] && params[v].start_with?('store:')
245
+ index = Integer(params.delete(v).split('store:').last)
246
+ params["#{v}_pem"] = @conf.options[:store][index]
247
+ end
248
+ end
249
+ MiniSSL::ContextBuilder.new(params, @events).context
250
+ end
228
251
 
229
252
  if fd = @inherited_fds.delete(str)
230
253
  logger.log "* Inherited #{str}"
@@ -234,7 +257,8 @@ module Puma
234
257
  logger.log "* Activated #{str}"
235
258
  else
236
259
  ios_len = @ios.length
237
- io = add_ssl_listener uri.host, uri.port, ctx
260
+ backlog = params.fetch('backlog', 1024).to_i
261
+ io = add_ssl_listener uri.host, uri.port, ctx, optimize_for_latency = true, backlog
238
262
 
239
263
  @ios[ios_len..-1].each do |i|
240
264
  addr = loc_addr_str i
@@ -282,6 +306,22 @@ module Puma
282
306
  end
283
307
  end
284
308
 
309
+ def localhost_authority
310
+ @localhost_authority ||= Localhost::Authority.fetch if defined?(Localhost::Authority) && !Puma::IS_JRUBY
311
+ end
312
+
313
+ def localhost_authority_context
314
+ return unless localhost_authority
315
+
316
+ key_path, crt_path = if [:key_path, :certificate_path].all? { |m| localhost_authority.respond_to?(m) }
317
+ [localhost_authority.key_path, localhost_authority.certificate_path]
318
+ else
319
+ local_certificates_path = File.expand_path("~/.localhost")
320
+ [File.join(local_certificates_path, "localhost.key"), File.join(local_certificates_path, "localhost.crt")]
321
+ end
322
+ MiniSSL::ContextBuilder.new({ "key" => key_path, "cert" => crt_path }, @events).context
323
+ end
324
+
285
325
  # Tell the server to listen on host +host+, port +port+.
286
326
  # If +optimize_for_latency+ is true (the default) then clients connecting
287
327
  # will be optimized for latency over throughput.
@@ -299,6 +339,7 @@ module Puma
299
339
 
300
340
  host = host[1..-2] if host and host[0..0] == '['
301
341
  tcp_server = TCPServer.new(host, port)
342
+
302
343
  if optimize_for_latency
303
344
  tcp_server.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
304
345
  end
@@ -320,6 +361,8 @@ module Puma
320
361
  optimize_for_latency=true, backlog=1024)
321
362
 
322
363
  raise "Puma compiled without SSL support" unless HAS_SSL
364
+ # Puma will try to use local authority context if context is supplied nil
365
+ ctx ||= localhost_authority_context
323
366
 
324
367
  if host == "localhost"
325
368
  loopback_addresses.each do |addr|
@@ -347,6 +390,8 @@ module Puma
347
390
 
348
391
  def inherit_ssl_listener(fd, ctx)
349
392
  raise "Puma compiled without SSL support" unless HAS_SSL
393
+ # Puma will try to use local authority context if context is supplied nil
394
+ ctx ||= localhost_authority_context
350
395
 
351
396
  s = fd.kind_of?(::TCPServer) ? fd : ::TCPServer.for_fd(fd)
352
397
 
data/lib/puma/cli.rb CHANGED
@@ -11,16 +11,17 @@ require 'puma/events'
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
+ # @deprecated 6.0.0
24
25
  KEYS_NOT_TO_PERSIST_IN_STATE = Launcher::KEYS_NOT_TO_PERSIST_IN_STATE
25
26
 
26
27
  # Create a new CLI object using +argv+ as the command line
@@ -112,6 +113,11 @@ module Puma
112
113
  file_config.load arg
113
114
  end
114
115
 
116
+ # Identical to supplying --config "-", but more semantic
117
+ o.on "--no-config", "Prevent Puma from searching for a config file" do |arg|
118
+ file_config.load "-"
119
+ end
120
+
115
121
  o.on "--control-url URL", "The bind url to use for the control server. Use 'auto' to use temp unix server" do |arg|
116
122
  configure_control_url(arg)
117
123
  end
@@ -179,6 +185,10 @@ module Puma
179
185
  user_config.restart_command cmd
180
186
  end
181
187
 
188
+ o.on "-s", "--silent", "Do not log prompt messages other than errors" do
189
+ @events = Events.new NullIO.new, $stderr
190
+ end
191
+
182
192
  o.on "-S", "--state PATH", "Where to store the state details" do |arg|
183
193
  user_config.state_path arg
184
194
  end