module Protocol::HTTP::Body::Stream::Reader
def each(&block)
Iterate over each chunk of data from the input stream.
def each(&block) return to_enum unless block_given? if @buffer yield @buffer @buffer = nil end while chunk = read_next yield chunk end end
def gets(separator = NEWLINE, limit = nil, chomp: false)
@parameter limit [Integer] The maximum number of bytes to read.
@parameter separator [String] The line separator, defaults to `\n`.
Read a single line from the stream.
def gets(separator = NEWLINE, limit = nil, chomp: false) # If the separator is an integer, it is actually the limit: if separator.is_a?(Integer) limit = separator separator = NEWLINE end # If no separator is given, this is the same as a read operation: if separator.nil? # I tried using `read(limit)` here but it will block until the limit is reached, which is not usually desirable behaviour. return read_partial(limit) end # We don't want to split on the separator, so we subtract the size of the separator: split_offset = separator.bytesize - 1 @buffer ||= read_next return nil if @buffer.nil? offset = 0 until index = @buffer.index(separator, offset) offset = @buffer.bytesize - split_offset offset = 0 if offset < 0 # If we have gone past the limit, we are done: if limit and offset >= limit @buffer.freeze matched = @buffer.byteslice(0, limit) @buffer = @buffer.byteslice(limit, @buffer.bytesize) return matched end # Read more data: if chunk = read_next @buffer << chunk else # No more data could be read, return the remaining data: buffer = @buffer @buffer = nil return @buffer end end # Freeze the buffer, as this enables us to use byteslice without generating a hidden copy: @buffer.freeze if limit and index > limit line = @buffer.byteslice(0, limit) @buffer = @buffer.byteslice(limit, @buffer.bytesize) else line = @buffer.byteslice(0, index+(chomp ? 0 : separator.bytesize)) @buffer = @buffer.byteslice(index+separator.bytesize, @buffer.bytesize) end return line end
def read(length = nil, buffer = nil)
@parameter buffer [String] the buffer which will receive the data
@parameterlength [Integer] the amount of data to read
If buffer is given, then the read data will be placed into buffer instead of a newly created String object.
If the length is not given, it will read all data until EOF, or return an empty string if the stream is already at EOF.
If given a non-negative length, it will read at most that many bytes from the stream. If the stream is at EOF, it will return nil.
Read data from the underlying stream.
def read(length = nil, buffer = nil) return "" if length == 0 buffer ||= String.new.force_encoding(Encoding::BINARY) # Take any previously buffered data and replace it into the given buffer. if @buffer buffer.replace(@buffer) @buffer = nil else buffer.clear end if length while buffer.bytesize < length and chunk = read_next buffer << chunk end # This ensures the subsequent `slice!` works correctly. buffer.force_encoding(Encoding::BINARY) # This will be at least one copy: @buffer = buffer.byteslice(length, buffer.bytesize) # This should be zero-copy: buffer.slice!(length, buffer.bytesize) if buffer.empty? return nil else return buffer end else while chunk = read_next buffer << chunk end return buffer end end
def read_nonblock(length, buffer = nil, exception: nil)
@parameter length [Integer] The maximum number of bytes to read.
Read data from the stream without blocking if possible.
def read_nonblock(length, buffer = nil, exception: nil) @buffer ||= read_next chunk = nil unless @buffer buffer&.clear return end if @buffer.bytesize > length chunk = @buffer.byteslice(0, length) @buffer = @buffer.byteslice(length, @buffer.bytesize) else chunk = @buffer @buffer = nil end if buffer buffer.replace(chunk) else buffer = chunk end return buffer end
def read_partial(length = nil, buffer = nil)
Will avoid reading from the underlying stream if there is buffered data available.
If the length is given, at most length bytes will be read. Otherwise, one chunk of data from the underlying stream will be read.
Read some bytes from the stream.
def read_partial(length = nil, buffer = nil) if @buffer if buffer buffer.replace(@buffer) else buffer = @buffer end @buffer = nil else if chunk = read_next if buffer buffer.replace(chunk) else buffer = chunk end else buffer&.clear buffer = nil end end if buffer and length if buffer.bytesize > length # This ensures the subsequent `slice!` works correctly. buffer.force_encoding(Encoding::BINARY) @buffer = buffer.byteslice(length, buffer.bytesize) buffer.slice!(length, buffer.bytesize) end end return buffer end
def read_until(pattern, offset = 0, chomp: false)
@parameter chomp [Boolean] Whether to remove the pattern from the returned data.
@parameter offset [Integer] The offset to start searching from.
@parameter pattern [String] The pattern to match.
Read data from the stream until encountering pattern.
def read_until(pattern, offset = 0, chomp: false) # We don't want to split on the pattern, so we subtract the size of the pattern. split_offset = pattern.bytesize - 1 @buffer ||= read_next return nil if @buffer.nil? until index = @buffer.index(pattern, offset) offset = @buffer.bytesize - split_offset offset = 0 if offset < 0 if chunk = read_next @buffer << chunk else return nil end end @buffer.freeze matched = @buffer.byteslice(0, index+(chomp ? 0 : pattern.bytesize)) @buffer = @buffer.byteslice(index+pattern.bytesize, @buffer.bytesize) return matched end
def readpartial(length, buffer = nil)
@parameter length [Integer] The maximum number of bytes to read.
Similar to {read_partial} but raises an `EOFError` if the stream is at EOF.
def readpartial(length, buffer = nil) read_partial(length, buffer) or raise EOFError, "End of file reached!" end