class Chunked
Represents a chunked body, which is a series of chunks, each with a length prefix.
See https://tools.ietf.org/html/rfc7230#section-4.1 for more details on the chunked transfer encoding.
Definitions
def initialize(connection, headers)
Initialize the chunked body.
Signature
-
parameter
connection
Protocol::HTTP1::Connection
the connection to read the body from.
-
parameter
headers
Protocol::HTTP::Headers
the headers to read the trailer into, if any.
Implementation
def initialize(connection, headers)
@connection = connection
@finished = false
@headers = headers
@length = 0
@count = 0
end
attr :count
Signature
-
attribute
Integer
the number of chunks read so far.
def length
Signature
-
attribute
Integer
the length of the body if known.
Implementation
def length
# We only know the length once we've read the final chunk:
if @finished
@length
end
end
def empty?
Signature
-
returns
Boolean
true if the body is empty, in other words
Protocol::HTTP1::Body::Chunked#read
will returnnil
.
Implementation
def empty?
@connection.nil?
end
def close(error = nil)
Close the connection and mark the body as finished.
Signature
-
parameter
error
Exception | Nil
the error that caused the body to be closed, if any.
Implementation
def close(error = nil)
if connection = @connection
@connection = nil
unless @finished
connection.close_read
end
end
super
end
def read
Read a chunk of data.
Follows the procedure outlined in https://tools.ietf.org/html/rfc7230#section-4.1.3
Signature
-
returns
String | Nil
the next chunk of data, or
nil
if the body is finished.-
raises
EOFError
if the connection is closed before the expected length is read.
Implementation
def read
if !@finished
if @connection
length, _extensions = @connection.read_line.split(";", 2)
unless length =~ VALID_CHUNK_LENGTH
raise BadRequest, "Invalid chunk length: #{length.inspect}"
end
# It is possible this line contains chunk extension, so we use `to_i` to only consider the initial integral part:
length = Integer(length, 16)
if length == 0
read_trailer
# The final chunk has been read and the connection is now closed:
@connection.receive_end_stream!
@connection = nil
@finished = true
return nil
end
# Read trailing CRLF:
chunk = @connection.read(length + 2)
if chunk.bytesize == length + 2
# ...and chomp it off:
chunk.chomp!(CRLF)
@length += length
@count += 1
return chunk
else
# The connection has been closed before we have read the requested length:
@connection.close_read
@connection = nil
end
end
# If the connection has been closed before we have read the final chunk, raise an error:
raise EOFError, "connection closed before expected length was read!"
end
end
def inspect
Signature
-
returns
String
a human-readable representation of the body.
Implementation
def inspect
"\#<#{self.class} #{@length} bytes read in #{@count} chunks>"
end
def read_trailer
Read the trailer from the connection, and add any headers to the trailer.
Implementation
def read_trailer
while line = @connection.read_line?
# Empty line indicates end of trailer:
break if line.empty?
if match = line.match(HEADER)
@headers.add(match[1], match[2])
else
raise BadHeader, "Could not parse header: #{line.inspect}"
end
end
end