In Merb is the new black Josh Susser mentions:
I also wrote a little client command line script in Ruby that wraps curl to upload the files to the web service. (I had to use curl because Ruby doesn’t have a simple solution for doing multipart file uploads.)
I ran into this once, and ended up writing a solution (for essentially the same problem) as a Multipart module. It’s ugly but functional, and almost all borrowed from here and Bill Stilwell.
[Update: See the comments for more useful versions]
Someone asked me how this actually gets used. Here’s a quick example:
def self.post_form(url, query, headers) Net::HTTP.start(url.host, url.port) {|con| con.read_timeout = TIMEOUT_SECONDS begin return con.post(url.path, query, headers) rescue => e puts "POSTING Failed #{e}... #{Time.now}" end } end # my server expects the params of the POST to # use the rails-like "blah[bar]" syntax, and I # need to send two things other than the file # itself. These are stored in the params Hash params = Hash.new # Open the actually file I want to send file = File.open(filename, "rb") # set the params to meaningful values params["file[new_file]"] = file params["file[abc_filename]"] = abc_filename params["xyz"] = xyz # make a MultipartPost mp = Multipart::MultipartPost.new # Get both the headers and the query ready, # given the new MultipartPost and the params # Hash query, headers = mp.prepare_query(params) # done with file now file.close # Make sure the URL is useable url = URI.parse(URL) # Do the actual POST, given the right inputs res = post_form(url, query, headers) # res holds the response to the POST case res when Net::HTTPSuccess puts "Hooray" when Net::HTTPInternalServerError raise "Server blew up" else raise "Unknown error #{res}: #{res.inspect}" endI played around it a bit and came up with the class more suitable for bigger files ( such as video ).
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 <= @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 endThe above post is partial. So I posted the full source on my own blog http://stanislavvitvitskiy.blogspot.com/2008/12/multipart-post-in-ruby.html
Thanks, Stanislav.