2018-09-17 12:41:14 -04:00
|
|
|
# frozen_string_literal: true
|
|
|
|
|
2012-08-09 19:54:55 -04:00
|
|
|
class IO
|
2012-08-10 22:35:47 -04:00
|
|
|
# We need to use this for a jruby work around on both 1.8 and 1.9.
|
|
|
|
# So this either creates the constant (on 1.8), or harmlessly
|
|
|
|
# reopens it (on 1.9).
|
2012-08-09 19:54:55 -04:00
|
|
|
module WaitReadable
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2012-08-10 22:35:47 -04:00
|
|
|
require 'puma/detect'
|
2016-07-25 01:02:23 -04:00
|
|
|
require 'tempfile'
|
2019-10-01 12:05:46 -04:00
|
|
|
require 'forwardable'
|
2012-08-10 22:35:47 -04:00
|
|
|
|
|
|
|
if Puma::IS_JRUBY
|
|
|
|
# We have to work around some OpenSSL buffer/io-readiness bugs
|
|
|
|
# so we pull it in regardless of if the user is binding
|
|
|
|
# to an SSL socket
|
|
|
|
require 'openssl'
|
|
|
|
end
|
|
|
|
|
2012-07-23 13:26:52 -04:00
|
|
|
module Puma
|
2013-06-01 17:20:45 -04:00
|
|
|
|
|
|
|
class ConnectionError < RuntimeError; end
|
|
|
|
|
2018-05-01 16:42:05 -04:00
|
|
|
# An instance of this class represents a unique request from a client.
|
2019-09-20 07:21:19 -04:00
|
|
|
# For example, this could be a web request from a browser or from CURL.
|
2018-05-01 16:42:05 -04:00
|
|
|
#
|
|
|
|
# An instance of `Puma::Client` can be used as if it were an IO object
|
2019-09-20 07:21:19 -04:00
|
|
|
# by the reactor. The reactor is expected to call `#to_io`
|
|
|
|
# on any non-IO objects it polls. For example, nio4r internally calls
|
2019-06-11 17:06:25 -04:00
|
|
|
# `IO::try_convert` (which may call `#to_io`) when a new socket is
|
|
|
|
# registered.
|
2018-05-01 16:42:05 -04:00
|
|
|
#
|
|
|
|
# Instances of this class are responsible for knowing if
|
|
|
|
# the header and body are fully buffered via the `try_to_finish` method.
|
|
|
|
# They can be used to "time out" a response via the `timeout_at` reader.
|
2012-07-23 13:26:52 -04:00
|
|
|
class Client
|
2019-09-20 07:30:22 -04:00
|
|
|
# The object used for a request with no body. All requests with
|
|
|
|
# no body share this one object since it has no state.
|
|
|
|
EmptyBody = NullIO.new
|
|
|
|
|
2012-07-23 13:26:52 -04:00
|
|
|
include Puma::Const
|
2019-10-01 12:05:46 -04:00
|
|
|
extend Forwardable
|
2012-07-23 13:26:52 -04:00
|
|
|
|
2013-08-07 19:36:04 -04:00
|
|
|
def initialize(io, env=nil)
|
2012-07-23 13:26:52 -04:00
|
|
|
@io = io
|
|
|
|
@to_io = io.to_io
|
|
|
|
@proto_env = env
|
2013-08-07 19:36:04 -04:00
|
|
|
if !env
|
|
|
|
@env = nil
|
|
|
|
else
|
|
|
|
@env = env.dup
|
|
|
|
end
|
2012-07-23 13:26:52 -04:00
|
|
|
|
|
|
|
@parser = HttpParser.new
|
|
|
|
@parsed_bytes = 0
|
|
|
|
@read_header = true
|
2012-07-23 17:29:33 -04:00
|
|
|
@ready = false
|
|
|
|
|
2012-07-23 13:26:52 -04:00
|
|
|
@body = nil
|
2019-04-26 23:06:48 -04:00
|
|
|
@body_read_start = nil
|
2012-07-23 13:26:52 -04:00
|
|
|
@buffer = nil
|
2015-04-21 11:48:13 -04:00
|
|
|
@tempfile = nil
|
2012-07-23 13:26:52 -04:00
|
|
|
|
|
|
|
@timeout_at = nil
|
2012-08-09 19:54:55 -04:00
|
|
|
|
|
|
|
@requests_served = 0
|
2013-02-06 01:39:16 -05:00
|
|
|
@hijacked = false
|
2016-01-06 13:12:09 -05:00
|
|
|
|
|
|
|
@peerip = nil
|
|
|
|
@remote_addr_header = nil
|
2019-02-20 11:42:33 -05:00
|
|
|
|
|
|
|
@body_remain = 0
|
2019-08-03 18:52:09 -04:00
|
|
|
|
|
|
|
@in_last_chunk = false
|
2012-07-23 13:26:52 -04:00
|
|
|
end
|
|
|
|
|
2015-04-21 11:48:13 -04:00
|
|
|
attr_reader :env, :to_io, :body, :io, :timeout_at, :ready, :hijacked,
|
|
|
|
:tempfile
|
2012-07-23 13:26:52 -04:00
|
|
|
|
2016-01-06 13:12:09 -05:00
|
|
|
attr_writer :peerip
|
|
|
|
|
|
|
|
attr_accessor :remote_addr_header
|
|
|
|
|
2019-10-01 12:05:46 -04:00
|
|
|
def_delegators :@io, :closed?
|
2016-02-25 16:17:47 -05:00
|
|
|
|
2012-08-09 19:54:55 -04:00
|
|
|
def inspect
|
|
|
|
"#<Puma::Client:0x#{object_id.to_s(16)} @ready=#{@ready.inspect}>"
|
|
|
|
end
|
|
|
|
|
2013-02-06 01:39:16 -05:00
|
|
|
# For the hijack protocol (allows us to just put the Client object
|
|
|
|
# into the env)
|
|
|
|
def call
|
|
|
|
@hijacked = true
|
|
|
|
env[HIJACK_IO] ||= @io
|
|
|
|
end
|
|
|
|
|
2014-01-30 17:37:38 -05:00
|
|
|
def in_data_phase
|
|
|
|
!@read_header
|
|
|
|
end
|
|
|
|
|
2012-07-23 13:26:52 -04:00
|
|
|
def set_timeout(val)
|
|
|
|
@timeout_at = Time.now + val
|
|
|
|
end
|
|
|
|
|
2012-09-09 22:51:36 -04:00
|
|
|
def reset(fast_check=true)
|
2012-07-23 13:26:52 -04:00
|
|
|
@parser.reset
|
|
|
|
@read_header = true
|
|
|
|
@env = @proto_env.dup
|
|
|
|
@body = nil
|
2015-04-21 11:48:13 -04:00
|
|
|
@tempfile = nil
|
2012-07-23 13:26:52 -04:00
|
|
|
@parsed_bytes = 0
|
2012-07-23 17:29:33 -04:00
|
|
|
@ready = false
|
2019-02-20 11:42:33 -05:00
|
|
|
@body_remain = 0
|
2019-03-07 14:21:03 -05:00
|
|
|
@peerip = nil
|
2019-08-03 18:52:09 -04:00
|
|
|
@in_last_chunk = false
|
2012-07-23 13:26:52 -04:00
|
|
|
|
|
|
|
if @buffer
|
|
|
|
@parsed_bytes = @parser.execute(@env, @buffer, @parsed_bytes)
|
|
|
|
|
|
|
|
if @parser.finished?
|
|
|
|
return setup_body
|
|
|
|
elsif @parsed_bytes >= MAX_HEADER
|
|
|
|
raise HttpParserError,
|
|
|
|
"HEADER is longer than allowed, aborting client early."
|
|
|
|
end
|
|
|
|
|
|
|
|
return false
|
2019-02-20 11:42:33 -05:00
|
|
|
else
|
|
|
|
begin
|
|
|
|
if fast_check &&
|
|
|
|
IO.select([@to_io], nil, nil, FAST_TRACK_KA_TIMEOUT)
|
|
|
|
return try_to_finish
|
|
|
|
end
|
|
|
|
rescue IOError
|
|
|
|
# swallow it
|
|
|
|
end
|
|
|
|
|
2012-07-23 13:26:52 -04:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def close
|
2012-07-24 20:24:44 -04:00
|
|
|
begin
|
|
|
|
@io.close
|
|
|
|
rescue IOError
|
2017-07-19 14:22:36 -04:00
|
|
|
Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
|
2012-07-24 20:24:44 -04:00
|
|
|
end
|
2012-07-23 13:26:52 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
def try_to_finish
|
|
|
|
return read_body unless @read_header
|
|
|
|
|
2012-08-23 01:34:10 -04:00
|
|
|
begin
|
|
|
|
data = @io.read_nonblock(CHUNK_SIZE)
|
2020-02-21 11:26:38 -05:00
|
|
|
rescue IO::WaitReadable
|
2012-08-23 01:34:10 -04:00
|
|
|
return false
|
2019-02-20 11:42:33 -05:00
|
|
|
rescue SystemCallError, IOError, EOFError
|
2013-06-01 17:20:45 -04:00
|
|
|
raise ConnectionError, "Connection error detected during read"
|
2012-08-23 01:34:10 -04:00
|
|
|
end
|
2012-07-23 13:26:52 -04:00
|
|
|
|
2018-01-19 11:58:31 -05:00
|
|
|
# No data means a closed socket
|
|
|
|
unless data
|
|
|
|
@buffer = nil
|
2018-04-24 14:54:51 -04:00
|
|
|
set_ready
|
2018-01-19 11:58:31 -05:00
|
|
|
raise EOFError
|
|
|
|
end
|
|
|
|
|
2012-07-23 13:26:52 -04:00
|
|
|
if @buffer
|
|
|
|
@buffer << data
|
|
|
|
else
|
|
|
|
@buffer = data
|
|
|
|
end
|
|
|
|
|
|
|
|
@parsed_bytes = @parser.execute(@env, @buffer, @parsed_bytes)
|
|
|
|
|
|
|
|
if @parser.finished?
|
|
|
|
return setup_body
|
|
|
|
elsif @parsed_bytes >= MAX_HEADER
|
|
|
|
raise HttpParserError,
|
|
|
|
"HEADER is longer than allowed, aborting client early."
|
|
|
|
end
|
2016-09-01 17:57:38 -04:00
|
|
|
|
2012-07-23 13:26:52 -04:00
|
|
|
false
|
|
|
|
end
|
|
|
|
|
2012-08-10 22:35:47 -04:00
|
|
|
if IS_JRUBY
|
|
|
|
def jruby_start_try_to_finish
|
|
|
|
return read_body unless @read_header
|
2012-08-09 19:54:55 -04:00
|
|
|
|
2012-08-10 22:35:47 -04:00
|
|
|
begin
|
|
|
|
data = @io.sysread_nonblock(CHUNK_SIZE)
|
|
|
|
rescue OpenSSL::SSL::SSLError => e
|
|
|
|
return false if e.kind_of? IO::WaitReadable
|
|
|
|
raise e
|
|
|
|
end
|
2012-08-09 19:54:55 -04:00
|
|
|
|
2018-01-19 11:58:31 -05:00
|
|
|
# No data means a closed socket
|
|
|
|
unless data
|
|
|
|
@buffer = nil
|
2018-04-24 14:54:51 -04:00
|
|
|
set_ready
|
2018-01-19 11:58:31 -05:00
|
|
|
raise EOFError
|
|
|
|
end
|
|
|
|
|
2012-08-10 22:35:47 -04:00
|
|
|
if @buffer
|
|
|
|
@buffer << data
|
|
|
|
else
|
|
|
|
@buffer = data
|
|
|
|
end
|
2012-08-09 19:54:55 -04:00
|
|
|
|
2012-08-10 22:35:47 -04:00
|
|
|
@parsed_bytes = @parser.execute(@env, @buffer, @parsed_bytes)
|
2012-08-09 19:54:55 -04:00
|
|
|
|
2012-08-10 22:35:47 -04:00
|
|
|
if @parser.finished?
|
|
|
|
return setup_body
|
|
|
|
elsif @parsed_bytes >= MAX_HEADER
|
|
|
|
raise HttpParserError,
|
|
|
|
"HEADER is longer than allowed, aborting client early."
|
|
|
|
end
|
|
|
|
|
|
|
|
false
|
2012-08-09 19:54:55 -04:00
|
|
|
end
|
|
|
|
|
2012-08-10 22:35:47 -04:00
|
|
|
def eagerly_finish
|
|
|
|
return true if @ready
|
2012-08-09 19:54:55 -04:00
|
|
|
|
2012-08-10 22:35:47 -04:00
|
|
|
if @io.kind_of? OpenSSL::SSL::SSLSocket
|
|
|
|
return true if jruby_start_try_to_finish
|
|
|
|
end
|
2012-08-09 19:54:55 -04:00
|
|
|
|
2012-08-10 22:35:47 -04:00
|
|
|
return false unless IO.select([@to_io], nil, nil, 0)
|
|
|
|
try_to_finish
|
2012-08-09 19:54:55 -04:00
|
|
|
end
|
|
|
|
|
2012-08-10 22:35:47 -04:00
|
|
|
else
|
|
|
|
|
|
|
|
def eagerly_finish
|
|
|
|
return true if @ready
|
|
|
|
return false unless IO.select([@to_io], nil, nil, 0)
|
|
|
|
try_to_finish
|
|
|
|
end
|
2020-02-07 11:40:04 -05:00
|
|
|
|
|
|
|
# For documentation, see https://github.com/puma/puma/issues/1754
|
|
|
|
send(:alias_method, :jruby_eagerly_finish, :eagerly_finish)
|
2012-08-10 22:35:47 -04:00
|
|
|
end # IS_JRUBY
|
2012-07-23 17:29:33 -04:00
|
|
|
|
2020-02-20 07:36:34 -05:00
|
|
|
def finish(timeout)
|
2015-01-20 07:20:39 -05:00
|
|
|
return true if @ready
|
|
|
|
until try_to_finish
|
2020-02-20 07:36:34 -05:00
|
|
|
unless IO.select([@to_io], nil, nil, timeout)
|
|
|
|
write_error(408) if in_data_phase
|
|
|
|
raise ConnectionError
|
|
|
|
end
|
2015-01-20 07:20:39 -05:00
|
|
|
end
|
|
|
|
true
|
|
|
|
end
|
2019-09-20 07:30:22 -04:00
|
|
|
|
2019-09-20 07:41:58 -04:00
|
|
|
def write_error(status_code)
|
2019-09-20 07:30:22 -04:00
|
|
|
begin
|
2019-09-20 07:41:58 -04:00
|
|
|
@io << ERROR_RESPONSE[status_code]
|
2019-09-20 07:30:22 -04:00
|
|
|
rescue StandardError
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def peerip
|
|
|
|
return @peerip if @peerip
|
|
|
|
|
|
|
|
if @remote_addr_header
|
|
|
|
hdr = (@env[@remote_addr_header] || LOCALHOST_ADDR).split(/[\s,]/).first
|
|
|
|
@peerip = hdr
|
|
|
|
return hdr
|
|
|
|
end
|
|
|
|
|
|
|
|
@peerip ||= @io.peeraddr.last
|
|
|
|
end
|
|
|
|
|
|
|
|
private
|
|
|
|
|
|
|
|
def setup_body
|
|
|
|
@body_read_start = Process.clock_gettime(Process::CLOCK_MONOTONIC, :millisecond)
|
|
|
|
|
|
|
|
if @env[HTTP_EXPECT] == CONTINUE
|
|
|
|
# TODO allow a hook here to check the headers before
|
|
|
|
# going forward
|
|
|
|
@io << HTTP_11_100
|
|
|
|
@io.flush
|
|
|
|
end
|
|
|
|
|
|
|
|
@read_header = false
|
|
|
|
|
|
|
|
body = @parser.body
|
|
|
|
|
|
|
|
te = @env[TRANSFER_ENCODING2]
|
|
|
|
|
|
|
|
if te && CHUNKED.casecmp(te) == 0
|
|
|
|
return setup_chunked_body(body)
|
|
|
|
end
|
|
|
|
|
|
|
|
@chunked_body = false
|
|
|
|
|
|
|
|
cl = @env[CONTENT_LENGTH]
|
|
|
|
|
|
|
|
unless cl
|
|
|
|
@buffer = body.empty? ? nil : body
|
|
|
|
@body = EmptyBody
|
|
|
|
set_ready
|
|
|
|
return true
|
|
|
|
end
|
|
|
|
|
|
|
|
remain = cl.to_i - body.bytesize
|
|
|
|
|
|
|
|
if remain <= 0
|
|
|
|
@body = StringIO.new(body)
|
|
|
|
@buffer = nil
|
|
|
|
set_ready
|
|
|
|
return true
|
|
|
|
end
|
|
|
|
|
|
|
|
if remain > MAX_BODY
|
|
|
|
@body = Tempfile.new(Const::PUMA_TMP_BASE)
|
|
|
|
@body.binmode
|
|
|
|
@tempfile = @body
|
|
|
|
else
|
|
|
|
# The body[0,0] trick is to get an empty string in the same
|
|
|
|
# encoding as body.
|
|
|
|
@body = StringIO.new body[0,0]
|
|
|
|
end
|
|
|
|
|
|
|
|
@body.write body
|
|
|
|
|
|
|
|
@body_remain = remain
|
|
|
|
|
|
|
|
return false
|
|
|
|
end
|
2015-01-20 07:20:39 -05:00
|
|
|
|
2012-07-23 13:26:52 -04:00
|
|
|
def read_body
|
2016-07-25 01:02:23 -04:00
|
|
|
if @chunked_body
|
|
|
|
return read_chunked_body
|
|
|
|
end
|
|
|
|
|
2012-07-23 13:26:52 -04:00
|
|
|
# Read an odd sized chunk so we can read even sized ones
|
|
|
|
# after this
|
|
|
|
remain = @body_remain
|
|
|
|
|
|
|
|
if remain > CHUNK_SIZE
|
|
|
|
want = CHUNK_SIZE
|
|
|
|
else
|
|
|
|
want = remain
|
|
|
|
end
|
|
|
|
|
2012-08-23 01:34:10 -04:00
|
|
|
begin
|
|
|
|
chunk = @io.read_nonblock(want)
|
2020-02-21 11:26:38 -05:00
|
|
|
rescue IO::WaitReadable
|
2012-08-23 01:34:10 -04:00
|
|
|
return false
|
2013-06-01 17:20:45 -04:00
|
|
|
rescue SystemCallError, IOError
|
|
|
|
raise ConnectionError, "Connection error detected during read"
|
2012-08-23 01:34:10 -04:00
|
|
|
end
|
2012-07-23 13:26:52 -04:00
|
|
|
|
|
|
|
# No chunk means a closed socket
|
|
|
|
unless chunk
|
|
|
|
@body.close
|
2012-07-24 13:59:04 -04:00
|
|
|
@buffer = nil
|
2018-04-24 14:54:51 -04:00
|
|
|
set_ready
|
2012-07-23 13:26:52 -04:00
|
|
|
raise EOFError
|
|
|
|
end
|
|
|
|
|
|
|
|
remain -= @body.write(chunk)
|
|
|
|
|
|
|
|
if remain <= 0
|
|
|
|
@body.rewind
|
2012-07-24 13:59:04 -04:00
|
|
|
@buffer = nil
|
2018-04-24 14:54:51 -04:00
|
|
|
set_ready
|
2012-07-23 13:26:52 -04:00
|
|
|
return true
|
|
|
|
end
|
|
|
|
|
|
|
|
@body_remain = remain
|
|
|
|
|
|
|
|
false
|
|
|
|
end
|
2012-09-06 01:09:42 -04:00
|
|
|
|
2019-09-20 07:30:22 -04:00
|
|
|
def read_chunked_body
|
|
|
|
while true
|
|
|
|
begin
|
|
|
|
chunk = @io.read_nonblock(4096)
|
|
|
|
rescue IO::WaitReadable
|
|
|
|
return false
|
|
|
|
rescue SystemCallError, IOError
|
|
|
|
raise ConnectionError, "Connection error detected during read"
|
|
|
|
end
|
|
|
|
|
|
|
|
# No chunk means a closed socket
|
|
|
|
unless chunk
|
|
|
|
@body.close
|
|
|
|
@buffer = nil
|
|
|
|
set_ready
|
|
|
|
raise EOFError
|
|
|
|
end
|
|
|
|
|
|
|
|
return true if decode_chunk(chunk)
|
2018-04-24 14:54:51 -04:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2019-09-20 07:30:22 -04:00
|
|
|
def setup_chunked_body(body)
|
|
|
|
@chunked_body = true
|
|
|
|
@partial_part_left = 0
|
|
|
|
@prev_chunk = ""
|
|
|
|
|
|
|
|
@body = Tempfile.new(Const::PUMA_TMP_BASE)
|
|
|
|
@body.binmode
|
|
|
|
@tempfile = @body
|
|
|
|
|
|
|
|
return decode_chunk(body)
|
2012-09-06 01:09:42 -04:00
|
|
|
end
|
|
|
|
|
2019-09-20 07:30:22 -04:00
|
|
|
def decode_chunk(chunk)
|
|
|
|
if @partial_part_left > 0
|
|
|
|
if @partial_part_left <= chunk.size
|
|
|
|
if @partial_part_left > 2
|
|
|
|
@body << chunk[0..(@partial_part_left-3)] # skip the \r\n
|
|
|
|
end
|
|
|
|
chunk = chunk[@partial_part_left..-1]
|
|
|
|
@partial_part_left = 0
|
|
|
|
else
|
|
|
|
@body << chunk if @partial_part_left > 2 # don't include the last \r\n
|
|
|
|
@partial_part_left -= chunk.size
|
|
|
|
return false
|
|
|
|
end
|
2014-01-30 13:23:01 -05:00
|
|
|
end
|
|
|
|
|
2019-09-20 07:30:22 -04:00
|
|
|
if @prev_chunk.empty?
|
|
|
|
io = StringIO.new(chunk)
|
|
|
|
else
|
|
|
|
io = StringIO.new(@prev_chunk+chunk)
|
|
|
|
@prev_chunk = ""
|
2012-09-06 01:09:42 -04:00
|
|
|
end
|
2016-01-06 13:12:09 -05:00
|
|
|
|
2019-09-20 07:30:22 -04:00
|
|
|
while !io.eof?
|
|
|
|
line = io.gets
|
|
|
|
if line.end_with?("\r\n")
|
|
|
|
len = line.strip.to_i(16)
|
|
|
|
if len == 0
|
|
|
|
@in_last_chunk = true
|
|
|
|
@body.rewind
|
|
|
|
rest = io.read
|
|
|
|
last_crlf_size = "\r\n".bytesize
|
|
|
|
if rest.bytesize < last_crlf_size
|
|
|
|
@buffer = nil
|
|
|
|
@partial_part_left = last_crlf_size - rest.bytesize
|
|
|
|
return false
|
|
|
|
else
|
|
|
|
@buffer = rest[last_crlf_size..-1]
|
|
|
|
@buffer = nil if @buffer.empty?
|
|
|
|
set_ready
|
|
|
|
return true
|
|
|
|
end
|
|
|
|
end
|
2016-01-06 13:12:09 -05:00
|
|
|
|
2019-09-20 07:30:22 -04:00
|
|
|
len += 2
|
|
|
|
|
|
|
|
part = io.read(len)
|
|
|
|
|
|
|
|
unless part
|
|
|
|
@partial_part_left = len
|
|
|
|
next
|
|
|
|
end
|
|
|
|
|
|
|
|
got = part.size
|
|
|
|
|
|
|
|
case
|
|
|
|
when got == len
|
|
|
|
@body << part[0..-3] # to skip the ending \r\n
|
|
|
|
when got <= len - 2
|
|
|
|
@body << part
|
|
|
|
@partial_part_left = len - part.size
|
|
|
|
when got == len - 1 # edge where we get just \r but not \n
|
|
|
|
@body << part[0..-2]
|
|
|
|
@partial_part_left = len - part.size
|
|
|
|
end
|
|
|
|
else
|
|
|
|
@prev_chunk = line
|
|
|
|
return false
|
|
|
|
end
|
2016-01-06 13:12:09 -05:00
|
|
|
end
|
|
|
|
|
2019-09-20 07:30:22 -04:00
|
|
|
if @in_last_chunk
|
|
|
|
set_ready
|
|
|
|
true
|
|
|
|
else
|
|
|
|
false
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def set_ready
|
|
|
|
if @body_read_start
|
|
|
|
@env['puma.request_body_wait'] = Process.clock_gettime(Process::CLOCK_MONOTONIC, :millisecond) - @body_read_start
|
|
|
|
end
|
|
|
|
@requests_served += 1
|
|
|
|
@ready = true
|
2016-01-06 13:12:09 -05:00
|
|
|
end
|
2012-07-23 13:26:52 -04:00
|
|
|
end
|
|
|
|
end
|