puma 3.6.0 → 3.12.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.txt → History.md} +293 -79
- data/README.md +143 -227
- data/docs/architecture.md +36 -0
- data/{DEPLOYMENT.md → docs/deployment.md} +0 -0
- 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 +39 -0
- data/docs/signals.md +56 -3
- data/docs/systemd.md +124 -22
- data/ext/puma_http11/extconf.rb +2 -0
- data/ext/puma_http11/http11_parser.c +85 -84
- data/ext/puma_http11/http11_parser.h +1 -0
- data/ext/puma_http11/http11_parser.rl +10 -9
- data/ext/puma_http11/io_buffer.c +7 -7
- data/ext/puma_http11/mini_ssl.c +62 -6
- data/ext/puma_http11/org/jruby/puma/Http11Parser.java +13 -16
- data/ext/puma_http11/org/jruby/puma/MiniSSL.java +15 -2
- data/ext/puma_http11/puma_http11.c +1 -0
- data/lib/puma.rb +13 -5
- data/lib/puma/app/status.rb +8 -0
- data/lib/puma/binder.rb +21 -14
- data/lib/puma/cli.rb +49 -33
- data/lib/puma/client.rb +39 -4
- data/lib/puma/cluster.rb +51 -11
- data/lib/puma/commonlogger.rb +19 -20
- data/lib/puma/compat.rb +3 -7
- data/lib/puma/configuration.rb +133 -130
- data/lib/puma/const.rb +13 -37
- data/lib/puma/control_cli.rb +38 -35
- data/lib/puma/convenient.rb +3 -3
- data/lib/puma/detect.rb +3 -1
- data/lib/puma/dsl.rb +80 -58
- data/lib/puma/events.rb +6 -8
- data/lib/puma/io_buffer.rb +1 -1
- data/lib/puma/jruby_restart.rb +0 -1
- data/lib/puma/launcher.rb +52 -30
- data/lib/puma/minissl.rb +73 -4
- data/lib/puma/null_io.rb +6 -13
- data/lib/puma/plugin/tmp_restart.rb +1 -2
- data/lib/puma/rack/builder.rb +3 -0
- data/lib/puma/rack/urlmap.rb +9 -8
- data/lib/puma/reactor.rb +135 -0
- data/lib/puma/runner.rb +23 -1
- data/lib/puma/server.rb +117 -34
- data/lib/puma/single.rb +14 -3
- data/lib/puma/thread_pool.rb +67 -20
- data/lib/puma/util.rb +1 -5
- data/lib/rack/handler/puma.rb +58 -17
- data/tools/jungle/README.md +12 -2
- data/tools/jungle/init.d/README.md +9 -2
- data/tools/jungle/init.d/puma +32 -62
- data/tools/jungle/init.d/run-puma +5 -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 +22 -92
- data/Gemfile +0 -13
- data/Manifest.txt +0 -77
- data/Rakefile +0 -158
- data/lib/puma/rack/backports/uri/common_18.rb +0 -59
- data/lib/puma/rack/backports/uri/common_192.rb +0 -55
- data/puma.gemspec +0 -52
data/lib/puma/events.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
require 'puma/const'
|
2
|
+
require "puma/null_io"
|
2
3
|
require 'stringio'
|
3
4
|
|
4
5
|
module Puma
|
@@ -34,8 +35,6 @@ module Puma
|
|
34
35
|
|
35
36
|
@debug = ENV.key? 'PUMA_DEBUG'
|
36
37
|
|
37
|
-
@on_booted = []
|
38
|
-
|
39
38
|
@hooks = Hash.new { |h,k| h[k] = [] }
|
40
39
|
end
|
41
40
|
|
@@ -48,7 +47,7 @@ module Puma
|
|
48
47
|
@hooks[hook].each { |t| t.call(*args) }
|
49
48
|
end
|
50
49
|
|
51
|
-
# Register a
|
50
|
+
# Register a callback for a given hook
|
52
51
|
#
|
53
52
|
def register(hook, obj=nil, &blk)
|
54
53
|
if obj and blk
|
@@ -92,8 +91,7 @@ module Puma
|
|
92
91
|
# parsing exception.
|
93
92
|
#
|
94
93
|
def parse_error(server, env, error)
|
95
|
-
@stderr.puts "#{Time.now}: HTTP parse error, malformed request (#{env[HTTP_X_FORWARDED_FOR] || env[REMOTE_ADDR]}): #{error.inspect}"
|
96
|
-
@stderr.puts "#{Time.now}: ENV: #{env.inspect}\n---\n"
|
94
|
+
@stderr.puts "#{Time.now}: HTTP parse error, malformed request (#{env[HTTP_X_FORWARDED_FOR] || env[REMOTE_ADDR]}): #{error.inspect}\n---\n"
|
97
95
|
end
|
98
96
|
|
99
97
|
# An SSL error has occurred.
|
@@ -124,12 +122,12 @@ module Puma
|
|
124
122
|
end
|
125
123
|
end
|
126
124
|
|
127
|
-
def on_booted(&
|
128
|
-
|
125
|
+
def on_booted(&block)
|
126
|
+
register(:on_booted, &block)
|
129
127
|
end
|
130
128
|
|
131
129
|
def fire_on_booted!
|
132
|
-
|
130
|
+
fire(:on_booted)
|
133
131
|
end
|
134
132
|
|
135
133
|
DEFAULT = new(STDOUT, STDERR)
|
data/lib/puma/io_buffer.rb
CHANGED
data/lib/puma/jruby_restart.rb
CHANGED
data/lib/puma/launcher.rb
CHANGED
@@ -1,15 +1,12 @@
|
|
1
|
-
require 'puma/
|
2
|
-
require 'puma/const'
|
3
|
-
require 'puma/configuration'
|
4
|
-
require 'puma/binder'
|
1
|
+
require 'puma/events'
|
5
2
|
require 'puma/detect'
|
6
|
-
|
7
|
-
require 'puma/util'
|
8
|
-
require 'puma/single'
|
3
|
+
|
9
4
|
require 'puma/cluster'
|
10
|
-
require 'puma/
|
5
|
+
require 'puma/single'
|
6
|
+
|
7
|
+
require 'puma/const'
|
11
8
|
|
12
|
-
require 'puma/
|
9
|
+
require 'puma/binder'
|
13
10
|
|
14
11
|
module Puma
|
15
12
|
# Puma::Launcher is the single entry point for starting a Puma server based on user
|
@@ -37,13 +34,13 @@ module Puma
|
|
37
34
|
#
|
38
35
|
# Examples:
|
39
36
|
#
|
40
|
-
# conf = Puma::Configuration.new do |
|
41
|
-
#
|
42
|
-
#
|
37
|
+
# conf = Puma::Configuration.new do |user_config|
|
38
|
+
# user_config.threads 1, 10
|
39
|
+
# user_config.app do |env|
|
43
40
|
# [200, {}, ["hello world"]]
|
44
41
|
# end
|
45
42
|
# end
|
46
|
-
# Puma::Launcher.new(conf,
|
43
|
+
# Puma::Launcher.new(conf, events: Puma::Events.stdio).run
|
47
44
|
def initialize(conf, launcher_args={})
|
48
45
|
@runner = nil
|
49
46
|
@events = launcher_args[:events] || Events::DEFAULT
|
@@ -62,6 +59,7 @@ module Puma
|
|
62
59
|
@config.load
|
63
60
|
|
64
61
|
@options = @config.options
|
62
|
+
@config.clamp
|
65
63
|
|
66
64
|
generate_restart_data
|
67
65
|
|
@@ -88,6 +86,7 @@ module Puma
|
|
88
86
|
else
|
89
87
|
@runner = Single.new(self, @events)
|
90
88
|
end
|
89
|
+
Puma.stats_object = @runner
|
91
90
|
|
92
91
|
@status = :run
|
93
92
|
end
|
@@ -107,6 +106,8 @@ module Puma
|
|
107
106
|
path = @options[:state]
|
108
107
|
return unless path
|
109
108
|
|
109
|
+
require 'puma/state_file'
|
110
|
+
|
110
111
|
sf = StateFile.new
|
111
112
|
sf.pid = Process.pid
|
112
113
|
sf.control_url = @options[:control_url]
|
@@ -163,6 +164,17 @@ module Puma
|
|
163
164
|
|
164
165
|
# Run the server. This blocks until the server is stopped
|
165
166
|
def run
|
167
|
+
previous_env =
|
168
|
+
if defined?(Bundler)
|
169
|
+
env = Bundler::ORIGINAL_ENV.dup
|
170
|
+
# add -rbundler/setup so we load from Gemfile when restarting
|
171
|
+
bundle = "-rbundler/setup"
|
172
|
+
env["RUBYOPT"] = [env["RUBYOPT"], bundle].join(" ").lstrip unless env["RUBYOPT"].to_s.include?(bundle)
|
173
|
+
env
|
174
|
+
else
|
175
|
+
ENV.to_h
|
176
|
+
end
|
177
|
+
|
166
178
|
@config.clamp
|
167
179
|
|
168
180
|
@config.plugins.fire_starts self
|
@@ -178,6 +190,7 @@ module Puma
|
|
178
190
|
graceful_stop
|
179
191
|
when :restart
|
180
192
|
log "* Restarting..."
|
193
|
+
ENV.replace(previous_env)
|
181
194
|
@runner.before_restart
|
182
195
|
restart!
|
183
196
|
when :exit
|
@@ -222,8 +235,8 @@ module Puma
|
|
222
235
|
else
|
223
236
|
redirects = {:close_others => true}
|
224
237
|
@binder.listeners.each_with_index do |(l, io), i|
|
225
|
-
|
226
|
-
|
238
|
+
ENV["PUMA_INHERIT_#{i}"] = "#{io.to_i}:#{l}"
|
239
|
+
redirects[io.to_i] = io.to_i
|
227
240
|
end
|
228
241
|
|
229
242
|
argv = restart_args
|
@@ -256,6 +269,8 @@ module Puma
|
|
256
269
|
ENV['PUMA_BUNDLER_PRUNED'] = '1'
|
257
270
|
wild = File.expand_path(File.join(puma_lib_dir, "../bin/puma-wild"))
|
258
271
|
args = [Gem.ruby, wild, '-I', dirs.join(':'), deps.join(',')] + @original_argv
|
272
|
+
# Ruby 2.0+ defaults to true which breaks socket activation
|
273
|
+
args += [{:close_others => false}] if RUBY_VERSION >= '2.0'
|
259
274
|
Kernel.exec(*args)
|
260
275
|
end
|
261
276
|
end
|
@@ -284,8 +299,8 @@ module Puma
|
|
284
299
|
end
|
285
300
|
|
286
301
|
def title
|
287
|
-
buffer
|
288
|
-
buffer
|
302
|
+
buffer = "puma #{Puma::Const::VERSION} (#{@options[:binds].join(',')})"
|
303
|
+
buffer += " [#{@options[:tag]}]" if @options[:tag] && !@options[:tag].empty?
|
289
304
|
buffer
|
290
305
|
end
|
291
306
|
|
@@ -335,8 +350,6 @@ module Puma
|
|
335
350
|
|
336
351
|
@restart_dir ||= Dir.pwd
|
337
352
|
|
338
|
-
require 'rubygems'
|
339
|
-
|
340
353
|
# if $0 is a file in the current directory, then restart
|
341
354
|
# it the same, otherwise add -S on there because it was
|
342
355
|
# picked up in PATH.
|
@@ -347,9 +360,10 @@ module Puma
|
|
347
360
|
arg0 = [Gem.ruby, "-S", $0]
|
348
361
|
end
|
349
362
|
|
350
|
-
# Detect and reinject -Ilib from the command line
|
363
|
+
# Detect and reinject -Ilib from the command line, used for testing without bundler
|
364
|
+
# cruby has an expanded path, jruby has just "lib"
|
351
365
|
lib = File.expand_path "lib"
|
352
|
-
arg0[1,0] = ["-I", lib] if
|
366
|
+
arg0[1,0] = ["-I", lib] if [lib, "lib"].include?($LOAD_PATH[0])
|
353
367
|
|
354
368
|
if defined? Puma::WILD_ARGS
|
355
369
|
@restart_argv = arg0 + Puma::WILD_ARGS + @original_argv
|
@@ -379,12 +393,28 @@ module Puma
|
|
379
393
|
|
380
394
|
begin
|
381
395
|
Signal.trap "SIGTERM" do
|
382
|
-
|
396
|
+
graceful_stop
|
397
|
+
|
398
|
+
raise SignalException, "SIGTERM"
|
383
399
|
end
|
384
400
|
rescue Exception
|
385
401
|
log "*** SIGTERM not implemented, signal based gracefully stopping unavailable!"
|
386
402
|
end
|
387
403
|
|
404
|
+
begin
|
405
|
+
Signal.trap "SIGINT" do
|
406
|
+
if Puma.jruby?
|
407
|
+
@status = :exit
|
408
|
+
graceful_stop
|
409
|
+
exit
|
410
|
+
end
|
411
|
+
|
412
|
+
stop
|
413
|
+
end
|
414
|
+
rescue Exception
|
415
|
+
log "*** SIGINT not implemented, signal based gracefully stopping unavailable!"
|
416
|
+
end
|
417
|
+
|
388
418
|
begin
|
389
419
|
Signal.trap "SIGHUP" do
|
390
420
|
if @runner.redirected_io?
|
@@ -396,14 +426,6 @@ module Puma
|
|
396
426
|
rescue Exception
|
397
427
|
log "*** SIGHUP not implemented, signal based logs reopening unavailable!"
|
398
428
|
end
|
399
|
-
|
400
|
-
if Puma.jruby?
|
401
|
-
Signal.trap("INT") do
|
402
|
-
@status = :exit
|
403
|
-
graceful_stop
|
404
|
-
exit
|
405
|
-
end
|
406
|
-
end
|
407
429
|
end
|
408
430
|
end
|
409
431
|
end
|
data/lib/puma/minissl.rb
CHANGED
@@ -1,3 +1,8 @@
|
|
1
|
+
begin
|
2
|
+
require 'io/wait'
|
3
|
+
rescue LoadError
|
4
|
+
end
|
5
|
+
|
1
6
|
module Puma
|
2
7
|
module MiniSSL
|
3
8
|
class Socket
|
@@ -11,6 +16,10 @@ module Puma
|
|
11
16
|
@socket
|
12
17
|
end
|
13
18
|
|
19
|
+
def closed?
|
20
|
+
@socket.closed?
|
21
|
+
end
|
22
|
+
|
14
23
|
def readpartial(size)
|
15
24
|
while true
|
16
25
|
output = @engine.read
|
@@ -36,12 +45,29 @@ module Puma
|
|
36
45
|
output
|
37
46
|
end
|
38
47
|
|
39
|
-
def read_nonblock(size)
|
48
|
+
def read_nonblock(size, *_)
|
49
|
+
# *_ is to deal with keyword args that were added
|
50
|
+
# at some point (and being used in the wild)
|
40
51
|
while true
|
41
52
|
output = engine_read_all
|
42
53
|
return output if output
|
43
54
|
|
44
|
-
|
55
|
+
begin
|
56
|
+
data = @socket.read_nonblock(size, exception: false)
|
57
|
+
if data == :wait_readable || data == :wait_writable
|
58
|
+
if @socket.to_io.respond_to?(data)
|
59
|
+
@socket.to_io.__send__(data)
|
60
|
+
elsif data == :wait_readable
|
61
|
+
IO.select([@socket.to_io])
|
62
|
+
else
|
63
|
+
IO.select(nil, [@socket.to_io])
|
64
|
+
end
|
65
|
+
elsif !data
|
66
|
+
return nil
|
67
|
+
else
|
68
|
+
break
|
69
|
+
end
|
70
|
+
end while true
|
45
71
|
|
46
72
|
@engine.inject(data)
|
47
73
|
output = engine_read_all
|
@@ -55,6 +81,8 @@ module Puma
|
|
55
81
|
end
|
56
82
|
|
57
83
|
def write(data)
|
84
|
+
return 0 if data.empty?
|
85
|
+
|
58
86
|
need = data.bytesize
|
59
87
|
|
60
88
|
while true
|
@@ -75,13 +103,52 @@ module Puma
|
|
75
103
|
end
|
76
104
|
|
77
105
|
alias_method :syswrite, :write
|
106
|
+
alias_method :<<, :write
|
107
|
+
|
108
|
+
# This is a temporary fix to deal with websockets code using
|
109
|
+
# write_nonblock. The problem with implementing it properly
|
110
|
+
# is that it means we'd have to have the ability to rewind
|
111
|
+
# an engine because after we write+extract, the socket
|
112
|
+
# write_nonblock call might raise an exception and later
|
113
|
+
# code would pass the same data in, but the engine would think
|
114
|
+
# it had already written the data in. So for the time being
|
115
|
+
# (and since write blocking is quite rare), go ahead and actually
|
116
|
+
# block in write_nonblock.
|
117
|
+
def write_nonblock(data, *_)
|
118
|
+
write data
|
119
|
+
end
|
78
120
|
|
79
121
|
def flush
|
80
122
|
@socket.flush
|
81
123
|
end
|
82
124
|
|
125
|
+
def read_and_drop(timeout = 1)
|
126
|
+
return :timeout unless IO.select([@socket], nil, nil, timeout)
|
127
|
+
return :eof unless read_nonblock(1024)
|
128
|
+
:drop
|
129
|
+
rescue Errno::EAGAIN
|
130
|
+
# do nothing
|
131
|
+
:eagain
|
132
|
+
end
|
133
|
+
|
134
|
+
def should_drop_bytes?
|
135
|
+
@engine.init? || !@engine.shutdown
|
136
|
+
end
|
137
|
+
|
83
138
|
def close
|
84
|
-
|
139
|
+
begin
|
140
|
+
# Read any drop any partially initialized sockets and any received bytes during shutdown.
|
141
|
+
# Don't let this socket hold this loop forever.
|
142
|
+
# If it can't send more packets within 1s, then give up.
|
143
|
+
while should_drop_bytes?
|
144
|
+
return if [:timeout, :eof].include?(read_and_drop(1))
|
145
|
+
end
|
146
|
+
rescue IOError, SystemCallError
|
147
|
+
Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
|
148
|
+
# nothing
|
149
|
+
ensure
|
150
|
+
@socket.close
|
151
|
+
end
|
85
152
|
end
|
86
153
|
|
87
154
|
def peeraddr
|
@@ -113,6 +180,7 @@ module Puma
|
|
113
180
|
# jruby-specific Context properties: java uses a keystore and password pair rather than a cert/key pair
|
114
181
|
attr_reader :keystore
|
115
182
|
attr_accessor :keystore_pass
|
183
|
+
attr_accessor :ssl_cipher_list
|
116
184
|
|
117
185
|
def keystore=(keystore)
|
118
186
|
raise ArgumentError, "No such keystore file '#{keystore}'" unless File.exist? keystore
|
@@ -128,6 +196,7 @@ module Puma
|
|
128
196
|
attr_reader :key
|
129
197
|
attr_reader :cert
|
130
198
|
attr_reader :ca
|
199
|
+
attr_accessor :ssl_cipher_filter
|
131
200
|
|
132
201
|
def key=(key)
|
133
202
|
raise ArgumentError, "No such key file '#{key}'" unless File.exist? key
|
@@ -182,7 +251,7 @@ module Puma
|
|
182
251
|
end
|
183
252
|
|
184
253
|
def close
|
185
|
-
@socket.close
|
254
|
+
@socket.close unless @socket.closed? # closed? call is for Windows
|
186
255
|
end
|
187
256
|
end
|
188
257
|
end
|
data/lib/puma/null_io.rb
CHANGED
@@ -1,42 +1,35 @@
|
|
1
1
|
module Puma
|
2
|
-
|
3
2
|
# Provides an IO-like object that always appears to contain no data.
|
4
3
|
# Used as the value for rack.input when the request has no body.
|
5
4
|
#
|
6
5
|
class NullIO
|
7
|
-
# Always returns nil
|
8
|
-
#
|
9
6
|
def gets
|
10
7
|
nil
|
11
8
|
end
|
12
9
|
|
13
|
-
# Never yields
|
14
|
-
#
|
15
10
|
def each
|
16
11
|
end
|
17
12
|
|
18
|
-
# Mimics IO#read with no data
|
13
|
+
# Mimics IO#read with no data.
|
19
14
|
#
|
20
|
-
def read(count=nil,
|
15
|
+
def read(count = nil, _buffer = nil)
|
21
16
|
(count && count > 0) ? nil : ""
|
22
17
|
end
|
23
18
|
|
24
|
-
# Does nothing
|
25
|
-
#
|
26
19
|
def rewind
|
27
20
|
end
|
28
21
|
|
29
|
-
# Does nothing
|
30
|
-
#
|
31
22
|
def close
|
32
23
|
end
|
33
24
|
|
34
|
-
# Always zero
|
35
|
-
#
|
36
25
|
def size
|
37
26
|
0
|
38
27
|
end
|
39
28
|
|
29
|
+
def eof?
|
30
|
+
true
|
31
|
+
end
|
32
|
+
|
40
33
|
def sync=(v)
|
41
34
|
end
|
42
35
|
|
@@ -8,7 +8,7 @@ Puma::Plugin.create do
|
|
8
8
|
|
9
9
|
# If we can't write to the path, then just don't bother with this plugin
|
10
10
|
begin
|
11
|
-
File.write
|
11
|
+
File.write(path, "") unless File.exist?(path)
|
12
12
|
orig = File.stat(path).mtime
|
13
13
|
rescue SystemCallError
|
14
14
|
return
|
@@ -32,4 +32,3 @@ Puma::Plugin.create do
|
|
32
32
|
end
|
33
33
|
end
|
34
34
|
end
|
35
|
-
|