puma 6.0.2 → 6.4.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (84) hide show
  1. checksums.yaml +4 -4
  2. data/History.md +213 -7
  3. data/LICENSE +0 -0
  4. data/README.md +59 -13
  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 +0 -0
  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 +12 -0
  17. data/docs/nginx.md +0 -0
  18. data/docs/plugins.md +0 -0
  19. data/docs/rails_dev_mode.md +0 -0
  20. data/docs/restart.md +1 -0
  21. data/docs/signals.md +0 -0
  22. data/docs/stats.md +0 -0
  23. data/docs/systemd.md +3 -6
  24. data/docs/testing_benchmarks_local_files.md +0 -0
  25. data/docs/testing_test_rackup_ci_files.md +0 -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 +5 -1
  29. data/ext/puma_http11/http11_parser.c +0 -0
  30. data/ext/puma_http11/http11_parser.h +0 -0
  31. data/ext/puma_http11/http11_parser.java.rl +0 -0
  32. data/ext/puma_http11/http11_parser.rl +0 -0
  33. data/ext/puma_http11/http11_parser_common.rl +0 -0
  34. data/ext/puma_http11/mini_ssl.c +96 -9
  35. data/ext/puma_http11/no_ssl/PumaHttp11Service.java +0 -0
  36. data/ext/puma_http11/org/jruby/puma/Http11.java +0 -0
  37. data/ext/puma_http11/org/jruby/puma/Http11Parser.java +0 -0
  38. data/ext/puma_http11/org/jruby/puma/MiniSSL.java +2 -1
  39. data/ext/puma_http11/puma_http11.c +0 -0
  40. data/lib/puma/app/status.rb +1 -1
  41. data/lib/puma/binder.rb +14 -11
  42. data/lib/puma/cli.rb +5 -1
  43. data/lib/puma/client.rb +77 -16
  44. data/lib/puma/cluster/worker.rb +5 -0
  45. data/lib/puma/cluster/worker_handle.rb +0 -0
  46. data/lib/puma/cluster.rb +71 -10
  47. data/lib/puma/commonlogger.rb +21 -14
  48. data/lib/puma/configuration.rb +6 -4
  49. data/lib/puma/const.rb +58 -9
  50. data/lib/puma/control_cli.rb +12 -5
  51. data/lib/puma/detect.rb +5 -4
  52. data/lib/puma/dsl.rb +157 -7
  53. data/lib/puma/error_logger.rb +2 -1
  54. data/lib/puma/events.rb +0 -0
  55. data/lib/puma/io_buffer.rb +0 -0
  56. data/lib/puma/jruby_restart.rb +0 -0
  57. data/lib/puma/json_serialization.rb +0 -0
  58. data/lib/puma/launcher/bundle_pruner.rb +0 -0
  59. data/lib/puma/launcher.rb +9 -22
  60. data/lib/puma/log_writer.rb +14 -4
  61. data/lib/puma/minissl/context_builder.rb +3 -0
  62. data/lib/puma/minissl.rb +22 -0
  63. data/lib/puma/null_io.rb +16 -2
  64. data/lib/puma/plugin/systemd.rb +90 -0
  65. data/lib/puma/plugin/tmp_restart.rb +0 -0
  66. data/lib/puma/plugin.rb +0 -0
  67. data/lib/puma/rack/builder.rb +2 -2
  68. data/lib/puma/rack/urlmap.rb +1 -1
  69. data/lib/puma/rack_default.rb +18 -3
  70. data/lib/puma/reactor.rb +16 -7
  71. data/lib/puma/request.rb +91 -64
  72. data/lib/puma/runner.rb +13 -2
  73. data/lib/puma/sd_notify.rb +149 -0
  74. data/lib/puma/server.rb +91 -27
  75. data/lib/puma/single.rb +2 -0
  76. data/lib/puma/state_file.rb +2 -2
  77. data/lib/puma/thread_pool.rb +41 -3
  78. data/lib/puma/util.rb +0 -0
  79. data/lib/puma.rb +0 -0
  80. data/lib/rack/handler/puma.rb +113 -86
  81. data/tools/Dockerfile +2 -2
  82. data/tools/trickletest.rb +0 -0
  83. metadata +5 -4
  84. data/lib/puma/systemd.rb +0 -47
data/lib/puma/dsl.rb CHANGED
@@ -65,6 +65,7 @@ module Puma
65
65
 
66
66
  ca_additions = "&ca=#{Puma::Util.escape(opts[:ca])}" if ['peer', 'force_peer'].include?(verify)
67
67
 
68
+ low_latency_str = opts.key?(:low_latency) ? "&low_latency=#{opts[:low_latency]}" : ''
68
69
  backlog_str = opts[:backlog] ? "&backlog=#{Integer(opts[:backlog])}" : ''
69
70
 
70
71
  if defined?(JRUBY_VERSION)
@@ -88,6 +89,7 @@ module Puma
88
89
 
89
90
  cert_flags = (cert = opts[:cert]) ? "cert=#{Puma::Util.escape(cert)}" : nil
90
91
  key_flags = (key = opts[:key]) ? "&key=#{Puma::Util.escape(key)}" : nil
92
+ password_flags = (password_command = opts[:key_password_command]) ? "&key_password_command=#{Puma::Util.escape(password_command)}" : nil
91
93
 
92
94
  reuse_flag =
93
95
  if (reuse = opts[:reuse])
@@ -113,8 +115,8 @@ module Puma
113
115
  nil
114
116
  end
115
117
 
116
- "ssl://#{host}:#{port}?#{cert_flags}#{key_flags}#{ssl_cipher_filter}" \
117
- "#{reuse_flag}&verify_mode=#{verify}#{tls_str}#{ca_additions}#{v_flags}#{backlog_str}"
118
+ "ssl://#{host}:#{port}?#{cert_flags}#{key_flags}#{password_flags}#{ssl_cipher_filter}" \
119
+ "#{reuse_flag}&verify_mode=#{verify}#{tls_str}#{ca_additions}#{v_flags}#{backlog_str}#{low_latency_str}"
118
120
  end
119
121
  end
120
122
 
@@ -313,16 +315,22 @@ module Puma
313
315
  bind URI::Generic.build(scheme: 'tcp', host: host, port: Integer(port)).to_s
314
316
  end
315
317
 
318
+ # Define how long the tcp socket stays open, if no data has been received.
319
+ # @see Puma::Server.new
320
+ def first_data_timeout(seconds)
321
+ @options[:first_data_timeout] = Integer(seconds)
322
+ end
323
+
316
324
  # Define how long persistent connections can be idle before Puma closes them.
317
325
  # @see Puma::Server.new
318
326
  def persistent_timeout(seconds)
319
327
  @options[:persistent_timeout] = Integer(seconds)
320
328
  end
321
329
 
322
- # Define how long the tcp socket stays open, if no data has been received.
330
+ # If a new request is not received within this number of seconds, begin shutting down.
323
331
  # @see Puma::Server.new
324
- def first_data_timeout(seconds)
325
- @options[:first_data_timeout] = Integer(seconds)
332
+ def idle_timeout(seconds)
333
+ @options[:idle_timeout] = Integer(seconds)
326
334
  end
327
335
 
328
336
  # Work around leaky apps that leave garbage in Thread locals
@@ -418,6 +426,11 @@ module Puma
418
426
  @options[:log_requests] = which
419
427
  end
420
428
 
429
+ # Pass in a custom logging class instance
430
+ def custom_logger(custom_logger)
431
+ @options[:custom_logger] = custom_logger
432
+ end
433
+
421
434
  # Show debugging info
422
435
  #
423
436
  def debug
@@ -503,6 +516,12 @@ module Puma
503
516
  # `true`, which sets reuse 'on' with default values, or a hash, with `:size`
504
517
  # and/or `:timeout` keys, each with integer values.
505
518
  #
519
+ # The `cert:` options hash parameter can be the path to a certificate
520
+ # file including all intermediate certificates in PEM format.
521
+ #
522
+ # The `cert_pem:` options hash parameter can be String containing the
523
+ # cerificate and all intermediate certificates in PEM format.
524
+ #
506
525
  # @example
507
526
  # ssl_bind '127.0.0.1', '9292', {
508
527
  # cert: path_to_cert,
@@ -584,6 +603,11 @@ module Puma
584
603
  @options[:silence_single_worker_warning] = true
585
604
  end
586
605
 
606
+ # Disable warning message when running single mode with callback hook defined.
607
+ def silence_fork_callback_warning
608
+ @options[:silence_fork_callback_warning] = true
609
+ end
610
+
587
611
  # Code to run immediately before master process
588
612
  # forks workers (once on boot). These hooks can block if necessary
589
613
  # to wait for background operations unknown to Puma to finish before
@@ -599,6 +623,8 @@ module Puma
599
623
  # puts "Starting workers..."
600
624
  # end
601
625
  def before_fork(&block)
626
+ warn_if_in_single_mode('before_fork')
627
+
602
628
  @options[:before_fork] ||= []
603
629
  @options[:before_fork] << block
604
630
  end
@@ -614,6 +640,8 @@ module Puma
614
640
  # puts 'Before worker boot...'
615
641
  # end
616
642
  def on_worker_boot(key = nil, &block)
643
+ warn_if_in_single_mode('on_worker_boot')
644
+
617
645
  process_hook :before_worker_boot, key, block, 'on_worker_boot'
618
646
  end
619
647
 
@@ -630,6 +658,8 @@ module Puma
630
658
  # puts 'On worker shutdown...'
631
659
  # end
632
660
  def on_worker_shutdown(key = nil, &block)
661
+ warn_if_in_single_mode('on_worker_shutdown')
662
+
633
663
  process_hook :before_worker_shutdown, key, block, 'on_worker_shutdown'
634
664
  end
635
665
 
@@ -644,6 +674,8 @@ module Puma
644
674
  # puts 'Before worker fork...'
645
675
  # end
646
676
  def on_worker_fork(&block)
677
+ warn_if_in_single_mode('on_worker_fork')
678
+
647
679
  process_hook :before_worker_fork, nil, block, 'on_worker_fork'
648
680
  end
649
681
 
@@ -658,11 +690,23 @@ module Puma
658
690
  # puts 'After worker fork...'
659
691
  # end
660
692
  def after_worker_fork(&block)
693
+ warn_if_in_single_mode('after_worker_fork')
694
+
661
695
  process_hook :after_worker_fork, nil, block, 'after_worker_fork'
662
696
  end
663
697
 
664
698
  alias_method :after_worker_boot, :after_worker_fork
665
699
 
700
+ # Code to run after puma is booted (works for both: single and clustered)
701
+ #
702
+ # @example
703
+ # on_booted do
704
+ # puts 'After booting...'
705
+ # end
706
+ def on_booted(&block)
707
+ @config.options[:events].on_booted(&block)
708
+ end
709
+
666
710
  # When `fork_worker` is enabled, code to run in Worker 0
667
711
  # before all other workers are re-forked from this process,
668
712
  # after the server has temporarily stopped serving requests
@@ -685,6 +729,51 @@ module Puma
685
729
  process_hook :before_refork, key, block, 'on_refork'
686
730
  end
687
731
 
732
+ # Provide a block to be executed just before a thread is added to the thread
733
+ # pool. Be careful: while the block executes, thread creation is delayed, and
734
+ # probably a request will have to wait too! The new thread will not be added to
735
+ # the threadpool until the provided block returns.
736
+ #
737
+ # Return values are ignored.
738
+ # Raising an exception will log a warning.
739
+ #
740
+ # This hook is useful for doing something when the thread pool grows.
741
+ #
742
+ # This can be called multiple times to add several hooks.
743
+ #
744
+ # @example
745
+ # on_thread_start do
746
+ # puts 'On thread start...'
747
+ # end
748
+ def on_thread_start(&block)
749
+ @options[:before_thread_start] ||= []
750
+ @options[:before_thread_start] << block
751
+ end
752
+
753
+ # Provide a block to be executed after a thread is trimmed from the thread
754
+ # pool. Be careful: while this block executes, Puma's main loop is
755
+ # blocked, so no new requests will be picked up.
756
+ #
757
+ # This hook only runs when a thread in the threadpool is trimmed by Puma.
758
+ # It does not run when a thread dies due to exceptions or any other cause.
759
+ #
760
+ # Return values are ignored.
761
+ # Raising an exception will log a warning.
762
+ #
763
+ # This hook is useful for cleaning up thread local resources when a thread
764
+ # is trimmed.
765
+ #
766
+ # This can be called multiple times to add several hooks.
767
+ #
768
+ # @example
769
+ # on_thread_exit do
770
+ # puts 'On thread exit...'
771
+ # end
772
+ def on_thread_exit(&block)
773
+ @options[:before_thread_exit] ||= []
774
+ @options[:before_thread_exit] << block
775
+ end
776
+
688
777
  # Code to run out-of-band when the worker is idle.
689
778
  # These hooks run immediately after a request has finished
690
779
  # processing and there are no busy threads on the worker.
@@ -816,7 +905,8 @@ module Puma
816
905
  # not a request timeout, it is to protect against a hung or dead process.
817
906
  # Setting this value will not protect against slow requests.
818
907
  #
819
- # The minimum value is 6 seconds, the default value is 60 seconds.
908
+ # This value must be greater than worker_check_interval.
909
+ # The default value is 60 seconds.
820
910
  #
821
911
  # @note Cluster mode only.
822
912
  # @example
@@ -1022,6 +1112,51 @@ module Puma
1022
1112
  @options[:mutate_stdout_and_stderr_to_sync_on_write] = enabled
1023
1113
  end
1024
1114
 
1115
+ # Specify how big the request payload should be, in bytes.
1116
+ # This limit is compared against Content-Length HTTP header.
1117
+ # If the payload size (CONTENT_LENGTH) is larger than http_content_length_limit,
1118
+ # HTTP 413 status code is returned.
1119
+ #
1120
+ # When no Content-Length http header is present, it is compared against the
1121
+ # size of the body of the request.
1122
+ #
1123
+ # The default value for http_content_length_limit is nil.
1124
+ def http_content_length_limit(limit)
1125
+ @options[:http_content_length_limit] = limit
1126
+ end
1127
+
1128
+ # Supported http methods, which will replace `Puma::Const::SUPPORTED_HTTP_METHODS`.
1129
+ # The value of `:any` will allows all methods, otherwise, the value must be
1130
+ # an array of strings. Note that methods are all uppercase.
1131
+ #
1132
+ # `Puma::Const::SUPPORTED_HTTP_METHODS` is conservative, if you want a
1133
+ # complete set of methods, the methods defined by the
1134
+ # [IANA Method Registry](https://www.iana.org/assignments/http-methods/http-methods.xhtml)
1135
+ # are pre-defined as the constant `Puma::Const::IANA_HTTP_METHODS`.
1136
+ #
1137
+ # @note If the `methods` value is `:any`, no method check with be performed,
1138
+ # similar to Puma v5 and earlier.
1139
+ #
1140
+ # @example Adds 'PROPFIND' to existing supported methods
1141
+ # supported_http_methods(Puma::Const::SUPPORTED_HTTP_METHODS + ['PROPFIND'])
1142
+ # @example Restricts methods to the array elements
1143
+ # supported_http_methods %w[HEAD GET POST PUT DELETE OPTIONS PROPFIND]
1144
+ # @example Restricts methods to the methods in the IANA Registry
1145
+ # supported_http_methods Puma::Const::IANA_HTTP_METHODS
1146
+ # @example Allows any method
1147
+ # supported_http_methods :any
1148
+ #
1149
+ def supported_http_methods(methods)
1150
+ if methods == :any
1151
+ @options[:supported_http_methods] = :any
1152
+ elsif Array === methods && methods == (ary = methods.grep(String).uniq) &&
1153
+ !ary.empty?
1154
+ @options[:supported_http_methods] = ary
1155
+ else
1156
+ raise "supported_http_methods must be ':any' or a unique array of strings"
1157
+ end
1158
+ end
1159
+
1025
1160
  private
1026
1161
 
1027
1162
  # To avoid adding cert_pem and key_pem as URI params, we store them on the
@@ -1049,7 +1184,22 @@ module Puma
1049
1184
  elsif key.nil?
1050
1185
  @options[options_key] << block
1051
1186
  else
1052
- raise "'#{method}' key must be String or Symbol"
1187
+ raise "'#{meth}' key must be String or Symbol"
1188
+ end
1189
+ end
1190
+
1191
+ def warn_if_in_single_mode(hook_name)
1192
+ return if @options[:silence_fork_callback_warning]
1193
+ # user_options (CLI) have precedence over config file
1194
+ workers_val = @config.options.user_options[:workers] || @options[:workers] ||
1195
+ @config.puma_default_options[:workers] || 0
1196
+ if workers_val == 0
1197
+ log_string =
1198
+ "Warning: You specified code to run in a `#{hook_name}` block, " \
1199
+ "but Puma is not configured to run in cluster mode (worker count > 0 ), " \
1200
+ "so your `#{hook_name}` block did not run"
1201
+
1202
+ LogWriter.stdio.log(log_string)
1053
1203
  end
1054
1204
  end
1055
1205
  end
@@ -102,7 +102,8 @@ module Puma
102
102
  @ioerr.is_a?(IO) and @ioerr.wait_writable(1)
103
103
  @ioerr.write "#{w_str}\n"
104
104
  @ioerr.flush unless @ioerr.sync
105
- rescue Errno::EPIPE, Errno::EBADF, IOError
105
+ rescue Errno::EPIPE, Errno::EBADF, IOError, Errno::EINVAL
106
+ # 'Invalid argument' (Errno::EINVAL) may be raised by flush
106
107
  end
107
108
  end
108
109
  rescue ThreadError
data/lib/puma/events.rb CHANGED
File without changes
File without changes
File without changes
File without changes
File without changes
data/lib/puma/launcher.rb CHANGED
@@ -59,6 +59,13 @@ module Puma
59
59
 
60
60
  @environment = conf.environment
61
61
 
62
+ # Load the systemd integration if we detect systemd's NOTIFY_SOCKET.
63
+ # Skip this on JRuby though, because it is incompatible with the systemd
64
+ # integration due to https://github.com/jruby/jruby/issues/6504
65
+ if ENV["NOTIFY_SOCKET"] && !Puma.jruby?
66
+ @config.plugins.create('systemd')
67
+ end
68
+
62
69
  if @config.options[:bind_to_activated_sockets]
63
70
  @config.options[:binds] = @binder.synthesize_binds_from_activated_fs(
64
71
  @config.options[:binds],
@@ -72,6 +79,8 @@ module Puma
72
79
  @log_writer.formatter = LogWriter::PidFormatter.new if clustered?
73
80
  @log_writer.formatter = options[:log_formatter] if @options[:log_formatter]
74
81
 
82
+ @log_writer.custom_logger = options[:custom_logger] if @options[:custom_logger]
83
+
75
84
  generate_restart_data
76
85
 
77
86
  if clustered? && !Puma.forkable?
@@ -180,7 +189,6 @@ module Puma
180
189
 
181
190
  setup_signals
182
191
  set_process_title
183
- integrate_with_systemd
184
192
 
185
193
  # This blocks until the server is stopped
186
194
  @runner.run
@@ -311,27 +319,6 @@ module Puma
311
319
  @runner.reload_worker_directory if @runner.respond_to?(:reload_worker_directory)
312
320
  end
313
321
 
314
- # Puma's systemd integration allows Puma to inform systemd:
315
- # 1. when it has successfully started
316
- # 2. when it is starting shutdown
317
- # 3. periodically for a liveness check with a watchdog thread
318
- def integrate_with_systemd
319
- return unless ENV["NOTIFY_SOCKET"]
320
-
321
- begin
322
- require_relative 'systemd'
323
- rescue LoadError
324
- log "Systemd integration failed. It looks like you're trying to use systemd notify but don't have sd_notify gem installed"
325
- return
326
- end
327
-
328
- log "* Enabling systemd notification integration"
329
-
330
- systemd = Systemd.new(@log_writer, @events)
331
- systemd.hook_events
332
- systemd.start_watchdog
333
- end
334
-
335
322
  def log(str)
336
323
  @log_writer.log(str)
337
324
  end
@@ -28,11 +28,12 @@ module Puma
28
28
  attr_reader :stdout,
29
29
  :stderr
30
30
 
31
- attr_accessor :formatter
31
+ attr_accessor :formatter, :custom_logger
32
32
 
33
33
  # Create a LogWriter that prints to +stdout+ and +stderr+.
34
34
  def initialize(stdout, stderr)
35
35
  @formatter = DefaultFormatter.new
36
+ @custom_logger = nil
36
37
  @stdout = stdout
37
38
  @stderr = stderr
38
39
 
@@ -59,7 +60,11 @@ module Puma
59
60
 
60
61
  # Write +str+ to +@stdout+
61
62
  def log(str)
62
- internal_write "#{@formatter.call str}\n"
63
+ if @custom_logger&.respond_to?(:write)
64
+ @custom_logger.write(format(str))
65
+ else
66
+ internal_write "#{@formatter.call str}\n"
67
+ end
63
68
  end
64
69
 
65
70
  def write(str)
@@ -73,13 +78,18 @@ module Puma
73
78
  @stdout.is_a?(IO) and @stdout.wait_writable(1)
74
79
  @stdout.write w_str
75
80
  @stdout.flush unless @stdout.sync
76
- rescue Errno::EPIPE, Errno::EBADF, IOError
81
+ rescue Errno::EPIPE, Errno::EBADF, IOError, Errno::EINVAL
82
+ # 'Invalid argument' (Errno::EINVAL) may be raised by flush
77
83
  end
78
84
  end
79
85
  rescue ThreadError
80
86
  end
81
87
  private :internal_write
82
88
 
89
+ def debug?
90
+ @debug
91
+ end
92
+
83
93
  def debug(str)
84
94
  log("% #{str}") if @debug
85
95
  end
@@ -115,7 +125,7 @@ module Puma
115
125
  def ssl_error(error, ssl_socket)
116
126
  peeraddr = ssl_socket.peeraddr.last rescue "<unknown>"
117
127
  peercert = ssl_socket.peercert
118
- subject = peercert ? peercert.subject : nil
128
+ subject = peercert&.subject
119
129
  @error_logger.info(error: error, text: "SSL error, peer: #{peeraddr}, peer cert: #{subject}")
120
130
  end
121
131
 
@@ -38,6 +38,7 @@ module Puma
38
38
 
39
39
  ctx.key = params['key'] if params['key']
40
40
  ctx.key_pem = params['key_pem'] if params['key_pem']
41
+ ctx.key_password_command = params['key_password_command'] if params['key_password_command']
41
42
 
42
43
  if params['cert'].nil? && params['cert_pem'].nil?
43
44
  log_writer.error "Please specify the SSL cert via 'cert=' or 'cert_pem='"
@@ -50,6 +51,8 @@ module Puma
50
51
  unless params['ca']
51
52
  log_writer.error "Please specify the SSL ca via 'ca='"
52
53
  end
54
+ # needed for Puma::MiniSSL::Socket#peercert, env['puma.peercert']
55
+ require 'openssl'
53
56
  end
54
57
 
55
58
  ctx.ca = params['ca'] if params['ca']
data/lib/puma/minissl.rb CHANGED
@@ -5,6 +5,7 @@ begin
5
5
  rescue LoadError
6
6
  end
7
7
 
8
+ require 'open3'
8
9
  # need for Puma::MiniSSL::OPENSSL constants used in `HAS_TLS1_3`
9
10
  # use require, see https://github.com/puma/puma/pull/2381
10
11
  require 'puma/puma_http11'
@@ -183,6 +184,11 @@ module Puma
183
184
  @socket.peeraddr
184
185
  end
185
186
 
187
+ # OpenSSL is loaded in `MiniSSL::ContextBuilder` when
188
+ # `MiniSSL::Context#verify_mode` is not `VERIFY_NONE`.
189
+ # When `VERIFY_NONE`, `MiniSSL::Engine#peercert` is nil, regardless of
190
+ # whether the client sends a cert.
191
+ # @return [OpenSSL::X509::Certificate, nil]
186
192
  # @!attribute [r] peercert
187
193
  def peercert
188
194
  return @peercert if @peercert
@@ -277,6 +283,7 @@ module Puma
277
283
  else
278
284
  # non-jruby Context properties
279
285
  attr_reader :key
286
+ attr_reader :key_password_command
280
287
  attr_reader :cert
281
288
  attr_reader :ca
282
289
  attr_reader :cert_pem
@@ -291,6 +298,10 @@ module Puma
291
298
  @key = key
292
299
  end
293
300
 
301
+ def key_password_command=(key_password_command)
302
+ @key_password_command = key_password_command
303
+ end
304
+
294
305
  def cert=(cert)
295
306
  check_file cert, 'Cert'
296
307
  @cert = cert
@@ -316,6 +327,17 @@ module Puma
316
327
  raise "Cert not configured" if @cert.nil? && @cert_pem.nil?
317
328
  end
318
329
 
330
+ # Executes the command to return the password needed to decrypt the key.
331
+ def key_password
332
+ raise "Key password command not configured" if @key_password_command.nil?
333
+
334
+ stdout_str, stderr_str, status = Open3.capture3(@key_password_command)
335
+
336
+ return stdout_str.chomp if status.success?
337
+
338
+ raise "Key password failed with code #{status.exitstatus}: #{stderr_str}"
339
+ end
340
+
319
341
  # Controls session reuse. Allowed values are as follows:
320
342
  # * 'off' - matches the behavior of Puma 5.6 and earlier. This is included
321
343
  # in case reuse 'on' is made the default in future Puma versions.
data/lib/puma/null_io.rb CHANGED
@@ -18,8 +18,22 @@ module Puma
18
18
 
19
19
  # Mimics IO#read with no data.
20
20
  #
21
- def read(count = nil, _buffer = nil)
22
- count && count > 0 ? nil : ""
21
+ def read(length = nil, buffer = nil)
22
+ if length.to_i < 0
23
+ raise ArgumentError, "(negative length #{length} given)"
24
+ end
25
+
26
+ buffer = if buffer.nil?
27
+ "".b
28
+ else
29
+ String.try_convert(buffer) or raise TypeError, "no implicit conversion of #{buffer.class} into String"
30
+ end
31
+ buffer.clear
32
+ if length.to_i > 0
33
+ nil
34
+ else
35
+ buffer
36
+ end
23
37
  end
24
38
 
25
39
  def rewind
@@ -0,0 +1,90 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../plugin'
4
+
5
+ # Puma's systemd integration allows Puma to inform systemd:
6
+ # 1. when it has successfully started
7
+ # 2. when it is starting shutdown
8
+ # 3. periodically for a liveness check with a watchdog thread
9
+ # 4. periodically set the status
10
+ Puma::Plugin.create do
11
+ def start(launcher)
12
+ require_relative '../sd_notify'
13
+
14
+ launcher.log_writer.log "* Enabling systemd notification integration"
15
+
16
+ # hook_events
17
+ launcher.events.on_booted { Puma::SdNotify.ready }
18
+ launcher.events.on_stopped { Puma::SdNotify.stopping }
19
+ launcher.events.on_restart { Puma::SdNotify.reloading }
20
+
21
+ # start watchdog
22
+ if Puma::SdNotify.watchdog?
23
+ ping_f = watchdog_sleep_time
24
+
25
+ in_background do
26
+ launcher.log_writer.log "Pinging systemd watchdog every #{ping_f.round(1)} sec"
27
+ loop do
28
+ sleep ping_f
29
+ Puma::SdNotify.watchdog
30
+ end
31
+ end
32
+ end
33
+
34
+ # start status loop
35
+ instance = self
36
+ sleep_time = 1.0
37
+ in_background do
38
+ launcher.log_writer.log "Sending status to systemd every #{sleep_time.round(1)} sec"
39
+
40
+ loop do
41
+ sleep sleep_time
42
+ # TODO: error handling?
43
+ Puma::SdNotify.status(instance.status)
44
+ end
45
+ end
46
+ end
47
+
48
+ def status
49
+ if clustered?
50
+ messages = stats[:worker_status].map do |worker|
51
+ common_message(worker[:last_status])
52
+ end.join(',')
53
+
54
+ "Puma #{Puma::Const::VERSION}: cluster: #{booted_workers}/#{workers}, worker_status: [#{messages}]"
55
+ else
56
+ "Puma #{Puma::Const::VERSION}: worker: #{common_message(stats)}"
57
+ end
58
+ end
59
+
60
+ private
61
+
62
+ def watchdog_sleep_time
63
+ usec = Integer(ENV["WATCHDOG_USEC"])
64
+
65
+ sec_f = usec / 1_000_000.0
66
+ # "It is recommended that a daemon sends a keep-alive notification message
67
+ # to the service manager every half of the time returned here."
68
+ sec_f / 2
69
+ end
70
+
71
+ def stats
72
+ Puma.stats_hash
73
+ end
74
+
75
+ def clustered?
76
+ stats.has_key?(:workers)
77
+ end
78
+
79
+ def workers
80
+ stats.fetch(:workers, 1)
81
+ end
82
+
83
+ def booted_workers
84
+ stats.fetch(:booted_workers, 1)
85
+ end
86
+
87
+ def common_message(stats)
88
+ "{ #{stats[:running]}/#{stats[:max_threads]} threads, #{stats[:pool_capacity]} available, #{stats[:backlog]} backlog }"
89
+ end
90
+ end
File without changes
data/lib/puma/plugin.rb CHANGED
File without changes
@@ -173,7 +173,7 @@ module Puma::Rack
173
173
  TOPLEVEL_BINDING, file, 0
174
174
  end
175
175
 
176
- def initialize(default_app = nil,&block)
176
+ def initialize(default_app = nil, &block)
177
177
  @use, @map, @run, @warmup = [], nil, default_app, nil
178
178
 
179
179
  # Conditionally load rack now, so that any rack middlewares,
@@ -183,7 +183,7 @@ module Puma::Rack
183
183
  rescue LoadError
184
184
  end
185
185
 
186
- instance_eval(&block) if block_given?
186
+ instance_eval(&block) if block
187
187
  end
188
188
 
189
189
  def self.app(default_app = nil, &block)
@@ -34,7 +34,7 @@ module Puma::Rack
34
34
  end
35
35
 
36
36
  location = location.chomp('/')
37
- match = Regexp.new("^#{Regexp.quote(location).gsub('/', '/+')}(.*)", nil, 'n')
37
+ match = Regexp.new("^#{Regexp.quote(location).gsub('/', '/+')}(.*)", Regexp::NOENCODING)
38
38
 
39
39
  [host, location, match, app]
40
40
  }.sort_by do |(host, location, _, _)|
@@ -2,8 +2,23 @@
2
2
 
3
3
  require_relative '../rack/handler/puma'
4
4
 
5
- module Rack::Handler
6
- def self.default(options = {})
7
- Rack::Handler::Puma
5
+ # rackup was removed in Rack 3, it is now a separate gem
6
+ if Object.const_defined? :Rackup
7
+ module Rackup
8
+ module Handler
9
+ def self.default(options = {})
10
+ ::Rackup::Handler::Puma
11
+ end
12
+ end
8
13
  end
14
+ elsif Object.const_defined?(:Rack) && Rack.release < '3'
15
+ module Rack
16
+ module Handler
17
+ def self.default(options = {})
18
+ ::Rack::Handler::Puma
19
+ end
20
+ end
21
+ end
22
+ else
23
+ raise "Rack 3 must be used with the Rackup gem"
9
24
  end