1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165
|
# frozen_string_literal: true
# Released under the MIT License.
# Copyright, 2019-2025, by Samuel Williams.
# Copyright, 2020, by Bryan Powell.
# Copyright, 2025, by William T. Nelson.
require_relative "readable"
module Protocol
module HTTP
module Body
# A body which buffers all its contents.
class Buffered < Readable
# Tries to wrap an object in a {Buffered} instance.
#
# For compatibility, also accepts anything that behaves like an `Array(String)`.
#
# @parameter body [String | Array(String) | Readable | nil] the body to wrap.
# @returns [Readable | nil] the wrapped body or nil if nil was given.
def self.wrap(object)
if object.is_a?(Readable)
return object
elsif object.is_a?(Array)
return self.new(object)
elsif object.is_a?(String)
return self.new([object])
elsif object
return self.read(object)
end
end
# Read the entire body into a buffered representation.
#
# @parameter body [Readable] the body to read.
# @returns [Buffered] the buffered body.
def self.read(body)
chunks = []
body.each do |chunk|
chunks << chunk
end
self.new(chunks)
end
# Initialize the buffered body with some chunks.
#
# @parameter chunks [Array(String)] the chunks to buffer.
# @parameter length [Integer] the length of the body, if known.
def initialize(chunks = [], length = nil)
@chunks = chunks
@length = length
@index = 0
end
# @attribute [Array(String)] chunks the buffered chunks.
attr :chunks
# A rewindable body wraps some other body. Convert it to a buffered body. The buffered body will share the same chunks as the rewindable body.
#
# @returns [Buffered] the buffered body.
def buffered
self.class.new(@chunks)
end
# Finish the body, this is a no-op.
#
# @returns [Buffered] self.
def finish
self
end
# Ensure that future reads return `nil`, but allow for rewinding.
#
# @parameter error [Exception | Nil] the error that caused the body to be closed, if any.
def close(error = nil)
@index = @chunks.length
return nil
end
# Clear the buffered chunks.
def clear
@chunks = []
@length = 0
@index = 0
end
# The length of the body. Will compute and cache the length of the body, if it was not provided.
def length
@length ||= @chunks.inject(0) {|sum, chunk| sum + chunk.bytesize}
end
# @returns [Boolean] if the body is empty.
def empty?
@index >= @chunks.length
end
# Whether the body is ready to be read.
# @returns [Boolean] a buffered response is always ready.
def ready?
true
end
# Read the next chunk from the buffered body.
#
# @returns [String | Nil] the next chunk or nil if there are no more chunks.
def read
return nil unless @chunks
if chunk = @chunks[@index]
@index += 1
return chunk.dup
end
end
# Discard the body. Invokes {#close}.
def discard
# It's safe to call close here because there is no underlying stream to close:
self.close
end
# Write a chunk to the buffered body.
def write(chunk)
@chunks << chunk
end
# Close the body for writing. This is a no-op.
def close_write(error)
# Nothing to do.
end
# Whether the body can be rewound.
#
# @returns [Boolean] if the body has chunks.
def rewindable?
@chunks != nil
end
# Rewind the body to the beginning, causing a subsequent read to return the first chunk.
def rewind
return false unless @chunks
@index = 0
return true
end
# Inspect the buffered body.
#
# @returns [String] a string representation of the buffered body.
def inspect
if @chunks and @chunks.size > 0
"#<#{self.class} #{@index}/#{@chunks.size} chunks, #{self.length} bytes>"
else
"#<#{self.class} empty>"
end
end
end
end
end
end
|