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
|
# frozen_string_literal: true
require "delegate"
require "stringio"
require "tempfile"
module HTTPX
# wraps and delegates to an internal buffer, which can be a StringIO or a Tempfile.
class Response::Buffer < SimpleDelegator
attr_reader :buffer
protected :buffer
# initializes buffer with the +threshold_size+ over which the payload gets buffer to a tempfile,
# the initial +bytesize+, and the +encoding+.
def initialize(threshold_size:, bytesize: 0, encoding: Encoding::BINARY)
@threshold_size = threshold_size
@bytesize = bytesize
@encoding = encoding
@buffer = StringIO.new("".b)
super(@buffer)
end
def initialize_dup(other)
super
# create new descriptor in READ-ONLY mode
@buffer =
case other.buffer
when StringIO
StringIO.new(other.buffer.string, mode: File::RDONLY)
else
other.buffer.class.new(other.buffer.path, encoding: Encoding::BINARY, mode: File::RDONLY).tap do |temp|
FileUtils.copy_file(other.buffer.path, temp.path)
end
end
end
# size in bytes of the buffered content.
def size
@bytesize
end
# writes the +chunk+ into the buffer.
def write(chunk)
@bytesize += chunk.bytesize
try_upgrade_buffer
@buffer.write(chunk)
end
# returns the buffered content as a string.
def to_s
case @buffer
when StringIO
begin
@buffer.string.force_encoding(@encoding)
rescue ArgumentError
@buffer.string
end
when Tempfile
rewind
content = @buffer.read
begin
content.force_encoding(@encoding)
rescue ArgumentError # ex: unknown encoding name - utf
content
end
end
end
# closes the buffer.
def close
@buffer.close
@buffer.unlink if @buffer.respond_to?(:unlink)
end
def ==(other)
super || begin
return false unless other.is_a?(Response::Buffer)
buffer_pos = @buffer.pos
other_pos = other.buffer.pos
@buffer.rewind
other.buffer.rewind
begin
FileUtils.compare_stream(@buffer, other.buffer)
ensure
@buffer.pos = buffer_pos
other.buffer.pos = other_pos
end
end
end
private
# initializes the buffer into a StringIO, or turns it into a Tempfile when the threshold
# has been reached.
def try_upgrade_buffer
return unless @bytesize > @threshold_size
return if @buffer.is_a?(Tempfile)
aux = @buffer
@buffer = Tempfile.new("httpx", encoding: Encoding::BINARY, mode: File::RDWR)
if aux
aux.rewind
IO.copy_stream(aux, @buffer)
aux.close
end
__setobj__(@buffer)
end
end
end
|