puma 7.2.2-java → 8.0.0-java
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 +39 -22
- data/README.md +1 -2
- data/docs/5.0-Upgrade.md +98 -0
- data/docs/6.0-Upgrade.md +56 -0
- data/docs/7.0-Upgrade.md +52 -0
- data/docs/8.0-Upgrade.md +100 -0
- data/docs/grpc.md +62 -0
- data/docs/images/favicon.svg +1 -0
- data/docs/images/running-puma.svg +1 -0
- data/docs/images/standard-logo.svg +1 -0
- data/docs/signals.md +1 -1
- data/ext/puma_http11/http11_parser.java.rl +51 -65
- data/ext/puma_http11/mini_ssl.c +1 -8
- data/ext/puma_http11/org/jruby/puma/EnvKey.java +241 -0
- data/ext/puma_http11/org/jruby/puma/Http11.java +168 -104
- data/ext/puma_http11/org/jruby/puma/Http11Parser.java +71 -85
- data/ext/puma_http11/org/jruby/puma/MiniSSL.java +34 -44
- data/ext/puma_http11/puma_http11.c +3 -4
- data/lib/puma/cli.rb +1 -1
- data/lib/puma/client.rb +114 -196
- data/lib/puma/client_env.rb +171 -0
- data/lib/puma/cluster.rb +1 -1
- data/lib/puma/configuration.rb +69 -8
- data/lib/puma/const.rb +3 -7
- data/lib/puma/control_cli.rb +4 -15
- data/lib/puma/detect.rb +11 -0
- data/lib/puma/dsl.rb +75 -22
- data/lib/puma/launcher.rb +3 -4
- data/lib/puma/minissl.rb +56 -101
- data/lib/puma/puma_http11.jar +0 -0
- data/lib/puma/reactor.rb +3 -35
- data/lib/puma/{request.rb → response.rb} +16 -201
- data/lib/puma/server.rb +84 -120
- data/lib/puma/server_plugin_control.rb +32 -0
- data/lib/puma/thread_pool.rb +129 -23
- data/lib/puma/util.rb +4 -8
- data/lib/rack/handler/puma.rb +1 -1
- metadata +14 -3
|
@@ -383,41 +383,36 @@ public class MiniSSL extends RubyObject { // MiniSSL::Engine
|
|
|
383
383
|
}
|
|
384
384
|
|
|
385
385
|
MiniSSLBuffer inboundAppData = new MiniSSLBuffer(engine.getSession().getApplicationBufferSize());
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
res = doOp(SSLOperation.UNWRAP, inboundNetData, inboundAppData);
|
|
402
|
-
if (res.getStatus() == Status.BUFFER_UNDERFLOW) {
|
|
403
|
-
// need more data before we can shake more hands
|
|
404
|
-
done = true;
|
|
405
|
-
}
|
|
406
|
-
handshakeStatus = res.getHandshakeStatus();
|
|
407
|
-
break;
|
|
408
|
-
case NEED_TASK:
|
|
409
|
-
Runnable runnable;
|
|
410
|
-
while ((runnable = engine.getDelegatedTask()) != null) {
|
|
411
|
-
runnable.run();
|
|
412
|
-
}
|
|
413
|
-
handshakeStatus = engine.getHandshakeStatus();
|
|
414
|
-
break;
|
|
415
|
-
default:
|
|
386
|
+
doOp(SSLOperation.UNWRAP, inboundNetData, inboundAppData);
|
|
387
|
+
|
|
388
|
+
HandshakeStatus handshakeStatus = engine.getHandshakeStatus();
|
|
389
|
+
boolean done = false;
|
|
390
|
+
while (!done) {
|
|
391
|
+
SSLEngineResult res;
|
|
392
|
+
switch (handshakeStatus) {
|
|
393
|
+
case NEED_WRAP:
|
|
394
|
+
res = doOp(SSLOperation.WRAP, inboundAppData, outboundNetData);
|
|
395
|
+
handshakeStatus = res.getHandshakeStatus();
|
|
396
|
+
break;
|
|
397
|
+
case NEED_UNWRAP:
|
|
398
|
+
res = doOp(SSLOperation.UNWRAP, inboundNetData, inboundAppData);
|
|
399
|
+
if (res.getStatus() == Status.BUFFER_UNDERFLOW) {
|
|
400
|
+
// need more data before we can shake more hands
|
|
416
401
|
done = true;
|
|
417
|
-
|
|
402
|
+
}
|
|
403
|
+
handshakeStatus = res.getHandshakeStatus();
|
|
404
|
+
break;
|
|
405
|
+
case NEED_TASK:
|
|
406
|
+
Runnable runnable;
|
|
407
|
+
while ((runnable = engine.getDelegatedTask()) != null) {
|
|
408
|
+
runnable.run();
|
|
409
|
+
}
|
|
410
|
+
handshakeStatus = engine.getHandshakeStatus();
|
|
411
|
+
break;
|
|
412
|
+
default:
|
|
413
|
+
done = true;
|
|
418
414
|
}
|
|
419
|
-
}
|
|
420
|
-
inboundNetData.hasRemaining() && inboundNetData.position() > consumedTo);
|
|
415
|
+
}
|
|
421
416
|
|
|
422
417
|
if (inboundNetData.hasRemaining()) {
|
|
423
418
|
inboundNetData.compact();
|
|
@@ -487,19 +482,14 @@ public class MiniSSL extends RubyObject { // MiniSSL::Engine
|
|
|
487
482
|
|
|
488
483
|
@JRubyMethod
|
|
489
484
|
public IRubyObject shutdown() {
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
// Generate close_notify for MiniSSL::Socket#close to send.
|
|
496
|
-
engine.closeOutbound();
|
|
497
|
-
try {
|
|
498
|
-
doOp(SSLOperation.WRAP, outboundAppData, outboundNetData);
|
|
499
|
-
} catch (SSLException e) {
|
|
485
|
+
if (closed || engine.isInboundDone() && engine.isOutboundDone()) {
|
|
486
|
+
if (engine.isOutboundDone()) {
|
|
487
|
+
engine.closeOutbound();
|
|
488
|
+
}
|
|
500
489
|
return getRuntime().getTrue();
|
|
490
|
+
} else {
|
|
491
|
+
return getRuntime().getFalse();
|
|
501
492
|
}
|
|
502
|
-
return getRuntime().getFalse();
|
|
503
493
|
}
|
|
504
494
|
|
|
505
495
|
private static RubyClass getSSLError(Ruby runtime) {
|
|
@@ -385,8 +385,7 @@ static VALUE HttpParser_execute(VALUE self, VALUE req_hash, VALUE data, VALUE st
|
|
|
385
385
|
long dlen = 0;
|
|
386
386
|
|
|
387
387
|
from = FIX2INT(start);
|
|
388
|
-
dptr
|
|
389
|
-
dlen = (long)RSTRING_LEN(data);
|
|
388
|
+
RSTRING_GETMEM(data, dptr, dlen);
|
|
390
389
|
|
|
391
390
|
if(from >= dlen) {
|
|
392
391
|
rb_raise(eHttpParserError, "%s", "Requested start is after data buffer end.");
|
|
@@ -399,7 +398,7 @@ static VALUE HttpParser_execute(VALUE self, VALUE req_hash, VALUE data, VALUE st
|
|
|
399
398
|
if(puma_parser_has_error(http)) {
|
|
400
399
|
rb_raise(eHttpParserError, "%s", "Invalid HTTP format, parsing fails. Are you trying to open an SSL connection to a non-SSL Puma?");
|
|
401
400
|
} else {
|
|
402
|
-
return
|
|
401
|
+
return INT2FIX(puma_parser_nread(http));
|
|
403
402
|
}
|
|
404
403
|
}
|
|
405
404
|
}
|
|
@@ -445,7 +444,7 @@ static VALUE HttpParser_nread(VALUE self)
|
|
|
445
444
|
{
|
|
446
445
|
puma_parser *http = HttpParser_unwrap(self);
|
|
447
446
|
|
|
448
|
-
return
|
|
447
|
+
return INT2FIX(http->nread);
|
|
449
448
|
}
|
|
450
449
|
|
|
451
450
|
/**
|
data/lib/puma/cli.rb
CHANGED
|
@@ -148,7 +148,7 @@ module Puma
|
|
|
148
148
|
|
|
149
149
|
o.on "-p", "--port PORT", "Define the TCP port to bind to",
|
|
150
150
|
"Use -b for more advanced options" do |arg|
|
|
151
|
-
user_config.
|
|
151
|
+
user_config.port arg, Configuration.default_tcp_host
|
|
152
152
|
end
|
|
153
153
|
|
|
154
154
|
o.on "--pidfile PATH", "Use PATH as a pidfile" do |arg|
|
data/lib/puma/client.rb
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
require_relative 'detect'
|
|
4
4
|
require_relative 'io_buffer'
|
|
5
|
+
require_relative 'client_env'
|
|
5
6
|
require 'tempfile'
|
|
6
7
|
|
|
7
8
|
if Puma::IS_JRUBY
|
|
@@ -20,8 +21,8 @@ module Puma
|
|
|
20
21
|
#———————————————————————— DO NOT USE — this class is for internal use only ———
|
|
21
22
|
|
|
22
23
|
|
|
23
|
-
# An instance of this class
|
|
24
|
-
# For example, this could be
|
|
24
|
+
# An instance of this class wraps a connection/socket.
|
|
25
|
+
# For example, this could be an http request from a browser or from CURL.
|
|
25
26
|
#
|
|
26
27
|
# An instance of `Puma::Client` can be used as if it were an IO object
|
|
27
28
|
# by the reactor. The reactor is expected to call `#to_io`
|
|
@@ -29,12 +30,18 @@ module Puma
|
|
|
29
30
|
# `IO::try_convert` (which may call `#to_io`) when a new socket is
|
|
30
31
|
# registered.
|
|
31
32
|
#
|
|
32
|
-
# Instances of this class are responsible for knowing if
|
|
33
|
-
#
|
|
33
|
+
# Instances of this class are responsible for knowing if the request line,
|
|
34
|
+
# headers and body are fully buffered and verified via the `try_to_finish` method.
|
|
35
|
+
# All verification of each request is done in the `Client` object.
|
|
34
36
|
# They can be used to "time out" a response via the `timeout_at` reader.
|
|
35
37
|
#
|
|
38
|
+
# Most of the code for env processing and verification is contained
|
|
39
|
+
# in `Puma::ClientEnv`, which is included.
|
|
40
|
+
#
|
|
36
41
|
class Client # :nodoc:
|
|
37
42
|
|
|
43
|
+
include ClientEnv
|
|
44
|
+
|
|
38
45
|
# this tests all values but the last, which must be chunked
|
|
39
46
|
ALLOWED_TRANSFER_ENCODING = %w[compress deflate gzip].freeze
|
|
40
47
|
|
|
@@ -47,8 +54,6 @@ module Puma
|
|
|
47
54
|
# chunk header.
|
|
48
55
|
MAX_CHUNK_HEADER_SIZE = 4096
|
|
49
56
|
|
|
50
|
-
SHUTDOWN_READ_GRACE = 1
|
|
51
|
-
|
|
52
57
|
# The maximum amount of excess data the client sends
|
|
53
58
|
# using chunk size extensions before we abort the connection.
|
|
54
59
|
MAX_CHUNK_EXCESS = 16 * 1024
|
|
@@ -67,7 +72,13 @@ module Puma
|
|
|
67
72
|
# no body share this one object since it has no state.
|
|
68
73
|
EmptyBody = NullIO.new
|
|
69
74
|
|
|
70
|
-
|
|
75
|
+
attr_reader :env, :to_io, :body, :io, :timeout_at, :ready, :hijacked,
|
|
76
|
+
:tempfile, :io_buffer, :http_content_length_limit_exceeded,
|
|
77
|
+
:requests_served, :error_status_code
|
|
78
|
+
|
|
79
|
+
attr_writer :peerip, :http_content_length_limit, :supported_http_methods
|
|
80
|
+
|
|
81
|
+
attr_accessor :remote_addr_header, :listener, :env_set_http_version
|
|
71
82
|
|
|
72
83
|
def initialize(io, env=nil)
|
|
73
84
|
@io = io
|
|
@@ -93,9 +104,8 @@ module Puma
|
|
|
93
104
|
@hijacked = false
|
|
94
105
|
|
|
95
106
|
@http_content_length_limit = nil
|
|
96
|
-
@http_content_length_limit_exceeded =
|
|
97
|
-
|
|
98
|
-
@reactor_error = nil
|
|
107
|
+
@http_content_length_limit_exceeded = nil
|
|
108
|
+
@error_status_code = nil
|
|
99
109
|
|
|
100
110
|
@peerip = nil
|
|
101
111
|
@peer_family = nil
|
|
@@ -104,8 +114,6 @@ module Puma
|
|
|
104
114
|
@expect_proxy_proto = false
|
|
105
115
|
|
|
106
116
|
@body_remain = 0
|
|
107
|
-
@bytes_read = 0
|
|
108
|
-
@pending_write = nil
|
|
109
117
|
|
|
110
118
|
@in_last_chunk = false
|
|
111
119
|
|
|
@@ -113,15 +121,6 @@ module Puma
|
|
|
113
121
|
@read_buffer = String.new # rubocop: disable Performance/UnfreezeString
|
|
114
122
|
end
|
|
115
123
|
|
|
116
|
-
attr_reader :env, :to_io, :body, :io, :timeout_at, :ready, :hijacked,
|
|
117
|
-
:tempfile, :io_buffer, :http_content_length_limit_exceeded,
|
|
118
|
-
:requests_served, :bytes_read
|
|
119
|
-
|
|
120
|
-
attr_writer :peerip, :http_content_length_limit
|
|
121
|
-
|
|
122
|
-
attr_accessor :remote_addr_header, :listener,
|
|
123
|
-
:reactor_error
|
|
124
|
-
|
|
125
124
|
# Remove in Puma 7?
|
|
126
125
|
def closed?
|
|
127
126
|
@to_io.closed?
|
|
@@ -164,14 +163,15 @@ module Puma
|
|
|
164
163
|
@parser.reset
|
|
165
164
|
@io_buffer.reset
|
|
166
165
|
@read_header = true
|
|
167
|
-
@read_proxy = !!@expect_proxy_proto
|
|
166
|
+
@read_proxy = !!@expect_proxy_proto
|
|
168
167
|
@env = @proto_env.dup
|
|
169
168
|
@parsed_bytes = 0
|
|
170
169
|
@ready = false
|
|
171
170
|
@body_remain = 0
|
|
172
171
|
@peerip = nil if @remote_addr_header
|
|
173
172
|
@in_last_chunk = false
|
|
174
|
-
@http_content_length_limit_exceeded =
|
|
173
|
+
@http_content_length_limit_exceeded = nil
|
|
174
|
+
@error_status_code = nil
|
|
175
175
|
end
|
|
176
176
|
|
|
177
177
|
# only used with back-to-back requests contained in the buffer
|
|
@@ -181,12 +181,7 @@ module Puma
|
|
|
181
181
|
|
|
182
182
|
@parsed_bytes = parser_execute
|
|
183
183
|
|
|
184
|
-
|
|
185
|
-
return setup_body
|
|
186
|
-
elsif @parsed_bytes >= MAX_HEADER
|
|
187
|
-
raise HttpParserError,
|
|
188
|
-
"HEADER is longer than allowed, aborting client early."
|
|
189
|
-
end
|
|
184
|
+
@parser.finished? ? process_env_body : false
|
|
190
185
|
end
|
|
191
186
|
end
|
|
192
187
|
|
|
@@ -218,56 +213,27 @@ module Puma
|
|
|
218
213
|
def try_to_parse_proxy_protocol
|
|
219
214
|
if @read_proxy
|
|
220
215
|
if @expect_proxy_proto == :v1
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
return false
|
|
226
|
-
elsif @buffer.start_with? "PROXY "
|
|
227
|
-
if @buffer.bytesize >= PROXY_PROTOCOL_V1_MAX_LENGTH
|
|
228
|
-
raise ConnectionError, "PROXY protocol v1 line is too long"
|
|
216
|
+
if @buffer.include? "\r\n"
|
|
217
|
+
if md = PROXY_PROTOCOL_V1_REGEX.match(@buffer)
|
|
218
|
+
if md[1]
|
|
219
|
+
@peerip = md[1].split(" ")[0]
|
|
229
220
|
end
|
|
230
|
-
|
|
221
|
+
@buffer = md.post_match
|
|
231
222
|
end
|
|
232
|
-
|
|
223
|
+
# if the buffer has a \r\n but doesn't have a PROXY protocol
|
|
224
|
+
# request, this is just HTTP from a non-PROXY client; move on
|
|
233
225
|
@read_proxy = false
|
|
234
|
-
return
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
if @buffer.start_with?("PROXY ") && crlf_index + 2 > PROXY_PROTOCOL_V1_MAX_LENGTH
|
|
238
|
-
raise ConnectionError, "PROXY protocol v1 line is too long"
|
|
239
|
-
end
|
|
240
|
-
|
|
241
|
-
if md = PROXY_PROTOCOL_V1_REGEX.match(@buffer)
|
|
242
|
-
if md[1]
|
|
243
|
-
@peerip = md[1].split(" ")[0]
|
|
244
|
-
end
|
|
245
|
-
@buffer = md.post_match
|
|
226
|
+
return @buffer.size > 0
|
|
227
|
+
else
|
|
228
|
+
return false
|
|
246
229
|
end
|
|
247
|
-
# if the buffer has a \r\n but doesn't have a PROXY protocol
|
|
248
|
-
# request, this is just HTTP from a non-PROXY client; move on
|
|
249
|
-
@read_proxy = false
|
|
250
|
-
return @buffer.size > 0
|
|
251
230
|
end
|
|
252
231
|
end
|
|
253
232
|
true
|
|
254
233
|
end
|
|
255
234
|
|
|
256
|
-
def try_to_finish
|
|
257
|
-
return
|
|
258
|
-
|
|
259
|
-
if env[CONTENT_LENGTH] && above_http_content_limit(env[CONTENT_LENGTH].to_i)
|
|
260
|
-
@http_content_length_limit_exceeded = true
|
|
261
|
-
end
|
|
262
|
-
|
|
263
|
-
if @http_content_length_limit_exceeded
|
|
264
|
-
@buffer = nil
|
|
265
|
-
@body = EmptyBody
|
|
266
|
-
set_ready
|
|
267
|
-
return true
|
|
268
|
-
end
|
|
269
|
-
|
|
270
|
-
return read_body(&deadline) if in_data_phase
|
|
235
|
+
def try_to_finish
|
|
236
|
+
return read_body if in_data_phase
|
|
271
237
|
|
|
272
238
|
data = nil
|
|
273
239
|
begin
|
|
@@ -287,7 +253,6 @@ module Puma
|
|
|
287
253
|
raise EOFError
|
|
288
254
|
end
|
|
289
255
|
|
|
290
|
-
@bytes_read += data.bytesize
|
|
291
256
|
if @buffer
|
|
292
257
|
@buffer << data
|
|
293
258
|
else
|
|
@@ -298,74 +263,32 @@ module Puma
|
|
|
298
263
|
|
|
299
264
|
@parsed_bytes = parser_execute
|
|
300
265
|
|
|
301
|
-
|
|
302
|
-
@http_content_length_limit_exceeded = true
|
|
303
|
-
end
|
|
304
|
-
|
|
305
|
-
if @parser.finished?
|
|
306
|
-
setup_body
|
|
307
|
-
elsif @parsed_bytes >= MAX_HEADER
|
|
308
|
-
raise HttpParserError,
|
|
309
|
-
"HEADER is longer than allowed, aborting client early."
|
|
310
|
-
else
|
|
311
|
-
false
|
|
312
|
-
end
|
|
266
|
+
@parser.finished? ? process_env_body : false
|
|
313
267
|
end
|
|
314
268
|
|
|
315
|
-
def eagerly_finish
|
|
269
|
+
def eagerly_finish
|
|
316
270
|
return true if @ready
|
|
317
|
-
while
|
|
318
|
-
|
|
319
|
-
return true if try_to_finish(&deadline)
|
|
320
|
-
check_deadline(&deadline) if deadline
|
|
321
|
-
return false if @bytes_read == read
|
|
271
|
+
while @to_io.wait_readable(0) # rubocop: disable Style/WhileUntilModifier
|
|
272
|
+
return true if try_to_finish
|
|
322
273
|
end
|
|
323
274
|
false
|
|
324
275
|
end
|
|
325
276
|
|
|
326
|
-
def finish(timeout
|
|
277
|
+
def finish(timeout)
|
|
327
278
|
return if @ready
|
|
328
|
-
|
|
329
|
-
read = @bytes_read
|
|
330
|
-
until try_to_finish(&deadline)
|
|
331
|
-
if @bytes_read > read
|
|
332
|
-
set_timeout timeout
|
|
333
|
-
read = @bytes_read
|
|
334
|
-
elsif self.timeout == 0
|
|
335
|
-
timeout!
|
|
336
|
-
end
|
|
337
|
-
if deadline
|
|
338
|
-
check_deadline(&deadline)
|
|
339
|
-
limit = yield
|
|
340
|
-
wait = [self.timeout, limit ? limit - Process.clock_gettime(Process::CLOCK_MONOTONIC) : 0.1].min
|
|
341
|
-
wait_io(wait) if wait > 0
|
|
342
|
-
else
|
|
343
|
-
wait_io(self.timeout) || timeout!
|
|
344
|
-
end
|
|
345
|
-
end
|
|
346
|
-
end
|
|
347
|
-
|
|
348
|
-
def write_pending?
|
|
349
|
-
!@pending_write.nil? || (@io.respond_to?(:write_pending?) && @io.write_pending?)
|
|
279
|
+
@to_io.wait_readable(timeout) || timeout! until try_to_finish
|
|
350
280
|
end
|
|
351
281
|
|
|
352
|
-
def take_pending_write
|
|
353
|
-
str = @pending_write
|
|
354
|
-
@pending_write = nil
|
|
355
|
-
str
|
|
356
|
-
end
|
|
357
|
-
|
|
358
|
-
def wait_io(timeout)
|
|
359
|
-
return @to_io.wait_readable(timeout) unless write_pending?
|
|
360
|
-
@to_io.wait_writable(timeout)
|
|
361
|
-
end
|
|
362
|
-
private :wait_io
|
|
363
|
-
|
|
364
282
|
# Wraps `@parser.execute` and adds meaningful error messages
|
|
365
283
|
# @return [Integer] bytes of buffer read by parser
|
|
366
284
|
#
|
|
367
285
|
def parser_execute
|
|
368
|
-
@parser.execute(@env, @buffer, @parsed_bytes)
|
|
286
|
+
ret = @parser.execute(@env, @buffer, @parsed_bytes)
|
|
287
|
+
|
|
288
|
+
if @env[REQUEST_METHOD] && @supported_http_methods != :any && !@supported_http_methods.key?(@env[REQUEST_METHOD])
|
|
289
|
+
raise HttpParserError501, "#{@env[REQUEST_METHOD]} method is not supported"
|
|
290
|
+
end
|
|
291
|
+
ret
|
|
369
292
|
rescue => e
|
|
370
293
|
@env[HTTP_CONNECTION] = 'close'
|
|
371
294
|
raise e unless HttpParserError === e && e.message.include?('non-SSL')
|
|
@@ -399,6 +322,15 @@ module Puma
|
|
|
399
322
|
end
|
|
400
323
|
end
|
|
401
324
|
|
|
325
|
+
# processes the `env` and the request body
|
|
326
|
+
def process_env_body
|
|
327
|
+
temp = setup_body
|
|
328
|
+
normalize_env
|
|
329
|
+
req_env_post_parse
|
|
330
|
+
raise HttpParserError if @error_status_code
|
|
331
|
+
temp
|
|
332
|
+
end
|
|
333
|
+
|
|
402
334
|
def timeout!
|
|
403
335
|
write_error(408) if in_data_phase
|
|
404
336
|
raise ConnectionError
|
|
@@ -406,51 +338,21 @@ module Puma
|
|
|
406
338
|
|
|
407
339
|
def write_error(status_code)
|
|
408
340
|
begin
|
|
409
|
-
|
|
341
|
+
@io << ERROR_RESPONSE[status_code]
|
|
410
342
|
rescue StandardError
|
|
411
343
|
end
|
|
412
344
|
end
|
|
413
345
|
|
|
414
|
-
def write_interim(str)
|
|
415
|
-
if @pending_write
|
|
416
|
-
@pending_write << str
|
|
417
|
-
return flush_pending_write
|
|
418
|
-
end
|
|
419
|
-
|
|
420
|
-
n = @io.write_nonblock(str, exception: false)
|
|
421
|
-
if n == :wait_writable
|
|
422
|
-
@pending_write = +str unless @io.respond_to?(:write_pending?) && @io.write_pending?
|
|
423
|
-
elsif n < str.bytesize
|
|
424
|
-
@pending_write = str.byteslice(n..-1)
|
|
425
|
-
end
|
|
426
|
-
rescue SystemCallError, IOError
|
|
427
|
-
raise ConnectionError, "Connection error detected during write"
|
|
428
|
-
end
|
|
429
|
-
private :write_interim
|
|
430
|
-
|
|
431
|
-
def flush_pending_write
|
|
432
|
-
return true unless @pending_write
|
|
433
|
-
|
|
434
|
-
n = @io.write_nonblock(@pending_write, exception: false)
|
|
435
|
-
return false if n == :wait_writable
|
|
436
|
-
|
|
437
|
-
@pending_write = n == @pending_write.bytesize ? nil : @pending_write.byteslice(n..-1)
|
|
438
|
-
@pending_write.nil?
|
|
439
|
-
rescue SystemCallError, IOError
|
|
440
|
-
raise ConnectionError, "Connection error detected during write"
|
|
441
|
-
end
|
|
442
|
-
private :flush_pending_write
|
|
443
|
-
|
|
444
346
|
def peerip
|
|
445
347
|
return @peerip if @peerip
|
|
446
348
|
|
|
447
349
|
if @remote_addr_header
|
|
448
|
-
hdr = (@env[@remote_addr_header] ||
|
|
350
|
+
hdr = (@env[@remote_addr_header] || socket_peerip).split(/[\s,]/).first
|
|
449
351
|
@peerip = hdr
|
|
450
352
|
return hdr
|
|
451
353
|
end
|
|
452
354
|
|
|
453
|
-
@peerip ||=
|
|
355
|
+
@peerip ||= socket_peerip
|
|
454
356
|
end
|
|
455
357
|
|
|
456
358
|
def peer_family
|
|
@@ -485,18 +387,36 @@ module Puma
|
|
|
485
387
|
|
|
486
388
|
private
|
|
487
389
|
|
|
390
|
+
IPV4_MAPPED_IPV6_PREFIX = "::ffff:"
|
|
391
|
+
private_constant :IPV4_MAPPED_IPV6_PREFIX
|
|
392
|
+
|
|
393
|
+
def socket_peerip
|
|
394
|
+
unmap_ipv6(@io.peeraddr.last)
|
|
395
|
+
end
|
|
396
|
+
|
|
397
|
+
# Converts IPv4-mapped IPv6 addresses (e.g. ::ffff:127.0.0.1) back to
|
|
398
|
+
# their IPv4 form. These addresses appear when IPv4 clients connect to
|
|
399
|
+
# a dual-stack IPv6 socket.
|
|
400
|
+
def unmap_ipv6(addr)
|
|
401
|
+
addr.delete_prefix(IPV4_MAPPED_IPV6_PREFIX)
|
|
402
|
+
end
|
|
403
|
+
|
|
404
|
+
# Checks the request `Transfer-Encoding` and/or `Content-Length` to see if
|
|
405
|
+
# they are valid. Raises errors if not, otherwise reads the body.
|
|
406
|
+
# @return [Boolean] true if the body can be completely read, false otherwise
|
|
407
|
+
#
|
|
488
408
|
def setup_body
|
|
489
409
|
@body_read_start = Process.clock_gettime(Process::CLOCK_MONOTONIC, :float_millisecond)
|
|
490
410
|
|
|
491
411
|
if @env[HTTP_EXPECT] == CONTINUE
|
|
492
|
-
# TODO allow a hook here to check the headers before
|
|
493
|
-
|
|
494
|
-
|
|
412
|
+
# TODO allow a hook here to check the headers before going forward
|
|
413
|
+
@io << HTTP_11_100
|
|
414
|
+
@io.flush
|
|
495
415
|
end
|
|
496
416
|
|
|
497
417
|
@read_header = false
|
|
498
418
|
|
|
499
|
-
|
|
419
|
+
parser_body = @parser.body
|
|
500
420
|
|
|
501
421
|
te = @env[TRANSFER_ENCODING2]
|
|
502
422
|
if te
|
|
@@ -513,10 +433,10 @@ module Puma
|
|
|
513
433
|
raise HttpParserError501, "#{TE_ERR_MSG}, unknown value: '#{te}'"
|
|
514
434
|
end
|
|
515
435
|
@env.delete TRANSFER_ENCODING2
|
|
516
|
-
return setup_chunked_body
|
|
436
|
+
return setup_chunked_body parser_body
|
|
517
437
|
elsif te_lwr == CHUNKED
|
|
518
438
|
@env.delete TRANSFER_ENCODING2
|
|
519
|
-
return setup_chunked_body
|
|
439
|
+
return setup_chunked_body parser_body
|
|
520
440
|
elsif ALLOWED_TRANSFER_ENCODING.include? te_lwr
|
|
521
441
|
raise HttpParserError , "#{TE_ERR_MSG}, single value must be chunked: '#{te}'"
|
|
522
442
|
else
|
|
@@ -531,10 +451,12 @@ module Puma
|
|
|
531
451
|
if cl
|
|
532
452
|
# cannot contain characters that are not \d, or be empty
|
|
533
453
|
if CONTENT_LENGTH_VALUE_INVALID.match?(cl) || cl.empty?
|
|
454
|
+
@error_status_code = 400
|
|
455
|
+
@env[HTTP_CONNECTION] = 'close'
|
|
534
456
|
raise HttpParserError, "Invalid Content-Length: #{cl.inspect}"
|
|
535
457
|
end
|
|
536
458
|
else
|
|
537
|
-
@buffer =
|
|
459
|
+
@buffer = parser_body.empty? ? nil : parser_body
|
|
538
460
|
@body = EmptyBody
|
|
539
461
|
set_ready
|
|
540
462
|
return true
|
|
@@ -542,23 +464,25 @@ module Puma
|
|
|
542
464
|
|
|
543
465
|
content_length = cl.to_i
|
|
544
466
|
|
|
545
|
-
|
|
467
|
+
raise_above_http_content_limit if @http_content_length_limit&.< content_length
|
|
468
|
+
|
|
469
|
+
remain = content_length - parser_body.bytesize
|
|
546
470
|
|
|
547
471
|
if remain <= 0
|
|
548
|
-
# Part of the
|
|
472
|
+
# Part of the parser_body is a pipelined request OR garbage. We'll deal with that later.
|
|
549
473
|
if content_length == 0
|
|
550
474
|
@body = EmptyBody
|
|
551
|
-
if
|
|
475
|
+
if parser_body.empty?
|
|
552
476
|
@buffer = nil
|
|
553
477
|
else
|
|
554
|
-
@buffer =
|
|
478
|
+
@buffer = parser_body
|
|
555
479
|
end
|
|
556
480
|
elsif remain == 0
|
|
557
|
-
@body = StringIO.new
|
|
481
|
+
@body = StringIO.new parser_body
|
|
558
482
|
@buffer = nil
|
|
559
483
|
else
|
|
560
|
-
@body = StringIO.new(
|
|
561
|
-
@buffer =
|
|
484
|
+
@body = StringIO.new(parser_body[0,content_length])
|
|
485
|
+
@buffer = parser_body[content_length..-1]
|
|
562
486
|
end
|
|
563
487
|
set_ready
|
|
564
488
|
return true
|
|
@@ -570,21 +494,21 @@ module Puma
|
|
|
570
494
|
@body.binmode
|
|
571
495
|
@tempfile = @body
|
|
572
496
|
else
|
|
573
|
-
# The
|
|
574
|
-
# encoding as
|
|
575
|
-
@body = StringIO.new
|
|
497
|
+
# The parser_body[0,0] trick is to get an empty string in the same
|
|
498
|
+
# encoding as parser_body.
|
|
499
|
+
@body = StringIO.new parser_body[0,0]
|
|
576
500
|
end
|
|
577
501
|
|
|
578
|
-
@body.write
|
|
502
|
+
@body.write parser_body
|
|
579
503
|
|
|
580
504
|
@body_remain = remain
|
|
581
505
|
|
|
582
506
|
false
|
|
583
507
|
end
|
|
584
508
|
|
|
585
|
-
def read_body
|
|
509
|
+
def read_body
|
|
586
510
|
if @chunked_body
|
|
587
|
-
return read_chunked_body
|
|
511
|
+
return read_chunked_body
|
|
588
512
|
end
|
|
589
513
|
|
|
590
514
|
# Read an odd sized chunk so we can read even sized ones
|
|
@@ -609,7 +533,6 @@ module Puma
|
|
|
609
533
|
raise EOFError
|
|
610
534
|
end
|
|
611
535
|
|
|
612
|
-
@bytes_read += chunk.bytesize
|
|
613
536
|
remain -= @body.write(chunk)
|
|
614
537
|
end
|
|
615
538
|
|
|
@@ -624,7 +547,7 @@ module Puma
|
|
|
624
547
|
end
|
|
625
548
|
end
|
|
626
549
|
|
|
627
|
-
def read_chunked_body
|
|
550
|
+
def read_chunked_body
|
|
628
551
|
while true
|
|
629
552
|
begin
|
|
630
553
|
chunk = @io.read_nonblock(CHUNK_SIZE, @read_buffer)
|
|
@@ -642,27 +565,13 @@ module Puma
|
|
|
642
565
|
raise EOFError
|
|
643
566
|
end
|
|
644
567
|
|
|
645
|
-
@bytes_read += chunk.bytesize
|
|
646
568
|
if decode_chunk(chunk)
|
|
647
569
|
@env[CONTENT_LENGTH] = @chunked_content_length.to_s
|
|
648
570
|
return true
|
|
649
571
|
end
|
|
650
|
-
|
|
651
|
-
check_deadline(&deadline) if deadline
|
|
652
572
|
end
|
|
653
573
|
end
|
|
654
574
|
|
|
655
|
-
def check_deadline
|
|
656
|
-
limit = yield
|
|
657
|
-
return unless limit
|
|
658
|
-
|
|
659
|
-
now = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
660
|
-
return if now < limit
|
|
661
|
-
return if now < limit + SHUTDOWN_READ_GRACE && @to_io.wait_readable(0)
|
|
662
|
-
|
|
663
|
-
timeout!
|
|
664
|
-
end
|
|
665
|
-
|
|
666
575
|
def setup_chunked_body(body)
|
|
667
576
|
@chunked_body = true
|
|
668
577
|
@partial_part_left = 0
|
|
@@ -683,6 +592,10 @@ module Puma
|
|
|
683
592
|
|
|
684
593
|
# @version 5.0.0
|
|
685
594
|
def write_chunk(str)
|
|
595
|
+
if @http_content_length_limit&.< @chunked_content_length + str.bytesize
|
|
596
|
+
raise_above_http_content_limit
|
|
597
|
+
end
|
|
598
|
+
|
|
686
599
|
@chunked_content_length += @body.write(str)
|
|
687
600
|
end
|
|
688
601
|
|
|
@@ -815,8 +728,13 @@ module Puma
|
|
|
815
728
|
@ready = true
|
|
816
729
|
end
|
|
817
730
|
|
|
818
|
-
def
|
|
819
|
-
@
|
|
731
|
+
def raise_above_http_content_limit
|
|
732
|
+
@http_content_length_limit_exceeded = true
|
|
733
|
+
@buffer = nil
|
|
734
|
+
@body = EmptyBody
|
|
735
|
+
@error_status_code = 413
|
|
736
|
+
@env[HTTP_CONNECTION] = 'close'
|
|
737
|
+
raise HttpParserError, "Payload Too Large"
|
|
820
738
|
end
|
|
821
739
|
end
|
|
822
740
|
end
|