puma 6.6.0 → 7.1.0
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 +170 -5
- data/README.md +24 -32
- data/docs/fork_worker.md +5 -5
- data/docs/kubernetes.md +8 -6
- data/docs/restart.md +2 -2
- data/docs/signals.md +11 -11
- data/docs/stats.md +3 -2
- data/docs/systemd.md +1 -1
- data/ext/puma_http11/extconf.rb +2 -17
- data/ext/puma_http11/mini_ssl.c +18 -8
- data/ext/puma_http11/org/jruby/puma/Http11.java +10 -2
- data/ext/puma_http11/puma_http11.c +23 -11
- data/lib/puma/binder.rb +10 -8
- data/lib/puma/cli.rb +3 -5
- data/lib/puma/client.rb +95 -61
- data/lib/puma/cluster/worker.rb +9 -10
- data/lib/puma/cluster/worker_handle.rb +38 -7
- data/lib/puma/cluster.rb +41 -26
- data/lib/puma/cluster_accept_loop_delay.rb +91 -0
- data/lib/puma/commonlogger.rb +3 -3
- data/lib/puma/configuration.rb +89 -43
- data/lib/puma/const.rb +9 -10
- data/lib/puma/control_cli.rb +6 -2
- data/lib/puma/detect.rb +2 -0
- data/lib/puma/dsl.rb +135 -94
- data/lib/puma/error_logger.rb +3 -1
- data/lib/puma/events.rb +25 -10
- data/lib/puma/io_buffer.rb +8 -4
- data/lib/puma/launcher/bundle_pruner.rb +1 -1
- data/lib/puma/launcher.rb +52 -48
- data/lib/puma/minissl.rb +0 -1
- data/lib/puma/plugin/systemd.rb +3 -3
- data/lib/puma/rack/urlmap.rb +1 -1
- data/lib/puma/reactor.rb +19 -4
- data/lib/puma/request.rb +45 -32
- data/lib/puma/runner.rb +8 -17
- data/lib/puma/server.rb +111 -61
- data/lib/puma/single.rb +5 -2
- data/lib/puma/state_file.rb +3 -2
- data/lib/puma/thread_pool.rb +47 -82
- data/lib/puma/util.rb +0 -7
- data/lib/puma.rb +10 -0
- data/lib/rack/handler/puma.rb +2 -2
- data/tools/Dockerfile +3 -1
- metadata +6 -4
|
@@ -29,7 +29,7 @@ public class Http11 extends RubyObject {
|
|
|
29
29
|
public final static int MAX_REQUEST_URI_LENGTH = getConstLength("PUMA_REQUEST_URI_MAX_LENGTH", 1024 * 12);
|
|
30
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
|
|
32
|
+
public final static String MAX_FRAGMENT_LENGTH_ERR = "HTTP element FRAGMENT is longer than the 1024 allowed length.";
|
|
33
33
|
public final static int MAX_REQUEST_PATH_LENGTH = getConstLength("PUMA_REQUEST_PATH_MAX_LENGTH", 8192);
|
|
34
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
35
|
public final static int MAX_QUERY_STRING_LENGTH = getConstLength("PUMA_QUERY_STRING_MAX_LENGTH", 10 * 1024);
|
|
@@ -109,6 +109,10 @@ public class Http11 extends RubyObject {
|
|
|
109
109
|
return (RubyClass)runtime.getModule("Puma").getConstant("HttpParserError");
|
|
110
110
|
}
|
|
111
111
|
|
|
112
|
+
private static boolean is_ows(int c) {
|
|
113
|
+
return c == ' ' || c == '\t';
|
|
114
|
+
}
|
|
115
|
+
|
|
112
116
|
public static void http_field(Ruby runtime, RubyHash req, ByteList buffer, int field, int flen, int value, int vlen) {
|
|
113
117
|
RubyString f;
|
|
114
118
|
IRubyObject v;
|
|
@@ -127,7 +131,11 @@ public class Http11 extends RubyObject {
|
|
|
127
131
|
}
|
|
128
132
|
}
|
|
129
133
|
|
|
130
|
-
while (vlen > 0 &&
|
|
134
|
+
while (vlen > 0 && is_ows(buffer.get(value + vlen - 1))) vlen--;
|
|
135
|
+
while (vlen > 0 && is_ows(buffer.get(value))) {
|
|
136
|
+
vlen--;
|
|
137
|
+
value++;
|
|
138
|
+
}
|
|
131
139
|
|
|
132
140
|
if (b.equals(CONTENT_LENGTH_BYTELIST) || b.equals(CONTENT_TYPE_BYTELIST)) {
|
|
133
141
|
f = RubyString.newString(runtime, b);
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
#define RSTRING_NOT_MODIFIED 1
|
|
8
8
|
|
|
9
9
|
#include "ruby.h"
|
|
10
|
+
#include "ruby/encoding.h"
|
|
10
11
|
#include "ext_help.h"
|
|
11
12
|
#include <assert.h>
|
|
12
13
|
#include <string.h>
|
|
@@ -48,8 +49,11 @@ static VALUE global_request_path;
|
|
|
48
49
|
#define VALIDATE_MAX_LENGTH(len, N) if(len > MAX_##N##_LENGTH) { rb_raise(eHttpParserError, MAX_##N##_LENGTH_ERR, len); }
|
|
49
50
|
|
|
50
51
|
/** Defines global strings in the init method. */
|
|
51
|
-
|
|
52
|
-
|
|
52
|
+
static inline void DEF_GLOBAL(VALUE *var, const char *cstr)
|
|
53
|
+
{
|
|
54
|
+
rb_global_variable(var);
|
|
55
|
+
*var = rb_enc_interned_str_cstr(cstr, rb_utf8_encoding());
|
|
56
|
+
}
|
|
53
57
|
|
|
54
58
|
/* Defines the maximum allowed lengths for various input elements.*/
|
|
55
59
|
#ifndef PUMA_REQUEST_URI_MAX_LENGTH
|
|
@@ -134,13 +138,13 @@ static void init_common_fields(void)
|
|
|
134
138
|
memcpy(tmp, HTTP_PREFIX, HTTP_PREFIX_LEN);
|
|
135
139
|
|
|
136
140
|
for(i = 0; i < ARRAY_SIZE(common_http_fields); cf++, i++) {
|
|
141
|
+
rb_global_variable(&cf->value);
|
|
137
142
|
if(cf->raw) {
|
|
138
143
|
cf->value = rb_str_new(cf->name, cf->len);
|
|
139
144
|
} else {
|
|
140
145
|
memcpy(tmp + HTTP_PREFIX_LEN, cf->name, cf->len + 1);
|
|
141
146
|
cf->value = rb_str_new(tmp, HTTP_PREFIX_LEN + cf->len);
|
|
142
147
|
}
|
|
143
|
-
rb_global_variable(&cf->value);
|
|
144
148
|
}
|
|
145
149
|
}
|
|
146
150
|
|
|
@@ -155,6 +159,10 @@ static VALUE find_common_field_value(const char *field, size_t flen)
|
|
|
155
159
|
return Qnil;
|
|
156
160
|
}
|
|
157
161
|
|
|
162
|
+
static int is_ows(const char c) {
|
|
163
|
+
return c == ' ' || c == '\t';
|
|
164
|
+
}
|
|
165
|
+
|
|
158
166
|
void http_field(puma_parser* hp, const char *field, size_t flen,
|
|
159
167
|
const char *value, size_t vlen)
|
|
160
168
|
{
|
|
@@ -181,7 +189,11 @@ void http_field(puma_parser* hp, const char *field, size_t flen,
|
|
|
181
189
|
f = rb_str_new(hp->buf, new_size);
|
|
182
190
|
}
|
|
183
191
|
|
|
184
|
-
while (vlen > 0 &&
|
|
192
|
+
while (vlen > 0 && is_ows(value[vlen - 1])) vlen--;
|
|
193
|
+
while (vlen > 0 && is_ows(value[0])) {
|
|
194
|
+
vlen--;
|
|
195
|
+
value++;
|
|
196
|
+
}
|
|
185
197
|
|
|
186
198
|
/* check for duplicate header */
|
|
187
199
|
v = rb_hash_aref(hp->request, f);
|
|
@@ -468,15 +480,15 @@ void Init_puma_http11(void)
|
|
|
468
480
|
VALUE mPuma = rb_define_module("Puma");
|
|
469
481
|
VALUE cHttpParser = rb_define_class_under(mPuma, "HttpParser", rb_cObject);
|
|
470
482
|
|
|
471
|
-
DEF_GLOBAL(
|
|
472
|
-
DEF_GLOBAL(
|
|
473
|
-
DEF_GLOBAL(
|
|
474
|
-
DEF_GLOBAL(
|
|
475
|
-
DEF_GLOBAL(
|
|
476
|
-
DEF_GLOBAL(
|
|
483
|
+
DEF_GLOBAL(&global_request_method, "REQUEST_METHOD");
|
|
484
|
+
DEF_GLOBAL(&global_request_uri, "REQUEST_URI");
|
|
485
|
+
DEF_GLOBAL(&global_fragment, "FRAGMENT");
|
|
486
|
+
DEF_GLOBAL(&global_query_string, "QUERY_STRING");
|
|
487
|
+
DEF_GLOBAL(&global_server_protocol, "SERVER_PROTOCOL");
|
|
488
|
+
DEF_GLOBAL(&global_request_path, "REQUEST_PATH");
|
|
477
489
|
|
|
478
|
-
eHttpParserError = rb_define_class_under(mPuma, "HttpParserError", rb_eStandardError);
|
|
479
490
|
rb_global_variable(&eHttpParserError);
|
|
491
|
+
eHttpParserError = rb_define_class_under(mPuma, "HttpParserError", rb_eStandardError);
|
|
480
492
|
|
|
481
493
|
rb_define_alloc_func(cHttpParser, HttpParser_alloc);
|
|
482
494
|
rb_define_method(cHttpParser, "initialize", HttpParser_init, 0);
|
data/lib/puma/binder.rb
CHANGED
|
@@ -5,7 +5,6 @@ require 'socket'
|
|
|
5
5
|
|
|
6
6
|
require_relative 'const'
|
|
7
7
|
require_relative 'util'
|
|
8
|
-
require_relative 'configuration'
|
|
9
8
|
|
|
10
9
|
module Puma
|
|
11
10
|
|
|
@@ -19,9 +18,9 @@ module Puma
|
|
|
19
18
|
|
|
20
19
|
RACK_VERSION = [1,6].freeze
|
|
21
20
|
|
|
22
|
-
def initialize(log_writer,
|
|
21
|
+
def initialize(log_writer, options, env: ENV)
|
|
23
22
|
@log_writer = log_writer
|
|
24
|
-
@
|
|
23
|
+
@options = options
|
|
25
24
|
@listeners = []
|
|
26
25
|
@inherited_fds = {}
|
|
27
26
|
@activated_sockets = {}
|
|
@@ -31,10 +30,10 @@ module Puma
|
|
|
31
30
|
@proto_env = {
|
|
32
31
|
"rack.version".freeze => RACK_VERSION,
|
|
33
32
|
"rack.errors".freeze => log_writer.stderr,
|
|
34
|
-
"rack.multithread".freeze =>
|
|
35
|
-
"rack.multiprocess".freeze =>
|
|
33
|
+
"rack.multithread".freeze => options[:max_threads] > 1,
|
|
34
|
+
"rack.multiprocess".freeze => options[:workers] >= 1,
|
|
36
35
|
"rack.run_once".freeze => false,
|
|
37
|
-
RACK_URL_SCHEME =>
|
|
36
|
+
RACK_URL_SCHEME => options[:rack_url_scheme],
|
|
38
37
|
"SCRIPT_NAME".freeze => env['SCRIPT_NAME'] || "",
|
|
39
38
|
|
|
40
39
|
# I'd like to set a default CONTENT_TYPE here but some things
|
|
@@ -44,7 +43,10 @@ module Puma
|
|
|
44
43
|
|
|
45
44
|
"QUERY_STRING".freeze => "",
|
|
46
45
|
SERVER_SOFTWARE => PUMA_SERVER_STRING,
|
|
47
|
-
GATEWAY_INTERFACE => CGI_VER
|
|
46
|
+
GATEWAY_INTERFACE => CGI_VER,
|
|
47
|
+
|
|
48
|
+
RACK_AFTER_REPLY => nil,
|
|
49
|
+
RACK_RESPONSE_FINISHED => nil,
|
|
48
50
|
}
|
|
49
51
|
|
|
50
52
|
@envs = {}
|
|
@@ -243,7 +245,7 @@ module Puma
|
|
|
243
245
|
cert_key.each do |v|
|
|
244
246
|
if params[v]&.start_with?('store:')
|
|
245
247
|
index = Integer(params.delete(v).split('store:').last)
|
|
246
|
-
params["#{v}_pem"] = @
|
|
248
|
+
params["#{v}_pem"] = @options[:store][index]
|
|
247
249
|
end
|
|
248
250
|
end
|
|
249
251
|
MiniSSL::ContextBuilder.new(params, @log_writer).context
|
data/lib/puma/cli.rb
CHANGED
|
@@ -39,10 +39,8 @@ module Puma
|
|
|
39
39
|
@control_url = nil
|
|
40
40
|
@control_options = {}
|
|
41
41
|
|
|
42
|
-
setup_options env
|
|
43
|
-
|
|
44
42
|
begin
|
|
45
|
-
|
|
43
|
+
setup_options env
|
|
46
44
|
|
|
47
45
|
if file = @argv.shift
|
|
48
46
|
@conf.configure do |user_config, file_config|
|
|
@@ -93,7 +91,7 @@ module Puma
|
|
|
93
91
|
#
|
|
94
92
|
|
|
95
93
|
def setup_options(env = ENV)
|
|
96
|
-
@conf = Configuration.new({}, {events: @events}, env) do |user_config, file_config|
|
|
94
|
+
@conf = Configuration.new({}, { events: @events }, env) do |user_config, file_config|
|
|
97
95
|
@parser = OptionParser.new do |o|
|
|
98
96
|
o.on "-b", "--bind URI", "URI to bind to (tcp://, unix://, ssl://)" do |arg|
|
|
99
97
|
user_config.bind arg
|
|
@@ -240,7 +238,7 @@ module Puma
|
|
|
240
238
|
$stdout.puts o
|
|
241
239
|
exit 0
|
|
242
240
|
end
|
|
243
|
-
end
|
|
241
|
+
end.parse! @argv
|
|
244
242
|
end
|
|
245
243
|
end
|
|
246
244
|
end
|
data/lib/puma/client.rb
CHANGED
|
@@ -1,13 +1,5 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
class IO
|
|
4
|
-
# We need to use this for a jruby work around on both 1.8 and 1.9.
|
|
5
|
-
# So this either creates the constant (on 1.8), or harmlessly
|
|
6
|
-
# reopens it (on 1.9).
|
|
7
|
-
module WaitReadable
|
|
8
|
-
end
|
|
9
|
-
end
|
|
10
|
-
|
|
11
3
|
require_relative 'detect'
|
|
12
4
|
require_relative 'io_buffer'
|
|
13
5
|
require 'tempfile'
|
|
@@ -64,6 +56,11 @@ module Puma
|
|
|
64
56
|
|
|
65
57
|
TE_ERR_MSG = 'Invalid Transfer-Encoding'
|
|
66
58
|
|
|
59
|
+
# See:
|
|
60
|
+
# https://httpwg.org/specs/rfc9110.html#rfc.section.5.6.1.1
|
|
61
|
+
# https://httpwg.org/specs/rfc9112.html#rfc.section.6.1
|
|
62
|
+
STRIP_OWS = /\A[ \t]+|[ \t]+\z/
|
|
63
|
+
|
|
67
64
|
# The object used for a request with no body. All requests with
|
|
68
65
|
# no body share this one object since it has no state.
|
|
69
66
|
EmptyBody = NullIO.new
|
|
@@ -111,7 +108,8 @@ module Puma
|
|
|
111
108
|
end
|
|
112
109
|
|
|
113
110
|
attr_reader :env, :to_io, :body, :io, :timeout_at, :ready, :hijacked,
|
|
114
|
-
:tempfile, :io_buffer, :http_content_length_limit_exceeded
|
|
111
|
+
:tempfile, :io_buffer, :http_content_length_limit_exceeded,
|
|
112
|
+
:requests_served
|
|
115
113
|
|
|
116
114
|
attr_writer :peerip, :http_content_length_limit
|
|
117
115
|
|
|
@@ -133,9 +131,9 @@ module Puma
|
|
|
133
131
|
"#<Puma::Client:0x#{object_id.to_s(16)} @ready=#{@ready.inspect}>"
|
|
134
132
|
end
|
|
135
133
|
|
|
136
|
-
# For the hijack protocol
|
|
137
|
-
#
|
|
138
|
-
def
|
|
134
|
+
# For the full hijack protocol, `env['rack.hijack']` is set to
|
|
135
|
+
# `client.method :full_hijack`
|
|
136
|
+
def full_hijack
|
|
139
137
|
@hijacked = true
|
|
140
138
|
env[HIJACK_IO] ||= @io
|
|
141
139
|
end
|
|
@@ -150,11 +148,12 @@ module Puma
|
|
|
150
148
|
end
|
|
151
149
|
|
|
152
150
|
# Number of seconds until the timeout elapses.
|
|
151
|
+
# @!attribute [r] timeout
|
|
153
152
|
def timeout
|
|
154
153
|
[@timeout_at - Process.clock_gettime(Process::CLOCK_MONOTONIC), 0].max
|
|
155
154
|
end
|
|
156
155
|
|
|
157
|
-
def reset
|
|
156
|
+
def reset
|
|
158
157
|
@parser.reset
|
|
159
158
|
@io_buffer.reset
|
|
160
159
|
@read_header = true
|
|
@@ -166,11 +165,14 @@ module Puma
|
|
|
166
165
|
@peerip = nil if @remote_addr_header
|
|
167
166
|
@in_last_chunk = false
|
|
168
167
|
@http_content_length_limit_exceeded = false
|
|
168
|
+
end
|
|
169
169
|
|
|
170
|
+
# only used with back-to-back requests contained in the buffer
|
|
171
|
+
def process_back_to_back_requests
|
|
170
172
|
if @buffer
|
|
171
173
|
return false unless try_to_parse_proxy_protocol
|
|
172
174
|
|
|
173
|
-
@parsed_bytes =
|
|
175
|
+
@parsed_bytes = parser_execute
|
|
174
176
|
|
|
175
177
|
if @parser.finished?
|
|
176
178
|
return setup_body
|
|
@@ -178,25 +180,20 @@ module Puma
|
|
|
178
180
|
raise HttpParserError,
|
|
179
181
|
"HEADER is longer than allowed, aborting client early."
|
|
180
182
|
end
|
|
181
|
-
|
|
182
|
-
return false
|
|
183
|
-
else
|
|
184
|
-
begin
|
|
185
|
-
if fast_check && @to_io.wait_readable(FAST_TRACK_KA_TIMEOUT)
|
|
186
|
-
return try_to_finish
|
|
187
|
-
end
|
|
188
|
-
rescue IOError
|
|
189
|
-
# swallow it
|
|
190
|
-
end
|
|
191
183
|
end
|
|
192
184
|
end
|
|
193
185
|
|
|
186
|
+
# if a client sends back-to-back requests, the buffer may contain one or more
|
|
187
|
+
# of them.
|
|
188
|
+
def has_back_to_back_requests?
|
|
189
|
+
!(@buffer.nil? || @buffer.empty?)
|
|
190
|
+
end
|
|
191
|
+
|
|
194
192
|
def close
|
|
195
193
|
tempfile_close
|
|
196
194
|
begin
|
|
197
195
|
@io.close
|
|
198
196
|
rescue IOError, Errno::EBADF
|
|
199
|
-
Puma::Util.purge_interrupt_queue
|
|
200
197
|
end
|
|
201
198
|
end
|
|
202
199
|
|
|
@@ -273,26 +270,28 @@ module Puma
|
|
|
273
270
|
|
|
274
271
|
return false unless try_to_parse_proxy_protocol
|
|
275
272
|
|
|
276
|
-
@parsed_bytes =
|
|
273
|
+
@parsed_bytes = parser_execute
|
|
277
274
|
|
|
278
275
|
if @parser.finished? && above_http_content_limit(@parser.body.bytesize)
|
|
279
276
|
@http_content_length_limit_exceeded = true
|
|
280
277
|
end
|
|
281
278
|
|
|
282
279
|
if @parser.finished?
|
|
283
|
-
|
|
280
|
+
setup_body
|
|
284
281
|
elsif @parsed_bytes >= MAX_HEADER
|
|
285
282
|
raise HttpParserError,
|
|
286
283
|
"HEADER is longer than allowed, aborting client early."
|
|
284
|
+
else
|
|
285
|
+
false
|
|
287
286
|
end
|
|
288
|
-
|
|
289
|
-
false
|
|
290
287
|
end
|
|
291
288
|
|
|
292
289
|
def eagerly_finish
|
|
293
290
|
return true if @ready
|
|
294
|
-
|
|
295
|
-
|
|
291
|
+
while @to_io.wait_readable(0) # rubocop: disable Style/WhileUntilModifier
|
|
292
|
+
return true if try_to_finish
|
|
293
|
+
end
|
|
294
|
+
false
|
|
296
295
|
end
|
|
297
296
|
|
|
298
297
|
def finish(timeout)
|
|
@@ -300,6 +299,44 @@ module Puma
|
|
|
300
299
|
@to_io.wait_readable(timeout) || timeout! until try_to_finish
|
|
301
300
|
end
|
|
302
301
|
|
|
302
|
+
# Wraps `@parser.execute` and adds meaningful error messages
|
|
303
|
+
# @return [Integer] bytes of buffer read by parser
|
|
304
|
+
#
|
|
305
|
+
def parser_execute
|
|
306
|
+
@parser.execute(@env, @buffer, @parsed_bytes)
|
|
307
|
+
rescue => e
|
|
308
|
+
@env[HTTP_CONNECTION] = 'close'
|
|
309
|
+
raise e unless HttpParserError === e && e.message.include?('non-SSL')
|
|
310
|
+
|
|
311
|
+
req, _ = @buffer.split "\r\n\r\n"
|
|
312
|
+
request_line, headers = req.split "\r\n", 2
|
|
313
|
+
|
|
314
|
+
# below checks for request issues and changes error message accordingly
|
|
315
|
+
if !@env.key? REQUEST_METHOD
|
|
316
|
+
if request_line.count(' ') != 2
|
|
317
|
+
# maybe this is an SSL connection ?
|
|
318
|
+
raise e
|
|
319
|
+
else
|
|
320
|
+
method = request_line[/\A[^ ]+/]
|
|
321
|
+
raise e, "Invalid HTTP format, parsing fails. Bad method #{method}"
|
|
322
|
+
end
|
|
323
|
+
elsif !@env.key? REQUEST_PATH
|
|
324
|
+
path = request_line[/\A[^ ]+ +([^ ?\r\n]+)/, 1]
|
|
325
|
+
raise e, "Invalid HTTP format, parsing fails. Bad path #{path}"
|
|
326
|
+
elsif request_line.match?(/\A[^ ]+ +[^ ?\r\n]+\?/) && !@env.key?(QUERY_STRING)
|
|
327
|
+
query = request_line[/\A[^ ]+ +[^? ]+\?([^ ]+)/, 1]
|
|
328
|
+
raise e, "Invalid HTTP format, parsing fails. Bad query #{query}"
|
|
329
|
+
elsif !@env.key? SERVER_PROTOCOL
|
|
330
|
+
# protocol is bad
|
|
331
|
+
text = request_line[/[^ ]*\z/]
|
|
332
|
+
raise HttpParserError, "Invalid HTTP format, parsing fails. Bad protocol #{text}"
|
|
333
|
+
elsif !headers.empty?
|
|
334
|
+
# headers are bad
|
|
335
|
+
hdrs = headers.split("\r\n").map { |h| h.gsub "\n", '\n'}.join "\n"
|
|
336
|
+
raise HttpParserError, "Invalid HTTP format, parsing fails. Bad headers\n#{hdrs}"
|
|
337
|
+
end
|
|
338
|
+
end
|
|
339
|
+
|
|
303
340
|
def timeout!
|
|
304
341
|
write_error(408) if in_data_phase
|
|
305
342
|
raise ConnectionError
|
|
@@ -374,17 +411,18 @@ module Puma
|
|
|
374
411
|
if te
|
|
375
412
|
te_lwr = te.downcase
|
|
376
413
|
if te.include? ','
|
|
377
|
-
te_ary = te_lwr.split
|
|
414
|
+
te_ary = te_lwr.split(',').each { |te| te.gsub!(STRIP_OWS, "") }
|
|
378
415
|
te_count = te_ary.count CHUNKED
|
|
379
416
|
te_valid = te_ary[0..-2].all? { |e| ALLOWED_TRANSFER_ENCODING.include? e }
|
|
380
|
-
if
|
|
381
|
-
@env.delete TRANSFER_ENCODING2
|
|
382
|
-
return setup_chunked_body body
|
|
383
|
-
elsif te_count >= 1
|
|
417
|
+
if te_count > 1
|
|
384
418
|
raise HttpParserError , "#{TE_ERR_MSG}, multiple chunked: '#{te}'"
|
|
419
|
+
elsif te_ary.last != CHUNKED
|
|
420
|
+
raise HttpParserError , "#{TE_ERR_MSG}, last value must be chunked: '#{te}'"
|
|
385
421
|
elsif !te_valid
|
|
386
422
|
raise HttpParserError501, "#{TE_ERR_MSG}, unknown value: '#{te}'"
|
|
387
423
|
end
|
|
424
|
+
@env.delete TRANSFER_ENCODING2
|
|
425
|
+
return setup_chunked_body body
|
|
388
426
|
elsif te_lwr == CHUNKED
|
|
389
427
|
@env.delete TRANSFER_ENCODING2
|
|
390
428
|
return setup_chunked_body body
|
|
@@ -462,40 +500,36 @@ module Puma
|
|
|
462
500
|
# after this
|
|
463
501
|
remain = @body_remain
|
|
464
502
|
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
503
|
+
# don't bother with reading zero bytes
|
|
504
|
+
unless remain.zero?
|
|
505
|
+
begin
|
|
506
|
+
chunk = @io.read_nonblock(remain.clamp(0, CHUNK_SIZE), @read_buffer)
|
|
507
|
+
rescue IO::WaitReadable
|
|
508
|
+
return false
|
|
509
|
+
rescue SystemCallError, IOError
|
|
510
|
+
raise ConnectionError, "Connection error detected during read"
|
|
511
|
+
end
|
|
470
512
|
|
|
471
|
-
|
|
472
|
-
chunk
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
513
|
+
# No chunk means a closed socket
|
|
514
|
+
unless chunk
|
|
515
|
+
@body.close
|
|
516
|
+
@buffer = nil
|
|
517
|
+
set_ready
|
|
518
|
+
raise EOFError
|
|
519
|
+
end
|
|
478
520
|
|
|
479
|
-
|
|
480
|
-
unless chunk
|
|
481
|
-
@body.close
|
|
482
|
-
@buffer = nil
|
|
483
|
-
set_ready
|
|
484
|
-
raise EOFError
|
|
521
|
+
remain -= @body.write(chunk)
|
|
485
522
|
end
|
|
486
523
|
|
|
487
|
-
remain -= @body.write(chunk)
|
|
488
|
-
|
|
489
524
|
if remain <= 0
|
|
490
525
|
@body.rewind
|
|
491
526
|
@buffer = nil
|
|
492
527
|
set_ready
|
|
493
|
-
|
|
528
|
+
true
|
|
529
|
+
else
|
|
530
|
+
@body_remain = remain
|
|
531
|
+
false
|
|
494
532
|
end
|
|
495
|
-
|
|
496
|
-
@body_remain = remain
|
|
497
|
-
|
|
498
|
-
false
|
|
499
533
|
end
|
|
500
534
|
|
|
501
535
|
def read_chunked_body
|
data/lib/puma/cluster/worker.rb
CHANGED
|
@@ -110,7 +110,6 @@ module Puma
|
|
|
110
110
|
begin
|
|
111
111
|
@worker_write << "#{PIPE_BOOT}#{Process.pid}:#{index}\n"
|
|
112
112
|
rescue SystemCallError, IOError
|
|
113
|
-
Puma::Util.purge_interrupt_queue
|
|
114
113
|
STDERR.puts "Master seems to have exited, exiting."
|
|
115
114
|
return
|
|
116
115
|
end
|
|
@@ -128,16 +127,16 @@ module Puma
|
|
|
128
127
|
|
|
129
128
|
while true
|
|
130
129
|
begin
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
io << payload
|
|
130
|
+
payload = base_payload.dup
|
|
131
|
+
|
|
132
|
+
hsh = server.stats
|
|
133
|
+
hsh.each do |k, v|
|
|
134
|
+
payload << %Q! "#{k}":#{v || 0},!
|
|
135
|
+
end
|
|
136
|
+
# sub call properly adds 'closing' string
|
|
137
|
+
io << payload.sub(/,\z/, " }\n")
|
|
138
|
+
server.reset_max
|
|
139
139
|
rescue IOError
|
|
140
|
-
Puma::Util.purge_interrupt_queue
|
|
141
140
|
break
|
|
142
141
|
end
|
|
143
142
|
sleep @options[:worker_check_interval]
|
|
@@ -4,13 +4,15 @@ module Puma
|
|
|
4
4
|
class Cluster < Runner
|
|
5
5
|
#—————————————————————— DO NOT USE — this class is for internal use only ———
|
|
6
6
|
|
|
7
|
-
|
|
8
7
|
# This class represents a worker process from the perspective of the puma
|
|
9
8
|
# master process. It contains information about the process and its health
|
|
10
9
|
# and it exposes methods to control the process via IPC. It does not
|
|
11
10
|
# include the actual logic executed by the worker process itself. For that,
|
|
12
11
|
# see Puma::Cluster::Worker.
|
|
13
12
|
class WorkerHandle # :nodoc:
|
|
13
|
+
# array of stat 'max' keys
|
|
14
|
+
WORKER_MAX_KEYS = [:backlog_max, :reactor_max]
|
|
15
|
+
|
|
14
16
|
def initialize(idx, pid, phase, options)
|
|
15
17
|
@index = idx
|
|
16
18
|
@pid = pid
|
|
@@ -23,12 +25,13 @@ module Puma
|
|
|
23
25
|
@last_checkin = Time.now
|
|
24
26
|
@last_status = {}
|
|
25
27
|
@term = false
|
|
28
|
+
@worker_max = Array.new WORKER_MAX_KEYS.length, 0
|
|
26
29
|
end
|
|
27
30
|
|
|
28
|
-
attr_reader :index, :pid, :phase, :signal, :last_checkin, :last_status, :started_at
|
|
31
|
+
attr_reader :index, :pid, :phase, :signal, :last_checkin, :last_status, :started_at, :process_status
|
|
29
32
|
|
|
30
33
|
# @version 5.0.0
|
|
31
|
-
attr_writer :pid, :phase
|
|
34
|
+
attr_writer :pid, :phase, :process_status
|
|
32
35
|
|
|
33
36
|
def booted?
|
|
34
37
|
@stage == :booted
|
|
@@ -51,12 +54,40 @@ module Puma
|
|
|
51
54
|
@term
|
|
52
55
|
end
|
|
53
56
|
|
|
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
|
-
|
|
57
57
|
def ping!(status)
|
|
58
|
+
hsh = {}
|
|
59
|
+
k, v = nil, nil
|
|
60
|
+
status.tr('}{"', '').strip.split(", ") do |kv|
|
|
61
|
+
cntr = 0
|
|
62
|
+
kv.split(':') do |t|
|
|
63
|
+
if cntr == 0
|
|
64
|
+
k = t
|
|
65
|
+
cntr = 1
|
|
66
|
+
else
|
|
67
|
+
v = t
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
hsh[k.to_sym] = v.to_i
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# check stat max values, we can't signal workers to reset the max values,
|
|
74
|
+
# so we do so here
|
|
75
|
+
WORKER_MAX_KEYS.each_with_index do |key, idx|
|
|
76
|
+
next unless hsh[key]
|
|
77
|
+
|
|
78
|
+
if hsh[key] < @worker_max[idx]
|
|
79
|
+
hsh[key] = @worker_max[idx]
|
|
80
|
+
else
|
|
81
|
+
@worker_max[idx] = hsh[key]
|
|
82
|
+
end
|
|
83
|
+
end
|
|
58
84
|
@last_checkin = Time.now
|
|
59
|
-
@last_status =
|
|
85
|
+
@last_status = hsh
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# Resets max values to zero. Called whenever `Cluster#stats` is called
|
|
89
|
+
def reset_max
|
|
90
|
+
WORKER_MAX_KEYS.length.times { |idx| @worker_max[idx] = 0 }
|
|
60
91
|
end
|
|
61
92
|
|
|
62
93
|
# @see Puma::Cluster#check_workers
|