Multipart File Upload in Ruby

Patrick Glandien picture Patrick Glandien · Jun 6, 2009 · Viewed 11.2k times · Source

I simply want to upload an image to a server with POST. As simple as this task sounds, there seems to be no simple solution in Ruby.

In my application I am using WWW::Mechanize for most things so I wanted to use it for this too, and had a source like this:

f = File.new(filename, File::RDWR)
reply = agent.post(
    'http://rest-test.heroku.com',
    {
         :pict       =>  f,
         :function   =>  'picture2',
         :username   =>  @username,
         :password   =>  @password,
         :pict_to    =>  0,
         :pict_type  =>  0
    }
)
f.close

This results in a totally garbage-ready file on the server that looks scrambled all over:

alt text http://imagehub.org/f/1tk8/garbage.png

My next step was to downgrade WWW::Mechanize to version 0.8.5. This worked until I tried to run it, which failed with an error like "Module not found in hpricot_scan.so". Using the Dependency Walker tool I could find out that hpricot_scan.so needed msvcrt-ruby18.dll. Yet after I put that .dll into my Ruby/bin-folder it gave me an empty error box from where on I couldn't debug very much further. So the problem here is that Mechanize 0.8.5 has a dependency on Hpricot instead of Nokogiri (which works flawlessly).


The next idea was to use a different gem, so I tried using Net::HTTP. After short research I could find out that there is no native support for multipart forms in Net::HTTP and instead you have to build a class that encodes etc. for you. The most helpful I could find was the Multipart-class by Stanislav Vitvitskiy. This class looked good so far, but it does not do what I need, because I don't want to post only files, I also want to post normal data, and that is not possible with his class.


My last attempt was to use RestClient. This looked promising, as there have been examples on how to upload files. Yet I can't get it to post the form as multipart.

f = File.new(filename, File::RDWR)
reply = RestClient.post(
    'http://rest-test.heroku.com',
    :pict       =>  f,
    :function   =>  'picture2',
    :username   =>  @username,
    :password   =>  @password,
    :pict_to    =>  0,
    :pict_type  =>  0
)
f.close

I am using http://rest-test.heroku.com which sends back the request to debug if it is sent correctly, and I always get this back:

POST http://rest-test.heroku.com/ with a 101 byte payload,
content type application/x-www-form-urlencoded
{
    "pict" => "#<File:0x30d30c4>",
    "username" => "s1kx",
    "pict_to" => "0",
    "function" => "picture2",
    "pict_type" => "0",
    "password" => "password"
}

This clearly shows that it does not use multipart/form-data as content-type but the standard application/x-www-form-urlencoded, although it definitely sees that pict is a file.


How can I upload a file in Ruby to a multipart form without implementing the whole encoding and data aligning myself?

Answer

Patrick Glandien picture Patrick Glandien · Jun 6, 2009

Long problem, short answer: I was missing the binary mode for reading the image under Windows.

f = File.new(filename, File::RDWR)

had to be

f = File.new(filename, "rb")