puma 5.3.1 → 5.6.4

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 (79) hide show
  1. checksums.yaml +4 -4
  2. data/History.md +152 -5
  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 +51 -6
  40. data/lib/puma/cli.rb +14 -4
  41. data/lib/puma/client.rb +106 -21
  42. data/lib/puma/cluster/worker.rb +14 -17
  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 +6 -1
  47. data/lib/puma/const.rb +9 -8
  48. data/lib/puma/control_cli.rb +1 -1
  49. data/lib/puma/detect.rb +8 -2
  50. data/lib/puma/dsl.rb +106 -12
  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 +27 -11
  68. data/lib/puma/runner.rb +22 -8
  69. data/lib/puma/server.rb +45 -44
  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
@@ -23,6 +23,8 @@ module Puma
23
23
 
24
24
  class ConnectionError < RuntimeError; end
25
25
 
26
+ class HttpParserError501 < IOError; end
27
+
26
28
  # An instance of this class represents a unique request from a client.
27
29
  # For example, this could be a web request from a browser or from CURL.
28
30
  #
@@ -35,7 +37,21 @@ module Puma
35
37
  # Instances of this class are responsible for knowing if
36
38
  # the header and body are fully buffered via the `try_to_finish` method.
37
39
  # They can be used to "time out" a response via the `timeout_at` reader.
40
+ #
38
41
  class Client
42
+
43
+ # this tests all values but the last, which must be chunked
44
+ ALLOWED_TRANSFER_ENCODING = %w[compress deflate gzip].freeze
45
+
46
+ # chunked body validation
47
+ CHUNK_SIZE_INVALID = /[^\h]/.freeze
48
+ CHUNK_VALID_ENDING = "\r\n".freeze
49
+
50
+ # Content-Length header value validation
51
+ CONTENT_LENGTH_VALUE_INVALID = /[^\d]/.freeze
52
+
53
+ TE_ERR_MSG = 'Invalid Transfer-Encoding'
54
+
39
55
  # The object used for a request with no body. All requests with
40
56
  # no body share this one object since it has no state.
41
57
  EmptyBody = NullIO.new
@@ -56,6 +72,7 @@ module Puma
56
72
  @parser = HttpParser.new
57
73
  @parsed_bytes = 0
58
74
  @read_header = true
75
+ @read_proxy = false
59
76
  @ready = false
60
77
 
61
78
  @body = nil
@@ -69,7 +86,9 @@ module Puma
69
86
  @hijacked = false
70
87
 
71
88
  @peerip = nil
89
+ @listener = nil
72
90
  @remote_addr_header = nil
91
+ @expect_proxy_proto = false
73
92
 
74
93
  @body_remain = 0
75
94
 
@@ -81,7 +100,7 @@ module Puma
81
100
 
82
101
  attr_writer :peerip
83
102
 
84
- attr_accessor :remote_addr_header
103
+ attr_accessor :remote_addr_header, :listener
85
104
 
86
105
  def_delegators :@io, :closed?
87
106
 
@@ -105,7 +124,7 @@ module Puma
105
124
 
106
125
  # @!attribute [r] in_data_phase
107
126
  def in_data_phase
108
- !@read_header
127
+ !(@read_header || @read_proxy)
109
128
  end
110
129
 
111
130
  def set_timeout(val)
@@ -120,6 +139,7 @@ module Puma
120
139
  def reset(fast_check=true)
121
140
  @parser.reset
122
141
  @read_header = true
142
+ @read_proxy = !!@expect_proxy_proto
123
143
  @env = @proto_env.dup
124
144
  @body = nil
125
145
  @tempfile = nil
@@ -130,6 +150,8 @@ module Puma
130
150
  @in_last_chunk = false
131
151
 
132
152
  if @buffer
153
+ return false unless try_to_parse_proxy_protocol
154
+
133
155
  @parsed_bytes = @parser.execute(@env, @buffer, @parsed_bytes)
134
156
 
135
157
  if @parser.finished?
@@ -142,8 +164,7 @@ module Puma
142
164
  return false
143
165
  else
144
166
  begin
145
- if fast_check &&
146
- IO.select([@to_io], nil, nil, FAST_TRACK_KA_TIMEOUT)
167
+ if fast_check && @to_io.wait_readable(FAST_TRACK_KA_TIMEOUT)
147
168
  return try_to_finish
148
169
  end
149
170
  rescue IOError
@@ -156,13 +177,37 @@ module Puma
156
177
  def close
157
178
  begin
158
179
  @io.close
159
- rescue IOError
160
- Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
180
+ rescue IOError, Errno::EBADF
181
+ Puma::Util.purge_interrupt_queue
161
182
  end
162
183
  end
163
184
 
185
+ # If necessary, read the PROXY protocol from the buffer. Returns
186
+ # false if more data is needed.
187
+ def try_to_parse_proxy_protocol
188
+ if @read_proxy
189
+ if @expect_proxy_proto == :v1
190
+ if @buffer.include? "\r\n"
191
+ if md = PROXY_PROTOCOL_V1_REGEX.match(@buffer)
192
+ if md[1]
193
+ @peerip = md[1].split(" ")[0]
194
+ end
195
+ @buffer = md.post_match
196
+ end
197
+ # if the buffer has a \r\n but doesn't have a PROXY protocol
198
+ # request, this is just HTTP from a non-PROXY client; move on
199
+ @read_proxy = false
200
+ return @buffer.size > 0
201
+ else
202
+ return false
203
+ end
204
+ end
205
+ end
206
+ true
207
+ end
208
+
164
209
  def try_to_finish
165
- return read_body unless @read_header
210
+ return read_body if in_data_phase
166
211
 
167
212
  begin
168
213
  data = @io.read_nonblock(CHUNK_SIZE)
@@ -187,6 +232,8 @@ module Puma
187
232
  @buffer = data
188
233
  end
189
234
 
235
+ return false unless try_to_parse_proxy_protocol
236
+
190
237
  @parsed_bytes = @parser.execute(@env, @buffer, @parsed_bytes)
191
238
 
192
239
  if @parser.finished?
@@ -201,13 +248,13 @@ module Puma
201
248
 
202
249
  def eagerly_finish
203
250
  return true if @ready
204
- return false unless IO.select([@to_io], nil, nil, 0)
251
+ return false unless @to_io.wait_readable(0)
205
252
  try_to_finish
206
253
  end
207
254
 
208
255
  def finish(timeout)
209
256
  return if @ready
210
- IO.select([@to_io], nil, nil, timeout) || timeout! until try_to_finish
257
+ @to_io.wait_readable(timeout) || timeout! until try_to_finish
211
258
  end
212
259
 
213
260
  def timeout!
@@ -243,6 +290,17 @@ module Puma
243
290
  @parsed_bytes == 0
244
291
  end
245
292
 
293
+ def expect_proxy_proto=(val)
294
+ if val
295
+ if @read_header
296
+ @read_proxy = true
297
+ end
298
+ else
299
+ @read_proxy = false
300
+ end
301
+ @expect_proxy_proto = val
302
+ end
303
+
246
304
  private
247
305
 
248
306
  def setup_body
@@ -260,16 +318,27 @@ module Puma
260
318
  body = @parser.body
261
319
 
262
320
  te = @env[TRANSFER_ENCODING2]
263
-
264
321
  if te
265
- if te.include?(",")
266
- te.split(",").each do |part|
267
- if CHUNKED.casecmp(part.strip) == 0
268
- return setup_chunked_body(body)
269
- end
322
+ te_lwr = te.downcase
323
+ if te.include? ','
324
+ te_ary = te_lwr.split ','
325
+ te_count = te_ary.count CHUNKED
326
+ te_valid = te_ary[0..-2].all? { |e| ALLOWED_TRANSFER_ENCODING.include? e }
327
+ if te_ary.last == CHUNKED && te_count == 1 && te_valid
328
+ @env.delete TRANSFER_ENCODING2
329
+ return setup_chunked_body body
330
+ elsif te_count >= 1
331
+ raise HttpParserError , "#{TE_ERR_MSG}, multiple chunked: '#{te}'"
332
+ elsif !te_valid
333
+ raise HttpParserError501, "#{TE_ERR_MSG}, unknown value: '#{te}'"
270
334
  end
271
- elsif CHUNKED.casecmp(te) == 0
272
- return setup_chunked_body(body)
335
+ elsif te_lwr == CHUNKED
336
+ @env.delete TRANSFER_ENCODING2
337
+ return setup_chunked_body body
338
+ elsif ALLOWED_TRANSFER_ENCODING.include? te_lwr
339
+ raise HttpParserError , "#{TE_ERR_MSG}, single value must be chunked: '#{te}'"
340
+ else
341
+ raise HttpParserError501 , "#{TE_ERR_MSG}, unknown value: '#{te}'"
273
342
  end
274
343
  end
275
344
 
@@ -277,7 +346,12 @@ module Puma
277
346
 
278
347
  cl = @env[CONTENT_LENGTH]
279
348
 
280
- unless cl
349
+ if cl
350
+ # cannot contain characters that are not \d
351
+ if cl =~ CONTENT_LENGTH_VALUE_INVALID
352
+ raise HttpParserError, "Invalid Content-Length: #{cl.inspect}"
353
+ end
354
+ else
281
355
  @buffer = body.empty? ? nil : body
282
356
  @body = EmptyBody
283
357
  set_ready
@@ -308,7 +382,7 @@ module Puma
308
382
 
309
383
  @body_remain = remain
310
384
 
311
- return false
385
+ false
312
386
  end
313
387
 
314
388
  def read_body
@@ -436,7 +510,13 @@ module Puma
436
510
  while !io.eof?
437
511
  line = io.gets
438
512
  if line.end_with?("\r\n")
439
- len = line.strip.to_i(16)
513
+ # Puma doesn't process chunk extensions, but should parse if they're
514
+ # present, which is the reason for the semicolon regex
515
+ chunk_hex = line.strip[/\A[^;]+/]
516
+ if chunk_hex =~ CHUNK_SIZE_INVALID
517
+ raise HttpParserError, "Invalid chunk size: '#{chunk_hex}'"
518
+ end
519
+ len = chunk_hex.to_i(16)
440
520
  if len == 0
441
521
  @in_last_chunk = true
442
522
  @body.rewind
@@ -467,7 +547,12 @@ module Puma
467
547
 
468
548
  case
469
549
  when got == len
470
- write_chunk(part[0..-3]) # to skip the ending \r\n
550
+ # proper chunked segment must end with "\r\n"
551
+ if part.end_with? CHUNK_VALID_ENDING
552
+ write_chunk(part[0..-3]) # to skip the ending \r\n
553
+ else
554
+ raise HttpParserError, "Chunk size mismatch"
555
+ end
471
556
  when got <= len - 2
472
557
  write_chunk(part)
473
558
  @partial_part_left = len - part.size
@@ -34,8 +34,8 @@ module Puma
34
34
  Signal.trap "SIGCHLD", "DEFAULT"
35
35
 
36
36
  Thread.new do
37
- Puma.set_thread_name "worker check pipe"
38
- IO.select [@check_pipe]
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
@@ -54,7 +54,14 @@ module Puma
54
54
  # things in shape before booting the app.
55
55
  @launcher.config.run_hooks :before_worker_boot, index, @launcher.events
56
56
 
57
+ begin
57
58
  server = @server ||= start_server
59
+ rescue Exception => e
60
+ log "! Unable to start worker"
61
+ log e.backtrace[0]
62
+ exit 1
63
+ end
64
+
58
65
  restart_server = Queue.new << true << false
59
66
 
60
67
  fork_worker = @options[:fork_worker] && index == 0
@@ -69,7 +76,7 @@ module Puma
69
76
  end
70
77
 
71
78
  Thread.new do
72
- Puma.set_thread_name "worker fork pipe"
79
+ Puma.set_thread_name "wrkr fork"
73
80
  while (idx = @fork_pipe.gets)
74
81
  idx = idx.to_i
75
82
  if idx == -1 # stop server
@@ -99,7 +106,7 @@ module Puma
99
106
  begin
100
107
  @worker_write << "b#{Process.pid}:#{index}\n"
101
108
  rescue SystemCallError, IOError
102
- Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
109
+ Puma::Util.purge_interrupt_queue
103
110
  STDERR.puts "Master seems to have exited, exiting."
104
111
  return
105
112
  end
@@ -107,7 +114,7 @@ module Puma
107
114
  while restart_server.pop
108
115
  server_thread = server.run
109
116
  stat_thread ||= Thread.new(@worker_write) do |io|
110
- Puma.set_thread_name "stat payload"
117
+ Puma.set_thread_name "stat pld"
111
118
  base_payload = "p#{Process.pid}"
112
119
 
113
120
  while true
@@ -120,10 +127,10 @@ module Puma
120
127
  payload = %Q!#{base_payload}{ "backlog":#{b}, "running":#{r}, "pool_capacity":#{t}, "max_threads": #{m}, "requests_count": #{rc} }\n!
121
128
  io << payload
122
129
  rescue IOError
123
- Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
130
+ Puma::Util.purge_interrupt_queue
124
131
  break
125
132
  end
126
- sleep Const::WORKER_CHECK_INTERVAL
133
+ sleep @options[:worker_check_interval]
127
134
  end
128
135
  end
129
136
  server_thread.join
@@ -161,16 +168,6 @@ module Puma
161
168
  @launcher.config.run_hooks :after_worker_fork, idx, @launcher.events
162
169
  pid
163
170
  end
164
-
165
- def wakeup!
166
- return unless @wakeup
167
-
168
- begin
169
- @wakeup.write "!" unless @wakeup.closed?
170
- rescue SystemCallError, IOError
171
- Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
172
- end
173
- end
174
171
  end
175
172
  end
176
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,
@@ -343,6 +346,8 @@ module Puma
343
346
  raise "Missing rackup file '#{rackup}'" unless File.exist?(rackup)
344
347
 
345
348
  rack_app, rack_options = rack_builder.parse_file(rackup)
349
+ rack_options = rack_options || {}
350
+
346
351
  @options.file_options.merge!(rack_options)
347
352
 
348
353
  config_ru_binds = []
data/lib/puma/const.rb CHANGED
@@ -76,7 +76,7 @@ module Puma
76
76
  508 => 'Loop Detected',
77
77
  510 => 'Not Extended',
78
78
  511 => 'Network Authentication Required'
79
- }
79
+ }.freeze
80
80
 
81
81
  # For some HTTP status codes the client only expects headers.
82
82
  #
@@ -85,7 +85,7 @@ module Puma
85
85
  204 => true,
86
86
  205 => true,
87
87
  304 => true
88
- }
88
+ }.freeze
89
89
 
90
90
  # Frequently used constants when constructing requests or responses. Many times
91
91
  # the constant just refers to a string with the same contents. Using these constants
@@ -100,8 +100,8 @@ module Puma
100
100
  # too taxing on performance.
101
101
  module Const
102
102
 
103
- PUMA_VERSION = VERSION = "5.3.1".freeze
104
- CODE_NAME = "Sweetnighter".freeze
103
+ PUMA_VERSION = VERSION = "5.6.4".freeze
104
+ CODE_NAME = "Birdie's Version".freeze
105
105
 
106
106
  PUMA_SERVER_STRING = ['puma', PUMA_VERSION, CODE_NAME].join(' ').freeze
107
107
 
@@ -145,9 +145,11 @@ module Puma
145
145
  408 => "HTTP/1.1 408 Request Timeout\r\nConnection: close\r\nServer: Puma #{PUMA_VERSION}\r\n\r\n".freeze,
146
146
  # Indicate that there was an internal error, obviously.
147
147
  500 => "HTTP/1.1 500 Internal Server Error\r\n\r\n".freeze,
148
+ # Incorrect or invalid header value
149
+ 501 => "HTTP/1.1 501 Not Implemented\r\n\r\n".freeze,
148
150
  # A common header for indicating the server is too busy. Not used yet.
149
151
  503 => "HTTP/1.1 503 Service Unavailable\r\n\r\nBUSY".freeze
150
- }
152
+ }.freeze
151
153
 
152
154
  # The basic max request size we'll try to read.
153
155
  CHUNK_SIZE = 16 * 1024
@@ -235,9 +237,6 @@ module Puma
235
237
 
236
238
  EARLY_HINTS = "rack.early_hints".freeze
237
239
 
238
- # Minimum interval to checks worker health
239
- WORKER_CHECK_INTERVAL = 5
240
-
241
240
  # Illegal character in the key or value of response header
242
241
  DQUOTE = "\"".freeze
243
242
  HTTP_HEADER_DELIMITER = Regexp.escape("(),/:;<=>?@[]{}\\").freeze
@@ -247,5 +246,7 @@ module Puma
247
246
 
248
247
  # Banned keys of response header
249
248
  BANNED_HEADER_KEY = /\A(rack\.|status\z)/.freeze
249
+
250
+ PROXY_PROTOCOL_V1_REGEX = /^PROXY (?:TCP4|TCP6|UNKNOWN) ([^\r]+)\r\n/.freeze
250
251
  end
251
252
  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