lib/httpx/response/body.rb in httpx-1.0.2 vs lib/httpx/response/body.rb in httpx-1.1.0
- old
+ new
@@ -1,21 +1,29 @@
# frozen_string_literal: true
module HTTPX
+ # Implementation of the HTTP Response body as a buffer which implements the IO writer protocol
+ # (for buffering the response payload), the IO reader protocol (for consuming the response payload),
+ # and can be iterated over (via #each, which yields the payload in chunks).
class Response::Body
- attr_reader :encoding, :encodings
+ # the payload encoding (i.e. "utf-8", "ASCII-8BIT")
+ attr_reader :encoding
+ # Array of encodings contained in the response "content-encoding" header.
+ attr_reader :encodings
+
+ # initialized with the corresponding HTTPX::Response +response+ and HTTPX::Options +options+.
def initialize(response, options)
@response = response
@headers = response.headers
@options = options
- @threshold_size = options.body_threshold_size
@window_size = options.window_size
@encoding = response.content_type.charset || Encoding::BINARY
@encodings = []
@length = 0
@buffer = nil
+ @reader = nil
@state = :idle
initialize_inflaters
end
def initialize_dup(other)
@@ -26,10 +34,12 @@
def closed?
@state == :closed
end
+ # write the response payload +chunk+ into the buffer. Inflates the chunk when required
+ # and supported.
def write(chunk)
return if @state == :closed
@inflaters.reverse_each do |inflater|
chunk = inflater.call(chunk)
@@ -42,10 +52,11 @@
@response.emit(:chunk_received, chunk)
size
end
+ # reads a chunk from the payload (implementation of the IO reader protocol).
def read(*args)
return unless @buffer
unless @reader
rewind
@@ -53,14 +64,17 @@
end
@reader.read(*args)
end
+ # size of the decoded response payload. May differ from "content-length" header if
+ # response was encoded over-the-wire.
def bytesize
@length
end
+ # yields the payload in chunks.
def each
return enum_for(__method__) unless block_given?
begin
if @buffer
@@ -72,28 +86,36 @@
ensure
close
end
end
+ # returns the declared filename in the "contennt-disposition" header, when present.
def filename
return unless @headers.key?("content-disposition")
Utils.get_filename(@headers["content-disposition"])
end
+ # returns the full response payload as a string.
def to_s
return "".b unless @buffer
@buffer.to_s
end
alias_method :to_str, :to_s
+ # whether the payload is empty.
def empty?
@length.zero?
end
+ # copies the payload to +dest+.
+ #
+ # body.copy_to("path/to/file")
+ # body.copy_to(Pathname.new("path/to/file"))
+ # body.copy_to(File.new("path/to/file"))
def copy_to(dest)
return unless @buffer
rewind
@@ -130,10 +152,11 @@
"@state=#{@state} " \
"@length=#{@length}>"
end
# :nocov:
+ # rewinds the response payload buffer.
def rewind
return unless @buffer
# in case there's some reading going on
@reader = nil
@@ -142,10 +165,12 @@
end
private
def initialize_inflaters
+ @inflaters = nil
+
return unless @headers.key?("content-encoding")
return unless @options.decompress_response_body
@inflaters = @headers.get("content-encoding").filter_map do |encoding|
@@ -166,22 +191,22 @@
case nextstate
when :open
return unless @state == :idle
@buffer = Response::Buffer.new(
- threshold_size: @threshold_size,
+ threshold_size: @options.body_threshold_size,
bytesize: @length,
encoding: @encoding
)
when :closed
return if @state == :closed
end
@state = nextstate
end
- def _with_same_buffer_pos
+ def _with_same_buffer_pos # :nodoc:
return yield unless @buffer && @buffer.respond_to?(:pos)
# @type ivar @buffer: StringIO | Tempfile
current_pos = @buffer.pos
@buffer.rewind
@@ -191,10 +216,10 @@
@buffer.pos = current_pos
end
end
class << self
- def initialize_inflater_by_encoding(encoding, response, **kwargs)
+ def initialize_inflater_by_encoding(encoding, response, **kwargs) # :nodoc:
case encoding
when "gzip"
Transcoder::GZIP.decode(response, **kwargs)
when "deflate"
Transcoder::Deflate.decode(response, **kwargs)