puma 5.6.4 → 6.3.0
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 +270 -4
- data/LICENSE +0 -0
- data/README.md +60 -20
- data/bin/puma-wild +1 -1
- data/docs/architecture.md +0 -0
- data/docs/compile_options.md +34 -0
- data/docs/deployment.md +0 -0
- data/docs/fork_worker.md +1 -3
- data/docs/images/puma-connection-flow-no-reactor.png +0 -0
- data/docs/images/puma-connection-flow.png +0 -0
- data/docs/images/puma-general-arch.png +0 -0
- data/docs/jungle/README.md +0 -0
- data/docs/jungle/rc.d/README.md +0 -0
- data/docs/jungle/rc.d/puma.conf +0 -0
- data/docs/kubernetes.md +0 -0
- data/docs/nginx.md +1 -1
- data/docs/plugins.md +0 -0
- data/docs/rails_dev_mode.md +0 -0
- data/docs/restart.md +0 -0
- data/docs/signals.md +0 -0
- data/docs/stats.md +0 -0
- data/docs/systemd.md +1 -2
- data/docs/testing_benchmarks_local_files.md +150 -0
- data/docs/testing_test_rackup_ci_files.md +36 -0
- data/ext/puma_http11/PumaHttp11Service.java +0 -0
- data/ext/puma_http11/ext_help.h +0 -0
- data/ext/puma_http11/extconf.rb +18 -10
- data/ext/puma_http11/http11_parser.c +1 -1
- data/ext/puma_http11/http11_parser.h +1 -1
- data/ext/puma_http11/http11_parser.java.rl +2 -2
- data/ext/puma_http11/http11_parser.rl +2 -2
- data/ext/puma_http11/http11_parser_common.rl +2 -2
- data/ext/puma_http11/mini_ssl.c +93 -26
- data/ext/puma_http11/no_ssl/PumaHttp11Service.java +0 -0
- data/ext/puma_http11/org/jruby/puma/Http11.java +3 -3
- data/ext/puma_http11/org/jruby/puma/Http11Parser.java +1 -1
- data/ext/puma_http11/org/jruby/puma/MiniSSL.java +166 -65
- data/ext/puma_http11/puma_http11.c +17 -9
- data/lib/puma/app/status.rb +7 -4
- data/lib/puma/binder.rb +49 -52
- data/lib/puma/cli.rb +12 -18
- data/lib/puma/client.rb +55 -16
- data/lib/puma/cluster/worker.rb +18 -11
- data/lib/puma/cluster/worker_handle.rb +4 -1
- data/lib/puma/cluster.rb +33 -30
- data/lib/puma/commonlogger.rb +21 -14
- data/lib/puma/configuration.rb +76 -58
- data/lib/puma/const.rb +129 -92
- data/lib/puma/control_cli.rb +21 -18
- data/lib/puma/detect.rb +4 -0
- data/lib/puma/dsl.rb +187 -49
- data/lib/puma/error_logger.rb +18 -9
- data/lib/puma/events.rb +6 -126
- data/lib/puma/io_buffer.rb +39 -4
- data/lib/puma/jruby_restart.rb +2 -1
- data/lib/puma/json_serialization.rb +0 -0
- data/lib/puma/launcher/bundle_pruner.rb +104 -0
- data/lib/puma/launcher.rb +113 -175
- data/lib/puma/log_writer.rb +147 -0
- data/lib/puma/minissl/context_builder.rb +24 -12
- data/lib/puma/minissl.rb +108 -15
- data/lib/puma/null_io.rb +5 -0
- data/lib/puma/plugin/systemd.rb +90 -0
- data/lib/puma/plugin/tmp_restart.rb +1 -1
- data/lib/puma/plugin.rb +0 -0
- data/lib/puma/rack/builder.rb +6 -6
- data/lib/puma/rack/urlmap.rb +0 -0
- data/lib/puma/rack_default.rb +19 -4
- data/lib/puma/reactor.rb +19 -10
- data/lib/puma/request.rb +365 -166
- data/lib/puma/runner.rb +52 -20
- data/lib/puma/sd_notify.rb +149 -0
- data/lib/puma/server.rb +73 -73
- data/lib/puma/single.rb +13 -11
- data/lib/puma/state_file.rb +2 -4
- data/lib/puma/thread_pool.rb +23 -19
- data/lib/puma/util.rb +12 -14
- data/lib/puma.rb +12 -11
- data/lib/rack/handler/puma.rb +113 -86
- data/tools/Dockerfile +0 -0
- data/tools/trickletest.rb +0 -0
- metadata +10 -5
- data/lib/puma/queue_close.rb +0 -26
- data/lib/puma/systemd.rb +0 -46
data/lib/puma/binder.rb
CHANGED
@@ -3,24 +3,15 @@
|
|
3
3
|
require 'uri'
|
4
4
|
require 'socket'
|
5
5
|
|
6
|
-
|
7
|
-
|
8
|
-
|
6
|
+
require_relative 'const'
|
7
|
+
require_relative 'util'
|
8
|
+
require_relative 'configuration'
|
9
9
|
|
10
10
|
module Puma
|
11
11
|
|
12
12
|
if HAS_SSL
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
# Odd bug in 'pure Ruby' nio4r version 2.5.2, which installs with Ruby 2.3.
|
17
|
-
# NIO doesn't create any OpenSSL objects, but it rescues an OpenSSL error.
|
18
|
-
# The bug was that it did not require openssl.
|
19
|
-
# @todo remove when Ruby 2.3 support is dropped
|
20
|
-
#
|
21
|
-
if windows? && RbConfig::CONFIG['ruby_version'] == '2.3.0'
|
22
|
-
require 'openssl'
|
23
|
-
end
|
13
|
+
require_relative 'minissl'
|
14
|
+
require_relative 'minissl/context_builder'
|
24
15
|
end
|
25
16
|
|
26
17
|
class Binder
|
@@ -28,8 +19,8 @@ module Puma
|
|
28
19
|
|
29
20
|
RACK_VERSION = [1,6].freeze
|
30
21
|
|
31
|
-
def initialize(
|
32
|
-
@
|
22
|
+
def initialize(log_writer, conf = Configuration.new)
|
23
|
+
@log_writer = log_writer
|
33
24
|
@conf = conf
|
34
25
|
@listeners = []
|
35
26
|
@inherited_fds = {}
|
@@ -38,7 +29,7 @@ module Puma
|
|
38
29
|
|
39
30
|
@proto_env = {
|
40
31
|
"rack.version".freeze => RACK_VERSION,
|
41
|
-
"rack.errors".freeze =>
|
32
|
+
"rack.errors".freeze => log_writer.stderr,
|
42
33
|
"rack.multithread".freeze => conf.options[:max_threads] > 1,
|
43
34
|
"rack.multiprocess".freeze => conf.options[:workers] >= 1,
|
44
35
|
"rack.run_once".freeze => false,
|
@@ -51,14 +42,12 @@ module Puma
|
|
51
42
|
# infer properly.
|
52
43
|
|
53
44
|
"QUERY_STRING".freeze => "",
|
54
|
-
SERVER_PROTOCOL => HTTP_11,
|
55
45
|
SERVER_SOFTWARE => PUMA_SERVER_STRING,
|
56
46
|
GATEWAY_INTERFACE => CGI_VER
|
57
47
|
}
|
58
48
|
|
59
49
|
@envs = {}
|
60
50
|
@ios = []
|
61
|
-
localhost_authority
|
62
51
|
end
|
63
52
|
|
64
53
|
attr_reader :ios
|
@@ -80,7 +69,7 @@ module Puma
|
|
80
69
|
# @!attribute [r] connected_ports
|
81
70
|
# @version 5.0.0
|
82
71
|
def connected_ports
|
83
|
-
ios.map { |io| io.addr[1] }.uniq
|
72
|
+
t = ios.map { |io| io.addr[1] }; t.uniq!; t
|
84
73
|
end
|
85
74
|
|
86
75
|
# @version 5.0.0
|
@@ -98,7 +87,7 @@ module Puma
|
|
98
87
|
# @version 5.0.0
|
99
88
|
#
|
100
89
|
def create_activated_fds(env_hash)
|
101
|
-
@
|
90
|
+
@log_writer.debug "ENV['LISTEN_FDS'] #{ENV['LISTEN_FDS'].inspect} env_hash['LISTEN_PID'] #{env_hash['LISTEN_PID'].inspect}"
|
102
91
|
return [] unless env_hash['LISTEN_FDS'] && env_hash['LISTEN_PID'].to_i == $$
|
103
92
|
env_hash['LISTEN_FDS'].to_i.times do |index|
|
104
93
|
sock = TCPServer.for_fd(socket_activation_fd(index))
|
@@ -106,11 +95,11 @@ module Puma
|
|
106
95
|
[:unix, Socket.unpack_sockaddr_un(sock.getsockname)]
|
107
96
|
rescue ArgumentError # Try to parse as a port/ip
|
108
97
|
port, addr = Socket.unpack_sockaddr_in(sock.getsockname)
|
109
|
-
addr = "[#{addr}]" if addr
|
98
|
+
addr = "[#{addr}]" if addr&.include? ':'
|
110
99
|
[:tcp, addr, port]
|
111
100
|
end
|
112
101
|
@activated_sockets[key] = sock
|
113
|
-
@
|
102
|
+
@log_writer.debug "Registered #{key.join ':'} for activation from LISTEN_FDS"
|
114
103
|
end
|
115
104
|
["LISTEN_FDS", "LISTEN_PID"] # Signal to remove these keys from ENV
|
116
105
|
end
|
@@ -152,29 +141,30 @@ module Puma
|
|
152
141
|
end
|
153
142
|
end
|
154
143
|
|
155
|
-
def parse(binds,
|
144
|
+
def parse(binds, log_writer = nil, log_msg = 'Listening')
|
145
|
+
log_writer ||= @log_writer
|
156
146
|
binds.each do |str|
|
157
147
|
uri = URI.parse str
|
158
148
|
case uri.scheme
|
159
149
|
when "tcp"
|
160
150
|
if fd = @inherited_fds.delete(str)
|
161
151
|
io = inherit_tcp_listener uri.host, uri.port, fd
|
162
|
-
|
152
|
+
log_writer.log "* Inherited #{str}"
|
163
153
|
elsif sock = @activated_sockets.delete([ :tcp, uri.host, uri.port ])
|
164
154
|
io = inherit_tcp_listener uri.host, uri.port, sock
|
165
|
-
|
155
|
+
log_writer.log "* Activated #{str}"
|
166
156
|
else
|
167
157
|
ios_len = @ios.length
|
168
158
|
params = Util.parse_query uri.query
|
169
159
|
|
170
|
-
|
160
|
+
low_latency = params.key?('low_latency') && params['low_latency'] != 'false'
|
171
161
|
backlog = params.fetch('backlog', 1024).to_i
|
172
162
|
|
173
|
-
io = add_tcp_listener uri.host, uri.port,
|
163
|
+
io = add_tcp_listener uri.host, uri.port, low_latency, backlog
|
174
164
|
|
175
165
|
@ios[ios_len..-1].each do |i|
|
176
166
|
addr = loc_addr_str i
|
177
|
-
|
167
|
+
log_writer.log "* #{log_msg} on http://#{addr}"
|
178
168
|
end
|
179
169
|
end
|
180
170
|
|
@@ -189,14 +179,14 @@ module Puma
|
|
189
179
|
end
|
190
180
|
|
191
181
|
if fd = @inherited_fds.delete(str)
|
192
|
-
@unix_paths << path unless abstract
|
182
|
+
@unix_paths << path unless abstract || File.exist?(path)
|
193
183
|
io = inherit_unix_listener path, fd
|
194
|
-
|
184
|
+
log_writer.log "* Inherited #{str}"
|
195
185
|
elsif sock = @activated_sockets.delete([ :unix, path ]) ||
|
196
186
|
@activated_sockets.delete([ :unix, File.realdirpath(path) ])
|
197
187
|
@unix_paths << path unless abstract || File.exist?(path)
|
198
188
|
io = inherit_unix_listener path, sock
|
199
|
-
|
189
|
+
log_writer.log "* Activated #{str}"
|
200
190
|
else
|
201
191
|
umask = nil
|
202
192
|
mode = nil
|
@@ -220,11 +210,12 @@ module Puma
|
|
220
210
|
|
221
211
|
@unix_paths << path unless abstract || File.exist?(path)
|
222
212
|
io = add_unix_listener path, umask, mode, backlog
|
223
|
-
|
213
|
+
log_writer.log "* #{log_msg} on #{str}"
|
224
214
|
end
|
225
215
|
|
226
216
|
@listeners << [str, io]
|
227
217
|
when "ssl"
|
218
|
+
cert_key = %w[cert key]
|
228
219
|
|
229
220
|
raise "Puma compiled without SSL support" unless HAS_SSL
|
230
221
|
|
@@ -233,49 +224,51 @@ module Puma
|
|
233
224
|
# If key and certs are not defined and localhost gem is required.
|
234
225
|
# localhost gem will be used for self signed
|
235
226
|
# Load localhost authority if not loaded.
|
236
|
-
|
227
|
+
# Ruby 3 `values_at` accepts an array, earlier do not
|
228
|
+
if params.values_at(*cert_key).all? { |v| v.to_s.empty? }
|
237
229
|
ctx = localhost_authority && localhost_authority_context
|
238
230
|
end
|
239
231
|
|
240
232
|
ctx ||=
|
241
233
|
begin
|
242
234
|
# Extract cert_pem and key_pem from options[:store] if present
|
243
|
-
|
244
|
-
if params[v]
|
235
|
+
cert_key.each do |v|
|
236
|
+
if params[v]&.start_with?('store:')
|
245
237
|
index = Integer(params.delete(v).split('store:').last)
|
246
238
|
params["#{v}_pem"] = @conf.options[:store][index]
|
247
239
|
end
|
248
240
|
end
|
249
|
-
MiniSSL::ContextBuilder.new(params, @
|
241
|
+
MiniSSL::ContextBuilder.new(params, @log_writer).context
|
250
242
|
end
|
251
243
|
|
252
244
|
if fd = @inherited_fds.delete(str)
|
253
|
-
|
245
|
+
log_writer.log "* Inherited #{str}"
|
254
246
|
io = inherit_ssl_listener fd, ctx
|
255
247
|
elsif sock = @activated_sockets.delete([ :tcp, uri.host, uri.port ])
|
256
248
|
io = inherit_ssl_listener sock, ctx
|
257
|
-
|
249
|
+
log_writer.log "* Activated #{str}"
|
258
250
|
else
|
259
251
|
ios_len = @ios.length
|
260
252
|
backlog = params.fetch('backlog', 1024).to_i
|
261
|
-
|
253
|
+
low_latency = params['low_latency'] != 'false'
|
254
|
+
io = add_ssl_listener uri.host, uri.port, ctx, low_latency, backlog
|
262
255
|
|
263
256
|
@ios[ios_len..-1].each do |i|
|
264
257
|
addr = loc_addr_str i
|
265
|
-
|
258
|
+
log_writer.log "* #{log_msg} on ssl://#{addr}?#{uri.query}"
|
266
259
|
end
|
267
260
|
end
|
268
261
|
|
269
262
|
@listeners << [str, io] if io
|
270
263
|
else
|
271
|
-
|
264
|
+
log_writer.error "Invalid URI: #{str}"
|
272
265
|
end
|
273
266
|
end
|
274
267
|
|
275
268
|
# If we inherited fds but didn't use them (because of a
|
276
269
|
# configuration change), then be sure to close them.
|
277
270
|
@inherited_fds.each do |str, fd|
|
278
|
-
|
271
|
+
log_writer.log "* Closing unused inherited connection: #{str}"
|
279
272
|
|
280
273
|
begin
|
281
274
|
IO.for_fd(fd).close
|
@@ -295,7 +288,7 @@ module Puma
|
|
295
288
|
fds = @ios.map(&:to_i)
|
296
289
|
@activated_sockets.each do |key, sock|
|
297
290
|
next if fds.include? sock.to_i
|
298
|
-
|
291
|
+
log_writer.log "* Closing unused activated socket: #{key.first}://#{key[1..-1].join ':'}"
|
299
292
|
begin
|
300
293
|
sock.close
|
301
294
|
rescue SystemCallError
|
@@ -319,7 +312,7 @@ module Puma
|
|
319
312
|
local_certificates_path = File.expand_path("~/.localhost")
|
320
313
|
[File.join(local_certificates_path, "localhost.key"), File.join(local_certificates_path, "localhost.crt")]
|
321
314
|
end
|
322
|
-
MiniSSL::ContextBuilder.new({ "key" => key_path, "cert" => crt_path }, @
|
315
|
+
MiniSSL::ContextBuilder.new({ "key" => key_path, "cert" => crt_path }, @log_writer).context
|
323
316
|
end
|
324
317
|
|
325
318
|
# Tell the server to listen on host +host+, port +port+.
|
@@ -457,11 +450,14 @@ module Puma
|
|
457
450
|
|
458
451
|
def close_listeners
|
459
452
|
@listeners.each do |l, io|
|
460
|
-
|
461
|
-
|
462
|
-
|
463
|
-
|
464
|
-
|
453
|
+
begin
|
454
|
+
io.close unless io.closed?
|
455
|
+
uri = URI.parse l
|
456
|
+
next unless uri.scheme == 'unix'
|
457
|
+
unix_path = "#{uri.host}#{uri.path}"
|
458
|
+
File.unlink unix_path if @unix_paths.include?(unix_path) && File.exist?(unix_path)
|
459
|
+
rescue Errno::EBADF
|
460
|
+
end
|
465
461
|
end
|
466
462
|
end
|
467
463
|
|
@@ -482,9 +478,10 @@ module Puma
|
|
482
478
|
|
483
479
|
# @!attribute [r] loopback_addresses
|
484
480
|
def loopback_addresses
|
485
|
-
Socket.ip_address_list.select do |addrinfo|
|
481
|
+
t = Socket.ip_address_list.select do |addrinfo|
|
486
482
|
addrinfo.ipv6_loopback? || addrinfo.ipv4_loopback?
|
487
|
-
end
|
483
|
+
end
|
484
|
+
t.map! { |addrinfo| addrinfo.ip_address }; t.uniq!; t
|
488
485
|
end
|
489
486
|
|
490
487
|
def loc_addr_str(io)
|
data/lib/puma/cli.rb
CHANGED
@@ -3,11 +3,11 @@
|
|
3
3
|
require 'optparse'
|
4
4
|
require 'uri'
|
5
5
|
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
6
|
+
require_relative '../puma'
|
7
|
+
require_relative 'configuration'
|
8
|
+
require_relative 'launcher'
|
9
|
+
require_relative 'const'
|
10
|
+
require_relative 'log_writer'
|
11
11
|
|
12
12
|
module Puma
|
13
13
|
class << self
|
@@ -21,19 +21,13 @@ module Puma
|
|
21
21
|
# Handles invoke a Puma::Server in a command line style.
|
22
22
|
#
|
23
23
|
class CLI
|
24
|
-
# @deprecated 6.0.0
|
25
|
-
KEYS_NOT_TO_PERSIST_IN_STATE = Launcher::KEYS_NOT_TO_PERSIST_IN_STATE
|
26
|
-
|
27
24
|
# Create a new CLI object using +argv+ as the command line
|
28
25
|
# arguments.
|
29
26
|
#
|
30
|
-
|
31
|
-
# this object will report status on.
|
32
|
-
#
|
33
|
-
def initialize(argv, events=Events.stdio)
|
27
|
+
def initialize(argv, log_writer = LogWriter.stdio, events = Events.new)
|
34
28
|
@debug = false
|
35
29
|
@argv = argv.dup
|
36
|
-
|
30
|
+
@log_writer = log_writer
|
37
31
|
@events = events
|
38
32
|
|
39
33
|
@conf = nil
|
@@ -69,7 +63,7 @@ module Puma
|
|
69
63
|
end
|
70
64
|
end
|
71
65
|
|
72
|
-
@launcher = Puma::Launcher.new(@conf, :events => @events, :argv => argv)
|
66
|
+
@launcher = Puma::Launcher.new(@conf, :log_writer => @log_writer, :events => @events, :argv => argv)
|
73
67
|
end
|
74
68
|
|
75
69
|
attr_reader :launcher
|
@@ -83,7 +77,7 @@ module Puma
|
|
83
77
|
|
84
78
|
private
|
85
79
|
def unsupported(str)
|
86
|
-
@
|
80
|
+
@log_writer.error(str)
|
87
81
|
raise UnsupportedOption
|
88
82
|
end
|
89
83
|
|
@@ -99,7 +93,7 @@ module Puma
|
|
99
93
|
#
|
100
94
|
|
101
95
|
def setup_options
|
102
|
-
@conf = Configuration.new do |user_config, file_config|
|
96
|
+
@conf = Configuration.new({}, {events: @events}) do |user_config, file_config|
|
103
97
|
@parser = OptionParser.new do |o|
|
104
98
|
o.on "-b", "--bind URI", "URI to bind to (tcp://, unix://, ssl://)" do |arg|
|
105
99
|
user_config.bind arg
|
@@ -152,7 +146,7 @@ module Puma
|
|
152
146
|
|
153
147
|
o.on "-p", "--port PORT", "Define the TCP port to bind to",
|
154
148
|
"Use -b for more advanced options" do |arg|
|
155
|
-
user_config.bind "tcp://#{Configuration::
|
149
|
+
user_config.bind "tcp://#{Configuration::DEFAULTS[:tcp_host]}:#{arg}"
|
156
150
|
end
|
157
151
|
|
158
152
|
o.on "--pidfile PATH", "Use PATH as a pidfile" do |arg|
|
@@ -186,7 +180,7 @@ module Puma
|
|
186
180
|
end
|
187
181
|
|
188
182
|
o.on "-s", "--silent", "Do not log prompt messages other than errors" do
|
189
|
-
@
|
183
|
+
@log_writer = LogWriter.new(NullIO.new, $stderr)
|
190
184
|
end
|
191
185
|
|
192
186
|
o.on "-S", "--state PATH", "Where to store the state details" do |arg|
|
data/lib/puma/client.rb
CHANGED
@@ -8,7 +8,8 @@ class IO
|
|
8
8
|
end
|
9
9
|
end
|
10
10
|
|
11
|
-
|
11
|
+
require_relative 'detect'
|
12
|
+
require_relative 'io_buffer'
|
12
13
|
require 'tempfile'
|
13
14
|
require 'forwardable'
|
14
15
|
|
@@ -25,6 +26,9 @@ module Puma
|
|
25
26
|
|
26
27
|
class HttpParserError501 < IOError; end
|
27
28
|
|
29
|
+
#———————————————————————— DO NOT USE — this class is for internal use only ———
|
30
|
+
|
31
|
+
|
28
32
|
# An instance of this class represents a unique request from a client.
|
29
33
|
# For example, this could be a web request from a browser or from CURL.
|
30
34
|
#
|
@@ -38,7 +42,7 @@ module Puma
|
|
38
42
|
# the header and body are fully buffered via the `try_to_finish` method.
|
39
43
|
# They can be used to "time out" a response via the `timeout_at` reader.
|
40
44
|
#
|
41
|
-
class Client
|
45
|
+
class Client # :nodoc:
|
42
46
|
|
43
47
|
# this tests all values but the last, which must be chunked
|
44
48
|
ALLOWED_TRANSFER_ENCODING = %w[compress deflate gzip].freeze
|
@@ -62,12 +66,9 @@ module Puma
|
|
62
66
|
def initialize(io, env=nil)
|
63
67
|
@io = io
|
64
68
|
@to_io = io.to_io
|
69
|
+
@io_buffer = IOBuffer.new
|
65
70
|
@proto_env = env
|
66
|
-
|
67
|
-
@env = nil
|
68
|
-
else
|
69
|
-
@env = env.dup
|
70
|
-
end
|
71
|
+
@env = env&.dup
|
71
72
|
|
72
73
|
@parser = HttpParser.new
|
73
74
|
@parsed_bytes = 0
|
@@ -85,7 +86,11 @@ module Puma
|
|
85
86
|
@requests_served = 0
|
86
87
|
@hijacked = false
|
87
88
|
|
89
|
+
@http_content_length_limit = nil
|
90
|
+
@http_content_length_limit_exceeded = false
|
91
|
+
|
88
92
|
@peerip = nil
|
93
|
+
@peer_family = nil
|
89
94
|
@listener = nil
|
90
95
|
@remote_addr_header = nil
|
91
96
|
@expect_proxy_proto = false
|
@@ -93,12 +98,15 @@ module Puma
|
|
93
98
|
@body_remain = 0
|
94
99
|
|
95
100
|
@in_last_chunk = false
|
101
|
+
|
102
|
+
# need unfrozen ASCII-8BIT, +'' is UTF-8
|
103
|
+
@read_buffer = String.new # rubocop: disable Performance/UnfreezeString
|
96
104
|
end
|
97
105
|
|
98
106
|
attr_reader :env, :to_io, :body, :io, :timeout_at, :ready, :hijacked,
|
99
|
-
:tempfile
|
107
|
+
:tempfile, :io_buffer, :http_content_length_limit_exceeded
|
100
108
|
|
101
|
-
attr_writer :peerip
|
109
|
+
attr_writer :peerip, :http_content_length_limit
|
102
110
|
|
103
111
|
attr_accessor :remote_addr_header, :listener
|
104
112
|
|
@@ -138,6 +146,7 @@ module Puma
|
|
138
146
|
|
139
147
|
def reset(fast_check=true)
|
140
148
|
@parser.reset
|
149
|
+
@io_buffer.reset
|
141
150
|
@read_header = true
|
142
151
|
@read_proxy = !!@expect_proxy_proto
|
143
152
|
@env = @proto_env.dup
|
@@ -148,6 +157,7 @@ module Puma
|
|
148
157
|
@body_remain = 0
|
149
158
|
@peerip = nil if @remote_addr_header
|
150
159
|
@in_last_chunk = false
|
160
|
+
@http_content_length_limit_exceeded = false
|
151
161
|
|
152
162
|
if @buffer
|
153
163
|
return false unless try_to_parse_proxy_protocol
|
@@ -207,6 +217,17 @@ module Puma
|
|
207
217
|
end
|
208
218
|
|
209
219
|
def try_to_finish
|
220
|
+
if env[CONTENT_LENGTH] && above_http_content_limit(env[CONTENT_LENGTH].to_i)
|
221
|
+
@http_content_length_limit_exceeded = true
|
222
|
+
end
|
223
|
+
|
224
|
+
if @http_content_length_limit_exceeded
|
225
|
+
@buffer = nil
|
226
|
+
@body = EmptyBody
|
227
|
+
set_ready
|
228
|
+
return true
|
229
|
+
end
|
230
|
+
|
210
231
|
return read_body if in_data_phase
|
211
232
|
|
212
233
|
begin
|
@@ -236,6 +257,10 @@ module Puma
|
|
236
257
|
|
237
258
|
@parsed_bytes = @parser.execute(@env, @buffer, @parsed_bytes)
|
238
259
|
|
260
|
+
if @parser.finished? && above_http_content_limit(@parser.body.bytesize)
|
261
|
+
@http_content_length_limit_exceeded = true
|
262
|
+
end
|
263
|
+
|
239
264
|
if @parser.finished?
|
240
265
|
return setup_body
|
241
266
|
elsif @parsed_bytes >= MAX_HEADER
|
@@ -273,7 +298,7 @@ module Puma
|
|
273
298
|
return @peerip if @peerip
|
274
299
|
|
275
300
|
if @remote_addr_header
|
276
|
-
hdr = (@env[@remote_addr_header] ||
|
301
|
+
hdr = (@env[@remote_addr_header] || @io.peeraddr.last).split(/[\s,]/).first
|
277
302
|
@peerip = hdr
|
278
303
|
return hdr
|
279
304
|
end
|
@@ -281,6 +306,16 @@ module Puma
|
|
281
306
|
@peerip ||= @io.peeraddr.last
|
282
307
|
end
|
283
308
|
|
309
|
+
def peer_family
|
310
|
+
return @peer_family if @peer_family
|
311
|
+
|
312
|
+
@peer_family ||= begin
|
313
|
+
@io.local_address.afamily
|
314
|
+
rescue
|
315
|
+
Socket::AF_INET
|
316
|
+
end
|
317
|
+
end
|
318
|
+
|
284
319
|
# Returns true if the persistent connection can be closed immediately
|
285
320
|
# without waiting for the configured idle/shutdown timeout.
|
286
321
|
# @version 5.0.0
|
@@ -304,7 +339,7 @@ module Puma
|
|
304
339
|
private
|
305
340
|
|
306
341
|
def setup_body
|
307
|
-
@body_read_start = Process.clock_gettime(Process::CLOCK_MONOTONIC, :
|
342
|
+
@body_read_start = Process.clock_gettime(Process::CLOCK_MONOTONIC, :float_millisecond)
|
308
343
|
|
309
344
|
if @env[HTTP_EXPECT] == CONTINUE
|
310
345
|
# TODO allow a hook here to check the headers before
|
@@ -348,7 +383,7 @@ module Puma
|
|
348
383
|
|
349
384
|
if cl
|
350
385
|
# cannot contain characters that are not \d
|
351
|
-
if cl
|
386
|
+
if CONTENT_LENGTH_VALUE_INVALID.match? cl
|
352
387
|
raise HttpParserError, "Invalid Content-Length: #{cl.inspect}"
|
353
388
|
end
|
354
389
|
else
|
@@ -401,7 +436,7 @@ module Puma
|
|
401
436
|
end
|
402
437
|
|
403
438
|
begin
|
404
|
-
chunk = @io.read_nonblock(want)
|
439
|
+
chunk = @io.read_nonblock(want, @read_buffer)
|
405
440
|
rescue IO::WaitReadable
|
406
441
|
return false
|
407
442
|
rescue SystemCallError, IOError
|
@@ -433,7 +468,7 @@ module Puma
|
|
433
468
|
def read_chunked_body
|
434
469
|
while true
|
435
470
|
begin
|
436
|
-
chunk = @io.read_nonblock(4096)
|
471
|
+
chunk = @io.read_nonblock(4096, @read_buffer)
|
437
472
|
rescue IO::WaitReadable
|
438
473
|
return false
|
439
474
|
rescue SystemCallError, IOError
|
@@ -513,7 +548,7 @@ module Puma
|
|
513
548
|
# Puma doesn't process chunk extensions, but should parse if they're
|
514
549
|
# present, which is the reason for the semicolon regex
|
515
550
|
chunk_hex = line.strip[/\A[^;]+/]
|
516
|
-
if chunk_hex
|
551
|
+
if CHUNK_SIZE_INVALID.match? chunk_hex
|
517
552
|
raise HttpParserError, "Invalid chunk size: '#{chunk_hex}'"
|
518
553
|
end
|
519
554
|
len = chunk_hex.to_i(16)
|
@@ -576,10 +611,14 @@ module Puma
|
|
576
611
|
|
577
612
|
def set_ready
|
578
613
|
if @body_read_start
|
579
|
-
@env['puma.request_body_wait'] = Process.clock_gettime(Process::CLOCK_MONOTONIC, :
|
614
|
+
@env['puma.request_body_wait'] = Process.clock_gettime(Process::CLOCK_MONOTONIC, :float_millisecond) - @body_read_start
|
580
615
|
end
|
581
616
|
@requests_served += 1
|
582
617
|
@ready = true
|
583
618
|
end
|
619
|
+
|
620
|
+
def above_http_content_limit(value)
|
621
|
+
@http_content_length_limit&.< value
|
622
|
+
end
|
584
623
|
end
|
585
624
|
end
|
data/lib/puma/cluster/worker.rb
CHANGED
@@ -2,27 +2,29 @@
|
|
2
2
|
|
3
3
|
module Puma
|
4
4
|
class Cluster < Puma::Runner
|
5
|
+
#—————————————————————— DO NOT USE — this class is for internal use only ———
|
6
|
+
|
7
|
+
|
5
8
|
# This class is instantiated by the `Puma::Cluster` and represents a single
|
6
9
|
# worker process.
|
7
10
|
#
|
8
11
|
# At the core of this class is running an instance of `Puma::Server` which
|
9
12
|
# gets created via the `start_server` method from the `Puma::Runner` class
|
10
13
|
# that this inherits from.
|
11
|
-
class Worker < Puma::Runner
|
14
|
+
class Worker < Puma::Runner # :nodoc:
|
12
15
|
attr_reader :index, :master
|
13
16
|
|
14
17
|
def initialize(index:, master:, launcher:, pipes:, server: nil)
|
15
|
-
super
|
18
|
+
super(launcher)
|
16
19
|
|
17
20
|
@index = index
|
18
21
|
@master = master
|
19
|
-
@launcher = launcher
|
20
|
-
@options = launcher.options
|
21
22
|
@check_pipe = pipes[:check_pipe]
|
22
23
|
@worker_write = pipes[:worker_write]
|
23
24
|
@fork_pipe = pipes[:fork_pipe]
|
24
25
|
@wakeup = pipes[:wakeup]
|
25
26
|
@server = server
|
27
|
+
@hook_data = {}
|
26
28
|
end
|
27
29
|
|
28
30
|
def run
|
@@ -52,13 +54,14 @@ module Puma
|
|
52
54
|
|
53
55
|
# Invoke any worker boot hooks so they can get
|
54
56
|
# things in shape before booting the app.
|
55
|
-
@
|
57
|
+
@config.run_hooks(:before_worker_boot, index, @log_writer, @hook_data)
|
56
58
|
|
57
59
|
begin
|
58
60
|
server = @server ||= start_server
|
59
61
|
rescue Exception => e
|
60
62
|
log "! Unable to start worker"
|
61
|
-
log e
|
63
|
+
log e
|
64
|
+
log e.backtrace.join("\n ")
|
62
65
|
exit 1
|
63
66
|
end
|
64
67
|
|
@@ -83,8 +86,7 @@ module Puma
|
|
83
86
|
if restart_server.length > 0
|
84
87
|
restart_server.clear
|
85
88
|
server.begin_restart(true)
|
86
|
-
@
|
87
|
-
Puma::Util.nakayoshi_gc @events if @options[:nakayoshi_fork]
|
89
|
+
@config.run_hooks(:before_refork, nil, @log_writer, @hook_data)
|
88
90
|
end
|
89
91
|
elsif idx == 0 # restart server
|
90
92
|
restart_server << true << false
|
@@ -113,6 +115,11 @@ module Puma
|
|
113
115
|
|
114
116
|
while restart_server.pop
|
115
117
|
server_thread = server.run
|
118
|
+
|
119
|
+
if @log_writer.debug? && index == 0
|
120
|
+
debug_loaded_extensions "Loaded Extensions - worker 0:"
|
121
|
+
end
|
122
|
+
|
116
123
|
stat_thread ||= Thread.new(@worker_write) do |io|
|
117
124
|
Puma.set_thread_name "stat pld"
|
118
125
|
base_payload = "p#{Process.pid}"
|
@@ -138,7 +145,7 @@ module Puma
|
|
138
145
|
|
139
146
|
# Invoke any worker shutdown hooks so they can prevent the worker
|
140
147
|
# exiting until any background operations are completed
|
141
|
-
@
|
148
|
+
@config.run_hooks(:before_worker_shutdown, index, @log_writer, @hook_data)
|
142
149
|
ensure
|
143
150
|
@worker_write << "t#{Process.pid}\n" rescue nil
|
144
151
|
@worker_write.close
|
@@ -147,7 +154,7 @@ module Puma
|
|
147
154
|
private
|
148
155
|
|
149
156
|
def spawn_worker(idx)
|
150
|
-
@
|
157
|
+
@config.run_hooks(:before_worker_fork, idx, @log_writer, @hook_data)
|
151
158
|
|
152
159
|
pid = fork do
|
153
160
|
new_worker = Worker.new index: idx,
|
@@ -165,7 +172,7 @@ module Puma
|
|
165
172
|
exit! 1
|
166
173
|
end
|
167
174
|
|
168
|
-
@
|
175
|
+
@config.run_hooks(:after_worker_fork, idx, @log_writer, @hook_data)
|
169
176
|
pid
|
170
177
|
end
|
171
178
|
end
|
@@ -2,12 +2,15 @@
|
|
2
2
|
|
3
3
|
module Puma
|
4
4
|
class Cluster < Runner
|
5
|
+
#—————————————————————— DO NOT USE — this class is for internal use only ———
|
6
|
+
|
7
|
+
|
5
8
|
# This class represents a worker process from the perspective of the puma
|
6
9
|
# master process. It contains information about the process and its health
|
7
10
|
# and it exposes methods to control the process via IPC. It does not
|
8
11
|
# include the actual logic executed by the worker process itself. For that,
|
9
12
|
# see Puma::Cluster::Worker.
|
10
|
-
class WorkerHandle
|
13
|
+
class WorkerHandle # :nodoc:
|
11
14
|
def initialize(idx, pid, phase, options)
|
12
15
|
@index = idx
|
13
16
|
@pid = pid
|