Wikipedia Affiliate Button

четвер, 25 грудня 2008 р.

Multipart post in Ruby

Uploading a file to a server is a common task in web-development practice. It requires you to create a special HTML form with enctype set to 'multipart/form-data' and include an input of type 'file' into it.
However sometime you want to upload a file to some third-party service from your Ruby script on background. For this you'll need to properly construct the multipart POST request and send it out via Ruby net/http library.
After playing a bit I came up with the following class. A very strong advantage of it is that it works well with big files ( such as videos ).
class Multipart
 
  def initialize( file_names )
    @file_names = file_names
  end
 
  def post( to_url )
    boundary = '----RubyMultipartClient' + rand(1000000).to_s + 'ZZZZZ'
   
    parts = []
    streams = []
    @file_names.each do |param_name, filepath|
      pos = filepath.rindex('/')
      filename = filepath[pos + 1, filepath.length - pos]
      parts << StringPart.new ( "--" + boundary + "\r\n" +
      "Content-Disposition: form-data; name=\"" + param_name.to_s + "\"; filename=\"" + filename + "\"\r\n" +
      "Content-Type: video/x-msvideo\r\n\r\n")
      stream = File.open(filepath, "rb")
      streams << stream
      parts << StreamPart.new (stream, File.size(filepath))
    end
    parts << StringPart.new ( "\r\n--" + boundary + "--\r\n" )
   
    post_stream = MultipartStream.new( parts )
   
    url = URI.parse( to_url )
    req = Net::HTTP::Post.new(url.path)
    req.content_length = post_stream.size
    req.content_type = 'multipart/form-data; boundary=' + boundary
    req.body_stream = post_stream
    res = Net::HTTP.new(url.host, url.port).start {|http| http.request(req) }

    streams.each do |stream|
      stream.close();
    end
   
    res
  end
 
end

class StreamPart
  def initialize( stream, size )
    @stream, @size = stream, size
  end
 
  def size
    @size
  end
 
  def read ( offset, how_much )
    @stream.read ( how_much )
  end
end

class StringPart
  def initialize ( str )
    @str = str
  end
 
  def size
    @str.length
  end
 
  def read ( offset, how_much )
    @str[offset, how_much]
  end
end

class MultipartStream
  def initialize( parts )
    @parts = parts
    @part_no = 0;
    @part_offset = 0;
  end
 
  def size
    total = 0
    @parts.each do |part|
      total += part.size
    end
    total
  end
 
  def read ( how_much )
   
    if @part_no >= @parts.size
      return nil;
    end
   
    how_much_current_part = @parts[@part_no].size - @part_offset
   
    how_much_current_part = if how_much_current_part > how_much
      how_much
    else
      how_much_current_part
    end
   
    how_much_next_part = how_much - how_much_current_part
   
    current_part = @parts[@part_no].read(@part_offset, how_much_current_part )

    if how_much_next_part > 0
      @part_no += 1
      @part_offset = 0
      next_part = read ( how_much_next_part  )
      current_part + if next_part
        next_part
      else
        ''
      end
    else
      @part_offset += how_much_current_part
      current_part
    end
  end
end

Немає коментарів: