puma 2.0.0.b5 → 5.0.0.beta1
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 +7 -0
- data/History.md +1598 -0
- data/LICENSE +23 -20
- data/README.md +222 -62
- data/bin/puma-wild +31 -0
- data/bin/pumactl +1 -1
- data/docs/architecture.md +37 -0
- data/docs/deployment.md +113 -0
- data/docs/fork_worker.md +31 -0
- 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/jungle/README.md +13 -0
- data/docs/jungle/rc.d/README.md +74 -0
- data/docs/jungle/rc.d/puma +61 -0
- data/docs/jungle/rc.d/puma.conf +10 -0
- data/docs/jungle/upstart/README.md +61 -0
- data/docs/jungle/upstart/puma-manager.conf +31 -0
- data/docs/jungle/upstart/puma.conf +69 -0
- data/docs/nginx.md +5 -10
- data/docs/plugins.md +38 -0
- data/docs/restart.md +41 -0
- data/docs/signals.md +97 -0
- data/docs/systemd.md +228 -0
- data/ext/puma_http11/PumaHttp11Service.java +2 -2
- data/ext/puma_http11/extconf.rb +23 -2
- data/ext/puma_http11/http11_parser.c +301 -482
- data/ext/puma_http11/http11_parser.h +13 -11
- data/ext/puma_http11/http11_parser.java.rl +26 -42
- data/ext/puma_http11/http11_parser.rl +22 -21
- data/ext/puma_http11/http11_parser_common.rl +5 -5
- data/ext/puma_http11/mini_ssl.c +377 -18
- data/ext/puma_http11/org/jruby/puma/Http11.java +108 -107
- data/ext/puma_http11/org/jruby/puma/Http11Parser.java +137 -170
- data/ext/puma_http11/org/jruby/puma/MiniSSL.java +265 -191
- data/ext/puma_http11/puma_http11.c +57 -81
- data/lib/puma.rb +25 -4
- data/lib/puma/accept_nonblock.rb +7 -1
- data/lib/puma/app/status.rb +61 -24
- data/lib/puma/binder.rb +212 -78
- data/lib/puma/cli.rb +149 -644
- data/lib/puma/client.rb +316 -65
- data/lib/puma/cluster.rb +659 -0
- data/lib/puma/commonlogger.rb +108 -0
- data/lib/puma/configuration.rb +279 -180
- data/lib/puma/const.rb +126 -39
- data/lib/puma/control_cli.rb +183 -96
- data/lib/puma/detect.rb +20 -1
- data/lib/puma/dsl.rb +776 -0
- data/lib/puma/events.rb +91 -23
- data/lib/puma/io_buffer.rb +9 -5
- data/lib/puma/jruby_restart.rb +9 -5
- data/lib/puma/launcher.rb +487 -0
- data/lib/puma/minissl.rb +239 -93
- data/lib/puma/minissl/context_builder.rb +76 -0
- data/lib/puma/null_io.rb +22 -12
- data/lib/puma/plugin.rb +111 -0
- data/lib/puma/plugin/tmp_restart.rb +36 -0
- data/lib/puma/rack/builder.rb +297 -0
- data/lib/puma/rack/urlmap.rb +93 -0
- data/lib/puma/rack_default.rb +9 -0
- data/lib/puma/reactor.rb +290 -43
- data/lib/puma/runner.rb +163 -0
- data/lib/puma/server.rb +493 -126
- data/lib/puma/single.rb +66 -0
- data/lib/puma/state_file.rb +34 -0
- data/lib/puma/thread_pool.rb +228 -47
- data/lib/puma/util.rb +115 -0
- data/lib/rack/handler/puma.rb +78 -31
- data/tools/Dockerfile +16 -0
- data/tools/trickletest.rb +44 -0
- metadata +60 -155
- data/COPYING +0 -55
- data/Gemfile +0 -8
- data/History.txt +0 -196
- data/Manifest.txt +0 -56
- data/Rakefile +0 -121
- data/TODO +0 -5
- data/docs/config.md +0 -0
- data/ext/puma_http11/io_buffer.c +0 -154
- data/lib/puma/capistrano.rb +0 -26
- data/lib/puma/compat.rb +0 -11
- data/lib/puma/daemon_ext.rb +0 -20
- data/lib/puma/delegation.rb +0 -11
- data/lib/puma/java_io_buffer.rb +0 -45
- data/lib/puma/rack_patch.rb +0 -25
- data/puma.gemspec +0 -45
- data/test/test_app_status.rb +0 -88
- data/test/test_cli.rb +0 -171
- data/test/test_config.rb +0 -16
- data/test/test_http10.rb +0 -27
- data/test/test_http11.rb +0 -126
- data/test/test_integration.rb +0 -150
- data/test/test_iobuffer.rb +0 -38
- data/test/test_minissl.rb +0 -22
- data/test/test_null_io.rb +0 -31
- data/test/test_persistent.rb +0 -238
- data/test/test_puma_server.rb +0 -128
- data/test/test_rack_handler.rb +0 -10
- data/test/test_rack_server.rb +0 -141
- data/test/test_thread_pool.rb +0 -146
- data/test/test_unix_socket.rb +0 -39
- data/test/test_ws.rb +0 -89
- data/tools/jungle/README.md +0 -54
- data/tools/jungle/puma +0 -332
- data/tools/jungle/run-puma +0 -3
data/lib/puma/client.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
class IO
|
2
4
|
# We need to use this for a jruby work around on both 1.8 and 1.9.
|
3
5
|
# So this either creates the constant (on 1.8), or harmlessly
|
@@ -7,6 +9,8 @@ class IO
|
|
7
9
|
end
|
8
10
|
|
9
11
|
require 'puma/detect'
|
12
|
+
require 'tempfile'
|
13
|
+
require 'forwardable'
|
10
14
|
|
11
15
|
if Puma::IS_JRUBY
|
12
16
|
# We have to work around some OpenSSL buffer/io-readiness bugs
|
@@ -16,14 +20,38 @@ if Puma::IS_JRUBY
|
|
16
20
|
end
|
17
21
|
|
18
22
|
module Puma
|
23
|
+
|
24
|
+
class ConnectionError < RuntimeError; end
|
25
|
+
|
26
|
+
# An instance of this class represents a unique request from a client.
|
27
|
+
# For example, this could be a web request from a browser or from CURL.
|
28
|
+
#
|
29
|
+
# An instance of `Puma::Client` can be used as if it were an IO object
|
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.
|
34
|
+
#
|
35
|
+
# Instances of this class are responsible for knowing if
|
36
|
+
# the header and body are fully buffered via the `try_to_finish` method.
|
37
|
+
# They can be used to "time out" a response via the `timeout_at` reader.
|
19
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
|
+
|
20
43
|
include Puma::Const
|
44
|
+
extend Forwardable
|
21
45
|
|
22
|
-
def initialize(io, env)
|
46
|
+
def initialize(io, env=nil)
|
23
47
|
@io = io
|
24
48
|
@to_io = io.to_io
|
25
49
|
@proto_env = env
|
26
|
-
|
50
|
+
if !env
|
51
|
+
@env = nil
|
52
|
+
else
|
53
|
+
@env = env.dup
|
54
|
+
end
|
27
55
|
|
28
56
|
@parser = HttpParser.new
|
29
57
|
@parsed_bytes = 0
|
@@ -31,15 +59,31 @@ module Puma
|
|
31
59
|
@ready = false
|
32
60
|
|
33
61
|
@body = nil
|
62
|
+
@body_read_start = nil
|
34
63
|
@buffer = nil
|
64
|
+
@tempfile = nil
|
35
65
|
|
36
66
|
@timeout_at = nil
|
37
67
|
|
38
68
|
@requests_served = 0
|
39
69
|
@hijacked = false
|
70
|
+
|
71
|
+
@peerip = nil
|
72
|
+
@remote_addr_header = nil
|
73
|
+
|
74
|
+
@body_remain = 0
|
75
|
+
|
76
|
+
@in_last_chunk = false
|
40
77
|
end
|
41
78
|
|
42
|
-
attr_reader :env, :to_io, :body, :io, :timeout_at, :ready, :hijacked
|
79
|
+
attr_reader :env, :to_io, :body, :io, :timeout_at, :ready, :hijacked,
|
80
|
+
:tempfile
|
81
|
+
|
82
|
+
attr_writer :peerip
|
83
|
+
|
84
|
+
attr_accessor :remote_addr_header
|
85
|
+
|
86
|
+
def_delegators :@io, :closed?
|
43
87
|
|
44
88
|
def inspect
|
45
89
|
"#<Puma::Client:0x#{object_id.to_s(16)} @ready=#{@ready.inspect}>"
|
@@ -52,6 +96,10 @@ module Puma
|
|
52
96
|
env[HIJACK_IO] ||= @io
|
53
97
|
end
|
54
98
|
|
99
|
+
def in_data_phase
|
100
|
+
!@read_header
|
101
|
+
end
|
102
|
+
|
55
103
|
def set_timeout(val)
|
56
104
|
@timeout_at = Time.now + val
|
57
105
|
end
|
@@ -61,8 +109,12 @@ module Puma
|
|
61
109
|
@read_header = true
|
62
110
|
@env = @proto_env.dup
|
63
111
|
@body = nil
|
112
|
+
@tempfile = nil
|
64
113
|
@parsed_bytes = 0
|
65
114
|
@ready = false
|
115
|
+
@body_remain = 0
|
116
|
+
@peerip = nil
|
117
|
+
@in_last_chunk = false
|
66
118
|
|
67
119
|
if @buffer
|
68
120
|
@parsed_bytes = @parser.execute(@env, @buffer, @parsed_bytes)
|
@@ -75,9 +127,16 @@ module Puma
|
|
75
127
|
end
|
76
128
|
|
77
129
|
return false
|
78
|
-
|
79
|
-
|
80
|
-
|
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
|
+
|
81
140
|
end
|
82
141
|
end
|
83
142
|
|
@@ -85,60 +144,26 @@ module Puma
|
|
85
144
|
begin
|
86
145
|
@io.close
|
87
146
|
rescue IOError
|
147
|
+
Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
|
88
148
|
end
|
89
149
|
end
|
90
150
|
|
91
|
-
# The object used for a request with no body. All requests with
|
92
|
-
# no body share this one object since it has no state.
|
93
|
-
EmptyBody = NullIO.new
|
94
|
-
|
95
|
-
def setup_body
|
96
|
-
body = @parser.body
|
97
|
-
cl = @env[CONTENT_LENGTH]
|
98
|
-
|
99
|
-
unless cl
|
100
|
-
@buffer = body.empty? ? nil : body
|
101
|
-
@body = EmptyBody
|
102
|
-
@requests_served += 1
|
103
|
-
@ready = true
|
104
|
-
return true
|
105
|
-
end
|
106
|
-
|
107
|
-
remain = cl.to_i - body.bytesize
|
108
|
-
|
109
|
-
if remain <= 0
|
110
|
-
@body = StringIO.new(body)
|
111
|
-
@buffer = nil
|
112
|
-
@requests_served += 1
|
113
|
-
@ready = true
|
114
|
-
return true
|
115
|
-
end
|
116
|
-
|
117
|
-
if remain > MAX_BODY
|
118
|
-
@body = Tempfile.new(Const::PUMA_TMP_BASE)
|
119
|
-
@body.binmode
|
120
|
-
else
|
121
|
-
# The body[0,0] trick is to get an empty string in the same
|
122
|
-
# encoding as body.
|
123
|
-
@body = StringIO.new body[0,0]
|
124
|
-
end
|
125
|
-
|
126
|
-
@body.write body
|
127
|
-
|
128
|
-
@body_remain = remain
|
129
|
-
|
130
|
-
@read_header = false
|
131
|
-
|
132
|
-
return false
|
133
|
-
end
|
134
|
-
|
135
151
|
def try_to_finish
|
136
152
|
return read_body unless @read_header
|
137
153
|
|
138
154
|
begin
|
139
155
|
data = @io.read_nonblock(CHUNK_SIZE)
|
140
|
-
rescue
|
156
|
+
rescue IO::WaitReadable
|
141
157
|
return false
|
158
|
+
rescue SystemCallError, IOError, EOFError
|
159
|
+
raise ConnectionError, "Connection error detected during read"
|
160
|
+
end
|
161
|
+
|
162
|
+
# No data means a closed socket
|
163
|
+
unless data
|
164
|
+
@buffer = nil
|
165
|
+
set_ready
|
166
|
+
raise EOFError
|
142
167
|
end
|
143
168
|
|
144
169
|
if @buffer
|
@@ -155,7 +180,7 @@ module Puma
|
|
155
180
|
raise HttpParserError,
|
156
181
|
"HEADER is longer than allowed, aborting client early."
|
157
182
|
end
|
158
|
-
|
183
|
+
|
159
184
|
false
|
160
185
|
end
|
161
186
|
|
@@ -170,6 +195,13 @@ module Puma
|
|
170
195
|
raise e
|
171
196
|
end
|
172
197
|
|
198
|
+
# No data means a closed socket
|
199
|
+
unless data
|
200
|
+
@buffer = nil
|
201
|
+
set_ready
|
202
|
+
raise EOFError
|
203
|
+
end
|
204
|
+
|
173
205
|
if @buffer
|
174
206
|
@buffer << data
|
175
207
|
else
|
@@ -206,9 +238,122 @@ module Puma
|
|
206
238
|
return false unless IO.select([@to_io], nil, nil, 0)
|
207
239
|
try_to_finish
|
208
240
|
end
|
241
|
+
|
242
|
+
# For documentation, see https://github.com/puma/puma/issues/1754
|
243
|
+
send(:alias_method, :jruby_eagerly_finish, :eagerly_finish)
|
209
244
|
end # IS_JRUBY
|
210
245
|
|
246
|
+
def finish(timeout)
|
247
|
+
return true if @ready
|
248
|
+
until try_to_finish
|
249
|
+
can_read = begin
|
250
|
+
IO.select([@to_io], nil, nil, timeout)
|
251
|
+
rescue ThreadPool::ForceShutdown
|
252
|
+
nil
|
253
|
+
end
|
254
|
+
unless can_read
|
255
|
+
write_error(408) if in_data_phase
|
256
|
+
raise ConnectionError
|
257
|
+
end
|
258
|
+
end
|
259
|
+
true
|
260
|
+
end
|
261
|
+
|
262
|
+
def write_error(status_code)
|
263
|
+
begin
|
264
|
+
@io << ERROR_RESPONSE[status_code]
|
265
|
+
rescue StandardError
|
266
|
+
end
|
267
|
+
end
|
268
|
+
|
269
|
+
def peerip
|
270
|
+
return @peerip if @peerip
|
271
|
+
|
272
|
+
if @remote_addr_header
|
273
|
+
hdr = (@env[@remote_addr_header] || LOCALHOST_IP).split(/[\s,]/).first
|
274
|
+
@peerip = hdr
|
275
|
+
return hdr
|
276
|
+
end
|
277
|
+
|
278
|
+
@peerip ||= @io.peeraddr.last
|
279
|
+
end
|
280
|
+
|
281
|
+
# Returns true if the persistent connection can be closed immediately
|
282
|
+
# without waiting for the configured idle/shutdown timeout.
|
283
|
+
def can_close?
|
284
|
+
# Allow connection to close if it's received at least one full request
|
285
|
+
# and hasn't received any data for a future request.
|
286
|
+
#
|
287
|
+
# From RFC 2616 section 8.1.4:
|
288
|
+
# Servers SHOULD always respond to at least one request per connection,
|
289
|
+
# if at all possible.
|
290
|
+
@requests_served > 0 && @parsed_bytes == 0
|
291
|
+
end
|
292
|
+
|
293
|
+
private
|
294
|
+
|
295
|
+
def setup_body
|
296
|
+
@body_read_start = Process.clock_gettime(Process::CLOCK_MONOTONIC, :millisecond)
|
297
|
+
|
298
|
+
if @env[HTTP_EXPECT] == CONTINUE
|
299
|
+
# TODO allow a hook here to check the headers before
|
300
|
+
# going forward
|
301
|
+
@io << HTTP_11_100
|
302
|
+
@io.flush
|
303
|
+
end
|
304
|
+
|
305
|
+
@read_header = false
|
306
|
+
|
307
|
+
body = @parser.body
|
308
|
+
|
309
|
+
te = @env[TRANSFER_ENCODING2]
|
310
|
+
|
311
|
+
if te && CHUNKED.casecmp(te) == 0
|
312
|
+
return setup_chunked_body(body)
|
313
|
+
end
|
314
|
+
|
315
|
+
@chunked_body = false
|
316
|
+
|
317
|
+
cl = @env[CONTENT_LENGTH]
|
318
|
+
|
319
|
+
unless cl
|
320
|
+
@buffer = body.empty? ? nil : body
|
321
|
+
@body = EmptyBody
|
322
|
+
set_ready
|
323
|
+
return true
|
324
|
+
end
|
325
|
+
|
326
|
+
remain = cl.to_i - body.bytesize
|
327
|
+
|
328
|
+
if remain <= 0
|
329
|
+
@body = StringIO.new(body)
|
330
|
+
@buffer = nil
|
331
|
+
set_ready
|
332
|
+
return true
|
333
|
+
end
|
334
|
+
|
335
|
+
if remain > MAX_BODY
|
336
|
+
@body = Tempfile.new(Const::PUMA_TMP_BASE)
|
337
|
+
@body.binmode
|
338
|
+
@tempfile = @body
|
339
|
+
else
|
340
|
+
# The body[0,0] trick is to get an empty string in the same
|
341
|
+
# encoding as body.
|
342
|
+
@body = StringIO.new body[0,0]
|
343
|
+
end
|
344
|
+
|
345
|
+
@body.write body
|
346
|
+
|
347
|
+
@body_remain = remain
|
348
|
+
|
349
|
+
return false
|
350
|
+
end
|
351
|
+
|
211
352
|
def read_body
|
353
|
+
if @chunked_body
|
354
|
+
return read_chunked_body
|
355
|
+
end
|
356
|
+
|
212
357
|
# Read an odd sized chunk so we can read even sized ones
|
213
358
|
# after this
|
214
359
|
remain = @body_remain
|
@@ -221,16 +366,17 @@ module Puma
|
|
221
366
|
|
222
367
|
begin
|
223
368
|
chunk = @io.read_nonblock(want)
|
224
|
-
rescue
|
369
|
+
rescue IO::WaitReadable
|
225
370
|
return false
|
371
|
+
rescue SystemCallError, IOError
|
372
|
+
raise ConnectionError, "Connection error detected during read"
|
226
373
|
end
|
227
374
|
|
228
375
|
# No chunk means a closed socket
|
229
376
|
unless chunk
|
230
377
|
@body.close
|
231
378
|
@buffer = nil
|
232
|
-
|
233
|
-
@ready = true
|
379
|
+
set_ready
|
234
380
|
raise EOFError
|
235
381
|
end
|
236
382
|
|
@@ -239,8 +385,7 @@ module Puma
|
|
239
385
|
if remain <= 0
|
240
386
|
@body.rewind
|
241
387
|
@buffer = nil
|
242
|
-
|
243
|
-
@ready = true
|
388
|
+
set_ready
|
244
389
|
return true
|
245
390
|
end
|
246
391
|
|
@@ -249,18 +394,124 @@ module Puma
|
|
249
394
|
false
|
250
395
|
end
|
251
396
|
|
252
|
-
def
|
253
|
-
|
254
|
-
|
255
|
-
|
397
|
+
def read_chunked_body
|
398
|
+
while true
|
399
|
+
begin
|
400
|
+
chunk = @io.read_nonblock(4096)
|
401
|
+
rescue IO::WaitReadable
|
402
|
+
return false
|
403
|
+
rescue SystemCallError, IOError
|
404
|
+
raise ConnectionError, "Connection error detected during read"
|
405
|
+
end
|
406
|
+
|
407
|
+
# No chunk means a closed socket
|
408
|
+
unless chunk
|
409
|
+
@body.close
|
410
|
+
@buffer = nil
|
411
|
+
set_ready
|
412
|
+
raise EOFError
|
413
|
+
end
|
414
|
+
|
415
|
+
return true if decode_chunk(chunk)
|
256
416
|
end
|
257
417
|
end
|
258
418
|
|
259
|
-
def
|
260
|
-
|
261
|
-
|
262
|
-
|
419
|
+
def setup_chunked_body(body)
|
420
|
+
@chunked_body = true
|
421
|
+
@partial_part_left = 0
|
422
|
+
@prev_chunk = ""
|
423
|
+
|
424
|
+
@body = Tempfile.new(Const::PUMA_TMP_BASE)
|
425
|
+
@body.binmode
|
426
|
+
@tempfile = @body
|
427
|
+
|
428
|
+
return decode_chunk(body)
|
429
|
+
end
|
430
|
+
|
431
|
+
def decode_chunk(chunk)
|
432
|
+
if @partial_part_left > 0
|
433
|
+
if @partial_part_left <= chunk.size
|
434
|
+
if @partial_part_left > 2
|
435
|
+
@body << chunk[0..(@partial_part_left-3)] # skip the \r\n
|
436
|
+
end
|
437
|
+
chunk = chunk[@partial_part_left..-1]
|
438
|
+
@partial_part_left = 0
|
439
|
+
else
|
440
|
+
@body << chunk if @partial_part_left > 2 # don't include the last \r\n
|
441
|
+
@partial_part_left -= chunk.size
|
442
|
+
return false
|
443
|
+
end
|
444
|
+
end
|
445
|
+
|
446
|
+
if @prev_chunk.empty?
|
447
|
+
io = StringIO.new(chunk)
|
448
|
+
else
|
449
|
+
io = StringIO.new(@prev_chunk+chunk)
|
450
|
+
@prev_chunk = ""
|
451
|
+
end
|
452
|
+
|
453
|
+
while !io.eof?
|
454
|
+
line = io.gets
|
455
|
+
if line.end_with?("\r\n")
|
456
|
+
len = line.strip.to_i(16)
|
457
|
+
if len == 0
|
458
|
+
@in_last_chunk = true
|
459
|
+
@body.rewind
|
460
|
+
rest = io.read
|
461
|
+
last_crlf_size = "\r\n".bytesize
|
462
|
+
if rest.bytesize < last_crlf_size
|
463
|
+
@buffer = nil
|
464
|
+
@partial_part_left = last_crlf_size - rest.bytesize
|
465
|
+
return false
|
466
|
+
else
|
467
|
+
@buffer = rest[last_crlf_size..-1]
|
468
|
+
@buffer = nil if @buffer.empty?
|
469
|
+
set_ready
|
470
|
+
return true
|
471
|
+
end
|
472
|
+
end
|
473
|
+
|
474
|
+
len += 2
|
475
|
+
|
476
|
+
part = io.read(len)
|
477
|
+
|
478
|
+
unless part
|
479
|
+
@partial_part_left = len
|
480
|
+
next
|
481
|
+
end
|
482
|
+
|
483
|
+
got = part.size
|
484
|
+
|
485
|
+
case
|
486
|
+
when got == len
|
487
|
+
@body << part[0..-3] # to skip the ending \r\n
|
488
|
+
when got <= len - 2
|
489
|
+
@body << part
|
490
|
+
@partial_part_left = len - part.size
|
491
|
+
when got == len - 1 # edge where we get just \r but not \n
|
492
|
+
@body << part[0..-2]
|
493
|
+
@partial_part_left = len - part.size
|
494
|
+
end
|
495
|
+
else
|
496
|
+
@prev_chunk = line
|
497
|
+
return false
|
498
|
+
end
|
499
|
+
end
|
500
|
+
|
501
|
+
if @in_last_chunk
|
502
|
+
set_ready
|
503
|
+
true
|
504
|
+
else
|
505
|
+
false
|
506
|
+
end
|
507
|
+
end
|
508
|
+
|
509
|
+
def set_ready
|
510
|
+
if @body_read_start
|
511
|
+
@env['puma.request_body_wait'] = Process.clock_gettime(Process::CLOCK_MONOTONIC, :millisecond) - @body_read_start
|
263
512
|
end
|
513
|
+
@requests_served += 1
|
514
|
+
@ready = true
|
264
515
|
end
|
265
516
|
end
|
266
517
|
end
|