puma 3.12.1 → 5.6.4
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 +1553 -447
- data/LICENSE +23 -20
- data/README.md +175 -63
- data/bin/puma-wild +3 -9
- data/docs/architecture.md +59 -21
- data/docs/compile_options.md +21 -0
- data/docs/deployment.md +69 -58
- data/docs/fork_worker.md +33 -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/jungle/README.md +9 -0
- data/{tools → docs}/jungle/rc.d/README.md +1 -1
- data/{tools → docs}/jungle/rc.d/puma +2 -2
- data/{tools → docs}/jungle/rc.d/puma.conf +0 -0
- data/docs/kubernetes.md +66 -0
- data/docs/nginx.md +1 -1
- data/docs/plugins.md +22 -12
- data/docs/rails_dev_mode.md +28 -0
- data/docs/restart.md +47 -22
- data/docs/signals.md +13 -11
- data/docs/stats.md +142 -0
- data/docs/systemd.md +95 -120
- data/ext/puma_http11/PumaHttp11Service.java +2 -2
- data/ext/puma_http11/ext_help.h +1 -1
- data/ext/puma_http11/extconf.rb +51 -1
- data/ext/puma_http11/http11_parser.c +105 -117
- data/ext/puma_http11/http11_parser.h +1 -1
- data/ext/puma_http11/http11_parser.java.rl +22 -38
- data/ext/puma_http11/http11_parser.rl +4 -2
- data/ext/puma_http11/http11_parser_common.rl +4 -4
- data/ext/puma_http11/mini_ssl.c +319 -96
- data/ext/puma_http11/no_ssl/PumaHttp11Service.java +15 -0
- data/ext/puma_http11/org/jruby/puma/Http11.java +108 -116
- data/ext/puma_http11/org/jruby/puma/Http11Parser.java +84 -99
- data/ext/puma_http11/org/jruby/puma/MiniSSL.java +120 -65
- data/ext/puma_http11/puma_http11.c +35 -51
- data/lib/puma/app/status.rb +68 -49
- data/lib/puma/binder.rb +234 -137
- data/lib/puma/cli.rb +28 -18
- data/lib/puma/client.rb +343 -230
- data/lib/puma/cluster/worker.rb +173 -0
- data/lib/puma/cluster/worker_handle.rb +94 -0
- data/lib/puma/cluster.rb +247 -232
- data/lib/puma/commonlogger.rb +2 -2
- data/lib/puma/configuration.rb +61 -51
- data/lib/puma/const.rb +42 -21
- data/lib/puma/control_cli.rb +109 -67
- data/lib/puma/detect.rb +29 -2
- data/lib/puma/dsl.rb +615 -123
- data/lib/puma/error_logger.rb +104 -0
- data/lib/puma/events.rb +55 -31
- data/lib/puma/io_buffer.rb +7 -5
- data/lib/puma/jruby_restart.rb +0 -58
- data/lib/puma/json_serialization.rb +96 -0
- data/lib/puma/launcher.rb +182 -69
- data/lib/puma/minissl/context_builder.rb +81 -0
- data/lib/puma/minissl.rb +161 -61
- data/lib/puma/null_io.rb +13 -1
- data/lib/puma/plugin/tmp_restart.rb +2 -0
- data/lib/puma/plugin.rb +7 -13
- data/lib/puma/queue_close.rb +26 -0
- data/lib/puma/rack/builder.rb +3 -5
- data/lib/puma/rack/urlmap.rb +2 -0
- data/lib/puma/rack_default.rb +2 -0
- data/lib/puma/reactor.rb +85 -316
- data/lib/puma/request.rb +472 -0
- data/lib/puma/runner.rb +48 -55
- data/lib/puma/server.rb +303 -695
- data/lib/puma/single.rb +11 -67
- data/lib/puma/state_file.rb +47 -8
- data/lib/puma/systemd.rb +46 -0
- data/lib/puma/thread_pool.rb +132 -82
- data/lib/puma/util.rb +21 -7
- data/lib/puma.rb +54 -0
- data/lib/rack/handler/puma.rb +5 -6
- data/tools/Dockerfile +16 -0
- data/tools/trickletest.rb +0 -1
- metadata +45 -29
- data/ext/puma_http11/io_buffer.c +0 -155
- data/lib/puma/accept_nonblock.rb +0 -23
- data/lib/puma/compat.rb +0 -14
- data/lib/puma/convenient.rb +0 -25
- data/lib/puma/daemon_ext.rb +0 -33
- data/lib/puma/delegation.rb +0 -13
- data/lib/puma/java_io_buffer.rb +0 -47
- data/lib/puma/rack/backports/uri/common_193.rb +0 -33
- data/lib/puma/tcp_logger.rb +0 -41
- data/tools/jungle/README.md +0 -19
- data/tools/jungle/init.d/README.md +0 -61
- data/tools/jungle/init.d/puma +0 -421
- data/tools/jungle/init.d/run-puma +0 -18
- data/tools/jungle/upstart/README.md +0 -61
- data/tools/jungle/upstart/puma-manager.conf +0 -31
- data/tools/jungle/upstart/puma.conf +0 -69
@@ -0,0 +1,104 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'puma/const'
|
4
|
+
|
5
|
+
module Puma
|
6
|
+
# The implementation of a detailed error logging.
|
7
|
+
# @version 5.0.0
|
8
|
+
#
|
9
|
+
class ErrorLogger
|
10
|
+
include Const
|
11
|
+
|
12
|
+
attr_reader :ioerr
|
13
|
+
|
14
|
+
REQUEST_FORMAT = %{"%s %s%s" - (%s)}
|
15
|
+
|
16
|
+
def initialize(ioerr)
|
17
|
+
@ioerr = ioerr
|
18
|
+
|
19
|
+
@debug = ENV.key? 'PUMA_DEBUG'
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.stdio
|
23
|
+
new $stderr
|
24
|
+
end
|
25
|
+
|
26
|
+
# Print occurred 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
|
+
log title(options)
|
35
|
+
end
|
36
|
+
|
37
|
+
# Print occurred 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 request_parsed?(req)
|
54
|
+
string_block << error.backtrace if error
|
55
|
+
|
56
|
+
log 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
|
+
|
96
|
+
private
|
97
|
+
|
98
|
+
def log(str)
|
99
|
+
ioerr.puts str
|
100
|
+
|
101
|
+
ioerr.flush unless ioerr.sync
|
102
|
+
end
|
103
|
+
end
|
104
|
+
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)
|
@@ -32,10 +30,8 @@ module Puma
|
|
32
30
|
@stdout = stdout
|
33
31
|
@stderr = stderr
|
34
32
|
|
35
|
-
@stdout.sync = true
|
36
|
-
@stderr.sync = true
|
37
|
-
|
38
33
|
@debug = ENV.key? 'PUMA_DEBUG'
|
34
|
+
@error_logger = ErrorLogger.new(@stderr)
|
39
35
|
|
40
36
|
@hooks = Hash.new { |h,k| h[k] = [] }
|
41
37
|
end
|
@@ -66,7 +62,10 @@ module Puma
|
|
66
62
|
# Write +str+ to +@stdout+
|
67
63
|
#
|
68
64
|
def log(str)
|
69
|
-
@stdout.puts format(str)
|
65
|
+
@stdout.puts format(str) if @stdout.respond_to? :puts
|
66
|
+
|
67
|
+
@stdout.flush unless @stdout.sync
|
68
|
+
rescue Errno::EPIPE
|
70
69
|
end
|
71
70
|
|
72
71
|
def write(str)
|
@@ -80,7 +79,7 @@ module Puma
|
|
80
79
|
# Write +str+ to +@stderr+
|
81
80
|
#
|
82
81
|
def error(str)
|
83
|
-
@
|
82
|
+
@error_logger.info(text: format("ERROR: #{str}"))
|
84
83
|
exit 1
|
85
84
|
end
|
86
85
|
|
@@ -88,50 +87,75 @@ module Puma
|
|
88
87
|
formatter.call(str)
|
89
88
|
end
|
90
89
|
|
90
|
+
# An HTTP connection error has occurred.
|
91
|
+
# +error+ a connection exception, +req+ the request,
|
92
|
+
# and +text+ additional info
|
93
|
+
# @version 5.0.0
|
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
|
-
@
|
103
|
+
def parse_error(error, req)
|
104
|
+
@error_logger.info(error: error, req: req, text: 'HTTP parse error, malformed request')
|
97
105
|
end
|
98
106
|
|
99
107
|
# An SSL error has occurred.
|
100
|
-
#
|
101
|
-
#
|
108
|
+
# @param error <Puma::MiniSSL::SSLError>
|
109
|
+
# @param ssl_socket <Puma::MiniSSL::Socket>
|
102
110
|
#
|
103
|
-
def ssl_error(
|
111
|
+
def ssl_error(error, ssl_socket)
|
112
|
+
peeraddr = ssl_socket.peeraddr.last rescue "<unknown>"
|
113
|
+
peercert = ssl_socket.peercert
|
104
114
|
subject = peercert ? peercert.subject : nil
|
105
|
-
@
|
115
|
+
@error_logger.info(error: error, text: "SSL error, peer: #{peeraddr}, peer cert: #{subject}")
|
106
116
|
end
|
107
117
|
|
108
118
|
# An unknown error has occurred.
|
109
|
-
# +
|
110
|
-
# +
|
119
|
+
# +error+ an exception object, +req+ the request,
|
120
|
+
# and +text+ additional info
|
111
121
|
#
|
112
|
-
def unknown_error(
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
@stderr.puts string_block.join("\n")
|
124
|
-
end
|
122
|
+
def unknown_error(error, req=nil, text="Unknown error")
|
123
|
+
@error_logger.info(error: error, req: req, text: text)
|
124
|
+
end
|
125
|
+
|
126
|
+
# Log occurred error debug dump.
|
127
|
+
# +error+ an exception object, +req+ the request,
|
128
|
+
# and +text+ additional info
|
129
|
+
# @version 5.0.0
|
130
|
+
#
|
131
|
+
def debug_error(error, req=nil, text="")
|
132
|
+
@error_logger.debug(error: error, req: req, text: text)
|
125
133
|
end
|
126
134
|
|
127
135
|
def on_booted(&block)
|
128
136
|
register(:on_booted, &block)
|
129
137
|
end
|
130
138
|
|
139
|
+
def on_restart(&block)
|
140
|
+
register(:on_restart, &block)
|
141
|
+
end
|
142
|
+
|
143
|
+
def on_stopped(&block)
|
144
|
+
register(:on_stopped, &block)
|
145
|
+
end
|
146
|
+
|
131
147
|
def fire_on_booted!
|
132
148
|
fire(:on_booted)
|
133
149
|
end
|
134
150
|
|
151
|
+
def fire_on_restart!
|
152
|
+
fire(:on_restart)
|
153
|
+
end
|
154
|
+
|
155
|
+
def fire_on_stopped!
|
156
|
+
fire(:on_stopped)
|
157
|
+
end
|
158
|
+
|
135
159
|
DEFAULT = new(STDOUT, STDERR)
|
136
160
|
|
137
161
|
# Returns an Events object which writes its status to 2 StringIO
|
data/lib/puma/io_buffer.rb
CHANGED
@@ -1,9 +1,11 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
3
|
+
module Puma
|
4
|
+
class IOBuffer < String
|
5
|
+
def append(*args)
|
6
|
+
args.each { |a| concat(a) }
|
7
|
+
end
|
4
8
|
|
5
|
-
|
6
|
-
|
7
|
-
else
|
8
|
-
require 'puma/puma_http11'
|
9
|
+
alias reset clear
|
10
|
+
end
|
9
11
|
end
|
data/lib/puma/jruby_restart.rb
CHANGED
@@ -22,63 +22,5 @@ module Puma
|
|
22
22
|
execlp(cmd, *argv)
|
23
23
|
raise SystemCallError.new(FFI.errno)
|
24
24
|
end
|
25
|
-
|
26
|
-
PermKey = 'PUMA_DAEMON_PERM'
|
27
|
-
RestartKey = 'PUMA_DAEMON_RESTART'
|
28
|
-
|
29
|
-
# Called to tell things "Your now always in daemon mode,
|
30
|
-
# don't try to reenter it."
|
31
|
-
#
|
32
|
-
def self.perm_daemonize
|
33
|
-
ENV[PermKey] = "1"
|
34
|
-
end
|
35
|
-
|
36
|
-
def self.daemon?
|
37
|
-
ENV.key?(PermKey) || ENV.key?(RestartKey)
|
38
|
-
end
|
39
|
-
|
40
|
-
def self.daemon_init
|
41
|
-
return true if ENV.key?(PermKey)
|
42
|
-
|
43
|
-
return false unless ENV.key? RestartKey
|
44
|
-
|
45
|
-
master = ENV[RestartKey]
|
46
|
-
|
47
|
-
# In case the master disappears early
|
48
|
-
begin
|
49
|
-
Process.kill "SIGUSR2", master.to_i
|
50
|
-
rescue SystemCallError => e
|
51
|
-
end
|
52
|
-
|
53
|
-
ENV[RestartKey] = ""
|
54
|
-
|
55
|
-
setsid
|
56
|
-
|
57
|
-
null = File.open "/dev/null", "w+"
|
58
|
-
STDIN.reopen null
|
59
|
-
STDOUT.reopen null
|
60
|
-
STDERR.reopen null
|
61
|
-
|
62
|
-
true
|
63
|
-
end
|
64
|
-
|
65
|
-
def self.daemon_start(dir, argv)
|
66
|
-
ENV[RestartKey] = Process.pid.to_s
|
67
|
-
|
68
|
-
if k = ENV['PUMA_JRUBY_DAEMON_OPTS']
|
69
|
-
ENV['JRUBY_OPTS'] = k
|
70
|
-
end
|
71
|
-
|
72
|
-
cmd = argv.first
|
73
|
-
argv = ([:string] * argv.size).zip(argv).flatten
|
74
|
-
argv << :string
|
75
|
-
argv << nil
|
76
|
-
|
77
|
-
chdir(dir)
|
78
|
-
ret = fork
|
79
|
-
return ret if ret != 0
|
80
|
-
execlp(cmd, *argv)
|
81
|
-
raise SystemCallError.new(FFI.errno)
|
82
|
-
end
|
83
25
|
end
|
84
26
|
end
|
@@ -0,0 +1,96 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require 'stringio'
|
3
|
+
|
4
|
+
module Puma
|
5
|
+
|
6
|
+
# Puma deliberately avoids the use of the json gem and instead performs JSON
|
7
|
+
# serialization without any external dependencies. In a puma cluster, loading
|
8
|
+
# any gem into the puma master process means that operators cannot use a
|
9
|
+
# phased restart to upgrade their application if the new version of that
|
10
|
+
# application uses a different version of that gem. The json gem in
|
11
|
+
# particular is additionally problematic because it leverages native
|
12
|
+
# extensions. If the puma master process relies on a gem with native
|
13
|
+
# extensions and operators remove gems from disk related to old releases,
|
14
|
+
# subsequent phased restarts can fail.
|
15
|
+
#
|
16
|
+
# The implementation of JSON serialization in this module is not designed to
|
17
|
+
# be particularly full-featured or fast. It just has to handle the few places
|
18
|
+
# where Puma relies on JSON serialization internally.
|
19
|
+
|
20
|
+
module JSONSerialization
|
21
|
+
QUOTE = /"/
|
22
|
+
BACKSLASH = /\\/
|
23
|
+
CONTROL_CHAR_TO_ESCAPE = /[\x00-\x1F]/ # As required by ECMA-404
|
24
|
+
CHAR_TO_ESCAPE = Regexp.union QUOTE, BACKSLASH, CONTROL_CHAR_TO_ESCAPE
|
25
|
+
|
26
|
+
class SerializationError < StandardError; end
|
27
|
+
|
28
|
+
class << self
|
29
|
+
def generate(value)
|
30
|
+
StringIO.open do |io|
|
31
|
+
serialize_value io, value
|
32
|
+
io.string
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
def serialize_value(output, value)
|
39
|
+
case value
|
40
|
+
when Hash
|
41
|
+
output << '{'
|
42
|
+
value.each_with_index do |(k, v), index|
|
43
|
+
output << ',' if index != 0
|
44
|
+
serialize_object_key output, k
|
45
|
+
output << ':'
|
46
|
+
serialize_value output, v
|
47
|
+
end
|
48
|
+
output << '}'
|
49
|
+
when Array
|
50
|
+
output << '['
|
51
|
+
value.each_with_index do |member, index|
|
52
|
+
output << ',' if index != 0
|
53
|
+
serialize_value output, member
|
54
|
+
end
|
55
|
+
output << ']'
|
56
|
+
when Integer, Float
|
57
|
+
output << value.to_s
|
58
|
+
when String
|
59
|
+
serialize_string output, value
|
60
|
+
when true
|
61
|
+
output << 'true'
|
62
|
+
when false
|
63
|
+
output << 'false'
|
64
|
+
when nil
|
65
|
+
output << 'null'
|
66
|
+
else
|
67
|
+
raise SerializationError, "Unexpected value of type #{value.class}"
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def serialize_string(output, value)
|
72
|
+
output << '"'
|
73
|
+
output << value.gsub(CHAR_TO_ESCAPE) do |character|
|
74
|
+
case character
|
75
|
+
when BACKSLASH
|
76
|
+
'\\\\'
|
77
|
+
when QUOTE
|
78
|
+
'\\"'
|
79
|
+
when CONTROL_CHAR_TO_ESCAPE
|
80
|
+
'\u%.4X' % character.ord
|
81
|
+
end
|
82
|
+
end
|
83
|
+
output << '"'
|
84
|
+
end
|
85
|
+
|
86
|
+
def serialize_object_key(output, value)
|
87
|
+
case value
|
88
|
+
when Symbol, String
|
89
|
+
serialize_string output, value.to_s
|
90
|
+
else
|
91
|
+
raise SerializationError, "Could not serialize object of type #{value.class} as object key"
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|