puma 5.6.5 → 6.1.1
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 +165 -11
- data/README.md +22 -17
- data/bin/puma-wild +1 -1
- data/docs/compile_options.md +34 -0
- data/docs/fork_worker.md +1 -3
- data/docs/nginx.md +1 -1
- 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/extconf.rb +11 -8
- 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 +36 -15
- 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 +156 -53
- data/ext/puma_http11/puma_http11.c +17 -9
- data/lib/puma/app/status.rb +3 -3
- data/lib/puma/binder.rb +40 -45
- data/lib/puma/cli.rb +11 -17
- data/lib/puma/client.rb +54 -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/configuration.rb +75 -58
- data/lib/puma/const.rb +76 -88
- data/lib/puma/control_cli.rb +3 -6
- data/lib/puma/detect.rb +4 -0
- data/lib/puma/dsl.rb +110 -52
- data/lib/puma/error_logger.rb +17 -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/launcher/bundle_pruner.rb +104 -0
- data/lib/puma/launcher.rb +100 -175
- data/lib/puma/log_writer.rb +141 -0
- data/lib/puma/minissl/context_builder.rb +23 -12
- data/lib/puma/minissl.rb +82 -11
- data/lib/puma/plugin/systemd.rb +90 -0
- data/lib/puma/plugin/tmp_restart.rb +1 -1
- data/lib/puma/rack/builder.rb +4 -4
- data/lib/puma/rack_default.rb +19 -4
- data/lib/puma/reactor.rb +4 -4
- data/lib/puma/request.rb +344 -165
- data/lib/puma/runner.rb +52 -20
- data/lib/puma/sd_notify.rb +149 -0
- data/lib/puma/server.rb +57 -71
- data/lib/puma/single.rb +13 -11
- data/lib/puma/state_file.rb +1 -4
- data/lib/puma/thread_pool.rb +16 -16
- data/lib/puma/util.rb +0 -11
- data/lib/puma.rb +12 -11
- data/lib/rack/handler/puma.rb +115 -94
- metadata +10 -5
- data/lib/puma/queue_close.rb +0 -26
- data/lib/puma/systemd.rb +0 -46
data/lib/puma/app/status.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
-
|
2
|
+
require_relative '../json_serialization'
|
3
3
|
|
4
4
|
module Puma
|
5
5
|
module App
|
@@ -85,8 +85,8 @@ module Puma
|
|
85
85
|
|
86
86
|
def rack_response(status, body, content_type='application/json')
|
87
87
|
headers = {
|
88
|
-
'
|
89
|
-
'
|
88
|
+
'content-type' => content_type,
|
89
|
+
'content-length' => body.bytesize.to_s
|
90
90
|
}
|
91
91
|
|
92
92
|
[status, headers, [body]]
|
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,7 +42,6 @@ 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
|
}
|
@@ -80,7 +70,7 @@ module Puma
|
|
80
70
|
# @!attribute [r] connected_ports
|
81
71
|
# @version 5.0.0
|
82
72
|
def connected_ports
|
83
|
-
ios.map { |io| io.addr[1] }.uniq
|
73
|
+
t = ios.map { |io| io.addr[1] }; t.uniq!; t
|
84
74
|
end
|
85
75
|
|
86
76
|
# @version 5.0.0
|
@@ -98,7 +88,7 @@ module Puma
|
|
98
88
|
# @version 5.0.0
|
99
89
|
#
|
100
90
|
def create_activated_fds(env_hash)
|
101
|
-
@
|
91
|
+
@log_writer.debug "ENV['LISTEN_FDS'] #{ENV['LISTEN_FDS'].inspect} env_hash['LISTEN_PID'] #{env_hash['LISTEN_PID'].inspect}"
|
102
92
|
return [] unless env_hash['LISTEN_FDS'] && env_hash['LISTEN_PID'].to_i == $$
|
103
93
|
env_hash['LISTEN_FDS'].to_i.times do |index|
|
104
94
|
sock = TCPServer.for_fd(socket_activation_fd(index))
|
@@ -106,11 +96,11 @@ module Puma
|
|
106
96
|
[:unix, Socket.unpack_sockaddr_un(sock.getsockname)]
|
107
97
|
rescue ArgumentError # Try to parse as a port/ip
|
108
98
|
port, addr = Socket.unpack_sockaddr_in(sock.getsockname)
|
109
|
-
addr = "[#{addr}]" if addr
|
99
|
+
addr = "[#{addr}]" if addr&.include? ':'
|
110
100
|
[:tcp, addr, port]
|
111
101
|
end
|
112
102
|
@activated_sockets[key] = sock
|
113
|
-
@
|
103
|
+
@log_writer.debug "Registered #{key.join ':'} for activation from LISTEN_FDS"
|
114
104
|
end
|
115
105
|
["LISTEN_FDS", "LISTEN_PID"] # Signal to remove these keys from ENV
|
116
106
|
end
|
@@ -152,29 +142,30 @@ module Puma
|
|
152
142
|
end
|
153
143
|
end
|
154
144
|
|
155
|
-
def parse(binds,
|
145
|
+
def parse(binds, log_writer = nil, log_msg = 'Listening')
|
146
|
+
log_writer ||= @log_writer
|
156
147
|
binds.each do |str|
|
157
148
|
uri = URI.parse str
|
158
149
|
case uri.scheme
|
159
150
|
when "tcp"
|
160
151
|
if fd = @inherited_fds.delete(str)
|
161
152
|
io = inherit_tcp_listener uri.host, uri.port, fd
|
162
|
-
|
153
|
+
log_writer.log "* Inherited #{str}"
|
163
154
|
elsif sock = @activated_sockets.delete([ :tcp, uri.host, uri.port ])
|
164
155
|
io = inherit_tcp_listener uri.host, uri.port, sock
|
165
|
-
|
156
|
+
log_writer.log "* Activated #{str}"
|
166
157
|
else
|
167
158
|
ios_len = @ios.length
|
168
159
|
params = Util.parse_query uri.query
|
169
160
|
|
170
|
-
|
161
|
+
low_latency = params.key?('low_latency') && params['low_latency'] != 'false'
|
171
162
|
backlog = params.fetch('backlog', 1024).to_i
|
172
163
|
|
173
|
-
io = add_tcp_listener uri.host, uri.port,
|
164
|
+
io = add_tcp_listener uri.host, uri.port, low_latency, backlog
|
174
165
|
|
175
166
|
@ios[ios_len..-1].each do |i|
|
176
167
|
addr = loc_addr_str i
|
177
|
-
|
168
|
+
log_writer.log "* #{log_msg} on http://#{addr}"
|
178
169
|
end
|
179
170
|
end
|
180
171
|
|
@@ -191,12 +182,12 @@ module Puma
|
|
191
182
|
if fd = @inherited_fds.delete(str)
|
192
183
|
@unix_paths << path unless abstract || File.exist?(path)
|
193
184
|
io = inherit_unix_listener path, fd
|
194
|
-
|
185
|
+
log_writer.log "* Inherited #{str}"
|
195
186
|
elsif sock = @activated_sockets.delete([ :unix, path ]) ||
|
196
187
|
@activated_sockets.delete([ :unix, File.realdirpath(path) ])
|
197
188
|
@unix_paths << path unless abstract || File.exist?(path)
|
198
189
|
io = inherit_unix_listener path, sock
|
199
|
-
|
190
|
+
log_writer.log "* Activated #{str}"
|
200
191
|
else
|
201
192
|
umask = nil
|
202
193
|
mode = nil
|
@@ -220,11 +211,12 @@ module Puma
|
|
220
211
|
|
221
212
|
@unix_paths << path unless abstract || File.exist?(path)
|
222
213
|
io = add_unix_listener path, umask, mode, backlog
|
223
|
-
|
214
|
+
log_writer.log "* #{log_msg} on #{str}"
|
224
215
|
end
|
225
216
|
|
226
217
|
@listeners << [str, io]
|
227
218
|
when "ssl"
|
219
|
+
cert_key = %w[cert key]
|
228
220
|
|
229
221
|
raise "Puma compiled without SSL support" unless HAS_SSL
|
230
222
|
|
@@ -233,49 +225,51 @@ module Puma
|
|
233
225
|
# If key and certs are not defined and localhost gem is required.
|
234
226
|
# localhost gem will be used for self signed
|
235
227
|
# Load localhost authority if not loaded.
|
236
|
-
|
228
|
+
# Ruby 3 `values_at` accepts an array, earlier do not
|
229
|
+
if params.values_at(*cert_key).all? { |v| v.to_s.empty? }
|
237
230
|
ctx = localhost_authority && localhost_authority_context
|
238
231
|
end
|
239
232
|
|
240
233
|
ctx ||=
|
241
234
|
begin
|
242
235
|
# Extract cert_pem and key_pem from options[:store] if present
|
243
|
-
|
244
|
-
if params[v]
|
236
|
+
cert_key.each do |v|
|
237
|
+
if params[v]&.start_with?('store:')
|
245
238
|
index = Integer(params.delete(v).split('store:').last)
|
246
239
|
params["#{v}_pem"] = @conf.options[:store][index]
|
247
240
|
end
|
248
241
|
end
|
249
|
-
MiniSSL::ContextBuilder.new(params, @
|
242
|
+
MiniSSL::ContextBuilder.new(params, @log_writer).context
|
250
243
|
end
|
251
244
|
|
252
245
|
if fd = @inherited_fds.delete(str)
|
253
|
-
|
246
|
+
log_writer.log "* Inherited #{str}"
|
254
247
|
io = inherit_ssl_listener fd, ctx
|
255
248
|
elsif sock = @activated_sockets.delete([ :tcp, uri.host, uri.port ])
|
256
249
|
io = inherit_ssl_listener sock, ctx
|
257
|
-
|
250
|
+
log_writer.log "* Activated #{str}"
|
258
251
|
else
|
259
252
|
ios_len = @ios.length
|
260
253
|
backlog = params.fetch('backlog', 1024).to_i
|
261
|
-
|
254
|
+
low_latency = params['low_latency'] != 'false'
|
255
|
+
io = add_ssl_listener uri.host, uri.port, ctx, low_latency, backlog
|
262
256
|
|
263
257
|
@ios[ios_len..-1].each do |i|
|
264
258
|
addr = loc_addr_str i
|
265
|
-
|
259
|
+
log_writer.log "* #{log_msg} on ssl://#{addr}?#{uri.query}"
|
266
260
|
end
|
267
261
|
end
|
268
262
|
|
269
263
|
@listeners << [str, io] if io
|
270
264
|
else
|
271
|
-
|
265
|
+
log_writer.error "Invalid URI: #{str}"
|
272
266
|
end
|
273
267
|
end
|
274
268
|
|
275
269
|
# If we inherited fds but didn't use them (because of a
|
276
270
|
# configuration change), then be sure to close them.
|
277
271
|
@inherited_fds.each do |str, fd|
|
278
|
-
|
272
|
+
log_writer.log "* Closing unused inherited connection: #{str}"
|
279
273
|
|
280
274
|
begin
|
281
275
|
IO.for_fd(fd).close
|
@@ -295,7 +289,7 @@ module Puma
|
|
295
289
|
fds = @ios.map(&:to_i)
|
296
290
|
@activated_sockets.each do |key, sock|
|
297
291
|
next if fds.include? sock.to_i
|
298
|
-
|
292
|
+
log_writer.log "* Closing unused activated socket: #{key.first}://#{key[1..-1].join ':'}"
|
299
293
|
begin
|
300
294
|
sock.close
|
301
295
|
rescue SystemCallError
|
@@ -319,7 +313,7 @@ module Puma
|
|
319
313
|
local_certificates_path = File.expand_path("~/.localhost")
|
320
314
|
[File.join(local_certificates_path, "localhost.key"), File.join(local_certificates_path, "localhost.crt")]
|
321
315
|
end
|
322
|
-
MiniSSL::ContextBuilder.new({ "key" => key_path, "cert" => crt_path }, @
|
316
|
+
MiniSSL::ContextBuilder.new({ "key" => key_path, "cert" => crt_path }, @log_writer).context
|
323
317
|
end
|
324
318
|
|
325
319
|
# Tell the server to listen on host +host+, port +port+.
|
@@ -482,9 +476,10 @@ module Puma
|
|
482
476
|
|
483
477
|
# @!attribute [r] loopback_addresses
|
484
478
|
def loopback_addresses
|
485
|
-
Socket.ip_address_list.select do |addrinfo|
|
479
|
+
t = Socket.ip_address_list.select do |addrinfo|
|
486
480
|
addrinfo.ipv6_loopback? || addrinfo.ipv4_loopback?
|
487
|
-
end
|
481
|
+
end
|
482
|
+
t.map! { |addrinfo| addrinfo.ip_address }; t.uniq!; t
|
488
483
|
end
|
489
484
|
|
490
485
|
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
|
|
@@ -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 ? env.dup : nil
|
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,14 @@ module Puma
|
|
93
98
|
@body_remain = 0
|
94
99
|
|
95
100
|
@in_last_chunk = false
|
101
|
+
|
102
|
+
@read_buffer = +""
|
96
103
|
end
|
97
104
|
|
98
105
|
attr_reader :env, :to_io, :body, :io, :timeout_at, :ready, :hijacked,
|
99
|
-
:tempfile
|
106
|
+
:tempfile, :io_buffer, :http_content_length_limit_exceeded
|
100
107
|
|
101
|
-
attr_writer :peerip
|
108
|
+
attr_writer :peerip, :http_content_length_limit
|
102
109
|
|
103
110
|
attr_accessor :remote_addr_header, :listener
|
104
111
|
|
@@ -138,6 +145,7 @@ module Puma
|
|
138
145
|
|
139
146
|
def reset(fast_check=true)
|
140
147
|
@parser.reset
|
148
|
+
@io_buffer.reset
|
141
149
|
@read_header = true
|
142
150
|
@read_proxy = !!@expect_proxy_proto
|
143
151
|
@env = @proto_env.dup
|
@@ -148,6 +156,7 @@ module Puma
|
|
148
156
|
@body_remain = 0
|
149
157
|
@peerip = nil if @remote_addr_header
|
150
158
|
@in_last_chunk = false
|
159
|
+
@http_content_length_limit_exceeded = false
|
151
160
|
|
152
161
|
if @buffer
|
153
162
|
return false unless try_to_parse_proxy_protocol
|
@@ -207,6 +216,17 @@ module Puma
|
|
207
216
|
end
|
208
217
|
|
209
218
|
def try_to_finish
|
219
|
+
if env[CONTENT_LENGTH] && above_http_content_limit(env[CONTENT_LENGTH].to_i)
|
220
|
+
@http_content_length_limit_exceeded = true
|
221
|
+
end
|
222
|
+
|
223
|
+
if @http_content_length_limit_exceeded
|
224
|
+
@buffer = nil
|
225
|
+
@body = EmptyBody
|
226
|
+
set_ready
|
227
|
+
return true
|
228
|
+
end
|
229
|
+
|
210
230
|
return read_body if in_data_phase
|
211
231
|
|
212
232
|
begin
|
@@ -236,6 +256,10 @@ module Puma
|
|
236
256
|
|
237
257
|
@parsed_bytes = @parser.execute(@env, @buffer, @parsed_bytes)
|
238
258
|
|
259
|
+
if @parser.finished? && above_http_content_limit(@parser.body.bytesize)
|
260
|
+
@http_content_length_limit_exceeded = true
|
261
|
+
end
|
262
|
+
|
239
263
|
if @parser.finished?
|
240
264
|
return setup_body
|
241
265
|
elsif @parsed_bytes >= MAX_HEADER
|
@@ -273,7 +297,7 @@ module Puma
|
|
273
297
|
return @peerip if @peerip
|
274
298
|
|
275
299
|
if @remote_addr_header
|
276
|
-
hdr = (@env[@remote_addr_header] ||
|
300
|
+
hdr = (@env[@remote_addr_header] || @io.peeraddr.last).split(/[\s,]/).first
|
277
301
|
@peerip = hdr
|
278
302
|
return hdr
|
279
303
|
end
|
@@ -281,6 +305,16 @@ module Puma
|
|
281
305
|
@peerip ||= @io.peeraddr.last
|
282
306
|
end
|
283
307
|
|
308
|
+
def peer_family
|
309
|
+
return @peer_family if @peer_family
|
310
|
+
|
311
|
+
@peer_family ||= begin
|
312
|
+
@io.local_address.afamily
|
313
|
+
rescue
|
314
|
+
Socket::AF_INET
|
315
|
+
end
|
316
|
+
end
|
317
|
+
|
284
318
|
# Returns true if the persistent connection can be closed immediately
|
285
319
|
# without waiting for the configured idle/shutdown timeout.
|
286
320
|
# @version 5.0.0
|
@@ -304,7 +338,7 @@ module Puma
|
|
304
338
|
private
|
305
339
|
|
306
340
|
def setup_body
|
307
|
-
@body_read_start = Process.clock_gettime(Process::CLOCK_MONOTONIC, :
|
341
|
+
@body_read_start = Process.clock_gettime(Process::CLOCK_MONOTONIC, :float_millisecond)
|
308
342
|
|
309
343
|
if @env[HTTP_EXPECT] == CONTINUE
|
310
344
|
# TODO allow a hook here to check the headers before
|
@@ -348,7 +382,7 @@ module Puma
|
|
348
382
|
|
349
383
|
if cl
|
350
384
|
# cannot contain characters that are not \d
|
351
|
-
if cl
|
385
|
+
if CONTENT_LENGTH_VALUE_INVALID.match? cl
|
352
386
|
raise HttpParserError, "Invalid Content-Length: #{cl.inspect}"
|
353
387
|
end
|
354
388
|
else
|
@@ -401,7 +435,7 @@ module Puma
|
|
401
435
|
end
|
402
436
|
|
403
437
|
begin
|
404
|
-
chunk = @io.read_nonblock(want)
|
438
|
+
chunk = @io.read_nonblock(want, @read_buffer)
|
405
439
|
rescue IO::WaitReadable
|
406
440
|
return false
|
407
441
|
rescue SystemCallError, IOError
|
@@ -433,7 +467,7 @@ module Puma
|
|
433
467
|
def read_chunked_body
|
434
468
|
while true
|
435
469
|
begin
|
436
|
-
chunk = @io.read_nonblock(4096)
|
470
|
+
chunk = @io.read_nonblock(4096, @read_buffer)
|
437
471
|
rescue IO::WaitReadable
|
438
472
|
return false
|
439
473
|
rescue SystemCallError, IOError
|
@@ -513,7 +547,7 @@ module Puma
|
|
513
547
|
# Puma doesn't process chunk extensions, but should parse if they're
|
514
548
|
# present, which is the reason for the semicolon regex
|
515
549
|
chunk_hex = line.strip[/\A[^;]+/]
|
516
|
-
if chunk_hex
|
550
|
+
if CHUNK_SIZE_INVALID.match? chunk_hex
|
517
551
|
raise HttpParserError, "Invalid chunk size: '#{chunk_hex}'"
|
518
552
|
end
|
519
553
|
len = chunk_hex.to_i(16)
|
@@ -576,10 +610,14 @@ module Puma
|
|
576
610
|
|
577
611
|
def set_ready
|
578
612
|
if @body_read_start
|
579
|
-
@env['puma.request_body_wait'] = Process.clock_gettime(Process::CLOCK_MONOTONIC, :
|
613
|
+
@env['puma.request_body_wait'] = Process.clock_gettime(Process::CLOCK_MONOTONIC, :float_millisecond) - @body_read_start
|
580
614
|
end
|
581
615
|
@requests_served += 1
|
582
616
|
@ready = true
|
583
617
|
end
|
618
|
+
|
619
|
+
def above_http_content_limit(value)
|
620
|
+
@http_content_length_limit&.< value
|
621
|
+
end
|
584
622
|
end
|
585
623
|
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
|