Automated file upload in Ruby with Net::HTTP and MultipartBody

The Internet is full of great blog post tutorials on specific little programming and sysadmin tasks, but sometimes what’s already out there isn’t quite specific or helpful enough for what you’re trying to do. That was the case with something I had to do today, namely uploading a file with an HTTP POST request using Ruby.

I wanted my method of accomplishing this to be abstract enough that I didn’t have to manually craft my HTTP request with a text editor, but flexible enough that I was able to set the file to upload and its content-type separately. Here’s how I did it.

First, we install Ruby, if it’s not already on our system. Comprehensive instructions for doing so can be found at the top of this page – I usually install it using RVM. Next, we need to grab a gem I found for crafting multipart form HTTP POST request bodies.

gem install multipart_body

These bodies follow the HTTP header fields in an HTTP request and look something like this:

---------------------34134234536525625654745
Content-Disposition: form-data; name="PostParameter"

ValueOfPostParameter    
---------------------34134234536525625654745
Content-Disposition: form-data; name="File"; filename="file.txt"
Content-Type: text/plain

<file contents>
---------------------34134234536525625654745--

The long string of hyphens and numbers is called the boundary, and, once specified in a header field, it’s used to separate individual parameters/files from each other. The final boundary is followed by two hyphens. But you don’t need to worry about all that too much, because multipart_body takes care of all the formatting for you.

So now that we’ve grabbed the gem we needed, we’re ready to begin our upload script.

require 'net/http'
require 'multipart_body'

Net::HTTP is included in the Ruby standard library, so no need to download that.

The next step is to use MultipartBody to craft our POST body. In this example, our post will contain two parameters: the file we’re uploading and a normal POST parameter of the kind you usually see in non-multipart POST requests.

filepart = ""
File.open(file, 'rb') do |f|
	file_part = Part.new :name => 'FilePostParameter',
    					 :body => f,
                         :filename => file,
                         :content_type => 'text/plain'
end

param_part = Part.new 'APostParameter', 'ItsValue'
boundary = "---------------------------#{rand(10000000000000000000)}"
body = MultipartBody.new [param_part, file_part], boundary

Note: I’m unsure of whether the hyphens are entirely necessary in the header boundary definition, but including them doesn’t seem to hurt.

And now that that’s done we can create the actual request. We initialise the object…

site = Net::HTTP.new "http://example.com"
request = Net::HTTP::Post.new "/UploadFile.php"

…add the request body and headers…

request.body = body.to_s

request["Cookie"] = "Name=Value; OtherName=OtherValue"
request["Connection"] = "keep-alive"
request["Content-Type"] = "multipart/form-data; boundary=#{boundary}"
# (add more headers specifying your user agent, the referer (sic), and so on and so on)

…and finally submit!

response = site.request(request)

And now your perfectly innocent file is sitting somewhere on your friendly neighbourhood server, ready to be served to the world, and no-one had to type out an HTTP request.

Webmentions(?)