Protocol::HTTPSourceProtocolHTTPBodyReadable

class Readable

Represents a readable input streams.

There are two major modes of operation:

  1. Reading chunks using Protocol::HTTP::Body::Readable#read (or Protocol::HTTP::Body::Readable#each/Protocol::HTTP::Body::Readable#join), until the body is empty, or
  2. 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.

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