puma 5.0.0-java → 5.1.0-java

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 (50) hide show
  1. checksums.yaml +4 -4
  2. data/History.md +1190 -574
  3. data/README.md +28 -20
  4. data/bin/puma-wild +3 -9
  5. data/docs/compile_options.md +19 -0
  6. data/docs/deployment.md +5 -6
  7. data/docs/fork_worker.md +2 -0
  8. data/docs/jungle/README.md +0 -4
  9. data/docs/jungle/rc.d/puma +2 -2
  10. data/docs/nginx.md +1 -1
  11. data/docs/restart.md +46 -23
  12. data/docs/systemd.md +25 -3
  13. data/ext/puma_http11/ext_help.h +1 -1
  14. data/ext/puma_http11/extconf.rb +4 -5
  15. data/ext/puma_http11/http11_parser.c +64 -64
  16. data/ext/puma_http11/mini_ssl.c +39 -37
  17. data/ext/puma_http11/puma_http11.c +25 -12
  18. data/lib/puma.rb +7 -4
  19. data/lib/puma/app/status.rb +44 -46
  20. data/lib/puma/binder.rb +48 -1
  21. data/lib/puma/cli.rb +4 -0
  22. data/lib/puma/client.rb +31 -80
  23. data/lib/puma/cluster.rb +39 -202
  24. data/lib/puma/cluster/worker.rb +176 -0
  25. data/lib/puma/cluster/worker_handle.rb +86 -0
  26. data/lib/puma/configuration.rb +20 -8
  27. data/lib/puma/const.rb +11 -3
  28. data/lib/puma/control_cli.rb +71 -70
  29. data/lib/puma/dsl.rb +67 -19
  30. data/lib/puma/error_logger.rb +2 -2
  31. data/lib/puma/events.rb +21 -3
  32. data/lib/puma/json.rb +96 -0
  33. data/lib/puma/launcher.rb +61 -12
  34. data/lib/puma/minissl.rb +8 -0
  35. data/lib/puma/puma_http11.jar +0 -0
  36. data/lib/puma/queue_close.rb +26 -0
  37. data/lib/puma/reactor.rb +79 -373
  38. data/lib/puma/request.rb +451 -0
  39. data/lib/puma/runner.rb +15 -21
  40. data/lib/puma/server.rb +193 -508
  41. data/lib/puma/single.rb +3 -2
  42. data/lib/puma/state_file.rb +5 -3
  43. data/lib/puma/systemd.rb +46 -0
  44. data/lib/puma/thread_pool.rb +22 -2
  45. data/lib/puma/util.rb +12 -0
  46. metadata +9 -6
  47. data/docs/jungle/upstart/README.md +0 -61
  48. data/docs/jungle/upstart/puma-manager.conf +0 -31
  49. data/docs/jungle/upstart/puma.conf +0 -69
  50. data/lib/puma/accept_nonblock.rb +0 -29
@@ -2,12 +2,7 @@
2
2
 
3
3
  #include <ruby.h>
4
4
  #include <ruby/version.h>
5
-
6
- #if RUBY_API_VERSION_MAJOR == 1
7
- #include <rubyio.h>
8
- #else
9
5
  #include <ruby/io.h>
10
- #endif
11
6
 
12
7
  #ifdef HAVE_OPENSSL_BIO_H
13
8
 
@@ -33,7 +28,8 @@ typedef struct {
33
28
  int bytes;
34
29
  } ms_cert_buf;
35
30
 
36
- void engine_free(ms_conn* conn) {
31
+ void engine_free(void *ptr) {
32
+ ms_conn *conn = ptr;
37
33
  ms_cert_buf* cert_buf = (ms_cert_buf*)SSL_get_app_data(conn->ssl);
38
34
  if(cert_buf) {
39
35
  OPENSSL_free(cert_buf->buf);
@@ -45,10 +41,16 @@ void engine_free(ms_conn* conn) {
45
41
  free(conn);
46
42
  }
47
43
 
44
+ const rb_data_type_t engine_data_type = {
45
+ "MiniSSL/ENGINE",
46
+ { 0, engine_free, 0 },
47
+ 0, 0, RUBY_TYPED_FREE_IMMEDIATELY,
48
+ };
49
+
48
50
  ms_conn* engine_alloc(VALUE klass, VALUE* obj) {
49
51
  ms_conn* conn;
50
52
 
51
- *obj = Data_Make_Struct(klass, ms_conn, 0, engine_free, conn);
53
+ *obj = TypedData_Make_Struct(klass, ms_conn, &engine_data_type, conn);
52
54
 
53
55
  conn->read = BIO_new(BIO_s_mem());
54
56
  BIO_set_nbio(conn->read, 1);
@@ -198,7 +200,7 @@ VALUE engine_init_server(VALUE self, VALUE mini_ssl_ctx) {
198
200
  else {
199
201
  min = TLS1_VERSION;
200
202
  }
201
-
203
+
202
204
  SSL_CTX_set_min_proto_version(ctx, min);
203
205
 
204
206
  SSL_CTX_set_options(ctx, ssl_options);
@@ -281,7 +283,7 @@ VALUE engine_inject(VALUE self, VALUE str) {
281
283
  ms_conn* conn;
282
284
  long used;
283
285
 
284
- Data_Get_Struct(self, ms_conn, conn);
286
+ TypedData_Get_Struct(self, ms_conn, &engine_data_type, conn);
285
287
 
286
288
  StringValue(str);
287
289
 
@@ -334,7 +336,7 @@ VALUE engine_read(VALUE self) {
334
336
  char buf[512];
335
337
  int bytes, error;
336
338
 
337
- Data_Get_Struct(self, ms_conn, conn);
339
+ TypedData_Get_Struct(self, ms_conn, &engine_data_type, conn);
338
340
 
339
341
  ERR_clear_error();
340
342
 
@@ -361,7 +363,7 @@ VALUE engine_write(VALUE self, VALUE str) {
361
363
  ms_conn* conn;
362
364
  int bytes;
363
365
 
364
- Data_Get_Struct(self, ms_conn, conn);
366
+ TypedData_Get_Struct(self, ms_conn, &engine_data_type, conn);
365
367
 
366
368
  StringValue(str);
367
369
 
@@ -385,7 +387,7 @@ VALUE engine_extract(VALUE self) {
385
387
  size_t pending;
386
388
  char buf[512];
387
389
 
388
- Data_Get_Struct(self, ms_conn, conn);
390
+ TypedData_Get_Struct(self, ms_conn, &engine_data_type, conn);
389
391
 
390
392
  pending = BIO_pending(conn->write);
391
393
  if(pending > 0) {
@@ -404,7 +406,7 @@ VALUE engine_shutdown(VALUE self) {
404
406
  ms_conn* conn;
405
407
  int ok;
406
408
 
407
- Data_Get_Struct(self, ms_conn, conn);
409
+ TypedData_Get_Struct(self, ms_conn, &engine_data_type, conn);
408
410
 
409
411
  ERR_clear_error();
410
412
 
@@ -419,7 +421,7 @@ VALUE engine_shutdown(VALUE self) {
419
421
  VALUE engine_init(VALUE self) {
420
422
  ms_conn* conn;
421
423
 
422
- Data_Get_Struct(self, ms_conn, conn);
424
+ TypedData_Get_Struct(self, ms_conn, &engine_data_type, conn);
423
425
 
424
426
  return SSL_in_init(conn->ssl) ? Qtrue : Qfalse;
425
427
  }
@@ -432,7 +434,7 @@ VALUE engine_peercert(VALUE self) {
432
434
  ms_cert_buf* cert_buf = NULL;
433
435
  VALUE rb_cert_buf;
434
436
 
435
- Data_Get_Struct(self, ms_conn, conn);
437
+ TypedData_Get_Struct(self, ms_conn, &engine_data_type, conn);
436
438
 
437
439
  cert = SSL_get_peer_certificate(conn->ssl);
438
440
  if(!cert) {
@@ -469,7 +471,7 @@ VALUE engine_peercert(VALUE self) {
469
471
  static VALUE
470
472
  engine_ssl_vers_st(VALUE self) {
471
473
  ms_conn* conn;
472
- Data_Get_Struct(self, ms_conn, conn);
474
+ TypedData_Get_Struct(self, ms_conn, &engine_data_type, conn);
473
475
  return rb_ary_new3(2, rb_str_new2(SSL_get_version(conn->ssl)), rb_str_new2(SSL_state_string(conn->ssl)));
474
476
  }
475
477
 
@@ -504,27 +506,27 @@ void Init_mini_ssl(VALUE puma) {
504
506
  #else
505
507
  rb_define_const(mod, "OPENSSL_LIBRARY_VERSION", rb_str_new2(SSLeay_version(SSLEAY_VERSION)));
506
508
  #endif
507
-
508
- #if defined(OPENSSL_NO_SSL3) || defined(OPENSSL_NO_SSL3_METHOD)
509
- /* True if SSL3 is not available */
510
- rb_define_const(mod, "OPENSSL_NO_SSL3", Qtrue);
511
- #else
512
- rb_define_const(mod, "OPENSSL_NO_SSL3", Qfalse);
513
- #endif
514
-
515
- #if defined(OPENSSL_NO_TLS1) || defined(OPENSSL_NO_TLS1_METHOD)
516
- /* True if TLS1 is not available */
517
- rb_define_const(mod, "OPENSSL_NO_TLS1", Qtrue);
518
- #else
519
- rb_define_const(mod, "OPENSSL_NO_TLS1", Qfalse);
520
- #endif
521
-
522
- #if defined(OPENSSL_NO_TLS1_1) || defined(OPENSSL_NO_TLS1_1_METHOD)
523
- /* True if TLS1_1 is not available */
524
- rb_define_const(mod, "OPENSSL_NO_TLS1_1", Qtrue);
525
- #else
526
- rb_define_const(mod, "OPENSSL_NO_TLS1_1", Qfalse);
527
- #endif
509
+
510
+ #if defined(OPENSSL_NO_SSL3) || defined(OPENSSL_NO_SSL3_METHOD)
511
+ /* True if SSL3 is not available */
512
+ rb_define_const(mod, "OPENSSL_NO_SSL3", Qtrue);
513
+ #else
514
+ rb_define_const(mod, "OPENSSL_NO_SSL3", Qfalse);
515
+ #endif
516
+
517
+ #if defined(OPENSSL_NO_TLS1) || defined(OPENSSL_NO_TLS1_METHOD)
518
+ /* True if TLS1 is not available */
519
+ rb_define_const(mod, "OPENSSL_NO_TLS1", Qtrue);
520
+ #else
521
+ rb_define_const(mod, "OPENSSL_NO_TLS1", Qfalse);
522
+ #endif
523
+
524
+ #if defined(OPENSSL_NO_TLS1_1) || defined(OPENSSL_NO_TLS1_1_METHOD)
525
+ /* True if TLS1_1 is not available */
526
+ rb_define_const(mod, "OPENSSL_NO_TLS1_1", Qtrue);
527
+ #else
528
+ rb_define_const(mod, "OPENSSL_NO_TLS1_1", Qfalse);
529
+ #endif
528
530
 
529
531
  rb_define_singleton_method(mod, "check", noop, 0);
530
532
 
@@ -40,7 +40,9 @@ static VALUE global_http_version;
40
40
  static VALUE global_request_path;
41
41
 
42
42
  /** Defines common length and error messages for input length validation. */
43
- #define DEF_MAX_LENGTH(N,length) const size_t MAX_##N##_LENGTH = length; const char *MAX_##N##_LENGTH_ERR = "HTTP element " # N " is longer than the " # length " allowed length (was %d)"
43
+ #define QUOTE(s) #s
44
+ #define EXPLAND_MAX_LENGHT_VALUE(s) QUOTE(s)
45
+ #define DEF_MAX_LENGTH(N,length) const size_t MAX_##N##_LENGTH = length; const char *MAX_##N##_LENGTH_ERR = "HTTP element " # N " is longer than the " EXPLAND_MAX_LENGHT_VALUE(length) " allowed length (was %d)"
44
46
 
45
47
  /** Validates the max length of given input and throws an HttpParserError exception if over. */
46
48
  #define VALIDATE_MAX_LENGTH(len, N) if(len > MAX_##N##_LENGTH) { rb_raise(eHttpParserError, MAX_##N##_LENGTH_ERR, len); }
@@ -50,12 +52,16 @@ static VALUE global_request_path;
50
52
 
51
53
 
52
54
  /* Defines the maximum allowed lengths for various input elements.*/
55
+ #ifndef PUMA_QUERY_STRING_MAX_LENGTH
56
+ #define PUMA_QUERY_STRING_MAX_LENGTH (1024 * 10)
57
+ #endif
58
+
53
59
  DEF_MAX_LENGTH(FIELD_NAME, 256);
54
60
  DEF_MAX_LENGTH(FIELD_VALUE, 80 * 1024);
55
61
  DEF_MAX_LENGTH(REQUEST_URI, 1024 * 12);
56
62
  DEF_MAX_LENGTH(FRAGMENT, 1024); /* Don't know if this length is specified somewhere or not */
57
63
  DEF_MAX_LENGTH(REQUEST_PATH, 8192);
58
- DEF_MAX_LENGTH(QUERY_STRING, (1024 * 10));
64
+ DEF_MAX_LENGTH(QUERY_STRING, PUMA_QUERY_STRING_MAX_LENGTH);
59
65
  DEF_MAX_LENGTH(HEADER, (1024 * (80 + 32)));
60
66
 
61
67
  struct common_field {
@@ -253,11 +259,18 @@ void HttpParser_free(void *data) {
253
259
  }
254
260
  }
255
261
 
256
- void HttpParser_mark(puma_parser* hp) {
262
+ void HttpParser_mark(void *ptr) {
263
+ puma_parser *hp = ptr;
257
264
  if(hp->request) rb_gc_mark(hp->request);
258
265
  if(hp->body) rb_gc_mark(hp->body);
259
266
  }
260
267
 
268
+ const rb_data_type_t HttpParser_data_type = {
269
+ "HttpParser",
270
+ { HttpParser_mark, HttpParser_free, 0 },
271
+ 0, 0, RUBY_TYPED_FREE_IMMEDIATELY,
272
+ };
273
+
261
274
  VALUE HttpParser_alloc(VALUE klass)
262
275
  {
263
276
  puma_parser *hp = ALLOC_N(puma_parser, 1);
@@ -274,7 +287,7 @@ VALUE HttpParser_alloc(VALUE klass)
274
287
 
275
288
  puma_parser_init(hp);
276
289
 
277
- return Data_Wrap_Struct(klass, HttpParser_mark, HttpParser_free, hp);
290
+ return TypedData_Wrap_Struct(klass, &HttpParser_data_type, hp);
278
291
  }
279
292
 
280
293
  /**
@@ -286,7 +299,7 @@ VALUE HttpParser_alloc(VALUE klass)
286
299
  VALUE HttpParser_init(VALUE self)
287
300
  {
288
301
  puma_parser *http = NULL;
289
- DATA_GET(self, puma_parser, http);
302
+ DATA_GET(self, puma_parser, &HttpParser_data_type, http);
290
303
  puma_parser_init(http);
291
304
 
292
305
  return self;
@@ -303,7 +316,7 @@ VALUE HttpParser_init(VALUE self)
303
316
  VALUE HttpParser_reset(VALUE self)
304
317
  {
305
318
  puma_parser *http = NULL;
306
- DATA_GET(self, puma_parser, http);
319
+ DATA_GET(self, puma_parser, &HttpParser_data_type, http);
307
320
  puma_parser_init(http);
308
321
 
309
322
  return Qnil;
@@ -320,7 +333,7 @@ VALUE HttpParser_reset(VALUE self)
320
333
  VALUE HttpParser_finish(VALUE self)
321
334
  {
322
335
  puma_parser *http = NULL;
323
- DATA_GET(self, puma_parser, http);
336
+ DATA_GET(self, puma_parser, &HttpParser_data_type, http);
324
337
  puma_parser_finish(http);
325
338
 
326
339
  return puma_parser_is_finished(http) ? Qtrue : Qfalse;
@@ -351,7 +364,7 @@ VALUE HttpParser_execute(VALUE self, VALUE req_hash, VALUE data, VALUE start)
351
364
  char *dptr = NULL;
352
365
  long dlen = 0;
353
366
 
354
- DATA_GET(self, puma_parser, http);
367
+ DATA_GET(self, puma_parser, &HttpParser_data_type, http);
355
368
 
356
369
  from = FIX2INT(start);
357
370
  dptr = rb_extract_chars(data, &dlen);
@@ -385,7 +398,7 @@ VALUE HttpParser_execute(VALUE self, VALUE req_hash, VALUE data, VALUE start)
385
398
  VALUE HttpParser_has_error(VALUE self)
386
399
  {
387
400
  puma_parser *http = NULL;
388
- DATA_GET(self, puma_parser, http);
401
+ DATA_GET(self, puma_parser, &HttpParser_data_type, http);
389
402
 
390
403
  return puma_parser_has_error(http) ? Qtrue : Qfalse;
391
404
  }
@@ -400,7 +413,7 @@ VALUE HttpParser_has_error(VALUE self)
400
413
  VALUE HttpParser_is_finished(VALUE self)
401
414
  {
402
415
  puma_parser *http = NULL;
403
- DATA_GET(self, puma_parser, http);
416
+ DATA_GET(self, puma_parser, &HttpParser_data_type, http);
404
417
 
405
418
  return puma_parser_is_finished(http) ? Qtrue : Qfalse;
406
419
  }
@@ -416,7 +429,7 @@ VALUE HttpParser_is_finished(VALUE self)
416
429
  VALUE HttpParser_nread(VALUE self)
417
430
  {
418
431
  puma_parser *http = NULL;
419
- DATA_GET(self, puma_parser, http);
432
+ DATA_GET(self, puma_parser, &HttpParser_data_type, http);
420
433
 
421
434
  return INT2FIX(http->nread);
422
435
  }
@@ -429,7 +442,7 @@ VALUE HttpParser_nread(VALUE self)
429
442
  */
430
443
  VALUE HttpParser_body(VALUE self) {
431
444
  puma_parser *http = NULL;
432
- DATA_GET(self, puma_parser, http);
445
+ DATA_GET(self, puma_parser, &HttpParser_data_type, http);
433
446
 
434
447
  return http->body;
435
448
  }
@@ -10,23 +10,26 @@ require 'stringio'
10
10
 
11
11
  require 'thread'
12
12
 
13
- require_relative 'puma/puma_http11'
14
- require_relative 'puma/detect'
13
+ require 'puma/puma_http11'
14
+ require 'puma/detect'
15
+ require 'puma/json'
15
16
 
16
17
  module Puma
17
18
  autoload :Const, 'puma/const'
18
19
  autoload :Server, 'puma/server'
19
20
  autoload :Launcher, 'puma/launcher'
20
21
 
22
+ # @!attribute [rw] stats_object=
21
23
  def self.stats_object=(val)
22
24
  @get_stats = val
23
25
  end
24
26
 
27
+ # @!attribute [rw] stats_object
25
28
  def self.stats
26
- require 'json'
27
- @get_stats.stats.to_json
29
+ Puma::JSON.generate @get_stats.stats
28
30
  end
29
31
 
32
+ # @!attribute [r] stats_hash
30
33
  # @version 5.0.0
31
34
  def self.stats_hash
32
35
  @get_stats.stats
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+ require 'puma/json'
2
3
 
3
4
  module Puma
4
5
  module App
@@ -7,71 +8,68 @@ module Puma
7
8
  class Status
8
9
  OK_STATUS = '{ "status": "ok" }'.freeze
9
10
 
10
- def initialize(cli, token = nil)
11
- @cli = cli
11
+ # @param launcher [::Puma::Launcher]
12
+ # @param token [String, nil] the token used for authentication
13
+ #
14
+ def initialize(launcher, token = nil)
15
+ @launcher = launcher
12
16
  @auth_token = token
13
17
  end
14
18
 
19
+ # most commands call methods in `::Puma::Launcher` based on command in
20
+ # `env['PATH_INFO']`
15
21
  def call(env)
16
22
  unless authenticate(env)
17
23
  return rack_response(403, 'Invalid auth token', 'text/plain')
18
24
  end
19
25
 
20
- if env['PATH_INFO'] =~ /\/(gc-stats|stats|thread-backtraces)$/
21
- require 'json'
22
- end
26
+ # resp_type is processed by following case statement, return
27
+ # is a number (status) or a string used as the body of a 200 response
28
+ resp_type =
29
+ case env['PATH_INFO'][/\/([^\/]+)$/, 1]
30
+ when 'stop'
31
+ @launcher.stop ; 200
23
32
 
24
- case env['PATH_INFO']
25
- when /\/stop$/
26
- @cli.stop
27
- rack_response(200, OK_STATUS)
33
+ when 'halt'
34
+ @launcher.halt ; 200
28
35
 
29
- when /\/halt$/
30
- @cli.halt
31
- rack_response(200, OK_STATUS)
36
+ when 'restart'
37
+ @launcher.restart ; 200
32
38
 
33
- when /\/restart$/
34
- @cli.restart
35
- rack_response(200, OK_STATUS)
39
+ when 'phased-restart'
40
+ @launcher.phased_restart ? 200 : 404
36
41
 
37
- when /\/phased-restart$/
38
- if !@cli.phased_restart
39
- rack_response(404, '{ "error": "phased restart not available" }')
40
- else
41
- rack_response(200, OK_STATUS)
42
- end
42
+ when 'reload-worker-directory'
43
+ @launcher.send(:reload_worker_directory) ? 200 : 404
43
44
 
44
- when /\/reload-worker-directory$/
45
- if !@cli.send(:reload_worker_directory)
46
- rack_response(404, '{ "error": "reload_worker_directory not available" }')
47
- else
48
- rack_response(200, OK_STATUS)
49
- end
45
+ when 'gc'
46
+ GC.start ; 200
50
47
 
51
- when /\/gc$/
52
- GC.start
53
- rack_response(200, OK_STATUS)
48
+ when 'gc-stats'
49
+ Puma::JSON.generate GC.stat
54
50
 
55
- when /\/gc-stats$/
56
- rack_response(200, GC.stat.to_json)
51
+ when 'stats'
52
+ Puma::JSON.generate @launcher.stats
57
53
 
58
- when /\/stats$/
59
- rack_response(200, @cli.stats.to_json)
54
+ when 'thread-backtraces'
55
+ backtraces = []
56
+ @launcher.thread_status do |name, backtrace|
57
+ backtraces << { name: name, backtrace: backtrace }
58
+ end
59
+ Puma::JSON.generate backtraces
60
60
 
61
- when /\/thread-backtraces$/
62
- backtraces = []
63
- @cli.thread_status do |name, backtrace|
64
- backtraces << { name: name, backtrace: backtrace }
61
+ else
62
+ return rack_response(404, "Unsupported action", 'text/plain')
65
63
  end
66
64
 
67
- rack_response(200, backtraces.to_json)
68
-
69
- when /\/refork$/
70
- Process.kill "SIGURG", $$
71
- rack_response(200, OK_STATUS)
72
-
73
- else
74
- rack_response 404, "Unsupported action", 'text/plain'
65
+ case resp_type
66
+ when String
67
+ rack_response 200, resp_type
68
+ when 200
69
+ rack_response 200, OK_STATUS
70
+ when 404
71
+ str = env['PATH_INFO'][/\/(\S+)/, 1].tr '-', '_'
72
+ rack_response 404, "{ \"error\": \"#{str} not available\" }"
75
73
  end
76
74
  end
77
75
 
@@ -12,7 +12,15 @@ module Puma
12
12
  if HAS_SSL
13
13
  require 'puma/minissl'
14
14
  require 'puma/minissl/context_builder'
15
- require 'puma/accept_nonblock'
15
+
16
+ # Odd bug in 'pure Ruby' nio4r verion 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
16
24
  end
17
25
 
18
26
  class Binder
@@ -66,6 +74,7 @@ module Puma
66
74
  @ios.each { |i| i.close }
67
75
  end
68
76
 
77
+ # @!attribute [r] connected_ports
69
78
  # @version 5.0.0
70
79
  def connected_ports
71
80
  ios.map { |io| io.addr[1] }.uniq
@@ -102,6 +111,43 @@ module Puma
102
111
  ["LISTEN_FDS", "LISTEN_PID"] # Signal to remove these keys from ENV
103
112
  end
104
113
 
114
+ # Synthesize binds from systemd socket activation
115
+ #
116
+ # When systemd socket activation is enabled, it can be tedious to keep the
117
+ # binds in sync. This method can synthesize any binds based on the received
118
+ # activated sockets. Any existing matching binds will be respected.
119
+ #
120
+ # When only_matching is true in, all binds that do not match an activated
121
+ # socket is removed in place.
122
+ #
123
+ # It's a noop if no activated sockets were received.
124
+ def synthesize_binds_from_activated_fs(binds, only_matching)
125
+ return binds unless activated_sockets.any?
126
+
127
+ activated_binds = []
128
+
129
+ activated_sockets.keys.each do |proto, addr, port|
130
+ if port
131
+ tcp_url = "#{proto}://#{addr}:#{port}"
132
+ ssl_url = "ssl://#{addr}:#{port}"
133
+ ssl_url_prefix = "#{ssl_url}?"
134
+
135
+ existing = binds.find { |bind| bind == tcp_url || bind == ssl_url || bind.start_with?(ssl_url_prefix) }
136
+
137
+ activated_binds << (existing || tcp_url)
138
+ else
139
+ # TODO: can there be a SSL bind without a port?
140
+ activated_binds << "#{proto}://#{addr}"
141
+ end
142
+ end
143
+
144
+ if only_matching
145
+ activated_binds
146
+ else
147
+ binds | activated_binds
148
+ end
149
+ end
150
+
105
151
  def parse(binds, logger, log_msg = 'Listening')
106
152
  binds.each do |str|
107
153
  uri = URI.parse str
@@ -391,6 +437,7 @@ module Puma
391
437
 
392
438
  private
393
439
 
440
+ # @!attribute [r] loopback_addresses
394
441
  def loopback_addresses
395
442
  Socket.ip_address_list.select do |addrinfo|
396
443
  addrinfo.ipv6_loopback? || addrinfo.ipv4_loopback?