Multipart POST in Ruby

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]

4 Responses to “Multipart POST in Ruby”

  1. Keith says:

    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}"
        end
    
  2. stanislav.vitvitskiy says:

    I 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
    end
    
  3. stanislav.vitvitskiy says:

    The 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

  4. Keith says:

    Thanks, Stanislav.