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.
@@ -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 'using OpenSSL pkgconfig (openssl.pc)'
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
- # below is yes for 1.0.2 & later
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
- # below are yes for 1.1.0 & later
41
- have_func "TLS_server_method" , "openssl/ssl.h"
42
- have_func "SSL_CTX_set_min_proto_version(NULL, 0)" , "openssl/ssl.h"
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
- have_func "X509_STORE_up_ref"
45
- have_func "SSL_CTX_set_ecdh_auto(NULL, 0)" , "openssl/ssl.h"
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
- # below exists in 1.1.0 and later, but isn't documented until 3.0.0
48
- have_func "SSL_CTX_set_dh_auto(NULL, 0)" , "openssl/ssl.h"
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
- # below is yes for 3.0.0 & later
51
- have_func "SSL_get1_peer_certificate" , "openssl/ssl.h"
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)
@@ -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 12288 allowed length.";
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 REQUEST_PATH is longer than the 1024 allowed length.";
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 8192 allowed length.";
35
- public final static int MAX_QUERY_STRING_LENGTH = 1024 * 10;
36
- public final static String MAX_QUERY_STRING_LENGTH_ERR = "HTTP element QUERY_STRING is longer than the 10240 allowed length.";
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("IOError"),runtime.getClass("IOError").getAllocator());
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", rb_eIOError);
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);
@@ -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('&;').include? "token=#{@auth_token}"
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 => ENV['SCRIPT_NAME'] || "",
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'] #{ENV['LISTEN_FDS'].inspect} env_hash['LISTEN_PID'] #{env_hash['LISTEN_PID'].inspect}"
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 => @log_writer, :events => @events, :argv => argv)
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 = @parser.execute(@env, @buffer, @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 = @parser.execute(@env, @buffer, @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
- return setup_body
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
- remain = cl.to_i - body.bytesize
452
+ content_length = cl.to_i
453
+
454
+ remain = content_length - body.bytesize
407
455
 
408
456
  if remain <= 0
409
- @body = StringIO.new(body)
410
- @buffer = nil
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.new(Const::PUMA_TMP_BASE)
417
- @body.unlink
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(4096, @read_buffer)
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.new(Const::PUMA_TMP_BASE)
510
- @body.unlink
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 + chunk.size >= MAX_CHUNK_HEADER_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
 
@@ -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 << "f#{pid}:#{idx}\n" rescue nil
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 << "e#{Process.pid}\n" rescue nil
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 << "b#{Process.pid}:#{index}\n"
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 = "p#{Process.pid}"
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
- payload = %Q!#{base_payload}{ "backlog":#{b}, "running":#{r}, "pool_capacity":#{t}, "max_threads": #{m}, "requests_count": #{rc} }\n!
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 << "t#{Process.pid}\n" rescue nil
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
- captures = status.match(/{ "backlog":(?<backlog>\d*), "running":(?<running>\d*), "pool_capacity":(?<pool_capacity>\d*), "max_threads": (?<max_threads>\d*), "requests_count": (?<requests_count>\d*) }/)
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