puma 3.7.1 → 4.1.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 +5 -5
- data/History.md +229 -1
- data/README.md +179 -212
- data/docs/architecture.md +37 -0
- data/{DEPLOYMENT.md → docs/deployment.md} +24 -4
- 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/plugins.md +28 -0
- data/docs/restart.md +41 -0
- data/docs/signals.md +56 -3
- data/docs/systemd.md +130 -37
- data/ext/puma_http11/PumaHttp11Service.java +2 -0
- data/ext/puma_http11/extconf.rb +8 -0
- data/ext/puma_http11/http11_parser.c +84 -84
- data/ext/puma_http11/http11_parser.rl +9 -9
- data/ext/puma_http11/mini_ssl.c +105 -9
- data/ext/puma_http11/org/jruby/puma/Http11Parser.java +13 -16
- data/ext/puma_http11/org/jruby/puma/IOBuffer.java +72 -0
- data/ext/puma_http11/org/jruby/puma/MiniSSL.java +30 -6
- data/lib/puma.rb +10 -0
- data/lib/puma/accept_nonblock.rb +2 -0
- data/lib/puma/app/status.rb +13 -0
- data/lib/puma/binder.rb +33 -18
- data/lib/puma/cli.rb +48 -33
- data/lib/puma/client.rb +94 -22
- data/lib/puma/cluster.rb +69 -21
- data/lib/puma/commonlogger.rb +2 -0
- data/lib/puma/configuration.rb +134 -136
- data/lib/puma/const.rb +16 -2
- data/lib/puma/control_cli.rb +31 -18
- data/lib/puma/convenient.rb +5 -3
- data/lib/puma/daemon_ext.rb +2 -0
- data/lib/puma/delegation.rb +2 -0
- data/lib/puma/detect.rb +2 -0
- data/lib/puma/dsl.rb +349 -113
- data/lib/puma/events.rb +8 -4
- data/lib/puma/io_buffer.rb +3 -6
- data/lib/puma/jruby_restart.rb +2 -1
- data/lib/puma/launcher.rb +60 -36
- data/lib/puma/minissl.rb +85 -28
- data/lib/puma/null_io.rb +2 -0
- data/lib/puma/plugin.rb +2 -0
- data/lib/puma/plugin/tmp_restart.rb +3 -2
- 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 +218 -30
- data/lib/puma/runner.rb +18 -4
- data/lib/puma/server.rb +149 -56
- data/lib/puma/single.rb +16 -5
- data/lib/puma/state_file.rb +2 -0
- data/lib/puma/tcp_logger.rb +2 -0
- data/lib/puma/thread_pool.rb +59 -6
- data/lib/puma/util.rb +2 -6
- data/lib/rack/handler/puma.rb +58 -19
- data/tools/jungle/README.md +12 -2
- data/tools/jungle/init.d/README.md +2 -0
- data/tools/jungle/init.d/puma +8 -8
- data/tools/jungle/init.d/run-puma +1 -1
- data/tools/jungle/rc.d/README.md +74 -0
- data/tools/jungle/rc.d/puma +61 -0
- data/tools/jungle/rc.d/puma.conf +10 -0
- data/tools/trickletest.rb +1 -1
- metadata +25 -85
- data/.github/issue_template.md +0 -20
- data/Gemfile +0 -12
- data/Manifest.txt +0 -77
- data/Rakefile +0 -158
- data/gemfiles/2.1-Gemfile +0 -12
- data/lib/puma/compat.rb +0 -14
- data/lib/puma/java_io_buffer.rb +0 -45
- data/lib/puma/rack/backports/uri/common_193.rb +0 -33
- data/puma.gemspec +0 -52
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'
|
@@ -27,8 +29,8 @@ module Puma
|
|
27
29
|
#
|
28
30
|
def initialize(stdout, stderr)
|
29
31
|
@formatter = DefaultFormatter.new
|
30
|
-
@stdout = stdout
|
31
|
-
@stderr = stderr
|
32
|
+
@stdout = stdout.dup
|
33
|
+
@stderr = stderr.dup
|
32
34
|
|
33
35
|
@stdout.sync = true
|
34
36
|
@stderr.sync = true
|
@@ -91,8 +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
|
95
|
-
|
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"
|
96
100
|
end
|
97
101
|
|
98
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,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'puma/events'
|
2
4
|
require 'puma/detect'
|
3
5
|
|
@@ -34,13 +36,13 @@ module Puma
|
|
34
36
|
#
|
35
37
|
# Examples:
|
36
38
|
#
|
37
|
-
# conf = Puma::Configuration.new do |
|
38
|
-
#
|
39
|
-
#
|
39
|
+
# conf = Puma::Configuration.new do |user_config|
|
40
|
+
# user_config.threads 1, 10
|
41
|
+
# user_config.app do |env|
|
40
42
|
# [200, {}, ["hello world"]]
|
41
43
|
# end
|
42
44
|
# end
|
43
|
-
# Puma::Launcher.new(conf,
|
45
|
+
# Puma::Launcher.new(conf, events: Puma::Events.stdio).run
|
44
46
|
def initialize(conf, launcher_args={})
|
45
47
|
@runner = nil
|
46
48
|
@events = launcher_args[:events] || Events::DEFAULT
|
@@ -59,11 +61,15 @@ module Puma
|
|
59
61
|
@config.load
|
60
62
|
|
61
63
|
@options = @config.options
|
64
|
+
@config.clamp
|
65
|
+
|
66
|
+
@events.formatter = Events::PidFormatter.new if clustered?
|
67
|
+
@events.formatter = options[:log_formatter] if @options[:log_formatter]
|
62
68
|
|
63
69
|
generate_restart_data
|
64
70
|
|
65
|
-
if clustered? &&
|
66
|
-
unsupported
|
71
|
+
if clustered? && !Process.respond_to?(:fork)
|
72
|
+
unsupported "worker mode not supported on #{RUBY_ENGINE} on this platform"
|
67
73
|
end
|
68
74
|
|
69
75
|
if @options[:daemon] && Puma.windows?
|
@@ -78,13 +84,13 @@ module Puma
|
|
78
84
|
set_rack_environment
|
79
85
|
|
80
86
|
if clustered?
|
81
|
-
@events.formatter = Events::PidFormatter.new
|
82
87
|
@options[:logger] = @events
|
83
88
|
|
84
89
|
@runner = Cluster.new(self, @events)
|
85
90
|
else
|
86
91
|
@runner = Single.new(self, @events)
|
87
92
|
end
|
93
|
+
Puma.stats_object = @runner
|
88
94
|
|
89
95
|
@status = :run
|
90
96
|
end
|
@@ -162,6 +168,17 @@ module Puma
|
|
162
168
|
|
163
169
|
# Run the server. This blocks until the server is stopped
|
164
170
|
def run
|
171
|
+
previous_env =
|
172
|
+
if defined?(Bundler)
|
173
|
+
env = Bundler::ORIGINAL_ENV.dup
|
174
|
+
# add -rbundler/setup so we load from Gemfile when restarting
|
175
|
+
bundle = "-rbundler/setup"
|
176
|
+
env["RUBYOPT"] = [env["RUBYOPT"], bundle].join(" ").lstrip unless env["RUBYOPT"].to_s.include?(bundle)
|
177
|
+
env
|
178
|
+
else
|
179
|
+
ENV.to_h
|
180
|
+
end
|
181
|
+
|
165
182
|
@config.clamp
|
166
183
|
|
167
184
|
@config.plugins.fire_starts self
|
@@ -177,6 +194,7 @@ module Puma
|
|
177
194
|
graceful_stop
|
178
195
|
when :restart
|
179
196
|
log "* Restarting..."
|
197
|
+
ENV.replace(previous_env)
|
180
198
|
@runner.before_restart
|
181
199
|
restart!
|
182
200
|
when :exit
|
@@ -198,6 +216,15 @@ module Puma
|
|
198
216
|
end
|
199
217
|
end
|
200
218
|
|
219
|
+
def close_binder_listeners
|
220
|
+
@binder.listeners.each do |l, io|
|
221
|
+
io.close
|
222
|
+
uri = URI.parse(l)
|
223
|
+
next unless uri.scheme == 'unix'
|
224
|
+
File.unlink("#{uri.host}#{uri.path}")
|
225
|
+
end
|
226
|
+
end
|
227
|
+
|
201
228
|
private
|
202
229
|
|
203
230
|
def reload_worker_directory
|
@@ -221,13 +248,13 @@ module Puma
|
|
221
248
|
else
|
222
249
|
redirects = {:close_others => true}
|
223
250
|
@binder.listeners.each_with_index do |(l, io), i|
|
224
|
-
|
225
|
-
|
251
|
+
ENV["PUMA_INHERIT_#{i}"] = "#{io.to_i}:#{l}"
|
252
|
+
redirects[io.to_i] = io.to_i
|
226
253
|
end
|
227
254
|
|
228
255
|
argv = restart_args
|
229
256
|
Dir.chdir(@restart_dir)
|
230
|
-
argv += [redirects]
|
257
|
+
argv += [redirects]
|
231
258
|
Kernel.exec(*argv)
|
232
259
|
end
|
233
260
|
end
|
@@ -256,7 +283,7 @@ module Puma
|
|
256
283
|
wild = File.expand_path(File.join(puma_lib_dir, "../bin/puma-wild"))
|
257
284
|
args = [Gem.ruby, wild, '-I', dirs.join(':'), deps.join(',')] + @original_argv
|
258
285
|
# Ruby 2.0+ defaults to true which breaks socket activation
|
259
|
-
args += [{:close_others => false}]
|
286
|
+
args += [{:close_others => false}]
|
260
287
|
Kernel.exec(*args)
|
261
288
|
end
|
262
289
|
end
|
@@ -285,8 +312,8 @@ module Puma
|
|
285
312
|
end
|
286
313
|
|
287
314
|
def title
|
288
|
-
buffer
|
289
|
-
buffer
|
315
|
+
buffer = "puma #{Puma::Const::VERSION} (#{@options[:binds].join(',')})"
|
316
|
+
buffer += " [#{@options[:tag]}]" if @options[:tag] && !@options[:tag].empty?
|
290
317
|
buffer
|
291
318
|
end
|
292
319
|
|
@@ -303,16 +330,6 @@ module Puma
|
|
303
330
|
@options[:prune_bundler] && clustered? && !@options[:preload_app]
|
304
331
|
end
|
305
332
|
|
306
|
-
def close_binder_listeners
|
307
|
-
@binder.listeners.each do |l, io|
|
308
|
-
io.close
|
309
|
-
uri = URI.parse(l)
|
310
|
-
next unless uri.scheme == 'unix'
|
311
|
-
File.unlink("#{uri.host}#{uri.path}")
|
312
|
-
end
|
313
|
-
end
|
314
|
-
|
315
|
-
|
316
333
|
def generate_restart_data
|
317
334
|
if dir = @options[:directory]
|
318
335
|
@restart_dir = dir
|
@@ -336,8 +353,6 @@ module Puma
|
|
336
353
|
|
337
354
|
@restart_dir ||= Dir.pwd
|
338
355
|
|
339
|
-
require 'rubygems'
|
340
|
-
|
341
356
|
# if $0 is a file in the current directory, then restart
|
342
357
|
# it the same, otherwise add -S on there because it was
|
343
358
|
# picked up in PATH.
|
@@ -348,9 +363,10 @@ module Puma
|
|
348
363
|
arg0 = [Gem.ruby, "-S", $0]
|
349
364
|
end
|
350
365
|
|
351
|
-
# Detect and reinject -Ilib from the command line
|
366
|
+
# Detect and reinject -Ilib from the command line, used for testing without bundler
|
367
|
+
# cruby has an expanded path, jruby has just "lib"
|
352
368
|
lib = File.expand_path "lib"
|
353
|
-
arg0[1,0] = ["-I", lib] if
|
369
|
+
arg0[1,0] = ["-I", lib] if [lib, "lib"].include?($LOAD_PATH[0])
|
354
370
|
|
355
371
|
if defined? Puma::WILD_ARGS
|
356
372
|
@restart_argv = arg0 + Puma::WILD_ARGS + @original_argv
|
@@ -380,12 +396,28 @@ module Puma
|
|
380
396
|
|
381
397
|
begin
|
382
398
|
Signal.trap "SIGTERM" do
|
383
|
-
|
399
|
+
graceful_stop
|
400
|
+
|
401
|
+
raise(SignalException, "SIGTERM") if @options[:raise_exception_on_sigterm]
|
384
402
|
end
|
385
403
|
rescue Exception
|
386
404
|
log "*** SIGTERM not implemented, signal based gracefully stopping unavailable!"
|
387
405
|
end
|
388
406
|
|
407
|
+
begin
|
408
|
+
Signal.trap "SIGINT" do
|
409
|
+
if Puma.jruby?
|
410
|
+
@status = :exit
|
411
|
+
graceful_stop
|
412
|
+
exit
|
413
|
+
end
|
414
|
+
|
415
|
+
stop
|
416
|
+
end
|
417
|
+
rescue Exception
|
418
|
+
log "*** SIGINT not implemented, signal based gracefully stopping unavailable!"
|
419
|
+
end
|
420
|
+
|
389
421
|
begin
|
390
422
|
Signal.trap "SIGHUP" do
|
391
423
|
if @runner.redirected_io?
|
@@ -397,14 +429,6 @@ module Puma
|
|
397
429
|
rescue Exception
|
398
430
|
log "*** SIGHUP not implemented, signal based logs reopening unavailable!"
|
399
431
|
end
|
400
|
-
|
401
|
-
if Puma.jruby?
|
402
|
-
Signal.trap("INT") do
|
403
|
-
@status = :exit
|
404
|
-
graceful_stop
|
405
|
-
exit
|
406
|
-
end
|
407
|
-
end
|
408
432
|
end
|
409
433
|
end
|
410
434
|
end
|
data/lib/puma/minissl.rb
CHANGED
@@ -1,3 +1,10 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
begin
|
4
|
+
require 'io/wait'
|
5
|
+
rescue LoadError
|
6
|
+
end
|
7
|
+
|
1
8
|
module Puma
|
2
9
|
module MiniSSL
|
3
10
|
class Socket
|
@@ -11,6 +18,10 @@ module Puma
|
|
11
18
|
@socket
|
12
19
|
end
|
13
20
|
|
21
|
+
def closed?
|
22
|
+
@socket.closed?
|
23
|
+
end
|
24
|
+
|
14
25
|
def readpartial(size)
|
15
26
|
while true
|
16
27
|
output = @engine.read
|
@@ -36,12 +47,28 @@ module Puma
|
|
36
47
|
output
|
37
48
|
end
|
38
49
|
|
39
|
-
def read_nonblock(size)
|
50
|
+
def read_nonblock(size, *_)
|
51
|
+
# *_ is to deal with keyword args that were added
|
52
|
+
# at some point (and being used in the wild)
|
40
53
|
while true
|
41
54
|
output = engine_read_all
|
42
55
|
return output if output
|
43
56
|
|
44
|
-
data = @socket.read_nonblock(size)
|
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
|
45
72
|
|
46
73
|
@engine.inject(data)
|
47
74
|
output = engine_read_all
|
@@ -55,6 +82,8 @@ module Puma
|
|
55
82
|
end
|
56
83
|
|
57
84
|
def write(data)
|
85
|
+
return 0 if data.empty?
|
86
|
+
|
58
87
|
need = data.bytesize
|
59
88
|
|
60
89
|
while true
|
@@ -77,39 +106,46 @@ module Puma
|
|
77
106
|
alias_method :syswrite, :write
|
78
107
|
alias_method :<<, :write
|
79
108
|
|
109
|
+
# This is a temporary fix to deal with websockets code using
|
110
|
+
# write_nonblock. The problem with implementing it properly
|
111
|
+
# is that it means we'd have to have the ability to rewind
|
112
|
+
# an engine because after we write+extract, the socket
|
113
|
+
# write_nonblock call might raise an exception and later
|
114
|
+
# code would pass the same data in, but the engine would think
|
115
|
+
# it had already written the data in. So for the time being
|
116
|
+
# (and since write blocking is quite rare), go ahead and actually
|
117
|
+
# block in write_nonblock.
|
118
|
+
def write_nonblock(data, *_)
|
119
|
+
write data
|
120
|
+
end
|
121
|
+
|
80
122
|
def flush
|
81
123
|
@socket.flush
|
82
124
|
end
|
83
125
|
|
84
|
-
def
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
return unless IO.select([@socket], nil, nil, 1)
|
93
|
-
begin
|
94
|
-
read_nonblock(1024)
|
95
|
-
rescue Errno::EAGAIN
|
96
|
-
end
|
97
|
-
end
|
98
|
-
|
99
|
-
done = @engine.shutdown
|
100
|
-
|
101
|
-
while true
|
102
|
-
enc = @engine.extract
|
103
|
-
@socket.write enc
|
104
|
-
|
105
|
-
notify = @socket.sysread(1024)
|
126
|
+
def read_and_drop(timeout = 1)
|
127
|
+
return :timeout unless IO.select([@socket], nil, nil, timeout)
|
128
|
+
return :eof unless read_nonblock(1024)
|
129
|
+
:drop
|
130
|
+
rescue Errno::EAGAIN
|
131
|
+
# do nothing
|
132
|
+
:eagain
|
133
|
+
end
|
106
134
|
|
107
|
-
|
108
|
-
|
135
|
+
def should_drop_bytes?
|
136
|
+
@engine.init? || !@engine.shutdown
|
137
|
+
end
|
109
138
|
|
110
|
-
|
139
|
+
def close
|
140
|
+
begin
|
141
|
+
# Read any drop any partially initialized sockets and any received bytes during shutdown.
|
142
|
+
# Don't let this socket hold this loop forever.
|
143
|
+
# If it can't send more packets within 1s, then give up.
|
144
|
+
while should_drop_bytes?
|
145
|
+
return if [:timeout, :eof].include?(read_and_drop(1))
|
111
146
|
end
|
112
147
|
rescue IOError, SystemCallError
|
148
|
+
Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
|
113
149
|
# nothing
|
114
150
|
ensure
|
115
151
|
@socket.close
|
@@ -140,11 +176,18 @@ module Puma
|
|
140
176
|
|
141
177
|
class Context
|
142
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
|
143
185
|
|
144
186
|
if defined?(JRUBY_VERSION)
|
145
187
|
# jruby-specific Context properties: java uses a keystore and password pair rather than a cert/key pair
|
146
188
|
attr_reader :keystore
|
147
189
|
attr_accessor :keystore_pass
|
190
|
+
attr_accessor :ssl_cipher_list
|
148
191
|
|
149
192
|
def keystore=(keystore)
|
150
193
|
raise ArgumentError, "No such keystore file '#{keystore}'" unless File.exist? keystore
|
@@ -160,6 +203,7 @@ module Puma
|
|
160
203
|
attr_reader :key
|
161
204
|
attr_reader :cert
|
162
205
|
attr_reader :ca
|
206
|
+
attr_accessor :ssl_cipher_filter
|
163
207
|
|
164
208
|
def key=(key)
|
165
209
|
raise ArgumentError, "No such key file '#{key}'" unless File.exist? key
|
@@ -181,6 +225,19 @@ module Puma
|
|
181
225
|
raise "Cert not configured" unless @cert
|
182
226
|
end
|
183
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
|
+
|
184
241
|
end
|
185
242
|
|
186
243
|
VERIFY_NONE = 0
|
@@ -214,7 +271,7 @@ module Puma
|
|
214
271
|
end
|
215
272
|
|
216
273
|
def close
|
217
|
-
@socket.close
|
274
|
+
@socket.close unless @socket.closed? # closed? call is for Windows
|
218
275
|
end
|
219
276
|
end
|
220
277
|
end
|