puma 6.0.0 → 6.6.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.
Files changed (85) hide show
  1. checksums.yaml +4 -4
  2. data/History.md +392 -13
  3. data/LICENSE +0 -0
  4. data/README.md +135 -29
  5. data/bin/puma-wild +0 -0
  6. data/docs/architecture.md +0 -0
  7. data/docs/compile_options.md +0 -0
  8. data/docs/deployment.md +0 -0
  9. data/docs/fork_worker.md +11 -1
  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/java_options.md +54 -0
  14. data/docs/jungle/README.md +0 -0
  15. data/docs/jungle/rc.d/README.md +0 -0
  16. data/docs/jungle/rc.d/puma.conf +0 -0
  17. data/docs/kubernetes.md +12 -0
  18. data/docs/nginx.md +1 -1
  19. data/docs/plugins.md +4 -0
  20. data/docs/rails_dev_mode.md +0 -0
  21. data/docs/restart.md +1 -0
  22. data/docs/signals.md +2 -2
  23. data/docs/stats.md +8 -3
  24. data/docs/systemd.md +13 -7
  25. data/docs/testing_benchmarks_local_files.md +0 -0
  26. data/docs/testing_test_rackup_ci_files.md +0 -0
  27. data/ext/puma_http11/PumaHttp11Service.java +0 -0
  28. data/ext/puma_http11/ext_help.h +0 -0
  29. data/ext/puma_http11/extconf.rb +21 -14
  30. data/ext/puma_http11/http11_parser.c +0 -0
  31. data/ext/puma_http11/http11_parser.h +0 -0
  32. data/ext/puma_http11/http11_parser.java.rl +0 -0
  33. data/ext/puma_http11/http11_parser.rl +0 -0
  34. data/ext/puma_http11/http11_parser_common.rl +0 -0
  35. data/ext/puma_http11/mini_ssl.c +107 -10
  36. data/ext/puma_http11/no_ssl/PumaHttp11Service.java +0 -0
  37. data/ext/puma_http11/org/jruby/puma/Http11.java +30 -7
  38. data/ext/puma_http11/org/jruby/puma/Http11Parser.java +0 -0
  39. data/ext/puma_http11/org/jruby/puma/MiniSSL.java +2 -1
  40. data/ext/puma_http11/puma_http11.c +4 -1
  41. data/lib/puma/app/status.rb +1 -1
  42. data/lib/puma/binder.rb +26 -15
  43. data/lib/puma/cli.rb +13 -5
  44. data/lib/puma/client.rb +113 -26
  45. data/lib/puma/cluster/worker.rb +14 -6
  46. data/lib/puma/cluster/worker_handle.rb +4 -5
  47. data/lib/puma/cluster.rb +93 -22
  48. data/lib/puma/commonlogger.rb +21 -14
  49. data/lib/puma/configuration.rb +42 -22
  50. data/lib/puma/const.rb +149 -89
  51. data/lib/puma/control_cli.rb +16 -9
  52. data/lib/puma/detect.rb +5 -4
  53. data/lib/puma/dsl.rb +432 -40
  54. data/lib/puma/error_logger.rb +6 -5
  55. data/lib/puma/events.rb +0 -0
  56. data/lib/puma/io_buffer.rb +10 -0
  57. data/lib/puma/jruby_restart.rb +0 -16
  58. data/lib/puma/json_serialization.rb +0 -0
  59. data/lib/puma/launcher/bundle_pruner.rb +0 -0
  60. data/lib/puma/launcher.rb +29 -29
  61. data/lib/puma/log_writer.rb +23 -13
  62. data/lib/puma/minissl/context_builder.rb +4 -0
  63. data/lib/puma/minissl.rb +23 -0
  64. data/lib/puma/null_io.rb +42 -2
  65. data/lib/puma/plugin/systemd.rb +90 -0
  66. data/lib/puma/plugin/tmp_restart.rb +0 -0
  67. data/lib/puma/plugin.rb +0 -0
  68. data/lib/puma/rack/builder.rb +2 -2
  69. data/lib/puma/rack/urlmap.rb +1 -1
  70. data/lib/puma/rack_default.rb +18 -3
  71. data/lib/puma/reactor.rb +17 -8
  72. data/lib/puma/request.rb +207 -126
  73. data/lib/puma/runner.rb +26 -4
  74. data/lib/puma/sd_notify.rb +146 -0
  75. data/lib/puma/server.rb +121 -49
  76. data/lib/puma/single.rb +3 -1
  77. data/lib/puma/state_file.rb +2 -2
  78. data/lib/puma/thread_pool.rb +56 -9
  79. data/lib/puma/util.rb +1 -1
  80. data/lib/puma.rb +1 -3
  81. data/lib/rack/handler/puma.rb +116 -86
  82. data/tools/Dockerfile +2 -2
  83. data/tools/trickletest.rb +0 -0
  84. metadata +12 -13
  85. data/lib/puma/systemd.rb +0 -47
@@ -36,6 +36,12 @@ void raise_file_error(const char* caller, const char *filename) {
36
36
  rb_raise(eError, "%s: error in file '%s': %s", caller, filename, ERR_error_string(ERR_get_error(), NULL));
37
37
  }
38
38
 
39
+ NORETURN(void raise_param_error(const char* caller, const char *param));
40
+
41
+ void raise_param_error(const char* caller, const char *param) {
42
+ rb_raise(eError, "%s: error with parameter '%s': %s", caller, param, ERR_error_string(ERR_get_error(), NULL));
43
+ }
44
+
39
45
  void engine_free(void *ptr) {
40
46
  ms_conn *conn = ptr;
41
47
  ms_cert_buf* cert_buf = (ms_cert_buf*)SSL_get_app_data(conn->ssl);
@@ -185,6 +191,18 @@ static int engine_verify_callback(int preverify_ok, X509_STORE_CTX* ctx) {
185
191
  return preverify_ok;
186
192
  }
187
193
 
194
+ static int password_callback(char *buf, int size, int rwflag, void *userdata) {
195
+ const char *password = (const char *) userdata;
196
+ size_t len = strlen(password);
197
+
198
+ if (len > (size_t) size) {
199
+ return 0;
200
+ }
201
+
202
+ memcpy(buf, password, len);
203
+ return (int) len;
204
+ }
205
+
188
206
  static VALUE
189
207
  sslctx_alloc(VALUE klass) {
190
208
  SSL_CTX *ctx;
@@ -211,11 +229,13 @@ VALUE
211
229
  sslctx_initialize(VALUE self, VALUE mini_ssl_ctx) {
212
230
  SSL_CTX* ctx;
213
231
  int ssl_options;
214
- VALUE key, cert, ca, verify_mode, ssl_cipher_filter, no_tlsv1, no_tlsv1_1,
215
- verification_flags, session_id_bytes, cert_pem, key_pem;
232
+ VALUE key, cert, ca, verify_mode, ssl_cipher_filter, ssl_ciphersuites, no_tlsv1, no_tlsv1_1,
233
+ verification_flags, session_id_bytes, cert_pem, key_pem, key_password_command, key_password;
216
234
  BIO *bio;
217
- X509 *x509;
235
+ X509 *x509 = NULL;
218
236
  EVP_PKEY *pkey;
237
+ pem_password_cb *password_cb = NULL;
238
+ const char *password = NULL;
219
239
  #ifdef HAVE_SSL_CTX_SET_MIN_PROTO_VERSION
220
240
  int min;
221
241
  #endif
@@ -235,6 +255,8 @@ sslctx_initialize(VALUE self, VALUE mini_ssl_ctx) {
235
255
 
236
256
  key = rb_funcall(mini_ssl_ctx, rb_intern_const("key"), 0);
237
257
 
258
+ key_password_command = rb_funcall(mini_ssl_ctx, rb_intern_const("key_password_command"), 0);
259
+
238
260
  cert = rb_funcall(mini_ssl_ctx, rb_intern_const("cert"), 0);
239
261
 
240
262
  ca = rb_funcall(mini_ssl_ctx, rb_intern_const("ca"), 0);
@@ -247,6 +269,8 @@ sslctx_initialize(VALUE self, VALUE mini_ssl_ctx) {
247
269
 
248
270
  ssl_cipher_filter = rb_funcall(mini_ssl_ctx, rb_intern_const("ssl_cipher_filter"), 0);
249
271
 
272
+ ssl_ciphersuites = rb_funcall(mini_ssl_ctx, rb_intern_const("ssl_ciphersuites"), 0);
273
+
250
274
  no_tlsv1 = rb_funcall(mini_ssl_ctx, rb_intern_const("no_tlsv1"), 0);
251
275
 
252
276
  no_tlsv1_1 = rb_funcall(mini_ssl_ctx, rb_intern_const("no_tlsv1_1"), 0);
@@ -261,6 +285,18 @@ sslctx_initialize(VALUE self, VALUE mini_ssl_ctx) {
261
285
  }
262
286
  }
263
287
 
288
+ if (!NIL_P(key_password_command)) {
289
+ key_password = rb_funcall(mini_ssl_ctx, rb_intern_const("key_password"), 0);
290
+
291
+ if (!NIL_P(key_password)) {
292
+ StringValue(key_password);
293
+ password_cb = password_callback;
294
+ password = RSTRING_PTR(key_password);
295
+ SSL_CTX_set_default_passwd_cb(ctx, password_cb);
296
+ SSL_CTX_set_default_passwd_cb_userdata(ctx, (void *) password);
297
+ }
298
+ }
299
+
264
300
  if (!NIL_P(key)) {
265
301
  StringValue(key);
266
302
 
@@ -270,22 +306,71 @@ sslctx_initialize(VALUE self, VALUE mini_ssl_ctx) {
270
306
  }
271
307
 
272
308
  if (!NIL_P(cert_pem)) {
309
+ X509 *ca = NULL;
310
+ unsigned long err;
311
+
273
312
  bio = BIO_new(BIO_s_mem());
274
313
  BIO_puts(bio, RSTRING_PTR(cert_pem));
314
+
315
+ /**
316
+ * Much of this pulled as a simplified version of the `use_certificate_chain_file` method
317
+ * from openssl's `ssl_rsa.c` file.
318
+ */
319
+
320
+ /* first read the cert as the first item in the pem file */
275
321
  x509 = PEM_read_bio_X509(bio, NULL, NULL, NULL);
322
+ if (NULL == x509) {
323
+ BIO_free_all(bio);
324
+ raise_param_error("PEM_read_bio_X509", "cert_pem");
325
+ }
276
326
 
277
- if (SSL_CTX_use_certificate(ctx, x509) != 1) {
278
- BIO_free(bio);
279
- raise_file_error("SSL_CTX_use_certificate", RSTRING_PTR(cert_pem));
327
+ /* Add the cert to the context */
328
+ /* 1 is success - otherwise check the error codes */
329
+ if (1 != SSL_CTX_use_certificate(ctx, x509)) {
330
+ BIO_free_all(bio);
331
+ raise_param_error("SSL_CTX_use_certificate", "cert_pem");
332
+ }
333
+
334
+ X509_free(x509); /* no longer need our reference */
335
+
336
+ /* Now lets load up the rest of the certificate chain */
337
+ /* 1 is success 0 is error */
338
+ if (0 == SSL_CTX_clear_chain_certs(ctx)) {
339
+ BIO_free_all(bio);
340
+ raise_param_error("SSL_CTX_clear_chain_certs","cert_pem");
341
+ }
342
+
343
+ while (1) {
344
+ ca = PEM_read_bio_X509(bio, NULL, NULL, NULL);
345
+
346
+ if (NULL == ca) {
347
+ break;
348
+ }
349
+
350
+ if (0 == SSL_CTX_add0_chain_cert(ctx, ca)) {
351
+ BIO_free_all(bio);
352
+ raise_param_error("SSL_CTX_add0_chain_cert","cert_pem");
353
+ }
354
+ /* don't free ca - its now owned by the context */
355
+ }
356
+
357
+ /* ca is NULL - so its either the end of the file or an error */
358
+ err = ERR_peek_last_error();
359
+
360
+ /* If its the end of the file - then we are done, in any case free the bio */
361
+ BIO_free_all(bio);
362
+
363
+ if ((ERR_GET_LIB(err) == ERR_LIB_PEM) && (ERR_GET_REASON(err) == PEM_R_NO_START_LINE)) {
364
+ ERR_clear_error();
365
+ } else {
366
+ raise_param_error("PEM_read_bio_X509","cert_pem");
280
367
  }
281
- X509_free(x509);
282
- BIO_free(bio);
283
368
  }
284
369
 
285
370
  if (!NIL_P(key_pem)) {
286
371
  bio = BIO_new(BIO_s_mem());
287
372
  BIO_puts(bio, RSTRING_PTR(key_pem));
288
- pkey = PEM_read_bio_PrivateKey(bio, NULL, NULL, NULL);
373
+ pkey = PEM_read_bio_PrivateKey(bio, NULL, password_cb, (void *) password);
289
374
 
290
375
  if (SSL_CTX_use_PrivateKey(ctx, pkey) != 1) {
291
376
  BIO_free(bio);
@@ -361,6 +446,14 @@ sslctx_initialize(VALUE self, VALUE mini_ssl_ctx) {
361
446
  SSL_CTX_set_cipher_list(ctx, "HIGH:!aNULL@STRENGTH");
362
447
  }
363
448
 
449
+ #if HAVE_SSL_CTX_SET_CIPHERSUITES
450
+ // Only override OpenSSL default ciphersuites if config option is supplied.
451
+ if (!NIL_P(ssl_ciphersuites)) {
452
+ StringValue(ssl_ciphersuites);
453
+ SSL_CTX_set_ciphersuites(ctx, RSTRING_PTR(ssl_ciphersuites));
454
+ }
455
+ #endif
456
+
364
457
  #if OPENSSL_VERSION_NUMBER < 0x10002000L
365
458
  // Remove this case if OpenSSL 1.0.1 (now EOL) support is no longer needed.
366
459
  ecdh = EC_KEY_new_by_curve_name(NID_X9_62_prime256v1);
@@ -463,7 +556,7 @@ NORETURN(void raise_error(SSL* ssl, int result));
463
556
 
464
557
  void raise_error(SSL* ssl, int result) {
465
558
  char buf[512];
466
- char msg[512];
559
+ char msg[768];
467
560
  const char* err_str;
468
561
  int err = errno;
469
562
  int mask = 4095;
@@ -721,6 +814,10 @@ void Init_mini_ssl(VALUE puma) {
721
814
 
722
815
  rb_define_method(eng, "init?", engine_init, 0);
723
816
 
817
+ /* @!attribute [r] peercert
818
+ * Returns `nil` when `MiniSSL::Context#verify_mode` is set to `VERIFY_NONE`.
819
+ * @return [String, nil] DER encoded cert
820
+ */
724
821
  rb_define_method(eng, "peercert", engine_peercert, 0);
725
822
 
726
823
  rb_define_method(eng, "ssl_vers_st", engine_ssl_vers_st, 0);
File without changes
@@ -26,14 +26,14 @@ public class Http11 extends RubyObject {
26
26
  public final static String MAX_FIELD_NAME_LENGTH_ERR = "HTTP element FIELD_NAME is longer than the 256 allowed length.";
27
27
  public final static int MAX_FIELD_VALUE_LENGTH = 80 * 1024;
28
28
  public final static String MAX_FIELD_VALUE_LENGTH_ERR = "HTTP element FIELD_VALUE is longer than the 81920 allowed length.";
29
- public final static int MAX_REQUEST_URI_LENGTH = 1024 * 12;
30
- public final static String MAX_REQUEST_URI_LENGTH_ERR = "HTTP element REQUEST_URI is longer than the 12288 allowed length.";
29
+ public final static int MAX_REQUEST_URI_LENGTH = getConstLength("PUMA_REQUEST_URI_MAX_LENGTH", 1024 * 12);
30
+ public final static String MAX_REQUEST_URI_LENGTH_ERR = "HTTP element REQUEST_URI is longer than the " + MAX_REQUEST_URI_LENGTH + " allowed length.";
31
31
  public final static int MAX_FRAGMENT_LENGTH = 1024;
32
32
  public final static String MAX_FRAGMENT_LENGTH_ERR = "HTTP element REQUEST_PATH is longer than the 1024 allowed length.";
33
- public final static int MAX_REQUEST_PATH_LENGTH = 8192;
34
- public final static String MAX_REQUEST_PATH_LENGTH_ERR = "HTTP element REQUEST_PATH is longer than the 8192 allowed length.";
35
- public final static int MAX_QUERY_STRING_LENGTH = 1024 * 10;
36
- public final static String MAX_QUERY_STRING_LENGTH_ERR = "HTTP element QUERY_STRING is longer than the 10240 allowed length.";
33
+ public final static int MAX_REQUEST_PATH_LENGTH = getConstLength("PUMA_REQUEST_PATH_MAX_LENGTH", 8192);
34
+ public final static String MAX_REQUEST_PATH_LENGTH_ERR = "HTTP element REQUEST_PATH is longer than the " + MAX_REQUEST_PATH_LENGTH + " allowed length.";
35
+ public final static int MAX_QUERY_STRING_LENGTH = getConstLength("PUMA_QUERY_STRING_MAX_LENGTH", 10 * 1024);
36
+ public final static String MAX_QUERY_STRING_LENGTH_ERR = "HTTP element QUERY_STRING is longer than the " + MAX_QUERY_STRING_LENGTH +" allowed length.";
37
37
  public final static int MAX_HEADER_LENGTH = 1024 * (80 + 32);
38
38
  public final static String MAX_HEADER_LENGTH_ERR = "HTTP element HEADER is longer than the 114688 allowed length.";
39
39
 
@@ -48,6 +48,27 @@ public class Http11 extends RubyObject {
48
48
  public static final ByteList QUERY_STRING_BYTELIST = new ByteList(ByteList.plain("QUERY_STRING"));
49
49
  public static final ByteList SERVER_PROTOCOL_BYTELIST = new ByteList(ByteList.plain("SERVER_PROTOCOL"));
50
50
 
51
+ public static String getEnvOrProperty(String name) {
52
+ String envValue = System.getenv(name);
53
+ return (envValue != null) ? envValue : System.getProperty(name);
54
+ }
55
+
56
+ public static int getConstLength(String name, Integer defaultValue) {
57
+ String stringValue = getEnvOrProperty(name);
58
+ if (stringValue == null || stringValue.isEmpty()) return defaultValue;
59
+
60
+ try {
61
+ int value = Integer.parseUnsignedInt(stringValue);
62
+ if (value <= 0) {
63
+ throw new NumberFormatException("The number is not positive.");
64
+ }
65
+ return value;
66
+ } catch (NumberFormatException e) {
67
+ System.err.println(String.format("The value %s for %s is invalid. Using default value %d instead.", stringValue, name, defaultValue));
68
+ return defaultValue;
69
+ }
70
+ }
71
+
51
72
  private static ObjectAllocator ALLOCATOR = new ObjectAllocator() {
52
73
  public IRubyObject allocate(Ruby runtime, RubyClass klass) {
53
74
  return new Http11(runtime, klass);
@@ -56,7 +77,7 @@ public class Http11 extends RubyObject {
56
77
 
57
78
  public static void createHttp11(Ruby runtime) {
58
79
  RubyModule mPuma = runtime.defineModule("Puma");
59
- mPuma.defineClassUnder("HttpParserError",runtime.getClass("IOError"),runtime.getClass("IOError").getAllocator());
80
+ mPuma.defineClassUnder("HttpParserError",runtime.getClass("StandardError"),runtime.getClass("StandardError").getAllocator());
60
81
 
61
82
  RubyClass cHttpParser = mPuma.defineClassUnder("HttpParser",runtime.getObject(),ALLOCATOR);
62
83
  cHttpParser.defineAnnotatedMethods(Http11.class);
@@ -99,6 +120,8 @@ public class Http11 extends RubyObject {
99
120
  int bite = b.get(i) & 0xFF;
100
121
  if(bite == '-') {
101
122
  b.set(i, (byte)'_');
123
+ } else if(bite == '_') {
124
+ b.set(i, (byte)',');
102
125
  } else {
103
126
  b.set(i, (byte)Character.toUpperCase(bite));
104
127
  }
File without changes
@@ -47,6 +47,7 @@ import static javax.net.ssl.SSLEngineResult.Status;
47
47
  import static javax.net.ssl.SSLEngineResult.HandshakeStatus;
48
48
 
49
49
  public class MiniSSL extends RubyObject { // MiniSSL::Engine
50
+ private static final long serialVersionUID = -6903439483039141234L;
50
51
  private static ObjectAllocator ALLOCATOR = new ObjectAllocator() {
51
52
  public IRubyObject allocate(Ruby runtime, RubyClass klass) {
52
53
  return new MiniSSL(runtime, klass);
@@ -500,7 +501,7 @@ public class MiniSSL extends RubyObject { // MiniSSL::Engine
500
501
  }
501
502
 
502
503
  private static RaiseException newError(Ruby runtime, RubyClass errorClass, String message, Throwable cause) {
503
- RaiseException ex = new RaiseException(runtime, errorClass, message, true);
504
+ RaiseException ex = RaiseException.from(runtime, errorClass, message);
504
505
  ex.initCause(cause);
505
506
  return ex;
506
507
  }
@@ -461,6 +461,9 @@ void Init_mini_ssl(VALUE mod);
461
461
 
462
462
  void Init_puma_http11(void)
463
463
  {
464
+ #ifdef HAVE_RB_EXT_RACTOR_SAFE
465
+ rb_ext_ractor_safe(true);
466
+ #endif
464
467
 
465
468
  VALUE mPuma = rb_define_module("Puma");
466
469
  VALUE cHttpParser = rb_define_class_under(mPuma, "HttpParser", rb_cObject);
@@ -472,7 +475,7 @@ void Init_puma_http11(void)
472
475
  DEF_GLOBAL(server_protocol, "SERVER_PROTOCOL");
473
476
  DEF_GLOBAL(request_path, "REQUEST_PATH");
474
477
 
475
- eHttpParserError = rb_define_class_under(mPuma, "HttpParserError", rb_eIOError);
478
+ eHttpParserError = rb_define_class_under(mPuma, "HttpParserError", rb_eStandardError);
476
479
  rb_global_variable(&eHttpParserError);
477
480
 
478
481
  rb_define_alloc_func(cHttpParser, HttpParser_alloc);
@@ -80,7 +80,7 @@ module Puma
80
80
 
81
81
  def authenticate(env)
82
82
  return true unless @auth_token
83
- env['QUERY_STRING'].to_s.split(/&;/).include?("token=#{@auth_token}")
83
+ env['QUERY_STRING'].to_s.split(/[&;]/).include? "token=#{@auth_token}"
84
84
  end
85
85
 
86
86
  def rack_response(status, body, content_type='application/json')
data/lib/puma/binder.rb CHANGED
@@ -19,13 +19,14 @@ module Puma
19
19
 
20
20
  RACK_VERSION = [1,6].freeze
21
21
 
22
- def initialize(log_writer, conf = Configuration.new)
22
+ def initialize(log_writer, conf = Configuration.new, env: ENV)
23
23
  @log_writer = log_writer
24
24
  @conf = conf
25
25
  @listeners = []
26
26
  @inherited_fds = {}
27
27
  @activated_sockets = {}
28
28
  @unix_paths = []
29
+ @env = env
29
30
 
30
31
  @proto_env = {
31
32
  "rack.version".freeze => RACK_VERSION,
@@ -34,7 +35,7 @@ module Puma
34
35
  "rack.multiprocess".freeze => conf.options[:workers] >= 1,
35
36
  "rack.run_once".freeze => false,
36
37
  RACK_URL_SCHEME => conf.options[:rack_url_scheme],
37
- "SCRIPT_NAME".freeze => ENV['SCRIPT_NAME'] || "",
38
+ "SCRIPT_NAME".freeze => env['SCRIPT_NAME'] || "",
38
39
 
39
40
  # I'd like to set a default CONTENT_TYPE here but some things
40
41
  # depend on their not being a default set and inferring
@@ -48,7 +49,6 @@ module Puma
48
49
 
49
50
  @envs = {}
50
51
  @ios = []
51
- localhost_authority
52
52
  end
53
53
 
54
54
  attr_reader :ios
@@ -88,7 +88,7 @@ module Puma
88
88
  # @version 5.0.0
89
89
  #
90
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}"
91
+ @log_writer.debug "ENV['LISTEN_FDS'] #{@env['LISTEN_FDS'].inspect} env_hash['LISTEN_PID'] #{env_hash['LISTEN_PID'].inspect}"
92
92
  return [] unless env_hash['LISTEN_FDS'] && env_hash['LISTEN_PID'].to_i == $$
93
93
  env_hash['LISTEN_FDS'].to_i.times do |index|
94
94
  sock = TCPServer.for_fd(socket_activation_fd(index))
@@ -142,7 +142,14 @@ module Puma
142
142
  end
143
143
  end
144
144
 
145
+ def before_parse(&block)
146
+ @before_parse ||= []
147
+ @before_parse << block if block
148
+ @before_parse
149
+ end
150
+
145
151
  def parse(binds, log_writer = nil, log_msg = 'Listening')
152
+ before_parse.each(&:call)
146
153
  log_writer ||= @log_writer
147
154
  binds.each do |str|
148
155
  uri = URI.parse str
@@ -158,10 +165,10 @@ module Puma
158
165
  ios_len = @ios.length
159
166
  params = Util.parse_query uri.query
160
167
 
161
- opt = params.key?('low_latency') && params['low_latency'] != 'false'
168
+ low_latency = params.key?('low_latency') && params['low_latency'] != 'false'
162
169
  backlog = params.fetch('backlog', 1024).to_i
163
170
 
164
- io = add_tcp_listener uri.host, uri.port, opt, backlog
171
+ io = add_tcp_listener uri.host, uri.port, low_latency, backlog
165
172
 
166
173
  @ios[ios_len..-1].each do |i|
167
174
  addr = loc_addr_str i
@@ -184,7 +191,7 @@ module Puma
184
191
  io = inherit_unix_listener path, fd
185
192
  log_writer.log "* Inherited #{str}"
186
193
  elsif sock = @activated_sockets.delete([ :unix, path ]) ||
187
- @activated_sockets.delete([ :unix, File.realdirpath(path) ])
194
+ !abstract && @activated_sockets.delete([ :unix, File.realdirpath(path) ])
188
195
  @unix_paths << path unless abstract || File.exist?(path)
189
196
  io = inherit_unix_listener path, sock
190
197
  log_writer.log "* Activated #{str}"
@@ -251,7 +258,8 @@ module Puma
251
258
  else
252
259
  ios_len = @ios.length
253
260
  backlog = params.fetch('backlog', 1024).to_i
254
- io = add_ssl_listener uri.host, uri.port, ctx, optimize_for_latency = true, backlog
261
+ low_latency = params['low_latency'] != 'false'
262
+ io = add_ssl_listener uri.host, uri.port, ctx, low_latency, backlog
255
263
 
256
264
  @ios[ios_len..-1].each do |i|
257
265
  addr = loc_addr_str i
@@ -330,7 +338,7 @@ module Puma
330
338
  return
331
339
  end
332
340
 
333
- host = host[1..-2] if host and host[0..0] == '['
341
+ host = host[1..-2] if host&.start_with? '['
334
342
  tcp_server = TCPServer.new(host, port)
335
343
 
336
344
  if optimize_for_latency
@@ -364,7 +372,7 @@ module Puma
364
372
  return
365
373
  end
366
374
 
367
- host = host[1..-2] if host[0..0] == '['
375
+ host = host[1..-2] if host&.start_with? '['
368
376
  s = TCPServer.new(host, port)
369
377
  if optimize_for_latency
370
378
  s.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
@@ -450,11 +458,14 @@ module Puma
450
458
 
451
459
  def close_listeners
452
460
  @listeners.each do |l, io|
453
- io.close unless io.closed?
454
- uri = URI.parse l
455
- next unless uri.scheme == 'unix'
456
- unix_path = "#{uri.host}#{uri.path}"
457
- File.unlink unix_path if @unix_paths.include?(unix_path) && File.exist?(unix_path)
461
+ begin
462
+ io.close unless io.closed?
463
+ uri = URI.parse l
464
+ next unless uri.scheme == 'unix'
465
+ unix_path = "#{uri.host}#{uri.path}"
466
+ File.unlink unix_path if @unix_paths.include?(unix_path) && File.exist?(unix_path)
467
+ rescue Errno::EBADF
468
+ end
458
469
  end
459
470
  end
460
471
 
data/lib/puma/cli.rb CHANGED
@@ -24,7 +24,7 @@ module Puma
24
24
  # Create a new CLI object using +argv+ as the command line
25
25
  # arguments.
26
26
  #
27
- def initialize(argv, log_writer = LogWriter.stdio, events = Events.new)
27
+ def initialize(argv, log_writer = LogWriter.stdio, events = Events.new, env: ENV)
28
28
  @debug = false
29
29
  @argv = argv.dup
30
30
  @log_writer = log_writer
@@ -39,7 +39,7 @@ module Puma
39
39
  @control_url = nil
40
40
  @control_options = {}
41
41
 
42
- setup_options
42
+ setup_options env
43
43
 
44
44
  begin
45
45
  @parser.parse! @argv
@@ -63,7 +63,7 @@ module Puma
63
63
  end
64
64
  end
65
65
 
66
- @launcher = Puma::Launcher.new(@conf, :log_writer => @log_writer, :events => @events, :argv => argv)
66
+ @launcher = Puma::Launcher.new(@conf, env: ENV, log_writer: @log_writer, events: @events, argv: argv)
67
67
  end
68
68
 
69
69
  attr_reader :launcher
@@ -92,8 +92,8 @@ module Puma
92
92
  # Build the OptionParser object to handle the available options.
93
93
  #
94
94
 
95
- def setup_options
96
- @conf = Configuration.new do |user_config, file_config|
95
+ def setup_options(env = ENV)
96
+ @conf = Configuration.new({}, {events: @events}, env) do |user_config, file_config|
97
97
  @parser = OptionParser.new do |o|
98
98
  o.on "-b", "--bind URI", "URI to bind to (tcp://, unix://, ssl://)" do |arg|
99
99
  user_config.bind arg
@@ -144,6 +144,10 @@ module Puma
144
144
  $LOAD_PATH.unshift(*arg.split(':'))
145
145
  end
146
146
 
147
+ o.on "--idle-timeout SECONDS", "Number of seconds until the next request before automatic shutdown" do |arg|
148
+ user_config.idle_timeout arg
149
+ end
150
+
147
151
  o.on "-p", "--port PORT", "Define the TCP port to bind to",
148
152
  "Use -b for more advanced options" do |arg|
149
153
  user_config.bind "tcp://#{Configuration::DEFAULTS[:tcp_host]}:#{arg}"
@@ -153,6 +157,10 @@ module Puma
153
157
  user_config.pidfile arg
154
158
  end
155
159
 
160
+ o.on "--plugin PLUGIN", "Load the given PLUGIN. Can be used multiple times to load multiple plugins." do |arg|
161
+ user_config.plugin arg
162
+ end
163
+
156
164
  o.on "--preload", "Preload the app. Cluster mode only" do
157
165
  user_config.preload_app!
158
166
  end