lib/protocol/http/body/readable.rb in protocol-http-0.32.0 vs lib/protocol/http/body/readable.rb in protocol-http-0.33.0
- old
+ new
@@ -5,13 +5,19 @@
# Copyright, 2023, by Bruno Sutic.
module Protocol
module HTTP
module Body
- # An interface for reading data from a body.
+ # Represents a readable input streams.
#
# Typically, you'd override `#read` to return chunks of data.
+ #
+ # I n general, you read chunks of data from a body until it is empty and returns `nil`. Upon reading `nil`, the body is considered consumed and should not be read from again.
+ #
+ # Reading can also fail, for example if the body represents a streaming upload, and the connection is lost. In this case, the body will raise some kind of error.
+ #
+ # If you don't want to read from a stream, and instead want to close it immediately, you can call `close` on the body. If the body is already completely consumed, `close` will do nothing, but if there is still data to be read, it will cause the underlying stream to be reset (and possibly closed).
class Readable
# Close the stream immediately.
def close(error = nil)
end
@@ -27,67 +33,50 @@
# @return [Boolean]
def ready?
false
end
+ # Whether the stream can be rewound using {rewind}.
def rewindable?
false
end
+ # Rewind the stream to the beginning.
+ # @returns [Boolean] Whether the stream was successfully rewound.
def rewind
false
end
+ # The total length of the body, if known.
+ # @returns [Integer | Nil] The total length of the body, or `nil` if the length is unknown.
def length
nil
end
# Read the next available chunk.
# @returns [String | Nil] The chunk of data, or `nil` if the stream has finished.
+ # @raises [StandardError] If an error occurs while reading.
def read
nil
end
- # Should the internal mechanism prefer to use {call}?
- # @returns [Boolean]
- def stream?
- false
- end
-
- # Write the body to the given stream.
- def call(stream)
- while chunk = self.read
- stream.write(chunk)
-
- # Flush the stream unless we are immediately expecting more data:
- unless self.ready?
- stream.flush
- end
- end
- end
-
- # Read all remaining chunks into a buffered body and close the underlying input.
- # @returns [Buffered] The buffered body.
- def finish
- # Internally, this invokes `self.each` which then invokes `self.close`.
- Buffered.read(self)
- end
-
# Enumerate all chunks until finished, then invoke `#close`.
#
+ # Closes the stream when finished or if an error occurs.
+ #
# @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.
def each
- return to_enum(:each) unless block_given?
+ return to_enum unless block_given?
- begin
- while chunk = self.read
- yield chunk
- end
- ensure
- self.close($!)
+ while chunk = self.read
+ yield chunk
end
+ rescue => error
+ raise
+ ensure
+ self.close(error)
end
# Read all remaining chunks into a single binary string using `#each`.
#
# @returns [String | Nil] The binary string containing all chunks of data, or `nil` if the stream has finished (or did not contain any data).
@@ -101,9 +90,38 @@
if buffer.empty?
return nil
else
return buffer
end
+ end
+
+ def stream?
+ false
+ end
+
+ # Write the body to the given stream.
+ #
+ # In some cases, the stream may also be readable, such as when hijacking an HTTP/1 connection. In that case, it may be acceptable to read and write to the stream directly.
+ #
+ # If the stream is not ready, it will be flushed after each chunk. Closes the stream when finished or if an error occurs.
+ #
+ 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
+ end
+
+ # Read all remaining chunks into a buffered body and close the underlying input.
+ #
+ # @returns [Buffered] The buffered body.
+ def finish
+ # Internally, this invokes `self.each` which then invokes `self.close`.
+ Buffered.read(self)
end
def as_json(...)
{
class: self.class.name,