puma 5.3.2 → 5.6.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 (79) hide show
  1. checksums.yaml +4 -4
  2. data/History.md +143 -8
  3. data/LICENSE +0 -0
  4. data/README.md +47 -6
  5. data/bin/puma-wild +0 -0
  6. data/docs/architecture.md +49 -16
  7. data/docs/compile_options.md +4 -2
  8. data/docs/deployment.md +53 -67
  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 +0 -0
  17. data/docs/nginx.md +0 -0
  18. data/docs/plugins.md +15 -15
  19. data/docs/rails_dev_mode.md +2 -3
  20. data/docs/restart.md +6 -6
  21. data/docs/signals.md +11 -10
  22. data/docs/stats.md +8 -8
  23. data/docs/systemd.md +64 -67
  24. data/ext/puma_http11/PumaHttp11Service.java +0 -0
  25. data/ext/puma_http11/ext_help.h +0 -0
  26. data/ext/puma_http11/extconf.rb +28 -5
  27. data/ext/puma_http11/http11_parser.c +23 -10
  28. data/ext/puma_http11/http11_parser.h +0 -0
  29. data/ext/puma_http11/http11_parser.java.rl +0 -0
  30. data/ext/puma_http11/http11_parser.rl +0 -0
  31. data/ext/puma_http11/http11_parser_common.rl +1 -1
  32. data/ext/puma_http11/mini_ssl.c +69 -9
  33. data/ext/puma_http11/no_ssl/PumaHttp11Service.java +0 -0
  34. data/ext/puma_http11/org/jruby/puma/Http11.java +0 -0
  35. data/ext/puma_http11/org/jruby/puma/Http11Parser.java +49 -47
  36. data/ext/puma_http11/org/jruby/puma/MiniSSL.java +28 -43
  37. data/ext/puma_http11/puma_http11.c +1 -1
  38. data/lib/puma/app/status.rb +4 -4
  39. data/lib/puma/binder.rb +50 -5
  40. data/lib/puma/cli.rb +14 -4
  41. data/lib/puma/client.rb +50 -9
  42. data/lib/puma/cluster/worker.rb +8 -18
  43. data/lib/puma/cluster/worker_handle.rb +4 -0
  44. data/lib/puma/cluster.rb +30 -24
  45. data/lib/puma/commonlogger.rb +0 -0
  46. data/lib/puma/configuration.rb +4 -1
  47. data/lib/puma/const.rb +4 -5
  48. data/lib/puma/control_cli.rb +1 -1
  49. data/lib/puma/detect.rb +8 -2
  50. data/lib/puma/dsl.rb +105 -11
  51. data/lib/puma/error_logger.rb +0 -0
  52. data/lib/puma/events.rb +0 -0
  53. data/lib/puma/io_buffer.rb +0 -0
  54. data/lib/puma/jruby_restart.rb +0 -0
  55. data/lib/puma/{json.rb → json_serialization.rb} +1 -1
  56. data/lib/puma/launcher.rb +4 -1
  57. data/lib/puma/minissl/context_builder.rb +8 -6
  58. data/lib/puma/minissl.rb +24 -23
  59. data/lib/puma/null_io.rb +0 -0
  60. data/lib/puma/plugin/tmp_restart.rb +0 -0
  61. data/lib/puma/plugin.rb +2 -2
  62. data/lib/puma/queue_close.rb +0 -0
  63. data/lib/puma/rack/builder.rb +1 -1
  64. data/lib/puma/rack/urlmap.rb +0 -0
  65. data/lib/puma/rack_default.rb +0 -0
  66. data/lib/puma/reactor.rb +0 -0
  67. data/lib/puma/request.rb +14 -9
  68. data/lib/puma/runner.rb +22 -8
  69. data/lib/puma/server.rb +32 -29
  70. data/lib/puma/single.rb +0 -0
  71. data/lib/puma/state_file.rb +41 -7
  72. data/lib/puma/systemd.rb +0 -0
  73. data/lib/puma/thread_pool.rb +7 -5
  74. data/lib/puma/util.rb +8 -1
  75. data/lib/puma.rb +2 -2
  76. data/lib/rack/handler/puma.rb +0 -0
  77. data/tools/Dockerfile +1 -1
  78. data/tools/trickletest.rb +0 -0
  79. metadata +7 -7
data/lib/puma/cli.rb CHANGED
@@ -11,16 +11,17 @@ require 'puma/events'
11
11
 
12
12
  module Puma
13
13
  class << self
14
- # The CLI exports its Puma::Configuration object here to allow
15
- # apps to pick it up. An app needs to use it conditionally though
16
- # since it is not set if the app is launched via another
17
- # mechanism than the CLI class.
14
+ # The CLI exports a Puma::Configuration instance here to allow
15
+ # apps to pick it up. An app must load this object conditionally
16
+ # because it is not set if the app is launched via any mechanism
17
+ # other than the CLI class.
18
18
  attr_accessor :cli_config
19
19
  end
20
20
 
21
21
  # Handles invoke a Puma::Server in a command line style.
22
22
  #
23
23
  class CLI
24
+ # @deprecated 6.0.0
24
25
  KEYS_NOT_TO_PERSIST_IN_STATE = Launcher::KEYS_NOT_TO_PERSIST_IN_STATE
25
26
 
26
27
  # Create a new CLI object using +argv+ as the command line
@@ -112,6 +113,11 @@ module Puma
112
113
  file_config.load arg
113
114
  end
114
115
 
116
+ # Identical to supplying --config "-", but more semantic
117
+ o.on "--no-config", "Prevent Puma from searching for a config file" do |arg|
118
+ file_config.load "-"
119
+ end
120
+
115
121
  o.on "--control-url URL", "The bind url to use for the control server. Use 'auto' to use temp unix server" do |arg|
116
122
  configure_control_url(arg)
117
123
  end
@@ -179,6 +185,10 @@ module Puma
179
185
  user_config.restart_command cmd
180
186
  end
181
187
 
188
+ o.on "-s", "--silent", "Do not log prompt messages other than errors" do
189
+ @events = Events.new NullIO.new, $stderr
190
+ end
191
+
182
192
  o.on "-S", "--state PATH", "Where to store the state details" do |arg|
183
193
  user_config.state_path arg
184
194
  end
data/lib/puma/client.rb CHANGED
@@ -56,6 +56,7 @@ module Puma
56
56
  @parser = HttpParser.new
57
57
  @parsed_bytes = 0
58
58
  @read_header = true
59
+ @read_proxy = false
59
60
  @ready = false
60
61
 
61
62
  @body = nil
@@ -71,6 +72,7 @@ module Puma
71
72
  @peerip = nil
72
73
  @listener = nil
73
74
  @remote_addr_header = nil
75
+ @expect_proxy_proto = false
74
76
 
75
77
  @body_remain = 0
76
78
 
@@ -106,7 +108,7 @@ module Puma
106
108
 
107
109
  # @!attribute [r] in_data_phase
108
110
  def in_data_phase
109
- !@read_header
111
+ !(@read_header || @read_proxy)
110
112
  end
111
113
 
112
114
  def set_timeout(val)
@@ -121,6 +123,7 @@ module Puma
121
123
  def reset(fast_check=true)
122
124
  @parser.reset
123
125
  @read_header = true
126
+ @read_proxy = !!@expect_proxy_proto
124
127
  @env = @proto_env.dup
125
128
  @body = nil
126
129
  @tempfile = nil
@@ -131,6 +134,8 @@ module Puma
131
134
  @in_last_chunk = false
132
135
 
133
136
  if @buffer
137
+ return false unless try_to_parse_proxy_protocol
138
+
134
139
  @parsed_bytes = @parser.execute(@env, @buffer, @parsed_bytes)
135
140
 
136
141
  if @parser.finished?
@@ -143,8 +148,7 @@ module Puma
143
148
  return false
144
149
  else
145
150
  begin
146
- if fast_check &&
147
- IO.select([@to_io], nil, nil, FAST_TRACK_KA_TIMEOUT)
151
+ if fast_check && @to_io.wait_readable(FAST_TRACK_KA_TIMEOUT)
148
152
  return try_to_finish
149
153
  end
150
154
  rescue IOError
@@ -157,13 +161,37 @@ module Puma
157
161
  def close
158
162
  begin
159
163
  @io.close
160
- rescue IOError
161
- Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
164
+ rescue IOError, Errno::EBADF
165
+ Puma::Util.purge_interrupt_queue
166
+ end
167
+ end
168
+
169
+ # If necessary, read the PROXY protocol from the buffer. Returns
170
+ # false if more data is needed.
171
+ def try_to_parse_proxy_protocol
172
+ if @read_proxy
173
+ if @expect_proxy_proto == :v1
174
+ if @buffer.include? "\r\n"
175
+ if md = PROXY_PROTOCOL_V1_REGEX.match(@buffer)
176
+ if md[1]
177
+ @peerip = md[1].split(" ")[0]
178
+ end
179
+ @buffer = md.post_match
180
+ end
181
+ # if the buffer has a \r\n but doesn't have a PROXY protocol
182
+ # request, this is just HTTP from a non-PROXY client; move on
183
+ @read_proxy = false
184
+ return @buffer.size > 0
185
+ else
186
+ return false
187
+ end
188
+ end
162
189
  end
190
+ true
163
191
  end
164
192
 
165
193
  def try_to_finish
166
- return read_body unless @read_header
194
+ return read_body if in_data_phase
167
195
 
168
196
  begin
169
197
  data = @io.read_nonblock(CHUNK_SIZE)
@@ -188,6 +216,8 @@ module Puma
188
216
  @buffer = data
189
217
  end
190
218
 
219
+ return false unless try_to_parse_proxy_protocol
220
+
191
221
  @parsed_bytes = @parser.execute(@env, @buffer, @parsed_bytes)
192
222
 
193
223
  if @parser.finished?
@@ -202,13 +232,13 @@ module Puma
202
232
 
203
233
  def eagerly_finish
204
234
  return true if @ready
205
- return false unless IO.select([@to_io], nil, nil, 0)
235
+ return false unless @to_io.wait_readable(0)
206
236
  try_to_finish
207
237
  end
208
238
 
209
239
  def finish(timeout)
210
240
  return if @ready
211
- IO.select([@to_io], nil, nil, timeout) || timeout! until try_to_finish
241
+ @to_io.wait_readable(timeout) || timeout! until try_to_finish
212
242
  end
213
243
 
214
244
  def timeout!
@@ -244,6 +274,17 @@ module Puma
244
274
  @parsed_bytes == 0
245
275
  end
246
276
 
277
+ def expect_proxy_proto=(val)
278
+ if val
279
+ if @read_header
280
+ @read_proxy = true
281
+ end
282
+ else
283
+ @read_proxy = false
284
+ end
285
+ @expect_proxy_proto = val
286
+ end
287
+
247
288
  private
248
289
 
249
290
  def setup_body
@@ -309,7 +350,7 @@ module Puma
309
350
 
310
351
  @body_remain = remain
311
352
 
312
- return false
353
+ false
313
354
  end
314
355
 
315
356
  def read_body
@@ -33,9 +33,9 @@ module Puma
33
33
  Signal.trap "SIGINT", "IGNORE"
34
34
  Signal.trap "SIGCHLD", "DEFAULT"
35
35
 
36
- Thread.new do
37
- Puma.set_thread_name "worker check pipe"
38
- IO.select [@check_pipe]
36
+ Thread.new do
37
+ Puma.set_thread_name "wrkr check"
38
+ @check_pipe.wait_readable
39
39
  log "! Detected parent died, dying"
40
40
  exit! 1
41
41
  end
@@ -76,7 +76,7 @@ module Puma
76
76
  end
77
77
 
78
78
  Thread.new do
79
- Puma.set_thread_name "worker fork pipe"
79
+ Puma.set_thread_name "wrkr fork"
80
80
  while (idx = @fork_pipe.gets)
81
81
  idx = idx.to_i
82
82
  if idx == -1 # stop server
@@ -106,7 +106,7 @@ module Puma
106
106
  begin
107
107
  @worker_write << "b#{Process.pid}:#{index}\n"
108
108
  rescue SystemCallError, IOError
109
- Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
109
+ Puma::Util.purge_interrupt_queue
110
110
  STDERR.puts "Master seems to have exited, exiting."
111
111
  return
112
112
  end
@@ -114,7 +114,7 @@ module Puma
114
114
  while restart_server.pop
115
115
  server_thread = server.run
116
116
  stat_thread ||= Thread.new(@worker_write) do |io|
117
- Puma.set_thread_name "stat payload"
117
+ Puma.set_thread_name "stat pld"
118
118
  base_payload = "p#{Process.pid}"
119
119
 
120
120
  while true
@@ -127,10 +127,10 @@ module Puma
127
127
  payload = %Q!#{base_payload}{ "backlog":#{b}, "running":#{r}, "pool_capacity":#{t}, "max_threads": #{m}, "requests_count": #{rc} }\n!
128
128
  io << payload
129
129
  rescue IOError
130
- Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
130
+ Puma::Util.purge_interrupt_queue
131
131
  break
132
132
  end
133
- sleep Const::WORKER_CHECK_INTERVAL
133
+ sleep @options[:worker_check_interval]
134
134
  end
135
135
  end
136
136
  server_thread.join
@@ -168,16 +168,6 @@ module Puma
168
168
  @launcher.config.run_hooks :after_worker_fork, idx, @launcher.events
169
169
  pid
170
170
  end
171
-
172
- def wakeup!
173
- return unless @wakeup
174
-
175
- begin
176
- @wakeup.write "!" unless @wakeup.closed?
177
- rescue SystemCallError, IOError
178
- Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
179
- end
180
- end
181
171
  end
182
172
  end
183
173
  end
@@ -40,6 +40,10 @@ module Puma
40
40
  @stage = :booted
41
41
  end
42
42
 
43
+ def term!
44
+ @term = true
45
+ end
46
+
43
47
  def term?
44
48
  @term
45
49
  end
data/lib/puma/cluster.rb CHANGED
@@ -108,24 +108,42 @@ module Puma
108
108
  def cull_workers
109
109
  diff = @workers.size - @options[:workers]
110
110
  return if diff < 1
111
+ debug "Culling #{diff} workers"
111
112
 
112
- debug "Culling #{diff.inspect} workers"
113
+ workers = workers_to_cull(diff)
114
+ debug "Workers to cull: #{workers.inspect}"
113
115
 
114
- workers_to_cull = @workers[-diff,diff]
115
- debug "Workers to cull: #{workers_to_cull.inspect}"
116
-
117
- workers_to_cull.each do |worker|
116
+ workers.each do |worker|
118
117
  log "- Worker #{worker.index} (PID: #{worker.pid}) terminating"
119
118
  worker.term
120
119
  end
121
120
  end
122
121
 
122
+ def workers_to_cull(diff)
123
+ workers = @workers.sort_by(&:started_at)
124
+
125
+ # In fork_worker mode, worker 0 acts as our master process.
126
+ # We should avoid culling it to preserve copy-on-write memory gains.
127
+ workers.reject! { |w| w.index == 0 } if @options[:fork_worker]
128
+
129
+ workers[cull_start_index(diff), diff]
130
+ end
131
+
132
+ def cull_start_index(diff)
133
+ case @options[:worker_culling_strategy]
134
+ when :oldest
135
+ 0
136
+ else # :youngest
137
+ -diff
138
+ end
139
+ end
140
+
123
141
  # @!attribute [r] next_worker_index
124
142
  def next_worker_index
125
- all_positions = 0...@options[:workers]
126
- occupied_positions = @workers.map { |w| w.index }
127
- available_positions = all_positions.to_a - occupied_positions
128
- available_positions.first
143
+ occupied_positions = @workers.map(&:index)
144
+ idx = 0
145
+ idx += 1 until !occupied_positions.include?(idx)
146
+ idx
129
147
  end
130
148
 
131
149
  def all_workers_booted?
@@ -135,7 +153,7 @@ module Puma
135
153
  def check_workers
136
154
  return if @next_check >= Time.now
137
155
 
138
- @next_check = Time.now + Const::WORKER_CHECK_INTERVAL
156
+ @next_check = Time.now + @options[:worker_check_interval]
139
157
 
140
158
  timeout_workers
141
159
  wait_workers
@@ -164,16 +182,6 @@ module Puma
164
182
  ].compact.min
165
183
  end
166
184
 
167
- def wakeup!
168
- return unless @wakeup
169
-
170
- begin
171
- @wakeup.write "!" unless @wakeup.closed?
172
- rescue SystemCallError, IOError
173
- Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
174
- end
175
- end
176
-
177
185
  def worker(index, master)
178
186
  @workers = []
179
187
 
@@ -426,9 +434,7 @@ module Puma
426
434
 
427
435
  check_workers
428
436
 
429
- res = IO.select([read], nil, nil, [0, @next_check - Time.now].max)
430
-
431
- if res
437
+ if read.wait_readable([0, @next_check - Time.now].max)
432
438
  req = read.read_nonblock(1)
433
439
 
434
440
  @next_check = Time.now if req == "!"
@@ -452,7 +458,7 @@ module Puma
452
458
  workers_not_booted -= 1
453
459
  when "e"
454
460
  # external term, see worker method, Signal.trap "SIGTERM"
455
- w.instance_variable_set :@term, true
461
+ w.term!
456
462
  when "t"
457
463
  w.term unless w.term?
458
464
  when "p"
File without changes
@@ -11,6 +11,7 @@ module Puma
11
11
 
12
12
  DefaultTCPHost = "0.0.0.0"
13
13
  DefaultTCPPort = 9292
14
+ DefaultWorkerCheckInterval = 5
14
15
  DefaultWorkerTimeout = 60
15
16
  DefaultWorkerShutdownTimeout = 30
16
17
  end
@@ -195,12 +196,14 @@ module Puma
195
196
  :workers => Integer(ENV['WEB_CONCURRENCY'] || 0),
196
197
  :silence_single_worker_warning => false,
197
198
  :mode => :http,
199
+ :worker_check_interval => DefaultWorkerCheckInterval,
198
200
  :worker_timeout => DefaultWorkerTimeout,
199
201
  :worker_boot_timeout => DefaultWorkerTimeout,
200
202
  :worker_shutdown_timeout => DefaultWorkerShutdownTimeout,
203
+ :worker_culling_strategy => :youngest,
201
204
  :remote_address => :socket,
202
205
  :tag => method(:infer_tag),
203
- :environment => -> { ENV['RACK_ENV'] || ENV['RAILS_ENV'] || "development" },
206
+ :environment => -> { ENV['APP_ENV'] || ENV['RACK_ENV'] || ENV['RAILS_ENV'] || 'development' },
204
207
  :rackup => DefaultRackup,
205
208
  :logger => STDOUT,
206
209
  :persistent_timeout => Const::PERSISTENT_TIMEOUT,
data/lib/puma/const.rb CHANGED
@@ -100,8 +100,8 @@ module Puma
100
100
  # too taxing on performance.
101
101
  module Const
102
102
 
103
- PUMA_VERSION = VERSION = "5.3.2".freeze
104
- CODE_NAME = "Sweetnighter".freeze
103
+ PUMA_VERSION = VERSION = "5.6.2".freeze
104
+ CODE_NAME = "Birdie's Version".freeze
105
105
 
106
106
  PUMA_SERVER_STRING = ['puma', PUMA_VERSION, CODE_NAME].join(' ').freeze
107
107
 
@@ -235,9 +235,6 @@ module Puma
235
235
 
236
236
  EARLY_HINTS = "rack.early_hints".freeze
237
237
 
238
- # Minimum interval to checks worker health
239
- WORKER_CHECK_INTERVAL = 5
240
-
241
238
  # Illegal character in the key or value of response header
242
239
  DQUOTE = "\"".freeze
243
240
  HTTP_HEADER_DELIMITER = Regexp.escape("(),/:;<=>?@[]{}\\").freeze
@@ -247,5 +244,7 @@ module Puma
247
244
 
248
245
  # Banned keys of response header
249
246
  BANNED_HEADER_KEY = /\A(rack\.|status\z)/.freeze
247
+
248
+ PROXY_PROTOCOL_V1_REGEX = /^PROXY (?:TCP4|TCP6|UNKNOWN) ([^\r]+)\r\n/.freeze
250
249
  end
251
250
  end
@@ -47,7 +47,7 @@ module Puma
47
47
  @control_auth_token = nil
48
48
  @config_file = nil
49
49
  @command = nil
50
- @environment = ENV['RACK_ENV'] || ENV['RAILS_ENV']
50
+ @environment = ENV['APP_ENV'] || ENV['RACK_ENV'] || ENV['RAILS_ENV']
51
51
 
52
52
  @argv = argv.dup
53
53
  @stdout = stdout
data/lib/puma/detect.rb CHANGED
@@ -10,8 +10,10 @@ module Puma
10
10
 
11
11
  IS_JRUBY = Object.const_defined? :JRUBY_VERSION
12
12
 
13
- IS_WINDOWS = !!(RUBY_PLATFORM =~ /mswin|ming|cygwin/ ||
14
- IS_JRUBY && RUBY_DESCRIPTION =~ /mswin/)
13
+ IS_OSX = RUBY_PLATFORM.include? 'darwin'
14
+
15
+ IS_WINDOWS = !!(RUBY_PLATFORM =~ /mswin|ming|cygwin/) ||
16
+ IS_JRUBY && RUBY_DESCRIPTION.include?('mswin')
15
17
 
16
18
  # @version 5.2.0
17
19
  IS_MRI = (RUBY_ENGINE == 'ruby' || RUBY_ENGINE.nil?)
@@ -20,6 +22,10 @@ module Puma
20
22
  IS_JRUBY
21
23
  end
22
24
 
25
+ def self.osx?
26
+ IS_OSX
27
+ end
28
+
23
29
  def self.windows?
24
30
  IS_WINDOWS
25
31
  end
data/lib/puma/dsl.rb CHANGED
@@ -48,6 +48,8 @@ module Puma
48
48
 
49
49
  ca_additions = "&ca=#{opts[:ca]}" if ['peer', 'force_peer'].include?(verify)
50
50
 
51
+ backlog_str = opts[:backlog] ? "&backlog=#{Integer(opts[:backlog])}" : ''
52
+
51
53
  if defined?(JRUBY_VERSION)
52
54
  ssl_cipher_list = opts[:ssl_cipher_list] ?
53
55
  "&ssl_cipher_list=#{opts[:ssl_cipher_list]}" : nil
@@ -55,7 +57,7 @@ module Puma
55
57
  keystore_additions = "keystore=#{opts[:keystore]}&keystore-pass=#{opts[:keystore_pass]}"
56
58
 
57
59
  "ssl://#{host}:#{port}?#{keystore_additions}#{ssl_cipher_list}" \
58
- "&verify_mode=#{verify}#{tls_str}#{ca_additions}"
60
+ "&verify_mode=#{verify}#{tls_str}#{ca_additions}#{backlog_str}"
59
61
  else
60
62
  ssl_cipher_filter = opts[:ssl_cipher_filter] ?
61
63
  "&ssl_cipher_filter=#{opts[:ssl_cipher_filter]}" : nil
@@ -64,7 +66,7 @@ module Puma
64
66
  "&verification_flags=#{Array(ary).join ','}" : nil
65
67
 
66
68
  "ssl://#{host}:#{port}?cert=#{opts[:cert]}&key=#{opts[:key]}" \
67
- "#{ssl_cipher_filter}&verify_mode=#{verify}#{tls_str}#{ca_additions}#{v_flags}"
69
+ "#{ssl_cipher_filter}&verify_mode=#{verify}#{tls_str}#{ca_additions}#{v_flags}#{backlog_str}"
68
70
  end
69
71
  end
70
72
 
@@ -191,7 +193,7 @@ module Puma
191
193
  end
192
194
 
193
195
  # Bind the server to +url+. "tcp://", "unix://" and "ssl://" are the only
194
- # accepted protocols. Multiple urls can be bound to, calling `bind` does
196
+ # accepted protocols. Multiple urls can be bound to, calling +bind+ does
195
197
  # not overwrite previous bindings.
196
198
  #
197
199
  # The default is "tcp://0.0.0.0:9292".
@@ -381,6 +383,13 @@ module Puma
381
383
  @options[:rackup] ||= path.to_s
382
384
  end
383
385
 
386
+ # Allows setting `env['rack.url_scheme']`.
387
+ # Only necessary if X-Forwarded-Proto is not being set by your proxy
388
+ # Normal values are 'http' or 'https'.
389
+ def rack_url_scheme(scheme=nil)
390
+ @options[:rack_url_scheme] = scheme
391
+ end
392
+
384
393
  def early_hints(answer=true)
385
394
  @options[:early_hints] = answer
386
395
  end
@@ -429,8 +438,15 @@ module Puma
429
438
  @options[:max_threads] = max
430
439
  end
431
440
 
432
- # Instead of `bind 'ssl://127.0.0.1:9292?key=key_path&cert=cert_path'` you
433
- # can also use the this method.
441
+ # Instead of using +bind+ and manually constructing a URI like:
442
+ #
443
+ # bind 'ssl://127.0.0.1:9292?key=key_path&cert=cert_path'
444
+ #
445
+ # you can use the this method.
446
+ #
447
+ # When binding on localhost you don't need to specify +cert+ and +key+,
448
+ # Puma will assume you are using the +localhost+ gem and try to load the
449
+ # appropriate files.
434
450
  #
435
451
  # @example
436
452
  # ssl_bind '127.0.0.1', '9292', {
@@ -440,14 +456,25 @@ module Puma
440
456
  # verify_mode: verify_mode, # default 'none'
441
457
  # verification_flags: flags, # optional, not supported by JRuby
442
458
  # }
443
- # @example For JRuby, two keys are required: keystore & keystore_pass.
459
+ #
460
+ # @example Using self-signed certificate with the +localhost+ gem:
461
+ # ssl_bind '127.0.0.1', '9292'
462
+ #
463
+ # @example Alternatively, you can provide +cert_pem+ and +key_pem+:
464
+ # ssl_bind '127.0.0.1', '9292', {
465
+ # cert_pem: File.read(path_to_cert),
466
+ # key_pem: File.read(path_to_key),
467
+ # }
468
+ #
469
+ # @example For JRuby, two keys are required: +keystore+ & +keystore_pass+
444
470
  # ssl_bind '127.0.0.1', '9292', {
445
471
  # keystore: path_to_keystore,
446
472
  # keystore_pass: password,
447
473
  # ssl_cipher_list: cipher_list, # optional
448
474
  # verify_mode: verify_mode # default 'none'
449
475
  # }
450
- def ssl_bind(host, port, opts)
476
+ def ssl_bind(host, port, opts = {})
477
+ add_pem_values_to_options_store(opts)
451
478
  bind self.class.ssl_bind_str(host, port, opts)
452
479
  end
453
480
 
@@ -578,7 +605,7 @@ module Puma
578
605
  # end
579
606
  def after_worker_fork(&block)
580
607
  @options[:after_worker_fork] ||= []
581
- @options[:after_worker_fork] = block
608
+ @options[:after_worker_fork] << block
582
609
  end
583
610
 
584
611
  alias_method :after_worker_boot, :after_worker_fork
@@ -720,6 +747,19 @@ module Puma
720
747
  @options[:tag] = string.to_s
721
748
  end
722
749
 
750
+ # Change the default interval for checking workers.
751
+ #
752
+ # The default value is 5 seconds.
753
+ #
754
+ # @note Cluster mode only.
755
+ # @example
756
+ # worker_check_interval 5
757
+ # @see Puma::Cluster#check_workers
758
+ #
759
+ def worker_check_interval(interval)
760
+ @options[:worker_check_interval] = Integer(interval)
761
+ end
762
+
723
763
  # Verifies that all workers have checked in to the master process within
724
764
  # the given timeout. If not the worker process will be restarted. This is
725
765
  # not a request timeout, it is to protect against a hung or dead process.
@@ -734,7 +774,7 @@ module Puma
734
774
  #
735
775
  def worker_timeout(timeout)
736
776
  timeout = Integer(timeout)
737
- min = Const::WORKER_CHECK_INTERVAL
777
+ min = @options.fetch(:worker_check_interval, Puma::ConfigDefault::DefaultWorkerCheckInterval)
738
778
 
739
779
  if timeout <= min
740
780
  raise "The minimum worker_timeout must be greater than the worker reporting interval (#{min})"
@@ -766,6 +806,30 @@ module Puma
766
806
  @options[:worker_shutdown_timeout] = Integer(timeout)
767
807
  end
768
808
 
809
+ # Set the strategy for worker culling.
810
+ #
811
+ # There are two possible values:
812
+ #
813
+ # 1. **:youngest** - the youngest workers (i.e. the workers that were
814
+ # the most recently started) will be culled.
815
+ # 2. **:oldest** - the oldest workers (i.e. the workers that were started
816
+ # the longest time ago) will be culled.
817
+ #
818
+ # @note Cluster mode only.
819
+ # @example
820
+ # worker_culling_strategy :oldest
821
+ # @see Puma::Cluster#cull_workers
822
+ #
823
+ def worker_culling_strategy(strategy)
824
+ stategy = strategy.to_sym
825
+
826
+ if ![:youngest, :oldest].include?(strategy)
827
+ raise "Invalid value for worker_culling_strategy - #{stategy}"
828
+ end
829
+
830
+ @options[:worker_culling_strategy] = strategy
831
+ end
832
+
769
833
  # When set to true (the default), workers accept all requests
770
834
  # and queue them before passing them to the handlers.
771
835
  # When set to false, each worker process accepts exactly as
@@ -811,7 +875,7 @@ module Puma
811
875
  # a kernel syscall is required which for very fast rack handlers
812
876
  # slows down the handling significantly.
813
877
  #
814
- # There are 4 possible values:
878
+ # There are 5 possible values:
815
879
  #
816
880
  # 1. **:socket** (the default) - read the peername from the socket using the
817
881
  # syscall. This is the normal behavior.
@@ -821,7 +885,10 @@ module Puma
821
885
  # `set_remote_address header: "X-Real-IP"`.
822
886
  # Only the first word (as separated by spaces or comma) is used, allowing
823
887
  # headers such as X-Forwarded-For to be used as well.
824
- # 4. **\<Any string\>** - this allows you to hardcode remote address to any value
888
+ # 4. **proxy_protocol: :v1**- set the remote address to the value read from the
889
+ # HAproxy PROXY protocol, version 1. If the request does not have the PROXY
890
+ # protocol attached to it, will fall back to :socket
891
+ # 5. **\<Any string\>** - this allows you to hardcode remote address to any value
825
892
  # you wish. Because Puma never uses this field anyway, it's format is
826
893
  # entirely in your hands.
827
894
  #
@@ -839,6 +906,13 @@ module Puma
839
906
  if hdr = val[:header]
840
907
  @options[:remote_address] = :header
841
908
  @options[:remote_address_header] = "HTTP_" + hdr.upcase.tr("-", "_")
909
+ elsif protocol_version = val[:proxy_protocol]
910
+ @options[:remote_address] = :proxy_protocol
911
+ protocol_version = protocol_version.downcase.to_sym
912
+ unless [:v1].include?(protocol_version)
913
+ raise "Invalid value for proxy_protocol - #{protocol_version.inspect}"
914
+ end
915
+ @options[:remote_address_proxy_protocol] = protocol_version
842
916
  else
843
917
  raise "Invalid value for set_remote_address - #{val.inspect}"
844
918
  end
@@ -910,5 +984,25 @@ module Puma
910
984
  def mutate_stdout_and_stderr_to_sync_on_write(enabled=true)
911
985
  @options[:mutate_stdout_and_stderr_to_sync_on_write] = enabled
912
986
  end
987
+
988
+ private
989
+
990
+ # To avoid adding cert_pem and key_pem as URI params, we store them on the
991
+ # options[:store] from where Puma binder knows how to find and extract them.
992
+ def add_pem_values_to_options_store(opts)
993
+ return if defined?(JRUBY_VERSION)
994
+
995
+ @options[:store] ||= []
996
+
997
+ # Store cert_pem and key_pem to options[:store] if present
998
+ [:cert, :key].each do |v|
999
+ opt_key = :"#{v}_pem"
1000
+ if opts[opt_key]
1001
+ index = @options[:store].length
1002
+ @options[:store] << opts[opt_key]
1003
+ opts[v] = "store:#{index}"
1004
+ end
1005
+ end
1006
+ end
913
1007
  end
914
1008
  end
File without changes
data/lib/puma/events.rb CHANGED
File without changes
File without changes
File without changes