puma 3.12.6 → 4.3.10
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of puma might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/History.md +145 -3
- data/README.md +76 -48
- data/docs/architecture.md +1 -0
- data/docs/deployment.md +24 -4
- data/docs/images/puma-connection-flow-no-reactor.png +0 -0
- data/docs/images/puma-connection-flow.png +0 -0
- data/docs/images/puma-general-arch.png +0 -0
- data/docs/plugins.md +20 -10
- data/docs/restart.md +4 -2
- data/docs/systemd.md +27 -9
- data/docs/tcp_mode.md +96 -0
- data/ext/puma_http11/PumaHttp11Service.java +2 -0
- data/ext/puma_http11/extconf.rb +13 -0
- data/ext/puma_http11/http11_parser.c +58 -70
- data/ext/puma_http11/http11_parser.java.rl +21 -37
- data/ext/puma_http11/http11_parser_common.rl +4 -4
- data/ext/puma_http11/mini_ssl.c +78 -8
- data/ext/puma_http11/org/jruby/puma/Http11.java +106 -114
- data/ext/puma_http11/org/jruby/puma/Http11Parser.java +86 -99
- data/ext/puma_http11/org/jruby/puma/IOBuffer.java +72 -0
- data/ext/puma_http11/org/jruby/puma/MiniSSL.java +15 -4
- data/ext/puma_http11/puma_http11.c +3 -0
- data/lib/puma/accept_nonblock.rb +7 -1
- data/lib/puma/app/status.rb +37 -29
- data/lib/puma/binder.rb +38 -60
- data/lib/puma/cli.rb +4 -0
- data/lib/puma/client.rb +242 -208
- data/lib/puma/cluster.rb +53 -30
- data/lib/puma/configuration.rb +4 -3
- data/lib/puma/const.rb +22 -18
- data/lib/puma/control_cli.rb +30 -5
- data/lib/puma/dsl.rb +299 -75
- data/lib/puma/events.rb +4 -1
- data/lib/puma/io_buffer.rb +1 -6
- data/lib/puma/launcher.rb +95 -53
- data/lib/puma/minissl/context_builder.rb +76 -0
- data/lib/puma/minissl.rb +35 -17
- data/lib/puma/plugin/tmp_restart.rb +2 -0
- data/lib/puma/plugin.rb +5 -2
- data/lib/puma/rack/builder.rb +2 -0
- data/lib/puma/rack/urlmap.rb +2 -0
- data/lib/puma/rack_default.rb +2 -0
- data/lib/puma/reactor.rb +110 -57
- data/lib/puma/runner.rb +11 -3
- data/lib/puma/server.rb +73 -57
- data/lib/puma/single.rb +3 -3
- data/lib/puma/thread_pool.rb +15 -33
- data/lib/puma/util.rb +1 -6
- data/lib/puma.rb +8 -0
- data/lib/rack/handler/puma.rb +3 -3
- data/tools/docker/Dockerfile +16 -0
- data/tools/jungle/init.d/puma +6 -6
- data/tools/trickletest.rb +0 -1
- metadata +26 -13
- data/lib/puma/compat.rb +0 -14
- data/lib/puma/convenient.rb +0 -25
- data/lib/puma/daemon_ext.rb +0 -33
- data/lib/puma/delegation.rb +0 -13
- data/lib/puma/java_io_buffer.rb +0 -47
- data/lib/puma/rack/backports/uri/common_193.rb +0 -33
data/lib/puma/client.rb
CHANGED
@@ -9,8 +9,8 @@ class IO
|
|
9
9
|
end
|
10
10
|
|
11
11
|
require 'puma/detect'
|
12
|
-
require 'puma/delegation'
|
13
12
|
require 'tempfile'
|
13
|
+
require 'forwardable'
|
14
14
|
|
15
15
|
if Puma::IS_JRUBY
|
16
16
|
# We have to work around some OpenSSL buffer/io-readiness bugs
|
@@ -24,19 +24,24 @@ module Puma
|
|
24
24
|
class ConnectionError < RuntimeError; end
|
25
25
|
|
26
26
|
# An instance of this class represents a unique request from a client.
|
27
|
-
# For example a web request from a browser or from CURL.
|
27
|
+
# For example, this could be a web request from a browser or from CURL.
|
28
28
|
#
|
29
29
|
# An instance of `Puma::Client` can be used as if it were an IO object
|
30
|
-
#
|
31
|
-
#
|
32
|
-
#
|
30
|
+
# by the reactor. The reactor is expected to call `#to_io`
|
31
|
+
# on any non-IO objects it polls. For example, nio4r internally calls
|
32
|
+
# `IO::try_convert` (which may call `#to_io`) when a new socket is
|
33
|
+
# registered.
|
33
34
|
#
|
34
35
|
# Instances of this class are responsible for knowing if
|
35
36
|
# the header and body are fully buffered via the `try_to_finish` method.
|
36
37
|
# They can be used to "time out" a response via the `timeout_at` reader.
|
37
38
|
class Client
|
39
|
+
# The object used for a request with no body. All requests with
|
40
|
+
# no body share this one object since it has no state.
|
41
|
+
EmptyBody = NullIO.new
|
42
|
+
|
38
43
|
include Puma::Const
|
39
|
-
extend
|
44
|
+
extend Forwardable
|
40
45
|
|
41
46
|
def initialize(io, env=nil)
|
42
47
|
@io = io
|
@@ -54,6 +59,7 @@ module Puma
|
|
54
59
|
@ready = false
|
55
60
|
|
56
61
|
@body = nil
|
62
|
+
@body_read_start = nil
|
57
63
|
@buffer = nil
|
58
64
|
@tempfile = nil
|
59
65
|
|
@@ -64,6 +70,10 @@ module Puma
|
|
64
70
|
|
65
71
|
@peerip = nil
|
66
72
|
@remote_addr_header = nil
|
73
|
+
|
74
|
+
@body_remain = 0
|
75
|
+
|
76
|
+
@in_last_chunk = false
|
67
77
|
end
|
68
78
|
|
69
79
|
attr_reader :env, :to_io, :body, :io, :timeout_at, :ready, :hijacked,
|
@@ -73,7 +83,7 @@ module Puma
|
|
73
83
|
|
74
84
|
attr_accessor :remote_addr_header
|
75
85
|
|
76
|
-
|
86
|
+
def_delegators :@io, :closed?
|
77
87
|
|
78
88
|
def inspect
|
79
89
|
"#<Puma::Client:0x#{object_id.to_s(16)} @ready=#{@ready.inspect}>"
|
@@ -102,6 +112,9 @@ module Puma
|
|
102
112
|
@tempfile = nil
|
103
113
|
@parsed_bytes = 0
|
104
114
|
@ready = false
|
115
|
+
@body_remain = 0
|
116
|
+
@peerip = nil
|
117
|
+
@in_last_chunk = false
|
105
118
|
|
106
119
|
if @buffer
|
107
120
|
@parsed_bytes = @parser.execute(@env, @buffer, @parsed_bytes)
|
@@ -114,9 +127,16 @@ module Puma
|
|
114
127
|
end
|
115
128
|
|
116
129
|
return false
|
117
|
-
|
118
|
-
|
119
|
-
|
130
|
+
else
|
131
|
+
begin
|
132
|
+
if fast_check &&
|
133
|
+
IO.select([@to_io], nil, nil, FAST_TRACK_KA_TIMEOUT)
|
134
|
+
return try_to_finish
|
135
|
+
end
|
136
|
+
rescue IOError
|
137
|
+
# swallow it
|
138
|
+
end
|
139
|
+
|
120
140
|
end
|
121
141
|
end
|
122
142
|
|
@@ -128,189 +148,21 @@ module Puma
|
|
128
148
|
end
|
129
149
|
end
|
130
150
|
|
131
|
-
# The object used for a request with no body. All requests with
|
132
|
-
# no body share this one object since it has no state.
|
133
|
-
EmptyBody = NullIO.new
|
134
|
-
|
135
|
-
def setup_chunked_body(body)
|
136
|
-
@chunked_body = true
|
137
|
-
@partial_part_left = 0
|
138
|
-
@prev_chunk = ""
|
139
|
-
|
140
|
-
@body = Tempfile.new(Const::PUMA_TMP_BASE)
|
141
|
-
@body.binmode
|
142
|
-
@tempfile = @body
|
143
|
-
|
144
|
-
return decode_chunk(body)
|
145
|
-
end
|
146
|
-
|
147
|
-
def decode_chunk(chunk)
|
148
|
-
if @partial_part_left > 0
|
149
|
-
if @partial_part_left <= chunk.size
|
150
|
-
@body << chunk[0..(@partial_part_left-3)] # skip the \r\n
|
151
|
-
chunk = chunk[@partial_part_left..-1]
|
152
|
-
else
|
153
|
-
@body << chunk
|
154
|
-
@partial_part_left -= chunk.size
|
155
|
-
return false
|
156
|
-
end
|
157
|
-
end
|
158
|
-
|
159
|
-
if @prev_chunk.empty?
|
160
|
-
io = StringIO.new(chunk)
|
161
|
-
else
|
162
|
-
io = StringIO.new(@prev_chunk+chunk)
|
163
|
-
@prev_chunk = ""
|
164
|
-
end
|
165
|
-
|
166
|
-
while !io.eof?
|
167
|
-
line = io.gets
|
168
|
-
if line.end_with?("\r\n")
|
169
|
-
len = line.strip.to_i(16)
|
170
|
-
if len == 0
|
171
|
-
@body.rewind
|
172
|
-
rest = io.read
|
173
|
-
rest = rest[2..-1] if rest.start_with?("\r\n")
|
174
|
-
@buffer = rest.empty? ? nil : rest
|
175
|
-
@requests_served += 1
|
176
|
-
@ready = true
|
177
|
-
return true
|
178
|
-
end
|
179
|
-
|
180
|
-
len += 2
|
181
|
-
|
182
|
-
part = io.read(len)
|
183
|
-
|
184
|
-
unless part
|
185
|
-
@partial_part_left = len
|
186
|
-
next
|
187
|
-
end
|
188
|
-
|
189
|
-
got = part.size
|
190
|
-
|
191
|
-
case
|
192
|
-
when got == len
|
193
|
-
@body << part[0..-3] # to skip the ending \r\n
|
194
|
-
when got <= len - 2
|
195
|
-
@body << part
|
196
|
-
@partial_part_left = len - part.size
|
197
|
-
when got == len - 1 # edge where we get just \r but not \n
|
198
|
-
@body << part[0..-2]
|
199
|
-
@partial_part_left = len - part.size
|
200
|
-
end
|
201
|
-
else
|
202
|
-
@prev_chunk = line
|
203
|
-
return false
|
204
|
-
end
|
205
|
-
end
|
206
|
-
|
207
|
-
return false
|
208
|
-
end
|
209
|
-
|
210
|
-
def read_chunked_body
|
211
|
-
while true
|
212
|
-
begin
|
213
|
-
chunk = @io.read_nonblock(4096)
|
214
|
-
rescue Errno::EAGAIN
|
215
|
-
return false
|
216
|
-
rescue SystemCallError, IOError
|
217
|
-
raise ConnectionError, "Connection error detected during read"
|
218
|
-
end
|
219
|
-
|
220
|
-
# No chunk means a closed socket
|
221
|
-
unless chunk
|
222
|
-
@body.close
|
223
|
-
@buffer = nil
|
224
|
-
@requests_served += 1
|
225
|
-
@ready = true
|
226
|
-
raise EOFError
|
227
|
-
end
|
228
|
-
|
229
|
-
return true if decode_chunk(chunk)
|
230
|
-
end
|
231
|
-
end
|
232
|
-
|
233
|
-
def setup_body
|
234
|
-
if @env[HTTP_EXPECT] == CONTINUE
|
235
|
-
# TODO allow a hook here to check the headers before
|
236
|
-
# going forward
|
237
|
-
@io << HTTP_11_100
|
238
|
-
@io.flush
|
239
|
-
end
|
240
|
-
|
241
|
-
@read_header = false
|
242
|
-
|
243
|
-
body = @parser.body
|
244
|
-
|
245
|
-
te = @env[TRANSFER_ENCODING2]
|
246
|
-
|
247
|
-
if te
|
248
|
-
if te.include?(",")
|
249
|
-
te.split(",").each do |part|
|
250
|
-
if CHUNKED.casecmp(part.strip) == 0
|
251
|
-
return setup_chunked_body(body)
|
252
|
-
end
|
253
|
-
end
|
254
|
-
elsif CHUNKED.casecmp(te) == 0
|
255
|
-
return setup_chunked_body(body)
|
256
|
-
end
|
257
|
-
end
|
258
|
-
|
259
|
-
@chunked_body = false
|
260
|
-
|
261
|
-
cl = @env[CONTENT_LENGTH]
|
262
|
-
|
263
|
-
unless cl
|
264
|
-
@buffer = body.empty? ? nil : body
|
265
|
-
@body = EmptyBody
|
266
|
-
@requests_served += 1
|
267
|
-
@ready = true
|
268
|
-
return true
|
269
|
-
end
|
270
|
-
|
271
|
-
remain = cl.to_i - body.bytesize
|
272
|
-
|
273
|
-
if remain <= 0
|
274
|
-
@body = StringIO.new(body)
|
275
|
-
@buffer = nil
|
276
|
-
@requests_served += 1
|
277
|
-
@ready = true
|
278
|
-
return true
|
279
|
-
end
|
280
|
-
|
281
|
-
if remain > MAX_BODY
|
282
|
-
@body = Tempfile.new(Const::PUMA_TMP_BASE)
|
283
|
-
@body.binmode
|
284
|
-
@tempfile = @body
|
285
|
-
else
|
286
|
-
# The body[0,0] trick is to get an empty string in the same
|
287
|
-
# encoding as body.
|
288
|
-
@body = StringIO.new body[0,0]
|
289
|
-
end
|
290
|
-
|
291
|
-
@body.write body
|
292
|
-
|
293
|
-
@body_remain = remain
|
294
|
-
|
295
|
-
return false
|
296
|
-
end
|
297
|
-
|
298
151
|
def try_to_finish
|
299
152
|
return read_body unless @read_header
|
300
153
|
|
301
154
|
begin
|
302
155
|
data = @io.read_nonblock(CHUNK_SIZE)
|
303
|
-
rescue
|
156
|
+
rescue IO::WaitReadable
|
304
157
|
return false
|
305
|
-
rescue SystemCallError, IOError
|
158
|
+
rescue SystemCallError, IOError, EOFError
|
306
159
|
raise ConnectionError, "Connection error detected during read"
|
307
160
|
end
|
308
161
|
|
309
162
|
# No data means a closed socket
|
310
163
|
unless data
|
311
164
|
@buffer = nil
|
312
|
-
|
313
|
-
@ready = true
|
165
|
+
set_ready
|
314
166
|
raise EOFError
|
315
167
|
end
|
316
168
|
|
@@ -346,8 +198,7 @@ module Puma
|
|
346
198
|
# No data means a closed socket
|
347
199
|
unless data
|
348
200
|
@buffer = nil
|
349
|
-
|
350
|
-
@ready = true
|
201
|
+
set_ready
|
351
202
|
raise EOFError
|
352
203
|
end
|
353
204
|
|
@@ -397,6 +248,92 @@ module Puma
|
|
397
248
|
true
|
398
249
|
end
|
399
250
|
|
251
|
+
def write_error(status_code)
|
252
|
+
begin
|
253
|
+
@io << ERROR_RESPONSE[status_code]
|
254
|
+
rescue StandardError
|
255
|
+
end
|
256
|
+
end
|
257
|
+
|
258
|
+
def peerip
|
259
|
+
return @peerip if @peerip
|
260
|
+
|
261
|
+
if @remote_addr_header
|
262
|
+
hdr = (@env[@remote_addr_header] || LOCALHOST_ADDR).split(/[\s,]/).first
|
263
|
+
@peerip = hdr
|
264
|
+
return hdr
|
265
|
+
end
|
266
|
+
|
267
|
+
@peerip ||= @io.peeraddr.last
|
268
|
+
end
|
269
|
+
|
270
|
+
private
|
271
|
+
|
272
|
+
def setup_body
|
273
|
+
@body_read_start = Process.clock_gettime(Process::CLOCK_MONOTONIC, :millisecond)
|
274
|
+
|
275
|
+
if @env[HTTP_EXPECT] == CONTINUE
|
276
|
+
# TODO allow a hook here to check the headers before
|
277
|
+
# going forward
|
278
|
+
@io << HTTP_11_100
|
279
|
+
@io.flush
|
280
|
+
end
|
281
|
+
|
282
|
+
@read_header = false
|
283
|
+
|
284
|
+
body = @parser.body
|
285
|
+
|
286
|
+
te = @env[TRANSFER_ENCODING2]
|
287
|
+
|
288
|
+
if te
|
289
|
+
if te.include?(",")
|
290
|
+
te.split(",").each do |part|
|
291
|
+
if CHUNKED.casecmp(part.strip) == 0
|
292
|
+
return setup_chunked_body(body)
|
293
|
+
end
|
294
|
+
end
|
295
|
+
elsif CHUNKED.casecmp(te) == 0
|
296
|
+
return setup_chunked_body(body)
|
297
|
+
end
|
298
|
+
end
|
299
|
+
|
300
|
+
@chunked_body = false
|
301
|
+
|
302
|
+
cl = @env[CONTENT_LENGTH]
|
303
|
+
|
304
|
+
unless cl
|
305
|
+
@buffer = body.empty? ? nil : body
|
306
|
+
@body = EmptyBody
|
307
|
+
set_ready
|
308
|
+
return true
|
309
|
+
end
|
310
|
+
|
311
|
+
remain = cl.to_i - body.bytesize
|
312
|
+
|
313
|
+
if remain <= 0
|
314
|
+
@body = StringIO.new(body)
|
315
|
+
@buffer = nil
|
316
|
+
set_ready
|
317
|
+
return true
|
318
|
+
end
|
319
|
+
|
320
|
+
if remain > MAX_BODY
|
321
|
+
@body = Tempfile.new(Const::PUMA_TMP_BASE)
|
322
|
+
@body.binmode
|
323
|
+
@tempfile = @body
|
324
|
+
else
|
325
|
+
# The body[0,0] trick is to get an empty string in the same
|
326
|
+
# encoding as body.
|
327
|
+
@body = StringIO.new body[0,0]
|
328
|
+
end
|
329
|
+
|
330
|
+
@body.write body
|
331
|
+
|
332
|
+
@body_remain = remain
|
333
|
+
|
334
|
+
return false
|
335
|
+
end
|
336
|
+
|
400
337
|
def read_body
|
401
338
|
if @chunked_body
|
402
339
|
return read_chunked_body
|
@@ -414,7 +351,7 @@ module Puma
|
|
414
351
|
|
415
352
|
begin
|
416
353
|
chunk = @io.read_nonblock(want)
|
417
|
-
rescue
|
354
|
+
rescue IO::WaitReadable
|
418
355
|
return false
|
419
356
|
rescue SystemCallError, IOError
|
420
357
|
raise ConnectionError, "Connection error detected during read"
|
@@ -424,8 +361,7 @@ module Puma
|
|
424
361
|
unless chunk
|
425
362
|
@body.close
|
426
363
|
@buffer = nil
|
427
|
-
|
428
|
-
@ready = true
|
364
|
+
set_ready
|
429
365
|
raise EOFError
|
430
366
|
end
|
431
367
|
|
@@ -434,8 +370,7 @@ module Puma
|
|
434
370
|
if remain <= 0
|
435
371
|
@body.rewind
|
436
372
|
@buffer = nil
|
437
|
-
|
438
|
-
@ready = true
|
373
|
+
set_ready
|
439
374
|
return true
|
440
375
|
end
|
441
376
|
|
@@ -444,37 +379,136 @@ module Puma
|
|
444
379
|
false
|
445
380
|
end
|
446
381
|
|
447
|
-
def
|
448
|
-
|
449
|
-
|
450
|
-
|
382
|
+
def read_chunked_body
|
383
|
+
while true
|
384
|
+
begin
|
385
|
+
chunk = @io.read_nonblock(4096)
|
386
|
+
rescue IO::WaitReadable
|
387
|
+
return false
|
388
|
+
rescue SystemCallError, IOError
|
389
|
+
raise ConnectionError, "Connection error detected during read"
|
390
|
+
end
|
391
|
+
|
392
|
+
# No chunk means a closed socket
|
393
|
+
unless chunk
|
394
|
+
@body.close
|
395
|
+
@buffer = nil
|
396
|
+
set_ready
|
397
|
+
raise EOFError
|
398
|
+
end
|
399
|
+
|
400
|
+
if decode_chunk(chunk)
|
401
|
+
@env[CONTENT_LENGTH] = @chunked_content_length
|
402
|
+
return true
|
403
|
+
end
|
451
404
|
end
|
452
405
|
end
|
453
406
|
|
454
|
-
def
|
455
|
-
|
456
|
-
|
457
|
-
|
407
|
+
def setup_chunked_body(body)
|
408
|
+
@chunked_body = true
|
409
|
+
@partial_part_left = 0
|
410
|
+
@prev_chunk = ""
|
411
|
+
|
412
|
+
@body = Tempfile.new(Const::PUMA_TMP_BASE)
|
413
|
+
@body.binmode
|
414
|
+
@tempfile = @body
|
415
|
+
|
416
|
+
@chunked_content_length = 0
|
417
|
+
|
418
|
+
if decode_chunk(body)
|
419
|
+
@env[CONTENT_LENGTH] = @chunked_content_length
|
420
|
+
return true
|
458
421
|
end
|
459
422
|
end
|
460
423
|
|
461
|
-
def
|
462
|
-
|
463
|
-
@io << ERROR_500_RESPONSE
|
464
|
-
rescue StandardError
|
465
|
-
end
|
424
|
+
def write_chunk(str)
|
425
|
+
@chunked_content_length += @body.write(str)
|
466
426
|
end
|
467
427
|
|
468
|
-
def
|
469
|
-
|
428
|
+
def decode_chunk(chunk)
|
429
|
+
if @partial_part_left > 0
|
430
|
+
if @partial_part_left <= chunk.size
|
431
|
+
if @partial_part_left > 2
|
432
|
+
write_chunk(chunk[0..(@partial_part_left-3)]) # skip the \r\n
|
433
|
+
end
|
434
|
+
chunk = chunk[@partial_part_left..-1]
|
435
|
+
@partial_part_left = 0
|
436
|
+
else
|
437
|
+
write_chunk(chunk) if @partial_part_left > 2 # don't include the last \r\n
|
438
|
+
@partial_part_left -= chunk.size
|
439
|
+
return false
|
440
|
+
end
|
441
|
+
end
|
470
442
|
|
471
|
-
if @
|
472
|
-
|
473
|
-
|
474
|
-
|
443
|
+
if @prev_chunk.empty?
|
444
|
+
io = StringIO.new(chunk)
|
445
|
+
else
|
446
|
+
io = StringIO.new(@prev_chunk+chunk)
|
447
|
+
@prev_chunk = ""
|
475
448
|
end
|
476
449
|
|
477
|
-
|
450
|
+
while !io.eof?
|
451
|
+
line = io.gets
|
452
|
+
if line.end_with?("\r\n")
|
453
|
+
len = line.strip.to_i(16)
|
454
|
+
if len == 0
|
455
|
+
@in_last_chunk = true
|
456
|
+
@body.rewind
|
457
|
+
rest = io.read
|
458
|
+
last_crlf_size = "\r\n".bytesize
|
459
|
+
if rest.bytesize < last_crlf_size
|
460
|
+
@buffer = nil
|
461
|
+
@partial_part_left = last_crlf_size - rest.bytesize
|
462
|
+
return false
|
463
|
+
else
|
464
|
+
@buffer = rest[last_crlf_size..-1]
|
465
|
+
@buffer = nil if @buffer.empty?
|
466
|
+
set_ready
|
467
|
+
return true
|
468
|
+
end
|
469
|
+
end
|
470
|
+
|
471
|
+
len += 2
|
472
|
+
|
473
|
+
part = io.read(len)
|
474
|
+
|
475
|
+
unless part
|
476
|
+
@partial_part_left = len
|
477
|
+
next
|
478
|
+
end
|
479
|
+
|
480
|
+
got = part.size
|
481
|
+
|
482
|
+
case
|
483
|
+
when got == len
|
484
|
+
write_chunk(part[0..-3]) # to skip the ending \r\n
|
485
|
+
when got <= len - 2
|
486
|
+
write_chunk(part)
|
487
|
+
@partial_part_left = len - part.size
|
488
|
+
when got == len - 1 # edge where we get just \r but not \n
|
489
|
+
write_chunk(part[0..-2])
|
490
|
+
@partial_part_left = len - part.size
|
491
|
+
end
|
492
|
+
else
|
493
|
+
@prev_chunk = line
|
494
|
+
return false
|
495
|
+
end
|
496
|
+
end
|
497
|
+
|
498
|
+
if @in_last_chunk
|
499
|
+
set_ready
|
500
|
+
true
|
501
|
+
else
|
502
|
+
false
|
503
|
+
end
|
504
|
+
end
|
505
|
+
|
506
|
+
def set_ready
|
507
|
+
if @body_read_start
|
508
|
+
@env['puma.request_body_wait'] = Process.clock_gettime(Process::CLOCK_MONOTONIC, :millisecond) - @body_read_start
|
509
|
+
end
|
510
|
+
@requests_served += 1
|
511
|
+
@ready = true
|
478
512
|
end
|
479
513
|
end
|
480
514
|
end
|