puma 6.4.3 → 6.6.1
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.
- checksums.yaml +4 -4
- data/History.md +159 -7
- data/README.md +86 -26
- data/docs/fork_worker.md +11 -1
- data/docs/java_options.md +54 -0
- data/docs/plugins.md +4 -0
- data/docs/signals.md +2 -2
- data/docs/stats.md +8 -3
- data/docs/systemd.md +11 -2
- data/ext/puma_http11/extconf.rb +19 -16
- data/ext/puma_http11/mini_ssl.c +11 -1
- data/ext/puma_http11/org/jruby/puma/Http11.java +29 -8
- data/ext/puma_http11/puma_http11.c +4 -1
- data/lib/puma/app/status.rb +1 -1
- data/lib/puma/binder.rb +12 -4
- data/lib/puma/cli.rb +9 -5
- data/lib/puma/client.rb +78 -17
- data/lib/puma/cluster/worker.rb +9 -6
- data/lib/puma/cluster/worker_handle.rb +4 -5
- data/lib/puma/cluster.rb +55 -29
- data/lib/puma/configuration.rb +36 -18
- data/lib/puma/const.rb +14 -3
- data/lib/puma/control_cli.rb +4 -4
- data/lib/puma/dsl.rb +282 -50
- data/lib/puma/error_logger.rb +4 -4
- data/lib/puma/jruby_restart.rb +0 -16
- data/lib/puma/launcher.rb +21 -8
- data/lib/puma/log_writer.rb +9 -9
- data/lib/puma/minissl/context_builder.rb +1 -0
- data/lib/puma/minissl.rb +1 -0
- data/lib/puma/null_io.rb +26 -0
- data/lib/puma/request.rb +19 -11
- data/lib/puma/runner.rb +9 -2
- data/lib/puma/sd_notify.rb +1 -4
- data/lib/puma/server.rb +33 -22
- data/lib/puma/single.rb +1 -1
- data/lib/puma/thread_pool.rb +14 -2
- data/lib/puma/util.rb +1 -1
- data/lib/rack/handler/puma.rb +8 -5
- data/tools/Dockerfile +3 -1
- metadata +10 -12
data/ext/puma_http11/extconf.rb
CHANGED
@@ -10,15 +10,13 @@ end
|
|
10
10
|
|
11
11
|
unless ENV["PUMA_DISABLE_SSL"]
|
12
12
|
# don't use pkg_config('openssl') if '--with-openssl-dir' is used
|
13
|
-
# also looks within the Ruby build for directory info
|
14
13
|
has_openssl_dir = dir_config('openssl').any? ||
|
15
|
-
RbConfig::CONFIG['configure_args']&.include?('openssl')
|
16
|
-
Dir.exist?("#{RbConfig::TOPDIR}/src/main/c/openssl") # TruffleRuby
|
14
|
+
RbConfig::CONFIG['configure_args']&.include?('openssl')
|
17
15
|
|
18
16
|
found_pkg_config = !has_openssl_dir && pkg_config('openssl')
|
19
17
|
|
20
18
|
found_ssl = if !$mingw && found_pkg_config
|
21
|
-
puts '
|
19
|
+
puts '──── Using OpenSSL pkgconfig (openssl.pc) ────'
|
22
20
|
true
|
23
21
|
elsif have_library('libcrypto', 'BIO_read') && have_library('libssl', 'SSL_CTX_new')
|
24
22
|
true
|
@@ -33,22 +31,27 @@ unless ENV["PUMA_DISABLE_SSL"]
|
|
33
31
|
if found_ssl
|
34
32
|
have_header "openssl/bio.h"
|
35
33
|
|
36
|
-
|
37
|
-
have_func "DTLS_method" , "openssl/ssl.h"
|
38
|
-
have_func "SSL_CTX_set_session_cache_mode(NULL, 0)", "openssl/ssl.h"
|
34
|
+
ssl_h = "openssl/ssl.h".freeze
|
39
35
|
|
40
|
-
|
41
|
-
have_func "
|
42
|
-
have_func "
|
36
|
+
puts "\n──── Below are yes for 1.0.2 & later ────"
|
37
|
+
have_func "DTLS_method" , ssl_h
|
38
|
+
have_func "SSL_CTX_set_session_cache_mode(NULL, 0)", ssl_h
|
43
39
|
|
44
|
-
|
45
|
-
have_func "
|
40
|
+
puts "\n──── Below are yes for 1.1.0 & later ────"
|
41
|
+
have_func "TLS_server_method" , ssl_h
|
42
|
+
have_func "SSL_CTX_set_min_proto_version(NULL, 0)" , ssl_h
|
46
43
|
|
47
|
-
|
48
|
-
|
44
|
+
puts "\n──── Below is yes for 1.1.0 and later, but isn't documented until 3.0.0 ────"
|
45
|
+
# https://github.com/openssl/openssl/blob/OpenSSL_1_1_0/include/openssl/ssl.h#L1159
|
46
|
+
have_func "SSL_CTX_set_dh_auto(NULL, 0)" , ssl_h
|
49
47
|
|
50
|
-
|
51
|
-
have_func "
|
48
|
+
puts "\n──── Below is yes for 1.1.1 & later ────"
|
49
|
+
have_func "SSL_CTX_set_ciphersuites(NULL, \"\")" , ssl_h
|
50
|
+
|
51
|
+
puts "\n──── Below is yes for 3.0.0 & later ────"
|
52
|
+
have_func "SSL_get1_peer_certificate" , ssl_h
|
53
|
+
|
54
|
+
puts ''
|
52
55
|
|
53
56
|
# Random.bytes available in Ruby 2.5 and later, Random::DEFAULT deprecated in 3.0
|
54
57
|
if Random.respond_to?(:bytes)
|
data/ext/puma_http11/mini_ssl.c
CHANGED
@@ -229,7 +229,7 @@ VALUE
|
|
229
229
|
sslctx_initialize(VALUE self, VALUE mini_ssl_ctx) {
|
230
230
|
SSL_CTX* ctx;
|
231
231
|
int ssl_options;
|
232
|
-
VALUE key, cert, ca, verify_mode, ssl_cipher_filter, no_tlsv1, no_tlsv1_1,
|
232
|
+
VALUE key, cert, ca, verify_mode, ssl_cipher_filter, ssl_ciphersuites, no_tlsv1, no_tlsv1_1,
|
233
233
|
verification_flags, session_id_bytes, cert_pem, key_pem, key_password_command, key_password;
|
234
234
|
BIO *bio;
|
235
235
|
X509 *x509 = NULL;
|
@@ -269,6 +269,8 @@ sslctx_initialize(VALUE self, VALUE mini_ssl_ctx) {
|
|
269
269
|
|
270
270
|
ssl_cipher_filter = rb_funcall(mini_ssl_ctx, rb_intern_const("ssl_cipher_filter"), 0);
|
271
271
|
|
272
|
+
ssl_ciphersuites = rb_funcall(mini_ssl_ctx, rb_intern_const("ssl_ciphersuites"), 0);
|
273
|
+
|
272
274
|
no_tlsv1 = rb_funcall(mini_ssl_ctx, rb_intern_const("no_tlsv1"), 0);
|
273
275
|
|
274
276
|
no_tlsv1_1 = rb_funcall(mini_ssl_ctx, rb_intern_const("no_tlsv1_1"), 0);
|
@@ -444,6 +446,14 @@ sslctx_initialize(VALUE self, VALUE mini_ssl_ctx) {
|
|
444
446
|
SSL_CTX_set_cipher_list(ctx, "HIGH:!aNULL@STRENGTH");
|
445
447
|
}
|
446
448
|
|
449
|
+
#if HAVE_SSL_CTX_SET_CIPHERSUITES
|
450
|
+
// Only override OpenSSL default ciphersuites if config option is supplied.
|
451
|
+
if (!NIL_P(ssl_ciphersuites)) {
|
452
|
+
StringValue(ssl_ciphersuites);
|
453
|
+
SSL_CTX_set_ciphersuites(ctx, RSTRING_PTR(ssl_ciphersuites));
|
454
|
+
}
|
455
|
+
#endif
|
456
|
+
|
447
457
|
#if OPENSSL_VERSION_NUMBER < 0x10002000L
|
448
458
|
// Remove this case if OpenSSL 1.0.1 (now EOL) support is no longer needed.
|
449
459
|
ecdh = EC_KEY_new_by_curve_name(NID_X9_62_prime256v1);
|
@@ -26,14 +26,14 @@ public class Http11 extends RubyObject {
|
|
26
26
|
public final static String MAX_FIELD_NAME_LENGTH_ERR = "HTTP element FIELD_NAME is longer than the 256 allowed length.";
|
27
27
|
public final static int MAX_FIELD_VALUE_LENGTH = 80 * 1024;
|
28
28
|
public final static String MAX_FIELD_VALUE_LENGTH_ERR = "HTTP element FIELD_VALUE is longer than the 81920 allowed length.";
|
29
|
-
public final static int MAX_REQUEST_URI_LENGTH = 1024 * 12;
|
30
|
-
public final static String MAX_REQUEST_URI_LENGTH_ERR = "HTTP element REQUEST_URI is longer than the
|
29
|
+
public final static int MAX_REQUEST_URI_LENGTH = getConstLength("PUMA_REQUEST_URI_MAX_LENGTH", 1024 * 12);
|
30
|
+
public final static String MAX_REQUEST_URI_LENGTH_ERR = "HTTP element REQUEST_URI is longer than the " + MAX_REQUEST_URI_LENGTH + " allowed length.";
|
31
31
|
public final static int MAX_FRAGMENT_LENGTH = 1024;
|
32
|
-
public final static String MAX_FRAGMENT_LENGTH_ERR = "HTTP element
|
33
|
-
public final static int MAX_REQUEST_PATH_LENGTH = 8192;
|
34
|
-
public final static String MAX_REQUEST_PATH_LENGTH_ERR = "HTTP element REQUEST_PATH is longer than the
|
35
|
-
public final static int MAX_QUERY_STRING_LENGTH =
|
36
|
-
public final static String MAX_QUERY_STRING_LENGTH_ERR = "HTTP element QUERY_STRING is longer than the
|
32
|
+
public final static String MAX_FRAGMENT_LENGTH_ERR = "HTTP element FRAGMENT is longer than the 1024 allowed length.";
|
33
|
+
public final static int MAX_REQUEST_PATH_LENGTH = getConstLength("PUMA_REQUEST_PATH_MAX_LENGTH", 8192);
|
34
|
+
public final static String MAX_REQUEST_PATH_LENGTH_ERR = "HTTP element REQUEST_PATH is longer than the " + MAX_REQUEST_PATH_LENGTH + " allowed length.";
|
35
|
+
public final static int MAX_QUERY_STRING_LENGTH = getConstLength("PUMA_QUERY_STRING_MAX_LENGTH", 10 * 1024);
|
36
|
+
public final static String MAX_QUERY_STRING_LENGTH_ERR = "HTTP element QUERY_STRING is longer than the " + MAX_QUERY_STRING_LENGTH +" allowed length.";
|
37
37
|
public final static int MAX_HEADER_LENGTH = 1024 * (80 + 32);
|
38
38
|
public final static String MAX_HEADER_LENGTH_ERR = "HTTP element HEADER is longer than the 114688 allowed length.";
|
39
39
|
|
@@ -48,6 +48,27 @@ public class Http11 extends RubyObject {
|
|
48
48
|
public static final ByteList QUERY_STRING_BYTELIST = new ByteList(ByteList.plain("QUERY_STRING"));
|
49
49
|
public static final ByteList SERVER_PROTOCOL_BYTELIST = new ByteList(ByteList.plain("SERVER_PROTOCOL"));
|
50
50
|
|
51
|
+
public static String getEnvOrProperty(String name) {
|
52
|
+
String envValue = System.getenv(name);
|
53
|
+
return (envValue != null) ? envValue : System.getProperty(name);
|
54
|
+
}
|
55
|
+
|
56
|
+
public static int getConstLength(String name, Integer defaultValue) {
|
57
|
+
String stringValue = getEnvOrProperty(name);
|
58
|
+
if (stringValue == null || stringValue.isEmpty()) return defaultValue;
|
59
|
+
|
60
|
+
try {
|
61
|
+
int value = Integer.parseUnsignedInt(stringValue);
|
62
|
+
if (value <= 0) {
|
63
|
+
throw new NumberFormatException("The number is not positive.");
|
64
|
+
}
|
65
|
+
return value;
|
66
|
+
} catch (NumberFormatException e) {
|
67
|
+
System.err.println(String.format("The value %s for %s is invalid. Using default value %d instead.", stringValue, name, defaultValue));
|
68
|
+
return defaultValue;
|
69
|
+
}
|
70
|
+
}
|
71
|
+
|
51
72
|
private static ObjectAllocator ALLOCATOR = new ObjectAllocator() {
|
52
73
|
public IRubyObject allocate(Ruby runtime, RubyClass klass) {
|
53
74
|
return new Http11(runtime, klass);
|
@@ -56,7 +77,7 @@ public class Http11 extends RubyObject {
|
|
56
77
|
|
57
78
|
public static void createHttp11(Ruby runtime) {
|
58
79
|
RubyModule mPuma = runtime.defineModule("Puma");
|
59
|
-
mPuma.defineClassUnder("HttpParserError",runtime.getClass("
|
80
|
+
mPuma.defineClassUnder("HttpParserError",runtime.getClass("StandardError"),runtime.getClass("StandardError").getAllocator());
|
60
81
|
|
61
82
|
RubyClass cHttpParser = mPuma.defineClassUnder("HttpParser",runtime.getObject(),ALLOCATOR);
|
62
83
|
cHttpParser.defineAnnotatedMethods(Http11.class);
|
@@ -461,6 +461,9 @@ void Init_mini_ssl(VALUE mod);
|
|
461
461
|
|
462
462
|
void Init_puma_http11(void)
|
463
463
|
{
|
464
|
+
#ifdef HAVE_RB_EXT_RACTOR_SAFE
|
465
|
+
rb_ext_ractor_safe(true);
|
466
|
+
#endif
|
464
467
|
|
465
468
|
VALUE mPuma = rb_define_module("Puma");
|
466
469
|
VALUE cHttpParser = rb_define_class_under(mPuma, "HttpParser", rb_cObject);
|
@@ -472,7 +475,7 @@ void Init_puma_http11(void)
|
|
472
475
|
DEF_GLOBAL(server_protocol, "SERVER_PROTOCOL");
|
473
476
|
DEF_GLOBAL(request_path, "REQUEST_PATH");
|
474
477
|
|
475
|
-
eHttpParserError = rb_define_class_under(mPuma, "HttpParserError",
|
478
|
+
eHttpParserError = rb_define_class_under(mPuma, "HttpParserError", rb_eStandardError);
|
476
479
|
rb_global_variable(&eHttpParserError);
|
477
480
|
|
478
481
|
rb_define_alloc_func(cHttpParser, HttpParser_alloc);
|
data/lib/puma/app/status.rb
CHANGED
@@ -80,7 +80,7 @@ module Puma
|
|
80
80
|
|
81
81
|
def authenticate(env)
|
82
82
|
return true unless @auth_token
|
83
|
-
env['QUERY_STRING'].to_s.split(
|
83
|
+
env['QUERY_STRING'].to_s.split(/[&;]/).include? "token=#{@auth_token}"
|
84
84
|
end
|
85
85
|
|
86
86
|
def rack_response(status, body, content_type='application/json')
|
data/lib/puma/binder.rb
CHANGED
@@ -19,13 +19,14 @@ module Puma
|
|
19
19
|
|
20
20
|
RACK_VERSION = [1,6].freeze
|
21
21
|
|
22
|
-
def initialize(log_writer, conf = Configuration.new)
|
22
|
+
def initialize(log_writer, conf = Configuration.new, env: ENV)
|
23
23
|
@log_writer = log_writer
|
24
24
|
@conf = conf
|
25
25
|
@listeners = []
|
26
26
|
@inherited_fds = {}
|
27
27
|
@activated_sockets = {}
|
28
28
|
@unix_paths = []
|
29
|
+
@env = env
|
29
30
|
|
30
31
|
@proto_env = {
|
31
32
|
"rack.version".freeze => RACK_VERSION,
|
@@ -34,7 +35,7 @@ module Puma
|
|
34
35
|
"rack.multiprocess".freeze => conf.options[:workers] >= 1,
|
35
36
|
"rack.run_once".freeze => false,
|
36
37
|
RACK_URL_SCHEME => conf.options[:rack_url_scheme],
|
37
|
-
"SCRIPT_NAME".freeze =>
|
38
|
+
"SCRIPT_NAME".freeze => env['SCRIPT_NAME'] || "",
|
38
39
|
|
39
40
|
# I'd like to set a default CONTENT_TYPE here but some things
|
40
41
|
# depend on their not being a default set and inferring
|
@@ -87,7 +88,7 @@ module Puma
|
|
87
88
|
# @version 5.0.0
|
88
89
|
#
|
89
90
|
def create_activated_fds(env_hash)
|
90
|
-
@log_writer.debug "ENV['LISTEN_FDS'] #{
|
91
|
+
@log_writer.debug "ENV['LISTEN_FDS'] #{@env['LISTEN_FDS'].inspect} env_hash['LISTEN_PID'] #{env_hash['LISTEN_PID'].inspect}"
|
91
92
|
return [] unless env_hash['LISTEN_FDS'] && env_hash['LISTEN_PID'].to_i == $$
|
92
93
|
env_hash['LISTEN_FDS'].to_i.times do |index|
|
93
94
|
sock = TCPServer.for_fd(socket_activation_fd(index))
|
@@ -141,7 +142,14 @@ module Puma
|
|
141
142
|
end
|
142
143
|
end
|
143
144
|
|
145
|
+
def before_parse(&block)
|
146
|
+
@before_parse ||= []
|
147
|
+
@before_parse << block if block
|
148
|
+
@before_parse
|
149
|
+
end
|
150
|
+
|
144
151
|
def parse(binds, log_writer = nil, log_msg = 'Listening')
|
152
|
+
before_parse.each(&:call)
|
145
153
|
log_writer ||= @log_writer
|
146
154
|
binds.each do |str|
|
147
155
|
uri = URI.parse str
|
@@ -183,7 +191,7 @@ module Puma
|
|
183
191
|
io = inherit_unix_listener path, fd
|
184
192
|
log_writer.log "* Inherited #{str}"
|
185
193
|
elsif sock = @activated_sockets.delete([ :unix, path ]) ||
|
186
|
-
@activated_sockets.delete([ :unix, File.realdirpath(path) ])
|
194
|
+
!abstract && @activated_sockets.delete([ :unix, File.realdirpath(path) ])
|
187
195
|
@unix_paths << path unless abstract || File.exist?(path)
|
188
196
|
io = inherit_unix_listener path, sock
|
189
197
|
log_writer.log "* Activated #{str}"
|
data/lib/puma/cli.rb
CHANGED
@@ -24,7 +24,7 @@ module Puma
|
|
24
24
|
# Create a new CLI object using +argv+ as the command line
|
25
25
|
# arguments.
|
26
26
|
#
|
27
|
-
def initialize(argv, log_writer = LogWriter.stdio, events = Events.new)
|
27
|
+
def initialize(argv, log_writer = LogWriter.stdio, events = Events.new, env: ENV)
|
28
28
|
@debug = false
|
29
29
|
@argv = argv.dup
|
30
30
|
@log_writer = log_writer
|
@@ -39,7 +39,7 @@ module Puma
|
|
39
39
|
@control_url = nil
|
40
40
|
@control_options = {}
|
41
41
|
|
42
|
-
setup_options
|
42
|
+
setup_options env
|
43
43
|
|
44
44
|
begin
|
45
45
|
@parser.parse! @argv
|
@@ -63,7 +63,7 @@ module Puma
|
|
63
63
|
end
|
64
64
|
end
|
65
65
|
|
66
|
-
@launcher = Puma::Launcher.new(@conf, :log_writer
|
66
|
+
@launcher = Puma::Launcher.new(@conf, env: ENV, log_writer: @log_writer, events: @events, argv: argv)
|
67
67
|
end
|
68
68
|
|
69
69
|
attr_reader :launcher
|
@@ -92,8 +92,8 @@ module Puma
|
|
92
92
|
# Build the OptionParser object to handle the available options.
|
93
93
|
#
|
94
94
|
|
95
|
-
def setup_options
|
96
|
-
@conf = Configuration.new({}, {events: @events}) do |user_config, file_config|
|
95
|
+
def setup_options(env = ENV)
|
96
|
+
@conf = Configuration.new({}, {events: @events}, env) do |user_config, file_config|
|
97
97
|
@parser = OptionParser.new do |o|
|
98
98
|
o.on "-b", "--bind URI", "URI to bind to (tcp://, unix://, ssl://)" do |arg|
|
99
99
|
user_config.bind arg
|
@@ -157,6 +157,10 @@ module Puma
|
|
157
157
|
user_config.pidfile arg
|
158
158
|
end
|
159
159
|
|
160
|
+
o.on "--plugin PLUGIN", "Load the given PLUGIN. Can be used multiple times to load multiple plugins." do |arg|
|
161
|
+
user_config.plugin arg
|
162
|
+
end
|
163
|
+
|
160
164
|
o.on "--preload", "Preload the app. Cluster mode only" do
|
161
165
|
user_config.preload_app!
|
162
166
|
end
|
data/lib/puma/client.rb
CHANGED
@@ -160,8 +160,6 @@ module Puma
|
|
160
160
|
@read_header = true
|
161
161
|
@read_proxy = !!@expect_proxy_proto
|
162
162
|
@env = @proto_env.dup
|
163
|
-
@body = nil
|
164
|
-
@tempfile = nil
|
165
163
|
@parsed_bytes = 0
|
166
164
|
@ready = false
|
167
165
|
@body_remain = 0
|
@@ -172,7 +170,7 @@ module Puma
|
|
172
170
|
if @buffer
|
173
171
|
return false unless try_to_parse_proxy_protocol
|
174
172
|
|
175
|
-
@parsed_bytes =
|
173
|
+
@parsed_bytes = parser_execute
|
176
174
|
|
177
175
|
if @parser.finished?
|
178
176
|
return setup_body
|
@@ -190,11 +188,11 @@ module Puma
|
|
190
188
|
rescue IOError
|
191
189
|
# swallow it
|
192
190
|
end
|
193
|
-
|
194
191
|
end
|
195
192
|
end
|
196
193
|
|
197
194
|
def close
|
195
|
+
tempfile_close
|
198
196
|
begin
|
199
197
|
@io.close
|
200
198
|
rescue IOError, Errno::EBADF
|
@@ -202,6 +200,15 @@ module Puma
|
|
202
200
|
end
|
203
201
|
end
|
204
202
|
|
203
|
+
def tempfile_close
|
204
|
+
tf_path = @tempfile&.path
|
205
|
+
@tempfile&.close
|
206
|
+
File.unlink(tf_path) if tf_path
|
207
|
+
@tempfile = nil
|
208
|
+
@body = nil
|
209
|
+
rescue Errno::ENOENT, IOError
|
210
|
+
end
|
211
|
+
|
205
212
|
# If necessary, read the PROXY protocol from the buffer. Returns
|
206
213
|
# false if more data is needed.
|
207
214
|
def try_to_parse_proxy_protocol
|
@@ -240,6 +247,7 @@ module Puma
|
|
240
247
|
|
241
248
|
return read_body if in_data_phase
|
242
249
|
|
250
|
+
data = nil
|
243
251
|
begin
|
244
252
|
data = @io.read_nonblock(CHUNK_SIZE)
|
245
253
|
rescue IO::WaitReadable
|
@@ -265,20 +273,20 @@ module Puma
|
|
265
273
|
|
266
274
|
return false unless try_to_parse_proxy_protocol
|
267
275
|
|
268
|
-
@parsed_bytes =
|
276
|
+
@parsed_bytes = parser_execute
|
269
277
|
|
270
278
|
if @parser.finished? && above_http_content_limit(@parser.body.bytesize)
|
271
279
|
@http_content_length_limit_exceeded = true
|
272
280
|
end
|
273
281
|
|
274
282
|
if @parser.finished?
|
275
|
-
|
283
|
+
setup_body
|
276
284
|
elsif @parsed_bytes >= MAX_HEADER
|
277
285
|
raise HttpParserError,
|
278
286
|
"HEADER is longer than allowed, aborting client early."
|
287
|
+
else
|
288
|
+
false
|
279
289
|
end
|
280
|
-
|
281
|
-
false
|
282
290
|
end
|
283
291
|
|
284
292
|
def eagerly_finish
|
@@ -292,6 +300,44 @@ module Puma
|
|
292
300
|
@to_io.wait_readable(timeout) || timeout! until try_to_finish
|
293
301
|
end
|
294
302
|
|
303
|
+
# Wraps `@parser.execute` and adds meaningful error messages
|
304
|
+
# @return [Integer] bytes of buffer read by parser
|
305
|
+
#
|
306
|
+
def parser_execute
|
307
|
+
@parser.execute(@env, @buffer, @parsed_bytes)
|
308
|
+
rescue => e
|
309
|
+
@env[HTTP_CONNECTION] = 'close'
|
310
|
+
raise e unless HttpParserError === e && e.message.include?('non-SSL')
|
311
|
+
|
312
|
+
req, _ = @buffer.split "\r\n\r\n"
|
313
|
+
request_line, headers = req.split "\r\n", 2
|
314
|
+
|
315
|
+
# below checks for request issues and changes error message accordingly
|
316
|
+
if !@env.key? REQUEST_METHOD
|
317
|
+
if request_line.count(' ') != 2
|
318
|
+
# maybe this is an SSL connection ?
|
319
|
+
raise e
|
320
|
+
else
|
321
|
+
method = request_line[/\A[^ ]+/]
|
322
|
+
raise e, "Invalid HTTP format, parsing fails. Bad method #{method}"
|
323
|
+
end
|
324
|
+
elsif !@env.key? REQUEST_PATH
|
325
|
+
path = request_line[/\A[^ ]+ +([^ ?\r\n]+)/, 1]
|
326
|
+
raise e, "Invalid HTTP format, parsing fails. Bad path #{path}"
|
327
|
+
elsif request_line.match?(/\A[^ ]+ +[^ ?\r\n]+\?/) && !@env.key?(QUERY_STRING)
|
328
|
+
query = request_line[/\A[^ ]+ +[^? ]+\?([^ ]+)/, 1]
|
329
|
+
raise e, "Invalid HTTP format, parsing fails. Bad query #{query}"
|
330
|
+
elsif !@env.key? SERVER_PROTOCOL
|
331
|
+
# protocol is bad
|
332
|
+
text = request_line[/[^ ]*\z/]
|
333
|
+
raise HttpParserError, "Invalid HTTP format, parsing fails. Bad protocol #{text}"
|
334
|
+
elsif !headers.empty?
|
335
|
+
# headers are bad
|
336
|
+
hdrs = headers.split("\r\n").map { |h| h.gsub "\n", '\n'}.join "\n"
|
337
|
+
raise HttpParserError, "Invalid HTTP format, parsing fails. Bad headers\n#{hdrs}"
|
338
|
+
end
|
339
|
+
end
|
340
|
+
|
295
341
|
def timeout!
|
296
342
|
write_error(408) if in_data_phase
|
297
343
|
raise ConnectionError
|
@@ -403,18 +449,33 @@ module Puma
|
|
403
449
|
return true
|
404
450
|
end
|
405
451
|
|
406
|
-
|
452
|
+
content_length = cl.to_i
|
453
|
+
|
454
|
+
remain = content_length - body.bytesize
|
407
455
|
|
408
456
|
if remain <= 0
|
409
|
-
|
410
|
-
|
457
|
+
# Part of the body is a pipelined request OR garbage. We'll deal with that later.
|
458
|
+
if content_length == 0
|
459
|
+
@body = EmptyBody
|
460
|
+
if body.empty?
|
461
|
+
@buffer = nil
|
462
|
+
else
|
463
|
+
@buffer = body
|
464
|
+
end
|
465
|
+
elsif remain == 0
|
466
|
+
@body = StringIO.new body
|
467
|
+
@buffer = nil
|
468
|
+
else
|
469
|
+
@body = StringIO.new(body[0,content_length])
|
470
|
+
@buffer = body[content_length..-1]
|
471
|
+
end
|
411
472
|
set_ready
|
412
473
|
return true
|
413
474
|
end
|
414
475
|
|
415
476
|
if remain > MAX_BODY
|
416
|
-
@body = Tempfile.
|
417
|
-
@body.
|
477
|
+
@body = Tempfile.create(Const::PUMA_TMP_BASE)
|
478
|
+
File.unlink @body.path unless IS_WINDOWS
|
418
479
|
@body.binmode
|
419
480
|
@tempfile = @body
|
420
481
|
else
|
@@ -478,7 +539,7 @@ module Puma
|
|
478
539
|
def read_chunked_body
|
479
540
|
while true
|
480
541
|
begin
|
481
|
-
chunk = @io.read_nonblock(
|
542
|
+
chunk = @io.read_nonblock(CHUNK_SIZE, @read_buffer)
|
482
543
|
rescue IO::WaitReadable
|
483
544
|
return false
|
484
545
|
rescue SystemCallError, IOError
|
@@ -506,8 +567,8 @@ module Puma
|
|
506
567
|
@prev_chunk = ""
|
507
568
|
@excess_cr = 0
|
508
569
|
|
509
|
-
@body = Tempfile.
|
510
|
-
@body.
|
570
|
+
@body = Tempfile.create(Const::PUMA_TMP_BASE)
|
571
|
+
File.unlink @body.path unless IS_WINDOWS
|
511
572
|
@body.binmode
|
512
573
|
@tempfile = @body
|
513
574
|
@chunked_content_length = 0
|
@@ -627,7 +688,7 @@ module Puma
|
|
627
688
|
@partial_part_left = len - part.size
|
628
689
|
end
|
629
690
|
else
|
630
|
-
if @prev_chunk.size +
|
691
|
+
if @prev_chunk.size + line.size >= MAX_CHUNK_HEADER_SIZE
|
631
692
|
raise HttpParserError, "maximum size of chunk header exceeded"
|
632
693
|
end
|
633
694
|
|
data/lib/puma/cluster/worker.rb
CHANGED
@@ -88,25 +88,27 @@ module Puma
|
|
88
88
|
server.begin_restart(true)
|
89
89
|
@config.run_hooks(:before_refork, nil, @log_writer, @hook_data)
|
90
90
|
end
|
91
|
+
elsif idx == -2 # refork cycle is done
|
92
|
+
@config.run_hooks(:after_refork, nil, @log_writer, @hook_data)
|
91
93
|
elsif idx == 0 # restart server
|
92
94
|
restart_server << true << false
|
93
95
|
else # fork worker
|
94
96
|
worker_pids << pid = spawn_worker(idx)
|
95
|
-
@worker_write << "
|
97
|
+
@worker_write << "#{PIPE_FORK}#{pid}:#{idx}\n" rescue nil
|
96
98
|
end
|
97
99
|
end
|
98
100
|
end
|
99
101
|
end
|
100
102
|
|
101
103
|
Signal.trap "SIGTERM" do
|
102
|
-
@worker_write << "
|
104
|
+
@worker_write << "#{PIPE_EXTERNAL_TERM}#{Process.pid}\n" rescue nil
|
103
105
|
restart_server.clear
|
104
106
|
server.stop
|
105
107
|
restart_server << false
|
106
108
|
end
|
107
109
|
|
108
110
|
begin
|
109
|
-
@worker_write << "
|
111
|
+
@worker_write << "#{PIPE_BOOT}#{Process.pid}:#{index}\n"
|
110
112
|
rescue SystemCallError, IOError
|
111
113
|
Puma::Util.purge_interrupt_queue
|
112
114
|
STDERR.puts "Master seems to have exited, exiting."
|
@@ -122,7 +124,7 @@ module Puma
|
|
122
124
|
|
123
125
|
stat_thread ||= Thread.new(@worker_write) do |io|
|
124
126
|
Puma.set_thread_name "stat pld"
|
125
|
-
base_payload = "
|
127
|
+
base_payload = "#{PIPE_PING}#{Process.pid}"
|
126
128
|
|
127
129
|
while true
|
128
130
|
begin
|
@@ -131,7 +133,8 @@ module Puma
|
|
131
133
|
t = server.pool_capacity || 0
|
132
134
|
m = server.max_threads || 0
|
133
135
|
rc = server.requests_count || 0
|
134
|
-
|
136
|
+
bt = server.busy_threads || 0
|
137
|
+
payload = %Q!#{base_payload}{ "backlog":#{b}, "running":#{r}, "pool_capacity":#{t}, "max_threads":#{m}, "requests_count":#{rc}, "busy_threads":#{bt} }\n!
|
135
138
|
io << payload
|
136
139
|
rescue IOError
|
137
140
|
Puma::Util.purge_interrupt_queue
|
@@ -147,7 +150,7 @@ module Puma
|
|
147
150
|
# exiting until any background operations are completed
|
148
151
|
@config.run_hooks(:before_worker_shutdown, index, @log_writer, @hook_data)
|
149
152
|
ensure
|
150
|
-
@worker_write << "
|
153
|
+
@worker_write << "#{PIPE_TERM}#{Process.pid}\n" rescue nil
|
151
154
|
@worker_write.close
|
152
155
|
end
|
153
156
|
|
@@ -51,13 +51,12 @@ module Puma
|
|
51
51
|
@term
|
52
52
|
end
|
53
53
|
|
54
|
+
STATUS_PATTERN = /{ "backlog":(?<backlog>\d*), "running":(?<running>\d*), "pool_capacity":(?<pool_capacity>\d*), "max_threads":(?<max_threads>\d*), "requests_count":(?<requests_count>\d*), "busy_threads":(?<busy_threads>\d*) }/
|
55
|
+
private_constant :STATUS_PATTERN
|
56
|
+
|
54
57
|
def ping!(status)
|
55
58
|
@last_checkin = Time.now
|
56
|
-
|
57
|
-
@last_status = captures.names.inject({}) do |hash, key|
|
58
|
-
hash[key.to_sym] = captures[key].to_i
|
59
|
-
hash
|
60
|
-
end
|
59
|
+
@last_status = status.match(STATUS_PATTERN).named_captures.map { |c_name, c| [c_name.to_sym, c.to_i] }.to_h
|
61
60
|
end
|
62
61
|
|
63
62
|
# @see Puma::Cluster#check_workers
|