puma 5.3.2 → 6.0.0
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 +284 -11
- data/LICENSE +0 -0
- data/README.md +61 -16
- data/bin/puma-wild +1 -1
- data/docs/architecture.md +49 -16
- data/docs/compile_options.md +38 -2
- data/docs/deployment.md +53 -67
- 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 +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/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 +44 -13
- data/ext/puma_http11/http11_parser.c +24 -11
- 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 +3 -3
- data/ext/puma_http11/mini_ssl.c +122 -23
- 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 +50 -48
- data/ext/puma_http11/org/jruby/puma/MiniSSL.java +188 -102
- data/ext/puma_http11/puma_http11.c +18 -10
- data/lib/puma/app/status.rb +9 -6
- data/lib/puma/binder.rb +81 -42
- data/lib/puma/cli.rb +23 -19
- data/lib/puma/client.rb +124 -30
- data/lib/puma/cluster/worker.rb +21 -29
- data/lib/puma/cluster/worker_handle.rb +8 -1
- data/lib/puma/cluster.rb +57 -48
- data/lib/puma/commonlogger.rb +0 -0
- data/lib/puma/configuration.rb +74 -55
- data/lib/puma/const.rb +21 -24
- data/lib/puma/control_cli.rb +22 -19
- data/lib/puma/detect.rb +10 -2
- data/lib/puma/dsl.rb +196 -57
- 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.rb → json_serialization.rb} +1 -1
- data/lib/puma/launcher/bundle_pruner.rb +104 -0
- data/lib/puma/launcher.rb +108 -154
- data/lib/puma/log_writer.rb +137 -0
- data/lib/puma/minissl/context_builder.rb +29 -16
- data/lib/puma/minissl.rb +115 -38
- data/lib/puma/null_io.rb +5 -0
- data/lib/puma/plugin/tmp_restart.rb +1 -1
- data/lib/puma/plugin.rb +2 -2
- data/lib/puma/rack/builder.rb +5 -5
- 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 +293 -153
- data/lib/puma/runner.rb +63 -28
- data/lib/puma/server.rb +83 -88
- data/lib/puma/single.rb +10 -10
- data/lib/puma/state_file.rb +39 -7
- data/lib/puma/systemd.rb +3 -2
- data/lib/puma/thread_pool.rb +22 -17
- data/lib/puma/util.rb +20 -15
- data/lib/puma.rb +12 -9
- data/lib/rack/handler/puma.rb +9 -9
- data/tools/Dockerfile +1 -1
- data/tools/trickletest.rb +0 -0
- metadata +13 -9
- data/lib/puma/queue_close.rb +0 -26
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,9 @@ 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
|
24
|
+
@conf = conf
|
33
25
|
@listeners = []
|
34
26
|
@inherited_fds = {}
|
35
27
|
@activated_sockets = {}
|
@@ -37,10 +29,11 @@ module Puma
|
|
37
29
|
|
38
30
|
@proto_env = {
|
39
31
|
"rack.version".freeze => RACK_VERSION,
|
40
|
-
"rack.errors".freeze =>
|
32
|
+
"rack.errors".freeze => log_writer.stderr,
|
41
33
|
"rack.multithread".freeze => conf.options[:max_threads] > 1,
|
42
34
|
"rack.multiprocess".freeze => conf.options[:workers] >= 1,
|
43
35
|
"rack.run_once".freeze => false,
|
36
|
+
RACK_URL_SCHEME => conf.options[:rack_url_scheme],
|
44
37
|
"SCRIPT_NAME".freeze => ENV['SCRIPT_NAME'] || "",
|
45
38
|
|
46
39
|
# I'd like to set a default CONTENT_TYPE here but some things
|
@@ -49,13 +42,13 @@ module Puma
|
|
49
42
|
# infer properly.
|
50
43
|
|
51
44
|
"QUERY_STRING".freeze => "",
|
52
|
-
SERVER_PROTOCOL => HTTP_11,
|
53
45
|
SERVER_SOFTWARE => PUMA_SERVER_STRING,
|
54
46
|
GATEWAY_INTERFACE => CGI_VER
|
55
47
|
}
|
56
48
|
|
57
49
|
@envs = {}
|
58
50
|
@ios = []
|
51
|
+
localhost_authority
|
59
52
|
end
|
60
53
|
|
61
54
|
attr_reader :ios
|
@@ -77,7 +70,7 @@ module Puma
|
|
77
70
|
# @!attribute [r] connected_ports
|
78
71
|
# @version 5.0.0
|
79
72
|
def connected_ports
|
80
|
-
ios.map { |io| io.addr[1] }.uniq
|
73
|
+
t = ios.map { |io| io.addr[1] }; t.uniq!; t
|
81
74
|
end
|
82
75
|
|
83
76
|
# @version 5.0.0
|
@@ -95,6 +88,7 @@ module Puma
|
|
95
88
|
# @version 5.0.0
|
96
89
|
#
|
97
90
|
def create_activated_fds(env_hash)
|
91
|
+
@log_writer.debug "ENV['LISTEN_FDS'] #{ENV['LISTEN_FDS'].inspect} env_hash['LISTEN_PID'] #{env_hash['LISTEN_PID'].inspect}"
|
98
92
|
return [] unless env_hash['LISTEN_FDS'] && env_hash['LISTEN_PID'].to_i == $$
|
99
93
|
env_hash['LISTEN_FDS'].to_i.times do |index|
|
100
94
|
sock = TCPServer.for_fd(socket_activation_fd(index))
|
@@ -102,11 +96,11 @@ module Puma
|
|
102
96
|
[:unix, Socket.unpack_sockaddr_un(sock.getsockname)]
|
103
97
|
rescue ArgumentError # Try to parse as a port/ip
|
104
98
|
port, addr = Socket.unpack_sockaddr_in(sock.getsockname)
|
105
|
-
addr = "[#{addr}]" if addr
|
99
|
+
addr = "[#{addr}]" if addr&.include? ':'
|
106
100
|
[:tcp, addr, port]
|
107
101
|
end
|
108
102
|
@activated_sockets[key] = sock
|
109
|
-
@
|
103
|
+
@log_writer.debug "Registered #{key.join ':'} for activation from LISTEN_FDS"
|
110
104
|
end
|
111
105
|
["LISTEN_FDS", "LISTEN_PID"] # Signal to remove these keys from ENV
|
112
106
|
end
|
@@ -148,29 +142,30 @@ module Puma
|
|
148
142
|
end
|
149
143
|
end
|
150
144
|
|
151
|
-
def parse(binds,
|
145
|
+
def parse(binds, log_writer = nil, log_msg = 'Listening')
|
146
|
+
log_writer ||= @log_writer
|
152
147
|
binds.each do |str|
|
153
148
|
uri = URI.parse str
|
154
149
|
case uri.scheme
|
155
150
|
when "tcp"
|
156
151
|
if fd = @inherited_fds.delete(str)
|
157
152
|
io = inherit_tcp_listener uri.host, uri.port, fd
|
158
|
-
|
153
|
+
log_writer.log "* Inherited #{str}"
|
159
154
|
elsif sock = @activated_sockets.delete([ :tcp, uri.host, uri.port ])
|
160
155
|
io = inherit_tcp_listener uri.host, uri.port, sock
|
161
|
-
|
156
|
+
log_writer.log "* Activated #{str}"
|
162
157
|
else
|
163
158
|
ios_len = @ios.length
|
164
159
|
params = Util.parse_query uri.query
|
165
160
|
|
166
161
|
opt = params.key?('low_latency') && params['low_latency'] != 'false'
|
167
|
-
|
162
|
+
backlog = params.fetch('backlog', 1024).to_i
|
168
163
|
|
169
|
-
io = add_tcp_listener uri.host, uri.port, opt,
|
164
|
+
io = add_tcp_listener uri.host, uri.port, opt, backlog
|
170
165
|
|
171
166
|
@ios[ios_len..-1].each do |i|
|
172
167
|
addr = loc_addr_str i
|
173
|
-
|
168
|
+
log_writer.log "* #{log_msg} on http://#{addr}"
|
174
169
|
end
|
175
170
|
end
|
176
171
|
|
@@ -185,13 +180,14 @@ module Puma
|
|
185
180
|
end
|
186
181
|
|
187
182
|
if fd = @inherited_fds.delete(str)
|
188
|
-
@unix_paths << path unless abstract
|
183
|
+
@unix_paths << path unless abstract || File.exist?(path)
|
189
184
|
io = inherit_unix_listener path, fd
|
190
|
-
|
191
|
-
elsif sock = @activated_sockets.delete([ :unix, path ])
|
185
|
+
log_writer.log "* Inherited #{str}"
|
186
|
+
elsif sock = @activated_sockets.delete([ :unix, path ]) ||
|
187
|
+
@activated_sockets.delete([ :unix, File.realdirpath(path) ])
|
192
188
|
@unix_paths << path unless abstract || File.exist?(path)
|
193
189
|
io = inherit_unix_listener path, sock
|
194
|
-
|
190
|
+
log_writer.log "* Activated #{str}"
|
195
191
|
else
|
196
192
|
umask = nil
|
197
193
|
mode = nil
|
@@ -215,43 +211,64 @@ module Puma
|
|
215
211
|
|
216
212
|
@unix_paths << path unless abstract || File.exist?(path)
|
217
213
|
io = add_unix_listener path, umask, mode, backlog
|
218
|
-
|
214
|
+
log_writer.log "* #{log_msg} on #{str}"
|
219
215
|
end
|
220
216
|
|
221
217
|
@listeners << [str, io]
|
222
218
|
when "ssl"
|
219
|
+
cert_key = %w[cert key]
|
223
220
|
|
224
221
|
raise "Puma compiled without SSL support" unless HAS_SSL
|
225
222
|
|
226
223
|
params = Util.parse_query uri.query
|
227
|
-
|
224
|
+
|
225
|
+
# If key and certs are not defined and localhost gem is required.
|
226
|
+
# localhost gem will be used for self signed
|
227
|
+
# Load localhost authority if not loaded.
|
228
|
+
# Ruby 3 `values_at` accepts an array, earlier do not
|
229
|
+
if params.values_at(*cert_key).all? { |v| v.to_s.empty? }
|
230
|
+
ctx = localhost_authority && localhost_authority_context
|
231
|
+
end
|
232
|
+
|
233
|
+
ctx ||=
|
234
|
+
begin
|
235
|
+
# Extract cert_pem and key_pem from options[:store] if present
|
236
|
+
cert_key.each do |v|
|
237
|
+
if params[v]&.start_with?('store:')
|
238
|
+
index = Integer(params.delete(v).split('store:').last)
|
239
|
+
params["#{v}_pem"] = @conf.options[:store][index]
|
240
|
+
end
|
241
|
+
end
|
242
|
+
MiniSSL::ContextBuilder.new(params, @log_writer).context
|
243
|
+
end
|
228
244
|
|
229
245
|
if fd = @inherited_fds.delete(str)
|
230
|
-
|
246
|
+
log_writer.log "* Inherited #{str}"
|
231
247
|
io = inherit_ssl_listener fd, ctx
|
232
248
|
elsif sock = @activated_sockets.delete([ :tcp, uri.host, uri.port ])
|
233
249
|
io = inherit_ssl_listener sock, ctx
|
234
|
-
|
250
|
+
log_writer.log "* Activated #{str}"
|
235
251
|
else
|
236
252
|
ios_len = @ios.length
|
237
|
-
|
253
|
+
backlog = params.fetch('backlog', 1024).to_i
|
254
|
+
io = add_ssl_listener uri.host, uri.port, ctx, optimize_for_latency = true, backlog
|
238
255
|
|
239
256
|
@ios[ios_len..-1].each do |i|
|
240
257
|
addr = loc_addr_str i
|
241
|
-
|
258
|
+
log_writer.log "* #{log_msg} on ssl://#{addr}?#{uri.query}"
|
242
259
|
end
|
243
260
|
end
|
244
261
|
|
245
262
|
@listeners << [str, io] if io
|
246
263
|
else
|
247
|
-
|
264
|
+
log_writer.error "Invalid URI: #{str}"
|
248
265
|
end
|
249
266
|
end
|
250
267
|
|
251
268
|
# If we inherited fds but didn't use them (because of a
|
252
269
|
# configuration change), then be sure to close them.
|
253
270
|
@inherited_fds.each do |str, fd|
|
254
|
-
|
271
|
+
log_writer.log "* Closing unused inherited connection: #{str}"
|
255
272
|
|
256
273
|
begin
|
257
274
|
IO.for_fd(fd).close
|
@@ -271,7 +288,7 @@ module Puma
|
|
271
288
|
fds = @ios.map(&:to_i)
|
272
289
|
@activated_sockets.each do |key, sock|
|
273
290
|
next if fds.include? sock.to_i
|
274
|
-
|
291
|
+
log_writer.log "* Closing unused activated socket: #{key.first}://#{key[1..-1].join ':'}"
|
275
292
|
begin
|
276
293
|
sock.close
|
277
294
|
rescue SystemCallError
|
@@ -282,6 +299,22 @@ module Puma
|
|
282
299
|
end
|
283
300
|
end
|
284
301
|
|
302
|
+
def localhost_authority
|
303
|
+
@localhost_authority ||= Localhost::Authority.fetch if defined?(Localhost::Authority) && !Puma::IS_JRUBY
|
304
|
+
end
|
305
|
+
|
306
|
+
def localhost_authority_context
|
307
|
+
return unless localhost_authority
|
308
|
+
|
309
|
+
key_path, crt_path = if [:key_path, :certificate_path].all? { |m| localhost_authority.respond_to?(m) }
|
310
|
+
[localhost_authority.key_path, localhost_authority.certificate_path]
|
311
|
+
else
|
312
|
+
local_certificates_path = File.expand_path("~/.localhost")
|
313
|
+
[File.join(local_certificates_path, "localhost.key"), File.join(local_certificates_path, "localhost.crt")]
|
314
|
+
end
|
315
|
+
MiniSSL::ContextBuilder.new({ "key" => key_path, "cert" => crt_path }, @log_writer).context
|
316
|
+
end
|
317
|
+
|
285
318
|
# Tell the server to listen on host +host+, port +port+.
|
286
319
|
# If +optimize_for_latency+ is true (the default) then clients connecting
|
287
320
|
# will be optimized for latency over throughput.
|
@@ -299,6 +332,7 @@ module Puma
|
|
299
332
|
|
300
333
|
host = host[1..-2] if host and host[0..0] == '['
|
301
334
|
tcp_server = TCPServer.new(host, port)
|
335
|
+
|
302
336
|
if optimize_for_latency
|
303
337
|
tcp_server.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
|
304
338
|
end
|
@@ -320,6 +354,8 @@ module Puma
|
|
320
354
|
optimize_for_latency=true, backlog=1024)
|
321
355
|
|
322
356
|
raise "Puma compiled without SSL support" unless HAS_SSL
|
357
|
+
# Puma will try to use local authority context if context is supplied nil
|
358
|
+
ctx ||= localhost_authority_context
|
323
359
|
|
324
360
|
if host == "localhost"
|
325
361
|
loopback_addresses.each do |addr|
|
@@ -347,6 +383,8 @@ module Puma
|
|
347
383
|
|
348
384
|
def inherit_ssl_listener(fd, ctx)
|
349
385
|
raise "Puma compiled without SSL support" unless HAS_SSL
|
386
|
+
# Puma will try to use local authority context if context is supplied nil
|
387
|
+
ctx ||= localhost_authority_context
|
350
388
|
|
351
389
|
s = fd.kind_of?(::TCPServer) ? fd : ::TCPServer.for_fd(fd)
|
352
390
|
|
@@ -437,9 +475,10 @@ module Puma
|
|
437
475
|
|
438
476
|
# @!attribute [r] loopback_addresses
|
439
477
|
def loopback_addresses
|
440
|
-
Socket.ip_address_list.select do |addrinfo|
|
478
|
+
t = Socket.ip_address_list.select do |addrinfo|
|
441
479
|
addrinfo.ipv6_loopback? || addrinfo.ipv4_loopback?
|
442
|
-
end
|
480
|
+
end
|
481
|
+
t.map! { |addrinfo| addrinfo.ip_address }; t.uniq!; t
|
443
482
|
end
|
444
483
|
|
445
484
|
def loc_addr_str(io)
|
data/lib/puma/cli.rb
CHANGED
@@ -3,36 +3,31 @@
|
|
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
|
14
|
-
# The CLI exports
|
15
|
-
# apps to pick it up. An app
|
16
|
-
#
|
17
|
-
#
|
14
|
+
# The CLI exports a Puma::Configuration instance here to allow
|
15
|
+
# apps to pick it up. An app must load this object conditionally
|
16
|
+
# because it is not set if the app is launched via any mechanism
|
17
|
+
# other than the CLI class.
|
18
18
|
attr_accessor :cli_config
|
19
19
|
end
|
20
20
|
|
21
21
|
# Handles invoke a Puma::Server in a command line style.
|
22
22
|
#
|
23
23
|
class CLI
|
24
|
-
KEYS_NOT_TO_PERSIST_IN_STATE = Launcher::KEYS_NOT_TO_PERSIST_IN_STATE
|
25
|
-
|
26
24
|
# Create a new CLI object using +argv+ as the command line
|
27
25
|
# arguments.
|
28
26
|
#
|
29
|
-
|
30
|
-
# this object will report status on.
|
31
|
-
#
|
32
|
-
def initialize(argv, events=Events.stdio)
|
27
|
+
def initialize(argv, log_writer = LogWriter.stdio, events = Events.new)
|
33
28
|
@debug = false
|
34
29
|
@argv = argv.dup
|
35
|
-
|
30
|
+
@log_writer = log_writer
|
36
31
|
@events = events
|
37
32
|
|
38
33
|
@conf = nil
|
@@ -68,7 +63,7 @@ module Puma
|
|
68
63
|
end
|
69
64
|
end
|
70
65
|
|
71
|
-
@launcher = Puma::Launcher.new(@conf, :events => @events, :argv => argv)
|
66
|
+
@launcher = Puma::Launcher.new(@conf, :log_writer => @log_writer, :events => @events, :argv => argv)
|
72
67
|
end
|
73
68
|
|
74
69
|
attr_reader :launcher
|
@@ -82,7 +77,7 @@ module Puma
|
|
82
77
|
|
83
78
|
private
|
84
79
|
def unsupported(str)
|
85
|
-
@
|
80
|
+
@log_writer.error(str)
|
86
81
|
raise UnsupportedOption
|
87
82
|
end
|
88
83
|
|
@@ -112,6 +107,11 @@ module Puma
|
|
112
107
|
file_config.load arg
|
113
108
|
end
|
114
109
|
|
110
|
+
# Identical to supplying --config "-", but more semantic
|
111
|
+
o.on "--no-config", "Prevent Puma from searching for a config file" do |arg|
|
112
|
+
file_config.load "-"
|
113
|
+
end
|
114
|
+
|
115
115
|
o.on "--control-url URL", "The bind url to use for the control server. Use 'auto' to use temp unix server" do |arg|
|
116
116
|
configure_control_url(arg)
|
117
117
|
end
|
@@ -146,7 +146,7 @@ module Puma
|
|
146
146
|
|
147
147
|
o.on "-p", "--port PORT", "Define the TCP port to bind to",
|
148
148
|
"Use -b for more advanced options" do |arg|
|
149
|
-
user_config.bind "tcp://#{Configuration::
|
149
|
+
user_config.bind "tcp://#{Configuration::DEFAULTS[:tcp_host]}:#{arg}"
|
150
150
|
end
|
151
151
|
|
152
152
|
o.on "--pidfile PATH", "Use PATH as a pidfile" do |arg|
|
@@ -179,6 +179,10 @@ module Puma
|
|
179
179
|
user_config.restart_command cmd
|
180
180
|
end
|
181
181
|
|
182
|
+
o.on "-s", "--silent", "Do not log prompt messages other than errors" do
|
183
|
+
@log_writer = LogWriter.new(NullIO.new, $stderr)
|
184
|
+
end
|
185
|
+
|
182
186
|
o.on "-S", "--state PATH", "Where to store the state details" do |arg|
|
183
187
|
user_config.state_path arg
|
184
188
|
end
|