puma 3.5.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} +318 -75
- 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 +67 -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 +22 -17
- data/lib/puma/cli.rb +49 -33
- data/lib/puma/client.rb +149 -4
- data/lib/puma/cluster.rb +54 -12
- 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 +19 -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 +61 -30
- data/lib/puma/minissl.rb +85 -4
- data/lib/puma/null_io.rb +6 -13
- data/lib/puma/plugin.rb +12 -1
- 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 +27 -1
- data/lib/puma/server.rb +134 -32
- data/lib/puma/single.rb +17 -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
|
|
@@ -316,6 +331,11 @@ module Puma
|
|
316
331
|
if dir = @options[:directory]
|
317
332
|
@restart_dir = dir
|
318
333
|
|
334
|
+
elsif Puma.windows?
|
335
|
+
# I guess the value of PWD is garbage on windows so don't bother
|
336
|
+
# using it.
|
337
|
+
@restart_dir = Dir.pwd
|
338
|
+
|
319
339
|
# Use the same trick as unicorn, namely favor PWD because
|
320
340
|
# it will contain an unresolved symlink, useful for when
|
321
341
|
# the pwd is /data/releases/current.
|
@@ -330,8 +350,6 @@ module Puma
|
|
330
350
|
|
331
351
|
@restart_dir ||= Dir.pwd
|
332
352
|
|
333
|
-
require 'rubygems'
|
334
|
-
|
335
353
|
# if $0 is a file in the current directory, then restart
|
336
354
|
# it the same, otherwise add -S on there because it was
|
337
355
|
# picked up in PATH.
|
@@ -342,9 +360,10 @@ module Puma
|
|
342
360
|
arg0 = [Gem.ruby, "-S", $0]
|
343
361
|
end
|
344
362
|
|
345
|
-
# 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"
|
346
365
|
lib = File.expand_path "lib"
|
347
|
-
arg0[1,0] = ["-I", lib] if
|
366
|
+
arg0[1,0] = ["-I", lib] if [lib, "lib"].include?($LOAD_PATH[0])
|
348
367
|
|
349
368
|
if defined? Puma::WILD_ARGS
|
350
369
|
@restart_argv = arg0 + Puma::WILD_ARGS + @original_argv
|
@@ -374,26 +393,38 @@ module Puma
|
|
374
393
|
|
375
394
|
begin
|
376
395
|
Signal.trap "SIGTERM" do
|
377
|
-
|
396
|
+
graceful_stop
|
397
|
+
|
398
|
+
raise SignalException, "SIGTERM"
|
378
399
|
end
|
379
400
|
rescue Exception
|
380
401
|
log "*** SIGTERM not implemented, signal based gracefully stopping unavailable!"
|
381
402
|
end
|
382
403
|
|
383
404
|
begin
|
384
|
-
Signal.trap "
|
385
|
-
|
405
|
+
Signal.trap "SIGINT" do
|
406
|
+
if Puma.jruby?
|
407
|
+
@status = :exit
|
408
|
+
graceful_stop
|
409
|
+
exit
|
410
|
+
end
|
411
|
+
|
412
|
+
stop
|
386
413
|
end
|
387
414
|
rescue Exception
|
388
|
-
log "***
|
415
|
+
log "*** SIGINT not implemented, signal based gracefully stopping unavailable!"
|
389
416
|
end
|
390
417
|
|
391
|
-
|
392
|
-
Signal.trap
|
393
|
-
@
|
394
|
-
|
395
|
-
|
418
|
+
begin
|
419
|
+
Signal.trap "SIGHUP" do
|
420
|
+
if @runner.redirected_io?
|
421
|
+
@runner.redirect_io
|
422
|
+
else
|
423
|
+
stop
|
424
|
+
end
|
396
425
|
end
|
426
|
+
rescue Exception
|
427
|
+
log "*** SIGHUP not implemented, signal based logs reopening unavailable!"
|
397
428
|
end
|
398
429
|
end
|
399
430
|
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,16 +180,23 @@ 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
|
119
187
|
@keystore = keystore
|
120
188
|
end
|
189
|
+
|
190
|
+
def check
|
191
|
+
raise "Keystore not configured" unless @keystore
|
192
|
+
end
|
193
|
+
|
121
194
|
else
|
122
195
|
# non-jruby Context properties
|
123
196
|
attr_reader :key
|
124
197
|
attr_reader :cert
|
125
198
|
attr_reader :ca
|
199
|
+
attr_accessor :ssl_cipher_filter
|
126
200
|
|
127
201
|
def key=(key)
|
128
202
|
raise ArgumentError, "No such key file '#{key}'" unless File.exist? key
|
@@ -138,6 +212,11 @@ module Puma
|
|
138
212
|
raise ArgumentError, "No such ca file '#{ca}'" unless File.exist? ca
|
139
213
|
@ca = ca
|
140
214
|
end
|
215
|
+
|
216
|
+
def check
|
217
|
+
raise "Key not configured" unless @key
|
218
|
+
raise "Cert not configured" unless @cert
|
219
|
+
end
|
141
220
|
end
|
142
221
|
end
|
143
222
|
|
@@ -156,6 +235,7 @@ module Puma
|
|
156
235
|
end
|
157
236
|
|
158
237
|
def accept
|
238
|
+
@ctx.check
|
159
239
|
io = @socket.accept
|
160
240
|
engine = Engine.server @ctx
|
161
241
|
|
@@ -163,6 +243,7 @@ module Puma
|
|
163
243
|
end
|
164
244
|
|
165
245
|
def accept_nonblock
|
246
|
+
@ctx.check
|
166
247
|
io = @socket.accept_nonblock
|
167
248
|
engine = Engine.server @ctx
|
168
249
|
|
@@ -170,7 +251,7 @@ module Puma
|
|
170
251
|
end
|
171
252
|
|
172
253
|
def close
|
173
|
-
@socket.close
|
254
|
+
@socket.close unless @socket.closed? # closed? call is for Windows
|
174
255
|
end
|
175
256
|
end
|
176
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
|
|