puma 6.0.2 → 6.4.2

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of puma might be problematic. Click here for more details.

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