class Part
- Includes
Readable: #copy_to, #save
Represents a single part within a multipart message.
Definitions
def initialize(readable, headers, boundary)
Initialize a new part with a readable stream, headers, and a boundary string.
Signature
-
parameter
readableIO::Stream The readable stream that contains the part's data.
-
parameter
headersHeaders The headers associated with this part.
-
parameter
boundaryString The boundary string used to separate parts.
Implementation
def initialize(readable, headers, boundary)
@readable = readable
@boundary = boundary
@headers = headers
@ended = false
@is_closing = false
end
attr_reader :headers
Signature
-
attribute
Headers The headers associated with this part.
def each(chunk_size = 8192)
Iterate through the part content in chunks.
Signature
-
parameter
chunk_sizeInteger The size of each chunk to read.
-
returns
Enumerable An enumerable of content chunks if no block given.
Implementation
def each(chunk_size = 8192)
return to_enum(:each, chunk_size) unless block_given?
return unless @readable
boundary_marker = "\r\n--#{@boundary}"
# Stream data in chunks using read_until with a limit
while @readable
if chunk = @readable.read_until(boundary_marker, limit: chunk_size, chomp: true)
# We found the boundary, check if it's a closing boundary:
if suffix = @readable.read_until("\r\n", chomp: true)
@is_closing = (suffix == "--")
@ended = true
@readable = nil
else
@readable = nil
raise EOFError, "Unexpected end of stream while reading part data!"
end
else
chunk = @readable.read(chunk_size)
end
if chunk
yield chunk unless chunk.empty?
else
# No more data to read, break the loop:
break
end
end
end
def read_empty_boundary?
Checks if the next content is an empty boundary (part with no content).
Signature
-
returns
Boolean True if an empty boundary was found and read, false otherwise.
Implementation
def read_empty_boundary?
boundary_marker = "--#{@boundary}"
if @readable.peek(boundary_marker.bytesize) == boundary_marker
@readable.read(boundary_marker.bytesize)
self.read_boundary_suffix
return true
end
return false
end
def read_boundary_suffix
Reads the suffix after a boundary to determine if it's a closing boundary.
Implementation
def read_boundary_suffix
# Read the rest of the boundary line to check if it's closing:
boundary_suffix = @readable.read(2)
if boundary_suffix == "--"
@is_closing = true
@ended = true
@readable = nil
elsif boundary_suffix == "\r\n"
@is_closing = false
@ended = true
@readable = nil
else
@readable = nil
raise EOFError, "Unexpected end of stream while reading part data!"
end
end
def finish
Finishes reading this part's data and advances to the next boundary.
Signature
-
returns
String | Nil The remaining content of the part, or nil if already finished.
Implementation
def finish
return unless @readable
# Read all data until boundary
data = @readable.read_until("\r\n--#{@boundary}", chomp: true)
self.read_boundary_suffix
return data
end
def discard
Efficiently discards all data until the next boundary is found. This is used to skip parts without reading their content into memory.
Signature
-
returns
Nil
Implementation
def discard
# Efficiently discard all data until boundary
return unless @readable
# Discard data until boundary
@readable.discard_until("\r\n--#{@boundary}")
self.read_boundary_suffix
return nil
end
def ended?
Checks if this part has been completely read.
Signature
-
returns
Boolean True if this part has been completely read.
Implementation
def ended?
@ended
end
def closing_boundary?
Checks if this part ends with a closing boundary. A closing boundary indicates that this is the last part in the multipart message.
Signature
-
returns
Boolean True if this part ends with a closing boundary.
Implementation
def closing_boundary?
@is_closing || (@readable.nil? && @is_closing)
end