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
|
# frozen_string_literal: true
module HTTP
class Request
class Body
attr_reader :source
def initialize(source)
@source = source
validate_source_type!
end
# Returns size which should be used for the "Content-Length" header.
#
# @return [Integer]
def size
if @source.is_a?(String)
@source.bytesize
elsif @source.respond_to?(:read)
raise RequestError, "IO object must respond to #size" unless @source.respond_to?(:size)
@source.size
elsif @source.nil?
0
else
raise RequestError, "cannot determine size of body: #{@source.inspect}"
end
end
# Yields chunks of content to be streamed to the request body.
#
# @yieldparam [String]
def each(&block)
if @source.is_a?(String)
yield @source
elsif @source.respond_to?(:read)
IO.copy_stream(@source, ProcIO.new(block))
rewind(@source)
elsif @source.is_a?(Enumerable)
@source.each(&block)
end
self
end
# Request bodies are equivalent when they have the same source.
def ==(other)
self.class == other.class && self.source == other.source # rubocop:disable Style/RedundantSelf
end
private
def rewind(io)
io.rewind if io.respond_to? :rewind
rescue Errno::ESPIPE, Errno::EPIPE
# Pipe IOs respond to `:rewind` but fail when you call it.
#
# Calling `IO#rewind` on a pipe, fails with *ESPIPE* on MRI,
# but *EPIPE* on jRuby.
#
# - **ESPIPE** -- "Illegal seek."
# Invalid seek operation (such as on a pipe).
#
# - **EPIPE** -- "Broken pipe."
# There is no process reading from the other end of a pipe. Every
# library function that returns this error code also generates
# a SIGPIPE signal; this signal terminates the program if not handled
# or blocked. Thus, your program will never actually see EPIPE unless
# it has handled or blocked SIGPIPE.
#
# See: https://www.gnu.org/software/libc/manual/html_node/Error-Codes.html
nil
end
def validate_source_type!
return if @source.is_a?(String)
return if @source.respond_to?(:read)
return if @source.is_a?(Enumerable)
return if @source.nil?
raise RequestError, "body of wrong type: #{@source.class}"
end
# This class provides a "writable IO" wrapper around a proc object, with
# #write simply calling the proc, which we can pass in as the
# "destination IO" in IO.copy_stream.
class ProcIO
def initialize(block)
@block = block
end
def write(data)
@block.call(data)
data.bytesize
end
end
end
end
end
|