class HTTP::Message::Body

Represents HTTP message body.

Constants

DEFAULT_CHUNK_SIZE

Default value for chunk_size

Attributes

chunk_size[RW]

maxbytes of IO#read for streaming request. See DEFAULT_CHUNK_SIZE.

positions[RW]

Hash that keeps IO positions

size[R]

Size of body. nil when size is unknown (e.g. chunked response).

Public Class Methods

new() click to toggle source

Creates a Message::Body. Use init_request or init_response for acutual initialize.

# File lib/httpclient/http.rb, line 455
def initialize
  @body = nil
  @size = nil
  @positions = nil
  @chunk_size = nil
end

Public Instance Methods

content() click to toggle source

Returns a message body itself.

# File lib/httpclient/http.rb, line 544
def content
  @body
end
dump(header = '', dev = '') click to toggle source

Dumps message body to given dev. dev needs to respond to <<.

Message header must be given as the first argument for performance reason. (header is dumped to dev, too) If no dev (the second argument) given, this method returns a dumped String.

assert: @size is not nil

# File lib/httpclient/http.rb, line 493
def dump(header = '', dev = '')
  if @body.is_a?(Parts)
    dev << header
    @body.parts.each do |part|
      if Message.file?(part)
        reset_pos(part)
        dump_file(part, dev, @body.sizes[part])
      else
        dev << part
      end
    end
  elsif Message.file?(@body)
    dev << header
    reset_pos(@body)
    dump_file(@body, dev, @size)
  elsif @body
    dev << header + @body
  else
    dev << header
  end
  dev
end
dump_chunked(header = '', dev = '') click to toggle source

Dumps message body with chunked encoding to given dev. dev needs to respond to <<.

Message header must be given as the first argument for performance reason. (header is dumped to dev, too) If no dev (the second argument) given, this method returns a dumped String.

# File lib/httpclient/http.rb, line 523
def dump_chunked(header = '', dev = '')
  dev << header
  if @body.is_a?(Parts)
    @body.parts.each do |part|
      if Message.file?(part)
        reset_pos(part)
        dump_chunks(part, dev)
      else
        dev << dump_chunk(part)
      end
    end
    dev << (dump_last_chunk + CRLF)
  elsif @body
    reset_pos(@body)
    dump_chunks(@body, dev)
    dev << (dump_last_chunk + CRLF)
  end
  dev
end
init_request(body = nil, boundary = nil) click to toggle source

Initialize this instance as a request.

# File lib/httpclient/http.rb, line 463
def init_request(body = nil, boundary = nil)
  @boundary = boundary
  @positions = {}
  set_content(body, boundary)
  @chunk_size = DEFAULT_CHUNK_SIZE
  self
end
init_response(body = nil) click to toggle source

Initialize this instance as a response.

# File lib/httpclient/http.rb, line 472
def init_response(body = nil)
  @body = body
  if @body.respond_to?(:bytesize)
    @size = @body.bytesize
  elsif @body.respond_to?(:size)
    @size = @body.size
  else
    @size = nil
  end
  self
end

Private Instance Methods

build_query_multipart_str(query, boundary) click to toggle source
# File lib/httpclient/http.rb, line 662
def build_query_multipart_str(query, boundary)
  parts = Parts.new
  query.each do |attr, value|
    headers = ["--#{boundary}"]
    if Message.file?(value)
      remember_pos(value)
      param_str = params_from_file(value).collect { |k, v|
        "#{k}=\"#{v}\""
      }.join("; ")
      if value.respond_to?(:mime_type)
        content_type = value.mime_type
      elsif value.respond_to?(:content_type)
        content_type = value.content_type
      else
        path = value.respond_to?(:path) ? value.path : nil
        content_type = Message.mime_type(path)
      end
      headers << %{Content-Disposition: form-data; name="#{attr}"; #{param_str}}
      headers << %{Content-Type: #{content_type}}
    elsif attr.is_a?(Hash)
      h = attr
      value = h[:content]
      h.each do |h_key, h_val|
        headers << %{#{h_key}: #{h_val}} if h_key != :content
      end
      remember_pos(value) if Message.file?(value)
    else
      headers << %{Content-Disposition: form-data; name="#{attr}"}
      value = value.to_s
    end
    parts.add(headers.join(CRLF) + CRLF + CRLF)
    parts.add(value)
    parts.add(CRLF)
  end
  parts.add("--#{boundary}--" + CRLF + CRLF) # empty epilogue
  parts
end
dump_chunk(str) click to toggle source
# File lib/httpclient/http.rb, line 593
def dump_chunk(str)
  dump_chunk_size(str.bytesize) + (str + CRLF)
end
dump_chunk_size(size) click to toggle source
# File lib/httpclient/http.rb, line 601
def dump_chunk_size(size)
  sprintf("%x", size) + CRLF
end
dump_chunks(io, dev) click to toggle source
# File lib/httpclient/http.rb, line 586
def dump_chunks(io, dev)
  buf = ''
  while !io.read(@chunk_size, buf).nil?
    dev << dump_chunk(buf)
  end
end
dump_file(io, dev, sz) click to toggle source
# File lib/httpclient/http.rb, line 575
def dump_file(io, dev, sz)
  buf = ''
  rest = sz
  while rest > 0
    n = io.read([rest, @chunk_size].min, buf)
    raise ArgumentError.new("Illegal size value: #size returns #{sz} but cannot read") if n.nil?
    dev << buf
    rest -= n.bytesize
  end
end
dump_last_chunk() click to toggle source
# File lib/httpclient/http.rb, line 597
def dump_last_chunk
  dump_chunk_size(0)
end
params_from_file(value) click to toggle source
# File lib/httpclient/http.rb, line 700
def params_from_file(value)
  params = {}
  original_filename = value.respond_to?(:original_filename) ? value.original_filename : nil
  path = value.respond_to?(:path) ? value.path : nil
  params['filename'] = original_filename || File.basename(path || '')
  # Creation time is not available from File::Stat
  if value.respond_to?(:mtime)
    params['modification-date'] = value.mtime.rfc822
  end
  if value.respond_to?(:atime)
    params['read-date'] = value.atime.rfc822
  end
  params
end
remember_pos(io) click to toggle source
# File lib/httpclient/http.rb, line 566
def remember_pos(io)
  # IO may not support it (ex. IO.pipe)
  @positions[io] = io.pos if io.respond_to?(:pos)
end
reset_pos(io) click to toggle source
# File lib/httpclient/http.rb, line 571
def reset_pos(io)
  io.pos = @positions[io] if @positions.key?(io)
end
set_content(body, boundary = nil) click to toggle source
# File lib/httpclient/http.rb, line 550
def set_content(body, boundary = nil)
  if Message.file?(body)
    # uses Transfer-Encoding: chunked if body does not respond to :size.
    # bear in mind that server may not support it. at least ruby's CGI doesn't.
    @body = body
    remember_pos(@body)
    @size = body.respond_to?(:size) ? body.size - body.pos : nil
  elsif boundary and Message.multiparam_query?(body)
    @body = build_query_multipart_str(body, boundary)
    @size = @body.size
  else
    @body = Message.create_query_part_str(body)
    @size = @body.bytesize
  end
end