puma 5.5.0 → 5.6.7
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.
- checksums.yaml +4 -4
- data/History.md +140 -3
- data/README.md +28 -6
- data/docs/architecture.md +49 -16
- data/docs/compile_options.md +4 -2
- data/docs/deployment.md +53 -52
- data/docs/plugins.md +15 -15
- data/docs/rails_dev_mode.md +2 -3
- data/docs/restart.md +6 -6
- data/docs/signals.md +11 -10
- data/docs/stats.md +8 -8
- data/docs/systemd.md +63 -67
- data/ext/puma_http11/extconf.rb +18 -7
- data/ext/puma_http11/http11_parser.c +23 -10
- data/ext/puma_http11/http11_parser_common.rl +1 -1
- data/ext/puma_http11/mini_ssl.c +75 -12
- data/ext/puma_http11/org/jruby/puma/Http11Parser.java +49 -47
- data/ext/puma_http11/org/jruby/puma/MiniSSL.java +38 -55
- data/ext/puma_http11/puma_http11.c +1 -1
- data/lib/puma/app/status.rb +3 -0
- data/lib/puma/binder.rb +20 -6
- data/lib/puma/cli.rb +9 -4
- data/lib/puma/client.rb +68 -18
- data/lib/puma/cluster/worker.rb +7 -17
- data/lib/puma/cluster/worker_handle.rb +4 -0
- data/lib/puma/cluster.rb +29 -21
- data/lib/puma/configuration.rb +4 -1
- data/lib/puma/const.rb +7 -8
- data/lib/puma/control_cli.rb +19 -13
- data/lib/puma/detect.rb +8 -2
- data/lib/puma/dsl.rb +91 -10
- data/lib/puma/launcher.rb +13 -1
- data/lib/puma/minissl/context_builder.rb +8 -6
- data/lib/puma/minissl.rb +28 -7
- data/lib/puma/null_io.rb +5 -0
- data/lib/puma/plugin.rb +1 -1
- data/lib/puma/request.rb +15 -6
- data/lib/puma/runner.rb +22 -8
- data/lib/puma/server.rb +29 -30
- data/lib/puma/state_file.rb +42 -7
- data/lib/puma/thread_pool.rb +2 -2
- data/lib/puma/util.rb +19 -3
- data/lib/puma.rb +5 -3
- data/lib/rack/version_restriction.rb +15 -0
- data/tools/Dockerfile +1 -1
- metadata +4 -3
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,22 @@ 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 = Const::LINE_END
|
49
|
+
CHUNK_VALID_ENDING_SIZE = CHUNK_VALID_ENDING.bytesize
|
50
|
+
|
51
|
+
# Content-Length header value validation
|
52
|
+
CONTENT_LENGTH_VALUE_INVALID = /[^\d]/.freeze
|
53
|
+
|
54
|
+
TE_ERR_MSG = 'Invalid Transfer-Encoding'
|
55
|
+
|
39
56
|
# The object used for a request with no body. All requests with
|
40
57
|
# no body share this one object since it has no state.
|
41
58
|
EmptyBody = NullIO.new
|
@@ -161,8 +178,8 @@ module Puma
|
|
161
178
|
def close
|
162
179
|
begin
|
163
180
|
@io.close
|
164
|
-
rescue IOError
|
165
|
-
|
181
|
+
rescue IOError, Errno::EBADF
|
182
|
+
Puma::Util.purge_interrupt_queue
|
166
183
|
end
|
167
184
|
end
|
168
185
|
|
@@ -302,16 +319,27 @@ module Puma
|
|
302
319
|
body = @parser.body
|
303
320
|
|
304
321
|
te = @env[TRANSFER_ENCODING2]
|
305
|
-
|
306
322
|
if te
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
|
323
|
+
te_lwr = te.downcase
|
324
|
+
if te.include? ','
|
325
|
+
te_ary = te_lwr.split ','
|
326
|
+
te_count = te_ary.count CHUNKED
|
327
|
+
te_valid = te_ary[0..-2].all? { |e| ALLOWED_TRANSFER_ENCODING.include? e }
|
328
|
+
if te_ary.last == CHUNKED && te_count == 1 && te_valid
|
329
|
+
@env.delete TRANSFER_ENCODING2
|
330
|
+
return setup_chunked_body body
|
331
|
+
elsif te_count >= 1
|
332
|
+
raise HttpParserError , "#{TE_ERR_MSG}, multiple chunked: '#{te}'"
|
333
|
+
elsif !te_valid
|
334
|
+
raise HttpParserError501, "#{TE_ERR_MSG}, unknown value: '#{te}'"
|
312
335
|
end
|
313
|
-
elsif
|
314
|
-
|
336
|
+
elsif te_lwr == CHUNKED
|
337
|
+
@env.delete TRANSFER_ENCODING2
|
338
|
+
return setup_chunked_body body
|
339
|
+
elsif ALLOWED_TRANSFER_ENCODING.include? te_lwr
|
340
|
+
raise HttpParserError , "#{TE_ERR_MSG}, single value must be chunked: '#{te}'"
|
341
|
+
else
|
342
|
+
raise HttpParserError501 , "#{TE_ERR_MSG}, unknown value: '#{te}'"
|
315
343
|
end
|
316
344
|
end
|
317
345
|
|
@@ -319,7 +347,12 @@ module Puma
|
|
319
347
|
|
320
348
|
cl = @env[CONTENT_LENGTH]
|
321
349
|
|
322
|
-
|
350
|
+
if cl
|
351
|
+
# cannot contain characters that are not \d, or be empty
|
352
|
+
if cl =~ CONTENT_LENGTH_VALUE_INVALID || cl.empty?
|
353
|
+
raise HttpParserError, "Invalid Content-Length: #{cl.inspect}"
|
354
|
+
end
|
355
|
+
else
|
323
356
|
@buffer = body.empty? ? nil : body
|
324
357
|
@body = EmptyBody
|
325
358
|
set_ready
|
@@ -477,19 +510,31 @@ module Puma
|
|
477
510
|
|
478
511
|
while !io.eof?
|
479
512
|
line = io.gets
|
480
|
-
if line.end_with?(
|
481
|
-
|
513
|
+
if line.end_with?(CHUNK_VALID_ENDING)
|
514
|
+
# Puma doesn't process chunk extensions, but should parse if they're
|
515
|
+
# present, which is the reason for the semicolon regex
|
516
|
+
chunk_hex = line.strip[/\A[^;]+/]
|
517
|
+
if chunk_hex =~ CHUNK_SIZE_INVALID
|
518
|
+
raise HttpParserError, "Invalid chunk size: '#{chunk_hex}'"
|
519
|
+
end
|
520
|
+
len = chunk_hex.to_i(16)
|
482
521
|
if len == 0
|
483
522
|
@in_last_chunk = true
|
484
523
|
@body.rewind
|
485
524
|
rest = io.read
|
486
|
-
|
487
|
-
if rest.bytesize < last_crlf_size
|
525
|
+
if rest.bytesize < CHUNK_VALID_ENDING_SIZE
|
488
526
|
@buffer = nil
|
489
|
-
@partial_part_left =
|
527
|
+
@partial_part_left = CHUNK_VALID_ENDING_SIZE - rest.bytesize
|
490
528
|
return false
|
491
529
|
else
|
492
|
-
|
530
|
+
# if the next character is a CRLF, set buffer to everything after that CRLF
|
531
|
+
start_of_rest = if rest.start_with?(CHUNK_VALID_ENDING)
|
532
|
+
CHUNK_VALID_ENDING_SIZE
|
533
|
+
else # we have started a trailer section, which we do not support. skip it!
|
534
|
+
rest.index(CHUNK_VALID_ENDING*2) + CHUNK_VALID_ENDING_SIZE*2
|
535
|
+
end
|
536
|
+
|
537
|
+
@buffer = rest[start_of_rest..-1]
|
493
538
|
@buffer = nil if @buffer.empty?
|
494
539
|
set_ready
|
495
540
|
return true
|
@@ -509,7 +554,12 @@ module Puma
|
|
509
554
|
|
510
555
|
case
|
511
556
|
when got == len
|
512
|
-
|
557
|
+
# proper chunked segment must end with "\r\n"
|
558
|
+
if part.end_with? CHUNK_VALID_ENDING
|
559
|
+
write_chunk(part[0..-3]) # to skip the ending \r\n
|
560
|
+
else
|
561
|
+
raise HttpParserError, "Chunk size mismatch"
|
562
|
+
end
|
513
563
|
when got <= len - 2
|
514
564
|
write_chunk(part)
|
515
565
|
@partial_part_left = len - part.size
|
data/lib/puma/cluster/worker.rb
CHANGED
@@ -33,8 +33,8 @@ module Puma
|
|
33
33
|
Signal.trap "SIGINT", "IGNORE"
|
34
34
|
Signal.trap "SIGCHLD", "DEFAULT"
|
35
35
|
|
36
|
-
|
37
|
-
Puma.set_thread_name "
|
36
|
+
Thread.new do
|
37
|
+
Puma.set_thread_name "wrkr check"
|
38
38
|
@check_pipe.wait_readable
|
39
39
|
log "! Detected parent died, dying"
|
40
40
|
exit! 1
|
@@ -76,7 +76,7 @@ module Puma
|
|
76
76
|
end
|
77
77
|
|
78
78
|
Thread.new do
|
79
|
-
Puma.set_thread_name "
|
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
|
-
|
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
|
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
|
-
|
130
|
+
Puma::Util.purge_interrupt_queue
|
131
131
|
break
|
132
132
|
end
|
133
|
-
sleep
|
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
|
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
|
-
|
113
|
+
workers = workers_to_cull(diff)
|
114
|
+
debug "Workers to cull: #{workers.inspect}"
|
113
115
|
|
114
|
-
|
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
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
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 +
|
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
|
|
@@ -450,7 +458,7 @@ module Puma
|
|
450
458
|
workers_not_booted -= 1
|
451
459
|
when "e"
|
452
460
|
# external term, see worker method, Signal.trap "SIGTERM"
|
453
|
-
w.
|
461
|
+
w.term!
|
454
462
|
when "t"
|
455
463
|
w.term unless w.term?
|
456
464
|
when "p"
|
data/lib/puma/configuration.rb
CHANGED
@@ -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'] ||
|
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
@@ -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.
|
104
|
-
CODE_NAME = "
|
103
|
+
PUMA_VERSION = VERSION = "5.6.7".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
|
data/lib/puma/control_cli.rb
CHANGED
@@ -17,26 +17,30 @@ module Puma
|
|
17
17
|
CMD_PATH_SIG_MAP = {
|
18
18
|
'gc' => nil,
|
19
19
|
'gc-stats' => nil,
|
20
|
-
'halt'
|
21
|
-
'
|
22
|
-
'
|
20
|
+
'halt' => 'SIGQUIT',
|
21
|
+
'info' => 'SIGINFO',
|
22
|
+
'phased-restart' => 'SIGUSR1',
|
23
|
+
'refork' => 'SIGURG',
|
23
24
|
'reload-worker-directory' => nil,
|
24
|
-
'
|
25
|
+
'reopen-log' => 'SIGHUP',
|
26
|
+
'restart' => 'SIGUSR2',
|
25
27
|
'start' => nil,
|
26
28
|
'stats' => nil,
|
27
29
|
'status' => '',
|
28
|
-
'stop'
|
29
|
-
'thread-backtraces' => nil
|
30
|
+
'stop' => 'SIGTERM',
|
31
|
+
'thread-backtraces' => nil,
|
32
|
+
'worker-count-down' => 'SIGTTOU',
|
33
|
+
'worker-count-up' => 'SIGTTIN'
|
30
34
|
}.freeze
|
31
35
|
|
32
36
|
# @deprecated 6.0.0
|
33
37
|
COMMANDS = CMD_PATH_SIG_MAP.keys.freeze
|
34
38
|
|
35
39
|
# commands that cannot be used in a request
|
36
|
-
NO_REQ_COMMANDS = %w
|
40
|
+
NO_REQ_COMMANDS = %w[info reopen-log worker-count-down worker-count-up].freeze
|
37
41
|
|
38
42
|
# @version 5.0.0
|
39
|
-
PRINTABLE_COMMANDS = %w
|
43
|
+
PRINTABLE_COMMANDS = %w[gc-stats stats thread-backtraces].freeze
|
40
44
|
|
41
45
|
def initialize(argv, stdout=STDOUT, stderr=STDERR)
|
42
46
|
@state = nil
|
@@ -47,7 +51,7 @@ module Puma
|
|
47
51
|
@control_auth_token = nil
|
48
52
|
@config_file = nil
|
49
53
|
@command = nil
|
50
|
-
@environment = ENV['RACK_ENV'] || ENV['RAILS_ENV']
|
54
|
+
@environment = ENV['APP_ENV'] || ENV['RACK_ENV'] || ENV['RAILS_ENV']
|
51
55
|
|
52
56
|
@argv = argv.dup
|
53
57
|
@stdout = stdout
|
@@ -185,8 +189,6 @@ module Puma
|
|
185
189
|
|
186
190
|
if @command == 'status'
|
187
191
|
message 'Puma is started'
|
188
|
-
elsif NO_REQ_COMMANDS.include? @command
|
189
|
-
raise "Invalid request command: #{@command}"
|
190
192
|
else
|
191
193
|
url = "/#{@command}"
|
192
194
|
|
@@ -242,7 +244,11 @@ module Puma
|
|
242
244
|
@stdout.flush unless @stdout.sync
|
243
245
|
return
|
244
246
|
elsif sig.start_with? 'SIG'
|
245
|
-
|
247
|
+
if Signal.list.key? sig.sub(/\ASIG/, '')
|
248
|
+
Process.kill sig, @pid
|
249
|
+
else
|
250
|
+
raise "Signal '#{sig}' not available'"
|
251
|
+
end
|
246
252
|
elsif @command == 'status'
|
247
253
|
begin
|
248
254
|
Process.kill 0, @pid
|
@@ -268,7 +274,7 @@ module Puma
|
|
268
274
|
return start if @command == 'start'
|
269
275
|
prepare_configuration
|
270
276
|
|
271
|
-
if Puma.windows? || @control_url
|
277
|
+
if Puma.windows? || @control_url && !NO_REQ_COMMANDS.include?(@command)
|
272
278
|
send_request
|
273
279
|
else
|
274
280
|
send_signal
|
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
|
-
|
14
|
-
|
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
@@ -1,6 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require 'puma/const'
|
4
|
+
require 'puma/util'
|
4
5
|
|
5
6
|
module Puma
|
6
7
|
# The methods that are available for use inside the configuration file.
|
@@ -46,7 +47,9 @@ module Puma
|
|
46
47
|
else ''
|
47
48
|
end
|
48
49
|
|
49
|
-
ca_additions = "&ca=#{opts[:ca]}" if ['peer', 'force_peer'].include?(verify)
|
50
|
+
ca_additions = "&ca=#{Puma::Util.escape(opts[:ca])}" if ['peer', 'force_peer'].include?(verify)
|
51
|
+
|
52
|
+
backlog_str = opts[:backlog] ? "&backlog=#{Integer(opts[:backlog])}" : ''
|
50
53
|
|
51
54
|
if defined?(JRUBY_VERSION)
|
52
55
|
ssl_cipher_list = opts[:ssl_cipher_list] ?
|
@@ -55,7 +58,7 @@ module Puma
|
|
55
58
|
keystore_additions = "keystore=#{opts[:keystore]}&keystore-pass=#{opts[:keystore_pass]}"
|
56
59
|
|
57
60
|
"ssl://#{host}:#{port}?#{keystore_additions}#{ssl_cipher_list}" \
|
58
|
-
"&verify_mode=#{verify}#{tls_str}#{ca_additions}"
|
61
|
+
"&verify_mode=#{verify}#{tls_str}#{ca_additions}#{backlog_str}"
|
59
62
|
else
|
60
63
|
ssl_cipher_filter = opts[:ssl_cipher_filter] ?
|
61
64
|
"&ssl_cipher_filter=#{opts[:ssl_cipher_filter]}" : nil
|
@@ -63,8 +66,11 @@ module Puma
|
|
63
66
|
v_flags = (ary = opts[:verification_flags]) ?
|
64
67
|
"&verification_flags=#{Array(ary).join ','}" : nil
|
65
68
|
|
66
|
-
|
67
|
-
|
69
|
+
cert_flags = (cert = opts[:cert]) ? "cert=#{Puma::Util.escape(opts[:cert])}" : nil
|
70
|
+
key_flags = (cert = opts[:key]) ? "&key=#{Puma::Util.escape(opts[:key])}" : nil
|
71
|
+
|
72
|
+
"ssl://#{host}:#{port}?#{cert_flags}#{key_flags}" \
|
73
|
+
"#{ssl_cipher_filter}&verify_mode=#{verify}#{tls_str}#{ca_additions}#{v_flags}#{backlog_str}"
|
68
74
|
end
|
69
75
|
end
|
70
76
|
|
@@ -191,7 +197,7 @@ module Puma
|
|
191
197
|
end
|
192
198
|
|
193
199
|
# Bind the server to +url+. "tcp://", "unix://" and "ssl://" are the only
|
194
|
-
# accepted protocols. Multiple urls can be bound to, calling
|
200
|
+
# accepted protocols. Multiple urls can be bound to, calling +bind+ does
|
195
201
|
# not overwrite previous bindings.
|
196
202
|
#
|
197
203
|
# The default is "tcp://0.0.0.0:9292".
|
@@ -436,8 +442,15 @@ module Puma
|
|
436
442
|
@options[:max_threads] = max
|
437
443
|
end
|
438
444
|
|
439
|
-
# Instead of
|
440
|
-
#
|
445
|
+
# Instead of using +bind+ and manually constructing a URI like:
|
446
|
+
#
|
447
|
+
# bind 'ssl://127.0.0.1:9292?key=key_path&cert=cert_path'
|
448
|
+
#
|
449
|
+
# you can use the this method.
|
450
|
+
#
|
451
|
+
# When binding on localhost you don't need to specify +cert+ and +key+,
|
452
|
+
# Puma will assume you are using the +localhost+ gem and try to load the
|
453
|
+
# appropriate files.
|
441
454
|
#
|
442
455
|
# @example
|
443
456
|
# ssl_bind '127.0.0.1', '9292', {
|
@@ -447,14 +460,25 @@ module Puma
|
|
447
460
|
# verify_mode: verify_mode, # default 'none'
|
448
461
|
# verification_flags: flags, # optional, not supported by JRuby
|
449
462
|
# }
|
450
|
-
#
|
463
|
+
#
|
464
|
+
# @example Using self-signed certificate with the +localhost+ gem:
|
465
|
+
# ssl_bind '127.0.0.1', '9292'
|
466
|
+
#
|
467
|
+
# @example Alternatively, you can provide +cert_pem+ and +key_pem+:
|
468
|
+
# ssl_bind '127.0.0.1', '9292', {
|
469
|
+
# cert_pem: File.read(path_to_cert),
|
470
|
+
# key_pem: File.read(path_to_key),
|
471
|
+
# }
|
472
|
+
#
|
473
|
+
# @example For JRuby, two keys are required: +keystore+ & +keystore_pass+
|
451
474
|
# ssl_bind '127.0.0.1', '9292', {
|
452
475
|
# keystore: path_to_keystore,
|
453
476
|
# keystore_pass: password,
|
454
477
|
# ssl_cipher_list: cipher_list, # optional
|
455
478
|
# verify_mode: verify_mode # default 'none'
|
456
479
|
# }
|
457
|
-
def ssl_bind(host, port, opts)
|
480
|
+
def ssl_bind(host, port, opts = {})
|
481
|
+
add_pem_values_to_options_store(opts)
|
458
482
|
bind self.class.ssl_bind_str(host, port, opts)
|
459
483
|
end
|
460
484
|
|
@@ -727,6 +751,19 @@ module Puma
|
|
727
751
|
@options[:tag] = string.to_s
|
728
752
|
end
|
729
753
|
|
754
|
+
# Change the default interval for checking workers.
|
755
|
+
#
|
756
|
+
# The default value is 5 seconds.
|
757
|
+
#
|
758
|
+
# @note Cluster mode only.
|
759
|
+
# @example
|
760
|
+
# worker_check_interval 5
|
761
|
+
# @see Puma::Cluster#check_workers
|
762
|
+
#
|
763
|
+
def worker_check_interval(interval)
|
764
|
+
@options[:worker_check_interval] = Integer(interval)
|
765
|
+
end
|
766
|
+
|
730
767
|
# Verifies that all workers have checked in to the master process within
|
731
768
|
# the given timeout. If not the worker process will be restarted. This is
|
732
769
|
# not a request timeout, it is to protect against a hung or dead process.
|
@@ -741,7 +778,7 @@ module Puma
|
|
741
778
|
#
|
742
779
|
def worker_timeout(timeout)
|
743
780
|
timeout = Integer(timeout)
|
744
|
-
min =
|
781
|
+
min = @options.fetch(:worker_check_interval, Puma::ConfigDefault::DefaultWorkerCheckInterval)
|
745
782
|
|
746
783
|
if timeout <= min
|
747
784
|
raise "The minimum worker_timeout must be greater than the worker reporting interval (#{min})"
|
@@ -773,6 +810,30 @@ module Puma
|
|
773
810
|
@options[:worker_shutdown_timeout] = Integer(timeout)
|
774
811
|
end
|
775
812
|
|
813
|
+
# Set the strategy for worker culling.
|
814
|
+
#
|
815
|
+
# There are two possible values:
|
816
|
+
#
|
817
|
+
# 1. **:youngest** - the youngest workers (i.e. the workers that were
|
818
|
+
# the most recently started) will be culled.
|
819
|
+
# 2. **:oldest** - the oldest workers (i.e. the workers that were started
|
820
|
+
# the longest time ago) will be culled.
|
821
|
+
#
|
822
|
+
# @note Cluster mode only.
|
823
|
+
# @example
|
824
|
+
# worker_culling_strategy :oldest
|
825
|
+
# @see Puma::Cluster#cull_workers
|
826
|
+
#
|
827
|
+
def worker_culling_strategy(strategy)
|
828
|
+
stategy = strategy.to_sym
|
829
|
+
|
830
|
+
if ![:youngest, :oldest].include?(strategy)
|
831
|
+
raise "Invalid value for worker_culling_strategy - #{stategy}"
|
832
|
+
end
|
833
|
+
|
834
|
+
@options[:worker_culling_strategy] = strategy
|
835
|
+
end
|
836
|
+
|
776
837
|
# When set to true (the default), workers accept all requests
|
777
838
|
# and queue them before passing them to the handlers.
|
778
839
|
# When set to false, each worker process accepts exactly as
|
@@ -927,5 +988,25 @@ module Puma
|
|
927
988
|
def mutate_stdout_and_stderr_to_sync_on_write(enabled=true)
|
928
989
|
@options[:mutate_stdout_and_stderr_to_sync_on_write] = enabled
|
929
990
|
end
|
991
|
+
|
992
|
+
private
|
993
|
+
|
994
|
+
# To avoid adding cert_pem and key_pem as URI params, we store them on the
|
995
|
+
# options[:store] from where Puma binder knows how to find and extract them.
|
996
|
+
def add_pem_values_to_options_store(opts)
|
997
|
+
return if defined?(JRUBY_VERSION)
|
998
|
+
|
999
|
+
@options[:store] ||= []
|
1000
|
+
|
1001
|
+
# Store cert_pem and key_pem to options[:store] if present
|
1002
|
+
[:cert, :key].each do |v|
|
1003
|
+
opt_key = :"#{v}_pem"
|
1004
|
+
if opts[opt_key]
|
1005
|
+
index = @options[:store].length
|
1006
|
+
@options[:store] << opts[opt_key]
|
1007
|
+
opts[v] = "store:#{index}"
|
1008
|
+
end
|
1009
|
+
end
|
1010
|
+
end
|
930
1011
|
end
|
931
1012
|
end
|