puma 5.6.9 → 6.0.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 +96 -28
- data/LICENSE +0 -0
- data/README.md +21 -17
- 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 +0 -0
- 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 +0 -0
- 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 +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/no_ssl/PumaHttp11Service.java +0 -0
- data/ext/puma_http11/org/jruby/puma/Http11.java +3 -5
- 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 +36 -42
- data/lib/puma/cli.rb +11 -17
- data/lib/puma/client.rb +29 -53
- data/lib/puma/cluster/worker.rb +13 -11
- data/lib/puma/cluster/worker_handle.rb +4 -1
- data/lib/puma/cluster.rb +28 -25
- data/lib/puma/commonlogger.rb +0 -0
- data/lib/puma/configuration.rb +74 -58
- data/lib/puma/const.rb +14 -26
- data/lib/puma/control_cli.rb +3 -6
- data/lib/puma/detect.rb +2 -0
- data/lib/puma/dsl.rb +93 -52
- data/lib/puma/error_logger.rb +17 -9
- data/lib/puma/events.rb +6 -126
- data/lib/puma/io_buffer.rb +29 -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 +96 -156
- data/lib/puma/log_writer.rb +137 -0
- data/lib/puma/minissl/context_builder.rb +23 -12
- data/lib/puma/minissl.rb +82 -11
- data/lib/puma/null_io.rb +0 -0
- data/lib/puma/plugin/tmp_restart.rb +1 -1
- data/lib/puma/plugin.rb +0 -0
- data/lib/puma/rack/builder.rb +4 -4
- data/lib/puma/rack/urlmap.rb +0 -0
- data/lib/puma/rack_default.rb +1 -1
- data/lib/puma/reactor.rb +3 -3
- data/lib/puma/request.rb +295 -177
- data/lib/puma/runner.rb +41 -20
- data/lib/puma/server.rb +53 -66
- data/lib/puma/single.rb +10 -10
- data/lib/puma/state_file.rb +1 -4
- data/lib/puma/systemd.rb +3 -2
- data/lib/puma/thread_pool.rb +16 -13
- data/lib/puma/util.rb +0 -11
- data/lib/puma.rb +10 -9
- data/lib/rack/handler/puma.rb +9 -9
- data/tools/Dockerfile +0 -0
- data/tools/trickletest.rb +0 -0
- metadata +9 -6
- data/lib/puma/queue_close.rb +0 -26
- data/lib/rack/version_restriction.rb +0 -15
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,17 +142,18 @@ 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
|
@@ -174,7 +165,7 @@ module Puma
|
|
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,28 +225,29 @@ 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
|
@@ -262,20 +255,20 @@ module Puma
|
|
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+.
|
@@ -482,9 +475,10 @@ module Puma
|
|
482
475
|
|
483
476
|
# @!attribute [r] loopback_addresses
|
484
477
|
def loopback_addresses
|
485
|
-
Socket.ip_address_list.select do |addrinfo|
|
478
|
+
t = Socket.ip_address_list.select do |addrinfo|
|
486
479
|
addrinfo.ipv6_loopback? || addrinfo.ipv4_loopback?
|
487
|
-
end
|
480
|
+
end
|
481
|
+
t.map! { |addrinfo| addrinfo.ip_address }; t.uniq!; t
|
488
482
|
end
|
489
483
|
|
490
484
|
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,7 @@ class IO
|
|
8
8
|
end
|
9
9
|
end
|
10
10
|
|
11
|
-
|
11
|
+
require_relative 'detect'
|
12
12
|
require 'tempfile'
|
13
13
|
require 'forwardable'
|
14
14
|
|
@@ -25,6 +25,9 @@ module Puma
|
|
25
25
|
|
26
26
|
class HttpParserError501 < IOError; end
|
27
27
|
|
28
|
+
#———————————————————————— DO NOT USE — this class is for internal use only ———
|
29
|
+
|
30
|
+
|
28
31
|
# An instance of this class represents a unique request from a client.
|
29
32
|
# For example, this could be a web request from a browser or from CURL.
|
30
33
|
#
|
@@ -38,23 +41,14 @@ module Puma
|
|
38
41
|
# the header and body are fully buffered via the `try_to_finish` method.
|
39
42
|
# They can be used to "time out" a response via the `timeout_at` reader.
|
40
43
|
#
|
41
|
-
class Client
|
44
|
+
class Client # :nodoc:
|
42
45
|
|
43
46
|
# this tests all values but the last, which must be chunked
|
44
47
|
ALLOWED_TRANSFER_ENCODING = %w[compress deflate gzip].freeze
|
45
48
|
|
46
49
|
# chunked body validation
|
47
50
|
CHUNK_SIZE_INVALID = /[^\h]/.freeze
|
48
|
-
CHUNK_VALID_ENDING =
|
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
|
51
|
+
CHUNK_VALID_ENDING = "\r\n".freeze
|
58
52
|
|
59
53
|
# Content-Length header value validation
|
60
54
|
CONTENT_LENGTH_VALUE_INVALID = /[^\d]/.freeze
|
@@ -72,11 +66,7 @@ module Puma
|
|
72
66
|
@io = io
|
73
67
|
@to_io = io.to_io
|
74
68
|
@proto_env = env
|
75
|
-
|
76
|
-
@env = nil
|
77
|
-
else
|
78
|
-
@env = env.dup
|
79
|
-
end
|
69
|
+
@env = env ? env.dup : nil
|
80
70
|
|
81
71
|
@parser = HttpParser.new
|
82
72
|
@parsed_bytes = 0
|
@@ -95,6 +85,7 @@ module Puma
|
|
95
85
|
@hijacked = false
|
96
86
|
|
97
87
|
@peerip = nil
|
88
|
+
@peer_family = nil
|
98
89
|
@listener = nil
|
99
90
|
@remote_addr_header = nil
|
100
91
|
@expect_proxy_proto = false
|
@@ -282,7 +273,7 @@ module Puma
|
|
282
273
|
return @peerip if @peerip
|
283
274
|
|
284
275
|
if @remote_addr_header
|
285
|
-
hdr = (@env[@remote_addr_header] ||
|
276
|
+
hdr = (@env[@remote_addr_header] || @io.peeraddr.last).split(/[\s,]/).first
|
286
277
|
@peerip = hdr
|
287
278
|
return hdr
|
288
279
|
end
|
@@ -290,6 +281,16 @@ module Puma
|
|
290
281
|
@peerip ||= @io.peeraddr.last
|
291
282
|
end
|
292
283
|
|
284
|
+
def peer_family
|
285
|
+
return @peer_family if @peer_family
|
286
|
+
|
287
|
+
@peer_family ||= begin
|
288
|
+
@io.local_address.afamily
|
289
|
+
rescue
|
290
|
+
Socket::AF_INET
|
291
|
+
end
|
292
|
+
end
|
293
|
+
|
293
294
|
# Returns true if the persistent connection can be closed immediately
|
294
295
|
# without waiting for the configured idle/shutdown timeout.
|
295
296
|
# @version 5.0.0
|
@@ -313,7 +314,7 @@ module Puma
|
|
313
314
|
private
|
314
315
|
|
315
316
|
def setup_body
|
316
|
-
@body_read_start = Process.clock_gettime(Process::CLOCK_MONOTONIC, :
|
317
|
+
@body_read_start = Process.clock_gettime(Process::CLOCK_MONOTONIC, :float_millisecond)
|
317
318
|
|
318
319
|
if @env[HTTP_EXPECT] == CONTINUE
|
319
320
|
# TODO allow a hook here to check the headers before
|
@@ -356,8 +357,8 @@ module Puma
|
|
356
357
|
cl = @env[CONTENT_LENGTH]
|
357
358
|
|
358
359
|
if cl
|
359
|
-
# cannot contain characters that are not \d
|
360
|
-
if
|
360
|
+
# cannot contain characters that are not \d
|
361
|
+
if CONTENT_LENGTH_VALUE_INVALID.match? cl
|
361
362
|
raise HttpParserError, "Invalid Content-Length: #{cl.inspect}"
|
362
363
|
end
|
363
364
|
else
|
@@ -468,7 +469,6 @@ module Puma
|
|
468
469
|
@chunked_body = true
|
469
470
|
@partial_part_left = 0
|
470
471
|
@prev_chunk = ""
|
471
|
-
@excess_cr = 0
|
472
472
|
|
473
473
|
@body = Tempfile.new(Const::PUMA_TMP_BASE)
|
474
474
|
@body.unlink
|
@@ -519,11 +519,11 @@ module Puma
|
|
519
519
|
|
520
520
|
while !io.eof?
|
521
521
|
line = io.gets
|
522
|
-
if line.end_with?(
|
522
|
+
if line.end_with?("\r\n")
|
523
523
|
# Puma doesn't process chunk extensions, but should parse if they're
|
524
524
|
# present, which is the reason for the semicolon regex
|
525
525
|
chunk_hex = line.strip[/\A[^;]+/]
|
526
|
-
if chunk_hex
|
526
|
+
if CHUNK_SIZE_INVALID.match? chunk_hex
|
527
527
|
raise HttpParserError, "Invalid chunk size: '#{chunk_hex}'"
|
528
528
|
end
|
529
529
|
len = chunk_hex.to_i(16)
|
@@ -531,39 +531,19 @@ module Puma
|
|
531
531
|
@in_last_chunk = true
|
532
532
|
@body.rewind
|
533
533
|
rest = io.read
|
534
|
-
|
534
|
+
last_crlf_size = "\r\n".bytesize
|
535
|
+
if rest.bytesize < last_crlf_size
|
535
536
|
@buffer = nil
|
536
|
-
@partial_part_left =
|
537
|
+
@partial_part_left = last_crlf_size - rest.bytesize
|
537
538
|
return false
|
538
539
|
else
|
539
|
-
|
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]
|
540
|
+
@buffer = rest[last_crlf_size..-1]
|
547
541
|
@buffer = nil if @buffer.empty?
|
548
542
|
set_ready
|
549
543
|
return true
|
550
544
|
end
|
551
545
|
end
|
552
546
|
|
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
|
-
|
567
547
|
len += 2
|
568
548
|
|
569
549
|
part = io.read(len)
|
@@ -591,10 +571,6 @@ module Puma
|
|
591
571
|
@partial_part_left = len - part.size
|
592
572
|
end
|
593
573
|
else
|
594
|
-
if @prev_chunk.size + chunk.size >= MAX_CHUNK_HEADER_SIZE
|
595
|
-
raise HttpParserError, "maximum size of chunk header exceeded"
|
596
|
-
end
|
597
|
-
|
598
574
|
@prev_chunk = line
|
599
575
|
return false
|
600
576
|
end
|
@@ -610,7 +586,7 @@ module Puma
|
|
610
586
|
|
611
587
|
def set_ready
|
612
588
|
if @body_read_start
|
613
|
-
@env['puma.request_body_wait'] = Process.clock_gettime(Process::CLOCK_MONOTONIC, :
|
589
|
+
@env['puma.request_body_wait'] = Process.clock_gettime(Process::CLOCK_MONOTONIC, :float_millisecond) - @body_read_start
|
614
590
|
end
|
615
591
|
@requests_served += 1
|
616
592
|
@ready = true
|
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
|
@@ -138,7 +140,7 @@ module Puma
|
|
138
140
|
|
139
141
|
# Invoke any worker shutdown hooks so they can prevent the worker
|
140
142
|
# exiting until any background operations are completed
|
141
|
-
@
|
143
|
+
@config.run_hooks(:before_worker_shutdown, index, @log_writer, @hook_data)
|
142
144
|
ensure
|
143
145
|
@worker_write << "t#{Process.pid}\n" rescue nil
|
144
146
|
@worker_write.close
|
@@ -147,7 +149,7 @@ module Puma
|
|
147
149
|
private
|
148
150
|
|
149
151
|
def spawn_worker(idx)
|
150
|
-
@
|
152
|
+
@config.run_hooks(:before_worker_fork, idx, @log_writer, @hook_data)
|
151
153
|
|
152
154
|
pid = fork do
|
153
155
|
new_worker = Worker.new index: idx,
|
@@ -165,7 +167,7 @@ module Puma
|
|
165
167
|
exit! 1
|
166
168
|
end
|
167
169
|
|
168
|
-
@
|
170
|
+
@config.run_hooks(:after_worker_fork, idx, @log_writer, @hook_data)
|
169
171
|
pid
|
170
172
|
end
|
171
173
|
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
|