def read(length = nil, outbuf = nil)
      fail IOError, "closed stream" if closed?

      remaining_length = length

      begin
        data = readpartial(remaining_length, outbuf)
        data = data.dup unless outbuf
        remaining_length = length - data.bytesize if length
      rescue EOFError
      end

      until remaining_length == 0 || eof?
        data << readpartial(remaining_length)
        remaining_length = length - data.bytesize if length
      end

      data.to_s unless length && length > 0 && (data.nil? || data.empty?)
    end
def read(length = nil, outbuf = nil)
      fail IOError, "closed stream" if closed?

      remaining_length = length

      begin
        data = readpartial(remaining_length, outbuf)
        data = data.dup unless outbuf
        remaining_length = length - data.bytesize if length
      rescue EOFError
      end

      until remaining_length == 0 || eof?
        data << readpartial(remaining_length)
        remaining_length = length - data.bytesize if length
      end

      data.to_s unless length && length > 0 && (data.nil? || data.empty?)
    end
Implements IO#read semantics. Without arguments it retrieves and returns
 all content.

 With `length` argument returns exactly that number of bytes if they're
 available.

 With `outbuf` argument each call will return that same string object,
 where the value is replaced with retrieved content.

 If end of file is reached, returns empty string if called without
 arguments, or nil if called with arguments. Raises IOError if closed.