class Readable
Represents a readable input streams.
There are two major modes of operation:
- Reading chunks using
Protocol::HTTP::Body::Readable#read
(orProtocol::HTTP::Body::Readable#each
/Protocol::HTTP::Body::Readable#join
), until the body is empty, or - Streaming chunks using
Protocol::HTTP::Body::Readable#call
, which writes chunks to a provided output stream.
In both cases, reading can fail, for example if the body represents a streaming upload, and the connection is lost. In this case, Protocol::HTTP::Body::Readable#read
will raise some kind of error, or the stream will be closed with an error.
At any point, you can use Protocol::HTTP::Body::Readable#close
to close the stream and release any resources, or Protocol::HTTP::Body::Readable#discard
to read all remaining data without processing it which may allow the underlying connection to be reused (but can be slower).
Definitions
def close(error = nil)
Close the stream immediately. After invoking this method, the stream should be considered closed, and all internal resources should be released.
If an error occured while handling the output, it can be passed as an argument. This may be propagated to the client, for example the client may be informed that the stream was not fully read correctly.
Invoking Protocol::HTTP::Body::Readable#read
after Protocol::HTTP::Body::Readable#close
will return nil
.
Signature
-
parameter
error
Exception | Nil
The error that caused this stream to be closed, if any.
Implementation
def close(error = nil)
end
def empty?
Optimistically determine whether read (may) return any data.
- If this returns true, then calling read will definitely return nil.
- If this returns false, then calling read may return nil.
Implementation
def empty?
false
end
def ready?
Whether calling read will return a chunk of data without blocking. We prefer pessimistic implementation, and thus default to false
.
Implementation
def ready?
false
end
def rewindable?
Whether the stream can be rewound using Protocol::HTTP::Body::Readable#rewind
.
Implementation
def rewindable?
false
end
def rewind
Rewind the stream to the beginning.
Signature
-
returns
Boolean
Whether the stream was successfully rewound.
Implementation
def rewind
false
end
def buffered
Return a buffered representation of this body.
This method must return a buffered body if #rewindable?
.
Signature
-
returns
Buffered | Nil
The buffered body.
Implementation
def buffered
nil
end
def length
The total length of the body, if known.
Signature
-
returns
Integer | Nil
The total length of the body, or
nil
if the length is unknown.
Implementation
def length
nil
end
def read
Read the next available chunk.
Signature
-
returns
String | Nil
The chunk of data, or
nil
if the stream has finished.-
raises
StandardError
If an error occurs while reading.
Implementation
def read
nil
end
def each
Enumerate all chunks until finished, then invoke Protocol::HTTP::Body::Readable#close
.
Closes the stream when finished or if an error occurs.
Signature
-
yields
{|chunk| ...}
The block to call with each chunk of data.
-
parameter
chunk
String | Nil
The chunk of data, or
nil
if the stream has finished.
-
parameter
Implementation
def each
return to_enum unless block_given?
begin
while chunk = self.read
yield chunk
end
rescue => error
raise
ensure
self.close(error)
end
end
def join
Read all remaining chunks into a single binary string using #each
.
Signature
-
returns
String | Nil
The binary string containing all chunks of data, or
nil
if the stream has finished (or did not contain any data).
Implementation
def join
buffer = String.new.force_encoding(Encoding::BINARY)
self.each do |chunk|
buffer << chunk
end
if buffer.empty?
return nil
else
return buffer
end
end
def stream?
Whether to prefer streaming the body using Protocol::HTTP::Body::Readable#call
rather than reading it using Protocol::HTTP::Body::Readable#read
or Protocol::HTTP::Body::Readable#each
.
Signature
-
returns
Boolean
Whether the body should be streamed.
Implementation
def stream?
false
end
def call(stream)
Invoke the body with the given stream.
The default implementation simply writes each chunk to the stream. If the body is not ready, it will be flushed after each chunk. Closes the stream when finished or if an error occurs.
Write the body to the given stream.
Signature
-
parameter
stream
IO | Object
An
IO
-like object that responds to#read
,#write
and#flush
.-
returns
Boolean
Whether the ownership of the stream was transferred.
Implementation
def call(stream)
self.each do |chunk|
stream.write(chunk)
# Flush the stream unless we are immediately expecting more data:
unless self.ready?
stream.flush
end
end
ensure
# TODO Should this invoke close_write(error) instead?
stream.close
end
def finish
Read all remaining chunks into a buffered body and close the underlying input.
Signature
-
returns
Buffered
The buffered body.
Implementation
def finish
# Internally, this invokes `self.each` which then invokes `self.close`.
Buffered.read(self)
end
def discard
Discard the body as efficiently as possible.
The default implementation simply reads all chunks until the body is empty.
Useful for discarding the body when it is not needed, but preserving the underlying connection.
Implementation
def discard
while chunk = self.read
end
end
def as_json(...)
Convert the body to a hash suitable for serialization. This won't include the contents of the body, but will include metadata such as the length, streamability, and readiness, etc.
Signature
-
returns
Hash
The body as a hash.
Implementation
def as_json(...)
{
class: self.class.name,
length: self.length,
stream: self.stream?,
ready: self.ready?,
empty: self.empty?
}
end
def to_json(...)
Convert the body to JSON.
Signature
-
returns
String
The body as JSON.
Implementation
def to_json(...)
as_json.to_json(...)
end