puma 3.9.1 → 4.3.1
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 +5 -5
- data/History.md +232 -0
- data/README.md +162 -224
- data/docs/architecture.md +37 -0
- data/{DEPLOYMENT.md → 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 +38 -0
- data/docs/restart.md +41 -0
- data/docs/signals.md +56 -3
- data/docs/systemd.md +130 -37
- 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 +115 -140
- data/ext/puma_http11/http11_parser.java.rl +21 -37
- data/ext/puma_http11/http11_parser.rl +9 -9
- data/ext/puma_http11/http11_parser_common.rl +3 -3
- data/ext/puma_http11/mini_ssl.c +104 -8
- data/ext/puma_http11/org/jruby/puma/Http11.java +106 -114
- data/ext/puma_http11/org/jruby/puma/Http11Parser.java +90 -108
- data/ext/puma_http11/org/jruby/puma/IOBuffer.java +72 -0
- data/ext/puma_http11/org/jruby/puma/MiniSSL.java +21 -4
- data/ext/puma_http11/puma_http11.c +2 -0
- data/lib/puma.rb +16 -0
- data/lib/puma/accept_nonblock.rb +7 -1
- data/lib/puma/app/status.rb +40 -26
- data/lib/puma/binder.rb +57 -74
- data/lib/puma/cli.rb +26 -7
- data/lib/puma/client.rb +243 -190
- data/lib/puma/cluster.rb +78 -34
- data/lib/puma/commonlogger.rb +2 -0
- data/lib/puma/configuration.rb +24 -16
- data/lib/puma/const.rb +36 -18
- data/lib/puma/control_cli.rb +46 -19
- data/lib/puma/detect.rb +2 -0
- data/lib/puma/dsl.rb +329 -68
- data/lib/puma/events.rb +6 -1
- data/lib/puma/io_buffer.rb +3 -6
- data/lib/puma/jruby_restart.rb +2 -1
- data/lib/puma/launcher.rb +120 -58
- data/lib/puma/minissl.rb +69 -27
- data/lib/puma/minissl/context_builder.rb +76 -0
- data/lib/puma/null_io.rb +2 -0
- data/lib/puma/plugin.rb +7 -2
- data/lib/puma/plugin/tmp_restart.rb +2 -1
- data/lib/puma/rack/builder.rb +4 -1
- data/lib/puma/rack/urlmap.rb +2 -0
- data/lib/puma/rack_default.rb +2 -0
- data/lib/puma/reactor.rb +224 -34
- data/lib/puma/runner.rb +25 -4
- data/lib/puma/server.rb +148 -62
- data/lib/puma/single.rb +16 -5
- data/lib/puma/state_file.rb +2 -0
- data/lib/puma/tcp_logger.rb +2 -0
- data/lib/puma/thread_pool.rb +61 -38
- data/lib/puma/util.rb +2 -6
- data/lib/rack/handler/puma.rb +10 -4
- data/tools/docker/Dockerfile +16 -0
- data/tools/jungle/README.md +12 -2
- data/tools/jungle/init.d/README.md +2 -0
- data/tools/jungle/init.d/puma +8 -8
- data/tools/jungle/init.d/run-puma +1 -1
- data/tools/jungle/rc.d/README.md +74 -0
- data/tools/jungle/rc.d/puma +61 -0
- data/tools/jungle/rc.d/puma.conf +10 -0
- data/tools/trickletest.rb +1 -2
- metadata +29 -56
- data/.github/issue_template.md +0 -20
- data/Gemfile +0 -14
- data/Manifest.txt +0 -78
- data/Rakefile +0 -165
- data/Release.md +0 -9
- data/gemfiles/2.1-Gemfile +0 -12
- data/lib/puma/compat.rb +0 -14
- data/lib/puma/convenient.rb +0 -23
- data/lib/puma/daemon_ext.rb +0 -31
- data/lib/puma/delegation.rb +0 -11
- data/lib/puma/java_io_buffer.rb +0 -45
- data/lib/puma/rack/backports/uri/common_193.rb +0 -33
- data/puma.gemspec +0 -20
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,8 +9,8 @@ class IO
|
|
7
9
|
end
|
8
10
|
|
9
11
|
require 'puma/detect'
|
10
|
-
require 'puma/delegation'
|
11
12
|
require 'tempfile'
|
13
|
+
require 'forwardable'
|
12
14
|
|
13
15
|
if Puma::IS_JRUBY
|
14
16
|
# We have to work around some OpenSSL buffer/io-readiness bugs
|
@@ -21,9 +23,25 @@ module Puma
|
|
21
23
|
|
22
24
|
class ConnectionError < RuntimeError; end
|
23
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.
|
24
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
|
+
|
25
43
|
include Puma::Const
|
26
|
-
extend
|
44
|
+
extend Forwardable
|
27
45
|
|
28
46
|
def initialize(io, env=nil)
|
29
47
|
@io = io
|
@@ -41,6 +59,7 @@ module Puma
|
|
41
59
|
@ready = false
|
42
60
|
|
43
61
|
@body = nil
|
62
|
+
@body_read_start = nil
|
44
63
|
@buffer = nil
|
45
64
|
@tempfile = nil
|
46
65
|
|
@@ -51,6 +70,10 @@ module Puma
|
|
51
70
|
|
52
71
|
@peerip = nil
|
53
72
|
@remote_addr_header = nil
|
73
|
+
|
74
|
+
@body_remain = 0
|
75
|
+
|
76
|
+
@in_last_chunk = false
|
54
77
|
end
|
55
78
|
|
56
79
|
attr_reader :env, :to_io, :body, :io, :timeout_at, :ready, :hijacked,
|
@@ -60,7 +83,7 @@ module Puma
|
|
60
83
|
|
61
84
|
attr_accessor :remote_addr_header
|
62
85
|
|
63
|
-
|
86
|
+
def_delegators :@io, :closed?
|
64
87
|
|
65
88
|
def inspect
|
66
89
|
"#<Puma::Client:0x#{object_id.to_s(16)} @ready=#{@ready.inspect}>"
|
@@ -89,6 +112,9 @@ module Puma
|
|
89
112
|
@tempfile = nil
|
90
113
|
@parsed_bytes = 0
|
91
114
|
@ready = false
|
115
|
+
@body_remain = 0
|
116
|
+
@peerip = nil
|
117
|
+
@in_last_chunk = false
|
92
118
|
|
93
119
|
if @buffer
|
94
120
|
@parsed_bytes = @parser.execute(@env, @buffer, @parsed_bytes)
|
@@ -101,175 +127,25 @@ module Puma
|
|
101
127
|
end
|
102
128
|
|
103
129
|
return false
|
104
|
-
elsif fast_check &&
|
105
|
-
IO.select([@to_io], nil, nil, FAST_TRACK_KA_TIMEOUT)
|
106
|
-
return try_to_finish
|
107
|
-
end
|
108
|
-
end
|
109
|
-
|
110
|
-
def close
|
111
|
-
begin
|
112
|
-
@io.close
|
113
|
-
rescue IOError
|
114
|
-
end
|
115
|
-
end
|
116
|
-
|
117
|
-
# The object used for a request with no body. All requests with
|
118
|
-
# no body share this one object since it has no state.
|
119
|
-
EmptyBody = NullIO.new
|
120
|
-
|
121
|
-
def setup_chunked_body(body)
|
122
|
-
@chunked_body = true
|
123
|
-
@partial_part_left = 0
|
124
|
-
@prev_chunk = ""
|
125
|
-
|
126
|
-
@body = Tempfile.new(Const::PUMA_TMP_BASE)
|
127
|
-
@body.binmode
|
128
|
-
@tempfile = @body
|
129
|
-
|
130
|
-
return decode_chunk(body)
|
131
|
-
end
|
132
|
-
|
133
|
-
def decode_chunk(chunk)
|
134
|
-
if @partial_part_left > 0
|
135
|
-
if @partial_part_left <= chunk.size
|
136
|
-
@body << chunk[0..(@partial_part_left-3)] # skip the \r\n
|
137
|
-
chunk = chunk[@partial_part_left..-1]
|
138
|
-
else
|
139
|
-
@body << chunk
|
140
|
-
@partial_part_left -= chunk.size
|
141
|
-
return false
|
142
|
-
end
|
143
|
-
end
|
144
|
-
|
145
|
-
if @prev_chunk.empty?
|
146
|
-
io = StringIO.new(chunk)
|
147
130
|
else
|
148
|
-
io = StringIO.new(@prev_chunk+chunk)
|
149
|
-
@prev_chunk = ""
|
150
|
-
end
|
151
|
-
|
152
|
-
while !io.eof?
|
153
|
-
line = io.gets
|
154
|
-
if line.end_with?("\r\n")
|
155
|
-
len = line.strip.to_i(16)
|
156
|
-
if len == 0
|
157
|
-
@body.rewind
|
158
|
-
rest = io.read
|
159
|
-
@buffer = rest.empty? ? nil : rest
|
160
|
-
@requests_served += 1
|
161
|
-
@ready = true
|
162
|
-
return true
|
163
|
-
end
|
164
|
-
|
165
|
-
len += 2
|
166
|
-
|
167
|
-
part = io.read(len)
|
168
|
-
|
169
|
-
unless part
|
170
|
-
@partial_part_left = len
|
171
|
-
next
|
172
|
-
end
|
173
|
-
|
174
|
-
got = part.size
|
175
|
-
|
176
|
-
case
|
177
|
-
when got == len
|
178
|
-
@body << part[0..-3] # to skip the ending \r\n
|
179
|
-
when got <= len - 2
|
180
|
-
@body << part
|
181
|
-
@partial_part_left = len - part.size
|
182
|
-
when got == len - 1 # edge where we get just \r but not \n
|
183
|
-
@body << part[0..-2]
|
184
|
-
@partial_part_left = len - part.size
|
185
|
-
end
|
186
|
-
else
|
187
|
-
@prev_chunk = line
|
188
|
-
return false
|
189
|
-
end
|
190
|
-
end
|
191
|
-
|
192
|
-
return false
|
193
|
-
end
|
194
|
-
|
195
|
-
def read_chunked_body
|
196
|
-
while true
|
197
131
|
begin
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
# No chunk means a closed socket
|
206
|
-
unless chunk
|
207
|
-
@body.close
|
208
|
-
@buffer = nil
|
209
|
-
@requests_served += 1
|
210
|
-
@ready = true
|
211
|
-
raise EOFError
|
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
|
212
138
|
end
|
213
139
|
|
214
|
-
return true if decode_chunk(chunk)
|
215
140
|
end
|
216
141
|
end
|
217
142
|
|
218
|
-
def
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
@io.flush
|
224
|
-
end
|
225
|
-
|
226
|
-
@read_header = false
|
227
|
-
|
228
|
-
body = @parser.body
|
229
|
-
|
230
|
-
te = @env[TRANSFER_ENCODING2]
|
231
|
-
|
232
|
-
if te && CHUNKED.casecmp(te) == 0
|
233
|
-
return setup_chunked_body(body)
|
234
|
-
end
|
235
|
-
|
236
|
-
@chunked_body = false
|
237
|
-
|
238
|
-
cl = @env[CONTENT_LENGTH]
|
239
|
-
|
240
|
-
unless cl
|
241
|
-
@buffer = body.empty? ? nil : body
|
242
|
-
@body = EmptyBody
|
243
|
-
@requests_served += 1
|
244
|
-
@ready = true
|
245
|
-
return true
|
246
|
-
end
|
247
|
-
|
248
|
-
remain = cl.to_i - body.bytesize
|
249
|
-
|
250
|
-
if remain <= 0
|
251
|
-
@body = StringIO.new(body)
|
252
|
-
@buffer = nil
|
253
|
-
@requests_served += 1
|
254
|
-
@ready = true
|
255
|
-
return true
|
256
|
-
end
|
257
|
-
|
258
|
-
if remain > MAX_BODY
|
259
|
-
@body = Tempfile.new(Const::PUMA_TMP_BASE)
|
260
|
-
@body.binmode
|
261
|
-
@tempfile = @body
|
262
|
-
else
|
263
|
-
# The body[0,0] trick is to get an empty string in the same
|
264
|
-
# encoding as body.
|
265
|
-
@body = StringIO.new body[0,0]
|
143
|
+
def close
|
144
|
+
begin
|
145
|
+
@io.close
|
146
|
+
rescue IOError
|
147
|
+
Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
|
266
148
|
end
|
267
|
-
|
268
|
-
@body.write body
|
269
|
-
|
270
|
-
@body_remain = remain
|
271
|
-
|
272
|
-
return false
|
273
149
|
end
|
274
150
|
|
275
151
|
def try_to_finish
|
@@ -279,10 +155,17 @@ module Puma
|
|
279
155
|
data = @io.read_nonblock(CHUNK_SIZE)
|
280
156
|
rescue Errno::EAGAIN
|
281
157
|
return false
|
282
|
-
rescue SystemCallError, IOError
|
158
|
+
rescue SystemCallError, IOError, EOFError
|
283
159
|
raise ConnectionError, "Connection error detected during read"
|
284
160
|
end
|
285
161
|
|
162
|
+
# No data means a closed socket
|
163
|
+
unless data
|
164
|
+
@buffer = nil
|
165
|
+
set_ready
|
166
|
+
raise EOFError
|
167
|
+
end
|
168
|
+
|
286
169
|
if @buffer
|
287
170
|
@buffer << data
|
288
171
|
else
|
@@ -312,6 +195,13 @@ module Puma
|
|
312
195
|
raise e
|
313
196
|
end
|
314
197
|
|
198
|
+
# No data means a closed socket
|
199
|
+
unless data
|
200
|
+
@buffer = nil
|
201
|
+
set_ready
|
202
|
+
raise EOFError
|
203
|
+
end
|
204
|
+
|
315
205
|
if @buffer
|
316
206
|
@buffer << data
|
317
207
|
else
|
@@ -358,6 +248,84 @@ module Puma
|
|
358
248
|
true
|
359
249
|
end
|
360
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 && CHUNKED.casecmp(te) == 0
|
289
|
+
return setup_chunked_body(body)
|
290
|
+
end
|
291
|
+
|
292
|
+
@chunked_body = false
|
293
|
+
|
294
|
+
cl = @env[CONTENT_LENGTH]
|
295
|
+
|
296
|
+
unless cl
|
297
|
+
@buffer = body.empty? ? nil : body
|
298
|
+
@body = EmptyBody
|
299
|
+
set_ready
|
300
|
+
return true
|
301
|
+
end
|
302
|
+
|
303
|
+
remain = cl.to_i - body.bytesize
|
304
|
+
|
305
|
+
if remain <= 0
|
306
|
+
@body = StringIO.new(body)
|
307
|
+
@buffer = nil
|
308
|
+
set_ready
|
309
|
+
return true
|
310
|
+
end
|
311
|
+
|
312
|
+
if remain > MAX_BODY
|
313
|
+
@body = Tempfile.new(Const::PUMA_TMP_BASE)
|
314
|
+
@body.binmode
|
315
|
+
@tempfile = @body
|
316
|
+
else
|
317
|
+
# The body[0,0] trick is to get an empty string in the same
|
318
|
+
# encoding as body.
|
319
|
+
@body = StringIO.new body[0,0]
|
320
|
+
end
|
321
|
+
|
322
|
+
@body.write body
|
323
|
+
|
324
|
+
@body_remain = remain
|
325
|
+
|
326
|
+
return false
|
327
|
+
end
|
328
|
+
|
361
329
|
def read_body
|
362
330
|
if @chunked_body
|
363
331
|
return read_chunked_body
|
@@ -385,8 +353,7 @@ module Puma
|
|
385
353
|
unless chunk
|
386
354
|
@body.close
|
387
355
|
@buffer = nil
|
388
|
-
|
389
|
-
@ready = true
|
356
|
+
set_ready
|
390
357
|
raise EOFError
|
391
358
|
end
|
392
359
|
|
@@ -395,8 +362,7 @@ module Puma
|
|
395
362
|
if remain <= 0
|
396
363
|
@body.rewind
|
397
364
|
@buffer = nil
|
398
|
-
|
399
|
-
@ready = true
|
365
|
+
set_ready
|
400
366
|
return true
|
401
367
|
end
|
402
368
|
|
@@ -405,37 +371,124 @@ module Puma
|
|
405
371
|
false
|
406
372
|
end
|
407
373
|
|
408
|
-
def
|
409
|
-
|
410
|
-
|
411
|
-
|
374
|
+
def read_chunked_body
|
375
|
+
while true
|
376
|
+
begin
|
377
|
+
chunk = @io.read_nonblock(4096)
|
378
|
+
rescue IO::WaitReadable
|
379
|
+
return false
|
380
|
+
rescue SystemCallError, IOError
|
381
|
+
raise ConnectionError, "Connection error detected during read"
|
382
|
+
end
|
383
|
+
|
384
|
+
# No chunk means a closed socket
|
385
|
+
unless chunk
|
386
|
+
@body.close
|
387
|
+
@buffer = nil
|
388
|
+
set_ready
|
389
|
+
raise EOFError
|
390
|
+
end
|
391
|
+
|
392
|
+
return true if decode_chunk(chunk)
|
412
393
|
end
|
413
394
|
end
|
414
395
|
|
415
|
-
def
|
416
|
-
|
417
|
-
|
418
|
-
|
419
|
-
|
396
|
+
def setup_chunked_body(body)
|
397
|
+
@chunked_body = true
|
398
|
+
@partial_part_left = 0
|
399
|
+
@prev_chunk = ""
|
400
|
+
|
401
|
+
@body = Tempfile.new(Const::PUMA_TMP_BASE)
|
402
|
+
@body.binmode
|
403
|
+
@tempfile = @body
|
404
|
+
|
405
|
+
return decode_chunk(body)
|
420
406
|
end
|
421
407
|
|
422
|
-
def
|
423
|
-
|
424
|
-
@
|
425
|
-
|
408
|
+
def decode_chunk(chunk)
|
409
|
+
if @partial_part_left > 0
|
410
|
+
if @partial_part_left <= chunk.size
|
411
|
+
if @partial_part_left > 2
|
412
|
+
@body << chunk[0..(@partial_part_left-3)] # skip the \r\n
|
413
|
+
end
|
414
|
+
chunk = chunk[@partial_part_left..-1]
|
415
|
+
@partial_part_left = 0
|
416
|
+
else
|
417
|
+
@body << chunk if @partial_part_left > 2 # don't include the last \r\n
|
418
|
+
@partial_part_left -= chunk.size
|
419
|
+
return false
|
420
|
+
end
|
426
421
|
end
|
427
|
-
end
|
428
422
|
|
429
|
-
|
430
|
-
|
423
|
+
if @prev_chunk.empty?
|
424
|
+
io = StringIO.new(chunk)
|
425
|
+
else
|
426
|
+
io = StringIO.new(@prev_chunk+chunk)
|
427
|
+
@prev_chunk = ""
|
428
|
+
end
|
431
429
|
|
432
|
-
|
433
|
-
|
434
|
-
|
435
|
-
|
430
|
+
while !io.eof?
|
431
|
+
line = io.gets
|
432
|
+
if line.end_with?("\r\n")
|
433
|
+
len = line.strip.to_i(16)
|
434
|
+
if len == 0
|
435
|
+
@in_last_chunk = true
|
436
|
+
@body.rewind
|
437
|
+
rest = io.read
|
438
|
+
last_crlf_size = "\r\n".bytesize
|
439
|
+
if rest.bytesize < last_crlf_size
|
440
|
+
@buffer = nil
|
441
|
+
@partial_part_left = last_crlf_size - rest.bytesize
|
442
|
+
return false
|
443
|
+
else
|
444
|
+
@buffer = rest[last_crlf_size..-1]
|
445
|
+
@buffer = nil if @buffer.empty?
|
446
|
+
set_ready
|
447
|
+
return true
|
448
|
+
end
|
449
|
+
end
|
450
|
+
|
451
|
+
len += 2
|
452
|
+
|
453
|
+
part = io.read(len)
|
454
|
+
|
455
|
+
unless part
|
456
|
+
@partial_part_left = len
|
457
|
+
next
|
458
|
+
end
|
459
|
+
|
460
|
+
got = part.size
|
461
|
+
|
462
|
+
case
|
463
|
+
when got == len
|
464
|
+
@body << part[0..-3] # to skip the ending \r\n
|
465
|
+
when got <= len - 2
|
466
|
+
@body << part
|
467
|
+
@partial_part_left = len - part.size
|
468
|
+
when got == len - 1 # edge where we get just \r but not \n
|
469
|
+
@body << part[0..-2]
|
470
|
+
@partial_part_left = len - part.size
|
471
|
+
end
|
472
|
+
else
|
473
|
+
@prev_chunk = line
|
474
|
+
return false
|
475
|
+
end
|
436
476
|
end
|
437
477
|
|
438
|
-
|
478
|
+
if @in_last_chunk
|
479
|
+
set_ready
|
480
|
+
true
|
481
|
+
else
|
482
|
+
false
|
483
|
+
end
|
484
|
+
end
|
485
|
+
|
486
|
+
def set_ready
|
487
|
+
if @body_read_start
|
488
|
+
@env['puma.request_body_wait'] = Process.clock_gettime(Process::CLOCK_MONOTONIC, :millisecond) - @body_read_start
|
489
|
+
end
|
490
|
+
@requests_served += 1
|
491
|
+
@ready = true
|
439
492
|
end
|
440
493
|
end
|
441
494
|
end
|