puma 5.0.0.beta1 → 5.0.0.beta2
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 +23 -1
- data/README.md +3 -3
- data/docs/architecture.md +3 -3
- data/docs/deployment.md +6 -2
- data/docs/signals.md +4 -4
- data/ext/puma_http11/http11_parser.c +3 -1
- data/ext/puma_http11/http11_parser.rl +3 -1
- data/ext/puma_http11/mini_ssl.c +12 -2
- data/ext/puma_http11/org/jruby/puma/MiniSSL.java +37 -6
- data/ext/puma_http11/puma_http11.c +1 -1
- data/lib/puma.rb +1 -0
- data/lib/puma/app/status.rb +4 -2
- data/lib/puma/binder.rb +5 -4
- data/lib/puma/client.rb +36 -9
- data/lib/puma/cluster.rb +8 -4
- data/lib/puma/commonlogger.rb +2 -2
- data/lib/puma/const.rb +1 -1
- data/lib/puma/dsl.rb +3 -3
- data/lib/puma/error_logger.rb +96 -0
- data/lib/puma/events.rb +33 -31
- data/lib/puma/launcher.rb +5 -2
- data/lib/puma/minissl.rb +34 -2
- data/lib/puma/reactor.rb +2 -2
- data/lib/puma/runner.rb +1 -1
- data/lib/puma/server.rb +84 -44
- data/lib/puma/state_file.rb +1 -1
- data/lib/puma/thread_pool.rb +5 -2
- metadata +8 -7
data/lib/puma/cluster.rb
CHANGED
@@ -5,7 +5,6 @@ require 'puma/util'
|
|
5
5
|
require 'puma/plugin'
|
6
6
|
|
7
7
|
require 'time'
|
8
|
-
require 'json'
|
9
8
|
|
10
9
|
module Puma
|
11
10
|
# This class is instantiated by the `Puma::Launcher` and used
|
@@ -95,6 +94,7 @@ module Puma
|
|
95
94
|
|
96
95
|
def ping!(status)
|
97
96
|
@last_checkin = Time.now
|
97
|
+
require 'json'
|
98
98
|
@last_status = JSON.parse(status, symbolize_names: true)
|
99
99
|
end
|
100
100
|
|
@@ -248,6 +248,7 @@ module Puma
|
|
248
248
|
$0 = title
|
249
249
|
|
250
250
|
Signal.trap "SIGINT", "IGNORE"
|
251
|
+
Signal.trap "SIGCHLD", "DEFAULT"
|
251
252
|
|
252
253
|
fork_worker = @options[:fork_worker] && index == 0
|
253
254
|
|
@@ -284,9 +285,11 @@ module Puma
|
|
284
285
|
|
285
286
|
if fork_worker
|
286
287
|
restart_server.clear
|
288
|
+
worker_pids = []
|
287
289
|
Signal.trap "SIGCHLD" do
|
288
|
-
|
289
|
-
|
290
|
+
wakeup! if worker_pids.reject! do |p|
|
291
|
+
Process.wait(p, Process::WNOHANG) rescue true
|
292
|
+
end
|
290
293
|
end
|
291
294
|
|
292
295
|
Thread.new do
|
@@ -303,7 +306,7 @@ module Puma
|
|
303
306
|
elsif idx == 0 # restart server
|
304
307
|
restart_server << true << false
|
305
308
|
else # fork worker
|
306
|
-
pid = spawn_worker(idx, master)
|
309
|
+
worker_pids << pid = spawn_worker(idx, master)
|
307
310
|
@worker_write << "f#{pid}:#{idx}\n" rescue nil
|
308
311
|
end
|
309
312
|
end
|
@@ -330,6 +333,7 @@ module Puma
|
|
330
333
|
while true
|
331
334
|
sleep Const::WORKER_CHECK_INTERVAL
|
332
335
|
begin
|
336
|
+
require 'json'
|
333
337
|
io << "p#{Process.pid}#{server.stats.to_json}\n"
|
334
338
|
rescue IOError
|
335
339
|
Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
|
data/lib/puma/commonlogger.rb
CHANGED
@@ -3,7 +3,7 @@
|
|
3
3
|
module Puma
|
4
4
|
# Rack::CommonLogger forwards every request to the given +app+, and
|
5
5
|
# logs a line in the
|
6
|
-
# {Apache common log format}[
|
6
|
+
# {Apache common log format}[https://httpd.apache.org/docs/1.3/logs.html#common]
|
7
7
|
# to the +logger+.
|
8
8
|
#
|
9
9
|
# If +logger+ is nil, CommonLogger will fall back +rack.errors+, which is
|
@@ -16,7 +16,7 @@ module Puma
|
|
16
16
|
# (which is called without arguments in order to make the error appear for
|
17
17
|
# sure)
|
18
18
|
class CommonLogger
|
19
|
-
# Common Log Format:
|
19
|
+
# Common Log Format: https://httpd.apache.org/docs/1.3/logs.html#common
|
20
20
|
#
|
21
21
|
# lilith.local - - [07/Aug/2006 23:58:02 -0400] "GET / HTTP/1.1" 500 -
|
22
22
|
#
|
data/lib/puma/const.rb
CHANGED
@@ -100,7 +100,7 @@ module Puma
|
|
100
100
|
# too taxing on performance.
|
101
101
|
module Const
|
102
102
|
|
103
|
-
PUMA_VERSION = VERSION = "5.0.0.
|
103
|
+
PUMA_VERSION = VERSION = "5.0.0.beta2".freeze
|
104
104
|
CODE_NAME = "Spoony Bard".freeze
|
105
105
|
PUMA_SERVER_STRING = ['puma', PUMA_VERSION, CODE_NAME].join(' ').freeze
|
106
106
|
|
data/lib/puma/dsl.rb
CHANGED
@@ -443,8 +443,8 @@ module Puma
|
|
443
443
|
#
|
444
444
|
# @note Cluster mode only.
|
445
445
|
# @example
|
446
|
-
#
|
447
|
-
# puts 'Before worker
|
446
|
+
# on_worker_boot do
|
447
|
+
# puts 'Before worker boot...'
|
448
448
|
# end
|
449
449
|
def on_worker_boot(&block)
|
450
450
|
@options[:before_worker_boot] ||= []
|
@@ -769,7 +769,7 @@ module Puma
|
|
769
769
|
# also increase time to boot and fork. See your logs for details on how much
|
770
770
|
# time this adds to your boot process. For most apps, it will be less than one
|
771
771
|
# second.
|
772
|
-
def nakayoshi_fork(enabled=
|
772
|
+
def nakayoshi_fork(enabled=true)
|
773
773
|
@options[:nakayoshi_fork] = enabled
|
774
774
|
end
|
775
775
|
end
|
@@ -0,0 +1,96 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'puma/const'
|
4
|
+
|
5
|
+
module Puma
|
6
|
+
# The implementation of a detailed error logging.
|
7
|
+
#
|
8
|
+
class ErrorLogger
|
9
|
+
include Const
|
10
|
+
|
11
|
+
attr_reader :ioerr
|
12
|
+
|
13
|
+
REQUEST_FORMAT = %{"%s %s%s" - (%s)}
|
14
|
+
|
15
|
+
def initialize(ioerr)
|
16
|
+
@ioerr = ioerr
|
17
|
+
@ioerr.sync = true
|
18
|
+
|
19
|
+
@debug = ENV.key? 'PUMA_DEBUG'
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.stdio
|
23
|
+
new $stderr
|
24
|
+
end
|
25
|
+
|
26
|
+
# Print occured error details.
|
27
|
+
# +options+ hash with additional options:
|
28
|
+
# - +error+ is an exception object
|
29
|
+
# - +req+ the http request
|
30
|
+
# - +text+ (default nil) custom string to print in title
|
31
|
+
# and before all remaining info.
|
32
|
+
#
|
33
|
+
def info(options={})
|
34
|
+
ioerr.puts title(options)
|
35
|
+
end
|
36
|
+
|
37
|
+
# Print occured error details only if
|
38
|
+
# environment variable PUMA_DEBUG is defined.
|
39
|
+
# +options+ hash with additional options:
|
40
|
+
# - +error+ is an exception object
|
41
|
+
# - +req+ the http request
|
42
|
+
# - +text+ (default nil) custom string to print in title
|
43
|
+
# and before all remaining info.
|
44
|
+
#
|
45
|
+
def debug(options={})
|
46
|
+
return unless @debug
|
47
|
+
|
48
|
+
error = options[:error]
|
49
|
+
req = options[:req]
|
50
|
+
|
51
|
+
string_block = []
|
52
|
+
string_block << title(options)
|
53
|
+
string_block << request_dump(req) if req
|
54
|
+
string_block << error_backtrace(options) if error
|
55
|
+
|
56
|
+
ioerr.puts string_block.join("\n")
|
57
|
+
end
|
58
|
+
|
59
|
+
def title(options={})
|
60
|
+
text = options[:text]
|
61
|
+
req = options[:req]
|
62
|
+
error = options[:error]
|
63
|
+
|
64
|
+
string_block = ["#{Time.now}"]
|
65
|
+
string_block << " #{text}" if text
|
66
|
+
string_block << " (#{request_title(req)})" if request_parsed?(req)
|
67
|
+
string_block << ": #{error.inspect}" if error
|
68
|
+
string_block.join('')
|
69
|
+
end
|
70
|
+
|
71
|
+
def request_dump(req)
|
72
|
+
"Headers: #{request_headers(req)}\n" \
|
73
|
+
"Body: #{req.body}"
|
74
|
+
end
|
75
|
+
|
76
|
+
def request_title(req)
|
77
|
+
env = req.env
|
78
|
+
|
79
|
+
REQUEST_FORMAT % [
|
80
|
+
env[REQUEST_METHOD],
|
81
|
+
env[REQUEST_PATH] || env[PATH_INFO],
|
82
|
+
env[QUERY_STRING] || "",
|
83
|
+
env[HTTP_X_FORWARDED_FOR] || env[REMOTE_ADDR] || "-"
|
84
|
+
]
|
85
|
+
end
|
86
|
+
|
87
|
+
def request_headers(req)
|
88
|
+
headers = req.env.select { |key, _| key.start_with?('HTTP_') }
|
89
|
+
headers.map { |key, value| [key[5..-1], value] }.to_h.inspect
|
90
|
+
end
|
91
|
+
|
92
|
+
def request_parsed?(req)
|
93
|
+
req && req.env[REQUEST_METHOD]
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
data/lib/puma/events.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require 'puma/const'
|
4
3
|
require "puma/null_io"
|
4
|
+
require 'puma/error_logger'
|
5
5
|
require 'stringio'
|
6
6
|
|
7
7
|
module Puma
|
@@ -23,8 +23,6 @@ module Puma
|
|
23
23
|
end
|
24
24
|
end
|
25
25
|
|
26
|
-
include Const
|
27
|
-
|
28
26
|
# Create an Events object that prints to +stdout+ and +stderr+.
|
29
27
|
#
|
30
28
|
def initialize(stdout, stderr)
|
@@ -36,6 +34,7 @@ module Puma
|
|
36
34
|
@stderr.sync = true
|
37
35
|
|
38
36
|
@debug = ENV.key? 'PUMA_DEBUG'
|
37
|
+
@error_logger = ErrorLogger.new(@stderr)
|
39
38
|
|
40
39
|
@hooks = Hash.new { |h,k| h[k] = [] }
|
41
40
|
end
|
@@ -66,7 +65,8 @@ module Puma
|
|
66
65
|
# Write +str+ to +@stdout+
|
67
66
|
#
|
68
67
|
def log(str)
|
69
|
-
@stdout.puts format(str)
|
68
|
+
@stdout.puts format(str) if @stdout.respond_to? :puts
|
69
|
+
rescue Errno::EPIPE
|
70
70
|
end
|
71
71
|
|
72
72
|
def write(str)
|
@@ -80,7 +80,7 @@ module Puma
|
|
80
80
|
# Write +str+ to +@stderr+
|
81
81
|
#
|
82
82
|
def error(str)
|
83
|
-
@
|
83
|
+
@error_logger.info(text: format("ERROR: #{str}"))
|
84
84
|
exit 1
|
85
85
|
end
|
86
86
|
|
@@ -88,43 +88,45 @@ module Puma
|
|
88
88
|
formatter.call(str)
|
89
89
|
end
|
90
90
|
|
91
|
+
# An HTTP connection error has occurred.
|
92
|
+
# +error+ a connection exception, +req+ the request,
|
93
|
+
# and +text+ additional info
|
94
|
+
#
|
95
|
+
def connection_error(error, req, text="HTTP connection error")
|
96
|
+
@error_logger.info(error: error, req: req, text: text)
|
97
|
+
end
|
98
|
+
|
91
99
|
# An HTTP parse error has occurred.
|
92
|
-
# +
|
93
|
-
#
|
100
|
+
# +error+ a parsing exception,
|
101
|
+
# and +req+ the request.
|
94
102
|
#
|
95
|
-
def parse_error(
|
96
|
-
@
|
97
|
-
"(#{env[HTTP_X_FORWARDED_FOR] || env[REMOTE_ADDR]}#{env[REQUEST_PATH]}): " \
|
98
|
-
"#{error.inspect}" \
|
99
|
-
"\n---\n"
|
103
|
+
def parse_error(error, req)
|
104
|
+
@error_logger.info(error: error, req: req, text: 'HTTP parse error, malformed request')
|
100
105
|
end
|
101
106
|
|
102
107
|
# An SSL error has occurred.
|
103
|
-
# +
|
104
|
-
# any peer certificate (if present)
|
108
|
+
# +error+ an exception object, +peeraddr+ peer address,
|
109
|
+
# and +peercert+ any peer certificate (if present).
|
105
110
|
#
|
106
|
-
def ssl_error(
|
111
|
+
def ssl_error(error, peeraddr, peercert)
|
107
112
|
subject = peercert ? peercert.subject : nil
|
108
|
-
@
|
113
|
+
@error_logger.info(error: error, text: "SSL error, peer: #{peeraddr}, peer cert: #{subject}")
|
109
114
|
end
|
110
115
|
|
111
116
|
# An unknown error has occurred.
|
112
|
-
# +
|
113
|
-
# +
|
117
|
+
# +error+ an exception object, +req+ the request,
|
118
|
+
# and +text+ additional info
|
114
119
|
#
|
115
|
-
def unknown_error(
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
string_block << error.backtrace
|
126
|
-
@stderr.puts string_block.join("\n")
|
127
|
-
end
|
120
|
+
def unknown_error(error, req=nil, text="Unknown error")
|
121
|
+
@error_logger.info(error: error, req: req, text: text)
|
122
|
+
end
|
123
|
+
|
124
|
+
# Log occurred error debug dump.
|
125
|
+
# +error+ an exception object, +req+ the request,
|
126
|
+
# and +text+ additional info
|
127
|
+
#
|
128
|
+
def debug_error(error, req=nil, text="")
|
129
|
+
@error_logger.debug(error: error, req: req, text: text)
|
128
130
|
end
|
129
131
|
|
130
132
|
def on_booted(&block)
|
data/lib/puma/launcher.rb
CHANGED
@@ -47,7 +47,7 @@ module Puma
|
|
47
47
|
@original_argv = @argv.dup
|
48
48
|
@config = conf
|
49
49
|
|
50
|
-
@binder = Binder.new(@events)
|
50
|
+
@binder = Binder.new(@events, conf)
|
51
51
|
@binder.create_inherited_fds(ENV).each { |k| ENV.delete k }
|
52
52
|
@binder.create_activated_fds(ENV).each { |k| ENV.delete k }
|
53
53
|
|
@@ -111,6 +111,7 @@ module Puma
|
|
111
111
|
sf.pid = Process.pid
|
112
112
|
sf.control_url = @options[:control_url]
|
113
113
|
sf.control_auth_token = @options[:control_auth_token]
|
114
|
+
sf.running_from = File.expand_path('.')
|
114
115
|
|
115
116
|
sf.save path, permission
|
116
117
|
end
|
@@ -172,12 +173,13 @@ module Puma
|
|
172
173
|
case @status
|
173
174
|
when :halt
|
174
175
|
log "* Stopping immediately!"
|
176
|
+
@runner.stop_control
|
175
177
|
when :run, :stop
|
176
178
|
graceful_stop
|
177
179
|
when :restart
|
178
180
|
log "* Restarting..."
|
179
181
|
ENV.replace(previous_env)
|
180
|
-
@runner.
|
182
|
+
@runner.stop_control
|
181
183
|
restart!
|
182
184
|
when :exit
|
183
185
|
# nothing
|
@@ -286,6 +288,7 @@ module Puma
|
|
286
288
|
end
|
287
289
|
|
288
290
|
def prune_bundler
|
291
|
+
return if ENV['PUMA_BUNDLER_PRUNED']
|
289
292
|
return unless defined?(Bundler)
|
290
293
|
require_rubygems_min_version!(Gem::Version.new("2.2"), "prune_bundler")
|
291
294
|
unless puma_wild_location
|
data/lib/puma/minissl.rb
CHANGED
@@ -5,8 +5,18 @@ begin
|
|
5
5
|
rescue LoadError
|
6
6
|
end
|
7
7
|
|
8
|
+
# need for Puma::MiniSSL::OPENSSL constants used in `HAS_TLS1_3`
|
9
|
+
require 'puma/puma_http11'
|
10
|
+
|
8
11
|
module Puma
|
9
12
|
module MiniSSL
|
13
|
+
|
14
|
+
# define constant at runtime, as it's easy to determine at built time,
|
15
|
+
# but Puma could (it shouldn't) be loaded with an older OpenSSL version
|
16
|
+
HAS_TLS1_3 = !IS_JRUBY &&
|
17
|
+
(OPENSSL_VERSION[/ \d+\.\d+\.\d+/].split('.').map(&:to_i) <=> [1,1,1]) != -1 &&
|
18
|
+
(OPENSSL_LIBRARY_VERSION[/ \d+\.\d+\.\d+/].split('.').map(&:to_i) <=> [1,1,1]) !=-1
|
19
|
+
|
10
20
|
class Socket
|
11
21
|
def initialize(socket, engine)
|
12
22
|
@socket = socket
|
@@ -22,6 +32,24 @@ module Puma
|
|
22
32
|
@socket.closed?
|
23
33
|
end
|
24
34
|
|
35
|
+
# returns a two element array
|
36
|
+
# first is protocol version (SSL_get_version)
|
37
|
+
# second is 'handshake' state (SSL_state_string)
|
38
|
+
#
|
39
|
+
# used for dropping tcp connections to ssl
|
40
|
+
# see OpenSSL ssl/ssl_stat.c SSL_state_string for info
|
41
|
+
#
|
42
|
+
def ssl_version_state
|
43
|
+
IS_JRUBY ? [nil, nil] : @engine.ssl_vers_st
|
44
|
+
end
|
45
|
+
|
46
|
+
# used to check the handshake status, in particular when a TCP connection
|
47
|
+
# is made with TLSv1.3 as an available protocol
|
48
|
+
def bad_tlsv1_3?
|
49
|
+
HAS_TLS1_3 && @engine.ssl_vers_st == ['TLSv1.3', 'SSLERR']
|
50
|
+
end
|
51
|
+
private :bad_tlsv1_3?
|
52
|
+
|
25
53
|
def readpartial(size)
|
26
54
|
while true
|
27
55
|
output = @engine.read
|
@@ -41,6 +69,7 @@ module Puma
|
|
41
69
|
|
42
70
|
def engine_read_all
|
43
71
|
output = @engine.read
|
72
|
+
raise SSLError.exception "HTTP connection?" if bad_tlsv1_3?
|
44
73
|
while output and additional_output = @engine.read
|
45
74
|
output << additional_output
|
46
75
|
end
|
@@ -167,7 +196,10 @@ module Puma
|
|
167
196
|
end
|
168
197
|
end
|
169
198
|
|
170
|
-
if
|
199
|
+
if IS_JRUBY
|
200
|
+
OPENSSL_NO_SSL3 = false
|
201
|
+
OPENSSL_NO_TLS1 = false
|
202
|
+
|
171
203
|
class SSLError < StandardError
|
172
204
|
# Define this for jruby even though it isn't used.
|
173
205
|
end
|
@@ -184,7 +216,7 @@ module Puma
|
|
184
216
|
@no_tlsv1_1 = false
|
185
217
|
end
|
186
218
|
|
187
|
-
if
|
219
|
+
if IS_JRUBY
|
188
220
|
# jruby-specific Context properties: java uses a keystore and password pair rather than a cert/key pair
|
189
221
|
attr_reader :keystore
|
190
222
|
attr_accessor :keystore_pass
|
data/lib/puma/reactor.rb
CHANGED
@@ -252,7 +252,7 @@ module Puma
|
|
252
252
|
c.close
|
253
253
|
clear_monitor mon
|
254
254
|
|
255
|
-
@events.ssl_error
|
255
|
+
@events.ssl_error e, addr, cert
|
256
256
|
|
257
257
|
# The client doesn't know HTTP well
|
258
258
|
rescue HttpParserError => e
|
@@ -263,7 +263,7 @@ module Puma
|
|
263
263
|
|
264
264
|
clear_monitor mon
|
265
265
|
|
266
|
-
@events.parse_error
|
266
|
+
@events.parse_error e, c
|
267
267
|
rescue StandardError => e
|
268
268
|
@server.lowlevel_error(e, c.env)
|
269
269
|
|