puma 5.3.2 → 5.6.8
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.
- checksums.yaml +4 -4
- data/History.md +211 -11
- data/README.md +47 -6
- data/docs/architecture.md +49 -16
- data/docs/compile_options.md +4 -2
- data/docs/deployment.md +53 -67
- 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 +64 -67
- data/ext/puma_http11/extconf.rb +34 -6
- 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 +90 -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 +7 -4
- data/lib/puma/binder.rb +51 -6
- data/lib/puma/cli.rb +14 -4
- data/lib/puma/client.rb +143 -25
- data/lib/puma/cluster/worker.rb +8 -18
- data/lib/puma/cluster/worker_handle.rb +4 -0
- data/lib/puma/cluster.rb +30 -24
- data/lib/puma/configuration.rb +4 -1
- data/lib/puma/const.rb +9 -8
- data/lib/puma/control_cli.rb +19 -13
- data/lib/puma/detect.rb +8 -2
- data/lib/puma/dsl.rb +111 -13
- data/lib/puma/{json.rb → json_serialization.rb} +1 -1
- data/lib/puma/launcher.rb +15 -1
- data/lib/puma/minissl/context_builder.rb +8 -6
- data/lib/puma/minissl.rb +33 -27
- data/lib/puma/null_io.rb +5 -0
- data/lib/puma/plugin.rb +2 -2
- data/lib/puma/rack/builder.rb +1 -1
- data/lib/puma/request.rb +19 -10
- data/lib/puma/runner.rb +22 -8
- data/lib/puma/server.rb +37 -29
- data/lib/puma/state_file.rb +42 -7
- data/lib/puma/thread_pool.rb +7 -5
- data/lib/puma/util.rb +20 -4
- data/lib/puma.rb +6 -4
- data/lib/rack/version_restriction.rb +15 -0
- data/tools/Dockerfile +1 -1
- metadata +8 -7
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,30 @@ 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
|
+
# The maximum number of bytes we'll buffer looking for a valid
|
52
|
+
# chunk header.
|
53
|
+
MAX_CHUNK_HEADER_SIZE = 4096
|
54
|
+
|
55
|
+
# The maximum amount of excess data the client sends
|
56
|
+
# using chunk size extensions before we abort the connection.
|
57
|
+
MAX_CHUNK_EXCESS = 16 * 1024
|
58
|
+
|
59
|
+
# Content-Length header value validation
|
60
|
+
CONTENT_LENGTH_VALUE_INVALID = /[^\d]/.freeze
|
61
|
+
|
62
|
+
TE_ERR_MSG = 'Invalid Transfer-Encoding'
|
63
|
+
|
39
64
|
# The object used for a request with no body. All requests with
|
40
65
|
# no body share this one object since it has no state.
|
41
66
|
EmptyBody = NullIO.new
|
@@ -56,6 +81,7 @@ module Puma
|
|
56
81
|
@parser = HttpParser.new
|
57
82
|
@parsed_bytes = 0
|
58
83
|
@read_header = true
|
84
|
+
@read_proxy = false
|
59
85
|
@ready = false
|
60
86
|
|
61
87
|
@body = nil
|
@@ -71,6 +97,7 @@ module Puma
|
|
71
97
|
@peerip = nil
|
72
98
|
@listener = nil
|
73
99
|
@remote_addr_header = nil
|
100
|
+
@expect_proxy_proto = false
|
74
101
|
|
75
102
|
@body_remain = 0
|
76
103
|
|
@@ -106,7 +133,7 @@ module Puma
|
|
106
133
|
|
107
134
|
# @!attribute [r] in_data_phase
|
108
135
|
def in_data_phase
|
109
|
-
|
136
|
+
!(@read_header || @read_proxy)
|
110
137
|
end
|
111
138
|
|
112
139
|
def set_timeout(val)
|
@@ -121,6 +148,7 @@ module Puma
|
|
121
148
|
def reset(fast_check=true)
|
122
149
|
@parser.reset
|
123
150
|
@read_header = true
|
151
|
+
@read_proxy = !!@expect_proxy_proto
|
124
152
|
@env = @proto_env.dup
|
125
153
|
@body = nil
|
126
154
|
@tempfile = nil
|
@@ -131,6 +159,8 @@ module Puma
|
|
131
159
|
@in_last_chunk = false
|
132
160
|
|
133
161
|
if @buffer
|
162
|
+
return false unless try_to_parse_proxy_protocol
|
163
|
+
|
134
164
|
@parsed_bytes = @parser.execute(@env, @buffer, @parsed_bytes)
|
135
165
|
|
136
166
|
if @parser.finished?
|
@@ -143,8 +173,7 @@ module Puma
|
|
143
173
|
return false
|
144
174
|
else
|
145
175
|
begin
|
146
|
-
if fast_check &&
|
147
|
-
IO.select([@to_io], nil, nil, FAST_TRACK_KA_TIMEOUT)
|
176
|
+
if fast_check && @to_io.wait_readable(FAST_TRACK_KA_TIMEOUT)
|
148
177
|
return try_to_finish
|
149
178
|
end
|
150
179
|
rescue IOError
|
@@ -157,13 +186,37 @@ module Puma
|
|
157
186
|
def close
|
158
187
|
begin
|
159
188
|
@io.close
|
160
|
-
rescue IOError
|
161
|
-
|
189
|
+
rescue IOError, Errno::EBADF
|
190
|
+
Puma::Util.purge_interrupt_queue
|
162
191
|
end
|
163
192
|
end
|
164
193
|
|
194
|
+
# If necessary, read the PROXY protocol from the buffer. Returns
|
195
|
+
# false if more data is needed.
|
196
|
+
def try_to_parse_proxy_protocol
|
197
|
+
if @read_proxy
|
198
|
+
if @expect_proxy_proto == :v1
|
199
|
+
if @buffer.include? "\r\n"
|
200
|
+
if md = PROXY_PROTOCOL_V1_REGEX.match(@buffer)
|
201
|
+
if md[1]
|
202
|
+
@peerip = md[1].split(" ")[0]
|
203
|
+
end
|
204
|
+
@buffer = md.post_match
|
205
|
+
end
|
206
|
+
# if the buffer has a \r\n but doesn't have a PROXY protocol
|
207
|
+
# request, this is just HTTP from a non-PROXY client; move on
|
208
|
+
@read_proxy = false
|
209
|
+
return @buffer.size > 0
|
210
|
+
else
|
211
|
+
return false
|
212
|
+
end
|
213
|
+
end
|
214
|
+
end
|
215
|
+
true
|
216
|
+
end
|
217
|
+
|
165
218
|
def try_to_finish
|
166
|
-
return read_body
|
219
|
+
return read_body if in_data_phase
|
167
220
|
|
168
221
|
begin
|
169
222
|
data = @io.read_nonblock(CHUNK_SIZE)
|
@@ -188,6 +241,8 @@ module Puma
|
|
188
241
|
@buffer = data
|
189
242
|
end
|
190
243
|
|
244
|
+
return false unless try_to_parse_proxy_protocol
|
245
|
+
|
191
246
|
@parsed_bytes = @parser.execute(@env, @buffer, @parsed_bytes)
|
192
247
|
|
193
248
|
if @parser.finished?
|
@@ -202,13 +257,13 @@ module Puma
|
|
202
257
|
|
203
258
|
def eagerly_finish
|
204
259
|
return true if @ready
|
205
|
-
return false unless
|
260
|
+
return false unless @to_io.wait_readable(0)
|
206
261
|
try_to_finish
|
207
262
|
end
|
208
263
|
|
209
264
|
def finish(timeout)
|
210
265
|
return if @ready
|
211
|
-
|
266
|
+
@to_io.wait_readable(timeout) || timeout! until try_to_finish
|
212
267
|
end
|
213
268
|
|
214
269
|
def timeout!
|
@@ -244,6 +299,17 @@ module Puma
|
|
244
299
|
@parsed_bytes == 0
|
245
300
|
end
|
246
301
|
|
302
|
+
def expect_proxy_proto=(val)
|
303
|
+
if val
|
304
|
+
if @read_header
|
305
|
+
@read_proxy = true
|
306
|
+
end
|
307
|
+
else
|
308
|
+
@read_proxy = false
|
309
|
+
end
|
310
|
+
@expect_proxy_proto = val
|
311
|
+
end
|
312
|
+
|
247
313
|
private
|
248
314
|
|
249
315
|
def setup_body
|
@@ -261,16 +327,27 @@ module Puma
|
|
261
327
|
body = @parser.body
|
262
328
|
|
263
329
|
te = @env[TRANSFER_ENCODING2]
|
264
|
-
|
265
330
|
if te
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
331
|
+
te_lwr = te.downcase
|
332
|
+
if te.include? ','
|
333
|
+
te_ary = te_lwr.split ','
|
334
|
+
te_count = te_ary.count CHUNKED
|
335
|
+
te_valid = te_ary[0..-2].all? { |e| ALLOWED_TRANSFER_ENCODING.include? e }
|
336
|
+
if te_ary.last == CHUNKED && te_count == 1 && te_valid
|
337
|
+
@env.delete TRANSFER_ENCODING2
|
338
|
+
return setup_chunked_body body
|
339
|
+
elsif te_count >= 1
|
340
|
+
raise HttpParserError , "#{TE_ERR_MSG}, multiple chunked: '#{te}'"
|
341
|
+
elsif !te_valid
|
342
|
+
raise HttpParserError501, "#{TE_ERR_MSG}, unknown value: '#{te}'"
|
271
343
|
end
|
272
|
-
elsif
|
273
|
-
|
344
|
+
elsif te_lwr == CHUNKED
|
345
|
+
@env.delete TRANSFER_ENCODING2
|
346
|
+
return setup_chunked_body body
|
347
|
+
elsif ALLOWED_TRANSFER_ENCODING.include? te_lwr
|
348
|
+
raise HttpParserError , "#{TE_ERR_MSG}, single value must be chunked: '#{te}'"
|
349
|
+
else
|
350
|
+
raise HttpParserError501 , "#{TE_ERR_MSG}, unknown value: '#{te}'"
|
274
351
|
end
|
275
352
|
end
|
276
353
|
|
@@ -278,7 +355,12 @@ module Puma
|
|
278
355
|
|
279
356
|
cl = @env[CONTENT_LENGTH]
|
280
357
|
|
281
|
-
|
358
|
+
if cl
|
359
|
+
# cannot contain characters that are not \d, or be empty
|
360
|
+
if cl =~ CONTENT_LENGTH_VALUE_INVALID || cl.empty?
|
361
|
+
raise HttpParserError, "Invalid Content-Length: #{cl.inspect}"
|
362
|
+
end
|
363
|
+
else
|
282
364
|
@buffer = body.empty? ? nil : body
|
283
365
|
@body = EmptyBody
|
284
366
|
set_ready
|
@@ -309,7 +391,7 @@ module Puma
|
|
309
391
|
|
310
392
|
@body_remain = remain
|
311
393
|
|
312
|
-
|
394
|
+
false
|
313
395
|
end
|
314
396
|
|
315
397
|
def read_body
|
@@ -386,6 +468,7 @@ module Puma
|
|
386
468
|
@chunked_body = true
|
387
469
|
@partial_part_left = 0
|
388
470
|
@prev_chunk = ""
|
471
|
+
@excess_cr = 0
|
389
472
|
|
390
473
|
@body = Tempfile.new(Const::PUMA_TMP_BASE)
|
391
474
|
@body.unlink
|
@@ -436,25 +519,51 @@ module Puma
|
|
436
519
|
|
437
520
|
while !io.eof?
|
438
521
|
line = io.gets
|
439
|
-
if line.end_with?(
|
440
|
-
|
522
|
+
if line.end_with?(CHUNK_VALID_ENDING)
|
523
|
+
# Puma doesn't process chunk extensions, but should parse if they're
|
524
|
+
# present, which is the reason for the semicolon regex
|
525
|
+
chunk_hex = line.strip[/\A[^;]+/]
|
526
|
+
if chunk_hex =~ CHUNK_SIZE_INVALID
|
527
|
+
raise HttpParserError, "Invalid chunk size: '#{chunk_hex}'"
|
528
|
+
end
|
529
|
+
len = chunk_hex.to_i(16)
|
441
530
|
if len == 0
|
442
531
|
@in_last_chunk = true
|
443
532
|
@body.rewind
|
444
533
|
rest = io.read
|
445
|
-
|
446
|
-
if rest.bytesize < last_crlf_size
|
534
|
+
if rest.bytesize < CHUNK_VALID_ENDING_SIZE
|
447
535
|
@buffer = nil
|
448
|
-
@partial_part_left =
|
536
|
+
@partial_part_left = CHUNK_VALID_ENDING_SIZE - rest.bytesize
|
449
537
|
return false
|
450
538
|
else
|
451
|
-
|
539
|
+
# if the next character is a CRLF, set buffer to everything after that CRLF
|
540
|
+
start_of_rest = if rest.start_with?(CHUNK_VALID_ENDING)
|
541
|
+
CHUNK_VALID_ENDING_SIZE
|
542
|
+
else # we have started a trailer section, which we do not support. skip it!
|
543
|
+
rest.index(CHUNK_VALID_ENDING*2) + CHUNK_VALID_ENDING_SIZE*2
|
544
|
+
end
|
545
|
+
|
546
|
+
@buffer = rest[start_of_rest..-1]
|
452
547
|
@buffer = nil if @buffer.empty?
|
453
548
|
set_ready
|
454
549
|
return true
|
455
550
|
end
|
456
551
|
end
|
457
552
|
|
553
|
+
# Track the excess as a function of the size of the
|
554
|
+
# header vs the size of the actual data. Excess can
|
555
|
+
# go negative (and is expected to) when the body is
|
556
|
+
# significant.
|
557
|
+
# The additional of chunk_hex.size and 2 compensates
|
558
|
+
# for a client sending 1 byte in a chunked body over
|
559
|
+
# a long period of time, making sure that that client
|
560
|
+
# isn't accidentally eventually punished.
|
561
|
+
@excess_cr += (line.size - len - chunk_hex.size - 2)
|
562
|
+
|
563
|
+
if @excess_cr >= MAX_CHUNK_EXCESS
|
564
|
+
raise HttpParserError, "Maximum chunk excess detected"
|
565
|
+
end
|
566
|
+
|
458
567
|
len += 2
|
459
568
|
|
460
569
|
part = io.read(len)
|
@@ -468,7 +577,12 @@ module Puma
|
|
468
577
|
|
469
578
|
case
|
470
579
|
when got == len
|
471
|
-
|
580
|
+
# proper chunked segment must end with "\r\n"
|
581
|
+
if part.end_with? CHUNK_VALID_ENDING
|
582
|
+
write_chunk(part[0..-3]) # to skip the ending \r\n
|
583
|
+
else
|
584
|
+
raise HttpParserError, "Chunk size mismatch"
|
585
|
+
end
|
472
586
|
when got <= len - 2
|
473
587
|
write_chunk(part)
|
474
588
|
@partial_part_left = len - part.size
|
@@ -477,6 +591,10 @@ module Puma
|
|
477
591
|
@partial_part_left = len - part.size
|
478
592
|
end
|
479
593
|
else
|
594
|
+
if @prev_chunk.size + chunk.size >= MAX_CHUNK_HEADER_SIZE
|
595
|
+
raise HttpParserError, "maximum size of chunk header exceeded"
|
596
|
+
end
|
597
|
+
|
480
598
|
@prev_chunk = line
|
481
599
|
return false
|
482
600
|
end
|
data/lib/puma/cluster/worker.rb
CHANGED
@@ -33,9 +33,9 @@ module Puma
|
|
33
33
|
Signal.trap "SIGINT", "IGNORE"
|
34
34
|
Signal.trap "SIGCHLD", "DEFAULT"
|
35
35
|
|
36
|
-
|
37
|
-
Puma.set_thread_name "
|
38
|
-
|
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 "
|
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
|
|
@@ -426,9 +434,7 @@ module Puma
|
|
426
434
|
|
427
435
|
check_workers
|
428
436
|
|
429
|
-
|
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.
|
461
|
+
w.term!
|
456
462
|
when "t"
|
457
463
|
w.term unless w.term?
|
458
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.8".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
|
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
|