puma 3.12.0 → 4.3.8
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 +164 -0
- data/README.md +76 -48
- data/docs/architecture.md +1 -0
- data/docs/deployment.md +24 -4
- data/docs/plugins.md +20 -10
- data/docs/restart.md +4 -2
- data/docs/systemd.md +27 -9
- data/docs/tcp_mode.md +96 -0
- data/ext/puma_http11/PumaHttp11Service.java +2 -0
- data/ext/puma_http11/extconf.rb +13 -0
- data/ext/puma_http11/http11_parser.c +40 -63
- data/ext/puma_http11/http11_parser.java.rl +21 -37
- data/ext/puma_http11/http11_parser.rl +3 -1
- data/ext/puma_http11/http11_parser_common.rl +3 -3
- data/ext/puma_http11/mini_ssl.c +86 -4
- data/ext/puma_http11/org/jruby/puma/Http11.java +106 -114
- data/ext/puma_http11/org/jruby/puma/Http11Parser.java +91 -106
- data/ext/puma_http11/org/jruby/puma/IOBuffer.java +72 -0
- data/ext/puma_http11/org/jruby/puma/MiniSSL.java +15 -4
- data/ext/puma_http11/puma_http11.c +3 -0
- data/lib/puma.rb +8 -0
- data/lib/puma/accept_nonblock.rb +7 -1
- data/lib/puma/app/status.rb +37 -29
- data/lib/puma/binder.rb +47 -68
- data/lib/puma/cli.rb +6 -0
- data/lib/puma/client.rb +244 -199
- data/lib/puma/cluster.rb +55 -30
- data/lib/puma/commonlogger.rb +2 -0
- data/lib/puma/configuration.rb +6 -3
- data/lib/puma/const.rb +32 -18
- data/lib/puma/control_cli.rb +41 -14
- data/lib/puma/detect.rb +2 -0
- data/lib/puma/dsl.rb +311 -77
- data/lib/puma/events.rb +6 -1
- data/lib/puma/io_buffer.rb +3 -6
- data/lib/puma/jruby_restart.rb +2 -0
- data/lib/puma/launcher.rb +99 -55
- data/lib/puma/minissl.rb +37 -17
- data/lib/puma/minissl/context_builder.rb +76 -0
- data/lib/puma/null_io.rb +2 -0
- data/lib/puma/plugin.rb +7 -2
- data/lib/puma/plugin/tmp_restart.rb +2 -0
- data/lib/puma/rack/builder.rb +4 -1
- data/lib/puma/rack/urlmap.rb +2 -0
- data/lib/puma/rack_default.rb +2 -0
- data/lib/puma/reactor.rb +112 -57
- data/lib/puma/runner.rb +13 -3
- data/lib/puma/server.rb +119 -48
- data/lib/puma/single.rb +5 -3
- data/lib/puma/state_file.rb +2 -0
- data/lib/puma/tcp_logger.rb +2 -0
- data/lib/puma/thread_pool.rb +17 -33
- data/lib/puma/util.rb +2 -6
- data/lib/rack/handler/puma.rb +6 -3
- data/tools/docker/Dockerfile +16 -0
- data/tools/jungle/init.d/puma +6 -6
- data/tools/trickletest.rb +0 -1
- metadata +26 -14
- data/lib/puma/compat.rb +0 -14
- data/lib/puma/convenient.rb +0 -23
- data/lib/puma/daemon_ext.rb +0 -31
- data/lib/puma/delegation.rb +0 -11
- data/lib/puma/java_io_buffer.rb +0 -45
- data/lib/puma/rack/backports/uri/common_193.rb +0 -33
data/lib/puma/events.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'puma/const'
|
2
4
|
require "puma/null_io"
|
3
5
|
require 'stringio'
|
@@ -91,7 +93,10 @@ module Puma
|
|
91
93
|
# parsing exception.
|
92
94
|
#
|
93
95
|
def parse_error(server, env, error)
|
94
|
-
@stderr.puts "#{Time.now}: HTTP parse error, malformed request
|
96
|
+
@stderr.puts "#{Time.now}: HTTP parse error, malformed request " \
|
97
|
+
"(#{env[HTTP_X_FORWARDED_FOR] || env[REMOTE_ADDR]}#{env[REQUEST_PATH]}): " \
|
98
|
+
"#{error.inspect}" \
|
99
|
+
"\n---\n"
|
95
100
|
end
|
96
101
|
|
97
102
|
# An SSL error has occurred.
|
data/lib/puma/io_buffer.rb
CHANGED
data/lib/puma/jruby_restart.rb
CHANGED
data/lib/puma/launcher.rb
CHANGED
@@ -1,11 +1,10 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'puma/events'
|
2
4
|
require 'puma/detect'
|
3
|
-
|
4
5
|
require 'puma/cluster'
|
5
6
|
require 'puma/single'
|
6
|
-
|
7
7
|
require 'puma/const'
|
8
|
-
|
9
8
|
require 'puma/binder'
|
10
9
|
|
11
10
|
module Puma
|
@@ -61,10 +60,13 @@ module Puma
|
|
61
60
|
@options = @config.options
|
62
61
|
@config.clamp
|
63
62
|
|
63
|
+
@events.formatter = Events::PidFormatter.new if clustered?
|
64
|
+
@events.formatter = options[:log_formatter] if @options[:log_formatter]
|
65
|
+
|
64
66
|
generate_restart_data
|
65
67
|
|
66
|
-
if clustered? &&
|
67
|
-
unsupported
|
68
|
+
if clustered? && !Process.respond_to?(:fork)
|
69
|
+
unsupported "worker mode not supported on #{RUBY_ENGINE} on this platform"
|
68
70
|
end
|
69
71
|
|
70
72
|
if @options[:daemon] && Puma.windows?
|
@@ -79,7 +81,6 @@ module Puma
|
|
79
81
|
set_rack_environment
|
80
82
|
|
81
83
|
if clustered?
|
82
|
-
@events.formatter = Events::PidFormatter.new
|
83
84
|
@options[:logger] = @events
|
84
85
|
|
85
86
|
@runner = Cluster.new(self, @events)
|
@@ -122,19 +123,6 @@ module Puma
|
|
122
123
|
File.unlink(path) if path && File.exist?(path)
|
123
124
|
end
|
124
125
|
|
125
|
-
# If configured, write the pid of the current process out
|
126
|
-
# to a file.
|
127
|
-
def write_pid
|
128
|
-
path = @options[:pidfile]
|
129
|
-
return unless path
|
130
|
-
|
131
|
-
File.open(path, 'w') { |f| f.puts Process.pid }
|
132
|
-
cur = Process.pid
|
133
|
-
at_exit do
|
134
|
-
delete_pidfile if cur == Process.pid
|
135
|
-
end
|
136
|
-
end
|
137
|
-
|
138
126
|
# Begin async shutdown of the server
|
139
127
|
def halt
|
140
128
|
@status = :halt
|
@@ -196,6 +184,7 @@ module Puma
|
|
196
184
|
when :exit
|
197
185
|
# nothing
|
198
186
|
end
|
187
|
+
@binder.close_unix_paths
|
199
188
|
end
|
200
189
|
|
201
190
|
# Return which tcp port the launcher is using, if it's using TCP
|
@@ -212,8 +201,25 @@ module Puma
|
|
212
201
|
end
|
213
202
|
end
|
214
203
|
|
204
|
+
def close_binder_listeners
|
205
|
+
@binder.close_listeners
|
206
|
+
end
|
207
|
+
|
215
208
|
private
|
216
209
|
|
210
|
+
# If configured, write the pid of the current process out
|
211
|
+
# to a file.
|
212
|
+
def write_pid
|
213
|
+
path = @options[:pidfile]
|
214
|
+
return unless path
|
215
|
+
|
216
|
+
File.open(path, 'w') { |f| f.puts Process.pid }
|
217
|
+
cur = Process.pid
|
218
|
+
at_exit do
|
219
|
+
delete_pidfile if cur == Process.pid
|
220
|
+
end
|
221
|
+
end
|
222
|
+
|
217
223
|
def reload_worker_directory
|
218
224
|
@runner.reload_worker_directory if @runner.respond_to?(:reload_worker_directory)
|
219
225
|
end
|
@@ -233,48 +239,71 @@ module Puma
|
|
233
239
|
Dir.chdir(@restart_dir)
|
234
240
|
Kernel.exec(*argv)
|
235
241
|
else
|
236
|
-
redirects = {:close_others => true}
|
237
|
-
@binder.listeners.each_with_index do |(l, io), i|
|
238
|
-
ENV["PUMA_INHERIT_#{i}"] = "#{io.to_i}:#{l}"
|
239
|
-
redirects[io.to_i] = io.to_i
|
240
|
-
end
|
241
|
-
|
242
242
|
argv = restart_args
|
243
243
|
Dir.chdir(@restart_dir)
|
244
|
-
argv += [
|
244
|
+
argv += [@binder.redirects_for_restart]
|
245
245
|
Kernel.exec(*argv)
|
246
246
|
end
|
247
247
|
end
|
248
248
|
|
249
|
-
def
|
250
|
-
|
251
|
-
|
252
|
-
|
249
|
+
def dependencies_and_files_to_require_after_prune
|
250
|
+
puma = spec_for_gem("puma")
|
251
|
+
|
252
|
+
deps = puma.runtime_dependencies.map do |d|
|
253
|
+
"#{d.name}:#{spec_for_gem(d.name).version}"
|
254
|
+
end
|
255
|
+
|
256
|
+
[deps, require_paths_for_gem(puma) + extra_runtime_deps_directories]
|
257
|
+
end
|
258
|
+
|
259
|
+
def extra_runtime_deps_directories
|
260
|
+
Array(@options[:extra_runtime_dependencies]).map do |d_name|
|
261
|
+
if (spec = spec_for_gem(d_name))
|
262
|
+
require_paths_for_gem(spec)
|
263
|
+
else
|
264
|
+
log "* Could not load extra dependency: #{d_name}"
|
265
|
+
nil
|
266
|
+
end
|
267
|
+
end.flatten.compact
|
268
|
+
end
|
269
|
+
|
270
|
+
def puma_wild_location
|
271
|
+
puma = spec_for_gem("puma")
|
272
|
+
dirs = require_paths_for_gem(puma)
|
253
273
|
puma_lib_dir = dirs.detect { |x| File.exist? File.join(x, '../bin/puma-wild') }
|
274
|
+
File.expand_path(File.join(puma_lib_dir, "../bin/puma-wild"))
|
275
|
+
end
|
254
276
|
|
255
|
-
|
277
|
+
def prune_bundler
|
278
|
+
return unless defined?(Bundler)
|
279
|
+
require_rubygems_min_version!(Gem::Version.new("2.2"), "prune_bundler")
|
280
|
+
unless puma_wild_location
|
256
281
|
log "! Unable to prune Bundler environment, continuing"
|
257
282
|
return
|
258
283
|
end
|
259
284
|
|
260
|
-
deps =
|
261
|
-
spec = Bundler.rubygems.loaded_specs(d.name)
|
262
|
-
"#{d.name}:#{spec.version.to_s}"
|
263
|
-
end
|
285
|
+
deps, dirs = dependencies_and_files_to_require_after_prune
|
264
286
|
|
265
287
|
log '* Pruning Bundler environment'
|
266
288
|
home = ENV['GEM_HOME']
|
267
289
|
Bundler.with_clean_env do
|
268
290
|
ENV['GEM_HOME'] = home
|
269
291
|
ENV['PUMA_BUNDLER_PRUNED'] = '1'
|
270
|
-
|
271
|
-
args = [Gem.ruby, wild, '-I', dirs.join(':'), deps.join(',')] + @original_argv
|
292
|
+
args = [Gem.ruby, puma_wild_location, '-I', dirs.join(':'), deps.join(',')] + @original_argv
|
272
293
|
# Ruby 2.0+ defaults to true which breaks socket activation
|
273
|
-
args += [{:close_others => false}]
|
294
|
+
args += [{:close_others => false}]
|
274
295
|
Kernel.exec(*args)
|
275
296
|
end
|
276
297
|
end
|
277
298
|
|
299
|
+
def spec_for_gem(gem_name)
|
300
|
+
Bundler.rubygems.loaded_specs(gem_name)
|
301
|
+
end
|
302
|
+
|
303
|
+
def require_paths_for_gem(gem_spec)
|
304
|
+
gem_spec.full_require_paths
|
305
|
+
end
|
306
|
+
|
278
307
|
def log(str)
|
279
308
|
@events.log str
|
280
309
|
end
|
@@ -294,6 +323,21 @@ module Puma
|
|
294
323
|
log "- Goodbye!"
|
295
324
|
end
|
296
325
|
|
326
|
+
def log_thread_status
|
327
|
+
Thread.list.each do |thread|
|
328
|
+
log "Thread TID-#{thread.object_id.to_s(36)} #{thread['label']}"
|
329
|
+
logstr = "Thread: TID-#{thread.object_id.to_s(36)}"
|
330
|
+
logstr += " #{thread.name}" if thread.respond_to?(:name)
|
331
|
+
log logstr
|
332
|
+
|
333
|
+
if thread.backtrace
|
334
|
+
log thread.backtrace.join("\n")
|
335
|
+
else
|
336
|
+
log "<no backtrace available>"
|
337
|
+
end
|
338
|
+
end
|
339
|
+
end
|
340
|
+
|
297
341
|
def set_process_title
|
298
342
|
Process.respond_to?(:setproctitle) ? Process.setproctitle(title) : $0 = title
|
299
343
|
end
|
@@ -317,16 +361,6 @@ module Puma
|
|
317
361
|
@options[:prune_bundler] && clustered? && !@options[:preload_app]
|
318
362
|
end
|
319
363
|
|
320
|
-
def close_binder_listeners
|
321
|
-
@binder.listeners.each do |l, io|
|
322
|
-
io.close
|
323
|
-
uri = URI.parse(l)
|
324
|
-
next unless uri.scheme == 'unix'
|
325
|
-
File.unlink("#{uri.host}#{uri.path}")
|
326
|
-
end
|
327
|
-
end
|
328
|
-
|
329
|
-
|
330
364
|
def generate_restart_data
|
331
365
|
if dir = @options[:directory]
|
332
366
|
@restart_dir = dir
|
@@ -395,7 +429,7 @@ module Puma
|
|
395
429
|
Signal.trap "SIGTERM" do
|
396
430
|
graceful_stop
|
397
431
|
|
398
|
-
raise
|
432
|
+
raise(SignalException, "SIGTERM") if @options[:raise_exception_on_sigterm]
|
399
433
|
end
|
400
434
|
rescue Exception
|
401
435
|
log "*** SIGTERM not implemented, signal based gracefully stopping unavailable!"
|
@@ -403,12 +437,6 @@ module Puma
|
|
403
437
|
|
404
438
|
begin
|
405
439
|
Signal.trap "SIGINT" do
|
406
|
-
if Puma.jruby?
|
407
|
-
@status = :exit
|
408
|
-
graceful_stop
|
409
|
-
exit
|
410
|
-
end
|
411
|
-
|
412
440
|
stop
|
413
441
|
end
|
414
442
|
rescue Exception
|
@@ -426,6 +454,22 @@ module Puma
|
|
426
454
|
rescue Exception
|
427
455
|
log "*** SIGHUP not implemented, signal based logs reopening unavailable!"
|
428
456
|
end
|
457
|
+
|
458
|
+
begin
|
459
|
+
Signal.trap "SIGINFO" do
|
460
|
+
log_thread_status
|
461
|
+
end
|
462
|
+
rescue Exception
|
463
|
+
# Not going to log this one, as SIGINFO is *BSD only and would be pretty annoying
|
464
|
+
# to see this constantly on Linux.
|
465
|
+
end
|
466
|
+
end
|
467
|
+
|
468
|
+
def require_rubygems_min_version!(min_version, feature)
|
469
|
+
return if min_version <= Gem::Version.new(Gem::VERSION)
|
470
|
+
|
471
|
+
raise "#{feature} is not supported on your version of RubyGems. " \
|
472
|
+
"You must have RubyGems #{min_version}+ to use this feature."
|
429
473
|
end
|
430
474
|
end
|
431
475
|
end
|
data/lib/puma/minissl.rb
CHANGED
@@ -1,6 +1,8 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
begin
|
2
4
|
require 'io/wait'
|
3
|
-
|
5
|
+
rescue LoadError
|
4
6
|
end
|
5
7
|
|
6
8
|
module Puma
|
@@ -52,22 +54,21 @@ module Puma
|
|
52
54
|
output = engine_read_all
|
53
55
|
return output if output
|
54
56
|
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
end while true
|
57
|
+
data = @socket.read_nonblock(size, exception: false)
|
58
|
+
if data == :wait_readable || data == :wait_writable
|
59
|
+
# It would make more sense to let @socket.read_nonblock raise
|
60
|
+
# EAGAIN if necessary but it seems like it'll misbehave on Windows.
|
61
|
+
# I don't have a Windows machine to debug this so I can't explain
|
62
|
+
# exactly whats happening in that OS. Please let me know if you
|
63
|
+
# find out!
|
64
|
+
#
|
65
|
+
# In the meantime, we can emulate the correct behavior by
|
66
|
+
# capturing :wait_readable & :wait_writable and raising EAGAIN
|
67
|
+
# ourselves.
|
68
|
+
raise IO::EAGAINWaitReadable
|
69
|
+
elsif data.nil?
|
70
|
+
return nil
|
71
|
+
end
|
71
72
|
|
72
73
|
@engine.inject(data)
|
73
74
|
output = engine_read_all
|
@@ -175,6 +176,12 @@ module Puma
|
|
175
176
|
|
176
177
|
class Context
|
177
178
|
attr_accessor :verify_mode
|
179
|
+
attr_reader :no_tlsv1, :no_tlsv1_1
|
180
|
+
|
181
|
+
def initialize
|
182
|
+
@no_tlsv1 = false
|
183
|
+
@no_tlsv1_1 = false
|
184
|
+
end
|
178
185
|
|
179
186
|
if defined?(JRUBY_VERSION)
|
180
187
|
# jruby-specific Context properties: java uses a keystore and password pair rather than a cert/key pair
|
@@ -218,6 +225,19 @@ module Puma
|
|
218
225
|
raise "Cert not configured" unless @cert
|
219
226
|
end
|
220
227
|
end
|
228
|
+
|
229
|
+
# disables TLSv1
|
230
|
+
def no_tlsv1=(tlsv1)
|
231
|
+
raise ArgumentError, "Invalid value of no_tlsv1" unless ['true', 'false', true, false].include?(tlsv1)
|
232
|
+
@no_tlsv1 = tlsv1
|
233
|
+
end
|
234
|
+
|
235
|
+
# disables TLSv1 and TLSv1.1. Overrides `#no_tlsv1=`
|
236
|
+
def no_tlsv1_1=(tlsv1_1)
|
237
|
+
raise ArgumentError, "Invalid value of no_tlsv1" unless ['true', 'false', true, false].include?(tlsv1_1)
|
238
|
+
@no_tlsv1_1 = tlsv1_1
|
239
|
+
end
|
240
|
+
|
221
241
|
end
|
222
242
|
|
223
243
|
VERIFY_NONE = 0
|
@@ -0,0 +1,76 @@
|
|
1
|
+
module Puma
|
2
|
+
module MiniSSL
|
3
|
+
class ContextBuilder
|
4
|
+
def initialize(params, events)
|
5
|
+
require 'puma/minissl'
|
6
|
+
MiniSSL.check
|
7
|
+
|
8
|
+
@params = params
|
9
|
+
@events = events
|
10
|
+
end
|
11
|
+
|
12
|
+
def context
|
13
|
+
ctx = MiniSSL::Context.new
|
14
|
+
|
15
|
+
if defined?(JRUBY_VERSION)
|
16
|
+
unless params['keystore']
|
17
|
+
events.error "Please specify the Java keystore via 'keystore='"
|
18
|
+
end
|
19
|
+
|
20
|
+
ctx.keystore = params['keystore']
|
21
|
+
|
22
|
+
unless params['keystore-pass']
|
23
|
+
events.error "Please specify the Java keystore password via 'keystore-pass='"
|
24
|
+
end
|
25
|
+
|
26
|
+
ctx.keystore_pass = params['keystore-pass']
|
27
|
+
ctx.ssl_cipher_list = params['ssl_cipher_list'] if params['ssl_cipher_list']
|
28
|
+
else
|
29
|
+
unless params['key']
|
30
|
+
events.error "Please specify the SSL key via 'key='"
|
31
|
+
end
|
32
|
+
|
33
|
+
ctx.key = params['key']
|
34
|
+
|
35
|
+
unless params['cert']
|
36
|
+
events.error "Please specify the SSL cert via 'cert='"
|
37
|
+
end
|
38
|
+
|
39
|
+
ctx.cert = params['cert']
|
40
|
+
|
41
|
+
if ['peer', 'force_peer'].include?(params['verify_mode'])
|
42
|
+
unless params['ca']
|
43
|
+
events.error "Please specify the SSL ca via 'ca='"
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
ctx.ca = params['ca'] if params['ca']
|
48
|
+
ctx.ssl_cipher_filter = params['ssl_cipher_filter'] if params['ssl_cipher_filter']
|
49
|
+
end
|
50
|
+
|
51
|
+
ctx.no_tlsv1 = true if params['no_tlsv1'] == 'true'
|
52
|
+
ctx.no_tlsv1_1 = true if params['no_tlsv1_1'] == 'true'
|
53
|
+
|
54
|
+
if params['verify_mode']
|
55
|
+
ctx.verify_mode = case params['verify_mode']
|
56
|
+
when "peer"
|
57
|
+
MiniSSL::VERIFY_PEER
|
58
|
+
when "force_peer"
|
59
|
+
MiniSSL::VERIFY_PEER | MiniSSL::VERIFY_FAIL_IF_NO_PEER_CERT
|
60
|
+
when "none"
|
61
|
+
MiniSSL::VERIFY_NONE
|
62
|
+
else
|
63
|
+
events.error "Please specify a valid verify_mode="
|
64
|
+
MiniSSL::VERIFY_NONE
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
ctx
|
69
|
+
end
|
70
|
+
|
71
|
+
private
|
72
|
+
|
73
|
+
attr_reader :params, :events
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|