puma 4.1.1 → 4.3.6

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.

@@ -10,6 +10,7 @@
10
10
  #include "ext_help.h"
11
11
  #include <assert.h>
12
12
  #include <string.h>
13
+ #include <ctype.h>
13
14
  #include "http11_parser.h"
14
15
 
15
16
  #ifndef MANAGED_STRINGS
@@ -200,6 +201,8 @@ void http_field(puma_parser* hp, const char *field, size_t flen,
200
201
  f = rb_str_new(hp->buf, new_size);
201
202
  }
202
203
 
204
+ while (vlen > 0 && isspace(value[vlen - 1])) vlen--;
205
+
203
206
  /* check for duplicate header */
204
207
  v = rb_hash_aref(hp->request, f);
205
208
 
@@ -22,4 +22,10 @@ module Puma
22
22
  def self.stats
23
23
  @get_stats.stats
24
24
  end
25
+
26
+ # Thread name is new in Ruby 2.3
27
+ def self.set_thread_name(name)
28
+ return unless Thread.current.respond_to?(:name=)
29
+ Thread.current.name = "puma #{name}"
30
+ end
25
31
  end
@@ -1,32 +1,15 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'json'
4
-
5
3
  module Puma
6
4
  module App
7
5
  # Check out {#call}'s source code to see what actions this web application
8
6
  # can respond to.
9
7
  class Status
10
- def initialize(cli)
11
- @cli = cli
12
- @auth_token = nil
13
- end
14
8
  OK_STATUS = '{ "status": "ok" }'.freeze
15
9
 
16
- attr_accessor :auth_token
17
-
18
- def authenticate(env)
19
- return true unless @auth_token
20
- env['QUERY_STRING'].to_s.split(/&;/).include?("token=#{@auth_token}")
21
- end
22
-
23
- def rack_response(status, body, content_type='application/json')
24
- headers = {
25
- 'Content-Type' => content_type,
26
- 'Content-Length' => body.bytesize.to_s
27
- }
28
-
29
- [status, headers, [body]]
10
+ def initialize(cli, token = nil)
11
+ @cli = cli
12
+ @auth_token = token
30
13
  end
31
14
 
32
15
  def call(env)
@@ -34,46 +17,66 @@ module Puma
34
17
  return rack_response(403, 'Invalid auth token', 'text/plain')
35
18
  end
36
19
 
20
+ if env['PATH_INFO'] =~ /\/(gc-stats|stats|thread-backtraces)$/
21
+ require 'json'
22
+ end
23
+
37
24
  case env['PATH_INFO']
38
25
  when /\/stop$/
39
26
  @cli.stop
40
- return rack_response(200, OK_STATUS)
27
+ rack_response(200, OK_STATUS)
41
28
 
42
29
  when /\/halt$/
43
30
  @cli.halt
44
- return rack_response(200, OK_STATUS)
31
+ rack_response(200, OK_STATUS)
45
32
 
46
33
  when /\/restart$/
47
34
  @cli.restart
48
- return rack_response(200, OK_STATUS)
35
+ rack_response(200, OK_STATUS)
49
36
 
50
37
  when /\/phased-restart$/
51
38
  if !@cli.phased_restart
52
- return rack_response(404, '{ "error": "phased restart not available" }')
39
+ rack_response(404, '{ "error": "phased restart not available" }')
53
40
  else
54
- return rack_response(200, OK_STATUS)
41
+ rack_response(200, OK_STATUS)
55
42
  end
56
43
 
57
44
  when /\/reload-worker-directory$/
58
45
  if !@cli.send(:reload_worker_directory)
59
- return rack_response(404, '{ "error": "reload_worker_directory not available" }')
46
+ rack_response(404, '{ "error": "reload_worker_directory not available" }')
60
47
  else
61
- return rack_response(200, OK_STATUS)
48
+ rack_response(200, OK_STATUS)
62
49
  end
63
50
 
64
51
  when /\/gc$/
65
52
  GC.start
66
- return rack_response(200, OK_STATUS)
53
+ rack_response(200, OK_STATUS)
67
54
 
68
55
  when /\/gc-stats$/
69
- return rack_response(200, GC.stat.to_json)
56
+ rack_response(200, GC.stat.to_json)
70
57
 
71
58
  when /\/stats$/
72
- return rack_response(200, @cli.stats)
59
+ rack_response(200, @cli.stats)
73
60
  else
74
61
  rack_response 404, "Unsupported action", 'text/plain'
75
62
  end
76
63
  end
64
+
65
+ private
66
+
67
+ def authenticate(env)
68
+ return true unless @auth_token
69
+ env['QUERY_STRING'].to_s.split(/&;/).include?("token=#{@auth_token}")
70
+ end
71
+
72
+ def rack_response(status, body, content_type='application/json')
73
+ headers = {
74
+ 'Content-Type' => content_type,
75
+ 'Content-Length' => body.bytesize.to_s
76
+ }
77
+
78
+ [status, headers, [body]]
79
+ end
77
80
  end
78
81
  end
79
82
  end
@@ -5,6 +5,7 @@ require 'socket'
5
5
 
6
6
  require 'puma/const'
7
7
  require 'puma/util'
8
+ require 'puma/minissl/context_builder'
8
9
 
9
10
  module Puma
10
11
  class Binder
@@ -42,7 +43,7 @@ module Puma
42
43
  @ios = []
43
44
  end
44
45
 
45
- attr_reader :listeners, :ios
46
+ attr_reader :ios
46
47
 
47
48
  def env(sock)
48
49
  @envs.fetch(sock, @proto_env)
@@ -50,14 +51,6 @@ module Puma
50
51
 
51
52
  def close
52
53
  @ios.each { |i| i.close }
53
- @unix_paths.each do |i|
54
- # Errno::ENOENT is intermittently raised
55
- begin
56
- unix_socket = UNIXSocket.new i
57
- unix_socket.close
58
- rescue Errno::ENOENT
59
- end
60
- end
61
54
  end
62
55
 
63
56
  def import_from_env
@@ -111,7 +104,17 @@ module Puma
111
104
  bak = params.fetch('backlog', 1024).to_i
112
105
 
113
106
  io = add_tcp_listener uri.host, uri.port, opt, bak
114
- logger.log "* Listening on #{str}"
107
+
108
+ @ios.each do |i|
109
+ next unless TCPServer === i
110
+ addr = if i.local_address.ipv6?
111
+ "[#{i.local_address.ip_unpack[0]}]:#{i.local_address.ip_unpack[1]}"
112
+ else
113
+ i.local_address.ip_unpack.join(':')
114
+ end
115
+
116
+ logger.log "* Listening on tcp://#{addr}"
117
+ end
115
118
  end
116
119
 
117
120
  @listeners << [str, io] if io
@@ -152,64 +155,7 @@ module Puma
152
155
  @listeners << [str, io]
153
156
  when "ssl"
154
157
  params = Util.parse_query uri.query
155
- require 'puma/minissl'
156
-
157
- MiniSSL.check
158
-
159
- ctx = MiniSSL::Context.new
160
-
161
- if defined?(JRUBY_VERSION)
162
- unless params['keystore']
163
- @events.error "Please specify the Java keystore via 'keystore='"
164
- end
165
-
166
- ctx.keystore = params['keystore']
167
-
168
- unless params['keystore-pass']
169
- @events.error "Please specify the Java keystore password via 'keystore-pass='"
170
- end
171
-
172
- ctx.keystore_pass = params['keystore-pass']
173
- ctx.ssl_cipher_list = params['ssl_cipher_list'] if params['ssl_cipher_list']
174
- else
175
- unless params['key']
176
- @events.error "Please specify the SSL key via 'key='"
177
- end
178
-
179
- ctx.key = params['key']
180
-
181
- unless params['cert']
182
- @events.error "Please specify the SSL cert via 'cert='"
183
- end
184
-
185
- ctx.cert = params['cert']
186
-
187
- if ['peer', 'force_peer'].include?(params['verify_mode'])
188
- unless params['ca']
189
- @events.error "Please specify the SSL ca via 'ca='"
190
- end
191
- end
192
-
193
- ctx.ca = params['ca'] if params['ca']
194
- ctx.ssl_cipher_filter = params['ssl_cipher_filter'] if params['ssl_cipher_filter']
195
- end
196
-
197
- ctx.no_tlsv1 = true if params['no_tlsv1'] == 'true'
198
- ctx.no_tlsv1_1 = true if params['no_tlsv1_1'] == 'true'
199
-
200
- if params['verify_mode']
201
- ctx.verify_mode = case params['verify_mode']
202
- when "peer"
203
- MiniSSL::VERIFY_PEER
204
- when "force_peer"
205
- MiniSSL::VERIFY_PEER | MiniSSL::VERIFY_FAIL_IF_NO_PEER_CERT
206
- when "none"
207
- MiniSSL::VERIFY_NONE
208
- else
209
- @events.error "Please specify a valid verify_mode="
210
- MiniSSL::VERIFY_NONE
211
- end
212
- end
158
+ ctx = MiniSSL::ContextBuilder.new(params, @events).context
213
159
 
214
160
  if fd = @inherited_fds.delete(str)
215
161
  logger.log "* Inherited #{str}"
@@ -359,7 +305,7 @@ module Puma
359
305
  # Tell the server to listen on +path+ as a UNIX domain socket.
360
306
  #
361
307
  def add_unix_listener(path, umask=nil, mode=nil, backlog=1024)
362
- @unix_paths << path
308
+ @unix_paths << path unless File.exist? path
363
309
 
364
310
  # Let anyone connect by default
365
311
  umask ||= 0
@@ -397,7 +343,7 @@ module Puma
397
343
  end
398
344
 
399
345
  def inherit_unix_listener(path, fd)
400
- @unix_paths << path
346
+ @unix_paths << path unless File.exist? path
401
347
 
402
348
  if fd.kind_of? TCPServer
403
349
  s = fd
@@ -413,5 +359,27 @@ module Puma
413
359
  s
414
360
  end
415
361
 
362
+ def close_listeners
363
+ @listeners.each do |l, io|
364
+ io.close
365
+ uri = URI.parse(l)
366
+ next unless uri.scheme == 'unix'
367
+ unix_path = "#{uri.host}#{uri.path}"
368
+ File.unlink unix_path if @unix_paths.include? unix_path
369
+ end
370
+ end
371
+
372
+ def close_unix_paths
373
+ @unix_paths.each { |up| File.unlink(up) if File.exist? up }
374
+ end
375
+
376
+ def redirects_for_restart
377
+ redirects = {:close_others => true}
378
+ @listeners.each_with_index do |(l, io), i|
379
+ ENV["PUMA_INHERIT_#{i}"] = "#{io.to_i}:#{l}"
380
+ redirects[io.to_i] = io.to_i
381
+ end
382
+ redirects
383
+ end
416
384
  end
417
385
  end
@@ -161,6 +161,10 @@ module Puma
161
161
  user_config.prune_bundler
162
162
  end
163
163
 
164
+ o.on "--extra-runtime-dependencies GEM1,GEM2", "Defines any extra needed gems when using --prune-bundler" do |arg|
165
+ user_config.extra_runtime_dependencies arg.split(',')
166
+ end
167
+
164
168
  o.on "-q", "--quiet", "Do not log requests internally (default true)" do
165
169
  user_config.quiet
166
170
  end
@@ -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,11 +24,11 @@ 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. This
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
- # by the reactor, that's because the latter is expected to call `#to_io`
31
- # on any non-IO objects it polls. For example nio4r internally calls
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
32
  # `IO::try_convert` (which may call `#to_io`) when a new socket is
33
33
  # registered.
34
34
  #
@@ -36,8 +36,12 @@ module Puma
36
36
  # the header and body are fully buffered via the `try_to_finish` method.
37
37
  # They can be used to "time out" a response via the `timeout_at` reader.
38
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
+
39
43
  include Puma::Const
40
- extend Puma::Delegation
44
+ extend Forwardable
41
45
 
42
46
  def initialize(io, env=nil)
43
47
  @io = io
@@ -79,7 +83,7 @@ module Puma
79
83
 
80
84
  attr_accessor :remote_addr_header
81
85
 
82
- forward :closed?, :@io
86
+ def_delegators :@io, :closed?
83
87
 
84
88
  def inspect
85
89
  "#<Puma::Client:0x#{object_id.to_s(16)} @ready=#{@ready.inspect}>"
@@ -144,179 +148,6 @@ module Puma
144
148
  end
145
149
  end
146
150
 
147
- # The object used for a request with no body. All requests with
148
- # no body share this one object since it has no state.
149
- EmptyBody = NullIO.new
150
-
151
- def setup_chunked_body(body)
152
- @chunked_body = true
153
- @partial_part_left = 0
154
- @prev_chunk = ""
155
-
156
- @body = Tempfile.new(Const::PUMA_TMP_BASE)
157
- @body.binmode
158
- @tempfile = @body
159
-
160
- return decode_chunk(body)
161
- end
162
-
163
- def decode_chunk(chunk)
164
- if @partial_part_left > 0
165
- if @partial_part_left <= chunk.size
166
- if @partial_part_left > 2
167
- @body << chunk[0..(@partial_part_left-3)] # skip the \r\n
168
- end
169
- chunk = chunk[@partial_part_left..-1]
170
- @partial_part_left = 0
171
- else
172
- @body << chunk if @partial_part_left > 2 # don't include the last \r\n
173
- @partial_part_left -= chunk.size
174
- return false
175
- end
176
- end
177
-
178
- if @prev_chunk.empty?
179
- io = StringIO.new(chunk)
180
- else
181
- io = StringIO.new(@prev_chunk+chunk)
182
- @prev_chunk = ""
183
- end
184
-
185
- while !io.eof?
186
- line = io.gets
187
- if line.end_with?("\r\n")
188
- len = line.strip.to_i(16)
189
- if len == 0
190
- @in_last_chunk = true
191
- @body.rewind
192
- rest = io.read
193
- last_crlf_size = "\r\n".bytesize
194
- if rest.bytesize < last_crlf_size
195
- @buffer = nil
196
- @partial_part_left = last_crlf_size - rest.bytesize
197
- return false
198
- else
199
- @buffer = rest[last_crlf_size..-1]
200
- @buffer = nil if @buffer.empty?
201
- set_ready
202
- return true
203
- end
204
- end
205
-
206
- len += 2
207
-
208
- part = io.read(len)
209
-
210
- unless part
211
- @partial_part_left = len
212
- next
213
- end
214
-
215
- got = part.size
216
-
217
- case
218
- when got == len
219
- @body << part[0..-3] # to skip the ending \r\n
220
- when got <= len - 2
221
- @body << part
222
- @partial_part_left = len - part.size
223
- when got == len - 1 # edge where we get just \r but not \n
224
- @body << part[0..-2]
225
- @partial_part_left = len - part.size
226
- end
227
- else
228
- @prev_chunk = line
229
- return false
230
- end
231
- end
232
-
233
- if @in_last_chunk
234
- set_ready
235
- true
236
- else
237
- false
238
- end
239
- end
240
-
241
- def read_chunked_body
242
- while true
243
- begin
244
- chunk = @io.read_nonblock(4096)
245
- rescue IO::WaitReadable
246
- return false
247
- rescue SystemCallError, IOError
248
- raise ConnectionError, "Connection error detected during read"
249
- end
250
-
251
- # No chunk means a closed socket
252
- unless chunk
253
- @body.close
254
- @buffer = nil
255
- set_ready
256
- raise EOFError
257
- end
258
-
259
- return true if decode_chunk(chunk)
260
- end
261
- end
262
-
263
- def setup_body
264
- @body_read_start = Process.clock_gettime(Process::CLOCK_MONOTONIC, :millisecond)
265
-
266
- if @env[HTTP_EXPECT] == CONTINUE
267
- # TODO allow a hook here to check the headers before
268
- # going forward
269
- @io << HTTP_11_100
270
- @io.flush
271
- end
272
-
273
- @read_header = false
274
-
275
- body = @parser.body
276
-
277
- te = @env[TRANSFER_ENCODING2]
278
-
279
- if te && CHUNKED.casecmp(te) == 0
280
- return setup_chunked_body(body)
281
- end
282
-
283
- @chunked_body = false
284
-
285
- cl = @env[CONTENT_LENGTH]
286
-
287
- unless cl
288
- @buffer = body.empty? ? nil : body
289
- @body = EmptyBody
290
- set_ready
291
- return true
292
- end
293
-
294
- remain = cl.to_i - body.bytesize
295
-
296
- if remain <= 0
297
- @body = StringIO.new(body)
298
- @buffer = nil
299
- set_ready
300
- return true
301
- end
302
-
303
- if remain > MAX_BODY
304
- @body = Tempfile.new(Const::PUMA_TMP_BASE)
305
- @body.binmode
306
- @tempfile = @body
307
- else
308
- # The body[0,0] trick is to get an empty string in the same
309
- # encoding as body.
310
- @body = StringIO.new body[0,0]
311
- end
312
-
313
- @body.write body
314
-
315
- @body_remain = remain
316
-
317
- return false
318
- end
319
-
320
151
  def try_to_finish
321
152
  return read_body unless @read_header
322
153
 
@@ -417,6 +248,92 @@ module Puma
417
248
  true
418
249
  end
419
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
+
420
337
  def read_body
421
338
  if @chunked_body
422
339
  return read_chunked_body
@@ -462,45 +379,124 @@ module Puma
462
379
  false
463
380
  end
464
381
 
465
- def set_ready
466
- if @body_read_start
467
- @env['puma.request_body_wait'] = Process.clock_gettime(Process::CLOCK_MONOTONIC, :millisecond) - @body_read_start
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
+ return true if decode_chunk(chunk)
468
401
  end
469
- @requests_served += 1
470
- @ready = true
471
402
  end
472
403
 
473
- def write_400
474
- begin
475
- @io << ERROR_400_RESPONSE
476
- rescue StandardError
477
- end
404
+ def setup_chunked_body(body)
405
+ @chunked_body = true
406
+ @partial_part_left = 0
407
+ @prev_chunk = ""
408
+
409
+ @body = Tempfile.new(Const::PUMA_TMP_BASE)
410
+ @body.binmode
411
+ @tempfile = @body
412
+
413
+ return decode_chunk(body)
478
414
  end
479
415
 
480
- def write_408
481
- begin
482
- @io << ERROR_408_RESPONSE
483
- rescue StandardError
416
+ def decode_chunk(chunk)
417
+ if @partial_part_left > 0
418
+ if @partial_part_left <= chunk.size
419
+ if @partial_part_left > 2
420
+ @body << chunk[0..(@partial_part_left-3)] # skip the \r\n
421
+ end
422
+ chunk = chunk[@partial_part_left..-1]
423
+ @partial_part_left = 0
424
+ else
425
+ @body << chunk if @partial_part_left > 2 # don't include the last \r\n
426
+ @partial_part_left -= chunk.size
427
+ return false
428
+ end
484
429
  end
485
- end
486
430
 
487
- def write_500
488
- begin
489
- @io << ERROR_500_RESPONSE
490
- rescue StandardError
431
+ if @prev_chunk.empty?
432
+ io = StringIO.new(chunk)
433
+ else
434
+ io = StringIO.new(@prev_chunk+chunk)
435
+ @prev_chunk = ""
491
436
  end
492
- end
493
437
 
494
- def peerip
495
- return @peerip if @peerip
438
+ while !io.eof?
439
+ line = io.gets
440
+ if line.end_with?("\r\n")
441
+ len = line.strip.to_i(16)
442
+ if len == 0
443
+ @in_last_chunk = true
444
+ @body.rewind
445
+ rest = io.read
446
+ last_crlf_size = "\r\n".bytesize
447
+ if rest.bytesize < last_crlf_size
448
+ @buffer = nil
449
+ @partial_part_left = last_crlf_size - rest.bytesize
450
+ return false
451
+ else
452
+ @buffer = rest[last_crlf_size..-1]
453
+ @buffer = nil if @buffer.empty?
454
+ set_ready
455
+ return true
456
+ end
457
+ end
496
458
 
497
- if @remote_addr_header
498
- hdr = (@env[@remote_addr_header] || LOCALHOST_ADDR).split(/[\s,]/).first
499
- @peerip = hdr
500
- return hdr
459
+ len += 2
460
+
461
+ part = io.read(len)
462
+
463
+ unless part
464
+ @partial_part_left = len
465
+ next
466
+ end
467
+
468
+ got = part.size
469
+
470
+ case
471
+ when got == len
472
+ @body << part[0..-3] # to skip the ending \r\n
473
+ when got <= len - 2
474
+ @body << part
475
+ @partial_part_left = len - part.size
476
+ when got == len - 1 # edge where we get just \r but not \n
477
+ @body << part[0..-2]
478
+ @partial_part_left = len - part.size
479
+ end
480
+ else
481
+ @prev_chunk = line
482
+ return false
483
+ end
501
484
  end
502
485
 
503
- @peerip ||= @io.peeraddr.last
486
+ if @in_last_chunk
487
+ set_ready
488
+ true
489
+ else
490
+ false
491
+ end
492
+ end
493
+
494
+ def set_ready
495
+ if @body_read_start
496
+ @env['puma.request_body_wait'] = Process.clock_gettime(Process::CLOCK_MONOTONIC, :millisecond) - @body_read_start
497
+ end
498
+ @requests_served += 1
499
+ @ready = true
504
500
  end
505
501
  end
506
502
  end